[
  {
    "path": ".browserslistrc",
    "content": "defaults"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # [devforth]\npatreon: devforth\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**Painterro version**\nE.g. v1.0.2\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots (Don't be lazy, PrnScr, Ctrl+V)\n\n**Browser**\n - e.g.  Google Chrome 11 on Windows \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Create a feature which makes Painterro better\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the feature**\nA clear and concise description of what you want to see and how it should work. Step by step, then we will adjust if needed\n\n**Possible analogs?**\nGimp, MSPaint, Inkscape, etc...\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n.idea\n\n# custom \nbuild/painterro*\nbuild/report*\n\nwp/"
  },
  {
    "path": ".npmignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n.idea\n\n# custom\nbuild/painterro-*\n\nwp/*\ndocs/*\n\n"
  },
  {
    "path": "CONTRIBUTORS.md",
    "content": "\n\nPainterro contributors\n============================================\n\n* **[Jesfery](https://github.com/Jesfery)**\n\n  * defaultTool param\n  \n* **[Ivan Borshchov](https://github.com/ivictbor)**  \n  * maintainer"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Ivan Borshchov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "<img src=\"https://raw.githubusercontent.com/devforth/painterro/master/res/painterro.png\" align=\"right\" style=\"padding:5px; width:70px\" /> \n\n**[live demo](https://tracklify.com/painterro_demo/)** | [npm](https://www.npmjs.com/package/painterro) | [GitHub](https://github.com/devforth/painterro)\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  ![npmvers](https://img.shields.io/npm/v/painterro) ![npmDown](https://img.shields.io/npm/dw/painterro?label=npm%20💾) ![totalNpm](https://img.shields.io/npm/dt/painterro?label=npm%20💾) ![ghdownloads](https://img.shields.io/github/downloads/devforth/painterro/total?label=github%20script%20💾) \n[![Ask AI](http://tluma.ai/badge)](http://tluma.ai/ask-ai/devforth/painterro)\n\nJavaScript painting plugin which allows editing images in a browser.\nCan be easily integrated into any website or webapp by calling simple initialization code.\n\n🆕 Check out our simple and game-changing opensource [Vue admin framework](https://adminforth.dev/) with a great look and extensibility!\n\n\n> 🙏🙏🙏 Scream for help to Ukraine \n> \n> 24 February 2022, Russia started bombing cities with peacefully civilized population in whole Ukraine. And has been doing it up to this day. Breaking all laws of war. Their bombs has been killing children and adults. This deserves Hague court.\n> - 🏠 If you are from Russia, please stop your government by any means including protests, don't trust local media, they are bribed by the government. They always was. I am sure you already feel lie by unexplainable crazy things in your country caused by world sanctions.\n> - 💣 Please spread the information about bombing of Ukraine in all social way you could. Russia treacherously broke into the territory of a sovereign state. Do not trust to anything from Russian media, most likely it will be bullshit\n> - 💼 If you have any kind of business cooperation with Russia, please block it now and keep most of money on your side, this is the only possible ethical decision\n> - ☢️ Ask your government to stop Russia from spreading invasion in any way. Russia is nuclear threat to the whole world. You think it is not possible? We thought that bombing of independent country with population of 44.13 million is also not possible.\n\n\nFeatures\n========\n\n- Paste image from clipboard with `Ctrl+V` (e.g. `PtnScr` screenshot), drag and drop it into widget, or load with file select dialog\n- Crop image by defined area\n- Paint primitives: line, rectangle, ellipse (alpha color can be used)\n- Brush – free drawing tool e.g. to implement finger-based signatures on tablet screens\n- Add text (you can use `Ctrl+B` - bold, `Ctrl+I` - italic, `Ctrl+U` - underlined, or just pase formatted HTML)\n- Rotate / resize, scale image\n- Pixelize some area to hide sensitive data\n- Draw arrows\n- Trash can tool to clear the canvas\n- Paint bucket tool for color fills\n- Add filters to images\n\n<img alt=\"Painterro gif preview\" src=\"https://raw.githubusercontent.com/devforth/painterro/master/docs/painterro_gif.gif\" \n style=\"box-shadow: 0 0 20px lightgrey; margin: 0 0 20px 0;\" /> \n\nUsed by\n=================\n\n  <table border=\"0\">\n  <tr>\n    <td align=\"center\">\n      <br>\n      <a href=\"https://nasa.github.io/openmct/\"><img src=\"https://nasa.github.io/openmct/static/res/images/logo-nasa.svg\" height='100px'/></a>\n      <br>\n      <a href=\"https://nasa.github.io/openmct/\">NASA Open MCT</a>\n      <br>\n      <br>\n    </td>\n    <td align=\"center\">\n      <br>\n      <a href=\"https://github.com/CiscoDevNet\"><img src=\"https://upload.wikimedia.org/wikipedia/commons/6/64/Cisco_logo.svg\" height='100px'/></a>\n      <br>\n      <a href=\"https://github.com/CiscoDevNet\">Cisco DevNet</a>\n      <br>\n      <br>\n    </td>\n    <td align=\"center\"> \n      <br>\n      <a href=\"https://tracklify.com\" ><img src=\"https://tracklify.com/static/img/header-logo.4916e646b063.svg\" height='100px' /></a>\n      <br>\n      <a href=\"https://tracklify.com\">Tracklify</a>\n      <br>\n      <br>\n    </td>\n    <td align=\"center\"> \n      <br>\n      <a href=\"https://fastdivs.com\" ><img src=\"https://fastdivs.com/static/svg/logo.c1c15aa6d612.svg\" height='100px' /></a>\n      <br>\n      <a href=\"https://fastdivs.com\">FastDivs</a>\n      <br>\n      <br>\n    </td>\n  </tr>\n  </table>\n  <br>\n\n\nAdvantages 💪\n=============\n\n- It is lightweight and minimalistic - written with vanilla JS, you don't need dependencies to use it\n- Designed to process images with minimal clicks, most actions support hot-keys\n- Could be easily integrated into SPA application (React, Vue, Angular)\n- Could be used in Electron and Cordova apps\n- Flexibale image saving - you provide your save handler, and get base64 data with any jpeg/png compression\n- Could be translated to any language \n\nOriginally Painterro was designed for quick screenshots processing: You make screenshot by pressing `PrtSc` button,\nthen open Painterro on your website, paste an image with `Ctrl+V`,\ncrop it to interested area, highlight something with line/rectangle tool and/or add some text \nto the image and save on server with custom save handler (e.g. simple `XHR` request to your backend).\nIn addition, you can use Painterro as image editor for any kind of raster images. Please try a [demo](https://tracklify.com/painterro_demo/).\nAlso painterro has [Wordpress Plugin](https://wordpress.org/plugins/painterro/).\n\nIf you want to see some feature in Painterro, please leave (or vote for) an issue [here](https://github.com/devforth/painterro/issues).\nThere is no promise that it will be implemented soon or ever, but it is interesting to know what features users want to have.\n\nUsefull hints and tweaks 😋:\n\n- [Painterro JS paint features review on HINTY](https://hinty.io/devforth/js-paint-plugin-for-your-website/)\n- [Dark theme for Painterro JS paint](https://hinty.io/devforth/painterro-dark-theme/)\n- [Round buttons for Painterro JS paint](https://hinty.io/devforth/how-to-round-the-painterro-buttons/)\n\n\nTable of contents\n=================\n\n  * [Table of contents](#table-of-contents)\n  * [Installation](#installation)\n    * [With npm](#with-npm)\n    * [By including script](#by-including-script)\n    * [Read after installation](#read-after-installation)\n  * [Supported hotkeys](#supported-hotkeys-)\n  * [Configuration](#configuration-)\n    * [Events](#events)\n    * [UI color scheme](#ui-color-scheme)\n    * [API](#api)\n    * [Translation](#translation-)\n  * [Saving image](#saving-image-)\n    * [Base64 saving](#base64-saving)\n    * [Binary saving](#binary-saving)\n    * [Saving to WYSIWYG](#saving-to-wysiwyg)\n    * [Format and quality](#format-and-quality)\n    * [Example: Open Painterro by Ctrl+V](#example-open-painterro-by-ctrlv)\n  * [Development](#development-)\n    * [Building painterro](#building-painterro)\n    * [Dev-server](#dev-server)\n    * [Regenerating icons font](#regenerating-icons-font)\n\n\nInstallation\n============\n\n\nWith npm\n--------\n\nIf you have npm-based project (e.g. SPA like React/Vue) you can run:\n```bash\nnpm install painterro --save\n```\nThen in your code\n\n```js\nimport Painterro from 'painterro'\n...\nPainterro().show()\n```\n\nBy including script\n-------------------\n\nYou can download latest `painterro-*.min.js` here https://github.com/devforth/painterro/releases/ \nor [build it by yourself](#building-painterro).\n\nThen insert `<script>` e.g to `<head>` section of your HTML file:\n```html\n<script src=\"/xxx/painterro-x.x.x.min.js\"></script>\n```\nThen in your code (`body` section, `onclick` handler, etc):\n```html\n<script>\n  Painterro().show()\n</script>\n```\nSee [jsfiddle.net example](https://jsfiddle.net/vanbrosh/wnebj4h7/)\n\n\nRead after installation\n-----------------------\n\nTo be able to save edited images on server or client see [Saving image](#saving-image). For configurations see [Configuration](#configuration)\n\nSupported hotkeys ⌨\n=================\n\n| | |\n|-|-|\n| `Ctrl + Z` | Cancel last operation |\n| `Ctrl + V` | Paste image from clipboard |\n| `Ctrl + C` | Copy selected aria to clipboard |\n| `Shift` when drawing **rect**/**ellipse** | Draw **square**/**circle** |\n| `Shift` when drawing **line** | draw at angles of `0`, `45`, `90`, `135` etc degrees | \n| `Alt` when using pipette | Hide zoom helper (colored grid) |\n| `Ctrl` + `Wheel mouse up/down` | Zoom image |\n| `Ctrl + S` | Save image |\n\nAlso some tools have own one-button hotkeys e.g. `C` - crop, you could see this shortcuts if you will hold mouse on toolbutton.\n\nConfiguration ⚙\n=============\n\nYou can pass parameters map to Painterro constructor:\n```js\nPainterro({\n  activeColor: '#00ff00', // default brush color is green\n  // ... other params here\n})\n```\n\n| Param | Description | Default |\n|-|-|-|\n| `id` | If provided, then Painterro will be placed to some holder on page with this `id`, in other case holder-element will be created (fullscreen with margins). Important note: If you are using your block and id option, please add `position`:`relative` or `absolute` or `fixed` on your container, default (`static`) will lead to positioning issues | `undefined` |\n|`activeColor`| Line/Text color that selected by default | `'#ff0000'` |\n|`activeColorAlpha` | Transparancy of `activeColor` from `0.0` to `1.0`, `0.0` = transparent | `1` |\n|`activeFillColor` | Fill color that selected by default | `'#000000'` |\n|`activeFillColorAlpha` | Transparancy of `activeColor` from `0.0` to `1.0` | `0` |\n|`defaultLineWidth` | Line width in `px` that selected by default | `5` |\n|`defaultPrimitiveShadowOn` | Enable Shadow for primitive tools (easier recognize them on a screenshots) | `true` |\n|`defaultEraserWidth` | Eraser width in `px` that selected by default | `5` |\n|`backgroundFillColor` | Default background color when image created/erased | `'#ffffff'` |\n|`backgroundFillColorAlpha`| Transparancy of `backgroundFillColor` from `0.0` to `1.0` | `1.0` |\n|`textStrokeColor`| Stroke color of text tool | `'#ffffff'` |\n|`textStrokeColorAlpha`| Stroke color of text tool | `1.0` |\n|`shadowScale`| Change text shadow blur for text and arrow | `1.0` |\n|`defaultFontSize` | Default font size in pixels | `24` |\n|`backplateImgUrl`| background for drawing, doesn't include in final image |`undefined` |\n|`defaultTextStrokeAndShadow` | Enables Stroke and Shadow for text tool by default (easier recognize text on screenshots) | `true` |\n|`defaultSize` | default image size, should be string in format `<width>x<height>` in pixel, e.g. `'200x100'`. If value is `'fill'`(default) than all container size will be used | `'fill'` |\n|`defaultTool` | Tool selected by default | `'select'` | \n|`hiddenTools` | List of tools that you wish to exclude from toolbar. Subset from this list `['crop', 'line', 'arrow', 'rect', 'ellipse', 'brush', 'text', 'rotate', 'resize',  'save', 'open', 'close', 'undo', 'redo', 'zoomin', 'zoomout', 'bucket']`, You can't hide default tool | `['redo']` |\n|`initText` | Display some centered text before painting (supports HTML). If null, no text will be shown | `null` |\n|`initTextColor` | Color of init text | `'#808080'` |\n|`initTextStyle` | Style of init text | `\"26px 'Open Sans', sans-serif\"` |\n|`pixelizePixelSize` | Default pixel size of pixelize tool. Can accept values - `x` - x pixels, `x%` - means percents of minimal area rectangle side | `20%` |\n|`pixelizeHideUserInput` | Don't allow users to enter pixel size In settings tools (and save in localstorage), this would allow developer to freeze pixel size by using params `pixelizePixelSize` to make sure users will not set low pixel sizes | `false` |\n|`availableLineWidths` | A list of the line width values that are available for selection in a drop down list e.g. `[1,2,4,8,16,64]`.  Otherwise an input field is used. | `undefined` |\n|`availableArrowLengths` | A list of the arrow sizes values that are available for selection in a drop down list e.g. `[10,20,30,40,50,60]`.  Otherwise an input field is used. | `undefined` |\n| `defaultArrowLength` | default arrow length | `15` |\n|`availableEraserWidths` | A list of the eraser width values that are available for selection in a drop down list e.g. `[1,2,4,8,16,64]`.  Otherwise an input field is used. | `undefined` |\n|`availableFontSizes` | A list of the font size values that are available for selection in a drop down list e.g. `[1,2,4,8,16,64]`.  Otherwise an input field is used. | `undefined` |\n|`toolbarPosition` | Whether to position the toolbar at the top or bottom. | `'bottom'` |\n|`fixMobilePageReloader` | By default painterro adds overflow-y: hidden to page body on mobile devices to prevent \"super smart\" feature lice Chrom's reload page. Unfortunately we can't prevent it by preventDefault. If your want to scroll page when painterro is open, set this to false | `true` |\n|`language` | Language of the widget. | `'en'` |\n|`how_to_paste_actions`| List of paste options that will be suggested on paste using some paste dialog e.g. `['extend_right', 'extend_down'] `. If there is only one option in list, then it will chosen automatically without dialog | `['replace_all', 'paste_over', 'extend_right', 'extend_down']` |\n|`replaceAllOnEmptyBackground`| Whether to select `replace_all` without dialog on first paste after painterro was just opened. So it will replaces background with image (will change dimensions to pasted image when background is empty) | `true` |\n|`hideByEsc`| If `true` then `ESC` press will hide widget | `false` | \n|`saveByEnter`| If `true` then `ENTER` press will do same as `Ctrl+S` | `false` | \n|`extraFonts`| By default Text tool supports only several [predefined](https://github.com/devforth/painterro/blob/master/js/text.js#L38) fonts due to compatibility considirations , but yousing this option you can add any fonts you want if you are sure they are available on your page/app | `['Roboto']` |\n|`toolbarHeightPx`| Height of toolbar in pixels | `40` | \n|`buttonSizePx`| Button for toolbar in pixels | `32` |\n|`bucketSensivity`| Bucket tool sensivity | `100` |\n|`customTools`| List of the custom tools which will appear at the left menu after default options. Custom tool includes three options : | `{name:string, callBack:function, iconUrl:dataURL string or URL}` |\n|`disableWheelZoom`| Disables the mousewheel zoom with ctrl | `false` |\n\n## Events\n\n| Param | Description | Accepted Arguments |\n|-|-|-|\n| `onBeforeClose` | Function that will be called when user closes painterro it, call `doClose` to confirm close | `hasUnsavedChaged: bool`, `doCloseCallback: function` |\n| `onClose` | If passed will be triggered when painterro closed by X button (use `onHide` for all close reasons) | `undefined` |\n| `onHide` | If passed will be triggered when painterro hides (by X button or save or any other way) | `undefined` |\n| `onChange` | Function that will be called if something will be changed (painted, erased, resized, etc) | `<exportable image>` | `undefined` |\n| `onUndo` | Function that will be called if user will undo (`Ctrl+Z`) | `{<current history state>}` |\n| `onRedo` | Function that will be called if user will redo (`Ctrl+Z`) | `{<current history state>}` |\n|`onImageFailedOpen`| Function that will be called if image can`t open | `undefined` |\n| `onImageLoaded` | Function that will be called if you passed image to `show` and when it was loaded | `undefined` | \n| `saveHandler` | Function that will be called when user presses Save (or `Ctrl+S`), Call `doneCallback` to reflect in painterro that image was saved | `{<exportable image>}`, `doneCallback : function` |\n\n\nEvents accepted arguments:\n\n* `{<exportable image>}` is object:\n\n```\n{ \n  image: {\n   asBlob: ƒ asBlob(type, quality) // returns blob\n   asDataURL: ƒ asDataURL(type, quality) // returns e.g. \"data:image/jpeg;base64,/9j/4AAQS....\"\n   suggestedFileName: ƒ suggestedFileName(type) // returns string\n   hasAlphaChannel(): ƒ suggestedFileName() // returns true or false\n   getOriginalMimeType: ƒ getOriginalMimeType() // e.g. image/jpeg;\n   getWidth: ƒ getWidth() // integer\n   getHeight: ƒ getHeight() // integer\n  }\n  operationsDone: int // integer\n} \n```\n\n* `{<current history state>}` is object:\n\n```\n{\n  prev: {<current history state>} or undefined\n  next: {<current history state>} or undefined\n  prevCount: int\n  sizeh: int\n  sizew: int\n}\n```\n\n\n\n\nUI color scheme\n---------------\n\nNext group of params used to configure painterro user interface in simple \"JS way\". \nThey should be placed under `colorScheme` group, for example:\n```js\nPainterro({\n  colorScheme: {\n    main: '#fdf6b8', // make panels light-yellow\n    control: '#FECF67' // change controls color\n  }\n}).show()\n```\n\n| Param | Description | Default |\n|-|-|-|\n|`main` | Color of panels, take most of UI space | `'#fff'` |\n|`control` | Color of controls background (e.g. button background) | `'#fff'` |\n|`controlShadow` | Color controls box shadow | `'0px 0px 3px 1px #bbb'` |\n|`controlContent` | Content of controls (e.g. button text) | `'#000000'` |\n|`activeControl` | Color for control when it active (e.g. button pressed) | `'#7485B1'` |\n|`activeControlContent` | Color for activated control content | `main` |\n|`inputBorderColor` | You can add border to inputs, by default color is same as `main` so borders will not be seen | `main` |\n|`inputBackground` | Background of inputs | `'#ffffff'` |\n|`inputShadow` | shadow of input | `'inset 0 0 4px 1px #ccc'` |\n|`inputText` | Color of text in input | `activeControl` |\n|`backgroundColor`| Background color of component area which left outside of image due to it size/ratio | `'#999999'` |\n|`dragOverBarColor`| Color of bar when dropping file to painterro | `'#899dff'` |\n|`hoverControl`| Controls color when mouse hovered | `control` |\n|`hoverControlContent`| Controls background color when mouse hovered | `'#1a3d67'` |\n|`toolControlNameColor`| Color of toolbar labels that prepend controls | `rgba(255,255,255,0.7)` |\n\n> NOTE: all these params are defined only for simplicity, you are free to redefine them in your cascade style files (we don't use importants and so on, so all props should be easily editable). This mettod is recommended for experts - because you can use your CSS preprocessor variables and adopt Painterro for your design. Example usecase is different color of shadows for a buttons with `::after`/`::before`\n\nAPI\n-------\n\n**.show([optional]openImage, [optional]initialMimeType)** - Shows painterro instance. `openImage` can have next values:\n\n* `false` - will open image that already was drawn before last close\n* `some string value`, e.g. `'http://placehold.it/120x120&text=image1'` - will try to load image from url\n* all another values - will clear content before open\n\n`initialMimeType` could be used to help painterro understand which file do you try to load there. Could be useful if you want to save the original mime and file opened explicitly (painterro open tool or dnd/ctrl+v handlers get it automatically)\n\n**.hide()** - hide instance\n\n**.setColor(options)** - sets the color of the chosen tool , or changes initial value of color. `options` should be array with two values, `[target,colorWidgetState]`\n\navailable values for `target`:\n\n|`line`|, |`bg`| \n\n`colorWidgetState` - object with requred properties :\n\n* `palleteColor` - color string . Just indicates which color will be shown on the collor pallete.\n* `alpha` - number in range from 0 to 1. The same but for alpha channel. \n* `alphaColor` - color string. Color with alpha, which will be using for drawing element.\n\n>NOTE: `paleteColor` and `alpha` is using only for displaing right values in color picker widget, this two options don't effect on color which will be used for drawing elements.\n\n**.setLineWidth()** - set line width for chosen tool. \n\n**.setArrowLength()** - set width for chosen arrow.\n\n**.setEraserWidth()** - set width of eraser\n\n**.setShadowOn()** - set shadowfor line elements or arrow element. It takes boolean value. \n\n**.save()** - call save (same save as on buttons bar). Can be used if save button is hidden (`hiddenTools: ['save']`)\n\n**.doScale({ width, height, scale })** - scale the image and resize area.\n\nScale to match the width and scale height proportinally (e.g. 50x32 will become 100->64):\n\n```\n.doScale({width: 100})\n```\n\nScale to fill width and height (e.g. 50x32 will become 11->15):\n\n```\n.doScale({width: 11, height: 15})\n```\n\nScale x2  (e.g. 11x12 will become 22->24):\n\n```\n.doScale({ scale: 2 })\n```\n\n**setZoom(zoomPercentage)** - sets the current zoom percentage\n\n\nExample:\n\n```js\nvar p = Painterro()\np.show()\n```\n\nTranslation 📙\n--------------\n\nWant to translate Painterro into your language?\n\nIf you need one of languages in table below, just pass pass `language` parameter, for example:\n \n```js\nPainterro({\n  language: 'es'  // Spanish\n}).show()\n```\nTranslated languages:\n\n| `language` param | Name |\n|-|-|\n| `ca` | Catalan |\n| `de` | German |\n| `en` | English |\n| `es` | Spanish |\n| `fa` | Iran-Farsi (Persian (Ir-Fa) |\n| `fr` | French |\n| `ja` | Japanese |\n| `pl` | Polish |\n| `pt-PT` | European Portuguese |\n| `pt-BR` | Brazilian  Portuguese |\n| `ru` | Russian |\n| `nl` | Dutch |\n\n\n\nIf you want to add another language, then:\n\n1. fork to your GitHub with button on top.\n2. Create empty file in folder langs [<LANG_ISO_CODE>.lang.js] for your translation. `LANG_ISO_CODE` should follow [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)\n3. Copy content from [langs/en.lang.js] to a new file\n4. Then translate all `'Strings'`\n5. Add reference in [js/translation.js] inside of your repo. \n5. After that create pull-request, or just open [issue](https://github.com/devforth/painterro/issues) if you don't know how to create a PR.\n\n🤔 Found a bug in some word for your language? Feel free to edit on GitHub directly and suggest a fix.\n \nIf you want to translate or change strings without contributing you can do this by passing \n`translation` parameter, for example:\n\n```js\nPainterro({\n  translation: {\n    name: 'ua',\n    strings: {\n      apply: 'Застосувати'    \n      // other strings\n    }\n  }\n}).show()\n```\nFor all strings that should be translated, see [langs/en.lang.js] \n    \n\nSaving image 💾\n===============\n\nYou should provide your own save handler, that will post/update image on server or will pass image to other\nfrontend components. In this section we will provide several backend examples on python Flask (easiest web server for python). Anyway if you will face any python exception you can use super-helpfull [fixexception.com](https://fixexception.com/) service to fix any issue you will face 💪.\n\nBinary saving\n-------------\n\nYou can post data with binary `multipart/form-data` request which is the most efficient way to pass data to backend. Example uses raw `XMLHttpRequest`. Of course,\n you can use `fetch`, `jQuery`, etc insead.\n\n```js\nvar ptro = Painterro({\n  saveHandler: function (image, done) {\n    var formData = new FormData();\n    formData.append('image', image.asBlob());\n    // you can also pass suggested filename \n    // formData.append('image', image.asBlob(), image.suggestedFileName());\n    var xhr = new XMLHttpRequest();\n    xhr.open('POST', 'http://127.0.0.1:5000/save-as-binary/', true);\n    xhr.onload = xhr.onerror = function () {\n      // after saving is done, call done callback\n      done(true); //done(true) will hide painterro, done(false) will leave opened\n    };\n    xhr.send(formData);\n  }\n})\nptro.show();\n```\nHere is python flask backend example (of course same can be implemented using any technology):\n```python\n@app.route(\"/save-as-binary/\", methods=['POST'])\ndef binary_saver():\n    filename = '{:10d}.png'.format(int(time()))  # generate some filename\n    filepath = os.path.join(get_tmp_dir(), filename)\n    request.files['image'].save(filepath)\n    return jsonify({})\n```\n\nSee full example in `example` directory. You can run it used python3 with installed `Flask` (`pip install flask`).\n\nBase64 saving\n-------------\n\n\nYou can also same image by posting `base64` string via plain POST json call.\nPlease note that base64 encoding is less efficient then binary data, for example some `1920 x 1080` image took `402398` bytes for `base64` upload.\nThe same image took `301949` bytes with `multipart/form-data`.\n\n\n```js\nvar ptro = Painterro({\n    saveHandler: function (image, done) {\n      // of course, instead of raw XHR you can use fetch, jQuery, etc\n      var xhr = new XMLHttpRequest();\n      xhr.open(\"POST\", \"http://127.0.0.1:5000/save-as-base64/\");\n      xhr.setRequestHeader(\"Content-Type\", \"application/json\");\n      xhr.send(JSON.stringify({\n        image: image.asDataURL()\n      }));\n      xhr.onload = function (e) {\n        // after saving is done, call done callback\n        done(true); //done(true) will hide painterro, done(false) will leave opened\n      }\n    },\n    activeColor: '#00b400'  // change active color to green\n});\nptro.show();\n```\nBackend should convert `base64` to binary and save file:\n```python\n@app.route(\"/save-as-base64/\", methods=['POST'])\ndef base64_saver():\n    filename = '{:10d}.png'.format(int(time()))  # generate some filename\n    filepath = os.path.join(get_tmp_dir(), filename)\n    with open(filepath, \"wb\") as fh:\n        base64_data = request.json['image'].replace('data:image/png;base64,', '')\n        fh.write(base64.b64decode(base64_data))\n    return jsonify({})\n```\n\n\nSaving to WYSIWYG\n-----------------\n\nYou can just insert image as data url to any WYSIWYG editor, e.g. TinyMCE:\n```js\n    tinymce.init({ selector:'textarea', });\n    var ptro = Painterro({\n      saveHandler: function (image, done) {\n        tinymce.activeEditor.execCommand('mceInsertContent', false, '<img src=\"' + image.asDataURL() + '\" />');\n        // after saving is done, call done callback\n        done(true); //done(true) will hide painterro, done(false) will leave opened\n      }\n    })\n```\n\nFormat and quality\n------------------\n\nWhen you call `image.asDataURL()` or `image.asBlob()`, you can also specify image mime type (format), e.g.\n`image.asDataURL('image/jpeg')`. \n\nDefault type is mimetype used by image which was loaded into Painterro, or \"image/png\" if image was created from scratch.\n\nIf type is `image/jpeg` or `image/webp`, you can also define image quality from `0.0` to `1.0`, default is `0.92`,\nexample: `image.asDataURL('image/jpeg', 0.5)`\n\n\nSave to jpeg or png depending on whether there is an alpha channel\n------------------\n\nAn efficient way to save an image might be implmented by checking whether image has some alpha pixels:\n* If yes - we need to serve image in less efficient png format\n* Otherwise lets just use JPEG\nThis is very simple with next:\n\n```\nvar ptro = Painterro({\n  saveHandler: function (image, done) {\n\n    image.asBlob(image.hasAlphaChannel() ? 'image/png' : 'image/jpeg');\n    // upload blob\n  }\n})\n```\n\nExample: Open Painterro by Ctrl+V\n-----------------\n\n```js\ndocument.onpaste = (event) => {\n  const { items } = event.clipboardData || event.originalEvent.clipboardData;\n  Array.from(items).forEach((item) => {\n    if (item.kind === 'file') {\n      if (!window.painterroOpenedInstance) {\n        // if painterro already opened - it will handle onpaste\n        const blob = item.getAsFile();\n        const reader = new FileReader();\n        reader.onload = (readerEvent) => {\n            window.painterroOpenedInstance = Painterro({\n              onHide: () => {\n                window.painterroOpenedInstance = undefined;\n              },\n              saveHandler: (image, done) => {\n                console.log('Save it here', image.asDataURL());  // you could provide your save handler\n                done(true);\n              },\n            }).show(readerEvent.target.result, item.type);\n        };\n        reader.readAsDataURL(blob);\n      }\n    }\n  });\n};\n```\n\nIf you face any painterro errors (exceptions), please reffer to [Painterro page on FixJSError](https://fixjserror.com/package/painterro/)\n\nDevelopment 🔨\n==============\n\nLatest supported NodeJS version is 16, use nvm to switch to it:\n\n```\nnvm install 16\nnvm use 16\n```\n\nCode written on ES6 which transplited by Babel and packed (minified) to a single file using webpack. All configs are inside so all you have to do after pulling repo is installing node modules:\n\n```bash\ncd painterro\nnpm ci\n```\n\nBuilding painterro\n------------------\n\n```bash\nnpm run build\n```\n\nResult file for `<script>` import is `build/painterro.min.js`.\n\nActually, above command produces 4 versions of library:\n\n- `build/painterro-x.y.z.min.js`, `build/painterro.min.js` the same files but with different filenames (with and without versiontag) - this is `var` version which will be loaded as global variable (`var painterro = <Library class>`) when you will import it as `<script src='painterro.min.js' />` tag. So this is for `script` tag only.   \n- `build/painterro.commonjs2.js` - this version sutable for js `require/import`. That's why it is used as entry point in `package.json` file - if you are using webpack or other tool that can handle `require/import` of `commonjs2` libraries then you can do `npm install painterro`, and do `import painterro` and it will use `commonjs2` version.\n- `build/painterro.amd.js` and `build/painterro.umd.js` - these both are same as above but for `AMD` and `UMD` importers respectivly.\n\n\nDev-server\n----------\n\nTo start hot-reload dev server (for reloading code \"on the fly\"):\n```bash\nnpm run dev\n```\nThen open http://localhost:3000 with demo page\n\n\nEditing source on the fly for painterro imported from side webpack app (e.g. your project SPA)\n------------------------------------------\n\n1. If your side app uses 'eslint' it, most likely side app will need eslint-plugin-import:\n\n```\nnpm i eslint-plugin-import\n```\n\n2. Since compiled painterro commonjs2 file already linted and minimized you need to exclude it from linting:\n\nAdd to package.json of your side app:\n```\n  \"eslintIgnore\": [\n    \"/home/ivan/devforth/painterro/build/painterro2.commonjs.js\"\n  ],\n```\nwhere `/home/ivan/devforth/painterro` is a folder with Painterro sources\n\n3. Replace\n\n```\nimport Painterro from 'painterro';\n```\nwith\n \n```\nimport Painterro from '/home/ivan/devforth/painterro/build/painterro.commonjs2.js';\n```\n\n4. Go to painterro source folder and run:\n\n```\nwatch npm run build\n```\n\nRegenerating icons font\n-----------------------\n\nIf you need add/edit icons in `res` folder, please after editing run:\n\n```bash\nnpm run buildfont\n```\n\nFor font generation we use method described here: [How to generate a webfont (automated setup)](https://hinty.io/brucehardywald/how-to-generate-a-webfont-automated-setup/)\n\n\n\nContributing \n------------\n\nPull-requests are welcome.\n\nIf you want to say thank us [Patreon is here](https://www.patreon.com/devforth)\n\n\n[npm]: https://img.shields.io/npm/v/painterro.svg\n[npm-url]: https://npmjs.com/package/painterro\n\n[deps]: https://david-dm.org/webpack/painterro.svg\n[deps-url]: https://david-dm.org/webpack/painterro\n\nSupported by [DevForth](https://devforth.io) - Best quality, rapid,  modern tech development services\n"
  },
  {
    "path": "Release.md",
    "content": "\n\nnpm login\n\nTocken from github:\n\nGH_PASS=`cat ~/.ghtoken`\n\nPassword from WP:\nWP_PASSWORD=`cat ~/.wppassword`"
  },
  {
    "path": "build/contained.html",
    "content": "<html>\n  <head>\n    <title>Painterro demo</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <style>\n      html,body,#wrapper {\n        height:100%;\n        margin:0;\n        padding:0;\n        background-color: lightgray;\n      }\n\n      #conatiner {\n        position: relative;\n        height: 300px;\n        width: 300px;\n      }\n\n    </style>\n  </head>\n<body>\n\n<div id=\"wrapper\" style=\"height: 100%;\">\n  <div style=\"height: 300px;  background-color: antiquewhite\"></div>\n  <div id=\"conatiner\"></div>\n</div>\n\n<!--<script src=\"https://github.com/ivictbor/painterro/releases/download/0.1.7/painterro-0.1.7.min.js\"></script>-->\n<script src=\"/painterro.min.js\"></script>\n\n<script>\n\n  Painterro({\n    id: 'conatiner',\n    backgroundFillColor: '#eee',\n    backplateImg: 'https://images.pexels.com/photos/3653762/pexels-photo-3653762.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',\n    colorScheme: {\n      main: '#faf',\n      control: '#d5d',\n      activeControl: '#572257',\n      inputBackground: '#cf99ca',\n    }\n  }).show();\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "build/index.html",
    "content": "<html>\n  <head>\n    <title>Painterro demo</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <style>\n      html,body,#wrapper {\n        height:100%;\n        margin:0;\n        padding:0;\n        background-color: white;\n      }\n\n      #conatiner {\n        position: absolute;\n        top: 50px;\n        bottom: 50px;\n        left: 20px;\n        right: 20px;\n      }\n\n#holder {\n  display: flex;\n}\n\n    </style>\n  </head>\n<body>\n  <div id=\"app\">\n  </div>\n\n  <div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div>\n  <div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div><div>asf</div>\n</div>\n\n<!--<script src=\"https://github.com/ivictbor/painterro/releases/download/0.1.7/painterro-0.1.7.min.js\"></script>-->\n<script src=\"painterro.min.js\"></script>\n\n<script>\n\nwindow.p =  Painterro({\n            language: 'uk',\n            backgroundFillColorAlpha: 0,\n            hideByEsc: true,\n            colorScheme: {\n                main: '#8B817A',\n                control: '#1A1A1A',\n                controlContent: '#fff',\n                controlShadow: 'none',\n                activeControl: '#6F6762',\n                activeControlContent: '#fff',\n                hoverControl: '#6F6762',\n                hoverControlContent: '#fff',\n            },\n         \n            //defaultTool : 'brush',\n            //how_to_paste_actions: ['extend_right'],\n            /*hiddenTools: [\n                'select',\n                'crop',\n                'pixelize',\n                'line',\n                'arrow',\n                'rect',\n                'ellipse',\n                // 'brush',\n                // 'eraser',\n                'text',\n                'rotate',\n                'resize',\n                // 'save',\n                'open',\n                // 'close',\n                // 'undo',\n                'redo',\n                // 'zoomin',\n                // 'zoomout',\n                'bucket',\n                'clear',\n                'settings',\n            ],\n            */\n           saveHandler: this._onPainterroSave,\n        });\n  window.p.show()\n  const ctx = window.p.ctx; \n\n  // ctx.beginPath();\n  // ctx.moveTo(0,0);\n  // ctx.lineTo(300,150);\n  // ctx.strokeStyle=\"#FF0000\";\n  // ctx.stroke(); \n\n\n// document.onpaste = (event) => {\n//   const { items } = event.clipboardData || event.originalEvent.clipboardData;\n//   Array.from(items).forEach((item) => {\n//     if (item.kind === 'file') {\n//       if (!window.painterroOpenedInstance) {\n//         // if painterro already opened - it will handle onpaste\n//         const blob = item.getAsFile();\n//         const reader = new FileReader();\n//         reader.onload = (readerEvent) => {\n//             window.painterroOpenedInstance = Painterro({\n//               initText: 'Press <b>PrtScr</b>, <b>Ctrl+V</b> to paste screenshot.<br>Press <b>Ctrl+S</b> to save', // todo: different for os-es\n//               onHide: () => {\n//                 window.painterroOpenedInstance = undefined;\n//               },\n//               saveHandler: (image, done) => {\n//                 console.log('Save it here', image.asDataURL());  // you could provide your save handler\n//                 done(true);\n//               },\n//             }).show(readerEvent.target.result, item.type);\n//         };\n//         reader.readAsDataURL(blob);\n//       }\n//     }\n//   });\n// };\n\nconst el = document.getElementById('app')\nel.addEventListener('changeActiveTool',(e)=>{console.log('activeToolHasBeenCHanged',e)})\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "css/bar-styles.css",
    "content": ".color-diwget-btn {\n    height: 32px;\n    width: 32px;\n    cursor: pointer;\n    z-index: 1;\n}\n\n.color-diwget-btn-substrate {\n    width: 32px;\n}\n\nselect.ptro-input[data-id='fontName'] {\n    width: 45px;\n}\n\n.ptro-bar .ptro-tool-ctl-name {\n    padding: 0 2px 0 0;\n    font-family: \"Open Sans\", sans-serif;\n    line-height: 22px;\n}\n\n.ptro-bar .ptro-tool-ctl-name {\n    margin-left: 5px;\n    border-top-left-radius: 10px;\n    border-bottom-left-radius: 10px;\n    padding-left: 3px;\n    padding-top: 4px;\n    padding-bottom: 4px;\n}\n\n.ptro-info {\n    font-family: \"Open Sans\", sans-serif;\n    font-size: 10px;\n    padding: 4px;\n    margin-left: auto;\n    word-break: keep-all;\n    white-space: normal;\n    text-align: right;\n}\n\n.ptro-info > span {\n    opacity: 0.5;\n}\n\n@media screen and (max-width: 768px) {\n    .ptro-bar > div {\n        white-space: nowrap;\n    }\n    span.ptro-bar-right {\n        float: none;\n    }\n    span.ptro-info {\n        display: none;\n    }\n    \n}\n\n.ptro-bar .ptro-input {\n    height: 32px;\n    line-height: 32px;\n    font-family: \"Open Sans\", sans-serif;\n    font-size: 16px;\n    position: relative;\n    padding-left: 2px;\n    padding-right: 0;\n}\n\n.ptro-bar .ptro-input[type=\"number\"] {\n    width: 42px;\n}\n\n.ptro-bar .ptro-named-btn p {\n    margin: 0;\n}\n\n.ptro-bar {\n    bottom: 0;\n    position: absolute;\n    width: 100%;\n    font-size: 16px;\n    line-height: normal;\n}\n\n.ptro-bar > div {\n    position: relative;\n}\n\n.ptro-bar > div::-webkit-scrollbar {\n    height: 2px;\n}\n    \n/* Track */\n.ptro-bar > div::-webkit-scrollbar-track {\n    background: #f1f1f1;\n}\n    \n/* Handle */\n.ptro-bar > div::-webkit-scrollbar-thumb {\n    background: #888;\n}\n    \n/* Handle on hover */\n.ptro-bar > div::-webkit-scrollbar-thumb:hover {\n    background: #555;\n}\n\n.ptro-bar .ptro-icon-btn {\n}\n\nbutton.ptro-icon-right:first-of-type {\n    margin-right: 4px;\n}\n\nbutton.ptro-input[data-value=\"false\"],button.ptro-input[data-value=\"true\"] {\n    width: 28px;\n    height: 28px;\n    border: 0;\n    background: transparent;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    padding-left: 0px;\n    cursor: pointer;\n    outline: 0;\n}\n\nbutton.ptro-input[data-value=\"true\"]::after {\n    content: '✔';\n    font-size: 20px;\n    line-height: 12px;\n    width: 12px;\n    height: 12px;\n    border: 0;\n    /* background: rgba(0,0,0,0.5); */\n    display: inline-block;\n}\n\n@-webkit-keyframes ptro-spin {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@keyframes ptro-spin {\n  0% {\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n\n.ptro-spinning {\n    -webkit-animation: ptro-spin 0.5s infinite steps(9);\n    animation: ptro-spin 0.8s infinite steps(9);\n    display: inline-block;\n    text-rendering: auto;\n    -webkit-font-smoothing: antialiased;\n}\n\n#container-bar {\n    display: block;\n}"
  },
  {
    "path": "css/icons/ptroiconfont.css",
    "content": "@font-face {\n\tfont-family: \"ptroiconfont\";\n\tsrc: url(\"ptroiconfont.woff?9d16276326db52747d3405a7ca0e1306\") format(\"woff\"),\nurl(\"ptroiconfont.ttf?9d16276326db52747d3405a7ca0e1306\") format(\"truetype\");\n\tfont-weight: normal;\n    font-style: normal;\n}\n\n.ptro-icon {\n}\n\n.ptro-icon:before {\n\tfont-family: ptroiconfont !important;\n\tfont-style: normal !important;\n\tfont-weight: normal !important;\n\tfont-variant: normal !important;\n\ttext-transform: none !important;\n\tspeak: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.ptro-icon-apply:before {\n\tcontent: \"\\f101\";\n}\n.ptro-icon-arrow:before {\n\tcontent: \"\\f102\";\n}\n.ptro-icon-brush:before {\n\tcontent: \"\\f103\";\n}\n.ptro-icon-bucket:before {\n\tcontent: \"\\f104\";\n}\n.ptro-icon-clear:before {\n\tcontent: \"\\f105\";\n}\n.ptro-icon-close:before {\n\tcontent: \"\\f106\";\n}\n.ptro-icon-crop:before {\n\tcontent: \"\\f107\";\n}\n.ptro-icon-ellipse:before {\n\tcontent: \"\\f108\";\n}\n.ptro-icon-eraser:before {\n\tcontent: \"\\f109\";\n}\n.ptro-icon-filters:before {\n\tcontent: \"\\f10a\";\n}\n.ptro-icon-line:before {\n\tcontent: \"\\f10b\";\n}\n.ptro-icon-linked:before {\n\tcontent: \"\\f10c\";\n}\n.ptro-icon-loading:before {\n\tcontent: \"\\f10d\";\n}\n.ptro-icon-mirror:before {\n\tcontent: \"\\f10e\";\n}\n.ptro-icon-open:before {\n\tcontent: \"\\f10f\";\n}\n.ptro-icon-painterro0:before {\n\tcontent: \"\\f110\";\n}\n.ptro-icon-paste_extend_down:before {\n\tcontent: \"\\f111\";\n}\n.ptro-icon-paste_extend_left:before {\n\tcontent: \"\\f112\";\n}\n.ptro-icon-paste_extend_right:before {\n\tcontent: \"\\f113\";\n}\n.ptro-icon-paste_extend_top:before {\n\tcontent: \"\\f114\";\n}\n.ptro-icon-paste_fit:before {\n\tcontent: \"\\f115\";\n}\n.ptro-icon-paste_over:before {\n\tcontent: \"\\f116\";\n}\n.ptro-icon-pipette:before {\n\tcontent: \"\\f117\";\n}\n.ptro-icon-pixelize:before {\n\tcontent: \"\\f118\";\n}\n.ptro-icon-rect:before {\n\tcontent: \"\\f119\";\n}\n.ptro-icon-redo:before {\n\tcontent: \"\\f11a\";\n}\n.ptro-icon-resize:before {\n\tcontent: \"\\f11b\";\n}\n.ptro-icon-rotate:before {\n\tcontent: \"\\f11c\";\n}\n.ptro-icon-save:before {\n\tcontent: \"\\f11d\";\n}\n.ptro-icon-select:before {\n\tcontent: \"\\f11e\";\n}\n.ptro-icon-settings:before {\n\tcontent: \"\\f11f\";\n}\n.ptro-icon-text:before {\n\tcontent: \"\\f120\";\n}\n.ptro-icon-undo:before {\n\tcontent: \"\\f121\";\n}\n.ptro-icon-unlinked:before {\n\tcontent: \"\\f122\";\n}\n.ptro-icon-zoomin:before {\n\tcontent: \"\\f123\";\n}\n.ptro-icon-zoomout:before {\n\tcontent: \"\\f124\";\n}\n"
  },
  {
    "path": "css/icons/ptroiconfont.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>ptroiconfont</title>\n\t<style>\n\t\tbody {\n\t\t\tfont-family: sans-serif;\n\t\t\tmargin: 0;\n\t\t\tpadding: 10px 20px;\n\t\t}\n\n\t\t.preview {\n\t\t\tline-height: 2em;\n\t\t}\n\n\t\t.preview__icon {\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 32px;\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t.ptro-icon {\n\t\t\tdisplay: inline-block;\n\t\t\tfont-size: 16px;\n\t\t}\n\n\t\t@font-face {\n\tfont-family: \"ptroiconfont\";\n\tsrc: url(\"ptroiconfont.woff?ebe1a4332c0507d6283582e66d937ed1\") format(\"woff\"),\nurl(\"ptroiconfont.ttf?ebe1a4332c0507d6283582e66d937ed1\") format(\"truetype\");\n\tfont-weight: normal;\n    font-style: normal;\n}\n\n.ptro-icon {\n}\n\n.ptro-icon:before {\n\tfont-family: ptroiconfont !important;\n\tfont-style: normal !important;\n\tfont-weight: normal !important;\n\tfont-variant: normal !important;\n\ttext-transform: none !important;\n\tspeak: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.ptro-icon-apply:before {\n\tcontent: \"\\f101\";\n}\n.ptro-icon-arrow:before {\n\tcontent: \"\\f102\";\n}\n.ptro-icon-brush:before {\n\tcontent: \"\\f103\";\n}\n.ptro-icon-bucket:before {\n\tcontent: \"\\f104\";\n}\n.ptro-icon-clear:before {\n\tcontent: \"\\f105\";\n}\n.ptro-icon-close:before {\n\tcontent: \"\\f106\";\n}\n.ptro-icon-crop:before {\n\tcontent: \"\\f107\";\n}\n.ptro-icon-ellipse:before {\n\tcontent: \"\\f108\";\n}\n.ptro-icon-eraser:before {\n\tcontent: \"\\f109\";\n}\n.ptro-icon-filters:before {\n\tcontent: \"\\f10a\";\n}\n.ptro-icon-line:before {\n\tcontent: \"\\f10b\";\n}\n.ptro-icon-linked:before {\n\tcontent: \"\\f10c\";\n}\n.ptro-icon-loading:before {\n\tcontent: \"\\f10d\";\n}\n.ptro-icon-mirror:before {\n\tcontent: \"\\f10e\";\n}\n.ptro-icon-open:before {\n\tcontent: \"\\f10f\";\n}\n.ptro-icon-painterro0:before {\n\tcontent: \"\\f110\";\n}\n.ptro-icon-paste_extend_down:before {\n\tcontent: \"\\f111\";\n}\n.ptro-icon-paste_extend_left:before {\n\tcontent: \"\\f112\";\n}\n.ptro-icon-paste_extend_right:before {\n\tcontent: \"\\f113\";\n}\n.ptro-icon-paste_extend_top:before {\n\tcontent: \"\\f114\";\n}\n.ptro-icon-paste_fit:before {\n\tcontent: \"\\f115\";\n}\n.ptro-icon-paste_over:before {\n\tcontent: \"\\f116\";\n}\n.ptro-icon-pipette:before {\n\tcontent: \"\\f117\";\n}\n.ptro-icon-pixelize:before {\n\tcontent: \"\\f118\";\n}\n.ptro-icon-rect:before {\n\tcontent: \"\\f119\";\n}\n.ptro-icon-redo:before {\n\tcontent: \"\\f11a\";\n}\n.ptro-icon-resize:before {\n\tcontent: \"\\f11b\";\n}\n.ptro-icon-rotate:before {\n\tcontent: \"\\f11c\";\n}\n.ptro-icon-save:before {\n\tcontent: \"\\f11d\";\n}\n.ptro-icon-select:before {\n\tcontent: \"\\f11e\";\n}\n.ptro-icon-settings:before {\n\tcontent: \"\\f11f\";\n}\n.ptro-icon-text:before {\n\tcontent: \"\\f120\";\n}\n.ptro-icon-undo:before {\n\tcontent: \"\\f121\";\n}\n.ptro-icon-unlinked:before {\n\tcontent: \"\\f122\";\n}\n.ptro-icon-zoomin:before {\n\tcontent: \"\\f123\";\n}\n.ptro-icon-zoomout:before {\n\tcontent: \"\\f124\";\n}\n\n\t</style>\n</head>\n<body>\n\t<h1>ptroiconfont</h1>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-apply\"></span>\n\t\t</span>\n\t\t<span>apply</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-arrow\"></span>\n\t\t</span>\n\t\t<span>arrow</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-brush\"></span>\n\t\t</span>\n\t\t<span>brush</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-bucket\"></span>\n\t\t</span>\n\t\t<span>bucket</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-clear\"></span>\n\t\t</span>\n\t\t<span>clear</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-close\"></span>\n\t\t</span>\n\t\t<span>close</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-crop\"></span>\n\t\t</span>\n\t\t<span>crop</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-ellipse\"></span>\n\t\t</span>\n\t\t<span>ellipse</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-eraser\"></span>\n\t\t</span>\n\t\t<span>eraser</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-filters\"></span>\n\t\t</span>\n\t\t<span>filters</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-line\"></span>\n\t\t</span>\n\t\t<span>line</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-linked\"></span>\n\t\t</span>\n\t\t<span>linked</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-loading\"></span>\n\t\t</span>\n\t\t<span>loading</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-mirror\"></span>\n\t\t</span>\n\t\t<span>mirror</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-open\"></span>\n\t\t</span>\n\t\t<span>open</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-painterro0\"></span>\n\t\t</span>\n\t\t<span>painterro0</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_extend_down\"></span>\n\t\t</span>\n\t\t<span>paste_extend_down</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_extend_left\"></span>\n\t\t</span>\n\t\t<span>paste_extend_left</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_extend_right\"></span>\n\t\t</span>\n\t\t<span>paste_extend_right</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_extend_top\"></span>\n\t\t</span>\n\t\t<span>paste_extend_top</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_fit\"></span>\n\t\t</span>\n\t\t<span>paste_fit</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-paste_over\"></span>\n\t\t</span>\n\t\t<span>paste_over</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-pipette\"></span>\n\t\t</span>\n\t\t<span>pipette</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-pixelize\"></span>\n\t\t</span>\n\t\t<span>pixelize</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-rect\"></span>\n\t\t</span>\n\t\t<span>rect</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-redo\"></span>\n\t\t</span>\n\t\t<span>redo</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-resize\"></span>\n\t\t</span>\n\t\t<span>resize</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-rotate\"></span>\n\t\t</span>\n\t\t<span>rotate</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-save\"></span>\n\t\t</span>\n\t\t<span>save</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-select\"></span>\n\t\t</span>\n\t\t<span>select</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-settings\"></span>\n\t\t</span>\n\t\t<span>settings</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-text\"></span>\n\t\t</span>\n\t\t<span>text</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-undo\"></span>\n\t\t</span>\n\t\t<span>undo</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-unlinked\"></span>\n\t\t</span>\n\t\t<span>unlinked</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-zoomin\"></span>\n\t\t</span>\n\t\t<span>zoomin</span>\n\t</div>\n\t<div class=\"preview\">\n\t\t<span class=\"preview__icon\">\n\t\t\t<span class=\"ptro-icon ptro-icon-zoomout\"></span>\n\t\t</span>\n\t\t<span>zoomout</span>\n\t</div>\n</body>\n</html>\n"
  },
  {
    "path": "css/styles.css",
    "content": ".ptro-wrapper {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n    text-align: center;\r\n    z-index: 10;\r\n    font-family: \"Open Sans\", sans-serif;\r\n}\r\n\r\n@media screen and (min-width: 869px) {\r\n    .ptro-holder {\r\n        position: fixed;\r\n        left: 35px;\r\n        right: 35px;\r\n        top: 35px;\r\n        bottom: 35px;\r\n        box-shadow: 1px 1px 5px #888;\r\n    }\r\n}\r\n\r\n@media screen and (max-width: 868px) {\r\n    .ptro-holder {\r\n        position: fixed;\r\n        box-shadow: 3px 3px 15px #787878;\r\n        left: 0;\r\n        right: 0;\r\n        top: 0;\r\n        bottom: 0;\r\n    }\r\n}\r\n\r\n.ptro-holder-wrapper {\r\n    position: fixed;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    background-color: rgba(0,0,0,0.2);\r\n}\r\n\r\n.ptro-wrapper.ptro-v-aligned:before {\r\n    content: \"\";\r\n    display: inline-block;\r\n    vertical-align: middle;\r\n    height: 100%;\r\n}\r\n\r\n\r\n.ptro-icon {\r\n    font-size: 14px;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n}\r\n\r\n\r\n.ptro-icon-btn:disabled {\r\n    color: gray;\r\n}\r\n\r\n.ptro-wrapper canvas {\r\n    /* vertical-align: middle; */\r\n    display: inline-block;\r\n    touch-action: none;\r\n    margin-left: auto;\r\n    margin-right: auto;\r\n    width: auto;\r\n    height: auto;\r\n}\r\n\r\n.ptro-center-table {\r\n    display:table;\r\n    width: 100%;\r\n    height: 100%;\r\n}\r\n\r\n.ptro-center-tablecell {\r\n    display:table-cell;\r\n    vertical-align:middle;\r\n}\r\n\r\n.ptro-icon-btn {\r\n    border: 0;\r\n    cursor: pointer;\r\n    flex-shrink: 0;\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n}\r\n\r\n.ptro-icon-btn i {\r\n}\r\n\r\n\r\n.ptro-named-btn {\r\n    border: 0;\r\n    display: inline-block;\r\n    height: 30px;\r\n    margin-left: 4px;\r\n    font-family: \"Open Sans\", sans-serif;\r\n    position: relative;\r\n    top:-5px;\r\n    font-size: 14px;\r\n    cursor: pointer;\r\n}\r\n\r\n.ptro-icon-btn:focus,\r\n.ptro-named-btn:focus,\r\n.color-diwget-btn:focus,\r\n.ptro-color-btn:focus,\r\n.ptro-selector-btn:focus {\r\n    outline: none;\r\n}\r\n\r\n.ptro-color-btn {\r\n    height: 32px;\r\n    width: 32px;\r\n    cursor: pointer;\r\n}\r\n\r\n\r\n.ptro-wrapper .select-handler {\r\n    background-color: white;\r\n    border: 1px solid black;\r\n    width: 6px;\r\n    height: 6px;\r\n    position: absolute;\r\n    z-index: 10;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-el {\r\n    position: absolute;\r\n}\r\n\r\n.ptro-wrapper .ptro-substrate {\r\n    opacity: 0.3;\r\n    background-image: url(\"checkers.svg\");\r\n    background-size: 32px 32px;\r\n    z-index: -1;\r\n    position: absolute;\r\n}\r\n\r\n.ptro-wrapper .ptro-close-color-picker {\r\n    height: 24px;\r\n    margin-top: 5px;\r\n    margin-bottom: -5px;\r\n    margin-left: auto;\r\n}\r\n\r\n.ptro-tool-controls {\r\n    flex-shrink: 0;\r\n    display: flex;\r\n    align-items: center;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-rect {\r\n    position: absolute;\r\n    background-color: rgba(225, 225, 225, .5);\r\n    border: 1px dashed black;\r\n    cursor: move;\r\n    -moz-user-select: none;\r\n    /* -webkit-user-select: none; */\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n    -webkit-user-drag: none;\r\n    user-drag: none;\r\n    -webkit-touch-callout: none;\r\n    background-repeat: no-repeat;\r\n    background-size: 100% 100%;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-tl {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    margin: -4px 0 0 -4px;\r\n    cursor: se-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-bl {\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    margin: 0 0 -4px -4px;\r\n    cursor: ne-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-br {\r\n    position: absolute;\r\n    right: 0;\r\n    bottom: 0;\r\n    margin: 0 -4px -4px 0;\r\n    cursor: se-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-tr {\r\n    position: absolute;\r\n    right: 0;\r\n    top: 0;\r\n    margin: -4px -4px 0 0;\r\n    cursor: ne-resize;\r\n}\r\n\r\n\r\n.ptro-wrapper .ptro-crp-l {\r\n    position: absolute;\r\n    top: 50%;\r\n    left: 0;\r\n    margin: -4px 0 0 -4px;\r\n    cursor: e-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-t {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 50%;\r\n    margin: -4px 0 0 -4px;\r\n    cursor: s-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-r {\r\n    position: absolute;\r\n    top: 50%;\r\n    right: 0;\r\n    margin: -4px -4px 0 0 ;\r\n    cursor: e-resize;\r\n}\r\n\r\n.ptro-wrapper .ptro-crp-b {\r\n    position: absolute;\r\n    left: 50%;\r\n    bottom: 0;\r\n    margin: 0 0 -4px -4px;\r\n    cursor: s-resize;\r\n}\r\n\r\n.ptro-wrapper div,\r\n.ptro-wrapper span,\r\n.ptro-wrapper i,\r\n.ptro-bar .ptro-tool-ctl-name,\r\n.ptro-bar input,\r\n.ptro-bar .ptro-named-btn p {\r\n    -moz-user-select: none;\r\n   /* -webkit-user-select: none; */\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n    -webkit-user-drag: none;\r\n    user-drag: none;\r\n    -webkit-touch-callout: none;\r\n}\r\n\r\n.ptro-bar > div {\r\n    overflow-x: auto;\r\n    overflow-y: hidden;\r\n    height: 100%;\r\n    display: flex;\r\n    align-items: center;\r\n}\r\n\r\n\r\n\r\n.ptro-wrapper .ptro-common-widget-wrapper {\r\n    position: absolute;\r\n    background-color: rgba(0, 0, 0, 0.6);\r\n    top: 0;\r\n    bottom: 0;\r\n    left: 0;\r\n    right: 0;\r\n}\r\n\r\n.ptro-wrapper .ptro-pallet canvas {\r\n    cursor: crosshair;\r\n}\r\n\r\ndiv.ptro-pallet {\r\n    line-height: 0;\r\n}\r\n\r\n.ptro-wrapper .ptro-pallet,\r\n.ptro-wrapper .ptro-resize-widget{\r\n    width: 200px;\r\n    padding: 10px;\r\n    z-index: 100;\r\n    box-sizing: border-box;\r\n}\r\n\r\n.ptro-error {\r\n    background-color: rgba(200, 0, 0, 0.5);\r\n    padding: 5px;\r\n    margin: 5px;\r\n    color: white;\r\n}\r\n\r\n.ptro-v-middle:before {\r\n    content: \"\";\r\n    display: inline-block;\r\n    vertical-align: middle;\r\n    height: 100%;\r\n}\r\n\r\n.ptro-v-middle-in {\r\n    display: inline-block;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n.ptro-wrapper .ptro-settings-widget {\r\n    width: 300px;\r\n    padding: 10px;\r\n    z-index: 100;\r\n    box-sizing: border-box;\r\n}\r\n\r\ntd.ptro-resize-table-left {\r\n    text-align: right;\r\n    padding-right: 5px;\r\n    float: none;\r\n    font-size: 14px;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-edit {\r\n    margin-top: 15px;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-edit input {\r\n    float: left;\r\n    height: 24px;\r\n    text-align: center;\r\n    font-family: monospace;\r\n    font-size: 14px;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-edit input:focus {\r\n    outline: none;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-edit input.ptro-color {\r\n    width: 70px;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-edit input.ptro-color-alpha {\r\n    font-size: 14px;\r\n    width: 55px;\r\n    padding: 0 0 0 2px;\r\n    line-height: 23px;\r\n    height: 23px;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-alpha-label,\r\n.ptro-wrapper .ptro-label  {\r\n    float: left;\r\n    padding: 0 2px 0 0;\r\n    margin-left: 5px;\r\n    font-family: \"Open Sans\", sans-serif;\r\n}\r\n\r\n.ptro-pixel-size-input {\r\n    width: 60px;\r\n}\r\n\r\n.ptro-wrapper .ptro-pipette {\r\n    height: 24px;\r\n    width: 24px;\r\n    margin: 0;\r\n}\r\n\r\ndiv.ptro-color-widget-wrapper {\r\n\tz-index: 1000;\r\n}\r\n\r\n.ptro-wrapper .ptro-pipette i {\r\n    line-height: 16px;\r\n}\r\n\r\n.ptro-wrapper .ptro-pipette:active {\r\n    outline: none;\r\n}\r\n\r\n.ptro-wrapper .ptro-color-widget-wrapper .ptro-canvas-light,\r\n.ptro-wrapper .ptro-color-widget-wrapper .ptro-canvas-alpha {\r\n    margin-top: 10px;\r\n}\r\n\r\nspan.ptro-color-light-regulator,\r\nspan.ptro-color-alpha-regulator {\r\n    display: block;\r\n    margin-top: -5px;\r\n    margin-left: 5px;\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    border-left: 5px solid transparent;\r\n    border-right: 5px solid transparent;\r\n    border-bottom: 5px solid;\r\n    cursor: crosshair;\r\n}\r\n\r\nspan.ptro-color-alpha-regulator {\r\n    margin-top: 0;\r\n}\r\n\r\n.alpha-checkers {\r\n    background-image: url(\"checkers.svg\");\r\n    display: block;\r\n    width: 100%;\r\n    height: 15px;\r\n    background-size: 10px 10px;\r\n    margin-top: -20px;\r\n}\r\n\r\ninput.ptro-input:focus,\r\nselect.ptro-input:focus {\r\n    outline: none;\r\n    box-shadow: none ;\r\n}\r\n\r\ninput.ptro-input,\r\nselect.ptro-input {\r\n    vertical-align: initial;\r\n    padding-top: 0;\r\n    padding-bottom: 0;\r\n    padding-right: 0;\r\n}\r\n\r\n.ptro-named-btn p {\r\n    font-size: inherit;\r\n    line-height: normal;\r\n    margin: inherit;\r\n}\r\n\r\n.ptro-wrapper .ptro-zoomer {\r\n    border-top:1px solid white;\r\n    border-left:1px solid white;\r\n    position: absolute;\r\n    z-index: 2000;\r\n    display: none;\r\n}\r\n\r\n.ptro-text-tool-input {\r\n    background-color: rgba(0,0,0,0);\r\n    width: auto;\r\n    outline: 1px dotted;\r\n    display: block;\r\n    min-width: 5px;\r\n    padding: 0 1px;\r\n    overflow-x: hidden;\r\n    word-wrap: break-word;\r\n    overflow-y: hidden;\r\n    box-sizing: content-box;\r\n    line-height: normal;\r\n    text-align: left;\r\n}\r\n.ptro-paster-wrappers-fits {\r\n    display: flex;\r\n    justify-content: space-around;\r\n    align-items: center;\r\n}\r\n.ptro-selector-extend[type] {\r\n    height: 70px;\r\n    width: 70px;\r\n}\r\n.ptro-selector-extend div:last-child {\r\n    display: none;\r\n}\r\n.ptro-selector-fit[type] {\r\n    height: 220px;\r\n    width: 220px;\r\n    margin: 0px;\r\n}\r\n.ptro-paster-fit[class] {\r\n    margin-right: 46px;\r\n}\r\n.ptro-text-tool-buttons {\r\n    display: flex;\r\n    position: absolute;\r\n}\r\n.ptro-text-tool-input-wrapper {\r\n    position: absolute;\r\n}\r\n\r\nspan.ptro-btn-color-checkers {\r\n    background-image: url(\"checkers.svg\");\r\n    display: block;\r\n    width: 32px;\r\n    height: 32px;\r\n    background-size: 16px 16px;\r\n    margin-top: -32px;\r\n}\r\n\r\nspan.ptro-btn-color-checkers-bar {\r\n    background-image: url(\"checkers.svg\");\r\n    width: 32px;\r\n    line-height: 12px;\r\n    height: 32px;\r\n    background-size: 16px 16px;\r\n    z-index: 0;\r\n    position: relative;\r\n    margin-left: -32px;\r\n}\r\n\r\n.ptro-bar-right {\r\n    display: flex;\r\n\r\n}\r\n\r\n.ptro-link {\r\n    float: left;\r\n    margin-right: -12px;\r\n    margin-top: -23px;\r\n}\r\n\r\n.ptro-resize-link-wrapper {\r\n    display: inline-block;\r\n    height: 40px;\r\n}\r\n\r\n\r\ninput.ptro-resize-width-input,\r\ninput.ptro-resize-heigth-input,\r\ninput.ptro-pixel-size-input {\r\n    line-height: 22px;\r\n    padding: 0 0 0 4px;\r\n    height: 22px;\r\n    width: 80px;\r\n}\r\n\r\n.ptro-selector-btn i {\r\n    font-size: 56px;\r\n}\r\n\r\n.ptro-selector-btn {\r\n    opacity: 0.8;\r\n    border: 0;\r\n    width: 100px;\r\n    cursor: pointer;\r\n}\r\n\r\n.ptro-selector-btn {\r\n    margin-left: 5px;\r\n    margin-right: 5px;\r\n    margin-top: 5px;\r\n    margin-bottom: 5px;\r\n}\r\n\r\n.ptro-selector-btn div {\r\n    margin: 5px 0;\r\n}\r\n\r\n.ptro-paster-select .ptro-in div {\r\n    font-family: \"Open Sans\", sans-serif;\r\n    font-size: 14px;\r\n}\r\n\r\n.ptro-selector-btn:hover {\r\n    opacity: 0.6;\r\n}\r\n\r\n.ptro-paster-select {\r\n    display: inline-block;\r\n    margin-left: auto;\r\n    margin-right: auto;\r\n    height: 100%;\r\n}\r\n\r\n.ptro-paster-select .ptro-in {\r\n    background-color: rgba(0,0,0,0.7);\r\n    padding: 40px;\r\n}\r\n.ptro-paster-select-wrapper {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n    bottom: 0;\r\n}\r\n.ptro-paster-select-wrapper-extends button:first-child {\r\n    display: block;\r\n    margin: 0 auto;\r\n}\r\n.ptro-paster-select-wrapper-extends button:last-child{\r\n    display: block;\r\n    margin: 0 auto;\r\n}\r\n.ptro-paster-select-wrapper-extends button:nth-child(2){\r\n    display: inline-block;\r\n    margin-right: 78px;\r\n}\r\n.ptro-paster-fit .ptro-paster-wrapper-label[class] {\r\n    display: block; \r\n    color: white;\r\n    font-size: 20px;\r\n    text-align: center;\r\n    margin-top: 10px;\r\n    text-transform: uppercase;\r\n}\r\n.ptro-paster-select-wrapper-extends .ptro-paster-wrapper-label[class] {\r\n    display: block; \r\n    color: white;\r\n    font-size: 20px;\r\n    text-align: center;\r\n    margin-top: 10px;\r\n    text-transform: uppercase;\r\n}\r\n.ptro-paste-label {\r\n    color: white;\r\n    margin-bottom: 10px;\r\n}\r\n\r\n.ptro-iframe {\r\n    width: 100%;\r\n    height: 100%;\r\n    border: 0;\r\n}\r\n\r\n\r\n\r\ni.mce-i-painterro:before, span.mce_painterro:before {\r\n\tfont-size: 20px;\r\n\tfont-family: ptroiconfont;\r\n\tfont-style: normal;\r\n\tfont-weight: normal;\r\n\tfont-variant: normal;\r\n\ttext-transform: none;\r\n\tspeak: none;\r\n\t-webkit-font-smoothing: antialiased;\r\n\t-moz-osx-font-smoothing: grayscale;\r\n\tcontent: \"\\f101\";\r\n}\r\n\r\n.ptro-scroller {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n    bottom: 0;\r\n}\r\n\r\ntd.ptro-strict-cell {\r\n    font-size: 8px;\r\n    line-height: normal;\r\n}"
  },
  {
    "path": "example/server.py",
    "content": "import base64\nimport os\nimport tempfile\nfrom time import time\nfrom flask import Flask, jsonify\nfrom flask.globals import request\nfrom flask.helpers import send_from_directory\nfrom flask.templating import render_template\n\napp = Flask(__name__)\n\nTMP_DIR_NAME = 'painterro_server'\n\ndef get_tmp_dir():\n    d = os.path.join(tempfile.gettempdir(), TMP_DIR_NAME)\n    if not os.path.exists(d):\n        os.makedirs(d)\n    return d\n\n\n@app.route(\"/\")\ndef base64_page():\n    files = reversed(\n        sorted(\n            [f for f in os.listdir(get_tmp_dir()) if f.endswith('png')]))\n    return render_template('paste_as_base64.html', files=files)\n\n\n@app.route(\"/bin/\")\ndef bin_page():\n    files = reversed(\n        sorted(\n            [f for f in os.listdir(get_tmp_dir()) if f.endswith('png')]))\n    return render_template('paste_as_bin.html', files=files)\n\n\n@app.route(\"/paste/\")\ndef paste_page():\n    return render_template('paste_to_tinymce.html')\n\n\n@app.route(\"/save-as-base64/\", methods=['POST'])\ndef base64_saver():\n    filename = '{:10d}.png'.format(int(time()))  # generate some filename\n    filepath = os.path.join(get_tmp_dir(), filename)\n    with open(filepath, \"wb\") as fh:\n        base64_data = request.json['image'].replace('data:image/png;base64,', '')\n        fh.write(base64.b64decode(base64_data))\n\n    return jsonify({})\n\n\n@app.route(\"/save-as-binary/\", methods=['POST'])\ndef binary_saver():\n    filename = '{:10d}.png'.format(int(time()))  # generate some filename\n    filepath = os.path.join(get_tmp_dir(), filename)\n    request.files['image'].save(filepath)\n\n    return jsonify({})\n\n\n@app.route('/image/<path:filename>')\ndef get_file(filename):\n    return send_from_directory(get_tmp_dir(), filename)\n\nprint(\"\"\"\n====================================================================\n||                WELCOME TO  THE PAINTERRO DEMO                  ||\n||  To make this work, please go to painterro root dir, and run\"  ||\n||                                                                ||\n||    npm install                                                 ||\n||    npm run dev                                                 ||\n====================================================================\n\"\"\")\n\napp.run()\n\n"
  },
  {
    "path": "example/templates/common.html",
    "content": "<div>\n  Painterro demo: <a href=\"/\">Upload as base64 DEMO</a> | <a href=\"/bin/\">Upload as binary DEMO</a> |\n  <a href=\"/paste/\">Paste to TinyMCE DEMO</a>\n</div>\n\n<div style=\"margin: 10px\">\n  <button aria-label='Open painterro' onclick='ptro.show()'>OPEN PAINTERRO</button>\n</div>\n"
  },
  {
    "path": "example/templates/images_list.html",
    "content": "<div>\n  {% for f in files %}\n  <div class=\"img-browse\">\n    <a href=\"/image/{{f}}\"><img src=\"/image/{{f}}\"/></a>\n  </div>\n  {% endfor %}\n</div>\n<style>\n  .img-browse {\n    width: 600px;\n    border: 1px solid gray;\n    padding: 10px;\n    margin: 10px;\n  }\n\n  .img-browse img {\n    width: 100%;\n  }\n</style>"
  },
  {
    "path": "example/templates/paste_as_base64.html",
    "content": "<html>\n  <head>\n    <script src=\"http://localhost:8080/painterro.min.js\"></script>\n  </head>\n  <body>\n    {% include 'common.html' %}\n    {% include 'images_list.html' %}\n    <script>\n      var ptro = Painterro({\n        saveHandler: function (image, done) {\n          var xhr = new XMLHttpRequest()\n          xhr.open('POST', 'http://127.0.0.1:5000/save-as-base64/')\n          xhr.setRequestHeader('Content-Type', 'application/json')\n          xhr.send(JSON.stringify({\n            image: image.asDataURL('image/png')\n          }))\n          xhr.onload = function (e) {\n            done(true)\n            window.location.reload()\n          }\n        }\n      })\n    </script>\n  </body>\n</html>"
  },
  {
    "path": "example/templates/paste_as_bin.html",
    "content": "<html>\n<head>\n  <script src=\"http://localhost:8080/painterro.min.js\"></script>\n</head>\n<body>\n  {% include 'common.html' %}\n  {% include 'images_list.html' %}\n  <script>\n    var ptro = Painterro({\n      saveHandler: function (image, done) {\n        var formData = new FormData()\n        formData.append('image', image.asBlob());\n\n        var xhr = new XMLHttpRequest();\n        xhr.open('POST', 'http://127.0.0.1:5000/save-as-binary/', true);\n        xhr.onload = xhr.onerror = function () {\n          done(true)\n          window.location.reload()\n        };\n        xhr.send(formData)\n      }\n    })\n  </script>\n</body>\n</html>"
  },
  {
    "path": "example/templates/paste_to_tinymce.html",
    "content": "<html>\n  <script src=\"http://localhost:8080/painterro.min.js\"></script>\n  <script src=\"https://cloud.tinymce.com/stable/tinymce.min.js\"></script>\n  <script>\n    tinymce.init({\n      selector: 'textarea',\n      plugins: [ 'fullscreen' ],\n      toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table | fontsizeselect | painterro',\n      height : \"700\",\n      setup: function(editor) {\n        editor.addButton(\"painterro\", {\n          title: \"Open Painterro\",\n          cmd: \"painterro_cmd\",\n          'icon': \"painterro ptro-icon ptro-icon-painterro\",\n        });\n\n        //button functionality.\n        editor.addCommand(\"painterro_cmd\", function () {\n          ptro.show();\n        });\n      }\n    });\n  </script>\n<body>\n  {% include 'common.html' %}\n\n  <textarea>Example content</textarea>\n\n  <script>\n    var ptro = Painterro({\n      defaultSize: '500x400',\n      saveHandler: function (image, done) {\n        tinymce.activeEditor.execCommand('mceInsertContent',false, '<img src=\"' + image.asDataURL() + '\" />');\n        done(true)\n      }\n    })\n  </script>\n</body>\n</html>"
  },
  {
    "path": "generate_font.js",
    "content": "const webfontsGenerator = require('@vusion/webfonts-generator');\nconst fs = require('fs');\n\nfs.readdir('res/font', function(err, items) {\n  if (err) {\n    console.log('cant read res directory');\n  }\n  const files = items.filter((i) => i.toLowerCase().endsWith('.svg')).map(\n    (i) => 'res/font/'+i);\n\n  webfontsGenerator({\n    files: files,\n    dest: 'css/icons',\n    fontName: 'ptroiconfont',\n    \n    html: true,\n    // https://github.com/nfroidure/svgicons2svgfont\n    normalize: true,\n    round: 10e2,\n    \n    cssTemplate: 'res/font/font-css.hbs',\n    templateOptions: {\n      classPrefix: 'ptro-icon-',\n      baseSelector: '.ptro-icon'\n    },\n    types: ['ttf', 'woff']\n  }, function (error) {\n    if (error) {\n      console.log('Fail!', error);\n    } else {\n      console.log('Done!');\n    }\n  })\n\n})\n\n\n"
  },
  {
    "path": "js/colorPicker.js",
    "content": "import { tr } from './translation';\nimport { KEYS } from './utils';\n\nexport function HexToRGB(hex) {\n  let parse = /^#?([a-fA-F\\d]{2})([a-fA-F\\d]{2})([a-fA-F\\d]{2})$/i.exec(hex);\n  if (parse) {\n    return {\n      r: parseInt(parse[1], 16),\n      g: parseInt(parse[2], 16),\n      b: parseInt(parse[3], 16),\n    };\n  }\n  parse = /^#?([a-fA-F\\d])([a-fA-F\\d])([a-fA-F\\d])$/i.exec(hex);\n  if (parse) {\n    return {\n      r: parseInt(parse[1].repeat(2), 16),\n      g: parseInt(parse[2].repeat(2), 16),\n      b: parseInt(parse[3].repeat(2), 16),\n    };\n  }\n}\n\nexport function HexToRGBA(hex, alpha) {\n  const rgb = HexToRGB(hex);\n  return `rgba(${rgb.r},${rgb.g},${rgb.b},${alpha})`;\n}\n\nfunction format2Hex(val) {\n  const hex = val.toString(16);\n  return (hex.length === 1 && (`0${hex}`)) || hex;\n}\n\nexport function rgbToHex(r, g, b) {\n  return `#${format2Hex(r)}${format2Hex(g)}${format2Hex(b)}`;\n}\n\nfunction reversedColor(color) {\n  const rgb = HexToRGB(color);\n  const index = ((rgb.r * 299) + (rgb.g * 587) + (rgb.b * 114)) / 1000;\n  return (index >= 128 && 'black') || 'white';\n}\n\nexport default class ColorPicker {\n  constructor(main, callback) {\n    this.callback = callback;\n    this.main = main;\n    this.w = 180;\n    this.h = 150;\n    const w = this.w;\n    const h = this.h;\n    this.lightPosition = this.w - 1;\n    this.wrapper = main.wrapper.querySelector('.ptro-color-widget-wrapper');\n    this.input = main.wrapper.querySelector('.ptro-color-widget-wrapper .ptro-color');\n    this.pipetteButton = main.wrapper.querySelector('.ptro-color-widget-wrapper button.ptro-pipette');\n    this.closeButton = main.wrapper.querySelector('.ptro-color-widget-wrapper button.ptro-close-color-picker');\n    this.canvas = main.wrapper.querySelector('.ptro-color-widget-wrapper canvas');\n    this.ctx = this.canvas.getContext('2d');\n\n    this.canvasLight = main.wrapper.querySelector('.ptro-color-widget-wrapper .ptro-canvas-light');\n    this.colorRegulator = main.wrapper.querySelector('.ptro-color-widget-wrapper .ptro-color-light-regulator');\n\n    this.canvasAlpha = main.wrapper.querySelector('.ptro-color-widget-wrapper .ptro-canvas-alpha');\n    this.alphaRegulator = main.wrapper.querySelector('.ptro-color-widget-wrapper .ptro-color-alpha-regulator');\n\n    this.ctxLight = this.canvasLight.getContext('2d');\n    this.ctxAlpha = this.canvasAlpha.getContext('2d');\n    this.canvas.setAttribute('width', `${w}`);\n    this.canvas.setAttribute('height', `${h}`);\n    this.canvasLight.setAttribute('width', `${w}`);\n    this.canvasLight.setAttribute('height', `${20}`);\n    this.canvasAlpha.setAttribute('width', `${w}`);\n    this.canvasAlpha.setAttribute('height', `${20}`);\n    const palette = this.ctx.createLinearGradient(0, 0, w, 0);\n    palette.addColorStop(1 / 15, '#ff0000');\n    palette.addColorStop(4 / 15, '#ffff00');\n    palette.addColorStop(5 / 15, '#00ff00');\n    palette.addColorStop(9 / 15, '#00ffff');\n    palette.addColorStop(12 / 15, '#0000ff');\n    palette.addColorStop(14 / 15, '#ff00ff');\n    this.ctx.fillStyle = palette;\n    this.ctx.fillRect(0, 0, w, h);\n\n    const darkOverlay = this.ctx.createLinearGradient(0, 0, 0, h);\n    darkOverlay.addColorStop(0, 'rgba(0, 0, 0, 0)');\n    darkOverlay.addColorStop(0.99, 'rgba(0, 0, 0, 1)');\n    darkOverlay.addColorStop(1, 'rgba(0, 0, 0, 1)');\n    this.ctx.fillStyle = darkOverlay;\n    this.ctx.fillRect(0, 0, w, h);\n\n    this.closeButton.onclick = () => {\n      this.close();\n    };\n    this.pipetteButton.onclick = () => {\n      this.wrapper.setAttribute('hidden', 'true');\n      this.opened = false;\n      this.choosing = true;\n    };\n\n    this.input.onkeyup = () => {\n      this.setActiveColor(this.input.value, true);\n    };\n  }\n\n  open(state, addCallback) {\n    this.target = state.target;\n    this.palleteColor = state.palleteColor;\n    this.alpha = state.alpha;\n    this.lightPosition = this.lightPosition || this.w - 1;\n\n    this.drawLighter();\n    this.colorRegulator.style.left = `${this.lightPosition}px`;\n    this.alphaRegulator.style.left = `${Math.round(this.alpha * this.w)}px`;\n    this.regetColor();\n\n    this.wrapper.removeAttribute('hidden');\n    this.opened = true;\n    this.addCallback = addCallback;\n  }\n\n  close() {\n    this.wrapper.setAttribute('hidden', 'true');\n    this.opened = false;\n  }\n\n  getPaletteColorAtPoint(e) {\n    let x = e.clientX - this.canvas.documentOffsetLeft;\n    let y = e.clientY - this.canvas.documentOffsetTop;\n    x = (x < 1 && 1) || x;\n    y = (y < 1 && 1) || y;\n    x = (x > this.w && this.w - 1) || x;\n    y = (y > this.h && this.h - 1) || y;\n    const p = this.ctx.getImageData(x, y, 1, 1).data;\n    this.palleteColor = rgbToHex(p[0], p[1], p[2]);\n    this.drawLighter();\n    this.regetColor();\n  }\n\n  regetColor() {\n    const p = this.ctxLight.getImageData(this.lightPosition, 5, 1, 1).data;\n    this.setActiveColor(rgbToHex(p[0], p[1], p[2]));\n    this.drawAlpher();\n  }\n\n  regetAlpha() {\n    const p = this.ctxAlpha.getImageData(this.alphaPosition, 5, 1, 1).data;\n    this.alpha = p[3] / 255;\n    this.setActiveColor(this.color, true);\n  }\n\n  getColorLightAtClick(e) {\n    let x = e.clientX - this.canvasLight.documentOffsetLeft;\n    x = (x < 1 && 1) || x;\n    x = (x > this.w - 1 && this.w - 1) || x;\n    this.lightPosition = x;\n    this.colorRegulator.style.left = `${x}px`;\n    this.regetColor();\n  }\n\n  getAlphaAtClick(e) {\n    let x = e.clientX - this.canvasAlpha.documentOffsetLeft;\n    x = (x < 1 && 1) || x;\n    x = (x > this.w - 1 && this.w - 1) || x;\n    this.alphaPosition = x;\n    this.alphaRegulator.style.left = `${x}px`;\n    this.regetAlpha();\n  }\n\n  handleKeyDown(event) {\n    if (this.opened && event.keyCode === KEYS.enter) {\n      return true; // mark as handled - user might expect doing save by enter\n    }\n    if (this.opened && event.keyCode === KEYS.esc) {\n      this.close();\n      return true;\n    }\n    return false;\n  }\n\n  handleMouseDown(e) {\n    if (this.choosing && e.button !== 2) { // 0 - m1, 1 middle, 2-m2\n      this.choosingActive = true;\n      this.handleMouseMove(e);\n      return true;\n    }\n    this.choosing = false;\n    if (e.target === this.canvas) {\n      this.selecting = true;\n      this.getPaletteColorAtPoint(e);\n    }\n    if (e.target === this.canvasLight || e.target === this.colorRegulator) {\n      this.lightSelecting = true;\n      this.getColorLightAtClick(e);\n    }\n    if (e.target === this.canvasAlpha || e.target === this.alphaRegulator) {\n      this.alphaSelecting = true;\n      this.getAlphaAtClick(e);\n    }\n    return false;\n  }\n\n  handleMouseMove(e) {\n    if (this.opened) {\n      if (this.selecting) {\n        this.getPaletteColorAtPoint(e);\n      }\n      if (this.lightSelecting) {\n        this.getColorLightAtClick(e);\n      }\n      if (this.alphaSelecting) {\n        this.getAlphaAtClick(e);\n      }\n    } else if (this.choosingActive) {\n      const scale = this.main.getScale();\n      let x = ((e.clientX - this.main.elLeft()) +\n        this.main.scroller.scrollLeft) * scale;\n      x = (x < 1 && 1) || x;\n      x = (x > this.main.size.w - 1 && this.main.size.w - 1) || x;\n      let y = ((e.clientY - this.main.elTop()) +\n        this.main.scroller.scrollTop) * scale;\n      y = (y < 1 && 1) || y;\n      y = (y > this.main.size.h - 1 && this.main.size.h - 1) || y;\n      const p = this.main.ctx.getImageData(x, y, 1, 1).data;\n      const color = rgbToHex(p[0], p[1], p[2]);\n      this.callback({\n        alphaColor: HexToRGBA(color, 1),\n        lightPosition: this.w - 1,\n        alpha: 1,\n        palleteColor: color,\n        target: this.target,\n      });\n      if (this.addCallback !== undefined) {\n        this.addCallback({\n          alphaColor: HexToRGBA(color, 1),\n          lightPosition: this.w - 1,\n          alpha: 1,\n          palleteColor: color,\n          target: this.target,\n        });\n      }\n    }\n  }\n\n  handleMouseUp() {\n    this.selecting = false;\n    this.lightSelecting = false;\n    this.choosing = false;\n    this.choosingActive = false;\n    this.alphaSelecting = false;\n    this.main.zoomHelper.hideZoomHelper();\n  }\n\n  setActiveColor(color, ignoreUpdateText) {\n    try {\n      this.input.style.color = reversedColor(color);\n    } catch (e) {\n      return;\n    }\n    this.input.style['background-color'] = color;\n    if (ignoreUpdateText === undefined) {\n      this.input.value = color;\n    }\n    this.color = color;\n    this.alphaColor = HexToRGBA(color, this.alpha);\n    if (this.callback !== undefined && this.opened) {\n      this.callback({\n        alphaColor: this.alphaColor,\n        lightPosition: this.lightPosition,\n        alpha: this.alpha,\n        palleteColor: this.color,\n        target: this.target,\n      });\n    }\n    if (this.addCallback !== undefined && this.opened) {\n      this.addCallback({\n        alphaColor: this.alphaColor,\n        lightPosition: this.lightPosition,\n        alpha: this.alpha,\n        palleteColor: this.color,\n        target: this.target,\n      });\n    }\n  }\n\n  static html() {\n    return '' +\n      '<div class=\"ptro-color-widget-wrapper ptro-common-widget-wrapper ptro-v-middle\" hidden>' +\n        '<div class=\"ptro-pallet ptro-color-main ptro-v-middle-in\">' +\n          '<canvas></canvas>' +\n          '<canvas class=\"ptro-canvas-light\"></canvas>' +\n          '<span class=\"ptro-color-light-regulator ptro-bordered-control\"></span>' +\n          '<canvas class=\"ptro-canvas-alpha\"></canvas>' +\n          '<span class=\"alpha-checkers\"></span>' +\n          '<span class=\"ptro-color-alpha-regulator ptro-bordered-control\"></span>' +\n          '<div class=\"ptro-colors\"></div>' +\n          '<div class=\"ptro-color-edit\">' +\n            '<button type=\"button\" aria-label=\"pipette\" class=\"ptro-icon-btn ptro-pipette ptro-color-control\" style=\"float: left; margin-right: 5px\">' +\n              '<i class=\"ptro-icon ptro-icon-pipette\"></i>' +\n            '</button>' +\n            '<input class=\"ptro-input ptro-color\" type=\"text\" size=\"7\"/>' +\n            '<button type=\"button\" aria-label=\"close\" class=\"ptro-named-btn ptro-close-color-picker ptro-color-control\" >' +\n            `${tr('close')}</button>` +\n          '</div>' +\n        '</div>' +\n      '</div>';\n  }\n\n  drawLighter() {\n    const lightGradient = this.ctxLight.createLinearGradient(0, 0, this.w, 0);\n    lightGradient.addColorStop(0, '#ffffff');\n    lightGradient.addColorStop(0.05, '#ffffff');\n    lightGradient.addColorStop(0.95, this.palleteColor);\n    lightGradient.addColorStop(1, this.palleteColor);\n    this.ctxLight.fillStyle = lightGradient;\n    this.ctxLight.fillRect(0, 0, this.w, 15);\n  }\n\n  drawAlpher() {\n    this.ctxAlpha.clearRect(0, 0, this.w, 15);\n    const lightGradient = this.ctxAlpha.createLinearGradient(0, 0, this.w, 0);\n    lightGradient.addColorStop(0, 'rgba(255,255,255,0)');\n    lightGradient.addColorStop(0.05, 'rgba(255,255,255,0)');\n    lightGradient.addColorStop(0.95, this.color);\n    lightGradient.addColorStop(1, this.color);\n    this.ctxAlpha.fillStyle = lightGradient;\n    this.ctxAlpha.fillRect(0, 0, this.w, 15);\n  }\n}\n"
  },
  {
    "path": "js/controlbuilder.js",
    "content": "import { setParam } from './params';\n\nexport default class ControlBuilder {\n  constructor(main) {\n    this.main = main;\n  }\n\n  buildFontSizeControl(controlIndex) {\n    const action = () => {\n      const fontSize =\n        this.main.getElemByIdSafe(this.main.activeTool.controls[controlIndex].id).value;\n      this.main.textTool.setFontSize(fontSize);\n      setParam('defaultFontSize', fontSize);\n    };\n    const getValue = () => this.main.textTool.fontSize;\n\n    if (this.main.params.availableFontSizes) {\n      return ControlBuilder.buildDropDownControl('fontSize', action, getValue, this.main.params.availableFontSizes);\n    }\n    return ControlBuilder.buildInputControl('fontSize', action, getValue, 1, 200);\n  }\n\n  buildEraserWidthControl(controlIndex) {\n    const action = () => {\n      const width = this.main.getElemByIdSafe(this.main.activeTool.controls[controlIndex].id).value;\n      this.main.primitiveTool.setEraserWidth(width);\n      setParam('defaultEraserWidth', width);\n    };\n    const getValue = () => this.main.primitiveTool.eraserWidth;\n\n    if (this.main.params.availableEraserWidths) {\n      return ControlBuilder.buildDropDownControl('eraserWidth', action, getValue, this.main.params.availableEraserWidths);\n    }\n    return ControlBuilder.buildInputControl('eraserWidth', action, getValue, 1, 99);\n  }\n\n  buildLineWidthControl(controlIndex) {\n    const action = () => {\n      const width = this.main.getElemByIdSafe(this.main.activeTool.controls[controlIndex].id).value;\n      this.main.primitiveTool.setLineWidth(width);\n      setParam('defaultLineWidth', width);\n    };\n    const getValue = () => this.main.primitiveTool.lineWidth;\n\n    if (this.main.params.availableLineWidths) {\n      return ControlBuilder.buildDropDownControl('lineWidth', action, getValue, this.main.params.availableLineWidths);\n    }\n    return ControlBuilder.buildInputControl('lineWidth', action, getValue, 0, 99);\n  }\n\n  buildShadowOnControl(controlIndex) {\n    return {\n      type: 'bool',\n      title: 'shadowOn',\n      titleFull: 'shadowOnFull',\n      target: 'shadowOn',\n      action: () => {\n        const btn = this.main.getElemByIdSafe(this.main.activeTool.controls[controlIndex].id);\n        const state = !(btn.getAttribute('data-value') === 'true');\n        this.main.primitiveTool.setShadowOn(state);\n        btn.setAttribute('data-value', state ? 'true' : 'false');\n        setParam('defaultPrimitiveShadowOn', state);\n      },\n      getValue: () => this.main.primitiveTool.shadowOn,\n    };\n  }\n\n  buildPaintBucketControl(controlIndex) {\n    const action = () => {\n      const width = document.getElementById(this.main.activeTool.controls[controlIndex].id).value;\n      console.log('buildPaintBucketControl width: ' + width);\n      // this.main.primitiveTool.setLineWidth(width);\n      setParam('activeFillColor', width);\n    };\n    const getValue = () => this.main.primitiveTool.lineWidth;\n\n    return ControlBuilder.buildInputControl('lineWidth', action, getValue, 1, 99);\n  }\n\n  buildArrowLengthControl(controlIndex) {\n    const action = () => {\n      const width = this.main.getElemByIdSafe(this.main.activeTool.controls[controlIndex].id).value;\n      this.main.primitiveTool.setArrowLength(width);\n      setParam('defaultArrowLength', width);\n    };\n    const getValue = () => this.main.primitiveTool.arrowLength;\n\n    if (this.main.params.availableArrowLengths) {\n      return ControlBuilder.buildDropDownControl('arrowLength', action, getValue, this.main.params.availableArrowLengths);\n    }\n    return ControlBuilder.buildInputControl('arrowLength', action, getValue, 1, 99);\n  }\n\n  static buildInputControl(name, action, getValue, minVal, maxVal) {\n    return {\n      type: 'int',\n      title: name,\n      titleFull: `${name}Full`,\n      target: name,\n      min: minVal,\n      max: maxVal,\n      action,\n      getValue,\n    };\n  }\n\n  static buildDropDownControl(name, action, getValue, availableValues) {\n    return {\n      type: 'dropdown',\n      title: name,\n      titleFull: `${name}Full`,\n      target: name,\n      action,\n      getValue,\n      getAvailableValues: () => availableValues.map(\n        x => ({ value: x, name: x.toString(), title: x.toString() })),\n    };\n  }\n}\n\n"
  },
  {
    "path": "js/customEvents.js",
    "content": "\nexport default class CustomEvents {\n  constructor(element) {\n    this.element = element;\n  }\n  createEvent(name, detail) {\n    return new CustomEvent(name, { detail });\n  }\n  dispatchEvent(name, detail) {\n    this.element.dispatchEvent(this.createEvent(name, detail));\n  }\n  addEventListener(name, callback) {\n    this.element.addEventListener(name, callback);\n  }\n\n  removeEventListener(name, callback) {\n    this.element.removeEventListener(name, callback);\n  }\n\n  useCustomEvent(name, details) {\n    this.dispatchEvent(name, details);\n  }\n\n\n\n  \n}   \n\n"
  },
  {
    "path": "js/filters.js",
    "content": "import { filter } from 'lodash';\nimport { tr } from './translation.js';\nimport { KEYS } from './utils.js';\n\nexport default class Filters {\n    constructor(main) {\n        this.main = main;\n    \n        this.wrapper = main.wrapper.querySelector('.ptro-filters-widget-wrapper');\n        this.input = main.wrapper.querySelector('.ptro-filters-widget-wrapper .ptro-filters-input');\n        this.applyButton = main.wrapper.querySelector('.ptro-filters-widget-wrapper button.ptro-apply');\n        this.closeButton = main.wrapper.querySelector('.ptro-filters-widget-wrapper button.ptro-close');\n        this.errorHolder = main.wrapper.querySelector('.ptro-filters-widget-wrapper .ptro-error');\n        this.filters = ['grayscale', 'sepia', 'invert', 'brightness', 'contrast',  'saturate', ];\n        this.chosenFilter = this.filters[0];\n        this.initCtxImageData = null;\n        this.filterValue = 0;\n        this.filtersForApply = this.createFiltersForApply()\n        \n    }\n\n    createFiltersForApply() {\n        const filtersForApply = {};\n        this.filters.forEach((f) => {\n            filtersForApply[f] = 0;\n        });\n        return filtersForApply;\n    }\n\n    createFilterString() {\n        let filterString = '';\n        for (const key in this.filtersForApply) {\n            if (this.filtersForApply[key] !== 0) {\n                filterString += `${key}(${this.filtersForApply[key]}%) `;\n            }\n        }\n        return filterString;\n    }\n\n    getFilters() {\n        return this.filters.map((f)=>{\n            return {\n                value: f,\n                name: f,\n                title: f,\n            };\n        });\n    }\n    setFilter(filter) {\n        this.chosenFilter = filter;\n        if (this.filtersForApply.hasOwnProperty(filter)) {\n           this.filtersForApply[filter] = this.filterValue;\n        //    console.log('filtersForApply', this.filtersForApply)\n        }\n    }\n\n    \n\n    setPercents(value) {\n        this.filterValue = value;\n        if (this.filtersForApply.hasOwnProperty(this.chosenFilter)) {\n            this.filtersForApply[this.chosenFilter] = value;\n            // console.log('filtersForApply', this.filtersForApply)\n\n        }\n    }\n\n    getFilter() {\n        const filterName = this.filters[0];\n        return filterName \n    }\n\n    applyFilter() {\n        const w = this.main.size.w;\n        const h = this.main.size.h;\n        const tmpData = this.initCtxImageData;\n        const tmpCan = this.main.doc.createElement('canvas');\n        tmpCan.width = w;\n        tmpCan.height = h;\n        tmpCan.getContext('2d').putImageData(tmpData, 0, 0);\n        this.main.ctx.save();\n        this.main.ctx.filter = this.createFilterString();\n        this.main.ctx.drawImage(tmpCan, 0, 0);\n        // this.adjustSizeFull();\n        // this.ctx.restore();\n        this.main.worklog.captureState();\n        // this.main.closeActiveTool();\n    }\n\n    saveInitImg() {\n        this.initCtxImageData = this.main.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n        this.createFiltersForApply();\n    }\n\n    open() {\n        this.wrapper.removeAttribute('hidden');\n        this.opened = true;\n    }\n\n    close() {\n        this.wrapper.setAttribute('hidden', '');\n        this.opened = false;\n    }\n\n    startClose() {\n        this.errorHolder.setAttribute('hidden', '');\n        this.main.closeActiveTool();\n    }\n    getValue(){\n        return\n    }\n\n    static html (main){\n        return '' +\n        '<div class=\"ptro-filters-widget-wrapper ptro-common-widget-wrapper ptro-v-middle\" hidden>' +\n        '   <div class=\"ptro-filters-widget-content ptro-common-widget-content\">' +\n        '       <div class=\"ptro-filters-widget-title\">' + tr('filters') + '</div>' +\n        '       <div class=\"ptro-filters-widget-input-wrapper\">' +\n        '           <div class=\"ptro-error ptro-error-hidden\">' + tr('wrongFilterValue') + '</div>' + \n        '       </div>' +\n        '       <div class=\"ptro-filters-widget-buttons\">' +\n        '           <button type=\"button\" class=\"ptro-apply ptro-color-control\">' + tr('apply') + '</button>' +\n        '           <button type=\"button\" class=\"ptro-close ptro-color-control\">' + tr('close') + '</button>' +\n        '       </div>' +\n        '   </div>' +\n        '</div>';\n    }\n   \n    }"
  },
  {
    "path": "js/inserter.js",
    "content": "import { tr } from './translation';\nimport { genId, KEYS, imgToDataURL } from './utils';\n\nexport default class Inserter {\n  constructor(main) {\n    this.main = main;\n    const extendObj = {\n      extend_top: {\n        internalName: 'extend_top',\n        handle: (img) => {\n          this.tmpImg = img;\n          const oldH = this.main.size.h;\n          const oldW = this.main.size.w;\n          const newH = oldH + img.naturalHeight;\n          const newW = Math.max(oldW, img.naturalWidth);\n          const tmpData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n          this.main.resize(newW, newH);\n          this.main.clearBackground();\n          this.ctx.putImageData(tmpData, 0, img.naturalHeight);\n          this.main.adjustSizeFull();\n          if (img.naturalWidth < oldW) {\n            const offset = Math.round((oldW - img.naturalWidth) / 2);\n            this.main.select.placeAt(offset, 0, offset, oldH, img);\n          } else {\n            this.main.select.placeAt(0, 0, 0, oldH, img);\n          }\n          this.worklog.captureState();\n        },\n      },\n      extend_left: {\n        internalName: 'extend_left',\n        handle: (img) => {\n          this.tmpImg = img;\n          const oldH = this.main.size.h;\n          const oldW = this.main.size.w;\n          const newW = oldW + img.naturalWidth;\n          const newH = Math.max(oldH, img.naturalHeight);\n          const tmpData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n          this.main.resize(newW, newH);\n          this.main.clearBackground();\n          this.ctx.putImageData(tmpData, img.naturalWidth, 0);\n          this.main.adjustSizeFull();\n          if (img.naturalHeight < oldH) {\n            const offset = Math.round((oldH - img.naturalHeight) / 2);\n            this.main.select.placeAt(0, offset, oldW, offset, img);\n          } else {\n            this.main.select.placeAt(0, 0, oldW, 0, img);\n          }\n          this.worklog.captureState();\n        },\n      },\n      extend_right: {\n        internalName: 'extend_right',\n        handle: (img) => {\n          this.tmpImg = img;\n          const oldH = this.main.size.h;\n          const oldW = this.main.size.w;\n          const newW = oldW + img.naturalWidth;\n          const newH = Math.max(oldH, img.naturalHeight);\n          const tmpData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n          this.main.resize(newW, newH);\n          this.main.clearBackground();\n          this.ctx.putImageData(tmpData, 0, 0);\n          this.main.adjustSizeFull();\n          if (img.naturalHeight < oldH) {\n            const offset = Math.round((oldH - img.naturalHeight) / 2);\n            this.main.select.placeAt(oldW, offset, 0, offset, img);\n          } else {\n            this.main.select.placeAt(oldW, 0, 0, 0, img);\n          }\n          this.worklog.captureState();\n        },\n      },\n      extend_down: {\n        internalName: 'extend_down',\n        handle: (img) => {\n          this.tmpImg = img;\n          const oldH = this.main.size.h;\n          const oldW = this.main.size.w;\n          const newH = oldH + img.naturalHeight;\n          const newW = Math.max(oldW, img.naturalWidth);\n          const tmpData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n          this.main.resize(newW, newH);\n          this.main.clearBackground();\n          this.ctx.putImageData(tmpData, 0, 0);\n          this.main.adjustSizeFull();\n          if (img.naturalWidth < oldW) {\n            const offset = Math.round((oldW - img.naturalWidth) / 2);\n            this.main.select.placeAt(offset, oldH, offset, 0, img);\n          } else {\n            this.main.select.placeAt(0, oldH, 0, 0, img);\n          }\n          this.worklog.captureState();\n        },\n      },\n    };\n    const fitObj = {\n      replace_all: {\n        internalName: 'fit',\n        handle: (img) => {\n          if (this.main.params.backplateImgUrl) {\n            this.main.params.backplateImgUrl = undefined;\n            this.main.tabelCell.style.background = '';\n            this.main.canvas.style.backgroundColor = `${this.main.params.backgroundFillColor}ff`;\n            this.pasteOptions = Object.assign({}, fitObj, extendObj);\n            this.activeOption = this.pasteOptions;\n            this.main.wrapper.querySelector('.ptro-paster-select-wrapper').remove();\n            this.main.wrapper.insertAdjacentHTML('beforeend', this.html());\n            this.init(main);\n          }\n          this.main.fitImage(img, this.mimetype);\n        },\n      },\n      paste_over: {\n        internalName: 'over',\n        handle: (img) => {\n          this.tmpImg = img;\n          const oldH = this.main.size.h;\n          const oldW = this.main.size.w;\n          if (img.naturalHeight <= oldH && img.naturalWidth <= oldW) {\n            this.main.select.placeAt(\n              0, 0,\n              oldW - img.naturalWidth,\n              oldH - img.naturalHeight, img);\n          } else if (img.naturalWidth / img.naturalHeight > oldW / oldH) {\n            const newH = oldW * (img.naturalHeight / img.naturalWidth);\n            this.main.select.placeAt(0, 0, 0, oldH - newH, img);\n          } else {\n            const newW = oldH * (img.naturalWidth / img.naturalHeight);\n            this.main.select.placeAt(0, 0, oldW - newW, 0, img);\n          }\n          this.worklog.captureState();\n        },\n      },\n    };\n    if (this.main.params.backplateImgUrl) {\n      this.pasteOptions = Object.assign({}, fitObj);\n      this.activeOption = this.pasteOptions;\n      return;\n    }\n    this.pasteOptions = Object.assign({}, fitObj, extendObj);\n    this.activeOption = this.pasteOptions;\n  }\n\n  init(main) {\n    this.CLIP_DATA_MARKER = 'painterro-image-data';\n    this.ctx = main.ctx;\n    this.main = main;\n    this.worklog = main.worklog;\n    this.selector = main.wrapper.querySelector('.ptro-paster-select-wrapper');\n    this.cancelChoosing();\n    this.img = null;\n    this.mimetype = null; // mime of pending image\n    this.getAvailableOptions().forEach((k) => {\n      const o = this.pasteOptions[k];\n      this.main.getElemByIdSafe(o.id).onclick = () => {\n        if (this.loading) {\n          this.doLater = o.handle;\n        } else {\n          o.handle(this.img);\n        }\n        this.cancelChoosing();\n      };\n    });\n    this.loading = false;\n    this.doLater = null;\n  }\n\n  insert(x, y, w, h) {\n    this.main.ctx.drawImage(this.tmpImg, x, y, w, h);\n    this.main.worklog.reCaptureState();\n  }\n\n  cancelChoosing() {\n    this.selector.setAttribute('hidden', '');\n    this.waitChoice = false;\n  }\n\n  loaded(img, mimetype) {\n    this.img = img;\n    this.mimetype = mimetype;\n    this.loading = false;\n    if (this.doLater) {\n      this.doLater(img);\n      this.doLater = null;\n    }\n  }\n\n  getAvailableOptions() {\n    if (this.main.params.how_to_paste_actions) {\n      // filter out only to selected\n      return Object.keys(this.activeOption).filter(\n        actionName => this.main.params.how_to_paste_actions.includes(actionName),\n      );\n    }\n    return Object.keys(this.activeOption);\n  }\n\n  handleOpen(src, mimetype) {\n    this.startLoading();\n    const handleIt = (source) => {\n      const img = new Image();\n      const empty = this.main.worklog.clean;\n      const replaceAllImmediately = empty && this.main.params.replaceAllOnEmptyBackground;\n      img.onload = () => {\n        if (replaceAllImmediately) {\n          this.main.fitImage(img, mimetype);\n        } else {\n          this.loaded(img, mimetype);\n        }\n        this.finishLoading();\n      };\n      img.onerror = () => {\n        if (typeof this.main.params.onImageFailedOpen === 'function') {\n          this.main.params.onImageFailedOpen();\n        }\n      };\n      // img.crossOrigin = '*'; TODO: try to identify CORS issues earlier?\n      img.src = source;\n      if (!replaceAllImmediately) {\n        const availableOptions = this.getAvailableOptions();\n        if (availableOptions.length !== 1) {\n          this.selector.removeAttribute('hidden');\n          this.waitChoice = true;\n        } else {\n          this.doLater = this.activeOption[availableOptions[0]].handle;\n        }\n      }\n    };\n\n    if (src.indexOf('data') !== 0) {\n      imgToDataURL(src, (dataUrl) => { // if CORS will not allow,\n        // better see error in console than have different canvas mode\n        handleIt(dataUrl);\n      }, () => {\n        if (typeof this.main.params.onImageFailedOpen === 'function') {\n          this.main.params.onImageFailedOpen();\n        }\n      });\n    } else {\n      handleIt(src);\n    }\n  }\n\n  handleKeyDown(evt) {\n    if (this.waitChoice && evt.keyCode === KEYS.esc) {\n      this.cancelChoosing();\n      return true;\n    }\n    if (this.waitChoice && event.keyCode === KEYS.enter) {\n      return true; // mark as handled - user might expect doing save by enter\n    }\n    return false;\n  }\n\n  startLoading() {\n    this.loading = true;\n    if (this.main.toolByName.open.buttonId) {\n      const btn = this.main.getElemByIdSafe(this.main.toolByName.open.buttonId);\n      if (btn) {\n        btn.setAttribute('disabled', 'true');\n      }\n      const icon = this.main.doc.querySelector(`#${this.main.toolByName.open.buttonId} > i`);\n      if (icon) {\n        icon.className = 'ptro-icon ptro-icon-loading ptro-spinning';\n      }\n    }\n  }\n\n  finishLoading() {\n    if (this.main.toolByName.open.buttonId) {\n      const btn = this.main.getElemByIdSafe(this.main.toolByName.open.buttonId);\n      if (btn) {\n        btn.removeAttribute('disabled');\n      }\n      const icon = this.main.doc.querySelector(`#${this.main.toolByName.open.buttonId} > i`);\n      if (icon) {\n        icon.className = 'ptro-icon ptro-icon-open';\n      }\n    }\n    if (this.main.params.onImageLoaded) {\n      this.main.params.onImageLoaded();\n    }\n  }\n\n  static get(main) {\n    if (main.inserter) {\n      return main.inserter;\n    }\n    main.inserter = new Inserter(main);\n    return main.inserter;\n  }\n\n  static controlObjToString(o, btnClassName = '') {\n    const tempObj = o;\n    tempObj.id = genId();\n    return `<button type=\"button\" id=\"${o.id}\" class=\"ptro-selector-btn ptro-color-control ${btnClassName}\">` +\n    `<div><i class=\"ptro-icon ptro-icon-paste_${o.internalName}\"></i></div>` +\n    `<div>${tr(`pasteOptions.${o.internalName}`)}</div>` +\n    '</button>';\n  }\n\n  html() {\n    const bcklOptions = this.main.params.backplateImgUrl;\n    let fitControls = '';\n    let extendControls = '';\n    this.getAvailableOptions().forEach((k) => {\n      if (k === 'replace_all' || k === 'paste_over') {\n        fitControls += `<div class=\"ptro-paster-fit\">\n          ${Inserter.controlObjToString(this.pasteOptions[k], 'ptro-selector-fit')}\n        <div class=\"ptro-paster-wrapper-label\">\n          ${tr(`pasteOptions.${this.pasteOptions[k].internalName}`)}\n        </div></div>`;\n      } else {\n        extendControls += Inserter.controlObjToString(this.pasteOptions[k], 'ptro-selector-extend');\n      }\n    });\n    return '<div class=\"ptro-paster-select-wrapper\" hidden><div class=\"ptro-paster-select ptro-v-middle\">' +\n      '<div class=\"ptro-in ptro-v-middle-in\">' +\n      ` <div class=\"ptro-paster-wrappers-fits\">\n        ${fitControls}\n        ${bcklOptions || !extendControls ? '' : `\n          <div class=\"ptro-paster-select-wrapper-extends\">\n            <div class=\"ptro-paster-extends-items\">\n              ${extendControls}\n            </div>\n            <div class=\"ptro-paster-wrapper-label\">${tr('pasteOptions.extend')}</div>\n          </div>`}\n        </div>\n      </div></div></div>`;\n  }\n}\n\n"
  },
  {
    "path": "js/main.js",
    "content": "import isMobile from 'ismobilejs';\n\nimport '../css/styles.css';\nimport '../css/bar-styles.css';\nimport '../css/icons/ptroiconfont.css';\n\nimport PainterroSelecter from './selecter';\nimport WorkLog from './worklog';\nimport { genId, addDocumentObjectHelpers, KEYS, trim,\n  getScrollbarWidth, distance, logError,setPrimitiveToolValue } from './utils';\nimport PrimitiveTool from './primitive';\nimport ColorPicker, { HexToRGB, rgbToHex } from './colorPicker';\nimport { setDefaults, setParam } from './params';\nimport { tr } from './translation';\nimport ZoomHelper from './zoomHelper';\nimport TextTool from './text';\nimport Resizer from './resizer';\nimport Inserter from './inserter';\nimport Settings from './settings';\nimport ControlBuilder from './controlbuilder';\nimport PaintBucket from './paintBucket';\nimport Filters from './filters';\nimport CustomEvents from './customEvents';\nimport { set } from 'lodash';\n\nclass PainterroProc {\n  constructor(params) {\n    const element =document.querySelector(`#${params.id}`) || document.getElementById('app');\n    this.customEvents = new CustomEvents(element);\n    addDocumentObjectHelpers();\n\n    this.getElemByIdSafe = (id) => {\n      if (!id) {\n        throw new Error(`Can't get element with id=${id}, please create an issue here, we will easily fx it: https://github.com/devforth/painterro/issues/`);\n      }\n      return document.getElementById(id);\n    };\n\n    this.tools = [{\n      name: 'select',\n      hotkey: 's',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.select.activate();\n        this.select.draw();\n      },\n      close: () => {\n        this.select.close();\n        this.toolContainer.style.cursor = 'auto';\n      },\n      eventListner: () => this.select,\n    }, {\n      name: 'crop',\n      hotkey: 'c',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.select.doCrop();\n        this.closeActiveTool();\n      },\n    }, {\n      name: 'pixelize',\n      hotkey: 'p',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.select.doPixelize();\n        this.closeActiveTool();\n      },\n    }, {\n      name: 'line',\n      hotkey: 'l',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'lineColor',\n          target: 'line',\n          titleFull: 'lineColorFull',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line);\n          },\n        }),\n        () => this.controlBuilder.buildLineWidthControl(1),\n        () => this.controlBuilder.buildShadowOnControl(2),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('line');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name: 'arrow',\n      hotkey: 'a',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'lineColor',\n          target: 'line',\n          titleFull: 'lineColorFull',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line);\n          },\n        }),\n        () => this.controlBuilder.buildArrowLengthControl(1),\n        () => this.controlBuilder.buildShadowOnControl(2),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('arrow');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name: 'rect',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'lineColor',\n          titleFull: 'lineColorFull',\n          target: 'line',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line);\n          },\n        }),\n        () => ({\n          type: 'color',\n          title: 'fillColor',\n          titleFull: 'fillColorFull',\n          target: 'fill',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.fill);\n          },\n        }),\n        () => this.controlBuilder.buildLineWidthControl(2),\n        () => this.controlBuilder.buildShadowOnControl(3),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('rect');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name:'filters',\n      controls: [\n        () => ({\n          type: 'dropdown',\n          title: 'filters',\n          titleFull: 'imageFilters',\n          action: () => {\n            const dropdown = this.activeTool.controls[0].id\n            const value = this.getElemByIdSafe(dropdown).value;\n            this.filters.setFilter(value);\n          },\n          getValue: () => this.filters.getFilter(),\n          getAvailableValues: () => this.filters.getFilters(),\n        }),\n        ()=>(\n          {\n            type: 'int',\n            title: 'percents',\n            titleFull: 'percentsFull',\n            min: 0,\n            max: 100,\n            options: {eventOnChange: true},\n            action: () => {\n              const input = this.getElemByIdSafe(this.activeTool.controls[1].id);\n              const value = input.value;\n              this.filters.setPercents(value);\n              this.filters.applyFilter();\n            },\n            getValue: () => 1,\n          }\n        )\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.filters.saveInitImg()\n        this.toolContainer.style.cursor = 'crosshair';\n      }\n    }, \n    {\n      name: 'ellipse',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'lineColor',\n          titleFull: 'lineColorFull',\n          target: 'line',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line);\n          },\n        }),\n        () => ({\n          type: 'color',\n          title: 'fillColor',\n          titleFull: 'fillColorFull',\n          target: 'fill',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.fill);\n          },\n        }),\n        () => this.controlBuilder.buildLineWidthControl(2),\n        () => this.controlBuilder.buildShadowOnControl(3),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('ellipse');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name: 'brush',\n      hotkey: 'b',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'lineColor',\n          target: 'line',\n          titleFull: 'lineColorFull',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line);\n          },\n        }),\n        () => this.controlBuilder.buildLineWidthControl(1),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('brush');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name: 'eraser',\n      controls: [\n        () => this.controlBuilder.buildEraserWidthControl(0),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('eraser');\n      },\n      eventListner: () => this.primitiveTool,\n    }, {\n      name: 'text',\n      hotkey: 't',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'textColor',\n          titleFull: 'textColorFull',\n          target: 'line',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.line, (c) => {\n              this.textTool.setFontColor(c.alphaColor);\n            });\n          },\n        }),\n        () =>this.controlBuilder.buildFontSizeControl(1),\n        () => ({\n          type: 'dropdown',\n          title: 'fontName',\n          titleFull: 'fontNameFull',\n          target: 'fontName',\n          action: () => {\n            const dropdown = this.getElemByIdSafe(this.activeTool.controls[2].id);\n            const font = dropdown.value;\n            this.textTool.setFont(font);\n          },\n          getValue: () => this.textTool.getFont(),\n          getAvailableValues: () => this.textTool.getFonts(),\n        }),\n        () => ({\n          type: 'bool',\n          title: 'fontIsBold',\n          titleFull: 'fontIsBoldFull',\n          target: 'fontIsBold',\n          action: () => {\n            const btn = this.getElemByIdSafe(this.activeTool.controls[3].id);\n            const state = !(btn.getAttribute('data-value') === 'true');\n            this.textTool.setFontIsBold(state);\n            setParam('defaultFontBold', state);\n            btn.setAttribute('data-value', state ? 'true' : 'false'); // invert\n          },\n          getValue: () => this.textTool.isBold,\n        }),\n        () => ({\n          type: 'bool',\n          title: 'fontIsItalic',\n          titleFull: 'fontIsItalicFull',\n          target: 'fontIsItalic',\n          action: () => {\n            const btn = this.getElemByIdSafe(this.activeTool.controls[4].id);\n            const state = !(btn.getAttribute('data-value') === 'true'); // invert\n            this.textTool.setFontIsItalic(state);\n            setParam('defaultFontItalic', state);\n            btn.setAttribute('data-value', state ? 'true' : 'false');\n          },\n          getValue: () => this.textTool.isItalic,\n        }),\n        () => ({\n          type: 'bool',\n          title: 'fontStrokeAndShadow',\n          titleFull: 'fontStrokeAndShadowFull',\n          target: 'fontStrokeAndShadow',\n          action: () => {\n            const btn = this.getElemByIdSafe(this.activeTool.controls[5].id);\n            const nextState = !(btn.getAttribute('data-value') === 'true');\n            this.textTool.setStrokeOn(nextState);\n            setParam('defaultTextStrokeAndShadow', nextState);\n            btn.setAttribute('data-value', nextState ? 'true' : 'false');\n          },\n          getValue: () => this.textTool.strokeOn,\n        }),\n      ],\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.textTool.setFontColor(this.colorWidgetState.line.alphaColor);\n        // this.textTool.setStrokeColor(this.colorWidgetState.stroke.alphaColor);\n        this.toolContainer.style.cursor = 'crosshair';\n      },\n      close: () => {\n        this.textTool.close();\n      },\n      eventListner: () => this.textTool,\n    }, {\n      name: 'rotate',\n      hotkey: 'r',\n      activate: () => {\n        if (this.initText) {\n          this.wrapper.click();\n        }\n        const w = this.size.w;\n        const h = this.size.h;\n        const tmpData = this.ctx.getImageData(0, 0, this.size.w, this.size.h);\n        const tmpCan = this.doc.createElement('canvas');\n        tmpCan.width = w;\n        tmpCan.height = h;\n        tmpCan.getContext('2d').putImageData(tmpData, 0, 0);\n        this.resize(h, w);\n        this.ctx.save();\n        this.ctx.translate(h / 2, w / 2);\n        this.ctx.rotate((90 * Math.PI) / 180);\n        this.ctx.drawImage(tmpCan, -w / 2, -h / 2);\n        this.adjustSizeFull();\n        this.ctx.restore();\n        this.worklog.captureState();\n        this.closeActiveTool();\n      },\n    }, {\n      name: 'resize',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.resizer.open();\n      },\n      close: () => {\n        this.resizer.close();\n      },\n      eventListner: () => this.resizer,\n    },\n    {\n      name: 'undo',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.worklog.undoState();\n      },\n      eventListner: () => this.resizer,\n    },\n    {\n      name: 'redo',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.worklog.redoState();\n      },\n      eventListner: () => this.resizer,\n    },\n    {\n      name: 'settings',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.settings.open();\n      },\n      close: () => {\n        this.settings.close();\n      },\n      eventListner: () => this.settings,\n    },\n    {\n      name: 'zoomout',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.zoomButtonActive = true;\n        const canvas = this.canvas;\n        const gbr = canvas.getBoundingClientRect();\n        const e = {\n          wheelDelta: -120,\n          clientX: gbr.right / 2,\n          clientY: gbr.bottom / 2,\n        };\n\n        this.curCord = [\n          (e.clientX - this.elLeft()) + this.scroller.scrollLeft,\n          (e.clientY - this.elTop()) + this.scroller.scrollTop,\n        ];\n\n        const scale = this.getScale();\n        this.curCord = [this.curCord[0] * scale, this.curCord[1] * scale];\n\n        this.zoomImage(e);\n      },\n    },\n    {\n      name: 'zoomin',\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.zoomButtonActive = true;\n        const canvas = this.canvas;\n        const gbr = canvas.getBoundingClientRect();\n        const e = {\n          wheelDelta: 120,\n          clientX: gbr.right / 2,\n          clientY: gbr.bottom / 2,\n        };\n\n        this.curCord = [\n          (e.clientX - this.elLeft()) + this.scroller.scrollLeft,\n          (e.clientY - this.elTop()) + this.scroller.scrollTop,\n        ];\n\n        const scale = this.getScale();\n        this.curCord = [this.curCord[0] * scale, this.curCord[1] * scale];\n\n        this.zoomImage(e);\n      },\n    },\n\n    {\n      name: 'bucket',\n      hotkey: 'f',\n      controls: [\n        () => ({\n          type: 'color',\n          title: 'fillColor',\n          target: 'fill',\n          titleFull: 'fillColorFull',\n          action: () => {\n            this.colorPicker.open(this.colorWidgetState.fill);\n          },\n        }),\n      ],\n      activate: () => {\n        // this.clear();\n        // this.closeActiveTool();\n        this.toolContainer.style.cursor = 'crosshair';\n        this.primitiveTool.activate('bucket');\n      },\n      eventListner: () => this.paintBucket,\n    },\n\n    {\n      name: 'clear',\n      activate: () => {\n        this.clear();\n        this.closeActiveTool();\n      },\n    },\n\n    {\n      name: 'save',\n      right: true,\n      hotkey: () => this.params.saveByEnter ? 'enter' : false,\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.save();\n        this.closeActiveTool();\n      },\n    }, {\n      name: 'open',\n      right: true,\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        this.closeActiveTool();\n        const input = this.getElemByIdSafe(this.fileInputId);\n        input.onchange = (event) => {\n          const files = event.target.files || event.dataTransfer.files;\n          if (!files.length) {\n            return;\n          }\n          this.openFile(files[0]);\n          input.value = ''; // to allow reopen\n        };\n        input.click();\n      },\n    }, {\n      name: 'close',\n      hotkey: () => this.params.hideByEsc ? 'esc' : false,\n      right: true,\n      activate: () => {\n        if (this.initText) this.wrapper.click();\n        const doClose = () => {\n          this.closeActiveTool();\n          this.close();\n          this.hide();\n        };\n\n        if (this.params.onBeforeClose) {\n          this.params.onBeforeClose(this.hasUnsaved, doClose);\n        } else {\n          doClose();\n        }\n      },\n    },...params.customTools?.map((ct)=>{return {name:ct.name,activate:ct.callBack,iconUrl:ct.iconUrl}}) || []];\n\n    this.params = setDefaults(params, this.tools.map(t => t.name));    \n    \n    this.colorWidgetState = {\n      line: {\n        target: 'line',\n        palleteColor: this.params.activeColor,\n        alpha: this.params.activeColorAlpha,\n        alphaColor: this.params.activeAlphaColor,\n      },\n      fill: {\n        target: 'fill',\n        palleteColor: this.params.activeFillColor,\n        alpha: this.params.activeFillColorAlpha,\n        alphaColor: this.params.activeFillAlphaColor,\n      },\n      bg: {\n        target: 'bg',\n        palleteColor: this.params.backgroundFillColor,\n        alpha: this.params.backgroundFillColorAlpha,\n        alphaColor: this.params.backgroundFillAlphaColor,\n      },\n      // stroke: {\n      //   target: 'stroke',\n      //   palleteColor: this.params.textStrokeColor,\n      //   alpha: this.params.textStrokeColorAlpha,\n      //   alphaColor: this.params.textStrokeAlphaColor,\n      // },\n    };\n    this.currentBackground = this.colorWidgetState.bg.alphaColor;\n    this.currentBackgroundAlpha = this.colorWidgetState.bg.alpha;\n\n\n    this.controlBuilder = new ControlBuilder(this);\n\n    this.isMobile = isMobile.any;\n    this.toolByName = {};\n    this.toolByKeyCode = {};\n    this.tools.forEach((t) => {\n      if (t.controls) {\n        t.controls = t.controls.map(t => t());\n      }\n      this.toolByName[t.name] = t;\n      if (t.hotkey instanceof Function) {\n        t.hotkey = t.hotkey();\n      }\n\n      if (t.hotkey && !this.params.hiddenTools.includes(t.name)) {\n        if (!KEYS[t.hotkey]) {\n          throw new Error(`Key code for ${t.hotkey} not defined in KEYS`);\n        }\n        this.toolByKeyCode[KEYS[t.hotkey]] = t;\n      }\n    });\n    this.activeTool = undefined;\n    this.zoom = false;\n    this.ratioRelation = undefined;\n    this.id = this.params.id;\n    this.saving = false;\n\n    if (this.id === undefined) {\n      this.id = genId();\n      this.holderId = genId();\n      this.holderEl = document.createElement('div');\n      this.holderEl.id = this.holderId;\n      this.holderEl.className = 'ptro-holder-wrapper';\n      document.body.appendChild(this.holderEl);\n      this.holderEl.innerHTML = `<div id='${this.id}' class=\"ptro-holder\"></div>`;\n      this.baseEl = this.getElemByIdSafe(this.id);\n    } else {\n      this.baseEl = this.getElemByIdSafe(this.id);\n      this.holderEl = null;\n    }\n    let bar = '';\n    let rightBar = '';\n    this.tools.filter(t => !this.params.hiddenTools.includes(t.name)).forEach((b) => {\n      const id = genId();\n      b.buttonId = id;\n      const hotkey = b.hotkey ? ` [${b.hotkey.toUpperCase()}]` : '';\n      const btn = b.iconUrl\n      ? `<button type=\"button\" aria-label=${b.name} class=\"ptro-icon-btn ptro-color-control\" title=\"${b.name}\" ` +\n      `id=\"${id}\" >` +\n      `<img width=\"14\" src=\"${b.iconUrl}\" alt=\"${`${b.name}`}\" /></button>`\n      : `<button type=\"button\" aria-label=${b.name} class=\"ptro-icon-btn ptro-color-control\" title=\"${tr(`tools.${b.name}`)}${hotkey}\" ` +\n        `id=\"${id}\" >` +\n        `<i class=\"ptro-icon ptro-icon-${b.name}\"></i></button>`;\n      if (b.right) {\n        rightBar += btn;\n      } else {\n        bar += btn;\n      }\n    });\n\n    this.inserter = Inserter.get(this);\n\n    const cropper = '<div class=\"ptro-crp-el\">' +\n      `${PainterroSelecter.code()}${TextTool.code()}</div>`;\n\n    this.loadedName = '';\n    this.doc = document;\n    this.wrapper = this.doc.createElement('div');\n    this.wrapper.id = `${this.id}-wrapper`;\n    this.wrapper.className = 'ptro-wrapper';\n    this.wrapper.innerHTML =\n      '<div class=\"ptro-scroller\">' +\n        '<div class=\"ptro-center-table\">' +\n          '<div class=\"ptro-center-tablecell\">' +\n            `<canvas id=\"${this.id}-canvas\"></canvas>` +\n            `<div class=\"ptro-substrate\"></div>${cropper}` +\n          '</div>' +\n        '</div>' +\n      `</div>${\n        ColorPicker.html() +\n        ZoomHelper.html() +\n        Resizer.html() +\n        Settings.html(this) +\n        Filters.html(this) +\n        this.inserter.html()}`;\n    this.baseEl.appendChild(this.wrapper);\n    this.scroller = this.doc.querySelector(`#${this.id}-wrapper .ptro-scroller`);\n    this.bar = this.doc.createElement('div');\n    this.bar.id = `${this.id}-bar`;\n    this.bar.className = 'ptro-bar ptro-color-main';\n    this.fileInputId = genId();\n    this.bar.innerHTML =\n      `<div>${bar}` +\n      '<span class=\"ptro-tool-controls\"></span>' +\n      '<span class=\"ptro-info\"></span>' +\n      `<span class=\"ptro-bar-right\">${rightBar}</span>` +\n      `<input id=\"${this.fileInputId}\" type=\"file\" style=\"display: none\" value=\"none\" accept=\"image/x-png,image/png,image/gif,image/jpeg\" /></div>`;\n    if (this.isMobile) {\n      this.bar.style['overflow-x'] = 'auto';\n    }\n\n    this.baseEl.appendChild(this.bar);\n    const style = this.doc.createElement('style');\n    style.type = 'text/css';\n    style.innerHTML = this.params.styles;\n    this.baseEl.appendChild(style);\n\n    // this.baseEl.innerHTML = '<iframe class=\"ptro-iframe\"></iframe>';\n    // this.iframe = this.baseEl.getElementsByTagName('iframe')[0];\n    // this.doc = this.iframe.contentDocument || this.iframe.contentWindow.document;\n    // this.doc.body.innerHTML = html;\n    this.saveBtn = this.baseEl.querySelector(`#${this.toolByName.save.buttonId}`);\n    if (this.toolByName.save.buttonId && this.saveBtn) {\n      this.saveBtn.setAttribute('disabled', 'true');\n    }\n    this.body = this.doc.body;\n    this.info = this.doc.querySelector(`#${this.id}-bar .ptro-info`);\n    this.canvas = this.doc.querySelector(`#${this.id}-canvas`);\n    this.ctx = this.canvas.getContext('2d');\n    this.toolControls = this.doc.querySelector(`#${this.id}-bar .ptro-tool-controls`);\n    this.toolContainer = this.doc.querySelector(`#${this.id}-wrapper .ptro-crp-el`);\n    this.substrate = this.doc.querySelector(`#${this.id}-wrapper .ptro-substrate`);\n    this.zoomHelper = new ZoomHelper(this);\n    this.zoomButtonActive = false;\n    this.select = new PainterroSelecter(this, (notEmpty) => {\n      [this.toolByName.crop, this.toolByName.pixelize].forEach((c) => {\n        this.setToolEnabled(c, notEmpty);\n      });\n    });\n    if (this.params.backplateImgUrl) {\n      this.tabelCell = this.canvas.parentElement;\n      this.tabelCell.style.backgroundImage = `url(${this.params.backplateImgUrl})`;\n      this.tabelCell.style.backgroundRepeat = 'no-repeat';\n      this.tabelCell.style.backgroundPosition = 'center center';\n      const img = new Image();\n      img.onload = () => {\n        this.resize(img.naturalWidth, img.naturalHeight);\n        this.adjustSizeFull();\n        this.worklog.captureState();\n        this.tabelCell.style.backgroundSize = `${window.getComputedStyle(this.substrate).width} ${window.getComputedStyle(this.substrate).height}`;\n      };\n      img.src = this.params.backplateImgUrl;\n    }\n    this.resizer = new Resizer(this);\n    this.settings = new Settings(this);\n    this.primitiveTool = new PrimitiveTool(this);\n    this.primitiveTool.setShadowOn(this.params.defaultPrimitiveShadowOn);\n    this.primitiveTool.setLineWidth(this.params.defaultLineWidth);\n    this.primitiveTool.setArrowLength(this.params.defaultArrowLength);\n    this.primitiveTool.setEraserWidth(this.params.defaultEraserWidth);\n    this.primitiveTool.setPixelSize(this.params.defaultPixelSize);\n    this.hasUnsaved = false;\n    this.worklog = new WorkLog(this, (state) => {\n      if (this.saveBtn && !state.initial) {\n        this.saveBtn.removeAttribute('disabled');\n        this.hasUnsaved = true;\n      }\n      this.setToolEnabled(this.toolByName.undo, !state.first);\n      this.setToolEnabled(this.toolByName.redo, !state.last);\n      if (this.params.onChange) {\n        this.params.onChange.call(this, {\n          image: this.imageSaver,\n          operationsDone: this.worklog.current.prevCount,\n          realesedMemoryOperations: this.worklog.clearedCount,\n        });\n      }\n    });\n    this.inserter.init(this);\n    this.paintBucket = new PaintBucket(this);\n    this.textTool = new TextTool(this);\n    this.filters = new Filters(this);\n    this.colorPicker = new ColorPicker(this, (widgetState) => {\n      this.colorWidgetState[widgetState.target] = widgetState;\n      this.doc.querySelector(\n        `#${this.id} .ptro-color-btn[data-id='${widgetState.target}']`).style['background-color'] =\n        widgetState.alphaColor;\n      const palletRGB = HexToRGB(widgetState.palleteColor);\n      if (palletRGB !== undefined) {\n        widgetState.palleteColor = rgbToHex(palletRGB.r, palletRGB.g, palletRGB.b);\n        if (widgetState.target === 'line') {\n          setParam('activeColor', widgetState.palleteColor);\n          setParam('activeColorAlpha', widgetState.alpha);\n        } else if (widgetState.target === 'fill') {\n          setParam('activeFillColor', widgetState.palleteColor);\n          setParam('activeFillColorAlpha', widgetState.alpha);\n        } else if (widgetState.target === 'bg') {\n          setParam('backgroundFillColor', widgetState.palleteColor);\n          setParam('backgroundFillColorAlpha', widgetState.alpha);\n        } else if (widgetState.target === 'stroke') {\n          setParam('textStrokeColor', widgetState.palleteColor);\n          setParam('textStrokeColorAlpha', widgetState.alpha);\n        }\n      }\n    });\n\n    this.defaultTool = this.toolByName[this.params.defaultTool];\n\n    this.tools.filter(t => this.params.hiddenTools.indexOf(t.name) === -1).forEach((b) => {\n      this.getBtnEl(b).onclick = () => {\n        if (b === this.defaultTool && this.activeTool === b) {\n          return;\n        }\n        const currentActive = this.activeTool;\n        this.closeActiveTool(true);\n        if (currentActive !== b) {\n          this.setActiveTool(b);\n          this.customEvents.dispatchEvent('changeActiveTool', b);\n\n        } else {\n          this.setActiveTool(this.defaultTool);\n        }\n      };\n      this.getBtnEl(b).ontouch = this.getBtnEl(b).onclick;\n    });\n\n    this.getBtnEl(this.defaultTool).click();\n\n    this.imageSaver = {\n      /**\n       * Returns image as base64 data url\n       * @param {string} type - type of data url, default image/png\n       * @param {string} quality - number from 0 to 1, works for `image/jpeg` or `image/webp`\n       */\n      asDataURL: (type, quality) => {\n        let realType = type;\n        if (realType === undefined) {\n          if (this.loadedImageType) {\n            realType = this.loadedImageType;\n          } else {\n            realType = 'image/png';\n          }\n        }\n        return this.getAsUri(realType, quality);\n      },\n      asBlob: (type, quality) => {\n        let realType = type;\n        if (realType === undefined) {\n          if (this.loadedImageType) {\n            realType = this.loadedImageType;\n          } else {\n            realType = 'image/png';\n          }\n        }\n        const uri = this.getAsUri(realType, quality);\n        const byteString = atob(uri.split(',')[1]);\n        const ab = new ArrayBuffer(byteString.length);\n        const ia = new Uint8Array(ab);\n        for (let i = 0; i < byteString.length; i += 1) {\n          ia[i] = byteString.charCodeAt(i);\n        }\n        return new Blob([ab], {\n          type: realType,\n        });\n      },\n      getOriginalMimeType: () => this.loadedImageType,\n      hasAlphaChannel: () => {\n        const data = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height).data;\n        for (let i = 3, n = data.length; i < n; i += 4) {\n          if (data[i] < 255) {\n            return true;\n          }\n        }\n        return false;\n      },\n      suggestedFileName: (type) => {\n        let realType = type;\n        if (realType === undefined) {\n          realType = 'png';\n        }\n        return `${(this.loadedName || `image-${genId()}`)}.${realType}`;\n      },\n      getWidth: () => this.size.w,\n      getHeight: () => this.size.h,\n    };\n\n    this.initEventHandlers();\n    this.hide();\n    this.zoomFactor = 1;\n  }\n\n  setToolEnabled(tool, state) {\n    if (tool.buttonId) {\n      const btn = this.getElemByIdSafe(tool.buttonId);\n      if (state) {\n        btn.removeAttribute('disabled');\n      } else {\n        btn.setAttribute('disabled', 'true');\n      }\n    }\n  }\n  getAsUri(type, quality) {\n    let realQuality = quality;\n    if (realQuality === undefined) {\n      realQuality = 0.92;\n    }\n    return this.canvas.toDataURL(type, realQuality);\n  }\n\n  getBtnEl(tool) {\n    return this.getElemByIdSafe(tool.buttonId);\n  }\n\n  save() {\n    if (this.saving) {\n      return this;\n    }\n    this.saving = true;\n    const btn = this.baseEl.querySelector(`#${this.toolByName.save.buttonId}`);\n    const icon = this.baseEl.querySelector(`#${this.toolByName.save.buttonId} > i`);\n    if (this.toolByName.save.buttonId && btn) {\n      btn.setAttribute('disabled', 'true');\n    }\n    this.hasUnsaved = false;\n\n    if (icon) {\n      icon.className = 'ptro-icon ptro-icon-loading ptro-spinning';\n    }\n\n    if (this.params.saveHandler !== undefined) {\n      this.params.saveHandler(this.imageSaver, (hide) => {\n        if (hide === true) {\n          this.hide();\n        }\n        if (icon) {\n          icon.className = 'ptro-icon ptro-icon-save';\n        }\n        this.saving = false;\n      });\n    } else {\n      logError('No saveHandler defined, please check documentation');\n      if (icon) {\n        icon.className = 'ptro-icon ptro-icon-save';\n      }\n      this.saving = false;\n    }\n    return this;\n  }\n\n  close() {\n    if (this.params.onClose !== undefined) {\n      this.params.onClose();\n    }\n  }\n\n  closeActiveTool(doNotSelect) {\n    if (this.activeTool !== undefined) {\n      if (this.activeTool.close !== undefined) {\n        this.activeTool.close();\n      }\n      this.toolControls.innerHTML = '';\n      const btnEl = this.getBtnEl(this.activeTool);\n      if (btnEl) {\n        btnEl.className =\n        this.getBtnEl(this.activeTool).className.replace(' ptro-color-active-control', '');\n      }\n      this.activeTool = undefined;\n    }\n    if (doNotSelect !== true) {\n      this.setActiveTool(this.defaultTool);\n    }\n  }\n\n  handleToolEvent(eventHandler, event) {\n    if (this.select.imagePlaced || this.select.area.activated) {\n      return this.select[eventHandler](event);\n    }\n    if (this.activeTool && this.activeTool.eventListner) {\n      const listner = this.activeTool.eventListner();\n      if (listner[eventHandler]) {\n        return listner[eventHandler](event);\n      }\n    }\n    \n    return false;\n  }\n\n  handleClipCopyEvent(evt) {\n    let handled = false;\n    const clipFormat = 'image/png';\n    if (evt.keyCode === KEYS.c && (evt.ctrlKey || evt.metaKey)) {\n      if (!this.inserter.waitChoice && !this.select.imagePlaced && this.select.shown) {\n        const a = this.select.area;\n        const w = a.bottoml[0] - a.topl[0];\n        const h = a.bottoml[1] - a.topl[1];\n        const tmpCan = this.doc.createElement('canvas');\n        tmpCan.width = w;\n        tmpCan.height = h;\n        const tmpCtx = tmpCan.getContext('2d');\n        tmpCtx.drawImage(this.canvas, -a.topl[0], -a.topl[1]);\n        tmpCan.toBlob((b) => {\n          /* eslint no-undef: \"off\" */\n          navigator.clipboard.write([new ClipboardItem({ [clipFormat]: b })]);\n        }, clipFormat, 1.0);\n        handled = true;\n      } else {\n        this.canvas.toBlob((b) => {\n          /* eslint no-undef: \"off\" */\n          navigator.clipboard.write([new ClipboardItem({ [clipFormat]: b })]);\n        }, clipFormat, 1.0);\n        handled = true;\n      }\n    }\n    return handled;\n  }\n  zoomImage({ wheelDelta, clientX, clientY }, forceWheenDelta, forceZoomForce) {\n    let whD = wheelDelta;\n    if (forceWheenDelta !== undefined) {\n      whD = forceWheenDelta;\n    }\n    let minFactor = 1;\n    if (this.size.w > this.wrapper.documentClientWidth) {\n      minFactor = Math.min(minFactor, this.wrapper.documentClientWidth / this.size.w);\n    }\n    if (this.size.h > this.wrapper.documentClientHeight) {\n      minFactor = Math.min(minFactor, this.wrapper.documentClientHeight / this.size.h);\n    }\n    if (!this.zoom && this.zoomFactor > minFactor) {\n      this.zoomFactor = minFactor;\n    }\n    let zoomForce = 0.2;\n    if (forceZoomForce !== undefined) {\n      zoomForce = forceZoomForce;\n    }\n\n    this.zoomFactor += Math.sign(whD) * zoomForce;\n    if (this.zoomFactor < minFactor) {\n      this.zoom = false;\n      this.zoomFactor = minFactor;\n    } else {\n      this.zoom = true;\n    }\n    this.adjustSizeFull();\n    this.select.adjustPosition();\n    if (this.zoom) {\n      this.scroller.scrollLeft = (this.curCord[0] / this.getScale()) -\n        (clientX - this.wrapper.documentOffsetLeft);\n      this.scroller.scrollTop = (this.curCord[1] / this.getScale()) -\n        (clientY - this.wrapper.documentOffsetTop);\n    }\n  }\n  initEventHandlers() {\n    this.documentHandlers = {\n      mousedown: (e) => {\n        if (this.shown) {\n          if (this.worklog.empty &&\n             (e.target.className.indexOf('ptro-crp-el') !== -1 ||\n              e.target.className.indexOf('ptro-icon') !== -1 ||\n              e.target.className.indexOf('ptro-named-btn') !== -1)) {\n            this.clearBackground(); // clear initText\n          }\n          if (this.colorPicker.handleMouseDown(e) !== true) {\n            this.handleToolEvent('handleMouseDown', e);\n          }\n        }\n      },\n      touchstart: (e) => {\n        if (e.touches.length === 1) {\n          e.clientX = e.changedTouches[0].clientX;\n          e.clientY = e.changedTouches[0].clientY;\n          this.documentHandlers.mousedown(e);\n        } else if (e.touches.length === 2) {\n          const fingersDist = distance({\n            x: e.touches[0].clientX,\n            y: e.touches[0].clientY,\n          }, {\n            x: e.touches[1].clientX,\n            y: e.touches[1].clientY,\n          });\n          this.lastFingerDist = fingersDist;\n        }\n      },\n      touchend: (e) => {\n        e.clientX = e.changedTouches[0].clientX;\n        e.clientY = e.changedTouches[0].clientY;\n        this.documentHandlers.mouseup(e);\n      },\n      touchmove: (e) => {\n        if (e.touches.length === 1) {\n          e.clientX = e.touches[0].clientX;\n          e.clientY = e.touches[0].clientY;\n          this.documentHandlers.mousemove(e);\n        } else if (e.touches.length === 2) {\n          const fingersDist = distance({\n            x: e.touches[0].clientX,\n            y: e.touches[0].clientY,\n          }, {\n            x: e.touches[1].clientX,\n            y: e.touches[1].clientY,\n          });\n\n          const center = {\n            x: (e.touches[0].clientX + e.touches[1].clientX) / 2,\n            y: (e.touches[0].clientY + e.touches[1].clientY) / 2,\n          };\n\n          e.clientX = center.x;\n          e.clientY = center.y;\n\n          const fingerDistDiff = Math.abs(fingersDist - this.lastFingerDist);\n          const zoomForce = (fingersDist > this.lastFingerDist ? 1 : -1);\n\n          this.documentHandlers.wheel(e, zoomForce, true, fingerDistDiff * 0.001);\n          \n          this.lastFingerDist = fingersDist;\n          e.stopPropagation();\n          if (!this.zoomButtonActive) e.preventDefault();\n        }\n      },\n      mousemove: (e) => {\n        const isEvenFromPtro = e.target.classList[0] === 'ptro-crp-el' || e.target.classList[0] === 'ptro-bar'; \n        if (this.shown) {\n          this.handleToolEvent('handleMouseMove', e);\n          this.colorPicker.handleMouseMove(e);\n          this.zoomHelper.handleMouseMove(e);\n          this.curCord = [\n            (e.clientX - this.elLeft()) + this.scroller.scrollLeft,\n            (e.clientY - this.elTop()) + this.scroller.scrollTop,\n          ];\n          const scale = this.getScale();\n          this.curCord = [this.curCord[0] * scale, this.curCord[1] * scale];\n          if (typeof e.target.tagName !== \"undefined\" && e.target.tagName.toLowerCase() !== 'input'\n              && e.target.tagName.toLowerCase() !== 'button' && e.target.tagName.toLowerCase() !== 'i'\n              && e.target.tagName.toLowerCase() !== 'select') {\n            // prevent default only if we are in paintero area    \n            if (!this.zoomButtonActive && isEvenFromPtro) e.preventDefault();\n          }\n        }\n      },\n      mouseup: (e) => {\n        if (this.shown) {\n          this.handleToolEvent('handleMouseUp', e);\n          this.colorPicker.handleMouseUp(e);\n        }\n      },\n      wheel: (e, forceWheenDelta, forceCtrlKey, zoomForce) => {\n        if (this.shown && !this.params.disableWheelZoom) {\n          if (forceCtrlKey !== undefined ? forceCtrlKey : e.ctrlKey) {\n            this.zoomImage(e, forceWheenDelta, zoomForce);\n            e.preventDefault();\n          }\n        }\n      },\n      keydown: (e) => {\n        const argetEl = event.target;\n        const ignoreForSelectors = ['input', 'textarea', 'div[contenteditable]'];\n\n        if (ignoreForSelectors.some(selector => argetEl.matches(selector))) {\n          return; // ignore all elemetents which could be focused\n        }\n        if (this.shown) {\n          if (this.colorPicker.handleKeyDown(e)) {\n            return;\n          }\n          if (this.handleClipCopyEvent(e)) {\n            return;\n          }\n          const evt = window.event ? event : e;\n          if (this.handleToolEvent('handleKeyDown', evt)) {\n            return;\n          }\n          if (\n            (evt.keyCode === KEYS.y && evt.ctrlKey) ||\n            (evt.keyCode === KEYS.z && evt.ctrlKey && evt.shiftKey)) {\n            this.worklog.redoState();\n            e.preventDefault();\n            if (this.params.userRedo) {\n              this.params.userRedo.call();\n            }\n          } else if (evt.keyCode === KEYS.z && evt.ctrlKey) {\n            this.worklog.undoState();\n            e.preventDefault();\n            if (this.params.userUndo) {\n              this.params.userUndo.call();\n            }\n          }\n          if (this.toolByKeyCode[event.keyCode]) {\n            this.getBtnEl(this.toolByKeyCode[event.keyCode]).click();\n            e.stopPropagation();\n            e.preventDefault();\n          }\n          if (this.saveBtn) {\n            if (evt.keyCode === KEYS.s && evt.ctrlKey) {\n              if (this.initText) this.wrapper.click();\n              this.save();\n              evt.preventDefault();\n            }\n          }\n        }\n      },\n      paste: (event) => {\n        if (this.initText) this.wrapper.click();\n        if (this.shown) {\n          const items = (event.clipboardData || event.originalEvent.clipboardData).items;\n          Object.keys(items).forEach((k) => {\n            const item = items[k];\n            if (item.kind === 'file' && item.type.split('/')[0] === 'image') {\n              this.openFile(item.getAsFile());\n              event.preventDefault();\n              event.stopPropagation();\n            }\n          });\n        }\n      },\n      dragover: (event) => {\n        if (this.shown) {\n          const mainClass = event.target.classList[0];\n          if (mainClass === 'ptro-crp-el' || mainClass === 'ptro-bar') {\n            this.bar.className = 'ptro-bar ptro-color-main ptro-bar-dragover';\n          }\n          event.preventDefault();\n        }\n      },\n      dragleave: () => {\n        if (this.shown) {\n          this.bar.className = 'ptro-bar ptro-color-main';\n        }\n      },\n      drop: (event) => {\n        if (this.shown) {\n          this.bar.className = 'ptro-bar ptro-color-main';\n          event.preventDefault();\n          const file = event.dataTransfer.files[0];\n          if (file) {\n            this.openFile(file);\n          } else {\n            const text = event.dataTransfer.getData('text/html');\n            const srcRe = /src.*?=['\"](.+?)['\"]/;\n            const srcMatch = srcRe.exec(text);\n            this.inserter.handleOpen(srcMatch[1]);\n          }\n        }\n      },\n    };\n\n    this.windowHandlers = {\n      resize: () => {\n        if (this.shown) {\n          this.adjustSizeFull();\n          this.syncToolElement();\n        }\n      },\n    };\n    this.listenersInstalled = false;\n  }\n\n  attachEventHandlers() {\n    if (this.listenersInstalled) {\n      return;\n    }\n    // passive: false fixes Unable to preventDefault inside passive event\n    // listener due to target being treated as passive\n    Object.keys(this.documentHandlers).forEach((key) => {\n      this.doc.addEventListener(key, this.documentHandlers[key], { passive: false });\n    });\n\n    Object.keys(this.windowHandlers).forEach((key) => {\n      window.addEventListener(key, this.windowHandlers[key], { passive: false });\n    });\n    this.listenersInstalled = true;\n  }\n\n  removeEventHandlers() {\n    if (!this.listenersInstalled) {\n      return;\n    }\n    Object.keys(this.documentHandlers).forEach((key) => {\n      this.doc.removeEventListener(key, this.documentHandlers[key]);\n    });\n    Object.keys(this.windowHandlers).forEach((key) => {\n      window.removeEventListener(key, this.windowHandlers[key]);\n    });\n\n    this.listenersInstalled = false;\n  }\n\n  elLeft() {\n    return this.toolContainer.documentOffsetLeft + this.scroller.scrollLeft;\n  }\n\n  elTop() {\n    return this.toolContainer.documentOffsetTop + this.scroller.scrollTop;\n  }\n\n  fitImage(img, mimetype) {\n    this.loadedImageType = mimetype;\n    this.resize(img.naturalWidth, img.naturalHeight);\n    this.ctx.drawImage(img, 0, 0);\n    const minValue = Math.min(this.wrapper.documentClientHeight / this.size.h, this.wrapper.documentClientWidth / this.size.w);\n    this.zoomFactor = minValue;\n    this.adjustSizeFull();\n    this.worklog.captureState();\n  }\n\n  loadImage(source, mimetype) {\n    this.inserter.handleOpen(source, mimetype);\n  }\n\n  show(openImage, originalMime) {\n    this.shown = true;\n    this.scrollWidth = getScrollbarWidth();\n    if (this.isMobile) {\n      this.origOverflowY = this.body.style['overflow-y'];\n      if (this.params.fixMobilePageReloader) {\n        this.body.style['overflow-y'] = 'hidden';\n      }\n    }\n    this.baseEl.removeAttribute('hidden');\n    if (this.holderEl) {\n      this.holderEl.removeAttribute('hidden');\n    }\n    if (typeof openImage === 'string') {\n      this.loadedName = trim(\n        (openImage.substring(openImage.lastIndexOf('/') + 1) || '').replace(/\\..+$/, ''));\n\n      this.loadImage(openImage, originalMime);\n    } else if (openImage !== false) {\n      this.clear();\n    }\n    this.attachEventHandlers();\n    return this;\n  }\n\n  hide() {\n    if (this.isMobile) {\n      this.body.style['overflow-y'] = this.origOverflowY;\n    }\n    this.shown = false;\n    this.baseEl.setAttribute('hidden', '');\n    if (this.holderEl) {\n      this.holderEl.setAttribute('hidden', '');\n    }\n    this.removeEventHandlers();\n    if (this.params.onHide !== undefined) {\n      this.params.onHide();\n    }\n    return this;\n  }\n\n  setZoom(zoomPercentage) {\n    if (!this.size) {\n      return;\n    }\n\n    this.zoomFactor = zoomPercentage / 100;\n\n    let minFactor = 1;\n    if (this.size.w > this.wrapper.documentClientWidth) {\n      minFactor = Math.min(minFactor, this.wrapper.documentClientWidth / this.size.w);\n    }\n    if (this.size.h > this.wrapper.documentClientHeight) {\n      minFactor = Math.min(minFactor, this.wrapper.documentClientHeight / this.size.h);\n    }\n\n    if (!this.zoom && this.zoomFactor > minFactor) {\n      this.zoomFactor = minFactor;\n    }\n\n    if (this.zoomFactor < minFactor) {\n      this.zoom = false;\n      this.zoomFactor = minFactor;\n    } else {\n      this.zoom = true;\n    }\n\n    this.adjustSizeFull();\n    this.select.adjustPosition();\n\n    const canvas = this.canvas;\n    const gbr = canvas.getBoundingClientRect();\n\n    this.curCord = [\n      (gbr.right / 2 - this.elLeft()) + this.scroller.scrollLeft,\n      (gbr.bottom / 2 - this.elTop()) + this.scroller.scrollTop,\n    ];\n\n    const scale = this.getScale();\n    this.curCord = [this.curCord[0] * scale, this.curCord[1] * scale];\n\n    if (this.zoom) {\n      this.scroller.scrollLeft = (this.curCord[0] / this.getScale()) -\n          (gbr.right / 2 - this.wrapper.documentOffsetLeft);\n      this.scroller.scrollTop = (this.curCord[1] / this.getScale()) -\n          (gbr.bottom / 2 - this.wrapper.documentOffsetTop);\n    }\n  }\n\n  doScale({width, height, scale}) {\n    if (scale) {\n      if (width || height) {\n        throw new Error(`You can't pass width or height and scale at the same time`)\n      }\n      this.resizer.newW = Math.round(this.size.w * scale);\n      this.resizer.newH = Math.round(this.size.h * scale);\n\n    } else {\n      if (scale) {\n        throw new Error(`You can't pass width or height and scale at the same time`)\n      }\n      this.resizer.newW = width || Math.round(this.size.w * (height / this.size.h));\n      this.resizer.newH = height || Math.round(this.size.h * (width / this.size.w));\n    }\n    this.resizer.scaleButton.onclick();\n  }\n\n  openFile(f) {\n    if (!f) {\n      return;\n    }\n    this.loadedName = trim((f.name || '').replace(/\\..+$/, ''));\n    const dataUrl = (window.URL ? window.URL : window.webkitURL).createObjectURL(f);\n    this.loadImage(dataUrl, f.type);\n  }\n\n  getScale() {\n    return this.canvas.getAttribute('width') / this.canvas.offsetWidth;\n  }\n\n  adjustSizeFull() {\n    const ratio = this.wrapper.documentClientWidth / this.wrapper.documentClientHeight;\n    \n    if (this.zoom === false && this.textTool.active === false) {\n        if (this.size.w > this.wrapper.documentClientWidth ||\n            this.size.h > this.wrapper.documentClientHeight) {\n            const newRelation = ratio < this.size.ratio;\n            this.ratioRelation = newRelation;\n            if (newRelation) {\n                this.canvas.style.width = `${this.wrapper.clientWidth}px`;\n                this.canvas.style.height = 'auto';\n            } else {\n                this.canvas.style.width = 'auto';\n                this.canvas.style.height = `${this.wrapper.clientHeight}px`;\n            }\n            \n        } else {\n            this.canvas.style.width = 'auto';\n            this.canvas.style.height = 'auto';\n            this.ratioRelation = 0;\n        }\n        this.scroller.style.overflow = 'hidden';\n    } else {\n        if (this.zoom === false) {\n          this.scroller.style.overflow = 'hidden';\n        } else {\n          this.scroller.style.overflow = 'scroll';\n        }\n        this.canvas.style.width = `${this.size.w * this.zoomFactor}px`;\n        this.canvas.style.height = `${this.size.h * this.zoomFactor}px`;\n        this.ratioRelation = 0;\n    }\n    this.syncToolElement();\n    this.select.draw();\n}\n\n  resize(x, y) {\n    this.info.innerHTML = `${x}<span>x</span>${y}<br>${(this.originalMime || 'png').replace('image/', '')}`;\n    this.size = {\n      w: x,\n      h: y,\n      ratio: x / y,\n    };\n    this.canvas.setAttribute('width', this.size.w);\n    this.canvas.setAttribute('height', this.size.h);\n  }\n\n  syncToolElement() {\n    const w = Math.round(this.canvas.documentClientWidth);\n    const l = this.canvas.offsetLeft;\n    const h = Math.round(this.canvas.documentClientHeight);\n    const t = this.canvas.offsetTop;\n    this.toolContainer.style.left = `${l}px`;\n    this.toolContainer.style.width = `${w}px`;\n    this.toolContainer.style.top = `${t}px`;\n    this.toolContainer.style.height = `${h}px`;\n    this.substrate.style.left = `${l}px`;\n    this.substrate.style.width = `${w}px`;\n    this.substrate.style.top = `${t}px`;\n    this.substrate.style.height = `${h}px`;\n  }\n\n  clear() {\n    const w = this.params.defaultSize.width === 'fill' ? this.wrapper.clientWidth : this.params.defaultSize.width;\n    const h = this.params.defaultSize.height === 'fill' ? this.wrapper.clientHeight : this.params.defaultSize.height;\n    this.resize(w, h);\n    this.clearBackground();\n    this.worklog.captureState(true);\n    this.worklog.clean = true;\n    this.syncToolElement();\n    this.adjustSizeFull();\n\n    if (this.params.initText && this.worklog.empty) {\n      this.ctx.lineWidth = 3;\n      this.ctx.strokeStyle = '#fff';\n      const initTexts = this.wrapper.querySelectorAll('.init-text');\n      if (initTexts.length > 0) {\n        initTexts.forEach((text) => {\n          text.remove();\n        });\n      }\n      this.initText = document.createElement('div');\n      this.initText.classList.add('init-text');\n      this.wrapper.append(this.initText);\n      this.initText.innerHTML = '<div style=\"pointer-events: none;position:absolute;top:50%;width:100%;left: 50%; transform: translate(-50%, -50%)\">' +\n        `${this.params.initText}</div>`;\n      this.initText.style.left = '0';\n      this.initText.style.top = '0';\n      this.initText.style.right = '0';\n      this.initText.style.bottom = '0';\n      this.initText.style.pointerEvents = 'none';\n      this.initText.style['text-align'] = 'center';\n      this.initText.style.position = 'absolute';\n      this.initText.style.color = this.params.initTextColor;\n      this.initText.style['font-family'] = this.params.initTextStyle.split(/ (.+)/)[1];\n      this.initText.style['font-size'] = this.params.initTextStyle.split(/ (.+)/)[0];\n\n      this.wrapper.addEventListener('click', () => {\n        this.initText.remove();\n        this.initText = null;\n      }, { once: true });\n    }\n  }\n\n  clearBackground() {\n    this.ctx.beginPath();\n    this.ctx.clearRect(0, 0, this.size.w, this.size.h);\n    this.ctx.rect(0, 0, this.size.w, this.size.h);\n    this.ctx.fillStyle = this.currentBackground;\n    this.ctx.fill();\n  }\n\n  setColor(options){\n    this.doc.querySelector(\n      `#${this.id} .ptro-color-btn[data-id='${options[0]}']`).style['background-color'] =\n      options[1].alphaColor;\n    this.colorWidgetState = {...this.colorWidgetState, line: {\n      target: options[0],\n      palleteColor: options[1].palleteColor,\n      alpha:options[1].alpha, \n      alphaColor: options[1].alphaColor, \n    }, }\n  \n  }\n\n\n\n  setLineWidth(width) {\n    setPrimitiveToolValue(width,this.primitiveTool,'setLineWidth','lineWidth');\n  }\n\n  setArrowLength(length) {\n    setPrimitiveToolValue(length,this.primitiveTool,'setArrowLength','arrowLength');\n  }\n\n  setEraserWidth(width) {\n    setPrimitiveToolValue(width,this.primitiveTool,'setEraserWidth','eraserWidth');\n  }\n\n  setPixelSize(size) {\n    setPrimitiveToolValue(size,this.primitiveTool,'setPixelSize','pixelSize');\n  }\n\n  setShadowOn(state) {\n    setPrimitiveToolValue(state,this.primitiveTool,'setShadowOn','shadowOn');\n  }\n\n\n  setActiveTool(b) {\n    this.activeTool = b;\n    \n    this.zoomButtonActive = false;\n    const btnEl = this.getBtnEl(this.activeTool);\n    if (btnEl) {\n      btnEl.className += ' ptro-color-active-control';\n    }\n    let ctrls = '';\n    (b.controls || []).forEach((ctl) => {\n      ctl.id = genId();\n      if (ctl.title) {\n        ctrls += `<span class=\"ptro-tool-ctl-name\" title=\"${tr(ctl.titleFull)}\">${tr(ctl.title)}</span>`;\n      }\n      if (ctl.type === 'btn') {\n        ctrls += `<button type=\"button\" ${ctl.hint ? `title=\"${tr(ctl.hint)}\"` : ''} class=\"ptro-color-control ${ctl.icon ? 'ptro-icon-btn' : 'ptro-named-btn'}\" ` +\n          `id=${ctl.id}>${ctl.icon ? `<i class=\"ptro-icon ptro-icon-${ctl.icon}\"></i>` : ''}` +\n          `<p>${ctl.name || ''}</p></button>`;\n      } else if (ctl.type === 'color') {\n        ctrls += `<button type=\"button\" id=${ctl.id} data-id='${ctl.target}' ` +\n          `style=\"background-color: ${this.colorWidgetState[ctl.target].alphaColor}\" ` +\n          'class=\"color-diwget-btn ptro-color-btn ptro-bordered-btn ptro-color-control\"></button>' +\n          '<span class=\"ptro-btn-color-checkers-bar\"></span>';\n      } else if (ctl.type === 'int') {\n        ctrls += `<input id=${ctl.id} class=\"ptro-input\" type=\"number\" min=\"${ctl.min}\" max=\"${ctl.max}\" ` +\n          `data-id='${ctl.target}'/>`;\n      } else if (ctl.type === 'bool') {\n        ctrls += `<button id=${ctl.id} class=\"ptro-input ptro-check\" data-value=\"false\" type=\"button\" ` +\n          `data-id='${ctl.target}'></button>`;\n      } else if (ctl.type === 'dropdown') {\n        let options = '';\n        ctl.getAvailableValues().forEach((o) => {\n          options += `<option ${o.extraStyle ? `style='${o.extraStyle}'` : ''}` +\n            ` value='${o.value}' ${o.title ? `title='${o.title}'` : ''}>${o.name}</option>`;\n        });\n        ctrls += `<select id=${ctl.id} class=\"ptro-input\" ` +\n          `data-id='${ctl.target}'>${options}</select>`;\n      }\n    });\n    this.toolControls.innerHTML = ctrls;\n    (b.controls || []).forEach((ctl) => {\n      if (ctl.type === 'int') {\n        this.getElemByIdSafe(ctl.id).value = ctl.getValue();\n        if (ctl.options && ctl.options.eventOnChange){\n          this.getElemByIdSafe(ctl.id).onchange = ctl.action;\n        } else {\n        this.getElemByIdSafe(ctl.id).oninput = ctl.action;\n        }\n      } else if (ctl.type === 'bool') {\n        this.getElemByIdSafe(ctl.id).setAttribute('data-value', ctl.getValue() ? 'true' : 'false');\n        this.getElemByIdSafe(ctl.id).onclick = ctl.action;\n      } else if (ctl.type === 'dropdown') {\n        this.getElemByIdSafe(ctl.id).onchange = ctl.action;\n        this.getElemByIdSafe(ctl.id).value = ctl.getValue();\n      } else {\n        this.getElemByIdSafe(ctl.id).onclick = ctl.action;\n      }\n    });\n    b.activate();\n  }\n}\n\nexport default params => new PainterroProc(params);\n"
  },
  {
    "path": "js/paintBucket.js",
    "content": "import ColorPicker, { HexToRGB, rgbToHex } from './colorPicker';\n\nexport default class PaintBucket {\n  constructor(main) {\n    this.main = main;\n    this.canvasWidth = 600;\n    this.canvasHeight = 420;\n    this.el = this.main.toolContainer;\n    this.input = this.el.querySelector('.ptro-text-tool-input');\n  }\n\n  init() {\n    this.ctx = this.main.ctx;\n    this.canvas = this.main.canvas;\n    this.colorLayerData = this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight);\n    this.drawingAreaX = 0;\n    this.drawingAreaY = 0;\n    this.canvasWidth = this.canvas.width\n    this.canvasHeight = this.canvas.height\n    this.drawingAreaWidth = this.canvasWidth;\n    this.drawingAreaHeight = this.canvasHeight;\n  }\n\n  handleMouseDown(event) {\n    const mainClass = event.target.classList[0];\n    const scale = this.main.getScale();\n    if (mainClass === 'ptro-crp-el') {\n      this.init();\n      this.colorLayerData = this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight);\n\n      if (!this.active) {\n        this.input.innerHTML = '<br>';\n        this.pendingClear = true;\n      }\n\n      this.active = true;\n      const cord = [\n        (event.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n        (event.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n      ];\n      const cur = {\n        x: cord[0] * scale,\n        y: cord[1] * scale,\n      };\n      this.paintAt(cur.x, cur.y);\n    }\n  }\n\n  paintAt(startX, startY) {\n    startX = Math.round(startX);\n    startY = Math.round(startY - 1);\n    startX = (startX > 0) ? startX : 0;\n    startY = (startY > 0) ? startY : 0;\n\n    // get clicked on color\n    this.getClickedOnColor(startX, startY);\n\n    var pixelPos = (startY * this.canvasWidth + startX) * 4;\n    var pixelColor = this.getPixelColor(this.colorLayerData, pixelPos);\n    var r = pixelColor.r;\n    var g = pixelColor.g;\n    var b = pixelColor.b;\n    var a = pixelColor.a;\n\n    const curColor = HexToRGB( this.main.colorWidgetState.fill.palleteColor );\n    this.color = this.main.colorWidgetState.fill.palleteColor;\n    \n    if (r === curColor.r && g === curColor.g && b === curColor.b) {\n      // Return because trying to fill with the same color\n      return;\n    }\n\n    this.floodFill(startX, startY, r, g, b);\n    this.ctx.putImageData(this.colorLayerData, 0, 0);\n    this.main.worklog.captureState();\n  }\n\n  // returns true if the current pixel's color matches the clicked on color.\n  matchStartColor(pixelPos) {\n    var pixelColor = this.getPixelColor(this.colorLayerData, pixelPos);\n    var v = this.matchClickedColor(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a);\n    return v;\n  }\n\n  matchClickedColor(r, g, b, a) {\n    const limit = this.main.params.bucketSensivity;\n    var matchedR = (Math.abs(r - this.clickedOnColor.r) < limit);\n    var matchedG = (Math.abs(g - this.clickedOnColor.g) < limit);\n    var matchedB = (Math.abs(b - this.clickedOnColor.b) < limit);\n    var matchedA = (Math.abs(a - this.clickedOnColor.a) < limit);\n    var v = (matchedR && matchedG && matchedB && matchedA);\n    return v;\n  }\n\n  getClickedOnColor(x, y) {\n    var pixelPos = (y * this.canvasWidth + x) * 4;\n    var pixelColor = this.getPixelColor(this.colorLayerData, pixelPos);\n    this.clickedOnColor = {r: pixelColor.r, g: pixelColor.g, b: pixelColor.b, a: pixelColor.a};\n  }\n\n  floodFill(startX, startY, startR, startG, startB) {\n    // console.log('flood: ' + startX + ' ' + startY + ' ' + startR + ' ' + startG + ' ' + startB);\n    \n    var newPos,\n      x,\n      y,\n      pixelPos,\n      reachLeft,\n      reachRight,\n      drawingBoundLeft = this.drawingAreaX,\n      drawingBoundTop = this.drawingAreaY,\n      drawingBoundRight = this.drawingAreaX + this.drawingAreaWidth - 1,\n      drawingBoundBottom = this.drawingAreaY + this.drawingAreaHeight - 1,\n      pixelStack = [[startX, startY]];\n\n    while (pixelStack.length) {\n\n      newPos = pixelStack.pop();\n      x = newPos[0];\n      y = newPos[1];\n\n      // Get current pixel position\n      pixelPos = (y * this.canvasWidth + x) * 4;\n      const curColor = HexToRGB(this.color);\n\n\n      // Go up as long as the color matches and are inside the canvas\n      while (y >= drawingBoundTop && this.matchStartColor(pixelPos)) {\n        y -= 1;\n        pixelPos -= this.canvasWidth * 4;\n      }\n\n      pixelPos += this.canvasWidth * 4;\n      y += 1;\n      reachLeft = false;\n      reachRight = false;\n\n      // Go down as long as the color matches and in inside the canvas\n      while (y <= drawingBoundBottom && this.matchStartColor(pixelPos)) {\n        y += 1;\n\n        this.colorPixel(pixelPos, curColor.r, curColor.g, curColor.b);\n\n        if (x > drawingBoundLeft) {\n          if (this.matchStartColor(pixelPos - 4)) {\n            if (!reachLeft) {\n              // Add pixel to stack\n              pixelStack.push([x - 1, y]);\n              reachLeft = true;\n            }\n          } else if (reachLeft) {\n            reachLeft = false;\n          }\n        }\n\n        if (x < drawingBoundRight) {\n          if (this.matchStartColor(pixelPos + 4)) {\n            if (!reachRight) {\n              // Add pixel to stack\n              pixelStack.push([x + 1, y]);\n              reachRight = true;\n            }\n          } else if (reachRight) {\n            reachRight = false;\n          }\n        }\n\n        pixelPos += this.canvasWidth * 4;\n      }\n    }    \n  }\n\n  colorPixel(pixelPos, r, g, b, a) {\n    this.colorLayerData.data[pixelPos] = r;\n    this.colorLayerData.data[pixelPos + 1] = g;\n    this.colorLayerData.data[pixelPos + 2] = b;\n    this.colorLayerData.data[pixelPos + 3] = a !== undefined ? a : 255;\n  }\n\n  getPixelColor(ctx, pixelPos) {\n    var r = ctx.data[pixelPos];\n    var g = ctx.data[pixelPos + 1];\n    var b = ctx.data[pixelPos + 2];\n    var a = ctx.data[pixelPos + 3];\n    return {r: r, g: g, b: b, a: a};\n  }\n}\n"
  },
  {
    "path": "js/params.js",
    "content": "/* eslint-disable */\nimport { HexToRGBA } from './colorPicker';\nimport { trim, logError } from './utils';\nimport Translation, { activate } from './translation';\n\nconst STORAGE_KEY = 'painterro-data';\n\nlet settings = {};\n\nfunction loadSettings() {\n  try {\n    settings = JSON.parse(localStorage.getItem(STORAGE_KEY));\n  } catch (e) {\n    console.warn(`Unable get from localStorage: ${e}`);\n  }\n  if (!settings) {\n    settings = {};\n  }\n}\n\nexport function setParam(name, val) {\n  settings[name] = val;\n  try {\n    localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));\n  } catch (e) {\n    console.warn(`Unable save to localstorage: ${e}`);\n  }\n}\n\nfunction firstDefined(...vals) {\n  for (let i = 0; i < vals.length; i += 1) {\n    if (vals[i] !== undefined) {\n      return vals[i];\n    }\n  }\n  return undefined;\n}\n\n\n\nexport function setDefaults(parameters, allToolsNames) {\n  loadSettings();\n  const params = parameters || {};\n  if (params.language) {\n    activate(params.language);\n  }\n  params.NON_SELECTABLE_TOOLS = ['pixelize', 'crop', 'rotate'];\n\n  params.activeColor = settings.activeColor || params.activeColor || '#ff0000';\n  params.activeColorAlpha = firstDefined(settings.activeColorAlpha, params.activeColorAlpha, 1.0);\n  params.activeAlphaColor = HexToRGBA(params.activeColor, params.activeColorAlpha);\n\n  params.activeFillColor = settings.activeFillColor || params.activeFillColor || '#000000';\n  params.activeFillColorAlpha = firstDefined(settings.activeFillColorAlpha,\n    params.activeFillColorAlpha, 0.0);\n  params.activeFillAlphaColor = HexToRGBA(params.activeFillColor, params.activeFillColorAlpha);\n  params.replace_all_on_empty_background = \n\n  params.initText = params.initText || null;\n  params.initTextColor = params.initTextColor || '#808080';\n  params.initTextStyle = params.initTextStyle || '26px \\'Open Sans\\', sans-serif';\n  params.defaultLineWidth = settings.defaultLineWidth || params.defaultLineWidth || 5;\n  params.defaultPrimitiveShadowOn = firstDefined(settings.defaultPrimitiveShadowOn,\n    params.defaultPrimitiveShadowOn, true);\n\n  params.defaultArrowLength = settings.defaultArrowLength || params.defaultArrowLength || 32;\n  params.defaultEraserWidth = firstDefined(settings.defaultEraserWidth,\n    params.defaultEraserWidth, 5);\n  params.defaultFontSize = firstDefined(settings.defaultFontSize, params.defaultFontSize, 24);\n  params.defaultFontBold = firstDefined(settings.defaultFontBold, params.defaultFontBold, false);\n  params.defaultFontItalic = firstDefined(settings.defaultFontItalic,\n    params.defaultFontItalic, false);\n  \n  params.replaceAllOnEmptyBackground = firstDefined(params.replaceAllOnEmptyBackground, true);\n  params.backgroundFillColor = settings.backgroundFillColor || params.backgroundFillColor || '#ffffff';\n  params.backgroundFillColorAlpha = params.backplateImgUrl ? 0 :\n    firstDefined(settings.backgroundFillColorAlpha, params.backgroundFillColorAlpha, 1.0);\n  params.backgroundFillAlphaColor = HexToRGBA(params.backgroundFillColor,\n    params.backgroundFillColorAlpha);\n\n  params.textStrokeColor = settings.textStrokeColor || params.textStrokeColor || '#ffffff';\n  params.textStrokeColorAlpha = firstDefined(settings.textStrokeColorAlpha,\n    params.textStrokeColorAlpha, 1.0);\n  params.textStrokeAlphaColor = HexToRGBA(params.textStrokeColor, params.textStrokeColorAlpha);\n\n  params.shadowScale = firstDefined(params.shadowScale, 1);\n\n  params.defaultTextStrokeAndShadow = firstDefined(settings.defaultTextStrokeAndShadow,\n    params.defaultTextStrokeAndShadow, true);\n\n  params.worklogLimit = firstDefined(params.worklogLimit, 100);\n\n  params.hiddenTools = params.hiddenTools || ['redo', 'zoomin', 'zoomout'];\n  params.hiddenTools.forEach(t => {\n    if (!allToolsNames.includes(t)) {\n      logError(`Hidden tool with name ${t}`)\n    }\n  })\n\n  if (params.defaultTool) {\n    if (params.hiddenTools.includes(params.defaultTool)) {\n      logError(`Can't hide default tool '${params.defaultTool}', please change default tool to another to hide it`);\n      params.hiddenTools.splice(defaultInHiddenIndex, 1);\n    } \n  } else {\n    params.defaultTool = allToolsNames.filter(t => !params.hiddenTools.includes(t) && !params.NON_SELECTABLE_TOOLS.includes(t))[0];  // select first tool which supports selecting\n  }\n\n  params.pixelizePixelSize = settings.pixelizePixelSize || params.pixelizePixelSize || '20%';\n  params.colorScheme = params.colorScheme || {};\n  params.colorScheme.main = params.colorScheme.main || '#fff';\n  params.colorScheme.control = params.colorScheme.control || '#fff';\n  params.colorScheme.controlShadow = params.colorScheme.controlShadow || '0px 0px 3px 1px #bbb';\n  params.colorScheme.controlContent = params.colorScheme.controlContent || '#000000';\n  params.colorScheme.hoverControl = params.colorScheme.hoverControl || params.colorScheme.control;\n  params.colorScheme.hoverControlContent = params.colorScheme.hoverControlContent || '#1a3d67';\n  params.colorScheme.toolControlNameColor = params.colorScheme.toolControlNameColor || 'rgba(0,0,0,0.07)';\n\n  params.colorScheme.activeControl = params.colorScheme.activeControl || '#7485B1';\n  params.colorScheme.activeControlContent = params.colorScheme.activeControlContent ||\n    params.colorScheme.main;\n  params.colorScheme.inputBorderColor = params.colorScheme.inputBorderColor ||\n    params.colorScheme.main;\n  params.colorScheme.inputBackground = params.colorScheme.inputBackground || '#ffffff';\n  params.colorScheme.inputShadow = params.colorScheme.inputShadow || 'inset 0 0 4px 1px #ccc';\n\n  params.colorScheme.inputText = params.colorScheme.inputText ||\n    params.colorScheme.activeControl;\n  params.colorScheme.backgroundColor = params.colorScheme.backgroundColor || '#999999';\n  params.colorScheme.dragOverBarColor = params.colorScheme.dragOverBarColor || '#899dff';\n\n  params.defaultSize = params.defaultSize || 'fill';\n  params.defaultPixelSize = params.defaultPixelSize || 4;\n  params.disableWheelZoom = params.disableWheelZoom || false;\n\n  params.extraFonts = params.extraFonts || [];\n\n  params.toolbarHeightPx = params.toolbarHeightPx || 40;\n  params.buttonSizePx = params.buttonSizePx || 32;\n  params.bucketSensivity = params.bucketSensivity || 100;\n\n\n  if (typeof params.defaultSize !== 'object') {\n    // otherwise its an object from localstorage\n    if (params.defaultSize === 'fill') {\n      params.defaultSize = {\n        width: 'fill',\n        height: 'fill',\n      };\n    } else {\n      const wh = params.defaultSize.split('x');\n      params.defaultSize = {\n        width: trim(wh[0]),\n        height: trim(wh[1]),\n      };\n    }\n  }\n\n  params.toolbarPosition = params.toolbarPosition || 'bottom';\n  params.fixMobilePageReloader = params.fixMobilePageReloader !== undefined ?\n    params.fixMobilePageReloader : true;\n  if (params.translation) {\n    const name = params.translation.name;\n    Translation.get().addTranslation(name, params.translation.strings);\n    Translation.get().activate(name);\n  }\n\n  params.styles =\n    `.ptro-color-main{\n      background-color:${params.colorScheme.main};\n      color:${params.colorScheme.controlContent};\n    }\n    .ptro-color-control{\n      box-shadow:${params.colorScheme.controlShadow};\n      background-color:${params.colorScheme.control};\n      color:${params.colorScheme.controlContent}}\n    .ptro-tool-ctl-name{\n      background-color:${params.colorScheme.toolControlNameColor};\n    }\n    button.ptro-color-control:hover:not(.ptro-color-active-control):not([disabled]){\n        background-color: ${params.colorScheme.hoverControl};\n        color:${params.colorScheme.hoverControlContent}}    \n    .ptro-bordered-control{border-color: ${params.colorScheme.activeControl}}\n    input.ptro-input,.ptro-check,input.ptro-input:focus,select.ptro-input,select.ptro-input:focus {\n      border: 1px solid ${params.colorScheme.inputBorderColor};\n      background-color: ${params.colorScheme.inputBackground};\n      color: ${params.colorScheme.inputText};\n      box-shadow:${params.colorScheme.inputShadow};\n    }\n    .ptro-bar-dragover{background-color:${params.colorScheme.dragOverBarColor}}\n    .ptro-color,.ptro-bordered-btn{\n      border: 1px solid ${params.colorScheme.inputBorderColor};\n    }\n    .ptro-color-control:active:enabled {\n        background-color: ${params.colorScheme.activeControl};\n        color: ${params.colorScheme.activeControlContent}}\n    .ptro-color-active-control{\n        background-color: ${params.colorScheme.activeControl};\n        color:${params.colorScheme.activeControlContent}}\n    .ptro-wrapper{\n      background-color:${params.colorScheme.backgroundColor};\n      bottom:${params.toolbarPosition === 'top' ? '0' : params.toolbarHeightPx}px;\n      top:${params.toolbarPosition === 'top' ? params.toolbarHeightPx : '0'}px;\n    }\n    .ptro-icon-btn {\n      height: ${params.buttonSizePx}px;\n      width: ${params.buttonSizePx}px;\n      margin: 0 0 0 ${(params.toolbarHeightPx - params.buttonSizePx) / 2}px;\n    }\n    .ptro-bar-right {\n      margin-right: ${(params.toolbarHeightPx - params.buttonSizePx) / 2}px;\n    }\n    .ptro-bar {\n      height: ${params.toolbarHeightPx}px;\n      ${params.toolbarPosition === 'top' ? 'top' : 'bottom'}: 0;\n    }`;\n\n  return params;\n}\n"
  },
  {
    "path": "js/primitive.js",
    "content": "export default class PrimitiveTool {\n  constructor(main) {\n    this.ctx = main.ctx;\n    this.el = main.toolContainer;\n    this.main = main;\n    this.helperCanvas = document.createElement('canvas');\n    this.canvas = main.canvas;\n  }\n\n  activate(type) {\n    this.type = type;\n    this.state = {};\n    if (type === 'line' || type === 'brush' || type === 'eraser' || type === 'arrow') {\n      this.ctx.lineJoin = 'round';\n    } else {\n      this.ctx.lineJoin = 'miter';\n    }\n  }\n\n  setLineWidth(width) {\n    if (`${width}`.match(/^\\d+$/)) {\n      this.lineWidth = +width;\n    } else {\n      throw Error(`WARN: STR \"${width}\" is not an int`);\n    }\n  }\n\n  setShadowOn(state) {\n    this.shadowOn = state;\n  }\n\n  setArrowLength(length) {\n    this.arrowLength = length;\n  }\n\n  setEraserWidth(width) {\n    this.eraserWidth = width;\n  }\n\n  handleMouseDown(event) {\n    this.activate(this.type);\n    const mainClass = event.target.classList[0];\n\n    this.ctx.lineWidth = this.lineWidth;\n    this.ctx.strokeStyle = this.main.colorWidgetState.line.alphaColor;\n    this.ctx.fillStyle = this.main.colorWidgetState.fill.alphaColor;\n    const scale = this.main.getScale();\n    this.ctx.lineCap = 'round';\n    if (mainClass === 'ptro-crp-el' || mainClass === 'ptro-zoomer') {\n      this.tmpData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n      if (this.type === 'brush' || this.type === 'eraser') {\n        this.state.cornerMarked = true;\n        const cord = [\n          (event.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n          (event.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n        ];\n        const cur = {\n          x: cord[0] * scale,\n          y: cord[1] * scale,\n        };\n\n        this.points = [cur];\n        this.drawBrushPath();\n      } else {\n        this.state.cornerMarked = true;\n        this.centerCord = [\n          (event.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n          (event.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n        ];\n        this.centerCord = [this.centerCord[0] * scale, this.centerCord[1] * scale];\n      }\n    }\n  }\n\n  drawBrushPath() {\n    const smPoints = this.points;\n    let lineFill;\n    const origComposition = this.ctx.globalCompositeOperation;\n    const isEraser = this.type === 'eraser';\n    lineFill = this.main.colorWidgetState.line.alphaColor;\n    const bgIsTransparent = this.main.currentBackgroundAlpha !== 1.0;\n    for (let i = 1; i <= (isEraser && bgIsTransparent ? 2 : 1); i += 1) {\n      if (isEraser) {\n        this.ctx.globalCompositeOperation = i === 1 && bgIsTransparent ? 'destination-out' : origComposition;\n        lineFill = i === 1 && bgIsTransparent ? 'rgba(0,0,0,1)' : this.main.currentBackground;\n      }\n      if (smPoints.length === 1) {\n        this.ctx.beginPath();\n        this.ctx.lineWidth = 0;\n        this.ctx.fillStyle = lineFill;\n        this.ctx.arc(\n          this.points[0].x, this.points[0].y,\n          this.lineWidth / 2, this.lineWidth / 2,\n          0, 2 * Math.PI);\n        this.ctx.fill();\n        this.ctx.closePath();\n      } else {\n        this.ctx.beginPath();\n        if (this.type === 'eraser') {\n          this.ctx.lineWidth = this.eraserWidth;\n        } else {\n          this.ctx.lineWidth = this.lineWidth;\n        }\n        this.ctx.strokeStyle = lineFill;\n        this.ctx.fillStyle = this.main.colorWidgetState.fill.alphaColor;\n\n        this.ctx.moveTo(this.points[0].x, this.points[0].y);\n        let last;\n        smPoints.slice(1).forEach((p) => {\n          this.ctx.lineTo(p.x, p.y);\n          last = p;\n        });\n        if (last) {\n          this.ctx.moveTo(last.x, last.y);\n        }\n        this.ctx.stroke();\n        this.ctx.closePath();\n      }\n    }\n    this.ctx.globalCompositeOperation = origComposition;\n  }\n\n  handleMouseMove(event) {\n    const ctx = this.ctx;\n    if (this.state.cornerMarked) {\n      this.ctx.putImageData(this.tmpData, 0, 0);\n      this.curCord = [\n        (event.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n        (event.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n      ];\n      const scale = this.main.getScale();\n      this.curCord = [this.curCord[0] * scale, this.curCord[1] * scale];\n\n      if (this.type === 'brush' || this.type === 'eraser') {\n        // const prevLast = this.points.slice(-1)[0];\n        const cur = {\n          x: this.curCord[0],\n          y: this.curCord[1],\n        };\n        this.points.push(cur);\n        this.drawBrushPath();\n      } else if (this.type === 'line') {\n        if (event.ctrlKey || event.shiftKey) {\n          const deg = (Math.atan(\n            -(this.curCord[1] - this.centerCord[1]) / (this.curCord[0] - this.centerCord[0]),\n          ) * 180) / Math.PI;\n          if (Math.abs(deg) < 45.0 / 2) {\n            this.curCord[1] = this.centerCord[1];\n          } else if (Math.abs(deg) > 45.0 + (45.0 / 2)) {\n            this.curCord[0] = this.centerCord[0];\n          } else {\n            const base = (Math.abs(this.curCord[0] - this.centerCord[0])\n              - Math.abs(this.centerCord[1] - this.curCord[1])) / 2;\n\n            this.curCord[0] -= base * (this.centerCord[0] < this.curCord[0] ? 1 : -1);\n            this.curCord[1] -= base * (this.centerCord[1] > this.curCord[1] ? 1 : -1);\n          }\n        }\n        ctx.beginPath();\n        ctx.moveTo(this.centerCord[0], this.centerCord[1]);\n        ctx.lineTo(this.curCord[0], this.curCord[1]);\n        ctx.closePath();\n        const origShadowColor = ctx.shadowColor;\n        if (this.shadowOn) {\n          ctx.shadowColor = 'rgba(0,0,0,0.7)';\n          ctx.shadowBlur = this.lineWidth;\n          ctx.shadowOffsetX = this.lineWidth / 2.0;\n          ctx.shadowOffsetY = this.lineWidth / 2.0;\n        }\n        ctx.stroke();\n        ctx.shadowColor = origShadowColor;\n      } else if (this.type === 'arrow') {\n        let deg = (Math.atan(\n          -(this.curCord[1] - this.centerCord[1]) / (this.curCord[0] - this.centerCord[0]),\n        ) * 180) / Math.PI;\n        if (event.ctrlKey || event.shiftKey) {\n          if (Math.abs(deg) < 45.0 / 2) {\n            this.curCord[1] = this.centerCord[1];\n          } else if (Math.abs(deg) > 45.0 + (45.0 / 2)) {\n            this.curCord[0] = this.centerCord[0];\n          } else {\n            const base = (Math.abs(this.curCord[0] - this.centerCord[0])\n              - Math.abs(this.centerCord[1] - this.curCord[1])) / 2;\n\n            this.curCord[0] -= base * (this.centerCord[0] < this.curCord[0] ? 1 : -1);\n            this.curCord[1] -= base * (this.centerCord[1] > this.curCord[1] ? 1 : -1);\n          }\n        }\n        if (this.curCord[0] < this.centerCord[0]) {\n          deg = (180 + deg);\n        }\n        this.ctx.beginPath();\n        const origCap = this.ctx.lineCap;\n        const origFill = this.ctx.fillStyle;\n        this.ctx.fillStyle = this.main.colorWidgetState.line.alphaColor;\n        this.ctx.lineCap = 'square';\n\n        const r = Math.min(this.arrowLength, 0.9 * Math.sqrt(\n          ((this.centerCord[0] - this.curCord[0]) ** 2) +\n          ((this.centerCord[1] - this.curCord[1]) ** 2)));\n\n        const fromx = this.centerCord[0];\n        const fromy = this.centerCord[1];\n        const tox = this.curCord[0];\n        const toy = this.curCord[1];\n        const xCenter = this.curCord[0];\n        const yCenter = this.curCord[1];\n        let angle;\n        let x;\n        let y;\n        angle = Math.atan2(toy - fromy, tox - fromx);\n\n        x = (r * Math.cos(angle)) + xCenter;\n        y = (r * Math.sin(angle)) + yCenter;\n\n        this.ctx.moveTo(x, y);\n\n        angle += (1.0 / 3) * (2 * Math.PI);\n        x = (r * Math.cos(angle)) + xCenter;\n        y = (r * Math.sin(angle)) + yCenter;\n        this.ctx.lineTo(x, y);\n\n        const xTail1 = xCenter + ((x - xCenter) / 3.0);\n        const yTail1 = yCenter + ((y - yCenter) / 3.0);\n        ctx.lineTo(xTail1, yTail1);\n\n        ctx.lineTo(this.centerCord[0], this.centerCord[1]);\n\n        angle += (1.0 / 3) * (2 * Math.PI);\n        x = (r * Math.cos(angle)) + xCenter;\n        y = (r * Math.sin(angle)) + yCenter;\n        const xTail2 = xCenter + ((x - xCenter) / 3.0);\n        const yTail2 = yCenter + ((y - yCenter) / 3.0);\n        ctx.lineTo(xTail2, yTail2);\n\n        ctx.lineTo(x, y);\n        ctx.closePath();\n        const origShadowColor = ctx.shadowColor;\n        if (this.shadowOn) {\n          ctx.shadowColor = 'rgba(0,0,0,0.7)';\n          ctx.shadowBlur = Math.log(r) * this.main.params.shadowScale;\n          ctx.shadowOffsetX = Math.log10(r);\n          ctx.shadowOffsetY = Math.log10(r);\n        }\n        ctx.fill();\n        ctx.lineCap = origCap;\n        ctx.fillStyle = origFill;\n        ctx.shadowColor = origShadowColor;\n      } else if (this.type === 'rect') {\n        ctx.beginPath();\n\n        const tl = [\n          this.centerCord[0],\n          this.centerCord[1]];\n\n        let w = this.curCord[0] - this.centerCord[0];\n        let h = this.curCord[1] - this.centerCord[1];\n        if (event.ctrlKey || event.shiftKey) {\n          const min = Math.min(Math.abs(w), Math.abs(h));\n          w = min * Math.sign(w);\n          h = min * Math.sign(h);\n        }\n        const halfLW = this.lineWidth / 2.0;\n        // normalize fix half compensation\n        if (w < 0) {\n          tl[0] += w;\n          w = -w;\n        }\n        if (h < 0) {\n          tl[1] += h;\n          h = -h;\n        }\n        this.ctx.rect(\n          tl[0] + halfLW,\n          tl[1] + halfLW,\n          (w - this.lineWidth),\n          (h - this.lineWidth));\n        this.ctx.fill();\n\n        const origShadowColor = ctx.shadowColor;\n        if (this.shadowOn) {\n          ctx.shadowColor = 'rgba(0,0,0,0.7)';\n          ctx.shadowBlur = this.lineWidth;\n          ctx.shadowOffsetX = this.lineWidth / 2.0;\n          ctx.shadowOffsetY = this.lineWidth / 2.0;\n        }\n        if (this.lineWidth) {\n          // TODO: no shadow on unstroked, do we need it?\n          this.ctx.strokeRect(tl[0], tl[1], w, h);\n        }\n        ctx.shadowColor = origShadowColor;\n\n        this.ctx.closePath();\n      } else if (this.type === 'ellipse') {\n        this.ctx.beginPath();\n        const x1 = this.centerCord[0];\n        const y1 = this.centerCord[1];\n        let w = this.curCord[0] - x1;\n        let h = this.curCord[1] - y1;\n\n        if (event.ctrlKey || event.shiftKey) {\n          const min = Math.min(Math.abs(w), Math.abs(h));\n          w = min * Math.sign(w);\n          h = min * Math.sign(h);\n        }\n\n        const rX = Math.abs(w);\n        const rY = Math.abs(h);\n\n        const tlX = Math.min(x1, x1 + w);\n        const tlY = Math.min(y1, y1 + h);\n\n        this.ctx.save();\n        let xScale = 1;\n        let yScale = 1;\n        let radius;\n        const hR = rX / 2;\n        const vR = rY / 2;\n        if (rX > rY) {\n          yScale = rX / rY;\n          radius = hR;\n        } else {\n          xScale = rY / rX;\n          radius = vR;\n        }\n        this.ctx.scale(1 / xScale, 1 / yScale);\n        this.ctx.arc(\n          (tlX + hR) * xScale,\n          (tlY + vR) * yScale,\n          radius, 0, 2 * Math.PI);\n        this.ctx.restore();\n        this.ctx.fill();\n        const origShadowColor = ctx.shadowColor;\n        if (this.shadowOn) {\n          ctx.shadowColor = 'rgba(0,0,0,0.7)';\n          ctx.shadowBlur = this.lineWidth;\n          ctx.shadowOffsetX = this.lineWidth / 2.0;\n          ctx.shadowOffsetY = this.lineWidth / 2.0;\n        }\n        ctx.stroke();\n        ctx.shadowColor = origShadowColor;\n        this.ctx.beginPath();\n      }\n    }\n  }\n\n  handleMouseUp() {\n    if (this.state.cornerMarked) {\n      this.state.cornerMarked = false;\n      this.main.worklog.captureState();\n    }\n  }\n\n  setPixelSize(size) {\n    this.pixelSize = size;\n  }\n}\n"
  },
  {
    "path": "js/resizer.js",
    "content": "import { tr } from './translation';\nimport { KEYS } from './utils';\n\nexport default class Resizer {\n  constructor(main) {\n    this.main = main;\n\n    this.wrapper = main.wrapper.querySelector('.ptro-resize-widget-wrapper');\n    this.inputW = main.wrapper.querySelector('.ptro-resize-widget-wrapper .ptro-resize-width-input');\n    this.inputH = main.wrapper.querySelector('.ptro-resize-widget-wrapper .ptro-resize-heigth-input');\n\n    this.inputWLimit = 10000;\n    this.inputHLimit = 13000;\n\n    this.linkButton = main.wrapper.querySelector('.ptro-resize-widget-wrapper button.ptro-link');\n    this.linkButtonIcon = main.wrapper.querySelector('.ptro-resize-widget-wrapper button.ptro-link i');\n    this.closeButton = main.wrapper.querySelector('.ptro-resize-widget-wrapper button.ptro-close');\n    this.scaleButton = main.wrapper.querySelector('.ptro-resize-widget-wrapper button.ptro-scale');\n    this.resizeButton = main.wrapper.querySelector('.ptro-resize-widget-wrapper button.ptro-resize');\n    this.linked = true;\n    this.closeButton.onclick = () => {\n      this.startClose();\n    };\n\n    this.scaleButton.onclick = () => {\n      if (!Resizer.validationZeroValue(this.newH, this.newW)) return;\n      const origW = this.main.size.w;\n      const origH = this.main.size.h;\n\n      const tmpData = this.main.canvas.toDataURL();\n\n      this.main.resize(this.newW, this.newH);\n\n      this.main.ctx.save();\n      // this.ctx.translate(h / 2, w / 2);\n      this.main.ctx.scale(this.newW / origW, this.newH / origH);\n      const img = new Image();\n      img.onload = () => {\n        this.main.ctx.drawImage(img, 0, 0);\n        this.main.adjustSizeFull();\n        this.main.ctx.restore();\n        this.main.worklog.captureState();\n        this.startClose();\n      };\n      img.src = tmpData;\n    };\n\n    this.resizeButton.onclick = () => {\n      if (!Resizer.validationZeroValue(this.newH, this.newW)) return;\n      const tmpData = this.main.canvas.toDataURL();\n      this.main.resize(this.newW, this.newH);\n      this.main.clearBackground();\n      const img = new Image();\n      img.onload = () => {\n        this.main.ctx.drawImage(img, 0, 0);\n        this.main.adjustSizeFull();\n        this.main.worklog.captureState();\n        this.startClose();\n      };\n      img.src = tmpData;\n    };\n\n    this.linkButton.onclick = () => {\n      this.linked = !this.linked;\n      if (this.linked) {\n        this.linkButtonIcon.className = 'ptro-icon ptro-icon-linked';\n      } else {\n        this.linkButtonIcon.className = 'ptro-icon ptro-icon-unlinked';\n      }\n    };\n\n    this.inputW.oninput = () => {\n      const widthVal = Number(this.inputW.value);\n      this.validationWidth(widthVal);\n      if (this.linked) {\n        const ratio = this.main.size.ratio;\n        this.newH = Math.round(this.newW / ratio);\n        this.validationHeight(this.newH);\n        this.inputH.value = this.newH;\n      }\n    };\n    this.inputH.oninput = () => {\n      const heightVal = Number(this.inputH.value);\n      this.validationHeight(heightVal);\n      if (this.linked) {\n        const ratio = this.main.size.ratio;\n        this.newW = Math.round(this.newH * ratio);\n        this.validationWidth(this.newW);\n        this.inputW.value = +this.newW;\n      }\n    };\n  }\n\n  validationWidthValue(value) {\n    return value <= this.inputWLimit;\n  }\n\n  validationHeightValue(value) {\n    return value <= this.inputHLimit;\n  }\n\n  static validationEmptyValue(value) {\n    return value !== '' || value !== '0';\n  }\n\n  static validationZeroValue(...args) {\n    let isValid = true;\n    args.forEach((v) => {\n      isValid = !(v === 0) && isValid;\n    });\n    return isValid;\n  }\n\n  validationHeight(value) {\n    if (this.validationHeightValue(value)) {\n      this.newH = value;\n    } else {\n      this.inputH.value = this.inputHLimit;\n      this.newH = this.inputHLimit;\n      return;\n    }\n\n    if (Resizer.validationEmptyValue(value)) {\n      this.newH = value;\n    } else {\n      this.inputH.value = 0;\n      this.newH = 0;\n    }\n  }\n\n  validationWidth(value) {\n    if (this.validationWidthValue(value)) {\n      this.newW = value;\n    } else {\n      this.inputW.value = this.inputWLimit;\n      this.newW = this.inputWLimit;\n      return;\n    }\n\n    if (Resizer.validationEmptyValue(value)) {\n      this.newW = value;\n    } else {\n      this.inputW.value = '0';\n      this.newW = 0;\n    }\n  }\n\n  open() {\n    this.wrapper.removeAttribute('hidden');\n    this.opened = true;\n    this.newW = this.main.size.w;\n    this.newH = this.main.size.h;\n    this.inputW.value = +this.newW;\n    this.inputH.value = +this.newH;\n  }\n\n  close() {\n    this.wrapper.setAttribute('hidden', 'true');\n    this.opened = false;\n  }\n\n  startClose() {\n    this.main.closeActiveTool();\n  }\n\n  handleKeyDown(event) {\n    if (event.keyCode === KEYS.enter) {\n      return true; // mark as handled - user might expect doing save by enter\n    }\n    if (event.keyCode === KEYS.esc) {\n      this.startClose();\n      return true;\n    }\n    return false;\n  }\n\n  static html() {\n    return '' +\n      '<div class=\"ptro-resize-widget-wrapper ptro-common-widget-wrapper ptro-v-middle\" hidden>' +\n        '<div class=\"ptro-resize-widget ptro-color-main ptro-v-middle-in\">' +\n          '<div style=\"display: inline-block\">' +\n            '<table>' +\n              '<tr>' +\n                `<td class=\"ptro-label ptro-resize-table-left\">${tr('width')}</td>` +\n                '<td>' +\n                  '<input class=\"ptro-input ptro-resize-width-input\" type=\"number\" min=\"0\" max=\"3000\" step=\"1\"/>' +\n                '</td>' +\n              '</tr>' +\n              '<tr>' +\n                `<td class=\"ptro-label ptro-resize-table-left\">${tr('height')}</td>` +\n                '<td>' +\n                  '<input class=\"ptro-input ptro-resize-heigth-input\" type=\"number\" min=\"0\" max=\"3000\" step=\"1\"/>' +\n                '</td>' +\n              '</tr>' +\n            '</table>' +\n          '</div>' +\n          '<div class=\"ptro-resize-link-wrapper\">' +\n            `<button type=\"button\" aria-label=\"button resize\" class=\"ptro-icon-btn ptro-link ptro-color-control\" title=\"${tr('keepRatio')}\">` +\n              '<i class=\"ptro-icon ptro-icon-linked\" style=\"font-size: 18px;\"></i>' +\n            '</button>' +\n          '</div>' +\n          '<div></div>' +\n          '<div style=\"margin-top: 40px;\">' +\n            '<button type=\"button\" aria-label=\"resize dimentions\" class=\"ptro-named-btn ptro-resize ptro-color-control\">' +\n                  `${tr('resizeResize')}</button>` +\n            '<button type=\"button\" aria-label=\"resize scale\" class=\"ptro-named-btn ptro-scale ptro-color-control\">' +\n                  `${tr('resizeScale')}</button>` +\n            '<button type=\"button\" aria-label=\"cancel\" class=\"ptro-named-btn ptro-close ptro-color-control\">' +\n                  `${tr('cancel')}</button>` +\n          '</div>' +\n        '</div>' +\n      '</div>';\n  }\n}\n"
  },
  {
    "path": "js/selecter.js",
    "content": "import { clearSelection, KEYS } from './utils';\n\nexport default class PainterroSelecter {\n  constructor(main, selectionCallback) {\n    this.main = main;\n    this.canvas = main.canvas;\n    this.wrapper = main.wrapper;\n    this.ctx = main.ctx;\n    this.areaionCallback = selectionCallback;\n    this.shown = false;\n    this.area = {\n      el: main.toolContainer,\n      rect: document.querySelector(`#${main.id} .ptro-crp-rect`),\n    };\n    this.imagePlaced = false;\n    this.areaionCallback(false);\n  }\n\n  static code() {\n    return '<div class=\"ptro-crp-rect\" hidden>' +\n      '<div class=\"ptro-crp-l select-handler\"></div><div class=\"ptro-crp-r select-handler\"></div>' +\n      '<div class=\"ptro-crp-t select-handler\"></div><div class=\"ptro-crp-b select-handler\"></div>' +\n      '<div class=\"ptro-crp-tl select-handler\"></div><div class=\"ptro-crp-tr select-handler\"></div>' +\n      '<div class=\"ptro-crp-bl select-handler\"></div><div class=\"ptro-crp-br select-handler\"></div>' +\n      '</div>';\n  }\n\n  activate() {\n    this.area.activated = true;\n    this.areaionCallback(false);\n  }\n\n  doCrop() {\n    const imgData = this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h);\n    this.main.resize(\n      this.area.bottoml[0] - this.area.topl[0],\n      this.area.bottoml[1] - this.area.topl[1]);\n    this.main.ctx.putImageData(imgData, -this.area.topl[0], -this.area.topl[1]);\n    this.main.adjustSizeFull();\n    this.main.worklog.captureState();\n  }\n\n  doPixelize() {\n    const c = this.area.topl;\n    const size = [\n      this.area.bottoml[0] - c[0], // width\n      this.area.bottoml[1] - c[1],\n    ];\n\n    this.pixelizePixelSize = this.main.params.pixelizePixelSize;\n\n    if (this.pixelizePixelSize.slice(-1) === '%') {\n      this.pixelSize = (Math.min(size[0], size[1]) / (100.0 / this.pixelizePixelSize.slice(0, -1)));\n    } else if (this.pixelizePixelSize.slice(-2).toLowerCase() === 'px') {\n      this.pixelSize = this.pixelizePixelSize.slice(0, -2);\n    } else {\n      this.pixelSize = this.pixelizePixelSize;\n    }\n\n\n    if (this.pixelSize < 2) {\n      this.pixelSize = 2; // prevent errors\n    }\n\n    if (size[1] < size[0]) {\n      this.pixelSizeY = this.pixelSize;\n      const desiredHorPxs = Math.round(size[0] / this.pixelSizeY);\n      this.pixelSizeX = (size[0] * 1.0) / desiredHorPxs;\n    } else {\n      this.pixelSizeX = this.pixelSize;\n      const desiredVerPxs = Math.round(size[1] / this.pixelSizeX);\n      this.pixelSizeY = (size[1] * 1.0) / desiredVerPxs;\n    }\n    const pxData = [];\n    const pxSize = [size[0] / this.pixelSizeX, size[1] / this.pixelSizeY];\n    for (let i = 0; i < pxSize[0]; i += 1) {\n      const row = [];\n      for (let j = 0; j < pxSize[1]; j += 1) {\n        row.push([0, 0, 0, 0, 0]);\n      }\n      pxData.push(row);\n    }\n    const data = this.ctx.getImageData(c[0], c[1], size[0], size[1]);\n    for (let i = 0; i < size[0]; i += 1) {\n      for (let j = 0; j < size[1]; j += 1) {\n        const ii = Math.floor(i / this.pixelSizeX);\n        const jj = Math.floor(j / this.pixelSizeY);\n        const base = ((j * size[0]) + i) * 4;\n        pxData[ii][jj][0] += data.data[base];\n        pxData[ii][jj][1] += data.data[base + 1];\n        pxData[ii][jj][2] += data.data[base + 2];\n        pxData[ii][jj][3] += data.data[base + 3];\n        pxData[ii][jj][4] += 1;\n      }\n    }\n    for (let i = 0; i < pxSize[0]; i += 1) {\n      for (let j = 0; j < pxSize[1]; j += 1) {\n        const s = pxData[i][j][4];\n        this.ctx.fillStyle = `rgba(\n${Math.round(pxData[i][j][0] / s)}, \n${Math.round(pxData[i][j][1] / s)}, \n${Math.round(pxData[i][j][2] / s)}, \n${Math.round(pxData[i][j][3] / s)})`;\n        const baseX = c[0] + (i * this.pixelSizeX);\n        const baseY = c[1] + (j * this.pixelSizeY);\n        this.ctx.fillRect(baseX, baseY, this.pixelSizeX, this.pixelSizeY);\n      }\n    }\n    this.main.worklog.captureState();\n  }\n\n  doClearArea() {\n    this.ctx.beginPath();\n    this.ctx.clearRect(\n      this.area.topl[0], this.area.topl[1],\n      this.area.bottoml[0] - this.area.topl[0], this.area.bottoml[1] - this.area.topl[1]);\n    this.ctx.rect(this.area.topl[0], this.area.topl[1],\n      this.area.bottoml[0] - this.area.topl[0], this.area.bottoml[1] - this.area.topl[1]);\n    this.ctx.fillStyle = this.main.currentBackground;\n    this.ctx.fill();\n    this.main.worklog.captureState();\n  }\n\n  selectAll() {\n    this.setLeft(0);\n    this.setRight(0);\n    this.setBottom(0);\n    this.setTop(0);\n    this.show();\n    this.reCalcCropperCords();\n    if (this.area.activated) {\n      this.areaionCallback(!this.imagePlaced &&\n        this.area.rect.clientWidth > 0 &&\n        this.area.rect.clientHeight > 0);\n    }\n  }\n\n  getScale() {\n    return this.canvas.clientWidth / this.canvas.getAttribute('width');\n  }\n\n  reCalcCropperCords() {\n    const ratio = this.getScale();\n    this.area.topl = [\n      Math.round(((this.rectLeft() - this.main.elLeft())) / ratio),\n      Math.round(((this.rectTop() - this.main.elTop())) / ratio)];\n\n    this.area.bottoml = [\n      Math.round(this.area.topl[0] + ((this.area.rect.clientWidth + 2) / ratio)),\n      Math.round(this.area.topl[1] + ((this.area.rect.clientHeight + 2) / ratio))];\n  }\n\n  adjustPosition() {\n    if (!this.shown) {\n      return;\n    }\n    const ratio = this.getScale();\n    this.setLeft(this.area.topl[0] * ratio);\n    this.setTop(this.area.topl[1] * ratio);\n    this.setRight(0);\n    this.setRight(this.canvas.clientWidth - (this.area.bottoml[0] * ratio));\n    this.setBottom(this.canvas.clientHeight - (this.area.bottoml[1] * ratio));\n  }\n\n  placeAt(l, t, r, b, img) {\n\n    if (this.imagePlaced) {\n      // for case when user inserts multiple images one after another without finishing placing them\n      this.finishPlacing();\n    }\n\n    this.main.closeActiveTool(true);\n    this.main.setActiveTool(this.main.defaultTool);\n    const scale = this.getScale();\n    this.setLeft(l * scale);\n    this.setTop(t * scale);\n    this.setRight(r * scale);\n    this.setBottom(b * scale);\n    const tmpCan = document.createElement('canvas');\n    tmpCan.width = img.naturalWidth;\n    tmpCan.height = img.naturalHeight;\n    const tmpCtx = tmpCan.getContext('2d');\n    tmpCtx.drawImage(img, 0, 0);\n    this.placedData = tmpCan.toDataURL('image/png');\n    const lowScale = 1000 / Math.max(img.naturalWidth, img.naturalHeight);\n    if (lowScale >= 1) {\n      this.placedDataLow = this.placedData;\n    } else {\n      tmpCan.width = img.naturalWidth * lowScale;\n      tmpCan.height = img.naturalHeight * lowScale;\n      tmpCtx.scale(lowScale, lowScale);\n      tmpCtx.drawImage(img, 0, 0);\n      this.placedDataLow = tmpCan.toDataURL('image/png');\n    }\n    this.main.select.area.rect.style['background-image'] = `url(${this.placedData})`;\n    this.show();\n    this.reCalcCropperCords();\n    this.imagePlaced = true;\n    this.main.select.activate();\n    this.placedRatio = img.naturalWidth / img.naturalHeight;\n  }\n\n  finishPlacing() {\n    this.imagePlaced = false;\n    this.main.select.area.rect.style['background-image'] = 'none';\n    this.main.inserter.insert(\n      this.area.topl[0],\n      this.area.topl[1],\n      this.area.bottoml[0] - this.area.topl[0],\n      this.area.bottoml[1] - this.area.topl[1]);\n    this.area.activated = false;\n  }\n\n  cancelPlacing() {\n    this.imagePlaced = false;\n    this.main.select.area.rect.style['background-image'] = 'none';\n    this.hide();\n    this.main.worklog.undoState();\n  }\n\n  handleKeyDown(evt) {\n    if (this.main.inserter.handleKeyDown(evt)) {\n      return true;\n    }\n    if (this.shown && this.imagePlaced) {\n      if (evt.keyCode === KEYS.enter) {\n        this.finishPlacing();\n        return true;\n      } else if (evt.keyCode === KEYS.esc) {\n        this.cancelPlacing();\n        return true;\n      }\n    } else if (this.shown && evt.keyCode === KEYS.del) {\n      this.doClearArea();\n      return true;\n    } else if (evt.keyCode === KEYS.a && evt.ctrlKey) {\n      this.selectAll();\n      event.preventDefault();\n      return true;\n    } else if (evt.keyCode === KEYS.esc && this.shown) {\n      this.hide();\n      return true;\n    }\n    return false;\n  }\n\n  handleMouseDown(event) {\n\n    const mainClass = event.target.classList[0];\n    const mousDownCallbacks = {\n      'ptro-crp-el': () => {\n        if (this.area.activated) {\n          if (this.imagePlaced) {\n            this.finishPlacing();\n          }\n          const x = (event.clientX - this.main.elLeft()) +\n            this.main.scroller.scrollLeft;\n          const y = (event.clientY - this.main.elTop()) +\n            this.main.scroller.scrollTop;\n\n          this.setLeft(x);\n          this.setTop(y);\n          this.setRight(this.area.el.clientWidth - x);\n          this.setBottom(this.area.el.clientHeight - y);\n\n          this.reCalcCropperCords();\n          this.area.resizingB = true;\n          this.area.resizingR = true;\n          this.hide();\n        }\n      },\n      'ptro-crp-rect': () => {\n        this.area.moving = true;\n        this.area.xHandle = (event.clientX - this.rectLeft()) +\n          this.main.scroller.scrollLeft;\n        this.area.yHandle = (event.clientY - this.rectTop()) +\n          this.main.scroller.scrollTop;\n      },\n      'ptro-crp-tr': () => {\n        this.area.resizingT = true;\n        this.area.resizingR = true;\n      },\n      'ptro-crp-br': () => {\n        this.area.resizingB = true;\n        this.area.resizingR = true;\n      },\n      'ptro-crp-bl': () => {\n        this.area.resizingB = true;\n        this.area.resizingL = true;\n      },\n      'ptro-crp-tl': () => {\n        this.area.resizingT = true;\n        this.area.resizingL = true;\n      },\n      'ptro-crp-t': () => {\n        this.area.resizingT = true;\n      },\n      'ptro-crp-r': () => {\n        this.area.resizingR = true;\n      },\n      'ptro-crp-b': () => {\n        this.area.resizingB = true;\n      },\n      'ptro-crp-l': () => {\n        this.area.resizingL = true;\n      },\n    };\n    if (mainClass in mousDownCallbacks) {\n      mousDownCallbacks[mainClass]();\n      if (this.imagePlaced) {\n        this.main.select.area.rect.style['background-image'] = `url(${this.placedDataLow})`;\n      }\n    }\n  }\n\n  setLeft(v) {\n    this.left = v;\n    this.area.rect.style.left = `${v}px`;\n  }\n\n  setRight(v) {\n    this.right = v;\n    this.area.rect.style.right = `${v}px`;\n  }\n\n  setTop(v) {\n    this.top = v;\n    this.area.rect.style.top = `${v}px`;\n  }\n\n  setBottom(v) {\n    this.bottom = v;\n    this.area.rect.style.bottom = `${v}px`;\n  }\n\n  handleMouseMove(event) {\n    if (!this.area.activated) {\n      return;\n    }\n    if (this.area.moving) {\n      let newLeft = (event.clientX - this.main.elLeft() - this.area.xHandle)\n        + this.main.scroller.scrollLeft;\n      if (newLeft < 0) {\n        newLeft = 0;\n      } else if (newLeft + this.area.rect.clientWidth > this.area.el.clientWidth - 2) {\n        newLeft = this.area.el.clientWidth - this.area.rect.clientWidth - 2;\n      }\n      const hDelta = newLeft - this.left;\n      this.setLeft(newLeft);\n      this.setRight(this.right - hDelta);\n\n      let newTop = (event.clientY - this.main.elTop() - this.area.yHandle)\n        + this.main.scroller.scrollTop;\n      if (newTop < 0) {\n        newTop = 0;\n      } else if (newTop + this.area.rect.clientHeight > this.area.el.clientHeight - 2) {\n        newTop = this.area.el.clientHeight - this.area.rect.clientHeight - 2;\n      }\n      const vDelta = newTop - this.top;\n      this.setTop(newTop);\n      this.setBottom(this.bottom - vDelta);\n      this.reCalcCropperCords();\n    } else {\n      let resizing = false;\n      if (this.area.resizingL) {\n        resizing = true;\n        const absLeft = this.fixCropperLeft(event.clientX + this.main.scroller.scrollLeft);\n        this.setLeft(absLeft - this.main.elLeft());\n        this.reCalcCropperCords();\n      }\n      if (this.area.resizingR) {\n        resizing = true;\n        const absRight = this.fixCropperRight(event.clientX + this.main.scroller.scrollLeft);\n        this.setRight(\n          (this.area.el.clientWidth + this.main.elLeft()) - absRight);\n        this.reCalcCropperCords();\n      }\n      if (this.area.resizingT) {\n        resizing = true;\n        const absTop = this.fixCropperTop(event.clientY + this.main.scroller.scrollTop);\n        this.setTop(absTop - this.main.elTop());\n        this.reCalcCropperCords();\n      }\n      if (this.area.resizingB) {\n        resizing = true;\n        const absBottom = this.fixCropperBottom(event.clientY + this.main.scroller.scrollTop);\n        this.setBottom(\n          (this.area.el.clientHeight + this.main.elTop()) - absBottom);\n        this.reCalcCropperCords();\n      }\n      if (this.imagePlaced && !(event.ctrlKey || event.shiftKey)) {\n        if (this.area.resizingT) {\n          if (this.area.resizingL) {\n            this.leftKeepRatio();\n          } else {\n            this.rightKeepRatio();\n          }\n          this.topKeepRatio();\n          this.reCalcCropperCords();\n        }\n        if (this.area.resizingB) {\n          if (this.area.resizingL) {\n            this.leftKeepRatio();\n          } else {\n            this.rightKeepRatio();\n          }\n          this.bottomKeepRatio();\n          this.reCalcCropperCords();\n        } if (this.area.resizingL) {\n          if (this.area.resizingT) {\n            this.topKeepRatio();\n          } else {\n            this.bottomKeepRatio();\n          }\n          this.leftKeepRatio();\n          this.reCalcCropperCords();\n        } if (this.area.resizingR) {\n          if (this.area.resizingT) {\n            this.topKeepRatio();\n          } else {\n            this.bottomKeepRatio();\n          }\n          this.rightKeepRatio();\n          this.reCalcCropperCords();\n        }\n      }\n      if (resizing && !this.shown) {\n        this.show();\n      }\n      if (resizing) {\n        clearSelection();\n      }\n    }\n  }\n\n  leftKeepRatio() {\n    const newW = this.area.rect.clientHeight * this.placedRatio;\n    const suggLeft = this.main.elLeft() +\n      (this.area.el.clientWidth - this.right - newW - 2);\n    const absLeft = this.fixCropperLeft(suggLeft);\n    this.setLeft(absLeft - this.main.elLeft());\n  }\n\n  topKeepRatio() {\n    const newH = this.area.rect.clientWidth / this.placedRatio;\n    const absTop = this.fixCropperTop(\n      this.main.elTop() + (this.area.el.clientHeight - this.bottom - newH - 2));\n    this.setTop(absTop - this.main.elTop());\n  }\n\n  bottomKeepRatio() {\n    const newH = this.area.rect.clientWidth / this.placedRatio;\n    const absBottom = this.fixCropperBottom(\n      this.main.elTop() +\n      this.top + newH + 2);\n    this.setBottom((this.area.el.clientHeight + this.main.elTop()) - absBottom);\n  }\n\n  rightKeepRatio() {\n    const newW = this.area.rect.clientHeight * this.placedRatio;\n    const absRight = this.fixCropperRight(\n      this.main.elLeft() +\n      this.left + newW + 2);\n    this.setRight((this.area.el.clientWidth + this.main.elLeft()) - absRight);\n  }\n\n  show() {\n    this.shown = true;\n    this.area.rect.removeAttribute('hidden');\n  }\n\n  handleMouseUp() {\n    if (this.area.activated) {\n      this.areaionCallback(!this.imagePlaced && this.area.rect.clientWidth > 0\n        && this.area.rect.clientHeight > 0);\n    }\n    this.area.moving = false;\n    this.area.resizingT = false;\n    this.area.resizingR = false;\n    this.area.resizingB = false;\n    this.area.resizingL = false;\n    if (this.imagePlaced) {\n      this.main.select.area.rect.style['background-image'] = `url(${this.placedData})`;\n    }\n  }\n\n  close() {\n    if (this.imagePlaced) {\n      this.finishPlacing();\n    }\n    this.area.activated = false;\n    this.hide();\n  }\n\n  hide() {\n    this.area.rect.setAttribute('hidden', 'true');\n    this.shown = false;\n    this.areaionCallback(false);\n  }\n\n  draw() {\n    if (this.area.topl) {\n      const ratio = this.canvas.clientWidth / this.canvas.getAttribute('width');\n      this.setLeft(this.area.topl[0] * ratio);\n      this.setTop(this.area.topl[1] * ratio);\n      this.setRight(this.area.el.clientWidth - (\n        (this.area.bottoml[0] - this.area.topl[0]) * ratio));\n      this.setBottom(this.area.el.clientHeight - (\n        (this.area.bottoml[1] - this.area.topl[1]) * ratio));\n    }\n  }\n\n  rectLeft() {\n    return this.area.rect.documentOffsetLeft + this.main.scroller.scrollLeft;\n  }\n\n  rectTop() {\n    return this.area.rect.documentOffsetTop + this.main.scroller.scrollTop;\n  }\n\n  /* fixers */\n  fixCropperLeft(left) {\n    let newLeft = left;\n    const absLeftMiddle = this.rectLeft() + this.area.rect.clientWidth;\n    if (newLeft < this.main.elLeft()) {\n      return this.main.elLeft();\n    } else if (newLeft > absLeftMiddle) {\n      newLeft = absLeftMiddle;\n      if (this.area.resizingL) {\n        this.area.resizingL = false;\n        this.area.resizingR = true;\n      }\n    }\n    return newLeft;\n  }\n\n  fixCropperRight(right) {\n    let newRight = right;\n    const absRightLimit = this.main.elLeft() + this.area.el.clientWidth;\n    if (newRight > absRightLimit) {\n      return absRightLimit;\n    } else if (newRight < this.rectLeft()) {\n      newRight = this.rectLeft() +\n        this.area.rect.clientWidth;\n      if (this.area.resizingR) {\n        this.area.resizingR = false;\n        this.area.resizingL = true;\n      }\n    }\n    return newRight;\n  }\n\n  fixCropperTop(top) {\n    let newTop = top;\n    const absTopMiddle = this.rectTop() + this.area.rect.clientHeight;\n    if (newTop < this.main.elTop()) {\n      return this.main.elTop();\n    } else if (newTop > absTopMiddle) {\n      newTop = absTopMiddle;\n      if (this.area.resizingT) {\n        this.area.resizingT = false;\n        this.area.resizingB = true;\n      }\n    }\n    return newTop;\n  }\n\n  fixCropperBottom(bottom) {\n    let newBottom = bottom;\n    const absBottomLimit = this.main.elTop() + this.area.el.clientHeight;\n    if (newBottom > absBottomLimit) {\n      return absBottomLimit;\n    } else if (newBottom < this.rectTop()) {\n      newBottom = this.rectTop() + this.area.rect.clientHeight;\n      if (this.area.resizingB) {\n        this.area.resizingB = false;\n        this.area.resizingT = true;\n      }\n    }\n    return newBottom;\n  }\n}\n"
  },
  {
    "path": "js/settings.js",
    "content": "import { tr } from './translation';\nimport { trim, KEYS } from './utils';\nimport { setParam } from './params';\n\nexport default class Settings {\n  constructor(main) {\n    this.main = main;\n\n    this.wrapper = main.wrapper.querySelector('.ptro-settings-widget-wrapper');\n    this.inputPixelSize = main.wrapper.querySelector('.ptro-settings-widget-wrapper .ptro-pixel-size-input');\n\n    this.applyButton = main.wrapper.querySelector('.ptro-settings-widget-wrapper button.ptro-apply');\n    this.closeButton = main.wrapper.querySelector('.ptro-settings-widget-wrapper button.ptro-close');\n    this.clearButton = main.wrapper.querySelector('.ptro-settings-widget-wrapper button.ptro-clear');\n    this.bgSelBtn = main.wrapper.querySelector('.ptro-settings-widget-wrapper .ptro-color-btn');\n    this.errorHolder = main.wrapper.querySelector('.ptro-settings-widget-wrapper .ptro-error');\n\n    this.clearButton.onclick = () => {\n      this.main.currentBackground = this.main.colorWidgetState.bg.alphaColor;\n      this.main.currentBackgroundAlpha = this.main.colorWidgetState.bg.alpha;\n      this.main.clearBackground();\n      this.startClose();\n    };\n\n    this.bgSelBtn.onclick = () => {\n      this.main.colorPicker.open(this.main.colorWidgetState.bg);\n    };\n\n    this.closeButton.onclick = () => {\n      this.startClose();\n    };\n\n    if (this.applyButton) {\n      this.applyButton.onclick = () => {\n        let pixelVal = trim(this.inputPixelSize.value);\n        let valid;\n        if (pixelVal.slice(-1) === '%') {\n          const checkInt = trim(pixelVal.slice(0, -1));\n          valid = /^\\d+$/.test(checkInt) && parseInt(checkInt, 10) !== 0;\n          if (valid) {\n            pixelVal = `${checkInt}%`;\n          }\n        } else {\n          valid = /^\\d+$/.test(pixelVal) && parseInt(pixelVal, 10) !== 0;\n        }\n        if (valid) {\n          this.main.select.pixelizePixelSize = pixelVal;\n          setParam('pixelizePixelSize', pixelVal);\n          this.startClose();\n          this.errorHolder.setAttribute('hidden', '');\n        } else {\n          this.errorHolder.innerText = tr('wrongPixelSizeValue');\n          this.errorHolder.removeAttribute('hidden');\n        }\n      };\n    }\n  }\n\n  handleKeyDown(event) {\n    if (event.keyCode === KEYS.enter) {\n      return true; // mark as handled - user might expect doing save by enter\n    }\n    if (event.keyCode === KEYS.esc) {\n      this.startClose();\n      return true;\n    }\n    return false;\n  }\n\n  open() {\n    this.wrapper.removeAttribute('hidden');\n    this.opened = true;\n    if (this.inputPixelSize) {\n      this.inputPixelSize.value = this.main.select.pixelizePixelSize;\n    }\n    this.bgSelBtn.style['background-color'] = this.main.colorWidgetState.bg.alphaColor;\n  }\n\n  close() {\n    this.wrapper.setAttribute('hidden', 'true');\n    this.opened = false;\n  }\n\n  startClose() {\n    this.errorHolder.setAttribute('hidden', '');\n    this.main.closeActiveTool();\n  }\n\n  /* eslint-disable */\n  static html(main) {\n    return '' +\n      '<div class=\"ptro-settings-widget-wrapper ptro-common-widget-wrapper ptro-v-middle\" hidden>' +\n        '<div class=\"ptro-settings-widget ptro-color-main ptro-v-middle-in\">' +\n            '<table style=\"margin-top: 5px\">' +\n              '<tr>' +\n                `<td class=\"ptro-label ptro-resize-table-left\" style=\"height:30px;\">${tr('backgroundColor')}</td>` +\n                '<td class=\"ptro-strict-cell\">' +\n                  '<button type=\"button\" aria-label=\"background\" data-id=\"bg\" class=\"ptro-color-btn ptro-bordered-btn ptro-color-control\" ' +\n                    'style=\"margin-top: -12px;\"></button>' +\n                  '<span class=\"ptro-btn-color-checkers\"></span>' +\n                '</td>' +\n                '<td>' +\n                  `<button type=\"button\" aria-label=\"fill page width\" style=\"margin-top: -2px;\" class=\"ptro-named-btn ptro-clear ptro-color-control\" title=\"${tr('fillPageWith')}\">${tr('clear')}</button>` +\n                '</td>' +\n              '</tr>' +\n              (!main.params.pixelizeHideUserInput ?\n                '<tr>' +\n                  `<td class=\"ptro-label ptro-resize-table-left\" >${tr('pixelizePixelSize')}</td>` +\n                  '<td colspan=\"2\">' +\n                    '<input class=\"ptro-input ptro-pixel-size-input\" pattern=\"[0-9]{1,}%?\" type=\"text\" />' +\n                  '</td>' +\n                '</tr>' : '') +\n            '</table>' +\n            '<div class=\"ptro-error\" hidden></div>' +\n            '<div style=\"margin-top: 20px\">' +\n              (!main.params.pixelizeHideUserInput ?\n                '<button type=\"button\" aria-label=\"apply\" class=\"ptro-named-btn ptro-apply ptro-color-control\">' +\n                      `${tr('apply')}</button>` +\n                `<button type=\"button\" aria-label=\"cancel\" class=\"ptro-named-btn ptro-close ptro-color-control\">${tr('cancel')}</button>`\n                :\n                `<button type=\"button\" aria-label=\"close\" class=\"ptro-named-btn ptro-close ptro-color-control\">${tr('close')}</button>`\n              ) +\n            '</div>' +\n        '</div>' +\n      '</div>';\n  }\n  /* eslint-enable */\n}\n"
  },
  {
    "path": "js/text.js",
    "content": "import { KEYS } from './utils';\nimport { tr } from './translation';\nimport domtoimage from 'dom-to-image';\n\nexport default class TextTool {\n  constructor(main) {\n    this.ctx = main.ctx;\n    this.el = main.toolContainer;\n    this.main = main;\n    this.wrapper = main.wrapper;\n    this.input = this.el.querySelector('.ptro-text-tool-input');\n    this.inputWrapper = this.el.querySelector('.ptro-text-tool-input-wrapper');\n    this.inputWrapper.style.display = 'none';\n    this.isBold = main.params.defaultFontBold;\n    this.isItalic = main.params.defaultFontItalic;\n    this.strokeOn = main.params.defaultTextStrokeAndShadow;\n\n    this.strokeColor = main.params.textStrokeAlphaColor;\n    this.setFontSize(main.params.defaultFontSize);\n    this.setFont(this.getFonts()[0].value);\n    this.setFontIsBold(this.isBold);\n    this.setFontIsItalic(this.isItalic);\n\n    this.el.querySelector('.ptro-text-tool-apply').onclick = () => {\n      this.apply();\n    };\n\n    this.el.querySelector('.ptro-text-tool-cancel').onclick = () => {\n      this.close();\n    };\n  }\n\n  getFont() {\n    return this.font;\n  }\n\n  getFonts() {\n    const fonts = [\n      'Arial, Helvetica, sans-serif',\n      '\"Arial Black\", Gadget, sans-serif',\n      '\"Comic Sans MS\", cursive, sans-serif',\n      'Impact, Charcoal, sans-serif',\n      '\"Lucida Sans Unicode\", \"Lucida Grande\", sans-serif',\n      'Tahoma, Geneva, sans-serif',\n      '\"Trebuchet MS\", Helvetica, sans-serif',\n      'Verdana, Geneva, sans-serif',\n      '\"Courier New\", Courier, monospace',\n      '\"Lucida Console\", Monaco, monospace',\n      ...this.main.params.extraFonts,\n    ];\n\n    const res = [];\n    fonts.forEach((f) => {\n      const fontName = f.split(',')[0].replace(/\"/g, '');\n      res.push({\n        value: f,\n        name: fontName,\n        extraStyle: `font-family:${f}`,\n        title: fontName,\n      });\n    });\n    return res;\n  }\n\n  setFont(font) {\n    this.font = font;\n    this.input.style['font-family'] = font;\n    if (this.active) {\n      this.input.focus();\n    }\n    if (this.active) {\n      this.reLimit();\n    }\n  }\n\n  setStrokeOn(state) {\n    this.strokeOn = state;\n    this.setStrokeParams();\n  }\n\n  setFontIsBold(state) {\n    this.isBold = state;\n    if (state) {\n      this.input.style['font-weight'] = 'bold';\n    } else {\n      this.input.style['font-weight'] = 'normal';\n    }\n    if (this.active) {\n      this.input.focus();\n      this.reLimit();\n    }\n    this.setStrokeParams();\n  }\n\n  setFontIsItalic(state) {\n    this.isItalic = state;\n    if (state) {\n      this.input.style['font-style'] = 'italic';\n    } else {\n      this.input.style['font-style'] = 'normal';\n    }\n    if (this.active) {\n      this.input.focus();\n      this.reLimit();\n    }\n  }\n\n  setFontSize(size) {\n    this.fontSize = size;\n    this.input.style['font-size'] = `${size}px`;\n    this.setStrokeParams();\n    if (this.active) {\n      this.reLimit();\n    }\n  }\n\n  setStrokeParams() {\n    if (this.strokeOn) {\n      const st = 1;\n      this.input.style['text-shadow'] = `\n      -${st}px -${st}px 1px ${this.strokeColor},${st}px -${st}px 1px ${this.strokeColor},\n      -${st}px  ${st}px 1px ${this.strokeColor},${st}px  ${st}px 1px ${this.strokeColor},\n      ${st}px ${st}px ${Math.log(this.fontSize) * this.main.params.shadowScale}px black`;\n    } else {\n      this.input.style['text-shadow'] = 'none';\n    }\n  }\n\n  setFontColor(color) {\n    this.color = color;\n    this.input.style.color = color;\n    this.input.style['outline-color'] = color;\n  }\n\n  inputLeft() {\n    return this.input.documentOffsetLeft + this.main.scroller.scrollLeft;\n  }\n\n  inputTop() {\n    return this.input.documentOffsetTop + this.main.scroller.scrollTop;\n  }\n\n  reLimit() {\n    this.inputWrapper.style.right = 'auto';\n    if (this.inputLeft() + this.input.clientWidth >\n        this.main.elLeft() + this.el.clientWidth) {\n      this.inputWrapper.style.right = '0';\n    } else {\n      this.inputWrapper.style.right = 'auto';\n    }\n\n    this.inputWrapper.style.bottom = 'auto';\n    if (this.inputTop() + this.input.clientHeight >\n        this.main.elTop() + this.el.clientHeight) {\n      this.inputWrapper.style.bottom = '0';\n    } else {\n      this.inputWrapper.style.bottom = 'auto';\n    }\n  }\n\n  handleMouseDown(event) {\n    const mainClass = event.target.classList[0];\n    if (mainClass === 'ptro-crp-el') {\n      if (!this.active) {\n        this.input.innerHTML = '<br>';\n        this.pendingClear = true;\n      }\n      this.active = true;\n      this.crd = [\n        (event.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n        (event.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n      ];\n      const scale = this.main.getScale();\n      this.scaledCord = [this.crd[0] * scale, this.crd[1] * scale];\n      this.inputWrapper.style.left = `${this.crd[0]}px`;\n      this.inputWrapper.style.top = `${this.crd[1]}px`;\n      this.inputWrapper.style.display = 'inline';\n      this.input.focus();\n      this.reLimit();\n      this.input.onkeydown = (e) => {\n        if (e.ctrlKey && e.keyCode === KEYS.enter) {\n          this.apply();\n          e.preventDefault();\n        }\n        if (e.keyCode === KEYS.esc) {\n          this.close();\n          this.main.closeActiveTool();\n          e.preventDefault();\n        }\n        this.reLimit();\n        if (this.pendingClear) {\n          this.input.innerText = this.input.innerText.slice(1);\n          this.pendingClear = false;\n        }\n        e.stopPropagation();\n      };\n      if (!this.main.isMobile) {\n        event.preventDefault();\n      }\n    }\n  }\n\n  apply() {\n    const origBorder = this.input.style.border;\n    const origOutline = this.input.style.outline;\n    const scale = this.main.getScale();\n\n    this.input.style.border = 'none';\n    const domToImageConfig = {\n      style: {\n        'transform-origin': 'top left',\n        transform: `scale(${scale})`,\n        'overflow-wrap': 'break-word',\n        'word-wrap': 'break-word',\n        'white-space': 'pre-wrap',\n        overflow: 'hidden',\n        width: this.input.clientWidth + 'px',\n        height: this.input.clientHeight + 'px',\n      },\n      width: this.input.clientWidth * (scale < 1 ? (1 / scale) : scale),\n      height: this.input.clientHeight * (scale < 1 ? (1 / scale) : scale),\n    };\n    domtoimage.toPng(this.input, domToImageConfig)\n      .then((dataUrl) => {\n        const img = new Image();\n        img.src = dataUrl;\n        img.onload = () => {\n          this.ctx.drawImage(img, this.scaledCord[0], this.scaledCord[1]);\n          this.input.style.border = origBorder;\n          this.input.style.outline = origOutline;\n          this.close();\n          this.main.worklog.captureState();\n          this.main.closeActiveTool();\n        };\n      })\n      .catch(function (error) {\n          console.error('oops, something went wrong!', error);\n      });\n  }\n\n  close() {\n    this.active = false;\n    this.inputWrapper.style.display = 'none';\n  }\n\n  static code() {\n    return '<span class=\"ptro-text-tool-input-wrapper\">' +\n      '<div contenteditable=\"true\" class=\"ptro-text-tool-input\"></div>' +\n        '<span class=\"ptro-text-tool-buttons\">' +\n          `<button type=\"button\" aria-label=\"apply\" class=\"ptro-text-tool-apply ptro-icon-btn ptro-color-control\" title=\"${tr('apply')}\" \n                   style=\"margin: 2px\">` +\n            '<i class=\"ptro-icon ptro-icon-apply\"></i>' +\n          '</button>' +\n          `<button type=\"button\" aria-label=\"close\" class=\"ptro-text-tool-cancel ptro-icon-btn ptro-color-control\" title=\"${tr('cancel')}\"\n                   style=\"margin: 2px\">` +\n            '<i class=\"ptro-icon ptro-icon-close\"></i>' +\n          '</button>' +\n        '</span>' +\n      '</span>';\n  }\n}\n"
  },
  {
    "path": "js/translation.js",
    "content": "import de from \"../langs/de.lang\";\nimport en from \"../langs/en.lang\";\nimport es from \"../langs/es.lang\";\nimport ca from \"../langs/ca.lang\";\nimport fr from \"../langs/fr.lang\";\nimport pl from \"../langs/pl.lang\";\nimport ptPTl from \"../langs/pt-PT.lang\";\nimport ptBRl from \"../langs/pt-BR.lang\";\nimport ru from \"../langs/ru.lang\";\nimport ja from \"../langs/ja.lang\";\nimport nl from \"../langs/nl.lang\";\nimport uk from \"../langs/uk.lang\";\n\nlet instance = null;\n\nexport default class Translation {\n  constructor() {\n    this.translations = {\n      de,\n      en,\n      es,\n      ca,\n      fr,\n      pl,\n      \"pt-PT\": ptPTl,\n      \"pt-BR\": ptBRl,\n      ru,\n      ja,\n      nl,\n      uk,\n    };\n    this.defaultTranslator = this.translations.en;\n  }\n\n  static get() {\n    if (instance) {\n      return instance;\n    }\n    instance = new Translation();\n    return instance;\n  }\n\n  addTranslation(name, dict) {\n    this.translations[name] = dict;\n  }\n\n  activate(trans) {\n    if (this.translations[trans] !== undefined) {\n      this.trans = trans;\n      this.translator = this.translations[this.trans];\n    } else {\n      this.translator = this.defaultTranslator;\n    }\n  }\n\n  tr(sentense) {\n    const levels = sentense.split(\".\");\n    let res = this.translator;\n    let fallbackRes = this.defaultTranslator;\n    levels.forEach((l) => {\n      fallbackRes = fallbackRes[l];\n      if (res !== undefined) {\n        res = res[l];\n      }\n    });\n    return res || fallbackRes;\n  }\n}\nexport function activate(a) {\n  return Translation.get().activate(a);\n}\nexport function tr(n) {\n  return Translation.get().tr(n);\n}\n"
  },
  {
    "path": "js/utils.js",
    "content": "export function genId() {\n  let text = \"ptro\";\n  const possible =\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\";\n  for (let i = 0; i < 20; i += 1) {\n    text += possible.charAt(Math.floor(Math.random() * possible.length));\n  }\n  return text;\n}\n\nexport function addDocumentObjectHelpers() {\n  if (!(\"documentOffsetTop\" in Element.prototype)) {\n    Object.defineProperty(Element.prototype, \"documentOffsetTop\", {\n      get() {\n        return this.getBoundingClientRect().top;\n      },\n    });\n  }\n  if (!(\"documentOffsetLeft\" in Element.prototype)) {\n    Object.defineProperty(Element.prototype, \"documentOffsetLeft\", {\n      get() {\n        return this.getBoundingClientRect().left;\n      },\n    });\n  }\n\n  if (!(\"documentClientWidth\" in Element.prototype)) {\n    Object.defineProperty(Element.prototype, \"documentClientWidth\", {\n      get() {\n        const rect = this.getBoundingClientRect();\n        if (rect.width) {\n          // `width` is available for IE9+\n          return rect.width;\n        }\n        // Calculate width for IE8 and below\n        return rect.right - rect.left;\n      },\n    });\n  }\n\n  if (!(\"documentClientHeight\" in Element.prototype)) {\n    Object.defineProperty(Element.prototype, \"documentClientHeight\", {\n      get() {\n        const rect = this.getBoundingClientRect();\n        if (rect.height) {\n          return rect.height;\n        }\n        return rect.bottom - rect.top;\n      },\n    });\n  }\n}\n\nexport function clearSelection() {\n  let selection = null;\n  if (window.getSelection) {\n    selection = window.getSelection();\n  } else if (document.selection) {\n    selection = document.selection;\n  }\n  if (selection) {\n    if (selection.empty) {\n      selection.empty();\n    }\n    if (selection.removeAllRanges) {\n      selection.removeAllRanges();\n    }\n  }\n}\n\nexport function distance(p1, p2) {\n  const a = p1.x - p2.x;\n  const b = p1.y - p2.y;\n  return Math.sqrt(a * a + b * b);\n}\n\nexport function trim(s) {\n  return String(s).replace(/^\\s+|\\s+$/g, \"\");\n}\n\nexport const KEYS = {\n  y: 89,\n  z: 90,\n  s: 83,\n  c: 67,\n  x: 88,\n  a: 65,\n  l: 76,\n  p: 80,\n  r: 82,\n  o: 79,\n  b: 66,\n  e: 69,\n  t: 84,\n  f: 70,\n  enter: 13,\n  esc: 27,\n  del: 46,\n};\n\n// Copies a string to the clipboard. Must be called from within an\n// event handler such as click. May return false if it failed, but\n// this is not always possible. Browser support for Chrome 43+,\n// Firefox 42+, Safari 10+, Edge and IE 10+.\n// IE: The clipboard feature may be disabled by an administrator. By\n// default a prompt is shown the first time the clipboard is\n// used (per session).\nexport function copyToClipboard(text) {\n  if (window.clipboardData && window.clipboardData.setData) {\n    // IE specific code path to prevent textarea being shown while dialog is visible.\n    window.clipboardData.setData(\"Text\", text);\n  } else if (\n    document.queryCommandSupported &&\n    document.queryCommandSupported(\"copy\")\n  ) {\n    const textarea = document.createElement(\"textarea\");\n    textarea.textContent = text;\n    textarea.style.position = \"fixed\"; // Prevent scrolling to bottom of page in MS Edge.\n    document.body.appendChild(textarea);\n    textarea.select();\n    try {\n      document.execCommand(\"copy\"); // Security exception may be thrown by some browsers.\n    } catch (ex) {\n      console.warn(\"Copy to clipboard failed.\", ex);\n    } finally {\n      document.body.removeChild(textarea);\n    }\n  }\n}\n\nexport function getScrollbarWidth() {\n  const outer = document.createElement(\"div\");\n  outer.style.visibility = \"hidden\";\n  outer.style.width = \"100px\";\n  outer.style.msOverflowStyle = \"scrollbar\"; // needed for WinJS apps\n  document.body.appendChild(outer);\n  const widthNoScroll = outer.offsetWidth;\n  outer.style.overflow = \"scroll\";\n  const inner = document.createElement(\"div\");\n  inner.style.width = \"100%\";\n  outer.appendChild(inner);\n  const widthWithScroll = inner.offsetWidth;\n  outer.parentNode.removeChild(outer);\n  return widthNoScroll - widthWithScroll;\n}\n\nexport function imgToDataURL(url, callback, failedCb) {\n  const xhr = new XMLHttpRequest();\n  xhr.onload = () => {\n    const reader = new FileReader();\n    reader.onloadend = () => {\n      callback(reader.result);\n    };\n    reader.readAsDataURL(xhr.response);\n  };\n  xhr.onerror = () => {\n    if (typeof failedCb === \"function\") {\n      failedCb();\n    }\n  };\n  xhr.open(\"GET\", url);\n  xhr.responseType = \"blob\";\n  xhr.send();\n}\n\nexport function logError(error) {\n  console.warn(`[Painterro] ${error}`);\n}\n\nexport function checkIn(what, where) {\n  return where.indexOf(what) !== -1;\n}\n\nexport function setPrimitiveToolValue(value, primitiveTool, method, param) {\n  primitiveTool[method](value);\n  const selector = `[data-id=\"${param}\"]`;\n  const ctl = document.querySelector(selector);\n  console.log(ctl, value);\n\n  if (ctl) {\n    if (method === \"setShadowOn\") {\n      ctl.setAttribute(\"data-value\", value ? \"true\" : \"false\");\n    } else {\n      ctl.value = value;\n    }\n  } else {\n    console.warn(`Control not found: ${selector}`);\n  }\n}\n"
  },
  {
    "path": "js/worklog.js",
    "content": "export default class WorkLog {\n  constructor(main, changedHandler) {\n    this.main = main;\n    this.current = null;\n    this.changedHandler = changedHandler;\n    this.empty = true;\n    this.clean = true;\n    this.ctx = main.ctx;\n  }\n\n  getWorklogAsString(params) {\n    const saveState = Object.assign({}, this.current);\n    let curCleared = this.clearedCount;\n\n    if (params.limit !== undefined) {\n      const limit = params.limit;\n      curCleared = 0;\n      let active = saveState;\n      let i;\n      for (i = 0; i < limit; i += 1) {\n        active.prevCount = limit - i;\n        if (i < limit - 1 && active.prev) {\n          active = active.prev;\n        }\n      }\n      active.prev = null;\n    }\n    return JSON.stringify({\n      clearedCount: curCleared,\n      current: saveState,\n    });\n  }\n\n  loadWorklogFromString(str) {\n    const obj = JSON.parse(str);\n    if (obj) {\n      this.clearedCount = obj.clearedCount;\n      this.current = obj.current;\n      this.applyState(this.current);\n    }\n    return this.main;\n  }\n\n  changed(initial) {\n    if (this.current.prevCount - this.clearedCount > this.main.params.worklogLimit) {\n      this.first = this.first.next;\n      this.first.prev = null;\n      this.clearedCount += 1;\n    }\n    this.changedHandler({\n      first: this.current.prev === null,\n      last: this.current.next === null,\n      initial,\n    });\n    this.empty = initial;\n    this.clean = false;\n  }\n\n  captureState(initial) {\n    let activeToolName = this.main.activeTool ? this.main.activeTool.name : null;\n    if (this.main.params.NON_SELECTABLE_TOOLS.includes(activeToolName)) {\n      activeToolName = this.main.defaultTool.name;\n    }\n\n    const state = {\n      sizew: this.main.size.w,\n      sizeh: this.main.size.h,\n      activeToolName,\n      data: this.ctx.getImageData(0, 0, this.main.size.w, this.main.size.h),\n    };\n    if (this.current === null) {\n      state.prev = null;\n      state.prevCount = 0;\n      this.first = state;\n      this.clearedCount = 0;\n    } else {\n      state.prev = this.current;\n      state.prevCount = this.current.prevCount + 1;\n      this.current.next = state;\n    }\n    state.next = null;\n    this.current = state;\n    this.changed(initial);\n  }\n\n  reCaptureState() {\n    if (this.current.prev !== null) {\n      this.current = this.current.prev;\n    }\n    this.captureState();\n  }\n\n  applyState(state) {\n    this.main.resize(state.sizew, state.sizeh);\n    this.main.ctx.putImageData(state.data, 0, 0);\n    this.main.adjustSizeFull();\n    this.main.select.hide();\n  }\n\n  undoState() {\n    if (this.current.prev !== null) {\n      let currentToolName = this.current.activeToolName;\n      this.current = this.current.prev;\n      this.applyState(this.current);\n      this.changed(false);\n      if (currentToolName) {\n        this.main.closeActiveTool(true);\n        this.main.setActiveTool(this.main.toolByName[currentToolName])\n      } else {\n        this.main.closeActiveTool();\n      }\n      \n      if (this.main.params.onUndo) {\n        this.main.params.onUndo(this.current);\n      }\n    }\n  }\n\n  redoState() {\n    if (this.current.next !== null) {\n      \n      this.current = this.current.next;\n      this.applyState(this.current);\n      this.changed(false);\n\n      const nextToolName = this.current.activeToolName;\n\n      if (nextToolName) {\n        this.main.closeActiveTool(true);\n        this.main.setActiveTool(this.main.toolByName[nextToolName])\n      } else {\n        this.main.closeActiveTool();\n      }\n\n      if (this.main.params.onRedo) {\n        this.main.params.onRedo(this.current);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "js/zoomHelper.js",
    "content": "export default class ZoomHelper {\n  constructor(main) {\n    this.main = main;\n    this.zomer = main.wrapper.querySelector('.ptro-zoomer');\n    this.zomerCtx = this.zomer.getContext('2d');\n    this.canvas = this.main.canvas;\n    this.ctx = this.main.ctx;\n    this.wrapper = this.main.wrapper;\n\n    this.gridColor = this.zomerCtx.createImageData(1, 1);\n    this.gridColor.data[0] = 255;\n    this.gridColor.data[1] = 255;\n    this.gridColor.data[2] = 255;\n    this.gridColor.data[3] = 255;\n\n    this.gridColorRed = this.zomerCtx.createImageData(1, 1);\n    this.gridColorRed.data[0] = 255;\n    this.gridColorRed.data[1] = 0;\n    this.gridColorRed.data[2] = 0;\n    this.gridColorRed.data[3] = 255;\n\n    this.captW = 7;\n    this.middle = Math.ceil(this.captW / 2) - 1;\n    this.periodW = 8;\n\n    this.fullW = this.captW * (this.periodW);\n    this.halfFullW = this.fullW / 2;\n    this.zomer.setAttribute('width', this.fullW);\n    this.zomer.setAttribute('height', this.fullW);\n    this.cursor = this.wrapper.style.cursor;\n  }\n\n  handleMouseMove(e) {\n    if (this.main.colorPicker.choosing && !e.altKey) {\n      if (!this.shown) {\n        this.shown = true;\n        this.zomer.style.display = 'block';\n        this.cursor = this.wrapper.style.cursor;\n        this.wrapper.style.cursor = 'none';\n      }\n      const scale = this.main.getScale();\n      const cord = [\n        (e.clientX - this.main.elLeft()) + this.main.scroller.scrollLeft,\n        (e.clientY - this.main.elTop()) + this.main.scroller.scrollTop,\n      ];\n\n      let x = cord[0] * scale;\n      x = x < 1 ? 1 : x;\n      x = x > this.main.size.w - 1 ? this.main.size.w - 1 : x;\n      let y = cord[1] * scale;\n      y = y < 1 ? 1 : y;\n      y = y > this.main.size.h - 1 ? this.main.size.h - 1 : y;\n\n      const captW = this.captW;\n      const periodW = this.periodW;\n\n      for (let i = 0; i < captW; i += 1) {\n        for (let j = 0; j < captW; j += 1) {\n          const d = this.ctx.getImageData((x + i) - this.middle, (y + j) - this.middle, 1, 1);\n          for (let ii = 0; ii < periodW; ii += 1) {\n            for (let jj = 0; jj < periodW; jj += 1) {\n              if (ii === periodW - 1 || jj === periodW - 1) {\n                if ((i === this.middle && j === this.middle) ||\n                  (i === this.middle && j === this.middle - 1 && jj === periodW - 1) ||\n                  (i === this.middle - 1 && j === this.middle && ii === periodW - 1)) {\n                  this.zomerCtx.putImageData(\n                    this.gridColorRed, (i * periodW) + ii,\n                    (j * periodW) + jj);\n                } else {\n                  this.zomerCtx.putImageData(\n                    this.gridColor, (i * periodW) + ii,\n                    (j * periodW) + jj);\n                }\n              } else {\n                this.zomerCtx.putImageData(d, (i * periodW) + ii, (j * periodW) + jj);\n              }\n            }\n          }\n        }\n      }\n      this.zomer.style.left = `${e.clientX - this.wrapper.documentOffsetLeft - this.halfFullW}px`;\n      this.zomer.style.top = `${e.clientY - this.wrapper.documentOffsetTop - this.halfFullW}px`;\n    } else if (this.shown) {\n      this.hideZoomHelper();\n    }\n  }\n\n  hideZoomHelper() {\n    this.zomer.style.display = 'none';\n    this.wrapper.style.cursor = this.cursor;\n    this.shown = false;\n  }\n\n  static html() {\n    return '<canvas class=\"ptro-zoomer\" width=\"\" height=\"0\"></canvas>';\n  }\n}\n"
  },
  {
    "path": "langs/ca.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Color de línia',\n  fillColor: 'O',\n  fillColorFull: 'Color per omplir',\n  alpha: 'T',\n  alphaFull: 'Transparència',\n  lineWidth: 'A',\n  lineWidthFull: 'Ample de línia',\n  arrowLength: 'L',\n  arrowLengthFull: 'Longitud de la fletxa',\n  eraserWidth: 'B',\n  eraserWidthFull: 'Ample de borrador',\n  textColor: 'C',\n  textColorFull: 'Color de texte',\n  fontSize: 'L',\n  fontSizeFull: 'Tamany de la lletra',\n  fontStrokeSize: 'TLl',\n  fontStrokeSizeFull: 'Tamany linea de la lletra',\n  fontStyle: 'EL',\n  fontStyleFull: 'Estil de lletra',\n  fontName: 'NL',\n  fontNameFull: 'Nom del tipus de lletra',\n  textStrokeColor: 'CL',\n  textStrokeColorFull: 'Color de lletra',\n  apply: 'Aplicar',\n  cancel: 'Cancel·lar',\n  close: 'Tancar',\n  clear: 'Netejar',\n  width: 'Ample',\n  height: 'Alt',\n  keepRatio: 'Mantenir ratio Ample/Alt',\n  fillPageWith: 'Omplir la pàgina amb el color de fons actual',\n  pixelSize: 'P',\n  pixelSizeFull: 'Tamany de pixel',\n  resizeScale: 'Escala',\n  resizeResize: 'Redimensionar',\n  backgroundColor: 'Color de fons de la pàgina',\n  pixelizePixelSize: 'Tamany de píxel al pixelar',\n  wrongPixelSizeValue:\n    'Tamany de píxel incorrecte. Pots entrar per exemple \\'20%\\' que significa que el tamany de píxel és 1/5 de l\\'àrea seleccionada, o \\'4\\' significa 4 px',\n  tools: {\n    crop: 'Retallar imatge a l\\'àrea seleccionada',\n    pixelize: 'Pixelar l\\'àrea seleccionada',\n    rect: 'Dibuixar rectangle',\n    ellipse: 'Dibuixar eclipse',\n    line: 'Dibuixar línia',\n    arrow: 'Dibuixa la fletxa',\n    rotate: 'Rotar imatge',\n    save: 'Guardar image',\n    load: 'Carregar image',\n    text: 'Escriure texte',\n    brush: 'Pinzell',\n    resize: 'Cambiar tamany o escalar',\n    open: 'Obrir imatge',\n    select: 'Seleció d\\'àrea',\n    close: 'Tancar editor',\n    eraser: 'Borrador',\n    settings: 'Paràmetres',\n    undo: 'Desfer',\n    filters: 'Filtres',\n  },\n  pasteOptions: {\n    fit: 'Reemplaçar tot',\n    extend_down: 'Extendre cap avall',\n    extend_right: 'Extendre a la dreta',\n    extend: 'Extendre',\n    over: 'Empegar al damunt',\n    how_to_paste: 'Com s\\'ha d\\'empegar?',\n  },\n};\n"
  },
  {
    "path": "langs/de.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Linienfarbe',\n  fillColor: 'F',\n  fillColorFull: 'Füllfarbe',\n  alpha: 'A',\n  alphaFull: 'Alpha',\n  lineWidth: 'B',\n  lineWidthFull: 'Linienbreite',\n  arrowLength: 'L',\n  arrowLengthFull: 'Pfeillänge',\n  eraserWidth: 'E',\n  eraserWidthFull: 'Radiergummibreite',\n  textColor: 'C',\n  textColorFull: 'Textfarbe',\n  fontSize: 'S',\n  fontSizeFull: 'Schriftgröße',\n  fontStrokeSize: 'St',\n  fontStrokeSizeFull: 'Strichbreite',\n  fontStyle: 'FS',\n  fontStyleFull: 'Schriftstil',\n  fontName: 'F',\n  fontNameFull: 'Schriftartenname',\n  textStrokeColor: 'SC',\n  textStrokeColorFull: 'Strichfarbe',\n  apply: 'Anwenden',\n  cancel: 'Abbrechen',\n  close: 'Schließen',\n  clear: 'Zurücksetzen',\n  width: 'Breite',\n  height: 'Höhe',\n  keepRatio: 'Breiten- / Höhenverhältnis beibehalten',\n  fillPageWith: 'Füllen Sie die Seite mit der aktuellen Hintergrundfarbe',\n  pixelSize: 'P',\n  pixelSizeFull: 'Pixel Größe',\n  resizeScale: 'Maßstab',\n  resizeResize: 'Größe ändern',\n  backgroundColor: 'Hintergrundfarbe der Seite',\n  pixelizePixelSize: 'Pixelate Pixelgröße',\n  language: 'Sprache',\n  wrongPixelSizeValue:\n    'Falsche Pixelgröße. Sie können zum Beispiel \\'20%\\' eingeben. Welche mittlere Pixelgröße wird 1/5 von die ausgewählte Bereichsseite, oder \\'4\\' bedeutet 4 px',\n  tools: {\n    crop: 'Bild auf ausgewählten Bereich zuschneiden',\n    pixelize: 'Pixelisierung des ausgewählten Bereiches',\n    rect: 'Rechteck zeichnen',\n    ellipse: 'Ellipse zeichnen',\n    line: 'Linie zeichnen',\n    arrow: 'Pfeil zeichnen',\n    rotate: 'Bild umdrehen',\n    save: 'Bild speichern',\n    load: 'Bild hochladen',\n    text: 'Text hinzufügen',\n    brush: 'Pinsel',\n    resize: 'Größe ändern oder skalieren',\n    open: 'Bild öffnen',\n    select: 'Bereich auswählen',\n    close: ' Painterro schließen',\n    eraser: 'Radierer',\n    settings: 'Einstellungen',\n    undo: 'Rückgängig machen',\n    redo: 'Wiederholen',\n  },\n  pasteOptions: {\n    fit: 'Alle Austauschen ',\n    extend_down: 'Nach unten strecken',\n    extend_right: 'Nach rechts strecken',\n    extend: 'Strecken',\n    over: 'Überkleben',\n    how_to_paste: 'Wie füge ich hinzu?',\n  },\n};\n"
  },
  {
    "path": "langs/en.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Line color',\n  fillColor: 'F',\n  fillColorFull: 'Fill color',\n  alpha: 'A',\n  alphaFull: 'Alpha',\n  lineWidth: 'W',\n  lineWidthFull: 'Line width',\n  arrowLength: 'L',\n  arrowLengthFull: 'Arrow length',\n  eraserWidth: 'E',\n  eraserWidthFull: 'Eraser width',\n  textColor: 'C',\n  textColorFull: 'Text color',\n  fontSize: 'S',\n  fontSizeFull: 'Font size',\n  fontStrokeSize: 'St',\n  fontStrokeSizeFull: 'Stroke width',\n  fontIsBold: '<b>B</b>',\n  fontIsBoldFull: 'Bold',\n  fontIsItalic: '<i>I</i>',\n  fontIsItalicFull: 'Italic',\n  shadowOn: 'SH',\n  shadowOnFull: 'Shadow',\n  fontStrokeAndShadow: 'S&S',\n  fontStrokeAndShadowFull: 'Stroke & Shadow',\n\n  fontName: 'F',\n  fontNameFull: 'Font name',\n  textStrokeColor: 'SC',\n  textStrokeColorFull: 'Stroke color',\n  apply: 'Apply',\n  cancel: 'Cancel',\n  close: 'Close',\n  clear: 'Clear',\n  width: 'Width',\n  height: 'Height',\n  keepRatio: 'Keep width/height ratio',\n  fillPageWith: 'Fill page with current background color',\n  pixelSize: 'P',\n  pixelSizeFull: 'Pixel size',\n  resizeScale: 'Scale',\n  resizeResize: 'Resize',\n  backgroundColor: 'Page background color',\n  pixelizePixelSize: 'Pixelize pixel size',\n  language: 'Language',\n  wrongPixelSizeValue:\n    'Wrong pixel size. You can enter e.g. \\'20%\\' which mean pixel size will be 1/5 of ' +\n    'the selected area side, or \\'4\\' means 4 px',\n  tools: {\n    crop: 'Crop image to selected area',\n    pixelize: 'Pixelize selected area',\n    rect: 'Draw rectangle',\n    ellipse: 'Draw ellipse',\n    line: 'Draw line',\n    arrow: 'Draw arrow',\n    rotate: 'Rotate image',\n    save: 'Save image',\n    load: 'Load image',\n    text: 'Put text',\n    brush: 'Brush',\n    resize: 'Resize or scale',\n    open: 'Open image',\n    select: 'Select area',\n    close: 'Close Painterro',\n    eraser: 'Eraser',\n    settings: 'Settings',\n    zoomin: 'Zoom In',\n    zoomout: 'Zoom Out',\n    undo: 'Undo',\n    redo: 'Redo',\n    clear: 'Clear',\n    bucket: 'Fill',\n  },\n  pasteOptions: {\n    fit: 'Replace all',\n    extend_down: 'Extend down',\n    extend_right: 'Extend right',\n    extend_left: 'Extend left',\n    extend_top: 'Extend top',\n    extend: 'Add',\n    over: 'Paste over',\n    how_to_paste: 'How to paste?',\n  },\n};\n"
  },
  {
    "path": "langs/es.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Color de linea',\n  fillColor: 'O',\n  fillColorFull: 'Color de relleno',\n  alpha: 'T',\n  alphaFull: 'Transparencia',\n  lineWidth: 'A',\n  lineWidthFull: 'Ancho de linea',\n  eraserWidth: 'B',\n  eraserWidthFull: 'Ancho de borrador',\n  textColor: 'C',\n  textColorFull: 'Color de texto',\n  fontSize: 'L',\n  fontSizeFull: 'Tamaño de letra',\n  fontStrokeSize: 'TLl',\n  fontStrokeSizeFull: 'Tamaño de linea de letra',\n  fontStyle: 'EL',\n  fontStyleFull: 'Estilo de letra',\n  fontName: 'NL',\n  fontNameFull: 'Nombre del tipo de letra',\n  textStrokeColor: 'CL',\n  textStrokeColorFull: 'Color de letra',\n  apply: 'Aplicar',\n  cancel: 'Cancelar',\n  close: 'Cerrar',\n  clear: 'Limpiar',\n  width: 'Ancho',\n  height: 'Alto',\n  keepRatio: 'Mantener ratio Ancho/Alto',\n  fillPageWith: 'Rellenar la página con el color de fondo actual',\n  pixelSize: 'P',\n  pixelSizeFull: 'Tamaño de pixel',\n  resizeScale: 'Escala',\n  resizeResize: 'Redimensionar',\n  backgroundColor: 'Color de fondo de la página',\n  pixelizePixelSize: 'Tamaño de pixel al pixelar',\n  wrongPixelSizeValue:\n    'Tamaño de pixel incorrecto. Puedes entrar por ejemplo \\'20%\\' que significa que el tamaño de pixel es 1/5 del area seleccionada, o \\'4\\' significa 4 px',\n  tools: {\n    crop: 'Recortar imagen a la area seleccionada',\n    pixelize: 'Pixelar la area seleccionada',\n    rect: 'Dibujar rectángulo',\n    ellipse: 'Dibujar eclipse',\n    line: 'Dibujar linea',\n    rotate: 'Rotar imagen',\n    save: 'Guardar imagen',\n    load: 'Cargar imagen',\n    text: 'Escribir texto',\n    brush: 'Pincel',\n    resize: 'Cambiar tamaño o escalar',\n    open: 'Abrir imagen',\n    select: 'Selección de area',\n    close: 'Cerrar editor',\n    eraser: 'Borrador',\n    settings: 'Parámetros',\n  },\n  pasteOptions: {\n    fit: 'Reemplazar todo',\n    extend_down: 'Extender hacia abajo',\n    extend_right: 'Extender a la derecha',\n    extend: 'Extender',\n    over: 'Pegar encima',\n    how_to_paste: 'Cómo se ha de pegar?',\n  },\n};\n"
  },
  {
    "path": "langs/fa.lang.js",
    "content": " export default { //Persian traslation file \n        name: 'fa',//Iran-Farsi\n        strings: {\n            lineColor: 'L',\n            lineColorFull: 'رنگ خظ',\n            fillColor: 'F',\n            fillColorFull: 'رنگ داخل',\n            alpha: 'A',\n            alphaFull: 'شفاف',\n            lineWidth: 'W',\n            lineWidthFull: 'عرض خط',\n            arrowLength: 'L',\n            arrowLengthFull: 'طول بردار',\n            eraserWidth: 'E',\n            eraserWidthFull: 'عرض پاککن',\n            textColor: 'C',\n            textColorFull: 'رنگ متن',\n            fontSize: 'S',\n            fontSizeFull: 'اندازه متن',\n            fontStrokeSize: 'St',\n            fontStrokeSizeFull: 'عرض حاشیه',\n            fontStyle: 'FS',\n            fontStyleFull: 'حالت فونت',\n            fontName: 'F',\n            fontNameFull: 'نام فونت',\n            textStrokeColor: 'SC',\n            textStrokeColorFull: 'رنگ حاشیه',\n            apply: 'اعمال',\n            cancel: 'انصراف',\n            close: 'بستن',\n            clear: 'پاک کردن',\n            width: 'عرض',\n            height: 'طول',\n            keepRatio: 'نگهداشتن نسبت طول/عرض',\n            fillPageWith: 'پرکردن صفحه با رنگ پس زمینه',\n            pixelSize: 'P',\n            pixelSizeFull: 'اندازه نقطه',\n            resizeScale: 'مقیاس',\n            resizeResize: 'تغییر اندازه',\n            backgroundColor: 'رنگ پس زمینه',\n            pixelizePixelSize: 'اندازه پیکس شطرنجی کردن' ,\n            language: 'زبان',\n            wrongPixelSizeValue:\n            'اندازه پیکسل اشتباه است. به طور مثال \\'20%\\' که به معنی اندازه 1/5 of ' +\n            'محدوده انتخاب شده است, یا \\'4\\' به معنی 4 px است.',\n            tools: {\n                crop: 'بریدن تصویر به محدوده انتخاب شده',\n                pixelize: 'پیکسی کردن محدوده انتخاب شده',\n                rect: 'ترسیم مستطیل',\n                ellipse: 'کشیدن نقطه چین',\n                line: 'ترسیم خط',\n                arrow: 'ترسیم بردار',\n                rotate: 'چرخاندن تصویر',\n                save: 'ذخیره تصویر',\n                load: 'بارگذازی تصویر',\n                text: 'گذاشتن متن',\n                brush: 'قلم مو',\n                resize: 'تغییر سایز یا مقیاس',\n                open: 'باز کردن تصویر',\n                select: 'انتخاب محدوده',\n                close: 'بستن محیط ترسیم',\n                eraser: 'پاک کردن',\n                settings: 'تنظیمات',\n                undo: 'باطل کردن',\n                redo: 'انجام دوباره',\n            },\n            pasteOptions: {\n                fit: 'جایگزین کردن همه',\n                extend_down: 'گسترش به پایین',\n                extend_right: 'گسترش به راست',\n                extend: 'گسترش',\n                over: 'چسباندن روی',\n                how_to_paste: 'چگونه چسبانده؟',\n            },\n        }\n    }\n"
  },
  {
    "path": "langs/fr.lang.js",
    "content": "export default {\n  lineColor: 'CL',\n  lineColorFull: 'Couleur de la ligne',\n  fillColor: 'CR',\n  fillColorFull: 'Couleur de Remplissage',\n  alpha: 'T',\n  alphaFull: 'Transparence',\n  lineWidth: 'L',\n  lineWidthFull: 'Largeur de ligne',\n  arrowLength: 'F',\n  arrowLengthFull: 'Longeur de la flèche',\n  eraserWidth: 'LG',\n  eraserWidthFull: 'Largeur de la gomme',\n  textColor: 'CT',\n  textColorFull: 'Couleur du texte',\n  fontSize: 'TP',\n  fontSizeFull: 'Taille de Police',\n  fontStrokeSize: 'LT',\n  fontStrokeSizeFull: 'Largeur du trait',\n  fontStyle: 'SP',\n  fontStyleFull: 'Style de police',\n  fontName: 'NP',\n  fontNameFull: 'Nom de la police',\n  textStrokeColor: 'CT',\n  textStrokeColorFull: 'Couleur du trait',\n  apply: 'Appliquer',\n  cancel: 'Annuler',\n  close: 'Fermer',\n  clear: 'Effacer',\n  width: 'Largeur',\n  height: 'Hauteur',\n  keepRatio: 'Conserver le rapport largeur/hauteur',\n  fillPageWith: 'Remplir avec la couleur d\\'arrière-plan courante',\n  pixelSize: 'P',\n  pixelSizeFull: 'Taille de Pixel',\n  resizeScale: 'Échelle',\n  resizeResize: 'Redimensionner',\n  backgroundColor: 'Couleur de fond de la page',\n  pixelizePixelSize: 'Pixéliser la taille de pixel',\n  language: 'Langue',\n  wrongPixelSizeValue:\n    'Mauvaise taille de pixel. Vous pouvez ajouter par exemple e.g. \\'20%\\' ce qui signifie que la taille moyenne des pixels sera 1/5 de la ' +\n    'surface sélectionnée, ou \\'4\\' signifie 4 px',\n  tools: {\n    crop: 'Recadrer l\\'image dans la zone sélectionnée',\n    pixelize: 'Pixéliser la zone sélectionnée',\n    rect: 'Dessiner un rectangle',\n    ellipse: 'Dessiner une ellipse',\n    line: 'Dessiner une ligne',\n    arrow: 'Dessiner une flèche',\n    rotate: 'Pivoter l\\'image',\n    save: 'Enregistrer l\\'image',\n    load: 'Charger l\\'image',\n    text: 'Mettre du texte',\n    brush: 'Brosse',\n    resize: 'Redimensionner ou échelle',\n    open: 'Ouvrir l\\'image',\n    select: 'Sélectionnez une région',\n    close: 'Fermer Painterro',\n    eraser: 'Gomme',\n    settings: 'Réglages',\n    undo: 'Annuler',\n    redo: 'Refaire',\n  },\n  pasteOptions: {\n    fit: 'Tout Remplacer',\n    extend_down: 'Étendre vers le bas',\n    extend_right: 'Étendre à droite',\n    extend: 'Étendre',\n    over: 'Coller sur',\n    how_to_paste: 'Comment coller?',\n  },\n};\n"
  },
  {
    "path": "langs/ja.lang.js",
    "content": "export default {\n  lineColor: '線',\n  lineColorFull: '線の色',\n  fillColor: '塗',\n  fillColorFull: '塗りつぶしの色',\n  alpha: 'A',\n  alphaFull: 'アルファ',\n  lineWidth: '幅',\n  lineWidthFull: '線の幅',\n  arrowLength: '矢',\n  arrowLengthFull: '矢印の長さ',\n  eraserWidth: '幅',\n  eraserWidthFull: '消しゴムの幅',\n  textColor: '色',\n  textColorFull: 'テキストの色',\n  fontSize: '級',\n  fontSizeFull: 'フォントサイズ',\n  fontStrokeSize: '幅',\n  fontStrokeSizeFull: 'ストロークの幅',\n  fontStyle: '書式',\n  fontStyleFull: 'フォントスタイル',\n  fontName: '書体',\n  fontNameFull: 'フォント',\n  textStrokeColor: 'SC',\n  textStrokeColorFull: 'ストロークの色',\n  apply: '適用',\n  cancel: 'キャンセル',\n  close: '閉じる',\n  clear: 'クリア',\n  width: '幅',\n  height: '高さ',\n  keepRatio: '幅／高さのアスペクト比を保つ',\n  fillPageWith: '背景色でページを塗りつぶす',\n  pixelSize: 'モザ',\n  pixelSizeFull: 'モザイクサイズ',\n  resizeScale: 'スケール',\n  resizeResize: 'リサイズ',\n  backgroundColor: 'ページの背景色',\n  pixelizePixelSize: 'モザイクのサイズ',\n  language: '言語',\n  wrongPixelSizeValue:\n    'モザイクのサイズが正しくありません。' +\n    '次のように指定していください：\\n' +\n    ' \"20%\"（モザイクサイズは選択範囲の1/5になります）\\n' +\n    ' \"4\"（4pxを意味します）\\n',\n  tools: {\n    crop: '切り抜き',\n    pixelize: 'モザイク',\n    rect: '四角形を描く',\n    ellipse: '円を描く',\n    line: '線を描く',\n    arrow: '矢印を描く',\n    rotate: '画像を回転',\n    save: '画像を保存',\n    load: '画像を読み込み',\n    text: 'テキストを配置',\n    brush: 'ブラシ',\n    resize: 'リサイズまたはスケール',\n    open: '画像を開く',\n    select: '範囲選択',\n    close: 'Painterroを閉じる',\n    eraser: '消しゴム',\n    settings: '設定',\n    undo: '元に戻す',\n    redo: 'やり直す',\n  },\n  pasteOptions: {\n    fit: 'すべてを置き換え',\n    extend_down: '下に拡張',\n    extend_right: '右に拡張',\n    extend: '拡張',\n    over: '重ねて貼り付け',\n    how_to_paste: 'どう貼り付ける？',\n  },\n};\n"
  },
  {
    "path": "langs/nl.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Lijnkleur',\n  fillColor: 'V',\n  fillColorFull: 'Vulkleur',\n  alpha: 'A',\n  alphaFull: 'Alpha',\n  lineWidth: 'D',\n  lineWidthFull: 'Lijndikte',\n  arrowLength: 'L',\n  arrowLengthFull: 'Pijllengte',\n  eraserWidth: 'G',\n  eraserWidthFull: 'Gumdikte',\n  textColor: 'K',\n  textColorFull: 'Tekstkleur',\n  fontSize: 'G',\n  fontSizeFull: 'Tekstgrootte',\n  fontStrokeSize: 'St',\n  fontStrokeSizeFull: 'Streepdikte',\n  fontIsBold: '<b>V</b>',\n  fontIsBoldFull: 'Vetgedrukt',\n  fontIsItalic: '<i>C</i>',\n  fontIsItalicFull: 'Cursief',\n  shadowOn: 'S',\n  shadowOnFull: 'Schaduw',\n  fontStrokeAndShadow: 'D&S',\n  fontStrokeAndShadowFull: 'Streep & Schaduw',\n\n  fontName: 'F',\n  fontNameFull: 'Naam lettertype',\n  textStrokeColor: 'SK',\n  textStrokeColorFull: 'Streepkleur',\n  apply: 'Toepassen',\n  cancel: 'Annuleren',\n  close: 'Sluiten',\n  clear: 'Opnieuw',\n  width: 'Breedte',\n  height: 'Hoogte',\n  keepRatio: 'Behoud breedte-/hoogteverhouding',\n  fillPageWith: 'Vul pagina met de huidige achtergrondkleur',\n  pixelSize: 'P',\n  pixelSizeFull: 'Pixelgrootte',\n  resizeScale: 'Schalen',\n  resizeResize: 'Grootte aanpassen',\n  backgroundColor: 'Achtergrondkleur pagina',\n  pixelizePixelSize: 'Pixelgrootte pixel',\n  language: 'Taal',\n  wrongPixelSizeValue:\n    'Foute pixelgrootte. Je kunt b.v. \\'20%\\' invullen, wat een pixelgrootte van ' +\n    '1/5 van de geselecteerde gebiedszijde betekent, of \\'4\\' voor een pixelgrootte van 4 px',\n  tools: {\n    crop: 'Afbeelding bijsnijden naar geselecteerde gebied',\n    pixelize: 'Geselecteerde gebied pixelen',\n    rect: 'Teken rechthoek',\n    ellipse: 'Teken ovaal',\n    line: 'Teken lijn',\n    arrow: 'Teken pijl',\n    rotate: 'Afbeelding draaien',\n    save: 'Afbeelding opslaan',\n    load: 'Afbeelding laden',\n    text: 'Text schrijven',\n    brush: 'Penseel',\n    resize: 'Grootte aanpassen',\n    open: 'Afbeelding openen',\n    select: 'Gebied selecteren',\n    close: 'Painterro sluiten',\n    eraser: 'Gum',\n    settings: 'Instellingen',\n    zoomin: 'Inzoomen',\n    zoomout: 'Uitzoomen',\n    undo: 'Ongedaan maken',\n    redo: 'Opnieuw doen',\n  },\n  pasteOptions: {\n    fit: 'Vervang alles',\n    extend_down: 'Onder uitbreiden',\n    extend_right: 'Rechts uitbreiden',\n    extend_left: 'Links uitbreiden',\n    extend_top: 'Boven uitbreiden',\n    extend: 'Uitbreiden',\n    over: 'Overheen plakken',\n    how_to_paste: 'Hoe te plakken?',\n  },\n};\n"
  },
  {
    "path": "langs/pl.lang.js",
    "content": "export default {\n  lineColor: 'K',\n  lineColorFull: 'Kolor linii',\n  fillColor: 'W',\n  fillColorFull: 'Kolor wypełnienia',\n  alpha: 'A',\n  alphaFull: 'Alpha',\n  lineWidth: 'L',\n  lineWidthFull: 'Szerokość linii',\n  arrowLength: 'S',\n  arrowLengthFull: 'Rozmiar strzałki',\n  eraserWidth: 'G',\n  eraserWidthFull: 'Wielkość gumki',\n  textColor: 'K',\n  textColorFull: 'Kolor tekstu',\n  fontSize: 'R',\n  fontSizeFull: 'Rozmiar czcionki',\n  fontStrokeSize: 'Sk',\n  fontStrokeSizeFull: 'Szerokość kreski',\n  fontStyle: 'Sc',\n  fontStyleFull: 'Styl czcionki',\n  fontName: 'C',\n  fontNameFull: 'Czcionka',\n  textStrokeColor: 'Kk',\n  textStrokeColorFull: 'Kolor kreski',\n  apply: 'Zastosuj',\n  cancel: 'Anuluj',\n  close: 'Zamknij',\n  clear: 'Wyczyść',\n  width: 'Szerokość',\n  height: 'Wysokość',\n  keepRatio: 'Zachowaj współczynnik proporcji',\n  fillPageWith: 'Wypełnij stronę obecnym kolorem tła',\n  pixelSize: 'P',\n  pixelSizeFull: 'Rozmiar pixela',\n  resizeScale: 'Skala',\n  resizeResize: 'Zmień rozmiar',\n  backgroundColor: 'Kolor tła strony',\n  pixelizePixelSize: 'Rozmiar pixela Pixelizera',\n  language: 'Język',\n  wrongPixelSizeValue:\n    'Zły rozmiar pixela. Poprawne wartości to np. \\'20%\\', co oznacza przyjęcie rozmiaru pixela jako 20% ' +\n    'pola wybranego obszaru, lub \\'4\\' jako 4 px',\n  tools: {\n    crop: 'Przytnij obraz do wybranego obszaru',\n    pixelize: 'Pixelizuj wybrany obszar',\n    rect: 'Rysuj prostokąt',\n    ellipse: 'Rysuj elipse',\n    line: 'Rysuj linię',\n    arrow: 'Rysuj strzałkę',\n    rotate: 'Obróć obraz',\n    save: 'Zapisz obraz',\n    load: 'Wczytaj obraz',\n    text: 'Dodaj tekst',\n    brush: 'Pędzel',\n    resize: 'Zmień rozmiar lub skalę',\n    open: 'Otwórz obraz',\n    select: 'Wybierz obszar',\n    close: 'Zamknij Painterro',\n    eraser: 'Gumka',\n    settings: 'Ustawienia',\n    undo: 'Cofnij',\n    redo: 'Przywróć',\n  },\n  pasteOptions: {\n    fit: 'Zastąp',\n    extend_down: 'Dodaj poniżej',\n    extend_right: 'Dodaj po prawej',\n    extend: 'Dodaj',\n    over: 'Dodaj ponad',\n    how_to_paste: 'Jak wkleić?',\n  },\n};\n"
  },
  {
    "path": "langs/pt-BR.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Cor da linha',\n  fillColor: 'O',\n  fillColorFull: 'Cor do preenchimento',\n  alpha: 'T',\n  alphaFull: 'Transparência',\n  lineWidth: 'A',\n  lineWidthFull: 'Largura de linha',\n  eraserWidth: 'B',\n  eraserWidthFull: 'Largura do apagador',\n  textColor: 'C',\n  textColorFull: 'Cor do texto',\n  fontSize: 'L',\n  fontSizeFull: 'Tamanho da letra',\n  fontStrokeSize: 'TLl',\n  fontStrokeSizeFull: 'Tamanho da linha da letra',\n  fontStyle: 'EL',\n  fontStyleFull: 'Estilo de letra',\n  fontName: 'NL',\n  fontNameFull: 'Nome do tipo de letra',\n  textStrokeColor: 'CL',\n  textStrokeColorFull: 'Cor da letra',\n  apply: 'Aplicar',\n  cancel: 'Cancelar',\n  close: 'Fechar',\n  clear: 'Limpar',\n  width: 'Largura',\n  height: 'Altura',\n  keepRatio: 'Manter rácio Largura/Altura',\n  fillPageWith: 'Preencher a página com a cor de fundo atual',\n  pixelSize: 'P',\n  pixelSizeFull: 'Tamanho de píxel',\n  resizeScale: 'Escala',\n  resizeResize: 'Redimensionar',\n  backgroundColor: 'Cor de fundo da página',\n  pixelizePixelSize: 'Tamanho de píxel ao Pixelizar',\n  wrongPixelSizeValue:\n    'Tamanho de píxel incorreto. Pode colocar por exemplo \\'20%\\' que significa que o tamanho de píxel é 1/5 da área selecionada, o \\'4\\' significa 4 px',\n  tools: {\n    crop: 'Recortar imagem pela área selecionada',\n    pixelize: 'Pixelizar a área selecionada',\n    rect: 'Desenhar retângulo',\n    ellipse: 'Desenhar elipse',\n    line: 'Desenhar linha',\n    rotate: 'Girar imagem',\n    save: 'Salvar imagem',\n    load: 'Carregar imagem',\n    text: 'Escrever texto',\n    brush: 'Pincel',\n    resize: 'Alterar tamanho ao redimensionar',\n    open: 'Abrir imagem',\n    select: 'Seleção da área',\n    close: 'Fechar editor',\n    eraser: 'Apagador',\n    settings: 'Configurações',\n  },\n  pasteOptions: {\n    fit: 'Substituir tudo',\n    extend_down: 'Acrescentar por baixo',\n    extend_right: 'Acrescentar pela direita',\n    extend: 'Acrescentar',\n    over: 'Por cima',\n    how_to_paste: 'Como colar?',\n  },\n};\n"
  },
  {
    "path": "langs/pt-PT.lang.js",
    "content": "export default {\n  lineColor: 'L',\n  lineColorFull: 'Cor da linha',\n  fillColor: 'O',\n  fillColorFull: 'Cor do preenchimento',\n  alpha: 'T',\n  alphaFull: 'Transparência',\n  lineWidth: 'A',\n  lineWidthFull: 'Largura de linha',\n  eraserWidth: 'B',\n  eraserWidthFull: 'Largura do apagador',\n  textColor: 'C',\n  textColorFull: 'Cor do texto',\n  fontSize: 'L',\n  fontSizeFull: 'Tamanho da letra',\n  fontStrokeSize: 'TLl',\n  fontStrokeSizeFull: 'Tamanho da linha da letra',\n  fontStyle: 'EL',\n  fontStyleFull: 'Tipo de letra',\n  fontName: 'NL',\n  fontNameFull: 'Nome do tipo de letra',\n  textStrokeColor: 'CL',\n  textStrokeColorFull: 'Cor da letra',\n  apply: 'Aplicar',\n  cancel: 'Cancelar',\n  close: 'Fechar',\n  clear: 'Limpar',\n  width: 'Largura',\n  height: 'Altura',\n  keepRatio: 'Manter rácio Largura/Altura',\n  fillPageWith: 'Preencher a página com a cor de fundo actual',\n  pixelSize: 'P',\n  pixelSizeFull: 'Tamanho de píxel',\n  resizeScale: 'Escala',\n  resizeResize: 'Redimensionar',\n  backgroundColor: 'Cor de fundo da página',\n  pixelizePixelSize: 'Tamanho de píxel ao Pixelizar',\n  wrongPixelSizeValue:\n    'Tamanho de píxel incorrecto. Podes colocar por exemplo \\'20%\\' que significa que o tamanho de píxel é 1/5 da área seleccionada, o \\'4\\' significa 4 px',\n  tools: {\n    crop: 'Recortar imagem pela área seleccionada',\n    pixelize: 'Pixelizar a área seleccionada',\n    rect: 'Desenhar rectângulo',\n    ellipse: 'Desenhar elipse',\n    line: 'Desenhar linha',\n    rotate: 'Rodar imagem',\n    save: 'Guardar imagem',\n    load: 'Carregar imagem',\n    text: 'Escrever texto',\n    brush: 'Pincel',\n    resize: 'Alterar tamanho ao redimensionar',\n    open: 'Abrir imagem',\n    select: 'Selecção da área',\n    close: 'Fechar editor',\n    eraser: 'Apagador',\n    settings: 'Definições',\n  },\n  pasteOptions: {\n    fit: 'Substituir tudo',\n    extend_down: 'Acrescentar por baixo',\n    extend_right: 'Acrescentar pela direita',\n    extend: 'Acrescentar',\n    over: 'Por cima',\n    how_to_paste: 'Como colar?',\n  },\n};\n"
  },
  {
    "path": "langs/ru.lang.js",
    "content": "export default {\n  lineColor: 'Л',\n  lineColorFull: 'Цвет линии',\n  fillColor: 'З',\n  fillColorFull: 'Цвет заливки',\n  alpha: 'П',\n  alphaFull: 'Прозрачность',\n  lineWidth: 'Ш',\n  lineWidthFull: 'Ширина линии',\n  arrowLength: 'Ш',\n  arrowLengthFull: 'Ширина стрелки',\n  eraserWidth: 'Ш',\n  eraserWidthFull: 'Ширина ластика',\n  textColor: 'Ц',\n  textColorFull: 'Цвет текста',\n  fontSize: 'Р',\n  fontSizeFull: 'Размер шрифта',\n  fontStrokeSize: 'Ш',\n  fontStrokeSizeFull: 'Ширина штриха',\n  fontIsBold: '<b>Ж</b>',\n  fontIsBoldFull: 'Жирный',\n  fontIsItalic: '<i>К</i>',\n  fontIsItalicFull: 'Курсив',\n  shadowOn: 'Т',\n  shadowOnFull: 'Тень',\n  fontStrokeAndShadow: 'О&Т',\n  fontStrokeAndShadowFull: 'Обводка и Тень',\n  fontName: 'Ш',\n  fontNameFull: 'Название шрифта',\n  textStrokeColor: 'ЦО',\n  textStrokeColorFull: 'Цвет обводки',\n  apply: 'Применить',\n  cancel: 'Отмена',\n  close: 'Закрыть',\n  clear: 'Очистить',\n  width: 'Ширина',\n  height: 'Высота',\n  keepRatio: 'Сохранить соотношение ширины/высоты',\n  fillPageWith: 'Заполнить страницу текущим цветом фона',\n  pixelSize: 'П',\n  pixelSizeFull: 'Размер пикселя',\n  resizeScale: 'Масштаб',\n  resizeResize: 'Изменить размер',\n  backgroundColor: 'Цвет фона страницы',\n  pixelizePixelSize: 'Пикселизация размера пикселя',\n  language: 'Язык',\n  wrongPixelSizeValue:\n    'Неправильный размер пикселя. Вы можете ввести, например \\'20%\\', что означает, что средний размер пикселя будет 1/5 от' +\n    'стороны выбранной области или \\'4\\', что означает 4 пикселя',\n  tools: {\n    crop: 'Обрезать изображение до выделенной области',\n    pixelize: 'Сделать пикселизацию выделенной области',\n    rect: 'Рисовать прямоугольник',\n    ellipse: 'Рисовать эллипс',\n    line: 'Рисовать линию',\n    arrow: 'Рисовать Стрелку',\n    rotate: 'Повернуть лист',\n    save: 'Сохрнить изображение',\n    load: 'Загрузть изображение',\n    text: 'Вставить текст',\n    brush: 'Кисть',\n    resize: 'Изменить размер или масштаб',\n    open: 'Открыть изображение',\n    select: 'Выбрать область',\n    close: 'Закрыть редактор',\n    eraser: 'Ластик',\n    settings: 'Настройки',\n    undo: 'Отменить',\n    redo: 'Повторить',\n  },\n  pasteOptions: {\n    fit: 'Заменить всё',\n    extend_down: 'Добавить вниз',\n    extend_right: 'Добавить вправо',\n    extend_left: 'Добавить влево',\n    extend_top: 'Добавить вверх',\n    extend: 'Добавить',\n    over: 'Вставить',\n    how_to_paste: 'Как вставить?',\n  },\n};\n"
  },
  {
    "path": "langs/uk.lang.js",
    "content": "export default {\n  lineColor: \"КЛ\",\n  lineColorFull: \"Колір лінії\",\n  fillColor: \"КЗ\",\n  fillColorFull: \"Колір заливки\",\n  alpha: \"Пр\",\n  alphaFull: \"Прозорість\",\n  lineWidth: \"ТЛ\",\n  lineWidthFull: \"Товщина лінії\",\n  arrowLength: \"ДЛ\",\n  arrowLengthFull: \"Довжина стрілки\",\n  eraserWidth: \"ШГ\",\n  eraserWidthFull: \"Ширина гумки\",\n  textColor: \"КТ\",\n  textColorFull: \"Колір тексту\",\n  fontSize: \"РШ\",\n  fontSizeFull: \"Розмір шрифту\",\n  fontStrokeSize: \"ТШ\",\n  fontStrokeSizeFull: \"Ширина шрифту\",\n  fontIsBold: \"<b>B</b>\",\n  fontIsBoldFull: \"Жирний\",\n  fontIsItalic: \"<i>I</i>\",\n  fontIsItalicFull: \"Курсив\",\n  shadowOn: \"Т\",\n  shadowOnFull: \"Тінь\",\n  filtersFull: \"Фільтри\", \n  fontStrokeAndShadow: \"Т+О\",\n  fontStrokeAndShadowFull: \"Тінь та обводка\",\n  filters: 'Фільтр',\n  imageFilters: 'Фільтри зображення',\n  percents: \"%\",\n  percentsFull: \"Проценти\",\n\n\n\n\n\n  fontName: \"НШ\",\n  fontNameFull: \"Назва шрифту\",\n  textStrokeColor: \"КО\",\n  textStrokeColorFull: \"Колір обводки\",\n  apply: \"Застосувати\",\n  cancel: \"Відміна\",\n  close: \"Закрити\",\n  clear: \"Очистити\",\n  width: \"Ширина\",\n  height: \"Висота\",\n  keepRatio: \"Зберегти пропорції ширини/висоти\",\n  fillPageWith: \"Залити сторінку поточним кольором фону\",\n  pixelSize: \"РП\",\n  pixelSizeFull: \"Розмір пікселя\",\n  resizeScale: \"Масштаб\",\n  resizeResize: \"Змінити розмір\",\n  backgroundColor: \"Колір фону сторінки\",\n  pixelizePixelSize: \"Розмити пікселі розміром\",\n  language: \"Мова\",\n  wrongPixelSizeValue:\n    \"Неправильний розмір пікселя. Ви можете ввести, наприклад, '20%', що означає, що середній розмір пікселя буде 1/5 від \" +\n    \"сторони вибраної області, або '4', що означає 4 пікселя\",\n  tools: {\n    crop: \"Обрізати зображення до виділеної області\",\n    pixelize: \"Зробити пікселізацію виділеної області\",\n    rect: \"Намалювати прямокутник\",\n    ellipse: \"Намалювати еліпс\",\n    line: \"Намалювати лінію\",\n    arrow: \"Намалювати стрілку\",\n    rotate: \"Повернути \",\n    save: \"Зберегти \",\n    load: \"Завантажити \",\n    text: \"Вставити текст\",\n    brush: \"Пензель\",\n    resize: \"Змінити розмір або масштаб\",\n    open: \"Відкрити зображення\",\n    select: \"Вибрати область\",\n    close: \"Закрити\",\n    eraser: \"Ластик\",\n    settings: \"Налаштування\",\n    zoomin: \"Наблизити\",\n    zoomout: \"Віддалити\",\n    undo: \"Скасувати\",\n    redo: \"Повторити\",\n    clear: \"Очистити\",\n    bucket: \"Заливка\",\n    filters: \"Фільтри\",\n  },\n  pasteOptions: {\n    fit: \"Замінити все\",\n    extend_down: \"Додати вниз\",\n    extend_right: \"Додати вправо\",\n    extend_left: \"Додати вліво\",\n    extend_top: \"Додати вгору\",\n    extend: \"Додати\",\n    over: \"Вставити\",\n    how_to_paste: \"Як вставити?\",\n  },\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"painterro\",\n  \"version\": \"1.2.92\",\n  \"description\": \"HTML5 image editing widget (js paint plugin)\",\n  \"main\": \"build/painterro.commonjs2.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/devforth/painterro\"\n  },\n  \"scripts\": {\n    \"prepare\": \"npm run build\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"build\": \"webpack --progress\",\n    \"dev\": \"webpack serve --open --host 127.0.0.1 --port 3000\",\n    \"watch\": \"webpack --watch\",\n    \"buildfont\": \"node generate_font.js\"\n  },\n  \"keywords\": [\n    \"paint\",\n    \"paint plugin\",\n    \"image editor\",\n    \"paint widget\",\n    \"screenshots edit\",\n    \"js paint\",\n    \"canvas paint\",\n    \"image\",\n    \"js paint plugin\",\n    \"html5 canvas\"\n  ],\n  \"homepage\": \"https://hinty.io/devforth/js-paint-plugin-for-your-website/\",\n  \"bugs\": {\n    \"url\": \"https://github.com/devforth/painterro/issues\",\n    \"email\": \"contact@devforth.io\"\n  },\n  \"author\": \"Ivan Borshchov\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.12.16\",\n    \"@babel/preset-env\": \"^7.12.16\",\n    \"@vusion/webfonts-generator\": \"^0.8.0\",\n    \"babel-loader\": \"^8.2.2\",\n    \"css-loader\": \"^5.0.2\",\n    \"es6-promise\": \"^4.2.8\",\n    \"eslint\": \"^6.8.0\",\n    \"eslint-config-airbnb-base\": \"^11.2.0\",\n    \"eslint-loader\": \"^4.0.2\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"ismobilejs\": \"^1.1.1\",\n    \"lodash\": \">=4.17.13\",\n    \"mime\": \">=1.4.1\",\n    \"raw-loader\": \"^0.5.1\",\n    \"string.prototype.repeat\": \"^0.2.0\",\n    \"style-loader\": \"^2.0.0\",\n    \"terser-webpack-plugin\": \"^5.1.4\",\n    \"url-loader\": \"^4.1.1\",\n    \"webpack\": \"^5.21.2\",\n    \"webpack-bundle-analyzer\": \"^4.4.0\",\n    \"webpack-cli\": \"^4.5.0\",\n    \"webpack-dev-server\": \"^4.0.0-beta.0\"\n  },\n  \"dependencies\": {\n    \"caniuse-lite\": \"^1.0.30001596\",\n    \"core-js\": \"^3.11.0\",\n    \"dom-to-image\": \"^2.6.0\"\n  }\n}\n"
  },
  {
    "path": "publish.sh",
    "content": "#!/usr/bin/env bash\n\n# Auto-release script for https://github.com/devforth/painterro\n# Creates new release, builds assets and performs publishing to github and npm\n\nGH_USER=ivictbor\nGH_REPO_USER=devforth\nGH_PASS=`cat ~/keys/gh_ptro_release_token`\nWP_PASSWORD=`cat ~/keys/.wppassword`\nGH_REPO=painterro\nGH_TARGET=master\nASSETS_PATH=build\nnpm --no-git-tag-version version patch\nVERSION=`grep '\"version\":' package.json | cut -d\\\" -f4`\nnpm run build\n\nif [ $? -eq 0 ]; then\n    echo BUILD OK\nelse\n    echo FAIL\n    exit 0\nfi\n\nrm -rf wp\nsvn co http://plugins.svn.wordpress.org/painterro/ wp\n\nrm -f wp/trunk/painterro-*.min.js\nrm -f wp/trunk/painterro-*.min.js.map\ncp build/painterro-${VERSION}.min.js wp/trunk/\ncp build/painterro-${VERSION}.min.js.map wp/trunk/\nsed -i -E \"s/([ \\t]+version: \\\")[0-9\\\\.]+(\\\",)/\\1${VERSION}\\2/\" wp/trunk/index.js\nsed -i -E \"s/(Version: )[0-9\\\\.]+/\\1${VERSION}/\" wp/trunk/painterro-wp.php\nsed -i -E \"s/(define\\\\(\\\"PAINTERRO_FILE\\\", \\\"painterro-)[0-9\\\\.]+(\\\\.min\\\\.js\\\"\\\\);)/\\1${VERSION}\\2/\" wp/trunk/painterro-wp.php\ncd wp\nsvn add --force .\nsvn st | grep ^! | awk '{print \" --force \"$2}' | xargs svn rm\nsvn --username=vanbrosh --password=\"${WP_PASSWORD}\" ci -m \"${VERSION}\"\ncd ..\n\ngit add -u\ngit commit -m \"$VERSION\"\ngit push\nnpm publish\n\necho \"GH USER AND PATH\",$GH_USER, $GH_PASS\n\nres=`curl --user \"$GH_USER:$GH_PASS\" -X POST https://api.github.com/repos/${GH_REPO_USER}/${GH_REPO}/releases \\\n-d \"\n{\n  \\\"tag_name\\\": \\\"v$VERSION\\\",\n  \\\"target_commitish\\\": \\\"$GH_TARGET\\\",\n  \\\"name\\\": \\\"v$VERSION\\\",\n  \\\"body\\\": \\\"new version $VERSION\\\",\n  \\\"draft\\\": false,\n  \\\"prerelease\\\": false\n}\"`\necho Create release result: ${res}\nrel_id=`echo ${res} | python -c 'import json,sys;print(json.load(sys.stdin)[\"id\"])'`\nfile_name=painterro-${VERSION}.min.js\n\necho \"Release id\", $rel_id\n\ncurl --user \"$GH_USER:$GH_PASS\" -X POST https://uploads.github.com/repos/${GH_REPO_USER}/${GH_REPO}/releases/${rel_id}/assets?name=${file_name}\\\n --header 'Content-Type: text/javascript ' --upload-file ${ASSETS_PATH}/${file_name}\n\nfile_map_name=painterro-${VERSION}.min.js.map\ncurl --user \"$GH_USER:$GH_PASS\" -X POST https://uploads.github.com/repos/${GH_REPO_USER}/${GH_REPO}/releases/${rel_id}/assets?name=${file_map_name}\\\n --header 'Content-Type: text/javascript ' --upload-file ${ASSETS_PATH}/${file_map_name}\n\nrm ${ASSETS_PATH}/${file_name}\nrm ${ASSETS_PATH}/${file_map_name}\n"
  },
  {
    "path": "res/font/font-css.hbs",
    "content": "@font-face {\n\tfont-family: \"{{fontName}}\";\n\tsrc: {{{src}}};\n\tfont-weight: normal;\n    font-style: normal;\n}\n\n{{baseSelector}} {\n}\n\n{{baseSelector}}:before {\n\tfont-family: {{fontName}} !important;\n\tfont-style: normal !important;\n\tfont-weight: normal !important;\n\tfont-variant: normal !important;\n\ttext-transform: none !important;\n\tspeak: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n{{#each codepoints}}\n.{{../classPrefix}}{{@key}}:before {\n\tcontent: \"\\\\{{this}}\";\n}\n{{/each}}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "'use strict';\nconst path = require('path');\nconst webpack = require('webpack');\nconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;\nconst TerserPlugin = require(\"terser-webpack-plugin\");\n\n\nfunction webpackConfig(target, mode) {\n  let filename;\n  if (target === 'var') {\n    filename = `painterro-${require('./package.json').version}.min.js`\n  } else if (target === 'var-latest') {\n    filename = 'painterro.min.js';\n    target = 'var';\n  } else {\n    filename = `painterro.${target}.js`;\n  }\n\n  let options = {\n    mode,\n    entry: ['./js/main.js'],\n    output: {\n      path: path.resolve(__dirname, 'build'),\n      filename,\n      libraryTarget: target,\n    },\n    module: {\n      rules: [\n        {\n          enforce: \"pre\",\n          test: /\\.js$/,\n          exclude: /node_modules/,\n          loader: \"eslint-loader\",\n        },\n        {\n          test: /\\.css$/,\n          use: [\n            { loader: \"style-loader\" },\n            { loader: \"css-loader\" }\n          ]\n        },\n        {\n          test: /\\.(ttf|woff|woff2|eot|svg)(\\?v=[0-9]\\.[0-9]\\.[0-9])?$/,\n          loader: \"url-loader\"\n        }\n      ]\n    },\n    stats: {\n      colors: true\n    },\n    devtool: 'source-map',\n    optimization: {\n      minimize: true,\n      minimizer: [new TerserPlugin()],\n    },\n  }\n  if (target === 'var') {\n    options.output.library = 'Painterro';\n    options.output.libraryExport = 'default';\n    options.target = 'browserslist';\n    options.module.rules.push({\n      test: /\\.js$/,\n      loader: 'babel-loader',\n      exclude: /node_modules/,\n      options: {\n        // sourceType: \"module\",\n        presets: [[\"@babel/preset-env\", {\n          \"modules\": \"commonjs\",\n          \"useBuiltIns\": \"entry\",\n          \"corejs\": \"2\"\n        }]],\n      }\n    });\n  } else {\n    options.target = 'es2020';\n  }\n  if (mode === 'development') {\n    options = {\n      ...options,\n      devServer: {\n        injectClient: false,\n        static: path.join(__dirname, 'build'),\n        hot: true,\n      },\n      devtool: 'inline-source-map',\n      plugins: [\n        new BundleAnalyzerPlugin({\n          analyzerMode: 'static',\n          reportFilename: `report-${target}.html`,\n          openAnalyzer: false,\n        }),\n      ],\n    }\n  }\n  return options;\n}\n\nconst isDevServer = process.argv.find(v => v.includes('serve'));\n\nif (!isDevServer) {\n  console.log('Building production');\n  module.exports = [\n    webpackConfig('var', 'production'),\n    webpackConfig('var-latest', 'production'),\n    webpackConfig('commonjs2', 'production'),\n    webpackConfig('amd', 'production'),\n    webpackConfig('umd', 'production')\n  ];\n} else {\n  console.log('Building development');\n  module.exports = [webpackConfig('var-latest', 'development')];\n}"
  }
]