diff --git a/core/vendor/filemanager/css/jplayer.blue.monday.min.css b/core/vendor/filemanager/css/jplayer.blue.monday.min.css new file mode 100644 index 00000000..7c3bb29d --- /dev/null +++ b/core/vendor/filemanager/css/jplayer.blue.monday.min.css @@ -0,0 +1 @@ +div.jp-audio,div.jp-audio-stream,div.jp-video{font-size:1.25em;font-family:Verdana,Arial,sans-serif;line-height:1.6;color:#666;border:1px solid #009be3;background-color:#eee}div.jp-audio{width:420px}div.jp-audio-stream{width:182px}div.jp-video-270p{width:480px}div.jp-video-360p{width:640px}div.jp-video-full{width:480px;height:270px;position:static!important;position:relative}div.jp-video-full div div{z-index:1000}div.jp-video-full div.jp-jplayer{top:0;left:0;position:fixed!important;position:relative;overflow:hidden}div.jp-video-full div.jp-gui{position:fixed!important;position:static;top:0;left:0;width:100%;height:100%;z-index:1001}div.jp-video-full div.jp-interface{position:absolute!important;position:relative;bottom:0;left:0}div.jp-interface{position:relative;background-color:#eee;width:100%}div.jp-audio div.jp-type-playlist div.jp-interface,div.jp-audio div.jp-type-single div.jp-interface,div.jp-audio-stream div.jp-type-single div.jp-interface{height:80px}div.jp-video div.jp-interface{border-top:1px solid #009be3}div.jp-controls-holder{clear:both;width:440px;margin:0 auto;position:relative;overflow:hidden;top:-8px}div.jp-interface ul.jp-controls{list-style-type:none;margin:0;padding:0;overflow:hidden}div.jp-audio ul.jp-controls{width:380px;padding:20px 20px 0}div.jp-audio-stream ul.jp-controls{width:142px;padding:20px 20px 0}div.jp-video div.jp-type-single ul.jp-controls{width:78px;margin-left:200px}div.jp-video div.jp-type-playlist ul.jp-controls{width:134px;margin-left:172px}div.jp-interface ul.jp-controls li,div.jp-video ul.jp-controls{display:inline;float:left}div.jp-interface ul.jp-controls a{display:block;overflow:hidden;text-indent:-9999px}a.jp-pause,a.jp-play{width:40px;height:40px}a.jp-play{background:url(jplayer.blue.monday.jpg) 0 0 no-repeat}a.jp-play:hover{background:url(jplayer.blue.monday.jpg) -41px 0 no-repeat}a.jp-pause{background:url(jplayer.blue.monday.jpg) 0 -42px no-repeat;display:none}a.jp-pause:hover{background:url(jplayer.blue.monday.jpg) -41px -42px no-repeat}a.jp-next,a.jp-previous,a.jp-stop{width:28px;height:28px;margin-top:6px}a.jp-stop{background:url(jplayer.blue.monday.jpg) 0 -83px no-repeat;margin-left:10px}a.jp-stop:hover{background:url(jplayer.blue.monday.jpg) -29px -83px no-repeat}a.jp-previous{background:url(jplayer.blue.monday.jpg) 0 -112px no-repeat}a.jp-previous:hover{background:url(jplayer.blue.monday.jpg) -29px -112px no-repeat}a.jp-next{background:url(jplayer.blue.monday.jpg) 0 -141px no-repeat}a.jp-next:hover{background:url(jplayer.blue.monday.jpg) -29px -141px no-repeat}div.jp-progress{overflow:hidden;background-color:#ddd}div.jp-audio div.jp-progress{position:absolute;top:32px;height:15px}div.jp-audio div.jp-type-single div.jp-progress{left:110px;width:186px}div.jp-audio div.jp-type-playlist div.jp-progress{left:166px;width:130px}div.jp-video div.jp-progress{top:0;left:0;width:100%;height:10px}div.jp-seek-bar{background:url(jplayer.blue.monday.jpg) 0 -202px repeat-x;width:0;height:100%;cursor:pointer}div.jp-play-bar{background:url(jplayer.blue.monday.jpg) 0 -218px repeat-x;width:0;height:100%}div.jp-seeking-bg{background:url(jplayer.blue.monday.seeking.gif)}a.jp-mute,a.jp-unmute,a.jp-volume-max{width:18px;height:15px;margin-top:12px}div.jp-audio div.jp-type-single a.jp-mute,div.jp-audio div.jp-type-single a.jp-unmute{margin-left:210px}div.jp-audio div.jp-type-playlist a.jp-mute,div.jp-audio div.jp-type-playlist a.jp-unmute{margin-left:154px}div.jp-audio-stream div.jp-type-single a.jp-mute,div.jp-audio-stream div.jp-type-single a.jp-unmute{margin-left:10px}div.jp-audio a.jp-volume-max,div.jp-audio-stream a.jp-volume-max{margin-left:56px}div.jp-video a.jp-mute,div.jp-video a.jp-unmute,div.jp-video a.jp-volume-max{position:absolute;top:12px;margin-top:0}div.jp-video a.jp-mute,div.jp-video a.jp-unmute{left:50px}div.jp-video a.jp-volume-max{left:134px}a.jp-mute{background:url(jplayer.blue.monday.jpg) 0 -170px no-repeat}a.jp-mute:hover{background:url(jplayer.blue.monday.jpg) -19px -170px no-repeat}a.jp-unmute{background:url(jplayer.blue.monday.jpg) -60px -170px no-repeat;display:none}a.jp-unmute:hover{background:url(jplayer.blue.monday.jpg) -79px -170px no-repeat}a.jp-volume-max{background:url(jplayer.blue.monday.jpg) 0 -186px no-repeat}a.jp-volume-max:hover{background:url(jplayer.blue.monday.jpg) -19px -186px no-repeat}div.jp-volume-bar{position:absolute;overflow:hidden;background:url(jplayer.blue.monday.jpg) 0 -250px repeat-x;width:46px;height:5px;cursor:pointer}div.jp-audio div.jp-volume-bar{top:37px;left:330px}div.jp-audio-stream div.jp-volume-bar{top:37px;left:92px}div.jp-video div.jp-volume-bar{top:17px;left:72px}div.jp-volume-bar-value{background:url(jplayer.blue.monday.jpg) 0 -256px repeat-x;width:0;height:5px}div.jp-audio div.jp-time-holder{position:absolute;top:50px}div.jp-audio div.jp-type-single div.jp-time-holder{left:110px;width:186px}div.jp-audio div.jp-type-playlist div.jp-time-holder{left:166px;width:130px}div.jp-current-time,div.jp-duration{width:60px;font-size:.64em;font-style:oblique}div.jp-current-time{float:left;display:inline}div.jp-duration{float:right;display:inline;text-align:right}div.jp-video div.jp-current-time{margin-left:20px}div.jp-video div.jp-duration{margin-right:20px}div.jp-details{font-weight:700;text-align:center}div.jp-details,div.jp-playlist{width:100%;background-color:#ccc;border-top:1px solid #009be3}div.jp-type-playlist div.jp-details,div.jp-type-single div.jp-details,div.jp-type-single div.jp-playlist{border-top:none}div.jp-details ul,div.jp-playlist ul{list-style-type:none;margin:0;padding:0 20px;font-size:.72em}div.jp-details li{padding:5px 0;font-weight:700}div.jp-playlist li{padding:5px 0 4px 20px;border-bottom:1px solid #eee}div.jp-playlist li div{display:inline}div.jp-type-playlist div.jp-playlist li:last-child{padding:5px 0 5px 20px;border-bottom:none}div.jp-type-playlist div.jp-playlist li.jp-playlist-current{list-style-type:square;list-style-position:inside;padding-left:7px}div.jp-type-playlist div.jp-playlist a{color:#333;text-decoration:none}div.jp-type-playlist div.jp-playlist a.jp-playlist-current,div.jp-type-playlist div.jp-playlist a:hover{color:#0d88c1}div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove{float:right;display:inline;text-align:right;margin-right:10px;font-weight:700;color:#666}div.jp-type-playlist div.jp-playlist a.jp-playlist-item-remove:hover{color:#0d88c1}div.jp-type-playlist div.jp-playlist span.jp-free-media{float:right;display:inline;text-align:right;margin-right:10px}div.jp-type-playlist div.jp-playlist span.jp-free-media a{color:#666}div.jp-type-playlist div.jp-playlist span.jp-free-media a:hover{color:#0d88c1}span.jp-artist{font-size:.8em;color:#666}div.jp-video-play{width:100%;overflow:hidden;cursor:pointer;background-color:transparent}div.jp-video-270p div.jp-video-play{margin-top:-270px;height:270px}div.jp-video-360p div.jp-video-play{margin-top:-360px;height:360px}div.jp-video-full div.jp-video-play{height:100%}a.jp-video-play-icon{position:relative;display:block;width:112px;height:100px;margin-left:-56px;margin-top:-50px;left:50%;top:50%;background:url(jplayer.blue.monday.video.play.png) 0 0 no-repeat;text-indent:-9999px}div.jp-video-play:hover a.jp-video-play-icon{background:url(jplayer.blue.monday.video.play.png) 0 -100px no-repeat}div.jp-jplayer,div.jp-jplayer audio{width:0;height:0}div.jp-jplayer{background-color:#000}ul.jp-toggles{list-style-type:none;padding:0;margin:0 auto;overflow:hidden}div.jp-audio .jp-type-single ul.jp-toggles{width:25px}div.jp-audio .jp-type-playlist ul.jp-toggles{width:55px;margin:0;position:absolute;left:325px;top:50px}div.jp-video ul.jp-toggles{margin-top:10px;width:100px}ul.jp-toggles li{display:block;float:right}ul.jp-toggles li a{display:block;width:25px;height:18px;text-indent:-9999px;line-height:100%}a.jp-full-screen{background:url(jplayer.blue.monday.jpg) 0 -310px no-repeat;margin-left:20px}a.jp-full-screen:hover{background:url(jplayer.blue.monday.jpg) -30px -310px no-repeat}a.jp-restore-screen{background:url(jplayer.blue.monday.jpg) -60px -310px no-repeat;margin-left:20px}a.jp-restore-screen:hover{background:url(jplayer.blue.monday.jpg) -90px -310px no-repeat}a.jp-repeat{background:url(jplayer.blue.monday.jpg) 0 -290px no-repeat}a.jp-repeat:hover{background:url(jplayer.blue.monday.jpg) -30px -290px no-repeat}a.jp-repeat-off{background:url(jplayer.blue.monday.jpg) -60px -290px no-repeat}a.jp-repeat-off:hover{background:url(jplayer.blue.monday.jpg) -90px -290px no-repeat}a.jp-shuffle{background:url(jplayer.blue.monday.jpg) 0 -270px no-repeat;margin-left:5px}a.jp-shuffle:hover{background:url(jplayer.blue.monday.jpg) -30px -270px no-repeat}a.jp-shuffle-off{background:url(jplayer.blue.monday.jpg) -60px -270px no-repeat;margin-left:5px}a.jp-shuffle-off:hover{background:url(jplayer.blue.monday.jpg) -90px -270px no-repeat}.jp-no-solution{padding:5px;font-size:.8em;background-color:#eee;border:2px solid #009be3;color:#000;display:none}.jp-no-solution a{color:#000}.jp-no-solution span{font-size:1em;display:block;text-align:center;font-weight:700} \ No newline at end of file diff --git a/core/vendor/filemanager/css/tui-color-picker.css b/core/vendor/filemanager/css/tui-color-picker.css new file mode 100644 index 00000000..c3cb01b1 --- /dev/null +++ b/core/vendor/filemanager/css/tui-color-picker.css @@ -0,0 +1,154 @@ +/*! + * TOAST UI Color Picker + * @version 2.2.6 + * @author NHN FE Development Team + * @license MIT + */ + .tui-colorpicker-clearfix { + zoom: 1; + } + .tui-colorpicker-clearfix:after { + content: ''; + display: block; + clear: both; + } + .tui-colorpicker-vml { + behavior: url("#default#VML"); + display: block; + } + .tui-colorpicker-container { + width: 152px; + } + .tui-colorpicker-palette-container { + width: 152px; + } + .tui-colorpicker-palette-container ul { + width: 152px; + margin: 0px; + padding: 0px; + } + .tui-colorpicker-palette-container li { + float: left; + margin: 0; + padding: 0 3px 3px 0; + list-style: none; + } + .tui-colorpicker-palette-button { + display: block; + border: none; + overflow: hidden; + outline: none; + margin: 0px; + padding: 0px; + width: 16px; + height: 16px; + border: 1px solid #ccc; + cursor: pointer; + } + .tui-colorpicker-palette-button.tui-colorpicker-selected { + border: 2px solid #000; + } + .tui-colorpicker-palette-button.tui-colorpicker-color-transparent { + barckground-repeat: repeat; + background-repeat: no-repeat; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAABfGlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGAqSSwoyGFhYGDIzSspCnJ3UoiIjFJgv8PAzcDDIMRgxSCemFxc4BgQ4MOAE3y7xsAIoi/rgsxK8/x506a1fP4WNq+ZclYlOrj1gQF3SmpxMgMDIweQnZxSnJwLZOcA2TrJBUUlQPYMIFu3vKQAxD4BZIsUAR0IZN8BsdMh7A8gdhKYzcQCVhMS5AxkSwDZAkkQtgaInQ5hW4DYyRmJKUC2B8guiBvAgNPDRcHcwFLXkYC7SQa5OaUwO0ChxZOaFxoMcgcQyzB4MLgwKDCYMxgwWDLoMjiWpFaUgBQ65xdUFmWmZ5QoOAJDNlXBOT+3oLQktUhHwTMvWU9HwcjA0ACkDhRnEKM/B4FNZxQ7jxDLX8jAYKnMwMDcgxBLmsbAsH0PA4PEKYSYyjwGBn5rBoZt5woSixLhDmf8xkKIX5xmbARh8zgxMLDe+///sxoDA/skBoa/E////73o//+/i4H2A+PsQA4AJHdp4IxrEg8AAAGbaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQuMCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjEzPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjE0PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CghrN1AAAABzSURBVCgVldKxEYAgDAXQD5VOpLuwgi4jlrTMqF00oOd5Aia/CcV/F4oYOgNlrLjvVyCEVJchBjEC25538PeaWTzRMBLxvIL7UZwFwL06qoA6aoAy+gFfJABvJAQPUoCMlICRRd8BzgHzJL4ok9aJ67l4AK9AxVKhHryUAAAAAElFTkSuQmCC"); + } + .tui-colorpicker-palette-hex { + font-family: monospace; + display: inline-block; + *display: inline; + zoom: 1; + width: 60px; + vertical-align: middle; + } + .tui-colorpicker-palette-preview { + display: inline-block; + *display: inline; + zoom: 1; + width: 12px; + height: 12px; + border: 1px solid #ccc; + border: 1px solid #ccc; + vertical-align: middle; + overflow: hidden; + } + .tui-colorpicker-palette-toggle-slider { + display: inline-block; + *display: inline; + zoom: 1; + vertical-align: middle; + float: right; + } + .tui-colorpicker-slider-container { + margin: 5px 0 0 0; + height: 122px; + zoom: 1; + } + .tui-colorpicker-slider-container:after { + content: ''; + display: block; + clear: both; + } + .tui-colorpicker-slider-left { + float: left; + width: 120px; + height: 120px; + } + .tui-colorpicker-slider-right { + float: right; + width: 32px; + height: 120px; + } + .tui-colorpicker-svg { + display: block; + } + .tui-colorpicker-slider-handle { + position: absolute; + overflow: visible; + top: 0; + left: 0; + width: 1px; + height: 1px; + z-index: 2; + opacity: 0.9; + } + .tui-colorpicker-svg-slider { + width: 120px; + height: 120px; + border: 1px solid #ccc; + overflow: hidden; + } + .tui-colorpicker-vml-slider { + position: relative; + width: 120px; + height: 120px; + border: 1px solid #ccc; + overflow: hidden; + } + .tui-colorpicker-vml-slider-bg { + position: absolute; + margin: -1px 0 0 -1px; + top: 0; + left: 0; + width: 122px; + height: 122px; + } + .tui-colorpicker-svg-huebar { + float: right; + width: 18px; + height: 120px; + border: 1px solid #ccc; + overflow: visible; + } + .tui-colorpicker-vml-huebar { + width: 32px; + position: relative; + } + .tui-colorpicker-vml-huebar-bg { + position: absolute; + top: 0; + right: 0; + width: 18px; + height: 121px; + } + \ No newline at end of file diff --git a/core/vendor/filemanager/css/tui-image-editor.css b/core/vendor/filemanager/css/tui-image-editor.css new file mode 100644 index 00000000..10515b67 --- /dev/null +++ b/core/vendor/filemanager/css/tui-image-editor.css @@ -0,0 +1,6 @@ +/*! + * TOAST UI ImageEditor + * @version 3.15.3 + * @license MIT + */ + body > textarea{position:fixed !important}.tui-image-editor-container{margin:0;padding:0;box-sizing:border-box;min-height:300px;height:100%;position:relative;background-color:#282828;overflow:hidden;letter-spacing:.3px}.tui-image-editor-container div,.tui-image-editor-container ul,.tui-image-editor-container label,.tui-image-editor-container input,.tui-image-editor-container li{box-sizing:border-box;margin:0;padding:0;-ms-user-select:none;-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;user-select:none}.tui-image-editor-container .tui-image-editor-header{min-width:533px;position:absolute;background-color:#151515;top:0;width:100%}.tui-image-editor-container .tui-image-editor-header-buttons,.tui-image-editor-container .tui-image-editor-controls-buttons{float:right;margin:8px}.tui-image-editor-container .tui-image-editor-header-logo,.tui-image-editor-container .tui-image-editor-controls-logo{float:left;width:30%;padding:17px}.tui-image-editor-container .tui-image-editor-controls-logo,.tui-image-editor-container .tui-image-editor-controls-buttons{width:270px;height:100%;display:none}.tui-image-editor-container .tui-image-editor-header-buttons button,.tui-image-editor-container .tui-image-editor-header-buttons div,.tui-image-editor-container .tui-image-editor-controls-buttons button,.tui-image-editor-container .tui-image-editor-controls-buttons div{display:inline-block;position:relative;width:120px;height:40px;padding:0;line-height:40px;outline:none;border-radius:20px;border:1px solid #ddd;font-family:'Noto Sans',sans-serif;font-size:12px;font-weight:bold;cursor:pointer;vertical-align:middle;letter-spacing:.3px;text-align:center}.tui-image-editor-container .tui-image-editor-download-btn{background-color:#fdba3b;border-color:#fdba3b;color:#fff}.tui-image-editor-container .tui-image-editor-load-btn{position:absolute;left:0;right:0;display:inline-block;top:0;bottom:0;width:100%;cursor:pointer;opacity:0}.tui-image-editor-container .tui-image-editor-main-container{position:absolute;width:100%;top:0;bottom:64px}.tui-image-editor-container .tui-image-editor-main{position:absolute;text-align:center;top:64px;bottom:0;right:0;left:0}.tui-image-editor-container .tui-image-editor-wrap{position:absolute;bottom:0;width:100%;overflow:auto}.tui-image-editor-container .tui-image-editor-wrap .tui-image-editor-size-wrap{display:table;width:100%;height:100%}.tui-image-editor-container .tui-image-editor-wrap .tui-image-editor-size-wrap .tui-image-editor-align-wrap{display:table-cell;vertical-align:middle}.tui-image-editor-container .tui-image-editor{position:relative;display:inline-block}.tui-image-editor-container .tui-image-editor-menu,.tui-image-editor-container .tui-image-editor-help-menu{width:auto;list-style:none;padding:0;margin:0 auto;display:table-cell;text-align:center;vertical-align:middle;white-space:nowrap}.tui-image-editor-container .tui-image-editor-menu > .tui-image-editor-item,.tui-image-editor-container .tui-image-editor-help-menu > .tui-image-editor-item{position:relative;display:inline-block;border-radius:2px;padding:7px 8px 3px 8px;cursor:pointer;margin:0 4px}.tui-image-editor-container .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:hover:before,.tui-image-editor-container .tui-image-editor-help-menu > .tui-image-editor-item[tooltip-content]:hover:before{content:'';position:absolute;display:inline-block;margin:0 auto 0;width:0;height:0;border-right:7px solid transparent;border-top:7px solid #2f2f2f;border-left:7px solid transparent;left:13px;top:-2px}.tui-image-editor-container .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:hover:after,.tui-image-editor-container .tui-image-editor-help-menu > .tui-image-editor-item[tooltip-content]:hover:after{content:attr(tooltip-content);position:absolute;display:inline-block;background-color:#2f2f2f;color:#fff;padding:5px 8px;font-size:11px;font-weight:lighter;border-radius:3px;max-height:23px;top:-25px;left:0;min-width:24px}.tui-image-editor-container .tui-image-editor-menu > .tui-image-editor-item.active,.tui-image-editor-container .tui-image-editor-help-menu > .tui-image-editor-item.active{background-color:#fff;transition:all .3s ease}.tui-image-editor-container .tui-image-editor-wrap{position:absolute}.tui-image-editor-container .tui-image-editor-grid-visual{display:none;position:absolute;width:100%;height:100%;border:1px solid rgba(255,255,255,0.7)}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor{transition:none}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-grid-visual,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-grid-visual,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-resize .tui-image-editor-grid-visual{display:block}.tui-image-editor-container .tui-image-editor-grid-visual table{width:100%;height:100%;border-collapse:collapse}.tui-image-editor-container .tui-image-editor-grid-visual table td{border:1px solid rgba(255,255,255,0.3)}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot:before{content:'';position:absolute;box-sizing:border-box;width:10px;height:10px;border:0;box-shadow:0 0 1px 0 rgba(0,0,0,0.3);border-radius:100%;background-color:#fff}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.left-top:before{top:-5px;left:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.right-top:before{top:-5px;right:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.left-bottom:before{bottom:-5px;left:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.right-bottom:before{bottom:-5px;right:-5px}.tui-image-editor-container .tui-image-editor-submenu{display:none;position:absolute;bottom:0;width:100%;height:150px;white-space:nowrap;z-index:2}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button:hover svg > use.active{display:block}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item li{display:inline-block;vertical-align:top}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-newline{display:block;margin-top:0}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button{position:relative;cursor:pointer;display:inline-block;font-weight:normal;font-size:11px;margin:0 9px 0 9px}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button.preset{margin:0 9px 20px 5px}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item label > span{display:inline-block;cursor:pointer;padding-top:5px;font-family:"Noto Sans",sans-serif;font-size:11px}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button.apply label,.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-image-editor-button.cancel label{vertical-align:7px}.tui-image-editor-container .tui-image-editor-submenu > div{display:none;vertical-align:bottom}.tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-submenu-style{opacity:.95;z-index:-1;position:absolute;top:0;bottom:0;left:0;right:0;display:block}.tui-image-editor-container .tui-image-editor-partition > div{width:1px;height:52px;border-left:1px solid #3c3c3c;margin:0 8px 0 8px}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-partition > div{height:108px;margin:0 29px 0 0}.tui-image-editor-container .tui-image-editor-submenu-align{text-align:left;margin-right:30px}.tui-image-editor-container .tui-image-editor-submenu-align label > span{width:55px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-submenu-align:first-child{margin-right:0}.tui-image-editor-container .tui-image-editor-submenu-align:first-child label > span{width:70px}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-crop .tui-image-editor-submenu > div.tui-image-editor-menu-crop,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-resize .tui-image-editor-submenu > div.tui-image-editor-menu-resize,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-submenu > div.tui-image-editor-menu-flip,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-submenu > div.tui-image-editor-menu-rotate,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-shape .tui-image-editor-submenu > div.tui-image-editor-menu-shape,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-text .tui-image-editor-submenu > div.tui-image-editor-menu-text,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-mask .tui-image-editor-submenu > div.tui-image-editor-menu-mask,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-icon .tui-image-editor-submenu > div.tui-image-editor-menu-icon,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-draw .tui-image-editor-submenu > div.tui-image-editor-menu-draw,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-submenu > div.tui-image-editor-menu-filter,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-zoom .tui-image-editor-submenu > div.tui-image-editor-menu-zoom{display:table-cell}.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-crop .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-resize .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-flip .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-shape .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-text .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-mask .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-icon .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-draw .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-filter .tui-image-editor-submenu,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-zoom .tui-image-editor-submenu{display:table}.tui-image-editor-container .tui-image-editor-help-menu{list-style:none;padding:0;margin:0 auto;text-align:center;vertical-align:middle;border-radius:20px;background-color:rgba(255,255,255,0.06);z-index:2;position:absolute}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history{display:none;background-color:#fff;color:#444;position:absolute;width:196px;height:276px;padding:4px 2px;box-shadow:0 2px 6px 0 rgba(0,0,0,0.15);cursor:auto;transform:translateX(calc(-50% + 12px))}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list{height:268px;padding:0;overflow:hidden scroll;list-style:none}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item{height:24px;font-size:11px;line-height:24px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item{position:relative;height:24px;cursor:pointer}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item svg{width:24px;height:24px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item span{display:inline-block;width:128px;height:24px;text-align:left}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item .history-item-icon{display:inline-block;width:24px;height:24px;position:absolute;top:6px;left:6px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item .history-item-checkbox{display:none;width:24px;height:24px;position:absolute;top:5px;right:-6px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item.selected-item{background-color:rgba(119,119,119,0.12)}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item.selected-item .history-item-checkbox{display:inline-block}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item.disabled-item{color:#333;opacity:.3}.tui-image-editor-container .tui-image-editor-help-menu .opened .tie-panel-history{display:block}.tui-image-editor-container .tui-image-editor-help-menu .opened .tie-panel-history:before{content:'';position:absolute;display:inline-block;margin:0 auto;width:0;height:0}.tui-image-editor-container .filter-color-item{display:inline-block}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox{display:block}.tui-image-editor-container .tui-image-editor-checkbox-wrap{display:inline-block !important;text-align:left}.tui-image-editor-container .tui-image-editor-checkbox-wrap.fixed-width{width:187px;white-space:normal}.tui-image-editor-container .tui-image-editor-checkbox{display:inline-block;margin:1px 0 1px 0}.tui-image-editor-container .tui-image-editor-checkbox input{width:14px;height:14px;opacity:0}.tui-image-editor-container .tui-image-editor-checkbox > label > span{color:#fff;height:14px;position:relative}.tui-image-editor-container .tui-image-editor-checkbox input + label:before,.tui-image-editor-container .tui-image-editor-checkbox > label > span:before{content:'';position:absolute;width:14px;height:14px;background-color:#fff;top:6px;left:-19px;display:inline-block;margin:0;text-align:center;font-size:11px;border:0;border-radius:2px;padding-top:1px;box-sizing:border-box}.tui-image-editor-container .tui-image-editor-checkbox input[type='checkbox']:checked + span:before{background-size:cover;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==)}.tui-image-editor-container .tui-image-editor-selectlist-wrap{position:relative}.tui-image-editor-container .tui-image-editor-selectlist-wrap select{width:100%;height:28px;margin-top:4px;border:0;outline:0;border-radius:0;border:1px solid #cbdbdb;background-color:#fff;-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0 7px 0 10px}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist{display:none;position:relative;top:-1px;border:1px solid #ccc;background-color:#fff;border-top:0;padding:4px 0}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li{display:block;text-align:left;padding:7px 10px;font-family:'Noto Sans',sans-serif}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li:hover{background-color:rgba(81,92,230,0.05)}.tui-image-editor-container .tui-image-editor-selectlist-wrap:before{content:'';position:absolute;display:inline-block;width:14px;height:14px;right:5px;top:10px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=);background-size:cover}.tui-image-editor-container .tui-image-editor-selectlist-wrap select::-ms-expand{display:none}.tui-image-editor-container .tui-image-editor-virtual-range-bar .tui-image-editor-disabled,.tui-image-editor-container .tui-image-editor-virtual-range-subbar .tui-image-editor-disabled,.tui-image-editor-container .tui-image-editor-virtual-range-pointer .tui-image-editor-disabled{backbround-color:#f00}.tui-image-editor-container .tui-image-editor-range{position:relative;top:5px;width:166px;height:17px;display:inline-block}.tui-image-editor-container .tui-image-editor-virtual-range-bar{top:7px;position:absolute;width:100%;height:2px;background-color:#666}.tui-image-editor-container .tui-image-editor-virtual-range-subbar{position:absolute;height:100%;left:0;right:0;background-color:#d1d1d1}.tui-image-editor-container .tui-image-editor-virtual-range-pointer{position:absolute;cursor:pointer;top:-5px;left:0;width:12px;height:12px;background-color:#fff;border-radius:100%}.tui-image-editor-container .tui-image-editor-range-wrap{display:inline-block;margin-left:4px}.tui-image-editor-container .tui-image-editor-range-wrap.short .tui-image-editor-range{width:100px}.tui-image-editor-container .color-picker-control .tui-image-editor-range{width:108px;margin-left:10px}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-pointer{background-color:#333}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-bar{background-color:#ccc}.tui-image-editor-container .color-picker-control .tui-image-editor-virtual-range-subbar{background-color:#606060}.tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short{margin-top:-2px;margin-left:19px}.tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label{color:#8e8e8e;font-weight:normal}.tui-image-editor-container .tui-image-editor-range-wrap label{vertical-align:baseline;font-size:11px;margin-right:7px;color:#fff}.tui-image-editor-container .tui-image-editor-range-value{cursor:default;width:40px;height:24px;outline:none;border-radius:2px;box-shadow:none;border:1px solid #d5d5d5;text-align:center;background-color:#1c1c1c;color:#fff;font-weight:lighter;vertical-align:baseline;font-family:'Noto Sans',sans-serif;margin-top:15px;margin-left:4px}.tui-image-editor-container .tui-image-editor-controls{position:absolute;background-color:#151515;width:100%;height:64px;display:table;bottom:0;z-index:2}.tui-image-editor-container .tui-image-editor-icpartition{display:inline-block;background-color:#444;width:1px;height:24px}.tui-image-editor-container.left .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:before{left:28px;top:11px;border-right:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container.left .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:after{top:7px;left:42px;white-space:nowrap}.tui-image-editor-container.left .tui-image-editor-submenu{left:0;height:100%;width:248px}.tui-image-editor-container.left .tui-image-editor-main-container{left:64px;width:calc(100% - 64px);height:100%}.tui-image-editor-container.left .tui-image-editor-controls{width:64px;height:100%;display:table}.tui-image-editor-container.left .tui-image-editor-menu,.tui-image-editor-container.right .tui-image-editor-menu{white-space:inherit}.tui-image-editor-container.left .tui-image-editor-submenu,.tui-image-editor-container.right .tui-image-editor-submenu{white-space:normal}.tui-image-editor-container.left .tui-image-editor-submenu > div,.tui-image-editor-container.right .tui-image-editor-submenu > div{vertical-align:middle}.tui-image-editor-container.left .tui-image-editor-controls li,.tui-image-editor-container.right .tui-image-editor-controls li{display:inline-block;margin:4px auto}.tui-image-editor-container.left .tui-image-editor-icpartition,.tui-image-editor-container.right .tui-image-editor-icpartition{position:relative;top:-7px;width:24px;height:1px}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition{display:block;width:75%;margin:auto}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition > div,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition > div{border-left:0;height:10px;border-bottom:1px solid #3c3c3c;width:100%;margin:0}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-align,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-align{margin-right:0}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-item li,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-item li{margin-top:15px}.tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-colorpicker-clearfix li,.tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-submenu-item .tui-colorpicker-clearfix li{margin-top:0}.tui-image-editor-container.left .tui-image-editor-checkbox-wrap.fixed-width,.tui-image-editor-container.right .tui-image-editor-checkbox-wrap.fixed-width{width:182px;white-space:normal}.tui-image-editor-container.left .tui-image-editor-range-wrap.tui-image-editor-newline label.range,.tui-image-editor-container.right .tui-image-editor-range-wrap.tui-image-editor-newline label.range{display:block;text-align:left;width:75%;margin:auto}.tui-image-editor-container.left .tui-image-editor-range,.tui-image-editor-container.right .tui-image-editor-range{width:136px}.tui-image-editor-container.right .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:before{left:-3px;top:11px;border-left:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container.right .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:after{top:7px;left:unset;right:43px;white-space:nowrap}.tui-image-editor-container.right .tui-image-editor-submenu{right:0;height:100%;width:248px}.tui-image-editor-container.right .tui-image-editor-main-container{right:64px;width:calc(100% - 64px);height:100%}.tui-image-editor-container.right .tui-image-editor-controls{right:0;width:64px;height:100%;display:table}.tui-image-editor-container.top .tui-image-editor-submenu .tui-image-editor-partition.only-left-right,.tui-image-editor-container.bottom .tui-image-editor-submenu .tui-image-editor-partition.only-left-right{display:none}.tui-image-editor-container.bottom .tui-image-editor-submenu > div{padding-bottom:24px}.tui-image-editor-container.top .color-picker-control .triangle{top:-8px;border-right:7px solid transparent;border-top:0;border-left:7px solid transparent;border-bottom:8px solid #fff}.tui-image-editor-container.top .tui-image-editor-size-wrap{height:100%}.tui-image-editor-container.top .tui-image-editor-main-container{bottom:0}.tui-image-editor-container.top .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:before{left:13px;border-top:0;border-bottom:7px solid #2f2f2f;top:33px}.tui-image-editor-container.top .tui-image-editor-menu > .tui-image-editor-item[tooltip-content]:after{top:38px}.tui-image-editor-container.top .tui-image-editor-submenu{top:0;bottom:auto}.tui-image-editor-container.top .tui-image-editor-submenu > div{padding-top:24px;vertical-align:top}.tui-image-editor-container.top .tui-image-editor-controls-logo{display:table-cell}.tui-image-editor-container.top .tui-image-editor-controls-buttons{display:table-cell}.tui-image-editor-container.top .tui-image-editor-main{top:64px;height:calc(100% - 64px)}.tui-image-editor-container.top .tui-image-editor-controls{top:0;bottom:inherit}.tui-image-editor-container .tui-image-editor-help-menu.top{white-space:nowrap;width:506px;height:40px;top:8px;left:50%;transform:translateX(-50%)}.tui-image-editor-container .tui-image-editor-help-menu.top .tie-panel-history{top:45px}.tui-image-editor-container .tui-image-editor-help-menu.top .opened .tie-panel-history:before{border-right:8px solid transparent;border-left:8px solid transparent;border-bottom:8px solid #fff;left:90px;top:-8px}.tui-image-editor-container .tui-image-editor-help-menu.top > .tui-image-editor-item[tooltip-content]:before{left:13px;top:35px;border:none;border-bottom:7px solid #2f2f2f;border-left:7px solid transparent;border-right:7px solid transparent}.tui-image-editor-container .tui-image-editor-help-menu.top > .tui-image-editor-item[tooltip-content]:after{top:41px;left:-4px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.top > .tui-image-editor-item[tooltip-content].opened:before,.tui-image-editor-container .tui-image-editor-help-menu.top > .tui-image-editor-item[tooltip-content].opened:after{content:none}.tui-image-editor-container .tui-image-editor-help-menu.bottom{white-space:nowrap;width:506px;height:40px;bottom:8px;left:50%;transform:translateX(-50%)}.tui-image-editor-container .tui-image-editor-help-menu.bottom .tie-panel-history{bottom:45px}.tui-image-editor-container .tui-image-editor-help-menu.bottom .opened .tie-panel-history:before{border-right:8px solid transparent;border-left:8px solid transparent;border-top:8px solid #fff;left:90px;bottom:-8px}.tui-image-editor-container .tui-image-editor-help-menu.bottom > .tui-image-editor-item[tooltip-content]:before{left:13px;top:auto;bottom:36px;border:none;border-top:7px solid #2f2f2f;border-left:7px solid transparent;border-right:7px solid transparent}.tui-image-editor-container .tui-image-editor-help-menu.bottom > .tui-image-editor-item[tooltip-content]:after{top:auto;left:-4px;bottom:41px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.bottom > .tui-image-editor-item[tooltip-content].opened:before,.tui-image-editor-container .tui-image-editor-help-menu.bottom > .tui-image-editor-item[tooltip-content].opened:after{content:none}.tui-image-editor-container .tui-image-editor-help-menu.left{white-space:inherit;width:40px;height:506px;left:8px;top:50%;transform:translateY(-50%)}.tui-image-editor-container .tui-image-editor-help-menu.left .tie-panel-history{left:140px;top:-4px}.tui-image-editor-container .tui-image-editor-help-menu.left .opened .tie-panel-history:before{border-top:8px solid transparent;border-bottom:8px solid transparent;border-right:8px solid #fff;left:-8px;top:14px}.tui-image-editor-container .tui-image-editor-help-menu.left .tui-image-editor-item{margin:4px auto;padding:6px 8px}.tui-image-editor-container .tui-image-editor-help-menu.left > .tui-image-editor-item[tooltip-content]:before{left:27px;top:11px;border:none;border-right:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container .tui-image-editor-help-menu.left > .tui-image-editor-item[tooltip-content]:after{top:7px;left:40px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.left > .tui-image-editor-item[tooltip-content].opened:before,.tui-image-editor-container .tui-image-editor-help-menu.left > .tui-image-editor-item[tooltip-content].opened:after{content:none}.tui-image-editor-container .tui-image-editor-help-menu.right{white-space:inherit;width:40px;height:506px;right:8px;top:50%;transform:translateY(-50%)}.tui-image-editor-container .tui-image-editor-help-menu.right .tie-panel-history{right:-30px;top:-4px}.tui-image-editor-container .tui-image-editor-help-menu.right .opened .tie-panel-history:before{border-top:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid #fff;right:-8px;top:14px}.tui-image-editor-container .tui-image-editor-help-menu.right .tui-image-editor-item{margin:4px auto;padding:6px 8px}.tui-image-editor-container .tui-image-editor-help-menu.right > .tui-image-editor-item[tooltip-content]:before{left:-6px;top:11px;border:none;border-left:7px solid #2f2f2f;border-top:7px solid transparent;border-bottom:7px solid transparent}.tui-image-editor-container .tui-image-editor-help-menu.right > .tui-image-editor-item[tooltip-content]:after{top:7px;left:auto;right:39px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.right > .tui-image-editor-item[tooltip-content].opened:before,.tui-image-editor-container .tui-image-editor-help-menu.right > .tui-image-editor-item[tooltip-content].opened:after{content:none}.tui-image-editor-container .tie-icon-add-button .tui-image-editor-button{min-width:42px}.tui-image-editor-container .svg_ic-menu,.tui-image-editor-container .svg_ic-helpmenu{width:24px;height:24px}.tui-image-editor-container .svg_ic-submenu{width:32px;height:32px}.tui-image-editor-container .svg_img-bi{width:257px;height:26px}.tui-image-editor-container .tui-image-editor-help-menu svg > use,.tui-image-editor-container .tui-image-editor-controls svg > use{display:none}.tui-image-editor-container .tui-image-editor-help-menu .enabled svg:hover > use.hover,.tui-image-editor-container .tui-image-editor-controls .enabled svg:hover > use.hover,.tui-image-editor-container .tui-image-editor-help-menu .normal svg:hover > use.hover,.tui-image-editor-container .tui-image-editor-controls .normal svg:hover > use.hover{display:block}.tui-image-editor-container .tui-image-editor-help-menu .active svg:hover > use.hover,.tui-image-editor-container .tui-image-editor-controls .active svg:hover > use.hover{display:none}.tui-image-editor-container .tui-image-editor-help-menu .on svg > use.hover,.tui-image-editor-container .tui-image-editor-controls .on svg > use.hover,.tui-image-editor-container .tui-image-editor-help-menu .opened svg > use.hover,.tui-image-editor-container .tui-image-editor-controls .opened svg > use.hover{display:block}.tui-image-editor-container .tui-image-editor-help-menu svg > use.normal,.tui-image-editor-container .tui-image-editor-controls svg > use.normal{display:block}.tui-image-editor-container .tui-image-editor-help-menu .active svg > use.active,.tui-image-editor-container .tui-image-editor-controls .active svg > use.active{display:block}.tui-image-editor-container .tui-image-editor-help-menu .enabled svg > use.enabled,.tui-image-editor-container .tui-image-editor-controls .enabled svg > use.enabled{display:block}.tui-image-editor-container .tui-image-editor-help-menu .active svg > use.normal,.tui-image-editor-container .tui-image-editor-controls .active svg > use.normal,.tui-image-editor-container .tui-image-editor-help-menu .enabled svg > use.normal,.tui-image-editor-container .tui-image-editor-controls .enabled svg > use.normal{display:none}.tui-image-editor-container .tui-image-editor-help-menu .help svg > use.disabled,.tui-image-editor-container .tui-image-editor-controls .help svg > use.disabled,.tui-image-editor-container .tui-image-editor-help-menu .help.enabled svg > use.normal,.tui-image-editor-container .tui-image-editor-controls .help.enabled svg > use.normal{display:block}.tui-image-editor-container .tui-image-editor-help-menu .help.enabled svg > use.disabled,.tui-image-editor-container .tui-image-editor-controls .help.enabled svg > use.disabled{display:none}.tui-image-editor-container .tui-image-editor-controls:hover{z-index:3}.tui-image-editor-container div.tui-colorpicker-clearfix{width:159px;height:28px;border:1px solid #d5d5d5;border-radius:2px;background-color:#f5f5f5;margin-top:6px;padding:4px 7px 4px 7px}.tui-image-editor-container .tui-colorpicker-palette-hex{width:114px;background-color:#f5f5f5;border:0;font-size:11px;margin-top:2px;font-family:'Noto Sans',sans-serif}.tui-image-editor-container .tui-colorpicker-palette-hex[value='#ffffff'] + .tui-colorpicker-palette-preview,.tui-image-editor-container .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview{border:1px solid #ccc}.tui-image-editor-container .tui-colorpicker-palette-hex[value=''] + .tui-colorpicker-palette-preview{background-size:cover;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC)}.tui-image-editor-container .tui-colorpicker-palette-preview{border-radius:100%;float:left;width:17px;height:17px;border:0}.tui-image-editor-container .color-picker-control{position:absolute;display:none;z-index:99;width:192px;background-color:#fff;box-shadow:0 3px 22px 6px rgba(0,0,0,0.15);padding:16px;border-radius:2px}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-toggle-slider{display:none}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button{border:0;border-radius:100%;margin:2px;background-size:cover;font-size:1px}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title='#ffffff']{border:1px solid #ccc}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title='']{border:1px solid #ccc}.tui-image-editor-container .color-picker-control .triangle{width:0;height:0;border-right:7px solid transparent;border-top:8px solid #fff;border-left:7px solid transparent;position:absolute;bottom:-8px;left:84px}.tui-image-editor-container .color-picker-control .tui-colorpicker-container,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container ul,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container{width:100%;height:auto}.tui-image-editor-container .filter-color-item .color-picker-control label{font-color:#333;font-weight:normal;margin-right:7pxleft}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox{margin-top:0}.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox input + label:before,.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox > label:before{left:-16px}.tui-image-editor-container .color-picker{width:100%;height:auto}.tui-image-editor-container .color-picker-value{width:32px;height:32px;border:0;border-radius:100%;margin:auto;margin-bottom:1px}.tui-image-editor-container .color-picker-value.transparent{border:1px solid #cbcbcb;background-size:cover;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAdBJREFUWAnFl0FuwjAQRZ0ukiugHqFSOQNdseuKW3ALzkA4BateICvUGyCxrtRFd4WuunH/TzykaYJrnLEYaTJJsP2+x8GZZCbQrLU5mj7Bn+EP8HvnCObd+R7xBV5lWfaNON4AnsA38E94qLEt+0yiFaBzAV/Bv+Cxxr4co7hKCDpw1q9wLeNYYdlAwyn8TYt8Hme3+8D5ozcTaMCZ68PXa2tnM2sbEcOZAJhrrpl2DAcTOGNjZPSfCdzkw6JrfbiMv+osBe4y9WOedhm4jZfhbENWuxS44H9Wz/xw4WzqLOAqh1+zycgAwzEMzr5k5gaHOa9ULBwuuDkFlHI1Kl4PJ66kgIpnoywOTmRFAYcbwYk9UMApWkD8zAV5ihcwHk4Rx7gl0IFTQL0EFc+CTQ9OZHWH3YhlVJiVpTHbrTGLhTHLZVgff6s9lyBsI9KduSS83oj+34rTwJutmBmCnMsvozRwZqB5GTkBw6/jdPDu69iJ6BYk6eCcfbcgcQIK/MByaaiMqm8rHcjol2TnpWDhyAKSGdA3FrxtJUToX0ODqatetfGE+8tyEUOV8GY5dGRwLP/MBS4RHQr4bT7NRAQjlcOTfZxmv2G+c4hI8nn+Ax5PG/zhI393AAAAAElFTkSuQmCC)}.tui-image-editor-container .color-picker-value + label{color:#fff}.tui-image-editor-container .tui-image-editor-submenu svg > use{display:none}.tui-image-editor-container .tui-image-editor-submenu svg > use.normal{display:block}.tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype="icon-bubble"] svg > use.active,.tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype="icon-heart"] svg > use.active,.tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype="icon-location"] svg > use.active,.tie-icon-add-button.icon-polygon .tui-image-editor-button[data-icontype="icon-polygon"] svg > use.active,.tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype="icon-star"] svg > use.active,.tie-icon-add-button.icon-star-2 .tui-image-editor-button[data-icontype="icon-star-2"] svg > use.active,.tie-icon-add-button.icon-arrow-3 .tui-image-editor-button[data-icontype="icon-arrow-3"] svg > use.active,.tie-icon-add-button.icon-arrow-2 .tui-image-editor-button[data-icontype="icon-arrow-2"] svg > use.active,.tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype="icon-arrow"] svg > use.active{display:block}.tie-draw-line-select-button.line .tui-image-editor-button.line svg > use.normal,.tie-draw-line-select-button.free .tui-image-editor-button.free svg > use.normal{display:none}.tie-draw-line-select-button.line .tui-image-editor-button.line svg > use.active,.tie-draw-line-select-button.free .tui-image-editor-button.free svg > use.active{display:block}.tie-flip-button.resetFlip .tui-image-editor-button.resetFlip svg > use.normal,.tie-flip-button.flipX .tui-image-editor-button.flipX svg > use.normal,.tie-flip-button.flipY .tui-image-editor-button.flipY svg > use.normal{display:none}.tie-flip-button.resetFlip .tui-image-editor-button.resetFlip svg > use.active,.tie-flip-button.flipX .tui-image-editor-button.flipX svg > use.active,.tie-flip-button.flipY .tui-image-editor-button.flipY svg > use.active{display:block}.tie-mask-apply.apply.active .tui-image-editor-button.apply label{color:#fff}.tie-mask-apply.apply.active .tui-image-editor-button.apply svg > use.active{display:block}.tie-crop-button .tui-image-editor-button.apply,.tie-crop-preset-button .tui-image-editor-button.apply{margin-right:24px}.tie-crop-button .tui-image-editor-button.preset.active svg > use.active,.tie-crop-preset-button .tui-image-editor-button.preset.active svg > use.active{display:block}.tie-crop-button .tui-image-editor-button.apply.active svg > use.active,.tie-crop-preset-button .tui-image-editor-button.apply.active svg > use.active{display:block}.tie-resize-button .tui-image-editor-button.apply,.tie-resize-preset-button .tui-image-editor-button.apply{margin-right:24px}.tie-resize-button .tui-image-editor-button.preset.active svg > use.active,.tie-resize-preset-button .tui-image-editor-button.preset.active svg > use.active{display:block}.tie-resize-button .tui-image-editor-button.apply.active svg > use.active,.tie-resize-preset-button .tui-image-editor-button.apply.active svg > use.active{display:block}.tie-shape-button.rect .tui-image-editor-button.rect svg > use.normal,.tie-shape-button.circle .tui-image-editor-button.circle svg > use.normal,.tie-shape-button.triangle .tui-image-editor-button.triangle svg > use.normal{display:none}.tie-shape-button.rect .tui-image-editor-button.rect svg > use.active,.tie-shape-button.circle .tui-image-editor-button.circle svg > use.active,.tie-shape-button.triangle .tui-image-editor-button.triangle svg > use.active{display:block}.tie-text-effect-button .tui-image-editor-button.active svg > use.active{display:block}.tie-text-align-button.tie-text-align-left .tui-image-editor-button.left svg > use.active,.tie-text-align-button.tie-text-align-center .tui-image-editor-button.center svg > use.active,.tie-text-align-button.tie-text-align-right .tui-image-editor-button.right svg > use.active{display:block}.tie-mask-image-file,.tie-icon-image-file{opacity:0;position:absolute;width:100%;height:100%;border:1px solid #008000;cursor:inherit;left:0;top:0}.tie-zoom-button.resetFlip .tui-image-editor-button.resetFlip svg > use.normal,.tie-zoom-button.flipX .tui-image-editor-button.flipX svg > use.normal,.tie-zoom-button.flipY .tui-image-editor-button.flipY svg > use.normal{display:none}.tie-zoom-button.resetFlip .tui-image-editor-button.resetFlip svg > use.active,.tie-zoom-button.flipX .tui-image-editor-button.flipX svg > use.active,.tie-zoom-button.flipY .tui-image-editor-button.flipY svg > use.active{display:block}.tui-image-editor-container.top.tui-image-editor-top-optimization .tui-image-editor-controls ul{text-align:right}.tui-image-editor-container.top.tui-image-editor-top-optimization .tui-image-editor-controls-logo{display:none} \ No newline at end of file diff --git a/core/vendor/filemanager/dialog.php b/core/vendor/filemanager/dialog.php index cb34977a..258afa9f 100644 --- a/core/vendor/filemanager/dialog.php +++ b/core/vendor/filemanager/dialog.php @@ -340,7 +340,7 @@ $get_params = http_build_query($get_params); + href="css/jplayer.blue.monday.min.css"/> @@ -368,14 +368,11 @@ $get_params = http_build_query($get_params); - - - - - + + + + + diff --git a/core/vendor/filemanager/js/tui-code-snippet.min.js b/core/vendor/filemanager/js/tui-code-snippet.min.js new file mode 100644 index 00000000..3b7c12a0 --- /dev/null +++ b/core/vendor/filemanager/js/tui-code-snippet.min.js @@ -0,0 +1,7 @@ +/*! + * tui-code-snippet.min.js + * @version 1.5.0 + * @author NHNEnt FE Development Lab + * @license MIT + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.util=e():(t.tui=t.tui||{},t.tui.util=e())}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="dist",e(0)}([function(t,e,n){"use strict";var r={},o=n(1),i=o.extend;i(r,o),i(r,n(3)),i(r,n(2)),i(r,n(4)),i(r,n(5)),i(r,n(6)),i(r,n(7)),i(r,n(8)),i(r,n(9)),r.browser=n(10),r.popup=n(11),r.formatDate=n(12),r.defineClass=n(13),r.defineModule=n(14),r.defineNamespace=n(15),r.CustomEvents=n(16),r.Enum=n(17),r.ExMap=n(18),r.HashMap=n(20),r.Map=n(19),t.exports=r},function(t,e,n){"use strict";function r(t,e){var n,r,o,i,u=Object.prototype.hasOwnProperty;for(o=1,i=arguments.length;o-1||h.inArray(e,o)>-1)return!1;for(n in e){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1}for(n in t){if(e.hasOwnProperty(n)!==t.hasOwnProperty(n))return!1;if(typeof e[n]!=typeof t[n])return!1;if("object"==typeof t[n]||"function"==typeof t[n]){if(r.push(t),o.push(e),!c(t[n],e[n]))return!1;r.pop(),o.pop()}else if(t[n]!==e[n])return!1}return!0}function p(t,e){for(var n=arguments,r=n[0],o=1,i=n.length;o=0&&r","'":"'"," ":" "};return t.replace(/&|<|>|"|'| /g,function(t){return e[t]?e[t]:t})}function o(t){var e={'"':"quot","&":"amp","<":"lt",">":"gt","'":"#39"};return t.replace(/[<>&"']/g,function(t){return e[t]?"&"+e[t]+";":t})}function i(t){return/[<>&"']/.test(t)}function u(t,e){for(var n,r,o=0,i=t.length,u={};o1}),u=a.keys(u).sort(),n=u.join("")}var s=n(4),a=n(1);t.exports={decodeHTMLEntity:r,encodeHTMLEntity:o,hasEncodableString:i,getDuplicatedChar:u}},function(t,e){"use strict";function n(t,e){function n(){o=u.call(arguments),window.clearTimeout(r),r=window.setTimeout(function(){t.apply(null,o)},e)}var r,o;return e=e||0,n}function r(){return Number(new Date)}function o(t,e){function n(){return c=u.call(arguments),p?(f(c),void(p=!1)):(a=i.timestamp(),o=o||a,s(c),void(a-o>=e&&f(c)))}function r(){p=!0,o=null}var o,s,a,c,p=!0,f=function(e){t.apply(null,e),o=null};return e=e||0,s=i.debounce(f,e),n.reset=r,n}var i={},u=Array.prototype.slice;i.timestamp=r,i.debounce=n,i.throttle=o,t.exports=i},function(t,e,n){"use strict";function r(t){var e=(new Date).getTime();return e-t>c}function o(t,e){var n="https://www.google-analytics.com/collect",o=location.hostname,u="event",s="use",c="TOAST UI "+t+" for "+o+": Statistics",p=window.localStorage.getItem(c);(a.isUndefined(window.tui)||window.tui.usageStatistics!==!1)&&(p&&!r(p)||(window.localStorage.setItem(c,(new Date).getTime()),setTimeout(function(){"interactive"!==document.readyState&&"complete"!==document.readyState||i(n,{v:1,t:u,tid:e,cid:o,dp:o,dh:t,el:t,ec:s})},1e3)))}function i(t,e){var n=s.map(u.keys(e),function(t,n){var r=0===n?"":"&";return r+t+"="+e[t]}).join(""),r=document.createElement("img");return r.src=t+"?"+n,r.style.display="none",document.body.appendChild(r),document.body.removeChild(r),r}var u=n(1),s=n(4),a=n(2),c=6048e5;t.exports={imagePing:i,sendHostname:o}},function(t,e){"use strict";var n,r,o={chrome:!1,firefox:!1,safari:!1,msie:!1,edge:!1,others:!1,version:0},i=window.navigator,u=i.appName.replace(/\s/g,"_"),s=i.userAgent,a=/MSIE\s([0-9]+[.0-9]*)/,c=/Trident.*rv:11\./,p=/Edge\/(\d+)\./,f={firefox:/Firefox\/(\d+)\./,chrome:/Chrome\/(\d+)\./,safari:/Version\/([\d.]+).*Safari\/(\d+)/},h={Microsoft_Internet_Explorer:function(){var t=s.match(a);t?(o.msie=!0,o.version=parseFloat(t[1])):o.others=!0},Netscape:function(){var t=!1;if(c.exec(s))o.msie=!0,o.version=11,t=!0;else if(p.exec(s))o.edge=!0,o.version=s.match(p)[1],t=!0;else for(n in f)if(f.hasOwnProperty(n)&&(r=s.match(f[n]),r&&r.length>1)){o[n]=t=!0,o.version=parseFloat(r[1]||0);break}t||(o.others=!0)}},l=h[u];l&&h[u](),t.exports=o},function(t,e,n){"use strict";function r(){this.openedPopup={},this.closeWithParentPopup={},this.postBridgeUrl=""}var o=n(4),i=n(2),u=n(5),s=n(10),a=n(1),c=0;r.prototype.getPopupList=function(t){var e;return e=i.isExisty(t)?this.openedPopup[t]:this.openedPopup},r.prototype.openPopup=function(t,e){var n,r,o;if(e=a.extend({popupName:"popup_"+c+"_"+Number(new Date),popupOptionStr:"",useReload:!0,closeWithParent:!0,method:"get",param:{}},e||{}),e.method=e.method.toUpperCase(),this.postBridgeUrl=e.postBridgeUrl||this.postBridgeUrl,o="POST"===e.method&&e.param&&s.msie&&11===s.version,!i.isExisty(t))throw new Error("Popup#open() need popup url.");c+=1,e.param&&("GET"===e.method?t=t+(/\?/.test(t)?"&":"?")+this._parameterize(e.param):"POST"===e.method&&(o||(r=this.createForm(t,e.param,e.method,e.popupName),t="about:blank"))),n=this.openedPopup[e.popupName],i.isExisty(n)?n.closed?this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr):(e.useReload&&n.location.replace(t),n.focus()):this.openedPopup[e.popupName]=n=this._open(o,e.param,t,e.popupName,e.popupOptionStr),this.closeWithParentPopup[e.popupName]=e.closeWithParent,(!n||n.closed||i.isUndefined(n.closed))&&alert("please enable popup windows for this website"),e.param&&"POST"===e.method&&!o&&(n&&r.submit(),r.parentNode&&r.parentNode.removeChild(r)),window.onunload=u.bind(this.closeAllPopup,this)},r.prototype.close=function(t,e){var n=e||window;t=!!i.isExisty(t)&&t,t&&(window.onunload=null),n.closed||(n.opener=window.location.href,n.close())},r.prototype.closeAllPopup=function(t){var e=i.isExisty(t);o.forEachOwnProperties(this.openedPopup,function(t,n){(e&&this.closeWithParentPopup[n]||!e)&&this.close(!1,t)},this)},r.prototype.focus=function(t){this.getPopupList(t).focus()},r.prototype.parseQuery=function(){var t,e,n={};return t=window.location.search.substr(1),o.forEachArray(t.split("&"),function(t){e=t.split("="),n[decodeURIComponent(e[0])]=decodeURIComponent(e[1])}),n},r.prototype.createForm=function(t,e,n,r,i){var u,s=document.createElement("form");return i=i||document.body,s.method=n||"POST",s.action=t||"",s.target=r||"",s.style.display="none",o.forEachOwnProperties(e,function(t,e){u=document.createElement("input"),u.name=e,u.type="hidden",u.value=t,s.appendChild(u)}),i.appendChild(s),s},r.prototype._parameterize=function(t){var e=[];return o.forEachOwnProperties(t,function(t,n){e.push(encodeURIComponent(n)+"="+encodeURIComponent(t))}),e.join("&")},r.prototype._open=function(t,e,n,r,o){var i;return t?(i=window.open(this.postBridgeUrl,r,o),setTimeout(function(){i.redirect(n,e)},100)):i=window.open(n,r,o),i},t.exports=new r},function(t,e,n){"use strict";function r(t,e,n){var r,o,i,u;return t=Number(t),e=Number(e),n=Number(n),r=t>-1&&t<100||t>1969&&t<2070,o=e>0&&e<13,!(!r||!o)&&(u=c[e],2===e&&t%4===0&&(t%100===0&&t%400!==0||(u=29)),i=n>0&&n<=u)}function o(t,e,n){var o,a,c,f=u.pick(n,"meridiemSet","AM")||"AM",h=u.pick(n,"meridiemSet","PM")||"PM";return a=i.isDate(e)?{year:e.getFullYear(),month:e.getMonth()+1,date:e.getDate(),hour:e.getHours(),minute:e.getMinutes()}:{year:e.year,month:e.month,date:e.date,hour:e.hour,minute:e.minute},!!r(a.year,a.month,a.date)&&(a.meridiem="",/([^\\]|^)[aA]\b/.test(t)&&(o=a.hour>11?h:f,a.hour>12&&(a.hour%=12),0===a.hour&&(a.hour=12),a.meridiem=o),c=t.replace(s,function(t){return t.indexOf("\\")>-1?t.replace(/\\/,""):p[t](a)||""}))}var i=n(2),u=n(1),s=/[\\]*YYYY|[\\]*YY|[\\]*MMMM|[\\]*MMM|[\\]*MM|[\\]*M|[\\]*DD|[\\]*D|[\\]*HH|[\\]*H|[\\]*A/gi,a=["Invalid month","January","February","March","April","May","June","July","August","September","October","November","December"],c=[0,31,28,31,30,31,30,31,31,30,31,30,31],p={M:function(t){return Number(t.month)},MM:function(t){var e=t.month;return Number(e)<10?"0"+e:e},MMM:function(t){return a[Number(t.month)].substr(0,3)},MMMM:function(t){return a[Number(t.month)]},D:function(t){return Number(t.date)},d:function(t){return p.D(t)},DD:function(t){var e=t.date;return Number(e)<10?"0"+e:e},dd:function(t){return p.DD(t)},YY:function(t){return Number(t.year)%100},yy:function(t){return p.YY(t)},YYYY:function(t){var e="20",n=t.year;return n>69&&n<100&&(e="19"),Number(n)<100?e+String(n):n},yyyy:function(t){return p.YYYY(t)},A:function(t){return t.meridiem},a:function(t){return t.meridiem},hh:function(t){var e=t.hour;return Number(e)<10?"0"+e:e},HH:function(t){return p.hh(t)},h:function(t){return String(Number(t.hour))},H:function(t){return p.h(t)},m:function(t){return String(Number(t.minute))},mm:function(t){var e=t.minute;return Number(e)<10?"0"+e:e}};t.exports=o},function(t,e,n){"use strict";function r(t,e){var n;return e||(e=t,t=null),n=e.init||function(){},t&&o(n,t),e.hasOwnProperty("static")&&(i(n,e["static"]),delete e["static"]),i(n.prototype,e),n}var o=n(6).inherit,i=n(1).extend;t.exports=r},function(t,e,n){"use strict";function r(t,e){var n=e||{};return i.isFunction(n[u])&&n[u](),o(t,n)}var o=n(15),i=n(2),u="initialize";t.exports=r},function(t,e,n){"use strict";function r(t,e,n){var r,u,s,a;return r=t.split("."),r.unshift(window),u=o.reduce(r,function(t,e){return t[e]=t[e]||{},t[e]}),n?(a=r.pop(),s=i.pick.apply(null,r),u=s[a]=e):i.extend(u,e),u}var o=n(4),i=n(1);t.exports=r},function(t,e,n){"use strict";function r(){this.events=null,this.contexts=null}var o=n(4),i=n(2),u=n(1),s=/\s+/g;r.mixin=function(t){u.extend(t.prototype,r.prototype)},r.prototype._getHandlerItem=function(t,e){var n={handler:t};return e&&(n.context=e),n},r.prototype._safeEvent=function(t){var e,n=this.events;return n||(n=this.events={}),t&&(e=n[t],e||(e=[],n[t]=e),n=e),n},r.prototype._safeContext=function(){var t=this.contexts;return t||(t=this.contexts=[]),t},r.prototype._indexOfContext=function(t){for(var e=this._safeContext(),n=0;e[n];){if(t===e[n][0])return n;n+=1}return-1},r.prototype._memorizeContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1?e[n][1]+=1:e.push([t,1]))},r.prototype._forgetContext=function(t){var e,n;i.isExisty(t)&&(e=this._safeContext(),n=this._indexOfContext(t),n>-1&&(e[n][1]-=1,e[n][1]<=0&&e.splice(n,1)))},r.prototype._bindEvent=function(t,e,n){var r=this._safeEvent(t);this._memorizeContext(n),r.push(this._getHandlerItem(e,n))},r.prototype.on=function(t,e,n){var r=this;i.isString(t)?(t=t.split(s),o.forEach(t,function(t){r._bindEvent(t,e,n)})):i.isObject(t)&&(n=e,o.forEach(t,function(t,e){r.on(e,t,n)}))},r.prototype.once=function(t,e,n){function r(){e.apply(n,arguments),u.off(t,r,n)}var u=this;return i.isObject(t)?(n=e,void o.forEach(t,function(t,e){u.once(e,t,n)})):void this.on(t,r,n)},r.prototype._spliceMatches=function(t,e){var n,r=0;if(i.isArray(t))for(n=t.length;r0},r.prototype.getListenerLength=function(t){var e=this._safeEvent(t);return e.length},t.exports=r},function(t,e,n){"use strict";function r(t){t&&this.set.apply(this,arguments)}var o=n(4),i=n(2),u=function(){try{return Object.defineProperty({},"x",{}),!0}catch(t){return!1}}(),s=0;r.prototype.set=function(t){var e=this;i.isArray(t)||(t=o.toArray(arguments)),o.forEach(t,function(t){e._addItem(t)})},r.prototype.getName=function(t){var e,n=this;return o.forEach(this,function(r,o){if(n._isEnumItem(o)&&t===r)return e=o,!1}),e},r.prototype._addItem=function(t){var e;this.hasOwnProperty(t)||(e=this._makeEnumValue(),u?Object.defineProperty(this,t,{enumerable:!0,configurable:!1,writable:!1,value:e}):this[t]=e)},r.prototype._makeEnumValue=function(){var t;return t=s,s+=1,t},r.prototype._isEnumItem=function(t){return i.isNumber(this[t])},t.exports=r},function(t,e,n){"use strict";function r(t){this._map=new i(t),this.size=this._map.size}var o=n(4),i=n(19),u=["get","has","forEach","keys","values","entries"],s=["delete","clear"];o.forEachArray(u,function(t){r.prototype[t]=function(){return this._map[t].apply(this._map,arguments)}}),o.forEachArray(s,function(t){r.prototype[t]=function(){var e=this._map[t].apply(this._map,arguments);return this.size=this._map.size,e}}),r.prototype.set=function(){return this._map.set.apply(this._map,arguments),this.size=this._map.size,this},r.prototype.setObject=function(t){o.forEachOwnProperties(t,function(t,e){this.set(e,t)},this)},r.prototype.deleteByKeys=function(t){o.forEachArray(t,function(t){this["delete"](t)},this)},r.prototype.merge=function(t){t.forEach(function(t,e){this.set(e,t)},this)},r.prototype.filter=function(t){var e=new r;return this.forEach(function(n,r){t(n,r)&&e.set(r,n)}),e},t.exports=r},function(t,e,n){"use strict";function r(t,e){this._keys=t,this._valueGetter=e,this._length=this._keys.length,this._index=-1,this._done=!1}function o(t){this._valuesForString={},this._valuesForIndex={},this._keys=[],t&&this._setInitData(t),this.size=0}var i=n(4),u=n(2),s=n(3),a=n(10),c=n(5),p={},f={};r.prototype.next=function(){var t={};do this._index+=1;while(u.isUndefined(this._keys[this._index])&&this._index=this._length?t.done=!0:(t.done=!1,t.value=this._valueGetter(this._keys[this._index],this._index)),t},o.prototype._setInitData=function(t){if(!u.isArray(t))throw new Error("Only Array is supported.");i.forEachArray(t,function(t){this.set(t[0],t[1])},this)},o.prototype._isNaN=function(t){return"number"==typeof t&&t!==t},o.prototype._getKeyIndex=function(t){var e,n=-1;return u.isString(t)?(e=this._valuesForString[t],e&&(n=e.keyIndex)):n=s.inArray(t,this._keys),n},o.prototype._getOriginKey=function(t){var e=t;return t===p?e=void 0:t===f&&(e=NaN),e},o.prototype._getUniqueKey=function(t){var e=t;return u.isUndefined(t)?e=p:this._isNaN(t)&&(e=f),e},o.prototype._getValueObject=function(t,e){return u.isString(t)?this._valuesForString[t]:(u.isUndefined(e)&&(e=this._getKeyIndex(t)),e>=0?this._valuesForIndex[e]:void 0)},o.prototype._getOriginValue=function(t,e){return this._getValueObject(t,e).origin},o.prototype._getKeyValuePair=function(t,e){return[this._getOriginKey(t),this._getOriginValue(t,e)]},o.prototype._createValueObject=function(t,e){return{keyIndex:e,origin:t}},o.prototype.set=function(t,e){var n,r=this._getUniqueKey(t),o=this._getKeyIndex(r);return o<0&&(o=this._keys.push(r)-1,this.size+=1),n=this._createValueObject(e,o),u.isString(t)?this._valuesForString[t]=n:this._valuesForIndex[o]=n,this},o.prototype.get=function(t){var e=this._getUniqueKey(t),n=this._getValueObject(e);return n&&n.origin},o.prototype.keys=function(){return new r(this._keys,c.bind(this._getOriginKey,this))},o.prototype.values=function(){return new r(this._keys,c.bind(this._getOriginValue,this))},o.prototype.entries=function(){return new r(this._keys,c.bind(this._getKeyValuePair,this))},o.prototype.has=function(t){return!!this._getValueObject(t)},o.prototype["delete"]=function(t){var e;u.isString(t)?this._valuesForString[t]&&(e=this._valuesForString[t].keyIndex,delete this._valuesForString[t]):(e=this._getKeyIndex(t),e>=0&&delete this._valuesForIndex[e]),e>=0&&(delete this._keys[e],this.size-=1)},o.prototype.forEach=function(t,e){e=e||this,i.forEachArray(this._keys,function(n){u.isUndefined(n)||t.call(e,this._getValueObject(n).origin,n,this)},this)},o.prototype.clear=function(){o.call(this)},function(){window.Map&&(a.firefox&&a.version>=37||a.chrome&&a.version>=42)&&(o=window.Map)}(),t.exports=o},function(t,e,n){"use strict";function r(t){this.length=0,t&&this.setObject(t)}var o=n(4),i=n(2),u="Ã¥";r.prototype.set=function(t,e){2===arguments.length?this.setKeyValue(t,e):this.setObject(t)},r.prototype.setKeyValue=function(t,e){this.has(t)||(this.length+=1),this[this.encodeKey(t)]=e},r.prototype.setObject=function(t){var e=this;o.forEachOwnProperties(t,function(t,n){e.setKeyValue(n,t)})},r.prototype.merge=function(t){var e=this;t.each(function(t,n){e.setKeyValue(n,t)})},r.prototype.encodeKey=function(t){return u+t},r.prototype.decodeKey=function(t){var e=t.split(u);return e[e.length-1]},r.prototype.get=function(t){return this[this.encodeKey(t)]},r.prototype.has=function(t){return this.hasOwnProperty(this.encodeKey(t))},r.prototype.remove=function(t){return arguments.length>1&&(t=o.toArray(arguments)),i.isArray(t)?this.removeByKeyArray(t):this.removeByKey(t)},r.prototype.removeByKey=function(t){var e=this.has(t)?this.get(t):null;return null!==e&&(delete this[this.encodeKey(t)],this.length-=1),e},r.prototype.removeByKeyArray=function(t){var e=[],n=this;return o.forEach(t,function(t){e.push(n.removeByKey(t))}),e},r.prototype.removeAll=function(){var t=this;this.each(function(e,n){t.remove(n)})},r.prototype.each=function(t){var e,n=this;o.forEachOwnProperties(this,function(r,o){if(o.charAt(0)===u&&(e=t(r,n.decodeKey(o))),e===!1)return e})},r.prototype.keys=function(){var t=[],e=this;return this.each(function(n,r){t.push(e.decodeKey(r))}),t},r.prototype.find=function(t){var e=[];return this.each(function(n,r){t(n,r)&&e.push(n)}),e},r.prototype.toArray=function(){var t=[];return this.each(function(e){t.push(e)}),t},t.exports=r}])}); \ No newline at end of file diff --git a/core/vendor/filemanager/js/tui-color-picker.js b/core/vendor/filemanager/js/tui-color-picker.js new file mode 100644 index 00000000..bd31d8a3 --- /dev/null +++ b/core/vendor/filemanager/js/tui-color-picker.js @@ -0,0 +1,5226 @@ +/*! + * TOAST UI Color Picker + * @version 2.2.6 + * @author NHN FE Development Team + * @license MIT + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["colorPicker"] = factory(); + else + root["tui"] = root["tui"] || {}, root["tui"]["colorPicker"] = factory(); +})(window, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); +/******/ } +/******/ }; +/******/ +/******/ // define __esModule on exports +/******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = "dist"; +/******/ +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 33); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Extend the target object from other objects. + * @author NHN FE Development Lab + */ + + + +/** + * @module object + */ + +/** + * Extend the target object from other objects. + * @param {object} target - Object that will be extended + * @param {...object} objects - Objects as sources + * @returns {object} Extended object + * @memberof module:object + */ +function extend(target, objects) { // eslint-disable-line no-unused-vars + var hasOwnProp = Object.prototype.hasOwnProperty; + var source, prop, i, len; + + for (i = 1, len = arguments.length; i < len; i += 1) { + source = arguments[i]; + for (prop in source) { + if (hasOwnProp.call(source, prop)) { + target[prop] = source[prop]; + } + } + } + + return target; +} + +module.exports = extend; + + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is an instance of Array or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is an instance of Array or not. + * If the given variable is an instance of Array, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is array instance? + * @memberof module:type + */ +function isArray(obj) { + return obj instanceof Array; +} + +module.exports = isArray; + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each property of object(or element of array) which actually exist. + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(1); +var forEachArray = __webpack_require__(6); +var forEachOwnProperties = __webpack_require__(7); + +/** + * @module collection + */ + +/** + * Execute the provided callback once for each property of object(or element of array) which actually exist. + * If the object is Array-like object(ex-arguments object), It needs to transform to Array.(see 'ex2' of example). + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the property(or The value of the element) + * 2) The name of the property(or The index of the element) + * 3) The object being traversed + * @param {Object} obj The object that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * var forEach = require('tui-code-snippet/collection/forEach'); // node, commonjs + * + * var sum = 0; + * + * forEach([1,2,3], function(value){ + * sum += value; + * }); + * alert(sum); // 6 + * + * // In case of Array-like object + * var array = Array.prototype.slice.call(arrayLike); // change to array + * forEach(array, function(value){ + * sum += value; + * }); + */ +function forEach(obj, iteratee, context) { + if (isArray(obj)) { + forEachArray(obj, iteratee, context); + } else { + forEachOwnProperties(obj, iteratee, context); + } +} + +module.exports = forEach; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is undefined or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is undefined or not. + * If the given variable is undefined, returns true. + * @param {*} obj - Target for checking + * @returns {boolean} Is undefined? + * @memberof module:type + */ +function isUndefined(obj) { + return obj === undefined; // eslint-disable-line no-undefined +} + +module.exports = isUndefined; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Utils for ColorPicker component + * @author NHN. FE dev Lab. + */ + + +var browser = __webpack_require__(22); + +var forEach = __webpack_require__(2); + +var forEachArray = __webpack_require__(6); + +var forEachOwnProperties = __webpack_require__(7); + +var sendHostname = __webpack_require__(37); + +var currentId = 0; +/** + * Utils + * @namespace util + * @ignore + */ + +var utils = { + /** + * Get the number of properties in the object. + * @param {Object} obj - object + * @returns {number} + */ + getLength: function (obj) { + var length = 0; + forEachOwnProperties(obj, function () { + length += 1; + }); + return length; + }, + + /** + * Constructs a new array by executing the provided callback function. + * @param {Object|Array} obj - object or array to be traversed + * @param {function} iteratee - callback function + * @param {Object} context - context of callback function + * @returns {Array} + */ + map: function (obj, iteratee, context) { + var result = []; + forEach(obj, function () { + result.push(iteratee.apply(context || null, arguments)); + }); + return result; + }, + + /** + * Construct a new array with elements that pass the test by the provided callback function. + * @param {Array|NodeList|Arguments} arr - array to be traversed + * @param {function} iteratee - callback function + * @param {Object} context - context of callback function + * @returns {Array} + */ + filter: function (arr, iteratee, context) { + var result = []; + forEachArray(arr, function (elem) { + if (iteratee.apply(context || null, arguments)) { + result.push(elem); + } + }); + return result; + }, + + /** + * Create an unique id for a color-picker instance. + * @returns {number} + */ + generateId: function () { + currentId += 1; + return currentId; + }, + + /** + * True when browser is below IE8. + */ + isOldBrowser: function () { + return browser.msie && browser.version < 9; + }(), + + /** + * send host name + * @ignore + */ + sendHostName: function () { + sendHostname('color-picker', 'UA-129987462-1'); + } +}; +module.exports = utils; + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* eslint-disable complexity */ +/** + * @fileoverview Returns the first index at which a given element can be found in the array. + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(1); + +/** + * @module array + */ + +/** + * Returns the first index at which a given element can be found in the array + * from start index(default 0), or -1 if it is not present. + * It compares searchElement to elements of the Array using strict equality + * (the same method used by the ===, or triple-equals, operator). + * @param {*} searchElement Element to locate in the array + * @param {Array} array Array that will be traversed. + * @param {number} startIndex Start index in array for searching (default 0) + * @returns {number} the First index at which a given element, or -1 if it is not present + * @memberof module:array + * @example + * var inArray = require('tui-code-snippet/array/inArray'); // node, commonjs + * + * var arr = ['one', 'two', 'three', 'four']; + * var idx1 = inArray('one', arr, 3); // -1 + * var idx2 = inArray('one', arr); // 0 + */ +function inArray(searchElement, array, startIndex) { + var i; + var length; + startIndex = startIndex || 0; + + if (!isArray(array)) { + return -1; + } + + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(array, searchElement, startIndex); + } + + length = array.length; + for (i = startIndex; startIndex >= 0 && i < length; i += 1) { + if (array[i] === searchElement) { + return i; + } + } + + return -1; +} + +module.exports = inArray; + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each element present in the array(or Array-like object) in ascending order. + * @author NHN FE Development Lab + */ + + + +/** + * Execute the provided callback once for each element present + * in the array(or Array-like object) in ascending order. + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the element + * 2) The index of the element + * 3) The array(or Array-like object) being traversed + * @param {Array|Arguments|NodeList} arr The array(or Array-like object) that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * var forEachArray = require('tui-code-snippet/collection/forEachArray'); // node, commonjs + * + * var sum = 0; + * + * forEachArray([1,2,3], function(value){ + * sum += value; + * }); + * alert(sum); // 6 + */ +function forEachArray(arr, iteratee, context) { + var index = 0; + var len = arr.length; + + context = context || null; + + for (; index < len; index += 1) { + if (iteratee.call(context, arr[index], index, arr) === false) { + break; + } + } +} + +module.exports = forEachArray; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each property of object which actually exist. + * @author NHN FE Development Lab + */ + + + +/** + * Execute the provided callback once for each property of object which actually exist. + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the property + * 2) The name of the property + * 3) The object being traversed + * @param {Object} obj The object that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * var forEachOwnProperties = require('tui-code-snippet/collection/forEachOwnProperties'); // node, commonjs + * + * var sum = 0; + * + * forEachOwnProperties({a:1,b:2,c:3}, function(value){ + * sum += value; + * }); + * alert(sum); // 6 + */ +function forEachOwnProperties(obj, iteratee, context) { + var key; + + context = context || null; + + for (key in obj) { + if (obj.hasOwnProperty(key)) { + if (iteratee.call(context, obj[key], key, obj) === false) { + break; + } + } + } +} + +module.exports = forEachOwnProperties; + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview The base class of views. + * @author NHN. FE Development Team + */ + + +var addClass = __webpack_require__(39); + +var isFunction = __webpack_require__(13); + +var isNumber = __webpack_require__(41); + +var isUndefined = __webpack_require__(3); + +var domUtil = __webpack_require__(9); + +var Collection = __webpack_require__(19); + +var util = __webpack_require__(4); +/** + * Base class of views. + * + * All views create own container element inside supplied container element. + * @constructor + * @param {options} options The object for describe view's specs. + * @param {HTMLElement} container Default container element for view. you can use this element for this.container syntax. + * @ignore + */ + + +function View(options, container) { + var id = util.generateId(); + options = options || {}; + + if (isUndefined(container)) { + container = domUtil.appendHTMLElement('div'); + } + + addClass(container, 'tui-view-' + id); + /** + * unique id + * @type {number} + */ + + this.id = id; + /** + * base element of view. + * @type {HTMLDIVElement} + */ + + this.container = container; + /** + * child views. + * @type {Collection} + */ + + this.childs = new Collection(function (view) { + return view.id; + }); + /** + * parent view instance. + * @type {View} + */ + + this.parent = null; +} +/** + * Add child views. + * @param {View} view The view instance to add. + * @param {function} [fn] Function for invoke before add. parent view class is supplied first arguments. + */ + + +View.prototype.addChild = function (view, fn) { + if (fn) { + fn.call(view, this); + } // add parent view + + + view.parent = this; + this.childs.add(view); +}; +/** + * Remove added child view. + * @param {(number|View)} id View id or instance itself to remove. + * @param {function} [fn] Function for invoke before remove. parent view class is supplied first arguments. + */ + + +View.prototype.removeChild = function (id, fn) { + var view = isNumber(id) ? this.childs.items[id] : id; + + if (fn) { + fn.call(view, this); + } + + this.childs.remove(view.id); +}; +/** + * Render view recursively. + */ + + +View.prototype.render = function () { + this.childs.each(function (childView) { + childView.render(); + }); +}; +/** + * Invoke function recursively. + * @param {function} fn - function to invoke child view recursively + * @param {boolean} [skipThis=false] - set true then skip invoke with this(root) view. + */ + + +View.prototype.recursive = function (fn, skipThis) { + if (!isFunction(fn)) { + return; + } + + if (!skipThis) { + fn(this); + } + + this.childs.each(function (childView) { + childView.recursive(fn); + }); +}; +/** + * Resize view recursively to parent. + */ + + +View.prototype.resize = function () { + var args = Array.prototype.slice.call(arguments); + var parent = this.parent; + + while (parent) { + if (isFunction(parent._onResize)) { + parent._onResize.apply(parent, args); + } + + parent = parent.parent; + } +}; +/** + * Invoking method before destroying. + */ + + +View.prototype._beforeDestroy = function () {}; +/** + * Clear properties + */ + + +View.prototype._destroy = function () { + this._beforeDestroy(); + + this.container.innerHTML = ''; + this.id = this.parent = this.childs = this.container = null; +}; +/** + * Destroy child view recursively. + * @param {boolean} isChildView - Whether it is the child view or not + */ + + +View.prototype.destroy = function (isChildView) { + if (this.childs) { + this.childs.each(function (childView) { + childView.destroy(true); + + childView._destroy(); + }); + this.childs.clear(); + } + + if (isChildView) { + return; + } + + this._destroy(); +}; +/** + * Calculate view's container element bound. + * @returns {object} The bound of container element. + */ + + +View.prototype.getViewBound = function () { + var bound = this.container.getBoundingClientRect(); + return { + x: bound.left, + y: bound.top, + width: bound.right - bound.left, + height: bound.bottom - bound.top + }; +}; + +module.exports = View; + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Utility modules for manipulate DOM elements. + * @author NHN. FE Development Team + */ + + +var domUtil = { + /** + * Create DOM element and return it. + * @param {string} tagName Tag name to append. + * @param {HTMLElement} [container] HTML element will be parent to created element. + * if not supplied, will use **document.body** + * @param {string} [className] Design class names to appling created element. + * @returns {HTMLElement} HTML element created. + */ + appendHTMLElement: function (tagName, container, className) { + var el = document.createElement(tagName); + el.className = className || ''; + + if (container) { + container.appendChild(el); + } else { + document.body.appendChild(el); + } + + return el; + } +}; +module.exports = domUtil; + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview This module provides some functions for custom events. And it is implemented in the observer design pattern. + * @author NHN FE Development Lab + */ + + + +var extend = __webpack_require__(0); +var isExisty = __webpack_require__(20); +var isString = __webpack_require__(11); +var isObject = __webpack_require__(21); +var isArray = __webpack_require__(1); +var isFunction = __webpack_require__(13); +var forEach = __webpack_require__(2); + +var R_EVENTNAME_SPLIT = /\s+/g; + +/** + * @class + * @example + * // node, commonjs + * var CustomEvents = require('tui-code-snippet/customEvents/customEvents'); + */ +function CustomEvents() { + /** + * @type {HandlerItem[]} + */ + this.events = null; + + /** + * only for checking specific context event was binded + * @type {object[]} + */ + this.contexts = null; +} + +/** + * Mixin custom events feature to specific constructor + * @param {function} func - constructor + * @example + * var CustomEvents = require('tui-code-snippet/customEvents/customEvents'); // node, commonjs + * + * var model; + * function Model() { + * this.name = ''; + * } + * CustomEvents.mixin(Model); + * + * model = new Model(); + * model.on('change', function() { this.name = 'model'; }, this); + * model.fire('change'); + * alert(model.name); // 'model'; + */ +CustomEvents.mixin = function(func) { + extend(func.prototype, CustomEvents.prototype); +}; + +/** + * Get HandlerItem object + * @param {function} handler - handler function + * @param {object} [context] - context for handler + * @returns {HandlerItem} HandlerItem object + * @private + */ +CustomEvents.prototype._getHandlerItem = function(handler, context) { + var item = {handler: handler}; + + if (context) { + item.context = context; + } + + return item; +}; + +/** + * Get event object safely + * @param {string} [eventName] - create sub event map if not exist. + * @returns {(object|array)} event object. if you supplied `eventName` + * parameter then make new array and return it + * @private + */ +CustomEvents.prototype._safeEvent = function(eventName) { + var events = this.events; + var byName; + + if (!events) { + events = this.events = {}; + } + + if (eventName) { + byName = events[eventName]; + + if (!byName) { + byName = []; + events[eventName] = byName; + } + + events = byName; + } + + return events; +}; + +/** + * Get context array safely + * @returns {array} context array + * @private + */ +CustomEvents.prototype._safeContext = function() { + var context = this.contexts; + + if (!context) { + context = this.contexts = []; + } + + return context; +}; + +/** + * Get index of context + * @param {object} ctx - context that used for bind custom event + * @returns {number} index of context + * @private + */ +CustomEvents.prototype._indexOfContext = function(ctx) { + var context = this._safeContext(); + var index = 0; + + while (context[index]) { + if (ctx === context[index][0]) { + return index; + } + + index += 1; + } + + return -1; +}; + +/** + * Memorize supplied context for recognize supplied object is context or + * name: handler pair object when off() + * @param {object} ctx - context object to memorize + * @private + */ +CustomEvents.prototype._memorizeContext = function(ctx) { + var context, index; + + if (!isExisty(ctx)) { + return; + } + + context = this._safeContext(); + index = this._indexOfContext(ctx); + + if (index > -1) { + context[index][1] += 1; + } else { + context.push([ctx, 1]); + } +}; + +/** + * Forget supplied context object + * @param {object} ctx - context object to forget + * @private + */ +CustomEvents.prototype._forgetContext = function(ctx) { + var context, contextIndex; + + if (!isExisty(ctx)) { + return; + } + + context = this._safeContext(); + contextIndex = this._indexOfContext(ctx); + + if (contextIndex > -1) { + context[contextIndex][1] -= 1; + + if (context[contextIndex][1] <= 0) { + context.splice(contextIndex, 1); + } + } +}; + +/** + * Bind event handler + * @param {(string|{name:string, handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {(function|object)} [handler] - handler function or context + * @param {object} [context] - context for binding + * @private + */ +CustomEvents.prototype._bindEvent = function(eventName, handler, context) { + var events = this._safeEvent(eventName); + this._memorizeContext(context); + events.push(this._getHandlerItem(handler, context)); +}; + +/** + * Bind event handlers + * @param {(string|{name:string, handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {(function|object)} [handler] - handler function or context + * @param {object} [context] - context for binding + * //-- #1. Get Module --// + * var CustomEvents = require('tui-code-snippet/customEvents/customEvents'); // node, commonjs + * + * //-- #2. Use method --// + * // # 2.1 Basic Usage + * CustomEvents.on('onload', handler); + * + * // # 2.2 With context + * CustomEvents.on('onload', handler, myObj); + * + * // # 2.3 Bind by object that name, handler pairs + * CustomEvents.on({ + * 'play': handler, + * 'pause': handler2 + * }); + * + * // # 2.4 Bind by object that name, handler pairs with context object + * CustomEvents.on({ + * 'play': handler + * }, myObj); + */ +CustomEvents.prototype.on = function(eventName, handler, context) { + var self = this; + + if (isString(eventName)) { + // [syntax 1, 2] + eventName = eventName.split(R_EVENTNAME_SPLIT); + forEach(eventName, function(name) { + self._bindEvent(name, handler, context); + }); + } else if (isObject(eventName)) { + // [syntax 3, 4] + context = handler; + forEach(eventName, function(func, name) { + self.on(name, func, context); + }); + } +}; + +/** + * Bind one-shot event handlers + * @param {(string|{name:string,handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {function|object} [handler] - handler function or context + * @param {object} [context] - context for binding + */ +CustomEvents.prototype.once = function(eventName, handler, context) { + var self = this; + + if (isObject(eventName)) { + context = handler; + forEach(eventName, function(func, name) { + self.once(name, func, context); + }); + + return; + } + + function onceHandler() { // eslint-disable-line require-jsdoc + handler.apply(context, arguments); + self.off(eventName, onceHandler, context); + } + + this.on(eventName, onceHandler, context); +}; + +/** + * Splice supplied array by callback result + * @param {array} arr - array to splice + * @param {function} predicate - function return boolean + * @private + */ +CustomEvents.prototype._spliceMatches = function(arr, predicate) { + var i = 0; + var len; + + if (!isArray(arr)) { + return; + } + + for (len = arr.length; i < len; i += 1) { + if (predicate(arr[i]) === true) { + arr.splice(i, 1); + len -= 1; + i -= 1; + } + } +}; + +/** + * Get matcher for unbind specific handler events + * @param {function} handler - handler function + * @returns {function} handler matcher + * @private + */ +CustomEvents.prototype._matchHandler = function(handler) { + var self = this; + + return function(item) { + var needRemove = handler === item.handler; + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Get matcher for unbind specific context events + * @param {object} context - context + * @returns {function} object matcher + * @private + */ +CustomEvents.prototype._matchContext = function(context) { + var self = this; + + return function(item) { + var needRemove = context === item.context; + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Get matcher for unbind specific hander, context pair events + * @param {function} handler - handler function + * @param {object} context - context + * @returns {function} handler, context matcher + * @private + */ +CustomEvents.prototype._matchHandlerAndContext = function(handler, context) { + var self = this; + + return function(item) { + var matchHandler = (handler === item.handler); + var matchContext = (context === item.context); + var needRemove = (matchHandler && matchContext); + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Unbind event by event name + * @param {string} eventName - custom event name to unbind + * @param {function} [handler] - handler function + * @private + */ +CustomEvents.prototype._offByEventName = function(eventName, handler) { + var self = this; + var andByHandler = isFunction(handler); + var matchHandler = self._matchHandler(handler); + + eventName = eventName.split(R_EVENTNAME_SPLIT); + + forEach(eventName, function(name) { + var handlerItems = self._safeEvent(name); + + if (andByHandler) { + self._spliceMatches(handlerItems, matchHandler); + } else { + forEach(handlerItems, function(item) { + self._forgetContext(item.context); + }); + + self.events[name] = []; + } + }); +}; + +/** + * Unbind event by handler function + * @param {function} handler - handler function + * @private + */ +CustomEvents.prototype._offByHandler = function(handler) { + var self = this; + var matchHandler = this._matchHandler(handler); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchHandler); + }); +}; + +/** + * Unbind event by object(name: handler pair object or context object) + * @param {object} obj - context or {name: handler} pair object + * @param {function} handler - handler function + * @private + */ +CustomEvents.prototype._offByObject = function(obj, handler) { + var self = this; + var matchFunc; + + if (this._indexOfContext(obj) < 0) { + forEach(obj, function(func, name) { + self.off(name, func); + }); + } else if (isString(handler)) { + matchFunc = this._matchContext(obj); + + self._spliceMatches(this._safeEvent(handler), matchFunc); + } else if (isFunction(handler)) { + matchFunc = this._matchHandlerAndContext(handler, obj); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchFunc); + }); + } else { + matchFunc = this._matchContext(obj); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchFunc); + }); + } +}; + +/** + * Unbind custom events + * @param {(string|object|function)} eventName - event name or context or + * {name: handler} pair object or handler function + * @param {(function)} handler - handler function + * @example + * //-- #1. Get Module --// + * var CustomEvents = require('tui-code-snippet/customEvents/customEvents'); // node, commonjs + * + * //-- #2. Use method --// + * // # 2.1 off by event name + * CustomEvents.off('onload'); + * + * // # 2.2 off by event name and handler + * CustomEvents.off('play', handler); + * + * // # 2.3 off by handler + * CustomEvents.off(handler); + * + * // # 2.4 off by context + * CustomEvents.off(myObj); + * + * // # 2.5 off by context and handler + * CustomEvents.off(myObj, handler); + * + * // # 2.6 off by context and event name + * CustomEvents.off(myObj, 'onload'); + * + * // # 2.7 off by an Object. that is {eventName: handler} + * CustomEvents.off({ + * 'play': handler, + * 'pause': handler2 + * }); + * + * // # 2.8 off the all events + * CustomEvents.off(); + */ +CustomEvents.prototype.off = function(eventName, handler) { + if (isString(eventName)) { + // [syntax 1, 2] + this._offByEventName(eventName, handler); + } else if (!arguments.length) { + // [syntax 8] + this.events = {}; + this.contexts = []; + } else if (isFunction(eventName)) { + // [syntax 3] + this._offByHandler(eventName); + } else if (isObject(eventName)) { + // [syntax 4, 5, 6] + this._offByObject(eventName, handler); + } +}; + +/** + * Fire custom event + * @param {string} eventName - name of custom event + */ +CustomEvents.prototype.fire = function(eventName) { // eslint-disable-line + this.invoke.apply(this, arguments); +}; + +/** + * Fire a event and returns the result of operation 'boolean AND' with all + * listener's results. + * + * So, It is different from {@link CustomEvents#fire}. + * + * In service code, use this as a before event in component level usually + * for notifying that the event is cancelable. + * @param {string} eventName - Custom event name + * @param {...*} data - Data for event + * @returns {boolean} The result of operation 'boolean AND' + * @example + * var map = new Map(); + * map.on({ + * 'beforeZoom': function() { + * // It should cancel the 'zoom' event by some conditions. + * if (that.disabled && this.getState()) { + * return false; + * } + * return true; + * } + * }); + * + * if (this.invoke('beforeZoom')) { // check the result of 'beforeZoom' + * // if true, + * // doSomething + * } + */ +CustomEvents.prototype.invoke = function(eventName) { + var events, args, index, item; + + if (!this.hasListener(eventName)) { + return true; + } + + events = this._safeEvent(eventName); + args = Array.prototype.slice.call(arguments, 1); + index = 0; + + while (events[index]) { + item = events[index]; + + if (item.handler.apply(item.context, args) === false) { + return false; + } + + index += 1; + } + + return true; +}; + +/** + * Return whether at least one of the handlers is registered in the given + * event name. + * @param {string} eventName - Custom event name + * @returns {boolean} Is there at least one handler in event name? + */ +CustomEvents.prototype.hasListener = function(eventName) { + return this.getListenerLength(eventName) > 0; +}; + +/** + * Return a count of events registered. + * @param {string} eventName - Custom event name + * @returns {number} number of event + */ +CustomEvents.prototype.getListenerLength = function(eventName) { + var events = this._safeEvent(eventName); + + return events.length; +}; + +module.exports = CustomEvents; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is a string or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is a string or not. + * If the given variable is a string, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is string? + * @memberof module:type + */ +function isString(obj) { + return typeof obj === 'string' || obj instanceof String; +} + +module.exports = isString; + + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Utility methods to manipulate colors + * @author NHN. FE Development Team + */ + + +var hexRX = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i; +var colorUtil = { + /** + * pad left zero characters. + * @param {number} number number value to pad zero. + * @param {number} length pad length to want. + * @returns {string} padded string. + */ + leadingZero: function (number, length) { + var zero = ''; + var i = 0; + + if ((number + '').length > length) { + return number + ''; + } + + for (; i < length - 1; i += 1) { + zero += '0'; + } + + return (zero + number).slice(length * -1); + }, + + /** + * Check validate of hex string value is RGB + * @param {string} str - rgb hex string + * @returns {boolean} return true when supplied str is valid RGB hex string + */ + isValidRGB: function (str) { + return hexRX.test(str); + }, + // @license RGB <-> HSV conversion utilities based off of http://www.cs.rit.edu/~ncs/color/t_convert.html + + /** + * Convert color hex string to rgb number array + * @param {string} hexStr - hex string + * @returns {number[]} rgb numbers + */ + hexToRGB: function (hexStr) { + var r, g, b; + + if (!colorUtil.isValidRGB(hexStr)) { + return false; + } + + hexStr = hexStr.substring(1); + r = parseInt(hexStr.substr(0, 2), 16); + g = parseInt(hexStr.substr(2, 2), 16); + b = parseInt(hexStr.substr(4, 2), 16); + return [r, g, b]; + }, + + /** + * Convert rgb number to hex string + * @param {number} r - red + * @param {number} g - green + * @param {number} b - blue + * @returns {string|boolean} return false when supplied rgb number is not valid. otherwise, converted hex string + */ + rgbToHEX: function (r, g, b) { + var hexStr = '#' + colorUtil.leadingZero(r.toString(16), 2) + colorUtil.leadingZero(g.toString(16), 2) + colorUtil.leadingZero(b.toString(16), 2); + + if (colorUtil.isValidRGB(hexStr)) { + return hexStr; + } + + return false; + }, + + /** + * Convert rgb number to HSV value + * @param {number} r - red + * @param {number} g - green + * @param {number} b - blue + * @returns {number[]} hsv value + */ + rgbToHSV: function (r, g, b) { + var max, min, h, s, v, d; + r /= 255; + g /= 255; + b /= 255; + max = Math.max(r, g, b); + min = Math.min(r, g, b); + v = max; + d = max - min; + s = max === 0 ? 0 : d / max; + + if (max === min) { + h = 0; + } else { + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + + case g: + h = (b - r) / d + 2; + break; + + case b: + h = (r - g) / d + 4; + break; + // no default + } + + h /= 6; + } + + return [Math.round(h * 360), Math.round(s * 100), Math.round(v * 100)]; + }, + + /** + * Convert HSV number to RGB + * @param {number} h - hue + * @param {number} s - saturation + * @param {number} v - value + * @returns {number[]} rgb value + */ + hsvToRGB: function (h, s, v) { + var r, g, b; + var i; + var f, p, q, t; + h = Math.max(0, Math.min(360, h)); + s = Math.max(0, Math.min(100, s)); + v = Math.max(0, Math.min(100, v)); + s /= 100; + v /= 100; + + if (s === 0) { + // Achromatic (grey) + r = g = b = v; + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } + + h /= 60; // sector 0 to 5 + + i = Math.floor(h); + f = h - i; // factorial part of h + + p = v * (1 - s); + q = v * (1 - s * f); + t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: + r = v; + g = p; + b = q; + break; + } + + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } +}; +module.exports = colorUtil; + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is a function or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is a function or not. + * If the given variable is a function, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is function? + * @memberof module:type + */ +function isFunction(obj) { + return obj instanceof Function; +} + +module.exports = isFunction; + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Bind DOM events + * @author NHN FE Development Lab + */ + + + +var isString = __webpack_require__(11); +var forEach = __webpack_require__(2); + +var safeEvent = __webpack_require__(26); + +/** + * Bind DOM events. + * @param {HTMLElement} element - element to bind events + * @param {(string|object)} types - Space splitted events names or eventName:handler object + * @param {(function|object)} handler - handler function or context for handler method + * @param {object} [context] context - context for handler method. + * @memberof module:domEvent + * @example + * var div = document.querySelector('div'); + * + * // Bind one event to an element. + * on(div, 'click', toggle); + * + * // Bind multiple events with a same handler to multiple elements at once. + * // Use event names splitted by a space. + * on(div, 'mouseenter mouseleave', changeColor); + * + * // Bind multiple events with different handlers to an element at once. + * // Use an object which of key is an event name and value is a handler function. + * on(div, { + * keydown: highlight, + * keyup: dehighlight + * }); + * + * // Set a context for handler method. + * var name = 'global'; + * var repository = {name: 'CodeSnippet'}; + * on(div, 'drag', function() { + * console.log(this.name); + * }, repository); + * // Result when you drag a div: "CodeSnippet" + */ +function on(element, types, handler, context) { + if (isString(types)) { + forEach(types.split(/\s+/g), function(type) { + bindEvent(element, type, handler, context); + }); + + return; + } + + forEach(types, function(func, type) { + bindEvent(element, type, func, handler); + }); +} + +/** + * Bind DOM events + * @param {HTMLElement} element - element to bind events + * @param {string} type - events name + * @param {function} handler - handler function or context for handler method + * @param {object} [context] context - context for handler method. + * @private + */ +function bindEvent(element, type, handler, context) { + /** + * Event handler + * @param {Event} e - event object + */ + function eventHandler(e) { + handler.call(context || element, e || window.event); + } + + if ('addEventListener' in element) { + element.addEventListener(type, eventHandler); + } else if ('attachEvent' in element) { + element.attachEvent('on' + type, eventHandler); + } + memorizeHandler(element, type, handler, eventHandler); +} + +/** + * Memorize DOM event handler for unbinding. + * @param {HTMLElement} element - element to bind events + * @param {string} type - events name + * @param {function} handler - handler function that user passed at on() use + * @param {function} wrappedHandler - handler function that wrapped by domevent for implementing some features + * @private + */ +function memorizeHandler(element, type, handler, wrappedHandler) { + var events = safeEvent(element, type); + var existInEvents = false; + + forEach(events, function(obj) { + if (obj.handler === handler) { + existInEvents = true; + + return false; + } + + return true; + }); + + if (!existInEvents) { + events.push({ + handler: handler, + wrappedHandler: wrappedHandler + }); + } +} + +module.exports = on; + + +/***/ }), +/* 15 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Prevent default action + * @author NHN FE Development Lab + */ + + + +/** + * Prevent default action + * @param {Event} e - event object + * @memberof module:domEvent + */ +function preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + + return; + } + + e.returnValue = false; +} + +module.exports = preventDefault; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Convert kebab-case + * @author NHN FE Development Lab + */ + + + +/** + * Convert kebab-case + * @param {string} key - string to be converted to Kebab-case + * @private + */ +function convertToKebabCase(key) { + return key.replace(/([A-Z])/g, function(match) { + return '-' + match.toLowerCase(); + }); +} + +module.exports = convertToKebabCase; + + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Unbind DOM events + * @author NHN FE Development Lab + */ + + + +var isString = __webpack_require__(11); +var forEach = __webpack_require__(2); + +var safeEvent = __webpack_require__(26); + +/** + * Unbind DOM events + * If a handler function is not passed, remove all events of that type. + * @param {HTMLElement} element - element to unbind events + * @param {(string|object)} types - Space splitted events names or eventName:handler object + * @param {function} [handler] - handler function + * @memberof module:domEvent + * @example + * // Following the example of domEvent#on + * + * // Unbind one event from an element. + * off(div, 'click', toggle); + * + * // Unbind multiple events with a same handler from multiple elements at once. + * // Use event names splitted by a space. + * off(element, 'mouseenter mouseleave', changeColor); + * + * // Unbind multiple events with different handlers from an element at once. + * // Use an object which of key is an event name and value is a handler function. + * off(div, { + * keydown: highlight, + * keyup: dehighlight + * }); + * + * // Unbind events without handlers. + * off(div, 'drag'); + */ +function off(element, types, handler) { + if (isString(types)) { + forEach(types.split(/\s+/g), function(type) { + unbindEvent(element, type, handler); + }); + + return; + } + + forEach(types, function(func, type) { + unbindEvent(element, type, func); + }); +} + +/** + * Unbind DOM events + * If a handler function is not passed, remove all events of that type. + * @param {HTMLElement} element - element to unbind events + * @param {string} type - events name + * @param {function} [handler] - handler function + * @private + */ +function unbindEvent(element, type, handler) { + var events = safeEvent(element, type); + var index; + + if (!handler) { + forEach(events, function(item) { + removeHandler(element, type, item.wrappedHandler); + }); + events.splice(0, events.length); + } else { + forEach(events, function(item, idx) { + if (handler === item.handler) { + removeHandler(element, type, item.wrappedHandler); + index = idx; + + return false; + } + + return true; + }); + events.splice(index, 1); + } +} + +/** + * Remove an event handler + * @param {HTMLElement} element - An element to remove an event + * @param {string} type - event type + * @param {function} handler - event handler + * @private + */ +function removeHandler(element, type, handler) { + if ('removeEventListener' in element) { + element.removeEventListener(type, handler); + } else if ('detachEvent' in element) { + element.detachEvent('on' + type, handler); + } +} + +module.exports = off; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Provide a simple inheritance in prototype-oriented. + * @author NHN FE Development Lab + */ + + + +var createObject = __webpack_require__(50); + +/** + * Provide a simple inheritance in prototype-oriented. + * Caution : + * Don't overwrite the prototype of child constructor. + * + * @param {function} subType Child constructor + * @param {function} superType Parent constructor + * @memberof module:inheritance + * @example + * var inherit = require('tui-code-snippet/inheritance/inherit'); // node, commonjs + * + * // Parent constructor + * function Animal(leg) { + * this.leg = leg; + * } + * Animal.prototype.growl = function() { + * // ... + * }; + * + * // Child constructor + * function Person(name) { + * this.name = name; + * } + * + * // Inheritance + * inherit(Person, Animal); + * + * // After this inheritance, please use only the extending of property. + * // Do not overwrite prototype. + * Person.prototype.walk = function(direction) { + * // ... + * }; + */ +function inherit(subType, superType) { + var prototype = createObject(superType.prototype); + prototype.constructor = subType; + subType.prototype = prototype; +} + +module.exports = inherit; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Common collections. + * @author NHN. FE Development Team + */ + + +var forEachArray = __webpack_require__(6); + +var forEachOwnProperties = __webpack_require__(7); + +var extend = __webpack_require__(0); + +var isArray = __webpack_require__(1); + +var isExisty = __webpack_require__(20); + +var isFunction = __webpack_require__(13); + +var isObject = __webpack_require__(21); + +var util = __webpack_require__(4); + +var slice = Array.prototype.slice; +/** + * Common collection. + * + * It need function for get model's unique id. + * + * if the function is not supplied then it use default function {@link Collection#getItemID} + * @constructor + * @param {function} [getItemIDFn] function for get model's id. + * @ignore + */ + +function Collection(getItemIDFn) { + /** + * @type {object.} + */ + this.items = {}; + /** + * @type {number} + */ + + this.length = 0; + + if (isFunction(getItemIDFn)) { + /** + * @type {function} + */ + this.getItemID = getItemIDFn; + } +} +/********** + * static props + **********/ + +/** + * Combind supplied function filters and condition. + * @param {...function} filters - function filters + * @returns {function} combined filter + */ + + +Collection.and = function (filters) { + var cnt; + filters = slice.call(arguments); + cnt = filters.length; + return function (item) { + var i = 0; + + for (; i < cnt; i += 1) { + if (!filters[i].call(null, item)) { + return false; + } + } + + return true; + }; +}; +/** + * Combine multiple function filters with OR clause. + * @param {...function} filters - function filters + * @returns {function} combined filter + */ + + +Collection.or = function (filters) { + var cnt; + filters = slice.call(arguments); + cnt = filters.length; + return function (item) { + var i = 1; + var result = filters[0].call(null, item); + + for (; i < cnt; i += 1) { + result = result || filters[i].call(null, item); + } + + return result; + }; +}; +/** + * Merge several collections. + * + * You can\'t merge collections different _getEventID functions. Take case of use. + * @param {...Collection} collections collection arguments to merge + * @returns {Collection} merged collection. + */ + + +Collection.merge = function (firstCollection) { + var newItems = {}; + var merged = new Collection(firstCollection.getItemID); + forEachArray(arguments, function (col) { + extend(newItems, col.items); + }); + merged.items = newItems; + merged.length = util.getLength(merged.items); + return merged; +}; +/********** + * prototype props + **********/ + +/** + * get model's unique id. + * @param {object} item model instance. + * @returns {number} model unique id. + */ + + +Collection.prototype.getItemID = function (item) { + return item._id + ''; +}; +/** + * add models. + * @param {...*} item models to add this collection. + */ + + +Collection.prototype.add = function (item) { + var id, ownItems; + + if (arguments.length > 1) { + forEachArray(slice.call(arguments), function (o) { + this.add(o); + }, this); + return; + } + + id = this.getItemID(item); + ownItems = this.items; + + if (!ownItems[id]) { + this.length += 1; + } + + ownItems[id] = item; +}; +/** + * remove models. + * @param {...(object|string|number)} id model instance or unique id to delete. + * @returns {array} deleted model list. + */ + + +Collection.prototype.remove = function (id) { + var removed = []; + var ownItems, itemToRemove; + + if (!this.length) { + return removed; + } + + if (arguments.length > 1) { + removed = util.map(slice.call(arguments), function (id) { + return this.remove(id); + }, this); + return removed; + } + + ownItems = this.items; + + if (isObject(id)) { + id = this.getItemID(id); + } + + if (!ownItems[id]) { + return removed; + } + + this.length -= 1; + itemToRemove = ownItems[id]; + delete ownItems[id]; + return itemToRemove; +}; +/** + * remove all models in collection. + */ + + +Collection.prototype.clear = function () { + this.items = {}; + this.length = 0; +}; +/** + * check collection has specific model. + * @param {(object|string|number|function)} id model instance or id or filter function to check + * @returns {boolean} is has model? + */ + + +Collection.prototype.has = function (id) { + var isFilter, has; + + if (!this.length) { + return false; + } + + isFilter = isFunction(id); + has = false; + + if (isFilter) { + this.each(function (item) { + if (id(item) === true) { + has = true; + return false; + } + + return true; + }); + } else { + id = isObject(id) ? this.getItemID(id) : id; + has = isExisty(this.items[id]); + } + + return has; +}; +/** + * invoke callback when model exist in collection. + * @param {(string|number)} id model unique id. + * @param {function} fn the callback. + * @param {*} [context] callback context. + */ + + +Collection.prototype.doWhenHas = function (id, fn, context) { + var item = this.items[id]; + + if (!isExisty(item)) { + return; + } + + fn.call(context || this, item); +}; +/** + * Search model. and return new collection. + * @param {function} filter filter function. + * @returns {Collection} new collection with filtered models. + * @example + * collection.find(function(item) { + * return item.edited === true; + * }); + * + * function filter1(item) { + * return item.edited === false; + * } + * + * function filter2(item) { + * return item.disabled === false; + * } + * + * collection.find(Collection.and(filter1, filter2)); + * + * collection.find(Collection.or(filter1, filter2)); + */ + + +Collection.prototype.find = function (filter) { + var result = new Collection(); + + if (this.hasOwnProperty('getItemID')) { + result.getItemID = this.getItemID; + } + + this.each(function (item) { + if (filter(item) === true) { + result.add(item); + } + }); + return result; +}; +/** + * Group element by specific key values. + * + * if key parameter is function then invoke it and use returned value. + * @param {(string|number|function|array)} key key property or getter function. if string[] supplied, create each collection before grouping. + * @param {function} [groupFunc] - function that return each group's key + * @returns {object.} grouped object + * @example + * + * // pass `string`, `number`, `boolean` type value then group by property value. + * collection.groupBy('gender'); // group by 'gender' property value. + * collection.groupBy(50); // group by '50' property value. + * + * // pass `function` then group by return value. each invocation `function` is called with `(item)`. + * collection.groupBy(function(item) { + * if (item.score > 60) { + * return 'pass'; + * } + * return 'fail'; + * }); + * + * // pass `array` with first arguments then create each collection before grouping. + * collection.groupBy(['go', 'ruby', 'javascript']); + * // result: { 'go': empty Collection, 'ruby': empty Collection, 'javascript': empty Collection } + * + * // can pass `function` with `array` then group each elements. + * collection.groupBy(['go', 'ruby', 'javascript'], function(item) { + * if (item.isFast) { + * return 'go'; + * } + * + * return item.name; + * }); + */ + + +Collection.prototype.groupBy = function (key, groupFunc) { + var result = {}; + var keyIsFunc = isFunction(key); + var getItemIDFn = this.getItemID; + var collection, baseValue; + + if (isArray(key)) { + forEachArray(key, function (k) { + result[k + ''] = new Collection(getItemIDFn); + }); + + if (!groupFunc) { + return result; + } + + key = groupFunc; + keyIsFunc = true; + } + + this.each(function (item) { + if (keyIsFunc) { + baseValue = key(item); + } else { + baseValue = item[key]; + + if (isFunction(baseValue)) { + baseValue = baseValue.apply(item); + } + } + + collection = result[baseValue]; + + if (!collection) { + collection = result[baseValue] = new Collection(getItemIDFn); + } + + collection.add(item); + }); + return result; +}; +/** + * Return single item in collection. + * + * Returned item is inserted in this collection firstly. + * @returns {object} item. + */ + + +Collection.prototype.single = function () { + var result; + this.each(function (item) { + result = item; + return false; + }, this); + return result; +}; +/** + * sort a basis of supplied compare function. + * @param {function} compareFunction compareFunction + * @returns {array} sorted array. + */ + + +Collection.prototype.sort = function (compareFunction) { + var arr = []; + this.each(function (item) { + arr.push(item); + }); + + if (isFunction(compareFunction)) { + arr = arr.sort(compareFunction); + } + + return arr; +}; +/** + * iterate each model element. + * + * when iteratee return false then break the loop. + * @param {function} iteratee iteratee(item, index, items) + * @param {*} [context] context + */ + + +Collection.prototype.each = function (iteratee, context) { + forEachOwnProperties(this.items, iteratee, context || this); +}; +/** + * return new array with collection items. + * @returns {array} new array. + */ + + +Collection.prototype.toArray = function () { + if (!this.length) { + return []; + } + + return util.map(this.items, function (item) { + return item; + }); +}; + +module.exports = Collection; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is existing or not. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(3); +var isNull = __webpack_require__(36); + +/** + * Check whether the given variable is existing or not. + * If the given variable is not null and not undefined, returns true. + * @param {*} param - Target for checking + * @returns {boolean} Is existy? + * @memberof module:type + * @example + * var isExisty = require('tui-code-snippet/type/isExisty'); // node, commonjs + * + * isExisty(''); //true + * isExisty(0); //true + * isExisty([]); //true + * isExisty({}); //true + * isExisty(null); //false + * isExisty(undefined); //false +*/ +function isExisty(param) { + return !isUndefined(param) && !isNull(param); +} + +module.exports = isExisty; + + +/***/ }), +/* 21 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is an object or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is an object or not. + * If the given variable is an object, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is object? + * @memberof module:type + */ +function isObject(obj) { + return obj === Object(obj); +} + +module.exports = isObject; + + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview This module detects the kind of well-known browser and version. + * @author NHN FE Development Lab + */ + + + +/** + * Browser module + * @module browser + */ + +/** + * This object has an information that indicate the kind of browser. + * The list below is a detectable browser list. + * - ie8 ~ ie11 + * - chrome + * - firefox + * - safari + * - edge + * @memberof module:browser + * @example + * var browser = require('tui-code-snippet/browser/browser'); // node, commonjs + * + * browser.chrome === true; // chrome + * browser.firefox === true; // firefox + * browser.safari === true; // safari + * browser.msie === true; // IE + * browser.edge === true; // edge + * browser.others === true; // other browser + * browser.version; // browser version + */ +var browser = { + chrome: false, + firefox: false, + safari: false, + msie: false, + edge: false, + others: false, + version: 0 +}; + +if (window && window.navigator) { + detectBrowser(); +} + +/** + * Detect the browser. + * @private + */ +function detectBrowser() { + var nav = window.navigator; + var appName = nav.appName.replace(/\s/g, '_'); + var userAgent = nav.userAgent; + + var rIE = /MSIE\s([0-9]+[.0-9]*)/; + var rIE11 = /Trident.*rv:11\./; + var rEdge = /Edge\/(\d+)\./; + var versionRegex = { + firefox: /Firefox\/(\d+)\./, + chrome: /Chrome\/(\d+)\./, + safari: /Version\/([\d.]+).*Safari\/(\d+)/ + }; + + var key, tmp; + + var detector = { + Microsoft_Internet_Explorer: function() { // eslint-disable-line camelcase + var detectedVersion = userAgent.match(rIE); + + if (detectedVersion) { // ie8 ~ ie10 + browser.msie = true; + browser.version = parseFloat(detectedVersion[1]); + } else { // no version information + browser.others = true; + } + }, + Netscape: function() { // eslint-disable-line complexity + var detected = false; + + if (rIE11.exec(userAgent)) { + browser.msie = true; + browser.version = 11; + detected = true; + } else if (rEdge.exec(userAgent)) { + browser.edge = true; + browser.version = userAgent.match(rEdge)[1]; + detected = true; + } else { + for (key in versionRegex) { + if (versionRegex.hasOwnProperty(key)) { + tmp = userAgent.match(versionRegex[key]); + if (tmp && tmp.length > 1) { // eslint-disable-line max-depth + browser[key] = detected = true; + browser.version = parseFloat(tmp[1] || 0); + break; + } + } + } + } + if (!detected) { + browser.others = true; + } + } + }; + + var fn = detector[appName]; + + if (fn) { + detector[appName](); + } +} + +module.exports = browser; + + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Get HTML element's design classes. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(3); + +/** + * Get HTML element's design classes. + * @param {(HTMLElement|SVGElement)} element target element + * @returns {string} element css class name + * @memberof module:domUtil + */ +function getClass(element) { + if (!element || !element.className) { + return ''; + } + + if (isUndefined(element.className.baseVal)) { + return element.className; + } + + return element.className.baseVal; +} + +module.exports = getClass; + + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) {/** + * @fileoverview General drag handler + * @author NHN. FE Development Team + */ + + +var CustomEvents = __webpack_require__(10); + +var disableTextSelection = __webpack_require__(42); + +var enableTextSelection = __webpack_require__(44); + +var getMouseButton = __webpack_require__(47); + +var getTarget = __webpack_require__(28); + +var off = __webpack_require__(17); + +var on = __webpack_require__(14); + +var preventDefault = __webpack_require__(15); + +var extend = __webpack_require__(0); +/** + * @constructor + * @mixes CustomEvents + * @param {object} options - options for drag handler + * @param {number} [options.distance=10] - distance in pixels after mouse must move before dragging should start + * @param {HTMLElement} container - container element to bind drag events + * @ignore + */ + + +function Drag(options, container) { + on(container, 'mousedown', this._onMouseDown, this); + this.options = extend({ + distance: 10 + }, options); + /** + * @type {HTMLElement} + */ + + this.container = container; + /** + * @type {boolean} + */ + + this._isMoved = false; + /** + * dragging distance in pixel between mousedown and firing dragStart events + * @type {number} + */ + + this._distance = 0; + /** + * @type {boolean} + */ + + this._dragStartFired = false; + /** + * @type {object} + */ + + this._dragStartEventData = null; +} +/** + * Destroy method. + */ + + +Drag.prototype.destroy = function () { + off(this.container, 'mousedown', this._onMouseDown); + this.options = this.container = this._isMoved = this._distance = this._dragStartFired = this._dragStartEventData = null; +}; +/** + * Toggle events for mouse dragging. + * @param {boolean} toBind - bind events related with dragging when supplied "true" + */ + + +Drag.prototype._toggleDragEvent = function (toBind) { + var container = this.container; + + if (toBind) { + disableTextSelection(container); + on(window, 'dragstart', preventDefault); + on(global.document, { + mousemove: this._onMouseMove, + mouseup: this._onMouseUp + }, this); + } else { + enableTextSelection(container); + off(window, 'dragstart', preventDefault); + off(global.document, { + mousemove: this._onMouseMove, + mouseup: this._onMouseUp + }); + } +}; +/** + * Normalize mouse event object. + * @param {MouseEvent} mouseEvent - mouse event object. + * @returns {object} normalized mouse event data. + */ + + +Drag.prototype._getEventData = function (mouseEvent) { + return { + target: getTarget(mouseEvent), + originEvent: mouseEvent + }; +}; +/** + * MouseDown DOM event handler. + * @param {MouseEvent} mouseDownEvent MouseDown event object. + */ + + +Drag.prototype._onMouseDown = function (mouseDownEvent) { + // only primary button can start drag. + if (getMouseButton(mouseDownEvent) !== 0) { + return; + } + + this._distance = 0; + this._dragStartFired = false; + this._dragStartEventData = this._getEventData(mouseDownEvent); + + this._toggleDragEvent(true); +}; +/** + * MouseMove DOM event handler. + * @emits Drag#drag + * @emits Drag#dragStart + * @param {MouseEvent} mouseMoveEvent MouseMove event object. + */ + + +Drag.prototype._onMouseMove = function (mouseMoveEvent) { + var distance = this.options.distance; // prevent automatic scrolling. + + preventDefault(mouseMoveEvent); + this._isMoved = true; + + if (this._distance < distance) { + this._distance += 1; + return; + } + + if (!this._dragStartFired) { + this._dragStartFired = true; + /** + * Drag starts events. cancelable. + * @event Drag#dragStart + * @type {object} + * @property {HTMLElement} target - target element in this event. + * @property {MouseEvent} originEvent - original mouse event object. + */ + + if (!this.invoke('dragStart', this._dragStartEventData)) { + this._toggleDragEvent(false); + + return; + } + } + /** + * Events while dragging. + * @event Drag#drag + * @type {object} + * @property {HTMLElement} target - target element in this event. + * @property {MouseEvent} originEvent - original mouse event object. + */ + + + this.fire('drag', this._getEventData(mouseMoveEvent)); +}; +/** + * MouseUp DOM event handler. + * @param {MouseEvent} mouseUpEvent MouseUp event object. + * @emits Drag#dragEnd + * @emits Drag#click + */ + + +Drag.prototype._onMouseUp = function (mouseUpEvent) { + this._toggleDragEvent(false); // emit "click" event when not emitted drag event between mousedown and mouseup. + + + if (this._isMoved) { + this._isMoved = false; + /** + * Drag end events. + * @event Drag#dragEnd + * @type {MouseEvent} + * @property {HTMLElement} target - target element in this event. + * @property {MouseEvent} originEvent - original mouse event object. + */ + + this.fire('dragEnd', this._getEventData(mouseUpEvent)); + return; + } + /** + * Click events. + * @event Drag#click + * @type {MouseEvent} + * @property {HTMLElement} target - target element in this event. + * @property {MouseEvent} originEvent - original mouse event object. + */ + + + this.fire('click', this._getEventData(mouseUpEvent)); +}; + +CustomEvents.mixin(Drag); +module.exports = Drag; +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(25))) + +/***/ }), +/* 25 */ +/***/ (function(module, exports) { + +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || new Function("return this")(); +} catch (e) { + // This works if the window reference is available + if (typeof window === "object") g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; + + +/***/ }), +/* 26 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Get event collection for specific HTML element + * @author NHN FE Development Lab + */ + + + +var EVENT_KEY = '_feEventKey'; + +/** + * Get event collection for specific HTML element + * @param {HTMLElement} element - HTML element + * @param {string} type - event type + * @returns {array} + * @private + */ +function safeEvent(element, type) { + var events = element[EVENT_KEY]; + var handlers; + + if (!events) { + events = element[EVENT_KEY] = {}; + } + + handlers = events[type]; + if (!handlers) { + handlers = events[type] = []; + } + + return handlers; +} + +module.exports = safeEvent; + + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check specific CSS style is available. + * @author NHN FE Development Lab + */ + + + +/** + * Check specific CSS style is available. + * @param {array} props property name to testing + * @returns {(string|boolean)} return true when property is available + * @private + */ +function testCSSProp(props) { + var style = document.documentElement.style; + var i, len; + + for (i = 0, len = props.length; i < len; i += 1) { + if (props[i] in style) { + return props[i]; + } + } + + return false; +} + +module.exports = testCSSProp; + + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Get a target element from an event object. + * @author NHN FE Development Lab + */ + + + +/** + * Get a target element from an event object. + * @param {Event} e - event object + * @returns {HTMLElement} - target element + * @memberof module:domEvent + */ +function getTarget(e) { + return e.target || e.srcElement; +} + +module.exports = getTarget; + + +/***/ }), +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Color palette view + * @author NHN. FE Development Team + */ + + +var CustomEvents = __webpack_require__(10); + +var getTarget = __webpack_require__(28); + +var off = __webpack_require__(17); + +var on = __webpack_require__(14); + +var hasClass = __webpack_require__(30); + +var extend = __webpack_require__(0); + +var inherit = __webpack_require__(18); + +var domUtil = __webpack_require__(9); + +var colorUtil = __webpack_require__(12); + +var View = __webpack_require__(8); + +var tmpl = __webpack_require__(51); +/** + * @constructor + * @extends {View} + * @mixes CustomEvents + * @param {object} options - options for color palette view + * @param {string[]} options.preset - color list + * @param {HTMLDivElement} container - container element + * @ignore + */ + + +function Palette(options, container) { + /** + * option object + * @type {object} + */ + this.options = extend({ + cssPrefix: 'tui-colorpicker-', + preset: ['#181818', '#282828', '#383838', '#585858', '#B8B8B8', '#D8D8D8', '#E8E8E8', '#F8F8F8', '#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'], + detailTxt: 'Detail' + }, options); + container = domUtil.appendHTMLElement('div', container, this.options.cssPrefix + 'palette-container'); + View.call(this, options, container); +} + +inherit(Palette, View); +/** + * Mouse click event handler + * @fires Palette#_selectColor + * @fires Palette#_toggleSlider + * @param {MouseEvent} clickEvent - mouse event object + */ + +Palette.prototype._onClick = function (clickEvent) { + var options = this.options; + var target = getTarget(clickEvent); + var eventData = {}; + + if (hasClass(target, options.cssPrefix + 'palette-button')) { + eventData.color = target.value; + /** + * @event Palette#_selectColor + * @type {object} + * @property {string} color - selected color value + */ + + this.fire('_selectColor', eventData); + return; + } + + if (hasClass(target, options.cssPrefix + 'palette-toggle-slider')) { + /** + * @event Palette#_toggleSlider + */ + this.fire('_toggleSlider'); + } +}; +/** + * Textbox change event handler + * @fires Palette#_selectColor + * @param {Event} changeEvent - change event object + */ + + +Palette.prototype._onChange = function (changeEvent) { + var options = this.options; + var target = getTarget(changeEvent); + var eventData = {}; + + if (hasClass(target, options.cssPrefix + 'palette-hex')) { + eventData.color = target.value; + /** + * @event Palette#_selectColor + * @type {object} + * @property {string} color - selected color value + */ + + this.fire('_selectColor', eventData); + } +}; +/** + * Invoke before destory + * @override + */ + + +Palette.prototype._beforeDestroy = function () { + this._toggleEvent(false); +}; +/** + * Toggle view DOM events + * @param {boolean} [toBind=false] - true to bind event. + */ + + +Palette.prototype._toggleEvent = function (toBind) { + var options = this.options; + var container = this.container; + var handleEvent = toBind ? on : off; + var hexTextBox; + handleEvent(container, 'click', this._onClick, this); + hexTextBox = container.querySelector('.' + options.cssPrefix + 'palette-hex', container); + + if (hexTextBox) { + handleEvent(hexTextBox, 'change', this._onChange, this); + } +}; +/** + * Render palette + * @override + */ + + +Palette.prototype.render = function (color) { + var options = this.options; + var html = ''; + + this._toggleEvent(false); + + html = tmpl({ + cssPrefix: options.cssPrefix, + preset: options.preset, + detailTxt: options.detailTxt, + color: color, + isValidRGB: colorUtil.isValidRGB, + getItemClass: function (itemColor) { + return !itemColor ? ' ' + options.cssPrefix + 'color-transparent' : ''; + }, + isSelected: function (itemColor) { + return itemColor === color ? ' ' + options.cssPrefix + 'selected' : ''; + } + }); + this.container.innerHTML = html; + + this._toggleEvent(true); +}; + +CustomEvents.mixin(Palette); +module.exports = Palette; + +/***/ }), +/* 30 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check element has specific css class + * @author NHN FE Development Lab + */ + + + +var inArray = __webpack_require__(5); +var getClass = __webpack_require__(23); + +/** + * Check element has specific css class + * @param {(HTMLElement|SVGElement)} element - target element + * @param {string} cssClass - css class + * @returns {boolean} + * @memberof module:domUtil + */ +function hasClass(element, cssClass) { + var origin; + + if (element.classList) { + return element.classList.contains(cssClass); + } + + origin = getClass(element).split(/\s+/); + + return inArray(cssClass, origin) > -1; +} + +module.exports = hasClass; + + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Slider view + * @author NHN. FE Development Team + */ + + +var CustomEvents = __webpack_require__(10); + +var getMousePosition = __webpack_require__(53); + +var closest = __webpack_require__(54); + +var hasClass = __webpack_require__(30); + +var extend = __webpack_require__(0); + +var inherit = __webpack_require__(18); + +var domUtil = __webpack_require__(9); + +var svgvml = __webpack_require__(32); + +var colorUtil = __webpack_require__(12); + +var View = __webpack_require__(8); + +var Drag = __webpack_require__(24); + +var tmpl = __webpack_require__(57); // Limitation position of point element inside of colorslider and hue bar +// Minimum value can to be negative because that using color point of handle element is center point. not left, top point. + + +var COLORSLIDER_POS_LIMIT_RANGE = [-7, 112]; +var HUEBAR_POS_LIMIT_RANGE = [-3, 115]; +var HUE_WHEEL_MAX = 359.99; +/** + * @constructor + * @extends {View} + * @mixes CustomEvents + * @param {object} options - options for view + * @param {string} options.cssPrefix - design css prefix + * @param {HTMLElement} container - container element + * @ignore + */ + +function Slider(options, container) { + container = domUtil.appendHTMLElement('div', container, options.cssPrefix + 'slider-container'); + container.style.display = 'none'; + View.call(this, options, container); + /** + * @type {object} + */ + + this.options = extend({ + color: '#f8f8f8', + cssPrefix: 'tui-colorpicker-' + }, options); + /** + * Cache immutable data in click, drag events. + * + * (i.e. is event related with colorslider? or huebar?) + * @type {object} + * @property {boolean} isColorSlider + * @property {number[]} containerSize + */ + + this._dragDataCache = {}; + /** + * Color slider handle element + * @type {SVG|VML} + */ + + this.sliderHandleElement = null; + /** + * hue bar handle element + * @type {SVG|VML} + */ + + this.huebarHandleElement = null; + /** + * Element that render base color in colorslider part + * @type {SVG|VML} + */ + + this.baseColorElement = null; + /** + * @type {Drag} + */ + + this.drag = new Drag({ + distance: 0 + }, container); // bind drag events + + this.drag.on({ + dragStart: this._onDragStart, + drag: this._onDrag, + dragEnd: this._onDragEnd, + click: this._onClick + }, this); +} + +inherit(Slider, View); +/** + * @override + */ + +Slider.prototype._beforeDestroy = function () { + this.drag.off(); + this.drag = this.options = this._dragDataCache = this.sliderHandleElement = this.huebarHandleElement = this.baseColorElement = null; +}; +/** + * Toggle slider view + * @param {boolean} onOff - set true then reveal slider view + */ + + +Slider.prototype.toggle = function (onOff) { + this.container.style.display = !!onOff ? 'block' : 'none'; +}; +/** + * Get slider display status + * @returns {boolean} return true when slider is visible + */ + + +Slider.prototype.isVisible = function () { + return this.container.style.display === 'block'; +}; +/** + * Render slider view + * @override + * @param {string} colorStr - hex string color from parent view (Layout) + */ + + +Slider.prototype.render = function (colorStr) { + var container = this.container; + var options = this.options; + var html = tmpl.layout; + var rgb, hsv; + + if (!colorUtil.isValidRGB(colorStr)) { + return; + } + + html = html.replace(/{{slider}}/, tmpl.slider); + html = html.replace(/{{huebar}}/, tmpl.huebar); + html = html.replace(/{{cssPrefix}}/g, options.cssPrefix); + html = html.replace(/{{id}}/g, options.id); + this.container.innerHTML = html; + this.sliderHandleElement = container.querySelector('.' + options.cssPrefix + 'slider-handle'); + this.huebarHandleElement = container.querySelector('.' + options.cssPrefix + 'huebar-handle'); + this.baseColorElement = container.querySelector('.' + options.cssPrefix + 'slider-basecolor'); + rgb = colorUtil.hexToRGB(colorStr); + hsv = colorUtil.rgbToHSV.apply(null, rgb); + this.moveHue(hsv[0], true); + this.moveSaturationAndValue(hsv[1], hsv[2], true); +}; +/** + * Move colorslider by newLeft(X), newTop(Y) value + * @private + * @param {number} newLeft - left pixel value to move handle + * @param {number} newTop - top pixel value to move handle + * @param {boolean} [silent=false] - set true then not fire custom event + */ + + +Slider.prototype._moveColorSliderHandle = function (newLeft, newTop, silent) { + var handle = this.sliderHandleElement; + var handleColor; // Check position limitation. + + newTop = Math.max(COLORSLIDER_POS_LIMIT_RANGE[0], newTop); + newTop = Math.min(COLORSLIDER_POS_LIMIT_RANGE[1], newTop); + newLeft = Math.max(COLORSLIDER_POS_LIMIT_RANGE[0], newLeft); + newLeft = Math.min(COLORSLIDER_POS_LIMIT_RANGE[1], newLeft); + svgvml.setTranslateXY(handle, newLeft, newTop); + handleColor = newTop > 50 ? 'white' : 'black'; + svgvml.setStrokeColor(handle, handleColor); + + if (!silent) { + this.fire('_selectColor', { + color: colorUtil.rgbToHEX.apply(null, this.getRGB()) + }); + } +}; +/** + * Move colorslider by supplied saturation and values. + * + * The movement of color slider handle follow HSV cylinder model. {@link https://en.wikipedia.org/wiki/HSL_and_HSV} + * @param {number} saturation - the percent of saturation (0% ~ 100%) + * @param {number} value - the percent of saturation (0% ~ 100%) + * @param {boolean} [silent=false] - set true then not fire custom event + */ + + +Slider.prototype.moveSaturationAndValue = function (saturation, value, silent) { + var absMin, maxValue, newLeft, newTop; + saturation = saturation || 0; + value = value || 0; + absMin = Math.abs(COLORSLIDER_POS_LIMIT_RANGE[0]); + maxValue = COLORSLIDER_POS_LIMIT_RANGE[1]; // subtract absMin value because current color position is not left, top of handle element. + // The saturation. from left 0 to right 100 + + newLeft = saturation * maxValue / 100 - absMin; // The Value. from top 100 to bottom 0. that why newTop subtract by maxValue. + + newTop = maxValue - value * maxValue / 100 - absMin; + + this._moveColorSliderHandle(newLeft, newTop, silent); +}; +/** + * Move color slider handle to supplied position + * + * The number of X, Y must be related value from color slider container + * @private + * @param {number} x - the pixel value to move handle + * @param {number} y - the pixel value to move handle + */ + + +Slider.prototype._moveColorSliderByPosition = function (x, y) { + var offset = COLORSLIDER_POS_LIMIT_RANGE[0]; + + this._moveColorSliderHandle(x + offset, y + offset); +}; +/** + * Get saturation and value value. + * @returns {number[]} saturation and value + */ + + +Slider.prototype.getSaturationAndValue = function () { + var absMin = Math.abs(COLORSLIDER_POS_LIMIT_RANGE[0]); + var maxValue = absMin + COLORSLIDER_POS_LIMIT_RANGE[1]; + var position = svgvml.getTranslateXY(this.sliderHandleElement); + var saturation, value; + saturation = (position[1] + absMin) / maxValue * 100; // The value of HSV color model is inverted. top 100 ~ bottom 0. so subtract by 100 + + value = 100 - (position[0] + absMin) / maxValue * 100; + return [saturation, value]; +}; +/** + * Move hue handle supplied pixel value + * @private + * @param {number} newTop - pixel to move hue handle + * @param {boolean} [silent=false] - set true then not fire custom event + */ + + +Slider.prototype._moveHueHandle = function (newTop, silent) { + var hueHandleElement = this.huebarHandleElement; + var baseColorElement = this.baseColorElement; + var newGradientColor, hexStr; + newTop = Math.max(HUEBAR_POS_LIMIT_RANGE[0], newTop); + newTop = Math.min(HUEBAR_POS_LIMIT_RANGE[1], newTop); + svgvml.setTranslateY(hueHandleElement, newTop); + newGradientColor = colorUtil.hsvToRGB(this.getHue(), 100, 100); + hexStr = colorUtil.rgbToHEX.apply(null, newGradientColor); + svgvml.setGradientColorStop(baseColorElement, hexStr); + + if (!silent) { + this.fire('_selectColor', { + color: colorUtil.rgbToHEX.apply(null, this.getRGB()) + }); + } +}; +/** + * Move hue bar handle by supplied degree + * @param {number} degree - (0 ~ 359.9 degree) + * @param {boolean} [silent=false] - set true then not fire custom event + */ + + +Slider.prototype.moveHue = function (degree, silent) { + var newTop = 0; + var absMin, maxValue; + absMin = Math.abs(HUEBAR_POS_LIMIT_RANGE[0]); + maxValue = absMin + HUEBAR_POS_LIMIT_RANGE[1]; + degree = degree || 0; + newTop = maxValue * degree / HUE_WHEEL_MAX - absMin; + + this._moveHueHandle(newTop, silent); +}; +/** + * Move hue bar handle by supplied percent + * @private + * @param {number} y - pixel value to move hue handle + */ + + +Slider.prototype._moveHueByPosition = function (y) { + var offset = HUEBAR_POS_LIMIT_RANGE[0]; + + this._moveHueHandle(y + offset); +}; +/** + * Get huebar handle position by color degree + * @returns {number} degree (0 ~ 359.9 degree) + */ + + +Slider.prototype.getHue = function () { + var handle = this.huebarHandleElement; + var position = svgvml.getTranslateXY(handle); + var absMin, maxValue; + absMin = Math.abs(HUEBAR_POS_LIMIT_RANGE[0]); + maxValue = absMin + HUEBAR_POS_LIMIT_RANGE[1]; // maxValue : 359.99 = pos.y : x + + return (position[0] + absMin) * HUE_WHEEL_MAX / maxValue; +}; +/** + * Get HSV value from slider + * @returns {number[]} hsv values + */ + + +Slider.prototype.getHSV = function () { + var sv = this.getSaturationAndValue(); + var h = this.getHue(); + return [h].concat(sv); +}; +/** + * Get RGB value from slider + * @returns {number[]} RGB value + */ + + +Slider.prototype.getRGB = function () { + return colorUtil.hsvToRGB.apply(null, this.getHSV()); +}; +/********** + * Drag event handler + **********/ + +/** + * Cache immutable data when dragging or click view + * @param {object} event - Click, DragStart event. + * @returns {object} cached data. + */ + + +Slider.prototype._prepareColorSliderForMouseEvent = function (event) { + var options = this.options; + var sliderPart = closest(event.target, '.' + options.cssPrefix + 'slider-part'); + var cache; + cache = this._dragDataCache = { + isColorSlider: hasClass(sliderPart, options.cssPrefix + 'slider-left'), + parentElement: sliderPart + }; + return cache; +}; +/** + * Click event handler + * @param {object} clickEvent - Click event from Drag module + */ + + +Slider.prototype._onClick = function (clickEvent) { + var cache = this._prepareColorSliderForMouseEvent(clickEvent); + + var mousePos = getMousePosition(clickEvent.originEvent, cache.parentElement); + + if (cache.isColorSlider) { + this._moveColorSliderByPosition(mousePos[0], mousePos[1]); + } else { + this._moveHueByPosition(mousePos[1]); + } + + this._dragDataCache = null; +}; +/** + * DragStart event handler + * @param {object} dragStartEvent - dragStart event data from Drag#dragStart + */ + + +Slider.prototype._onDragStart = function (dragStartEvent) { + this._prepareColorSliderForMouseEvent(dragStartEvent); +}; +/** + * Drag event handler + * @param {Drag#drag} dragEvent - drag event data + */ + + +Slider.prototype._onDrag = function (dragEvent) { + var cache = this._dragDataCache; + var mousePos = getMousePosition(dragEvent.originEvent, cache.parentElement); + + if (cache.isColorSlider) { + this._moveColorSliderByPosition(mousePos[0], mousePos[1]); + } else { + this._moveHueByPosition(mousePos[1]); + } +}; +/** + * Drag#dragEnd event handler + */ + + +Slider.prototype._onDragEnd = function () { + this._dragDataCache = null; +}; + +CustomEvents.mixin(Slider); +module.exports = Slider; + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview module for manipulate SVG or VML object + * @author NHN. FE Development Team + */ + + +var isOldBrowser = __webpack_require__(4).isOldBrowser; + +var PARSE_TRANSLATE_NUM_REGEX = /[\.\-0-9]+/g; +var SVG_HUE_HANDLE_RIGHT_POS = -6; +/* istanbul ignore next */ + +var svgvml = { + /** + * Get translate transform value + * @param {SVG|VML} obj - svg or vml object that want to know translate x, y + * @returns {number[]} translated coordinates [x, y] + */ + getTranslateXY: function (obj) { + var temp; + + if (isOldBrowser) { + temp = obj.style; + return [parseFloat(temp.top), parseFloat(temp.left)]; + } + + temp = obj.getAttribute('transform'); + + if (!temp) { + return [0, 0]; + } + + temp = temp.match(PARSE_TRANSLATE_NUM_REGEX); // need caution for difference of VML, SVG coordinates system. + // translate command need X coords in first parameter. but VML is use CSS coordinate system(top, left) + + return [parseFloat(temp[1]), parseFloat(temp[0])]; + }, + + /** + * Set translate transform value + * @param {SVG|VML} obj - SVG or VML object to setting translate transform. + * @param {number} x - translate X value + * @param {number} y - translate Y value + */ + setTranslateXY: function (obj, x, y) { + if (isOldBrowser) { + obj.style.left = x + 'px'; + obj.style.top = y + 'px'; + } else { + obj.setAttribute('transform', 'translate(' + x + ',' + y + ')'); + } + }, + + /** + * Set translate only Y value + * @param {SVG|VML} obj - SVG or VML object to setting translate transform. + * @param {number} y - translate Y value + */ + setTranslateY: function (obj, y) { + if (isOldBrowser) { + obj.style.top = y + 'px'; + } else { + obj.setAttribute('transform', 'translate(' + SVG_HUE_HANDLE_RIGHT_POS + ',' + y + ')'); + } + }, + + /** + * Set stroke color to SVG or VML object + * @param {SVG|VML} obj - SVG or VML object to setting stroke color + * @param {string} colorStr - color string + */ + setStrokeColor: function (obj, colorStr) { + if (isOldBrowser) { + obj.strokecolor = colorStr; + } else { + obj.setAttribute('stroke', colorStr); + } + }, + + /** + * Set gradient stop color to SVG, VML object. + * @param {SVG|VML} obj - SVG, VML object to applying gradient stop color + * @param {string} colorStr - color string + */ + setGradientColorStop: function (obj, colorStr) { + if (isOldBrowser) { + obj.color = colorStr; + } else { + obj.setAttribute('stop-color', colorStr); + } + } +}; +module.exports = svgvml; + +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +__webpack_require__(34); +module.exports = __webpack_require__(35); + + +/***/ }), +/* 34 */ +/***/ (function(module, exports, __webpack_require__) { + +// extracted by mini-css-extract-plugin + +/***/ }), +/* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var Collection = __webpack_require__(19); + +var View = __webpack_require__(8); + +var Drag = __webpack_require__(24); + +var create = __webpack_require__(48); + +var Palette = __webpack_require__(29); + +var Slider = __webpack_require__(31); + +var colorUtil = __webpack_require__(12); + +var svgvml = __webpack_require__(32); + +var colorPicker = { + Collection: Collection, + View: View, + Drag: Drag, + create: create, + Palette: Palette, + Slider: Slider, + colorutil: colorUtil, + svgvml: svgvml +}; +module.exports = colorPicker; + +/***/ }), +/* 36 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is null or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is null or not. + * If the given variable(arguments[0]) is null, returns true. + * @param {*} obj - Target for checking + * @returns {boolean} Is null? + * @memberof module:type + */ +function isNull(obj) { + return obj === null; +} + +module.exports = isNull; + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Send hostname on DOMContentLoaded. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(3); +var imagePing = __webpack_require__(38); + +var ms7days = 7 * 24 * 60 * 60 * 1000; + +/** + * Check if the date has passed 7 days + * @param {number} date - milliseconds + * @returns {boolean} + * @private + */ +function isExpired(date) { + var now = new Date().getTime(); + + return now - date > ms7days; +} + +/** + * Send hostname on DOMContentLoaded. + * To prevent hostname set tui.usageStatistics to false. + * @param {string} appName - application name + * @param {string} trackingId - GA tracking ID + * @ignore + */ +function sendHostname(appName, trackingId) { + var url = 'https://www.google-analytics.com/collect'; + var hostname = location.hostname; + var hitType = 'event'; + var eventCategory = 'use'; + var applicationKeyForStorage = 'TOAST UI ' + appName + ' for ' + hostname + ': Statistics'; + var date = window.localStorage.getItem(applicationKeyForStorage); + + // skip if the flag is defined and is set to false explicitly + if (!isUndefined(window.tui) && window.tui.usageStatistics === false) { + return; + } + + // skip if not pass seven days old + if (date && !isExpired(date)) { + return; + } + + window.localStorage.setItem(applicationKeyForStorage, new Date().getTime()); + + setTimeout(function() { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + imagePing(url, { + v: 1, + t: hitType, + tid: trackingId, + cid: hostname, + dp: hostname, + dh: appName, + el: appName, + ec: eventCategory + }); + } + }, 1000); +} + +module.exports = sendHostname; + + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Request image ping. + * @author NHN FE Development Lab + */ + + + +var forEachOwnProperties = __webpack_require__(7); + +/** + * @module request + */ + +/** + * Request image ping. + * @param {String} url url for ping request + * @param {Object} trackingInfo infos for make query string + * @returns {HTMLElement} + * @memberof module:request + * @example + * var imagePing = require('tui-code-snippet/request/imagePing'); // node, commonjs + * + * imagePing('https://www.google-analytics.com/collect', { + * v: 1, + * t: 'event', + * tid: 'trackingid', + * cid: 'cid', + * dp: 'dp', + * dh: 'dh' + * }); + */ +function imagePing(url, trackingInfo) { + var trackingElement = document.createElement('img'); + var queryString = ''; + forEachOwnProperties(trackingInfo, function(value, key) { + queryString += '&' + key + '=' + value; + }); + queryString = queryString.substring(1); + + trackingElement.src = url + '?' + queryString; + + trackingElement.style.display = 'none'; + document.body.appendChild(trackingElement); + document.body.removeChild(trackingElement); + + return trackingElement; +} + +module.exports = imagePing; + + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Add css class to element + * @author NHN FE Development Lab + */ + + + +var forEach = __webpack_require__(2); +var inArray = __webpack_require__(5); +var getClass = __webpack_require__(23); +var setClassName = __webpack_require__(40); + +/** + * domUtil module + * @module domUtil + */ + +/** + * Add css class to element + * @param {(HTMLElement|SVGElement)} element - target element + * @param {...string} cssClass - css classes to add + * @memberof module:domUtil + */ +function addClass(element) { + var cssClass = Array.prototype.slice.call(arguments, 1); + var classList = element.classList; + var newClass = []; + var origin; + + if (classList) { + forEach(cssClass, function(name) { + element.classList.add(name); + }); + + return; + } + + origin = getClass(element); + + if (origin) { + cssClass = [].concat(origin.split(/\s+/), cssClass); + } + + forEach(cssClass, function(cls) { + if (inArray(cls, newClass) < 0) { + newClass.push(cls); + } + }); + + setClassName(element, newClass); +} + +module.exports = addClass; + + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Set className value + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(1); +var isUndefined = __webpack_require__(3); + +/** + * Set className value + * @param {(HTMLElement|SVGElement)} element - target element + * @param {(string|string[])} cssClass - class names + * @private + */ +function setClassName(element, cssClass) { + cssClass = isArray(cssClass) ? cssClass.join(' ') : cssClass; + + cssClass = cssClass.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + + if (isUndefined(element.className.baseVal)) { + element.className = cssClass; + + return; + } + + element.className.baseVal = cssClass; +} + +module.exports = setClassName; + + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is a number or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is a number or not. + * If the given variable is a number, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is number? + * @memberof module:type + */ +function isNumber(obj) { + return typeof obj === 'number' || obj instanceof Number; +} + +module.exports = isNumber; + + +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Disable browser's text selection behaviors. + * @author NHN FE Development Lab + */ + + + +var on = __webpack_require__(14); +var preventDefault = __webpack_require__(15); +var setData = __webpack_require__(43); +var testCSSProp = __webpack_require__(27); + +var SUPPORT_SELECTSTART = 'onselectstart' in document; +var KEY_PREVIOUS_USER_SELECT = 'prevUserSelect'; +var userSelectProperty = testCSSProp([ + 'userSelect', + 'WebkitUserSelect', + 'OUserSelect', + 'MozUserSelect', + 'msUserSelect' +]); + +/** + * Disable browser's text selection behaviors. + * @param {HTMLElement} [el] - target element. if not supplied, use `document` + * @memberof module:domUtil + */ +function disableTextSelection(el) { + if (!el) { + el = document; + } + + if (SUPPORT_SELECTSTART) { + on(el, 'selectstart', preventDefault); + } else { + el = (el === document) ? document.documentElement : el; + setData(el, KEY_PREVIOUS_USER_SELECT, el.style[userSelectProperty]); + el.style[userSelectProperty] = 'none'; + } +} + +module.exports = disableTextSelection; + + +/***/ }), +/* 43 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Set data attribute to target element + * @author NHN FE Development Lab + */ + + + +var convertToKebabCase = __webpack_require__(16); + +/** + * Set data attribute to target element + * @param {HTMLElement} element - element to set data attribute + * @param {string} key - key + * @param {string} value - value + * @memberof module:domUtil + */ +function setData(element, key, value) { + if (element.dataset) { + element.dataset[key] = value; + + return; + } + + element.setAttribute('data-' + convertToKebabCase(key), value); +} + +module.exports = setData; + + +/***/ }), +/* 44 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Transform the Array-like object to Array. + * @author NHN FE Development Lab + */ + + + +var off = __webpack_require__(17); +var preventDefault = __webpack_require__(15); +var getData = __webpack_require__(45); +var removeData = __webpack_require__(46); +var testCSSProp = __webpack_require__(27); + +var SUPPORT_SELECTSTART = 'onselectstart' in document; +var KEY_PREVIOUS_USER_SELECT = 'prevUserSelect'; +var userSelectProperty = testCSSProp([ + 'userSelect', + 'WebkitUserSelect', + 'OUserSelect', + 'MozUserSelect', + 'msUserSelect' +]); + +/** + * Enable browser's text selection behaviors. + * @param {HTMLElement} [el] - target element. if not supplied, use `document` + * @memberof module:domUtil + */ +function enableTextSelection(el) { + if (!el) { + el = document; + } + + if (SUPPORT_SELECTSTART) { + off(el, 'selectstart', preventDefault); + } else { + el = (el === document) ? document.documentElement : el; + el.style[userSelectProperty] = getData(el, KEY_PREVIOUS_USER_SELECT) || 'auto'; + removeData(el, KEY_PREVIOUS_USER_SELECT); + } +} + +module.exports = enableTextSelection; + + +/***/ }), +/* 45 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Get data value from data-attribute + * @author NHN FE Development Lab + */ + + + +var convertToKebabCase = __webpack_require__(16); + +/** + * Get data value from data-attribute + * @param {HTMLElement} element - target element + * @param {string} key - key + * @returns {string} value + * @memberof module:domUtil + */ +function getData(element, key) { + if (element.dataset) { + return element.dataset[key]; + } + + return element.getAttribute('data-' + convertToKebabCase(key)); +} + +module.exports = getData; + + +/***/ }), +/* 46 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Remove data property + * @author NHN FE Development Lab + */ + + + +var convertToKebabCase = __webpack_require__(16); + +/** + * Remove data property + * @param {HTMLElement} element - target element + * @param {string} key - key + * @memberof module:domUtil + */ +function removeData(element, key) { + if (element.dataset) { + delete element.dataset[key]; + + return; + } + + element.removeAttribute('data-' + convertToKebabCase(key)); +} + +module.exports = removeData; + + +/***/ }), +/* 47 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Normalize mouse event's button attributes. + * @author NHN FE Development Lab + */ + + + +var browser = __webpack_require__(22); +var inArray = __webpack_require__(5); + +var primaryButton = ['0', '1', '3', '5', '7']; +var secondaryButton = ['2', '6']; +var wheelButton = ['4']; + +/** + * @module domEvent + */ + +/** + * Normalize mouse event's button attributes. + * + * Can detect which button is clicked by this method. + * + * Meaning of return numbers + * + * - 0: primary mouse button + * - 1: wheel button or center button + * - 2: secondary mouse button + * @param {MouseEvent} mouseEvent - The mouse event object want to know. + * @returns {number} - The value of meaning which button is clicked? + * @memberof module:domEvent + */ +function getMouseButton(mouseEvent) { + if (browser.msie && browser.version <= 8) { + return getMouseButtonIE8AndEarlier(mouseEvent); + } + + return mouseEvent.button; +} + +/** + * Normalize return value of mouseEvent.button + * Make same to standard MouseEvent's button value + * @param {DispCEventObj} mouseEvent - mouse event object + * @returns {number|null} - id indicating which mouse button is clicked + * @private + */ +function getMouseButtonIE8AndEarlier(mouseEvent) { + var button = String(mouseEvent.button); + + if (inArray(button, primaryButton) > -1) { + return 0; + } + + if (inArray(button, secondaryButton) > -1) { + return 2; + } + + if (inArray(button, wheelButton) > -1) { + return 1; + } + + return null; +} + +module.exports = getMouseButton; + + +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview ColorPicker factory module + * @author NHN. FE Development Team + */ + + +var CustomEvents = __webpack_require__(10); + +var extend = __webpack_require__(0); + +var util = __webpack_require__(4); + +var colorUtil = __webpack_require__(12); + +var Layout = __webpack_require__(49); + +var Palette = __webpack_require__(29); + +var Slider = __webpack_require__(31); +/** + * Create an unique id for a color-picker instance. + * @private + */ + + +var currentId = 0; + +function generateId() { + currentId += 1; + return currentId; +} +/** + * @constructor + * @param {object} options - options for colorpicker component + * @param {HTMLDivElement} options.container - container element + * @param {string} [options.color='#ffffff'] - default selected color + * @param {string[]} [options.preset] - color preset for palette (use base16 palette if not supplied) + * @param {string} [options.cssPrefix='tui-colorpicker-'] - css prefix text for each child elements + * @param {string} [options.detailTxt='Detail'] - text for detail button. + * @param {boolean} [options.usageStatistics=true] - Let us know the hostname. If you don't want to send the hostname, please set to false. + * @example + * var colorPicker = tui.colorPicker; // or require('tui-color-picker') + * + * var instance = colorPicker.create({ + * container: document.getElementById('color-picker') + * }); + */ + + +function ColorPicker(options) { + var layout; + + if (!(this instanceof ColorPicker)) { + return new ColorPicker(options); + } + /** + * Option object + * @type {object} + * @private + */ + + + options = this.options = extend({ + container: null, + color: '#f8f8f8', + preset: ['#181818', '#282828', '#383838', '#585858', '#b8b8b8', '#d8d8d8', '#e8e8e8', '#f8f8f8', '#ab4642', '#dc9656', '#f7ca88', '#a1b56c', '#86c1b9', '#7cafc2', '#ba8baf', '#a16946'], + cssPrefix: 'tui-colorpicker-', + detailTxt: 'Detail', + id: generateId(), + usageStatistics: true + }, options); + + if (!options.container) { + throw new Error('ColorPicker(): need container option.'); + } + /********** + * Create layout view + **********/ + + /** + * @type {Layout} + * @private + */ + + + layout = this.layout = new Layout(options, options.container); + /********** + * Create palette view + **********/ + + this.palette = new Palette(options, layout.container); + this.palette.on({ + _selectColor: this._onSelectColorInPalette, + _toggleSlider: this._onToggleSlider + }, this); + /********** + * Create slider view + **********/ + + this.slider = new Slider(options, layout.container); + this.slider.on('_selectColor', this._onSelectColorInSlider, this); + /********** + * Add child views + **********/ + + layout.addChild(this.palette); + layout.addChild(this.slider); + this.render(options.color); + + if (options.usageStatistics) { + util.sendHostName(); + } +} +/** + * Handler method for Palette#_selectColor event + * @private + * @fires ColorPicker#selectColor + * @param {object} selectColorEventData - event data + */ + + +ColorPicker.prototype._onSelectColorInPalette = function (selectColorEventData) { + var color = selectColorEventData.color; + var opt = this.options; + + if (!colorUtil.isValidRGB(color) && color !== '') { + this.render(); + return; + } + /** + * @event ColorPicker#selectColor + * @type {object} + * @property {string} color - selected color (hex string) + * @property {string} origin - flags for represent the source of event fires. + */ + + + this.fire('selectColor', { + color: color, + origin: 'palette' + }); + + if (opt.color === color) { + return; + } + + opt.color = color; + this.render(color); +}; +/** + * Handler method for Palette#_toggleSlider event + * @private + */ + + +ColorPicker.prototype._onToggleSlider = function () { + this.slider.toggle(!this.slider.isVisible()); +}; +/** + * Handler method for Slider#_selectColor event + * @private + * @fires ColorPicker#selectColor + * @param {object} selectColorEventData - event data + */ + + +ColorPicker.prototype._onSelectColorInSlider = function (selectColorEventData) { + var color = selectColorEventData.color; + var opt = this.options; + /** + * @event ColorPicker#selectColor + * @type {object} + * @property {string} color - selected color (hex string) + * @property {string} origin - flags for represent the source of event fires. + * @ignore + */ + + this.fire('selectColor', { + color: color, + origin: 'slider' + }); + + if (opt.color === color) { + return; + } + + opt.color = color; + this.palette.render(color); +}; +/********** + * PUBLIC API + **********/ + +/** + * Set color to colorpicker instance.
+ * The string parameter must be hex color value + * @param {string} hexStr - hex formatted color string + * @example + * instance.setColor('#ffff00'); + */ + + +ColorPicker.prototype.setColor = function (hexStr) { + if (!colorUtil.isValidRGB(hexStr)) { + throw new Error('ColorPicker#setColor(): need valid hex string color value'); + } + + this.options.color = hexStr; + this.render(hexStr); +}; +/** + * Get hex color string of current selected color in colorpicker instance. + * @returns {string} hex string formatted color + * @example + * instance.setColor('#ffff00'); + * instance.getColor(); // '#ffff00'; + */ + + +ColorPicker.prototype.getColor = function () { + return this.options.color; +}; +/** + * Toggle colorpicker element. set true then reveal colorpicker view. + * @param {boolean} [isShow=false] - A flag to show + * @example + * instance.toggle(false); // hide + * instance.toggle(); // hide + * instance.toggle(true); // show + */ + + +ColorPicker.prototype.toggle = function (isShow) { + this.layout.container.style.display = !!isShow ? 'block' : 'none'; +}; +/** + * Render colorpicker + * @param {string} [color] - selected color + * @ignore + */ + + +ColorPicker.prototype.render = function (color) { + this.layout.render(color || this.options.color); +}; +/** + * Destroy colorpicker instance. + * @example + * instance.destroy(); // DOM-element is removed + */ + + +ColorPicker.prototype.destroy = function () { + this.layout.destroy(); + this.options.container.innerHTML = ''; + this.layout = this.slider = this.palette = this.options = null; +}; + +CustomEvents.mixin(ColorPicker); +module.exports = ColorPicker; + +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview ColorPicker layout module + * @author NHN. FE Development Team + */ + + +var extend = __webpack_require__(0); + +var inherit = __webpack_require__(18); + +var domUtil = __webpack_require__(9); + +var View = __webpack_require__(8); +/** + * @constructor + * @extends {View} + * @param {object} options - option object + * @param {string} options.cssPrefix - css prefix for each child elements + * @param {HTMLDivElement} container - container + * @ignore + */ + + +function Layout(options, container) { + /** + * option object + * @type {object} + */ + this.options = extend({ + cssPrefix: 'tui-colorpicker-' + }, options); + container = domUtil.appendHTMLElement('div', container, this.options.cssPrefix + 'container'); + View.call(this, options, container); + this.render(); +} + +inherit(Layout, View); +/** + * @override + * @param {string} [color] - selected color + */ + +Layout.prototype.render = function (color) { + this.recursive(function (view) { + view.render(color); + }, true); +}; + +module.exports = Layout; + +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Create a new object with the specified prototype object and properties. + * @author NHN FE Development Lab + */ + + + +/** + * @module inheritance + */ + +/** + * Create a new object with the specified prototype object and properties. + * @param {Object} obj This object will be a prototype of the newly-created object. + * @returns {Object} + * @memberof module:inheritance + */ +function createObject(obj) { + function F() {} // eslint-disable-line require-jsdoc + F.prototype = obj; + + return new F(); +} + +module.exports = createObject; + + +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Palette view template + * @author NHN. FE Development Team + */ + + +var template = __webpack_require__(52); + +module.exports = function (context) { + var item = ['
  • '].join(''); + var layout = ['
      ', '{{each preset}}', item, '{{/each}}', '
    ', '
    ', '', '', '{{color}}', '
    '].join('\n'); + return template(layout, context); +}; + +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Convert text by binding expressions with context. + * @author NHN FE Development Lab + */ + + + +var inArray = __webpack_require__(5); +var forEach = __webpack_require__(2); +var isArray = __webpack_require__(1); +var isString = __webpack_require__(11); +var extend = __webpack_require__(0); + +// IE8 does not support capture groups. +var EXPRESSION_REGEXP = /{{\s?|\s?}}/g; +var BRACKET_NOTATION_REGEXP = /^[a-zA-Z0-9_@]+\[[a-zA-Z0-9_@"']+\]$/; +var BRACKET_REGEXP = /\[\s?|\s?\]/; +var DOT_NOTATION_REGEXP = /^[a-zA-Z_]+\.[a-zA-Z_]+$/; +var DOT_REGEXP = /\./; +var STRING_NOTATION_REGEXP = /^["']\w+["']$/; +var STRING_REGEXP = /"|'/g; +var NUMBER_REGEXP = /^-?\d+\.?\d*$/; + +var EXPRESSION_INTERVAL = 2; + +var BLOCK_HELPERS = { + 'if': handleIf, + 'each': handleEach, + 'with': handleWith +}; + +var isValidSplit = 'a'.split(/a/).length === 3; + +/** + * Split by RegExp. (Polyfill for IE8) + * @param {string} text - text to be splitted\ + * @param {RegExp} regexp - regular expression + * @returns {Array.} + */ +var splitByRegExp = (function() { + if (isValidSplit) { + return function(text, regexp) { + return text.split(regexp); + }; + } + + return function(text, regexp) { + var result = []; + var prevIndex = 0; + var match, index; + + if (!regexp.global) { + regexp = new RegExp(regexp, 'g'); + } + + match = regexp.exec(text); + while (match !== null) { + index = match.index; + result.push(text.slice(prevIndex, index)); + + prevIndex = index + match[0].length; + match = regexp.exec(text); + } + result.push(text.slice(prevIndex)); + + return result; + }; +})(); + +/** + * Find value in the context by an expression. + * @param {string} exp - an expression + * @param {object} context - context + * @returns {*} + * @private + */ +// eslint-disable-next-line complexity +function getValueFromContext(exp, context) { + var splitedExps; + var value = context[exp]; + + if (exp === 'true') { + value = true; + } else if (exp === 'false') { + value = false; + } else if (STRING_NOTATION_REGEXP.test(exp)) { + value = exp.replace(STRING_REGEXP, ''); + } else if (BRACKET_NOTATION_REGEXP.test(exp)) { + splitedExps = exp.split(BRACKET_REGEXP); + value = getValueFromContext(splitedExps[0], context)[getValueFromContext(splitedExps[1], context)]; + } else if (DOT_NOTATION_REGEXP.test(exp)) { + splitedExps = exp.split(DOT_REGEXP); + value = getValueFromContext(splitedExps[0], context)[splitedExps[1]]; + } else if (NUMBER_REGEXP.test(exp)) { + value = parseFloat(exp); + } + + return value; +} + +/** + * Extract elseif and else expressions. + * @param {Array.} ifExps - args of if expression + * @param {Array.} sourcesInsideBlock - sources inside if block + * @returns {object} - exps: expressions of if, elseif, and else / sourcesInsideIf: sources inside if, elseif, and else block. + * @private + */ +function extractElseif(ifExps, sourcesInsideBlock) { + var exps = [ifExps]; + var sourcesInsideIf = []; + var otherIfCount = 0; + var start = 0; + + // eslint-disable-next-line complexity + forEach(sourcesInsideBlock, function(source, index) { + if (source.indexOf('if') === 0) { + otherIfCount += 1; + } else if (source === '/if') { + otherIfCount -= 1; + } else if (!otherIfCount && (source.indexOf('elseif') === 0 || source === 'else')) { + exps.push(source === 'else' ? ['true'] : source.split(' ').slice(1)); + sourcesInsideIf.push(sourcesInsideBlock.slice(start, index)); + start = index + 1; + } + }); + + sourcesInsideIf.push(sourcesInsideBlock.slice(start)); + + return { + exps: exps, + sourcesInsideIf: sourcesInsideIf + }; +} + +/** + * Helper function for "if". + * @param {Array.} exps - array of expressions split by spaces + * @param {Array.} sourcesInsideBlock - array of sources inside the if block + * @param {object} context - context + * @returns {string} + * @private + */ +function handleIf(exps, sourcesInsideBlock, context) { + var analyzed = extractElseif(exps, sourcesInsideBlock); + var result = false; + var compiledSource = ''; + + forEach(analyzed.exps, function(exp, index) { + result = handleExpression(exp, context); + if (result) { + compiledSource = compile(analyzed.sourcesInsideIf[index], context); + } + + return !result; + }); + + return compiledSource; +} + +/** + * Helper function for "each". + * @param {Array.} exps - array of expressions split by spaces + * @param {Array.} sourcesInsideBlock - array of sources inside the each block + * @param {object} context - context + * @returns {string} + * @private + */ +function handleEach(exps, sourcesInsideBlock, context) { + var collection = handleExpression(exps, context); + var additionalKey = isArray(collection) ? '@index' : '@key'; + var additionalContext = {}; + var result = ''; + + forEach(collection, function(item, key) { + additionalContext[additionalKey] = key; + additionalContext['@this'] = item; + extend(context, additionalContext); + + result += compile(sourcesInsideBlock.slice(), context); + }); + + return result; +} + +/** + * Helper function for "with ... as" + * @param {Array.} exps - array of expressions split by spaces + * @param {Array.} sourcesInsideBlock - array of sources inside the with block + * @param {object} context - context + * @returns {string} + * @private + */ +function handleWith(exps, sourcesInsideBlock, context) { + var asIndex = inArray('as', exps); + var alias = exps[asIndex + 1]; + var result = handleExpression(exps.slice(0, asIndex), context); + + var additionalContext = {}; + additionalContext[alias] = result; + + return compile(sourcesInsideBlock, extend(context, additionalContext)) || ''; +} + +/** + * Extract sources inside block in place. + * @param {Array.} sources - array of sources + * @param {number} start - index of start block + * @param {number} end - index of end block + * @returns {Array.} + * @private + */ +function extractSourcesInsideBlock(sources, start, end) { + var sourcesInsideBlock = sources.splice(start + 1, end - start); + sourcesInsideBlock.pop(); + + return sourcesInsideBlock; +} + +/** + * Handle block helper function + * @param {string} helperKeyword - helper keyword (ex. if, each, with) + * @param {Array.} sourcesToEnd - array of sources after the starting block + * @param {object} context - context + * @returns {Array.} + * @private + */ +function handleBlockHelper(helperKeyword, sourcesToEnd, context) { + var executeBlockHelper = BLOCK_HELPERS[helperKeyword]; + var helperCount = 1; + var startBlockIndex = 0; + var endBlockIndex; + var index = startBlockIndex + EXPRESSION_INTERVAL; + var expression = sourcesToEnd[index]; + + while (helperCount && isString(expression)) { + if (expression.indexOf(helperKeyword) === 0) { + helperCount += 1; + } else if (expression.indexOf('/' + helperKeyword) === 0) { + helperCount -= 1; + endBlockIndex = index; + } + + index += EXPRESSION_INTERVAL; + expression = sourcesToEnd[index]; + } + + if (helperCount) { + throw Error(helperKeyword + ' needs {{/' + helperKeyword + '}} expression.'); + } + + sourcesToEnd[startBlockIndex] = executeBlockHelper( + sourcesToEnd[startBlockIndex].split(' ').slice(1), + extractSourcesInsideBlock(sourcesToEnd, startBlockIndex, endBlockIndex), + context + ); + + return sourcesToEnd; +} + +/** + * Helper function for "custom helper". + * If helper is not a function, return helper itself. + * @param {Array.} exps - array of expressions split by spaces (first element: helper) + * @param {object} context - context + * @returns {string} + * @private + */ +function handleExpression(exps, context) { + var result = getValueFromContext(exps[0], context); + + if (result instanceof Function) { + return executeFunction(result, exps.slice(1), context); + } + + return result; +} + +/** + * Execute a helper function. + * @param {Function} helper - helper function + * @param {Array.} argExps - expressions of arguments + * @param {object} context - context + * @returns {string} - result of executing the function with arguments + * @private + */ +function executeFunction(helper, argExps, context) { + var args = []; + forEach(argExps, function(exp) { + args.push(getValueFromContext(exp, context)); + }); + + return helper.apply(null, args); +} + +/** + * Get a result of compiling an expression with the context. + * @param {Array.} sources - array of sources split by regexp of expression. + * @param {object} context - context + * @returns {Array.} - array of sources that bind with its context + * @private + */ +function compile(sources, context) { + var index = 1; + var expression = sources[index]; + var exps, firstExp, result; + + while (isString(expression)) { + exps = expression.split(' '); + firstExp = exps[0]; + + if (BLOCK_HELPERS[firstExp]) { + result = handleBlockHelper(firstExp, sources.splice(index, sources.length - index), context); + sources = sources.concat(result); + } else { + sources[index] = handleExpression(exps, context); + } + + index += EXPRESSION_INTERVAL; + expression = sources[index]; + } + + return sources.join(''); +} + +/** + * Convert text by binding expressions with context. + *
    + * If expression exists in the context, it will be replaced. + * ex) '{{title}}' with context {title: 'Hello!'} is converted to 'Hello!'. + * An array or object can be accessed using bracket and dot notation. + * ex) '{{odds\[2\]}}' with context {odds: \[1, 3, 5\]} is converted to '5'. + * ex) '{{evens\[first\]}}' with context {evens: \[2, 4\], first: 0} is converted to '2'. + * ex) '{{project\["name"\]}}' and '{{project.name}}' with context {project: {name: 'CodeSnippet'}} is converted to 'CodeSnippet'. + *
    + * If replaced expression is a function, next expressions will be arguments of the function. + * ex) '{{add 1 2}}' with context {add: function(a, b) {return a + b;}} is converted to '3'. + *
    + * It has 3 predefined block helpers '{{helper ...}} ... {{/helper}}': 'if', 'each', 'with ... as ...'. + * 1) 'if' evaluates conditional statements. It can use with 'elseif' and 'else'. + * 2) 'each' iterates an array or object. It provides '@index'(array), '@key'(object), and '@this'(current element). + * 3) 'with ... as ...' provides an alias. + * @param {string} text - text with expressions + * @param {object} context - context + * @returns {string} - text that bind with its context + * @memberof module:domUtil + * @example + * var template = require('tui-code-snippet/domUtil/template'); + * + * var source = + * '

    ' + * + '{{if isValidNumber title}}' + * + '{{title}}th' + * + '{{elseif isValidDate title}}' + * + 'Date: {{title}}' + * + '{{/if}}' + * + '

    ' + * + '{{each list}}' + * + '{{with addOne @index as idx}}' + * + '

    {{idx}}: {{@this}}

    ' + * + '{{/with}}' + * + '{{/each}}'; + * + * var context = { + * isValidDate: function(text) { + * return /^\d{4}-(0|1)\d-(0|1|2|3)\d$/.test(text); + * }, + * isValidNumber: function(text) { + * return /^\d+$/.test(text); + * } + * title: '2019-11-25', + * list: ['Clean the room', 'Wash the dishes'], + * addOne: function(num) { + * return num + 1; + * } + * }; + * + * var result = template(source, context); + * console.log(result); //

    Date: 2019-11-25

    1: Clean the room

    2: Wash the dishes

    + */ +function template(text, context) { + return compile(splitByRegExp(text, EXPRESSION_REGEXP), context); +} + +module.exports = template; + + +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Get mouse position from mouse event + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(1); + +/** + * Get mouse position from mouse event + * + * If supplied relatveElement parameter then return relative position based on + * element + * @param {(MouseEvent|object|number[])} position - mouse position object + * @param {HTMLElement} relativeElement HTML element that calculate relative + * position + * @returns {number[]} mouse position + * @memberof module:domEvent + */ +function getMousePosition(position, relativeElement) { + var positionArray = isArray(position); + var clientX = positionArray ? position[0] : position.clientX; + var clientY = positionArray ? position[1] : position.clientY; + var rect; + + if (!relativeElement) { + return [clientX, clientY]; + } + + rect = relativeElement.getBoundingClientRect(); + + return [ + clientX - rect.left - relativeElement.clientLeft, + clientY - rect.top - relativeElement.clientTop + ]; +} + +module.exports = getMousePosition; + + +/***/ }), +/* 54 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Find parent element recursively + * @author NHN FE Development Lab + */ + + + +var matches = __webpack_require__(55); + +/** + * Find parent element recursively + * @param {HTMLElement} element - base element to start find + * @param {string} selector - selector string for find + * @returns {HTMLElement} - element finded or null + * @memberof module:domUtil + */ +function closest(element, selector) { + var parent = element.parentNode; + + if (matches(element, selector)) { + return element; + } + + while (parent && parent !== document) { + if (matches(parent, selector)) { + return parent; + } + + parent = parent.parentNode; + } + + return null; +} + +module.exports = closest; + + +/***/ }), +/* 55 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check element match selector + * @author NHN FE Development Lab + */ + + + +var inArray = __webpack_require__(5); +var toArray = __webpack_require__(56); + +var elProto = Element.prototype; +var matchSelector = elProto.matches || + elProto.webkitMatchesSelector || + elProto.mozMatchesSelector || + elProto.msMatchesSelector || + function(selector) { + var doc = this.document || this.ownerDocument; + + return inArray(this, toArray(doc.querySelectorAll(selector))) > -1; + }; + +/** + * Check element match selector + * @param {HTMLElement} element - element to check + * @param {string} selector - selector to check + * @returns {boolean} is selector matched to element? + * @memberof module:domUtil + */ +function matches(element, selector) { + return matchSelector.call(element, selector); +} + +module.exports = matches; + + +/***/ }), +/* 56 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Transform the Array-like object to Array. + * @author NHN FE Development Lab + */ + + + +var forEachArray = __webpack_require__(6); + +/** + * Transform the Array-like object to Array. + * In low IE (below 8), Array.prototype.slice.call is not perfect. So, try-catch statement is used. + * @param {*} arrayLike Array-like object + * @returns {Array} Array + * @memberof module:collection + * @example + * var toArray = require('tui-code-snippet/collection/toArray'); // node, commonjs + * + * var arrayLike = { + * 0: 'one', + * 1: 'two', + * 2: 'three', + * 3: 'four', + * length: 4 + * }; + * var result = toArray(arrayLike); + * + * alert(result instanceof Array); // true + * alert(result); // one,two,three,four + */ +function toArray(arrayLike) { + var arr; + try { + arr = Array.prototype.slice.call(arrayLike); + } catch (e) { + arr = []; + forEachArray(arrayLike, function(value) { + arr.push(value); + }); + } + + return arr; +} + +module.exports = toArray; + + +/***/ }), +/* 57 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(global) {/** + * @fileoverview Slider template + * @author NHN. FE Development Team + */ + + +var isOldBrowser = __webpack_require__(4).isOldBrowser; + +var layout = ['
    {{slider}}
    ', '
    {{huebar}}
    '].join('\n'); +var SVGSlider = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''].join('\n'); +var VMLSlider = ['
    ', '', '', '', '', '', '', '', '
    '].join('\n'); +var SVGHuebar = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', ''].join('\n'); +var VMLHuebar = ['
    ', '', '', '', '', '
    '].join('\n'); + +if (isOldBrowser) { + global.document.namespaces.add('v', 'urn:schemas-microsoft-com:vml'); +} + +module.exports = { + layout: layout, + slider: isOldBrowser ? VMLSlider : SVGSlider, + huebar: isOldBrowser ? VMLHuebar : SVGHuebar +}; +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(25))) + +/***/ }) +/******/ ]); +}); \ No newline at end of file diff --git a/core/vendor/filemanager/js/tui-image-editor.js b/core/vendor/filemanager/js/tui-image-editor.js new file mode 100644 index 00000000..687cfde2 --- /dev/null +++ b/core/vendor/filemanager/js/tui-image-editor.js @@ -0,0 +1,62237 @@ +/*! + * TOAST UI ImageEditor + * @version 3.15.3 + * @license MIT + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(require("tui-color-picker")); + else if(typeof define === 'function' && define.amd) + define(["tui-color-picker"], factory); + else if(typeof exports === 'object') + exports["tui"] = factory(require("tui-color-picker")); + else + root["tui"] = root["tui"] || {}, root["tui"]["ImageEditor"] = factory(root["tui"]["colorPicker"]); +})(self, function(__WEBPACK_EXTERNAL_MODULE__4858__) { +return /******/ (function() { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 2777: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +/* build: `node build.js modules=ALL exclude=gestures,accessors,erasing requirejs minifier=uglifyjs` */ +/*! Fabric.js Copyright 2008-2015, Printio (Juriy Zaytsev, Maxim Chernyak) */ + +var fabric = fabric || { version: '4.6.0' }; +if (true) { + exports.fabric = fabric; +} +/* _AMD_START_ */ +else {} +/* _AMD_END_ */ +if (typeof document !== 'undefined' && typeof window !== 'undefined') { + if (document instanceof (typeof HTMLDocument !== 'undefined' ? HTMLDocument : Document)) { + fabric.document = document; + } + else { + fabric.document = document.implementation.createHTMLDocument(''); + } + fabric.window = window; +} +else { + // assume we're running under node.js when document/window are not present + var jsdom = __webpack_require__(4960); + var virtualWindow = new jsdom.JSDOM( + decodeURIComponent('%3C!DOCTYPE%20html%3E%3Chtml%3E%3Chead%3E%3C%2Fhead%3E%3Cbody%3E%3C%2Fbody%3E%3C%2Fhtml%3E'), + { + features: { + FetchExternalResources: ['img'] + }, + resources: 'usable' + }).window; + fabric.document = virtualWindow.document; + fabric.jsdomImplForWrapper = __webpack_require__(6759).implForWrapper; + fabric.nodeCanvas = __webpack_require__(6272).Canvas; + fabric.window = virtualWindow; + DOMParser = fabric.window.DOMParser; +} + +/** + * True when in environment that supports touch events + * @type boolean + */ +fabric.isTouchSupported = 'ontouchstart' in fabric.window || 'ontouchstart' in fabric.document || + (fabric.window && fabric.window.navigator && fabric.window.navigator.maxTouchPoints > 0); + +/** + * True when in environment that's probably Node.js + * @type boolean + */ +fabric.isLikelyNode = typeof Buffer !== 'undefined' && + typeof window === 'undefined'; + +/* _FROM_SVG_START_ */ +/** + * Attributes parsed from all SVG elements + * @type array + */ +fabric.SHARED_ATTRIBUTES = [ + 'display', + 'transform', + 'fill', 'fill-opacity', 'fill-rule', + 'opacity', + 'stroke', 'stroke-dasharray', 'stroke-linecap', 'stroke-dashoffset', + 'stroke-linejoin', 'stroke-miterlimit', + 'stroke-opacity', 'stroke-width', + 'id', 'paint-order', 'vector-effect', + 'instantiated_by_use', 'clip-path', +]; +/* _FROM_SVG_END_ */ + +/** + * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. + */ +fabric.DPI = 96; +fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)'; +fabric.commaWsp = '(?:\\s+,?\\s*|,\\s*)'; +fabric.rePathCommand = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:[eE][-+]?\d+)?)/ig; +fabric.reNonWord = /[ \n\.,;!\?\-]/; +fabric.fontPaths = { }; +fabric.iMatrix = [1, 0, 0, 1, 0, 0]; +fabric.svgNS = 'http://www.w3.org/2000/svg'; + +/** + * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine. + * @since 1.7.14 + * @type Number + * @default + */ +fabric.perfLimitSizeTotal = 2097152; + +/** + * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000 + * @since 1.7.14 + * @type Number + * @default + */ +fabric.maxCacheSideLimit = 4096; + +/** + * Lowest pixel limit for cache canvases, set at 256PX + * @since 1.7.14 + * @type Number + * @default + */ +fabric.minCacheSideLimit = 256; + +/** + * Cache Object for widths of chars in text rendering. + */ +fabric.charWidthsCache = { }; + +/** + * if webgl is enabled and available, textureSize will determine the size + * of the canvas backend + * @since 2.0.0 + * @type Number + * @default + */ +fabric.textureSize = 2048; + +/** + * When 'true', style information is not retained when copy/pasting text, making + * pasted text use destination style. + * Defaults to 'false'. + * @type Boolean + * @default + */ +fabric.disableStyleCopyPaste = false; + +/** + * Enable webgl for filtering picture is available + * A filtering backend will be initialized, this will both take memory and + * time since a default 2048x2048 canvas will be created for the gl context + * @since 2.0.0 + * @type Boolean + * @default + */ +fabric.enableGLFiltering = true; + +/** + * Device Pixel Ratio + * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html + */ +fabric.devicePixelRatio = fabric.window.devicePixelRatio || + fabric.window.webkitDevicePixelRatio || + fabric.window.mozDevicePixelRatio || + 1; +/** + * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value, + * which is unitless and not rendered equally across browsers. + * + * Values that work quite well (as of October 2017) are: + * - Chrome: 1.5 + * - Edge: 1.75 + * - Firefox: 0.9 + * - Safari: 0.95 + * + * @since 2.0.0 + * @type Number + * @default 1 + */ +fabric.browserShadowBlurConstant = 1; + +/** + * This object contains the result of arc to bezier conversion for faster retrieving if the same arc needs to be converted again. + * It was an internal variable, is accessible since version 2.3.4 + */ +fabric.arcToSegmentsCache = { }; + +/** + * This object keeps the results of the boundsOfCurve calculation mapped by the joined arguments necessary to calculate it. + * It does speed up calculation, if you parse and add always the same paths, but in case of heavy usage of freedrawing + * you do not get any speed benefit and you get a big object in memory. + * The object was a private variable before, while now is appended to the lib so that you have access to it and you + * can eventually clear it. + * It was an internal variable, is accessible since version 2.3.4 + */ +fabric.boundsOfCurveCache = { }; + +/** + * If disabled boundsOfCurveCache is not used. For apps that make heavy usage of pencil drawing probably disabling it is better + * @default true + */ +fabric.cachesBoundsOfCurve = true; + +/** + * Skip performance testing of setupGLContext and force the use of putImageData that seems to be the one that works best on + * Chrome + old hardware. if your users are experiencing empty images after filtering you may try to force this to true + * this has to be set before instantiating the filtering backend ( before filtering the first image ) + * @type Boolean + * @default false + */ +fabric.forceGLPutImageData = false; + +fabric.initFilterBackend = function() { + if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) { + console.log('max texture size: ' + fabric.maxTextureSize); + return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize })); + } + else if (fabric.Canvas2dFilterBackend) { + return (new fabric.Canvas2dFilterBackend()); + } +}; + + +if (typeof document !== 'undefined' && typeof window !== 'undefined') { + // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) + window.fabric = fabric; +} + + +(function() { + + /** + * @private + * @param {String} eventName + * @param {Function} handler + */ + function _removeEventListener(eventName, handler) { + if (!this.__eventListeners[eventName]) { + return; + } + var eventListener = this.__eventListeners[eventName]; + if (handler) { + eventListener[eventListener.indexOf(handler)] = false; + } + else { + fabric.util.array.fill(eventListener, false); + } + } + + /** + * Observes specified event + * @memberOf fabric.Observable + * @alias on + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function that receives a notification when an event of the specified type occurs + * @return {Self} thisArg + * @chainable + */ + function on(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = { }; + } + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } + else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = []; + } + this.__eventListeners[eventName].push(handler); + } + return this; + } + + function _once(eventName, handler) { + var _handler = function () { + handler.apply(this, arguments); + this.off(eventName, _handler); + }.bind(this); + this.on(eventName, _handler); + } + + function once(eventName, handler) { + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + _once.call(this, prop, eventName[prop]); + } + } + else { + _once.call(this, eventName, handler); + } + return this; + } + + /** + * Stops event observing for a particular event handler. Calling this method + * without arguments removes all handlers for all events + * @memberOf fabric.Observable + * @alias off + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function to be deleted from EventListeners + * @return {Self} thisArg + * @chainable + */ + function off(eventName, handler) { + if (!this.__eventListeners) { + return this; + } + + // remove all key/value pairs (event name -> event handler) + if (arguments.length === 0) { + for (eventName in this.__eventListeners) { + _removeEventListener.call(this, eventName); + } + } + // one object with key/value pairs was passed + else if (arguments.length === 1 && typeof arguments[0] === 'object') { + for (var prop in eventName) { + _removeEventListener.call(this, prop, eventName[prop]); + } + } + else { + _removeEventListener.call(this, eventName, handler); + } + return this; + } + + /** + * Fires event with an optional options object + * @memberOf fabric.Observable + * @param {String} eventName Event name to fire + * @param {Object} [options] Options object + * @return {Self} thisArg + * @chainable + */ + function fire(eventName, options) { + if (!this.__eventListeners) { + return this; + } + + var listenersForEvent = this.__eventListeners[eventName]; + if (!listenersForEvent) { + return this; + } + + for (var i = 0, len = listenersForEvent.length; i < len; i++) { + listenersForEvent[i] && listenersForEvent[i].call(this, options || { }); + } + this.__eventListeners[eventName] = listenersForEvent.filter(function(value) { + return value !== false; + }); + return this; + } + + /** + * @namespace fabric.Observable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} + * @see {@link http://fabricjs.com/events|Events demo} + */ + fabric.Observable = { + fire: fire, + on: on, + once: once, + off: off, + }; +})(); + + +/** + * @namespace fabric.Collection + */ +fabric.Collection = { + + _objects: [], + + /** + * Adds objects to collection, Canvas or Group, then renders canvas + * (if `renderOnAddRemove` is not `false`). + * in case of Group no changes to bounding box are made. + * Objects should be instances of (or inherit from) fabric.Object + * Use of this function is highly discouraged for groups. + * you can add a bunch of objects with the add method but then you NEED + * to run a addWithUpdate call for the Group class or position/bbox will be wrong. + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + * @chainable + */ + add: function () { + this._objects.push.apply(this._objects, arguments); + if (this._onObjectAdded) { + for (var i = 0, length = arguments.length; i < length; i++) { + this._onObjectAdded(arguments[i]); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) + * An object should be an instance of (or inherit from) fabric.Object + * Use of this function is highly discouraged for groups. + * you can add a bunch of objects with the insertAt method but then you NEED + * to run a addWithUpdate call for the Group class or position/bbox will be wrong. + * @param {Object} object Object to insert + * @param {Number} index Index to insert object at + * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs + * @return {Self} thisArg + * @chainable + */ + insertAt: function (object, index, nonSplicing) { + var objects = this._objects; + if (nonSplicing) { + objects[index] = object; + } + else { + objects.splice(index, 0, object); + } + this._onObjectAdded && this._onObjectAdded(object); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + * @chainable + */ + remove: function() { + var objects = this._objects, + index, somethingRemoved = false; + + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + somethingRemoved = true; + objects.splice(index, 1); + this._onObjectRemoved && this._onObjectRemoved(arguments[i]); + } + } + + this.renderOnAddRemove && somethingRemoved && this.requestRenderAll(); + return this; + }, + + /** + * Executes given function for each object in this group + * @param {Function} callback + * Callback invoked with current object as first argument, + * index - as second and an array of all objects - as third. + * Callback is invoked in a context of Global Object (e.g. `window`) + * when no `context` argument is given + * + * @param {Object} context Context (aka thisObject) + * @return {Self} thisArg + * @chainable + */ + forEachObject: function(callback, context) { + var objects = this.getObjects(); + for (var i = 0, len = objects.length; i < len; i++) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + + /** + * Returns an array of children objects of this instance + * Type parameter introduced in 1.3.10 + * since 2.3.5 this method return always a COPY of the array; + * @param {String} [type] When specified, only objects of this type are returned + * @return {Array} + */ + getObjects: function(type) { + if (typeof type === 'undefined') { + return this._objects.concat(); + } + return this._objects.filter(function(o) { + return o.type === type; + }); + }, + + /** + * Returns object at specified index + * @param {Number} index + * @return {Self} thisArg + */ + item: function (index) { + return this._objects[index]; + }, + + /** + * Returns true if collection contains no objects + * @return {Boolean} true if collection is empty + */ + isEmpty: function () { + return this._objects.length === 0; + }, + + /** + * Returns a size of a collection (i.e: length of an array containing its objects) + * @return {Number} Collection size + */ + size: function() { + return this._objects.length; + }, + + /** + * Returns true if collection contains an object + * @param {Object} object Object to check against + * @param {Boolean} [deep=false] `true` to check all descendants, `false` to check only `_objects` + * @return {Boolean} `true` if collection contains an object + */ + contains: function (object, deep) { + if (this._objects.indexOf(object) > -1) { + return true; + } + else if (deep) { + return this._objects.some(function (obj) { + return typeof obj.contains === 'function' && obj.contains(object, true); + }); + } + return false; + }, + + /** + * Returns number representation of a collection complexity + * @return {Number} complexity + */ + complexity: function () { + return this._objects.reduce(function (memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + } +}; + + +/** + * @namespace fabric.CommonMethods + */ +fabric.CommonMethods = { + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + _setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + }, + + /** + * @private + * @param {Object} [filler] Options object + * @param {String} [property] property to set the Gradient to + */ + _initGradient: function(filler, property) { + if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) { + this.set(property, new fabric.Gradient(filler)); + } + }, + + /** + * @private + * @param {Object} [filler] Options object + * @param {String} [property] property to set the Pattern to + * @param {Function} [callback] callback to invoke after pattern load + */ + _initPattern: function(filler, property, callback) { + if (filler && filler.source && !(filler instanceof fabric.Pattern)) { + this.set(property, new fabric.Pattern(filler, callback)); + } + else { + callback && callback(); + } + }, + + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + if (typeof key === 'object') { + this._setObject(key); + } + else { + this._set(key, value); + } + return this; + }, + + _set: function(key, value) { + this[key] = value; + }, + + /** + * Toggles specified property from `true` to `false` or from `false` to `true` + * @param {String} property Property to toggle + * @return {fabric.Object} thisArg + * @chainable + */ + toggle: function(property) { + var value = this.get(property); + if (typeof value === 'boolean') { + this.set(property, !value); + } + return this; + }, + + /** + * Basic getter + * @param {String} property Property name + * @return {*} value of a property + */ + get: function(property) { + return this[property]; + } +}; + + +(function(global) { + + var sqrt = Math.sqrt, + atan2 = Math.atan2, + pow = Math.pow, + PiBy180 = Math.PI / 180, + PiBy2 = Math.PI / 2; + + /** + * @namespace fabric.util + */ + fabric.util = { + + /** + * Calculate the cos of an angle, avoiding returning floats for known results + * @static + * @memberOf fabric.util + * @param {Number} angle the angle in radians or in degree + * @return {Number} + */ + cos: function(angle) { + if (angle === 0) { return 1; } + if (angle < 0) { + // cos(a) = cos(-a) + angle = -angle; + } + var angleSlice = angle / PiBy2; + switch (angleSlice) { + case 1: case 3: return 0; + case 2: return -1; + } + return Math.cos(angle); + }, + + /** + * Calculate the sin of an angle, avoiding returning floats for known results + * @static + * @memberOf fabric.util + * @param {Number} angle the angle in radians or in degree + * @return {Number} + */ + sin: function(angle) { + if (angle === 0) { return 0; } + var angleSlice = angle / PiBy2, sign = 1; + if (angle < 0) { + // sin(-a) = -sin(a) + sign = -1; + } + switch (angleSlice) { + case 1: return sign; + case 2: return 0; + case 3: return -sign; + } + return Math.sin(angle); + }, + + /** + * Removes value from an array. + * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` + * @static + * @memberOf fabric.util + * @param {Array} array + * @param {*} value + * @return {Array} original array + */ + removeFromArray: function(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); + } + return array; + }, + + /** + * Returns random number between 2 specified ones. + * @static + * @memberOf fabric.util + * @param {Number} min lower limit + * @param {Number} max upper limit + * @return {Number} random value (between min and max) + */ + getRandomInt: function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + /** + * Transforms degrees to radians. + * @static + * @memberOf fabric.util + * @param {Number} degrees value in degrees + * @return {Number} value in radians + */ + degreesToRadians: function(degrees) { + return degrees * PiBy180; + }, + + /** + * Transforms radians to degrees. + * @static + * @memberOf fabric.util + * @param {Number} radians value in radians + * @return {Number} value in degrees + */ + radiansToDegrees: function(radians) { + return radians / PiBy180; + }, + + /** + * Rotates `point` around `origin` with `radians` + * @static + * @memberOf fabric.util + * @param {fabric.Point} point The point to rotate + * @param {fabric.Point} origin The origin of the rotation + * @param {Number} radians The radians of the angle for the rotation + * @return {fabric.Point} The new rotated point + */ + rotatePoint: function(point, origin, radians) { + var newPoint = new fabric.Point(point.x - origin.x, point.y - origin.y), + v = fabric.util.rotateVector(newPoint, radians); + return new fabric.Point(v.x, v.y).addEquals(origin); + }, + + /** + * Rotates `vector` with `radians` + * @static + * @memberOf fabric.util + * @param {Object} vector The vector to rotate (x and y) + * @param {Number} radians The radians of the angle for the rotation + * @return {Object} The new rotated point + */ + rotateVector: function(vector, radians) { + var sin = fabric.util.sin(radians), + cos = fabric.util.cos(radians), + rx = vector.x * cos - vector.y * sin, + ry = vector.x * sin + vector.y * cos; + return { + x: rx, + y: ry + }; + }, + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[2] * p.y, + t[1] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[2] * p.y + t[4], + t[1] * p.x + t[3] * p.y + t[5] + ); + }, + + /** + * Returns coordinates of points's bounding rectangle (left, top, width, height) + * @param {Array} points 4 points array + * @param {Array} [transform] an array of 6 numbers representing a 2x3 transform matrix + * @return {Object} Object with left, top, width, height properties + */ + makeBoundingBoxFromPoints: function(points, transform) { + if (transform) { + for (var i = 0; i < points.length; i++) { + points[i] = fabric.util.transformPoint(points[i], transform); + } + } + var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], + minX = fabric.util.array.min(xPoints), + maxX = fabric.util.array.max(xPoints), + width = maxX - minX, + yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], + minY = fabric.util.array.min(yPoints), + maxY = fabric.util.array.max(yPoints), + height = maxY - minY; + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + invertTransform: function(t) { + var a = 1 / (t[0] * t[3] - t[1] * t[2]), + r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], + o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); + r[4] = -o.x; + r[5] = -o.y; + return r; + }, + + /** + * A wrapper around Number#toFixed, which contrary to native method returns number, not string. + * @static + * @memberOf fabric.util + * @param {Number|String} number number to operate on + * @param {Number} fractionDigits number of fraction digits to "leave" + * @return {Number} + */ + toFixed: function(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + }, + + /** + * Converts from attribute value to pixel value if applicable. + * Returns converted pixels or original value not converted. + * @param {Number|String} value number to operate on + * @param {Number} fontSize + * @return {Number|String} + */ + parseUnit: function(value, fontSize) { + var unit = /\D{0,2}$/.exec(value), + number = parseFloat(value); + if (!fontSize) { + fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + switch (unit[0]) { + case 'mm': + return number * fabric.DPI / 25.4; + + case 'cm': + return number * fabric.DPI / 2.54; + + case 'in': + return number * fabric.DPI; + + case 'pt': + return number * fabric.DPI / 72; // or * 4 / 3 + + case 'pc': + return number * fabric.DPI / 72 * 12; // or * 16 + + case 'em': + return number * fontSize; + + default: + return number; + } + }, + + /** + * Function which always returns `false`. + * @static + * @memberOf fabric.util + * @return {Boolean} + */ + falseFunction: function() { + return false; + }, + + /** + * Returns klass "Class" object of given namespace + * @memberOf fabric.util + * @param {String} type Type of object (eg. 'circle') + * @param {String} namespace Namespace to get klass "Class" object from + * @return {Object} klass "Class" + */ + getKlass: function(type, namespace) { + // capitalize first letter only + type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); + return fabric.util.resolveNamespace(namespace)[type]; + }, + + /** + * Returns array of attributes for given svg that fabric parses + * @memberOf fabric.util + * @param {String} type Type of svg element (eg. 'circle') + * @return {Array} string names of supported attributes + */ + getSvgAttributes: function(type) { + var attributes = [ + 'instantiated_by_use', + 'style', + 'id', + 'class' + ]; + switch (type) { + case 'linearGradient': + attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); + break; + case 'radialGradient': + attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); + break; + case 'stop': + attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']); + break; + } + return attributes; + }, + + /** + * Returns object of given namespace + * @memberOf fabric.util + * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' + * @return {Object} Object for given namespace (default fabric) + */ + resolveNamespace: function(namespace) { + if (!namespace) { + return fabric; + } + + var parts = namespace.split('.'), + len = parts.length, i, + obj = global || fabric.window; + + for (i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + + return obj; + }, + + /** + * Loads image element from given url and passes it to a callback + * @memberOf fabric.util + * @param {String} url URL representing an image + * @param {Function} callback Callback; invoked with loaded image + * @param {*} [context] Context to invoke callback in + * @param {Object} [crossOrigin] crossOrigin value to set image element to + */ + loadImage: function(url, callback, context, crossOrigin) { + if (!url) { + callback && callback.call(context, url); + return; + } + + var img = fabric.util.createImage(); + + /** @ignore */ + var onLoadCallback = function () { + callback && callback.call(context, img, false); + img = img.onload = img.onerror = null; + }; + + img.onload = onLoadCallback; + /** @ignore */ + img.onerror = function() { + fabric.log('Error loading ' + img.src); + callback && callback.call(context, null, true); + img = img.onload = img.onerror = null; + }; + + // data-urls appear to be buggy with crossOrigin + // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 + // see https://code.google.com/p/chromium/issues/detail?id=315152 + // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 + // crossOrigin null is the same as not set. + if (url.indexOf('data') !== 0 && + crossOrigin !== undefined && + crossOrigin !== null) { + img.crossOrigin = crossOrigin; + } + + // IE10 / IE11-Fix: SVG contents from data: URI + // will only be available if the IMG is present + // in the DOM (and visible) + if (url.substring(0,14) === 'data:image/svg') { + img.onload = null; + fabric.util.loadImageInDom(img, onLoadCallback); + } + + img.src = url; + }, + + /** + * Attaches SVG image with data: URL to the dom + * @memberOf fabric.util + * @param {Object} img Image object with data:image/svg src + * @param {Function} callback Callback; invoked with loaded image + * @return {Object} DOM element (div containing the SVG image) + */ + loadImageInDom: function(img, onLoadCallback) { + var div = fabric.document.createElement('div'); + div.style.width = div.style.height = '1px'; + div.style.left = div.style.top = '-100%'; + div.style.position = 'absolute'; + div.appendChild(img); + fabric.document.querySelector('body').appendChild(div); + /** + * Wrap in function to: + * 1. Call existing callback + * 2. Cleanup DOM + */ + img.onload = function () { + onLoadCallback(); + div.parentNode.removeChild(div); + div = null; + }; + }, + + /** + * Creates corresponding fabric instances from their object representations + * @static + * @memberOf fabric.util + * @param {Array} objects Objects to enliven + * @param {Function} callback Callback to invoke when all objects are created + * @param {String} namespace Namespace to get klass "Class" object from + * @param {Function} reviver Method for further parsing of object elements, + * called after each fabric object created. + */ + enlivenObjects: function(objects, callback, namespace, reviver) { + objects = objects || []; + + var enlivenedObjects = [], + numLoadedObjects = 0, + numTotalObjects = objects.length; + + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + callback && callback(enlivenedObjects.filter(function(obj) { + // filter out undefined objects (objects that gave error) + return obj; + })); + } + } + + if (!numTotalObjects) { + callback && callback(enlivenedObjects); + return; + } + + objects.forEach(function (o, index) { + // if sparse array + if (!o || !o.type) { + onLoaded(); + return; + } + var klass = fabric.util.getKlass(o.type, namespace); + klass.fromObject(o, function (obj, error) { + error || (enlivenedObjects[index] = obj); + reviver && reviver(o, obj, error); + onLoaded(); + }); + }); + }, + + /** + * Create and wait for loading of patterns + * @static + * @memberOf fabric.util + * @param {Array} patterns Objects to enliven + * @param {Function} callback Callback to invoke when all objects are created + * called after each fabric object created. + */ + enlivenPatterns: function(patterns, callback) { + patterns = patterns || []; + + function onLoaded() { + if (++numLoadedPatterns === numPatterns) { + callback && callback(enlivenedPatterns); + } + } + + var enlivenedPatterns = [], + numLoadedPatterns = 0, + numPatterns = patterns.length; + + if (!numPatterns) { + callback && callback(enlivenedPatterns); + return; + } + + patterns.forEach(function (p, index) { + if (p && p.source) { + new fabric.Pattern(p, function(pattern) { + enlivenedPatterns[index] = pattern; + onLoaded(); + }); + } + else { + enlivenedPatterns[index] = p; + onLoaded(); + } + }); + }, + + /** + * Groups SVG elements (usually those retrieved from SVG document) + * @static + * @memberOf fabric.util + * @param {Array} elements SVG elements to group + * @param {Object} [options] Options object + * @param {String} path Value to set sourcePath to + * @return {fabric.Object|fabric.Group} + */ + groupSVGElements: function(elements, options, path) { + var object; + if (elements && elements.length === 1) { + return elements[0]; + } + if (options) { + if (options.width && options.height) { + options.centerPoint = { + x: options.width / 2, + y: options.height / 2 + }; + } + else { + delete options.width; + delete options.height; + } + } + object = new fabric.Group(elements, options); + if (typeof path !== 'undefined') { + object.sourcePath = path; + } + return object; + }, + + /** + * Populates an object with properties of another object + * @static + * @memberOf fabric.util + * @param {Object} source Source object + * @param {Object} destination Destination object + * @return {Array} properties Properties names to include + */ + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === '[object Array]') { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } + } + } + }, + + /** + * WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated. + * WILL BE REMOVED IN FABRIC 5.0 + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x start x coordinate + * @param {Number} y start y coordinate + * @param {Number} x2 end x coordinate + * @param {Number} y2 end y coordinate + * @param {Array} da dash array pattern + * @deprecated + */ + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, + dy = y2 - y, + len = sqrt(dx * dx + dy * dy), + rot = atan2(dy, dx), + dc = da.length, + di = 0, + draw = true; + + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); + + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; + } + ctx[draw ? 'lineTo' : 'moveTo'](x, 0); + draw = !draw; + } + + ctx.restore(); + }, + + /** + * Creates canvas element + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ + createCanvasElement: function() { + return fabric.document.createElement('canvas'); + }, + + /** + * Creates a canvas element that is a copy of another and is also painted + * @param {CanvasElement} canvas to copy size and content of + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ + copyCanvasElement: function(canvas) { + var newCanvas = fabric.util.createCanvasElement(); + newCanvas.width = canvas.width; + newCanvas.height = canvas.height; + newCanvas.getContext('2d').drawImage(canvas, 0, 0); + return newCanvas; + }, + + /** + * since 2.6.0 moved from canvas instance to utility. + * @param {CanvasElement} canvasEl to copy size and content of + * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too + * @param {Number} quality <= 1 and > 0 + * @static + * @memberOf fabric.util + * @return {String} data url + */ + toDataURL: function(canvasEl, format, quality) { + return canvasEl.toDataURL('image/' + format, quality); + }, + + /** + * Creates image element (works on client and node) + * @static + * @memberOf fabric.util + * @return {HTMLImageElement} HTML image element + */ + createImage: function() { + return fabric.document.createElement('img'); + }, + + /** + * Multiply matrix A by matrix B to nest transformations + * @static + * @memberOf fabric.util + * @param {Array} a First transformMatrix + * @param {Array} b Second transformMatrix + * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices + * @return {Array} The product of the two transform matrices + */ + multiplyTransformMatrices: function(a, b, is2x2) { + // Matrix multiply a * b + return [ + a[0] * b[0] + a[2] * b[1], + a[1] * b[0] + a[3] * b[1], + a[0] * b[2] + a[2] * b[3], + a[1] * b[2] + a[3] * b[3], + is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], + is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5] + ]; + }, + + /** + * Decomposes standard 2x3 matrix into transform components + * @static + * @memberOf fabric.util + * @param {Array} a transformMatrix + * @return {Object} Components of transform + */ + qrDecompose: function(a) { + var angle = atan2(a[1], a[0]), + denom = pow(a[0], 2) + pow(a[1], 2), + scaleX = sqrt(denom), + scaleY = (a[0] * a[3] - a[2] * a[1]) / scaleX, + skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); + return { + angle: angle / PiBy180, + scaleX: scaleX, + scaleY: scaleY, + skewX: skewX / PiBy180, + skewY: 0, + translateX: a[4], + translateY: a[5] + }; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] angle in degrees + * @return {Number[]} transform matrix + */ + calcRotateMatrix: function(options) { + if (!options.angle) { + return fabric.iMatrix.concat(); + } + var theta = fabric.util.degreesToRadians(options.angle), + cos = fabric.util.cos(theta), + sin = fabric.util.sin(theta); + return [cos, sin, -sin, cos, 0, 0]; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet. + * is called DimensionsTransformMatrix because those properties are the one that influence + * the size of the resulting box of the object. + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Boolean} [options.flipX] + * @param {Boolean} [options.flipY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @return {Number[]} transform matrix + */ + calcDimensionsMatrix: function(options) { + var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, + scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, + scaleMatrix = [ + options.flipX ? -scaleX : scaleX, + 0, + 0, + options.flipY ? -scaleY : scaleY, + 0, + 0], + multiply = fabric.util.multiplyTransformMatrices, + degreesToRadians = fabric.util.degreesToRadians; + if (options.skewX) { + scaleMatrix = multiply( + scaleMatrix, + [1, 0, Math.tan(degreesToRadians(options.skewX)), 1], + true); + } + if (options.skewY) { + scaleMatrix = multiply( + scaleMatrix, + [1, Math.tan(degreesToRadians(options.skewY)), 0, 1], + true); + } + return scaleMatrix; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Boolean} [options.flipX] + * @param {Boolean} [options.flipY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @param {Number} [options.translateX] + * @param {Number} [options.translateY] + * @return {Number[]} transform matrix + */ + composeMatrix: function(options) { + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0], + multiply = fabric.util.multiplyTransformMatrices; + if (options.angle) { + matrix = multiply(matrix, fabric.util.calcRotateMatrix(options)); + } + if (options.scaleX !== 1 || options.scaleY !== 1 || + options.skewX || options.skewY || options.flipX || options.flipY) { + matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options)); + } + return matrix; + }, + + /** + * reset an object transform state to neutral. Top and left are not accounted for + * @static + * @memberOf fabric.util + * @param {fabric.Object} target object to transform + */ + resetObjectTransform: function (target) { + target.scaleX = 1; + target.scaleY = 1; + target.skewX = 0; + target.skewY = 0; + target.flipX = false; + target.flipY = false; + target.rotate(0); + }, + + /** + * Extract Object transform values + * @static + * @memberOf fabric.util + * @param {fabric.Object} target object to read from + * @return {Object} Components of transform + */ + saveObjectTransform: function (target) { + return { + scaleX: target.scaleX, + scaleY: target.scaleY, + skewX: target.skewX, + skewY: target.skewY, + angle: target.angle, + left: target.left, + flipX: target.flipX, + flipY: target.flipY, + top: target.top + }; + }, + + /** + * Returns true if context has transparent pixel + * at specified location (taking tolerance into account) + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @param {Number} tolerance Tolerance + */ + isTransparent: function(ctx, x, y, tolerance) { + + // If tolerance is > 0 adjust start coords to take into account. + // If moves off Canvas fix to 0 + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } + else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } + else { + y = 0; + } + } + + var _isTransparent = true, i, temp, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1), + l = imageData.data.length; + + // Split image data - for tolerance > 1, pixelDataSize = 4; + for (i = 3; i < l; i += 4) { + temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) { + break; // Stop if colour found + } + } + + imageData = null; + + return _isTransparent; + }, + + /** + * Parse preserveAspectRatio attribute from element + * @param {string} attribute to be parsed + * @return {Object} an object containing align and meetOrSlice attribute + */ + parsePreserveAspectRatioAttribute: function(attribute) { + var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', + aspectRatioAttrs = attribute.split(' '), align; + + if (aspectRatioAttrs && aspectRatioAttrs.length) { + meetOrSlice = aspectRatioAttrs.pop(); + if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { + align = meetOrSlice; + meetOrSlice = 'meet'; + } + else if (aspectRatioAttrs.length) { + align = aspectRatioAttrs.pop(); + } + } + //divide align in alignX and alignY + alignX = align !== 'none' ? align.slice(1, 4) : 'none'; + alignY = align !== 'none' ? align.slice(5, 8) : 'none'; + return { + meetOrSlice: meetOrSlice, + alignX: alignX, + alignY: alignY + }; + }, + + /** + * Clear char widths cache for the given font family or all the cache if no + * fontFamily is specified. + * Use it if you know you are loading fonts in a lazy way and you are not waiting + * for custom fonts to load properly when adding text objects to the canvas. + * If a text object is added when its own font is not loaded yet, you will get wrong + * measurement and so wrong bounding boxes. + * After the font cache is cleared, either change the textObject text content or call + * initDimensions() to trigger a recalculation + * @memberOf fabric.util + * @param {String} [fontFamily] font family to clear + */ + clearFabricFontCache: function(fontFamily) { + fontFamily = (fontFamily || '').toLowerCase(); + if (!fontFamily) { + fabric.charWidthsCache = { }; + } + else if (fabric.charWidthsCache[fontFamily]) { + delete fabric.charWidthsCache[fontFamily]; + } + }, + + /** + * Given current aspect ratio, determines the max width and height that can + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Number} ar aspect ratio + * @param {Number} maximumArea Maximum area you want to achieve + * @return {Object.x} Limited dimensions by X + * @return {Object.y} Limited dimensions by Y + */ + limitDimsByArea: function(ar, maximumArea) { + var roughWidth = Math.sqrt(maximumArea * ar), + perfLimitSizeY = Math.floor(maximumArea / roughWidth); + return { x: Math.floor(roughWidth), y: perfLimitSizeY }; + }, + + capValue: function(min, value, max) { + return Math.max(min, Math.min(value, max)); + }, + + /** + * Finds the scale for the object source to fit inside the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to fit into destination + */ + findScaleToFit: function(source, destination) { + return Math.min(destination.width / source.width, destination.height / source.height); + }, + + /** + * Finds the scale for the object source to cover entirely the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to cover destination + */ + findScaleToCover: function(source, destination) { + return Math.max(destination.width / source.width, destination.height / source.height); + }, + + /** + * given an array of 6 number returns something like `"matrix(...numbers)"` + * @memberOf fabric.util + * @param {Array} transform an array with 6 numbers + * @return {String} transform matrix for svg + * @return {Object.y} Limited dimensions by Y + */ + matrixToSVG: function(transform) { + return 'matrix(' + transform.map(function(value) { + return fabric.util.toFixed(value, fabric.Object.NUM_FRACTION_DIGITS); + }).join(' ') + ')'; + }, + + /** + * given an object and a transform, apply the inverse transform to the object, + * this is equivalent to remove from that object that transformation, so that + * added in a space with the removed transform, the object will be the same as before. + * Removing from an object a transform that scale by 2 is like scaling it by 1/2. + * Removing from an object a transfrom that rotate by 30deg is like rotating by 30deg + * in the opposite direction. + * This util is used to add objects inside transformed groups or nested groups. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + removeTransformFromObject: function(object, transform) { + var inverted = fabric.util.invertTransform(transform), + finalTransform = fabric.util.multiplyTransformMatrices(inverted, object.calcOwnMatrix()); + fabric.util.applyTransformToObject(object, finalTransform); + }, + + /** + * given an object and a transform, apply the transform to the object. + * this is equivalent to change the space where the object is drawn. + * Adding to an object a transform that scale by 2 is like scaling it by 2. + * This is used when removing an object from an active selection for example. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + addTransformToObject: function(object, transform) { + fabric.util.applyTransformToObject( + object, + fabric.util.multiplyTransformMatrices(transform, object.calcOwnMatrix()) + ); + }, + + /** + * discard an object transform state and apply the one from the matrix. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + applyTransformToObject: function(object, transform) { + var options = fabric.util.qrDecompose(transform), + center = new fabric.Point(options.translateX, options.translateY); + object.flipX = false; + object.flipY = false; + object.set('scaleX', options.scaleX); + object.set('scaleY', options.scaleY); + object.skewX = options.skewX; + object.skewY = options.skewY; + object.angle = options.angle; + object.setPositionByOrigin(center, 'center', 'center'); + }, + + /** + * given a width and height, return the size of the bounding box + * that can contains the box with width/height with applied transform + * described in options. + * Use to calculate the boxes around objects for controls. + * @memberOf fabric.util + * @param {Number} width + * @param {Number} height + * @param {Object} options + * @param {Number} options.scaleX + * @param {Number} options.scaleY + * @param {Number} options.skewX + * @param {Number} options.skewY + * @return {Object.x} width of containing + * @return {Object.y} height of containing + */ + sizeAfterTransform: function(width, height, options) { + var dimX = width / 2, dimY = height / 2, + points = [ + { + x: -dimX, + y: -dimY + }, + { + x: dimX, + y: -dimY + }, + { + x: -dimX, + y: dimY + }, + { + x: dimX, + y: dimY + }], + transformMatrix = fabric.util.calcDimensionsMatrix(options), + bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); + return { + x: bbox.width, + y: bbox.height, + }; + } + }; +})( true ? exports : 0); + + +(function() { + var _join = Array.prototype.join, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' + }; + function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { + var costh2 = fabric.util.cos(th2), + sinth2 = fabric.util.sin(th2), + costh3 = fabric.util.cos(th3), + sinth3 = fabric.util.sin(th3), + toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, + toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, + cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2), + cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2), + cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), + cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); + + return ['C', + cp1X, cp1Y, + cp2X, cp2Y, + toX, toY + ]; + } + + /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp + * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here + * http://mozilla.org/MPL/2.0/ + */ + function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { + var PI = Math.PI, th = rotateX * PI / 180, + sinTh = fabric.util.sin(th), + cosTh = fabric.util.cos(th), + fromX = 0, fromY = 0; + + rx = Math.abs(rx); + ry = Math.abs(ry); + + var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, + py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, + rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, + pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, + root = 0; + + if (pl < 0) { + var s = Math.sqrt(1 - pl / (rx2 * ry2)); + rx *= s; + ry *= s; + } + else { + root = (large === sweep ? -1.0 : 1.0) * + Math.sqrt( pl / (rx2 * py2 + ry2 * px2)); + } + + var cx = root * rx * py / ry, + cy = -root * ry * px / rx, + cx1 = cosTh * cx - sinTh * cy + toX * 0.5, + cy1 = sinTh * cx + cosTh * cy + toY * 0.5, + mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), + dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); + + if (sweep === 0 && dtheta > 0) { + dtheta -= 2 * PI; + } + else if (sweep === 1 && dtheta < 0) { + dtheta += 2 * PI; + } + + // Convert into cubic bezier segments <= 90deg + var segments = Math.ceil(Math.abs(dtheta / PI * 2)), + result = [], mDelta = dtheta / segments, + mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), + th3 = mTheta + mDelta; + + for (var i = 0; i < segments; i++) { + result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); + fromX = result[i][5]; + fromY = result[i][6]; + mTheta = th3; + th3 += mDelta; + } + return result; + } + + /* + * Private + */ + function calcVectorAngle(ux, uy, vx, vy) { + var ta = Math.atan2(uy, ux), + tb = Math.atan2(vy, vx); + if (tb >= ta) { + return tb - ta; + } + else { + return 2 * Math.PI - (ta - tb); + } + } + + /** + * Calculate bounding box of a beziercurve + * @param {Number} x0 starting point + * @param {Number} y0 + * @param {Number} x1 first control point + * @param {Number} y1 + * @param {Number} x2 secondo control point + * @param {Number} y2 + * @param {Number} x3 end of bezier + * @param {Number} y3 + */ + // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. + // TODO: can we normalize this with the starting points set at 0 and then translated the bbox? + function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { + var argsString; + if (fabric.cachesBoundsOfCurve) { + argsString = _join.call(arguments); + if (fabric.boundsOfCurveCache[argsString]) { + return fabric.boundsOfCurveCache[argsString]; + } + } + + var sqrt = Math.sqrt, + min = Math.min, max = Math.max, + abs = Math.abs, tvalues = [], + bounds = [[], []], + a, b, c, t, t1, t2, b2ac, sqrtb2ac; + + b = 6 * x0 - 12 * x1 + 6 * x2; + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; + c = 3 * x1 - 3 * x0; + + for (var i = 0; i < 2; ++i) { + if (i > 0) { + b = 6 * y0 - 12 * y1 + 6 * y2; + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; + c = 3 * y1 - 3 * y0; + } + + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue; + } + t = -c / b; + if (0 < t && t < 1) { + tvalues.push(t); + } + continue; + } + b2ac = b * b - 4 * c * a; + if (b2ac < 0) { + continue; + } + sqrtb2ac = sqrt(b2ac); + t1 = (-b + sqrtb2ac) / (2 * a); + if (0 < t1 && t1 < 1) { + tvalues.push(t1); + } + t2 = (-b - sqrtb2ac) / (2 * a); + if (0 < t2 && t2 < 1) { + tvalues.push(t2); + } + } + + var x, y, j = tvalues.length, jlen = j, mt; + while (j--) { + t = tvalues[j]; + mt = 1 - t; + x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); + bounds[0][j] = x; + + y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); + bounds[1][j] = y; + } + + bounds[0][jlen] = x0; + bounds[1][jlen] = y0; + bounds[0][jlen + 1] = x3; + bounds[1][jlen + 1] = y3; + var result = [ + { + x: min.apply(null, bounds[0]), + y: min.apply(null, bounds[1]) + }, + { + x: max.apply(null, bounds[0]), + y: max.apply(null, bounds[1]) + } + ]; + if (fabric.cachesBoundsOfCurve) { + fabric.boundsOfCurveCache[argsString] = result; + } + return result; + } + + /** + * Converts arc to a bunch of bezier curves + * @param {Number} fx starting point x + * @param {Number} fy starting point y + * @param {Array} coords Arc command + */ + function fromArcToBeziers(fx, fy, coords) { + var rx = coords[1], + ry = coords[2], + rot = coords[3], + large = coords[4], + sweep = coords[5], + tx = coords[6], + ty = coords[7], + segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + + for (var i = 0, len = segsNorm.length; i < len; i++) { + segsNorm[i][1] += fx; + segsNorm[i][2] += fy; + segsNorm[i][3] += fx; + segsNorm[i][4] += fy; + segsNorm[i][5] += fx; + segsNorm[i][6] += fy; + } + return segsNorm; + }; + + /** + * This function take a parsed SVG path and make it simpler for fabricJS logic. + * simplification consist of: only UPPERCASE absolute commands ( relative converted to absolute ) + * S converted in C, T converted in Q, A converted in C. + * @param {Array} path the array of commands of a parsed svg path for fabric.Path + * @return {Array} the simplified array of commands of a parsed svg path for fabric.Path + */ + function makePathSimpler(path) { + // x and y represent the last point of the path. the previous command point. + // we add them to each relative command to make it an absolute comment. + // we also swap the v V h H with L, because are easier to transform. + var x = 0, y = 0, len = path.length, + // x1 and y1 represent the last point of the subpath. the subpath is started with + // m or M command. When a z or Z command is drawn, x and y need to be resetted to + // the last x1 and y1. + x1 = 0, y1 = 0, current, i, converted, + // previous will host the letter of the previous command, to handle S and T. + // controlX and controlY will host the previous reflected control point + destinationPath = [], previous, controlX, controlY; + for (i = 0; i < len; ++i) { + converted = false; + current = path[i].slice(0); + switch (current[0]) { // first letter + case 'l': // lineto, relative + current[0] = 'L'; + current[1] += x; + current[2] += y; + // falls through + case 'L': + x = current[1]; + y = current[2]; + break; + case 'h': // horizontal lineto, relative + current[1] += x; + // falls through + case 'H': + current[0] = 'L'; + current[2] = y; + x = current[1]; + break; + case 'v': // vertical lineto, relative + current[1] += y; + // falls through + case 'V': + current[0] = 'L'; + y = current[1]; + current[1] = x; + current[2] = y; + break; + case 'm': // moveTo, relative + current[0] = 'M'; + current[1] += x; + current[2] += y; + // falls through + case 'M': + x = current[1]; + y = current[2]; + x1 = current[1]; + y1 = current[2]; + break; + case 'c': // bezierCurveTo, relative + current[0] = 'C'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + current[5] += x; + current[6] += y; + // falls through + case 'C': + controlX = current[3]; + controlY = current[4]; + x = current[5]; + y = current[6]; + break; + case 's': // shorthand cubic bezierCurveTo, relative + current[0] = 'S'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + // falls through + case 'S': + // would be sScC but since we are swapping sSc for C, we check just that. + if (previous === 'C') { + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + else { + // If there is no previous command or if the previous command was not a C, c, S, or s, + // the control point is coincident with the current point + controlX = x; + controlY = y; + } + x = current[3]; + y = current[4]; + current[0] = 'C'; + current[5] = current[3]; + current[6] = current[4]; + current[3] = current[1]; + current[4] = current[2]; + current[1] = controlX; + current[2] = controlY; + // current[3] and current[4] are NOW the second control point. + // we keep it for the next reflection. + controlX = current[3]; + controlY = current[4]; + break; + case 'q': // quadraticCurveTo, relative + current[0] = 'Q'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + // falls through + case 'Q': + controlX = current[1]; + controlY = current[2]; + x = current[3]; + y = current[4]; + break; + case 't': // shorthand quadraticCurveTo, relative + current[0] = 'T'; + current[1] += x; + current[2] += y; + // falls through + case 'T': + if (previous === 'Q') { + // calculate reflection of previous control point + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + else { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + current[0] = 'Q'; + x = current[1]; + y = current[2]; + current[1] = controlX; + current[2] = controlY; + current[3] = x; + current[4] = y; + break; + case 'a': + current[0] = 'A'; + current[6] += x; + current[7] += y; + // falls through + case 'A': + converted = true; + destinationPath = destinationPath.concat(fromArcToBeziers(x, y, current)); + x = current[6]; + y = current[7]; + break; + case 'z': + case 'Z': + x = x1; + y = y1; + break; + default: + } + if (!converted) { + destinationPath.push(current); + } + previous = current[0]; + } + return destinationPath; + }; + + /** + * Calc length from point x1,y1 to x2,y2 + * @param {Number} x1 starting point x + * @param {Number} y1 starting point y + * @param {Number} x2 starting point x + * @param {Number} y2 starting point y + * @return {Number} length of segment + */ + function calcLineLength(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + + // functions for the Cubic beizer + // taken from: https://github.com/konvajs/konva/blob/7.0.5/src/shapes/Path.ts#L350 + function CB1(t) { + return t * t * t; + } + function CB2(t) { + return 3 * t * t * (1 - t); + } + function CB3(t) { + return 3 * t * (1 - t) * (1 - t); + } + function CB4(t) { + return (1 - t) * (1 - t) * (1 - t); + } + + function getPointOnCubicBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) { + return function(pct) { + var c1 = CB1(pct), c2 = CB2(pct), c3 = CB3(pct), c4 = CB4(pct); + return { + x: p4x * c1 + p3x * c2 + p2x * c3 + p1x * c4, + y: p4y * c1 + p3y * c2 + p2y * c3 + p1y * c4 + }; + }; + } + + function getTangentCubicIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) { + return function (pct) { + var invT = 1 - pct, + tangentX = (3 * invT * invT * (p2x - p1x)) + (6 * invT * pct * (p3x - p2x)) + + (3 * pct * pct * (p4x - p3x)), + tangentY = (3 * invT * invT * (p2y - p1y)) + (6 * invT * pct * (p3y - p2y)) + + (3 * pct * pct * (p4y - p3y)); + return Math.atan2(tangentY, tangentX); + }; + } + + function QB1(t) { + return t * t; + } + + function QB2(t) { + return 2 * t * (1 - t); + } + + function QB3(t) { + return (1 - t) * (1 - t); + } + + function getPointOnQuadraticBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y) { + return function(pct) { + var c1 = QB1(pct), c2 = QB2(pct), c3 = QB3(pct); + return { + x: p3x * c1 + p2x * c2 + p1x * c3, + y: p3y * c1 + p2y * c2 + p1y * c3 + }; + }; + } + + function getTangentQuadraticIterator(p1x, p1y, p2x, p2y, p3x, p3y) { + return function (pct) { + var invT = 1 - pct, + tangentX = (2 * invT * (p2x - p1x)) + (2 * pct * (p3x - p2x)), + tangentY = (2 * invT * (p2y - p1y)) + (2 * pct * (p3y - p2y)); + return Math.atan2(tangentY, tangentX); + }; + } + + + // this will run over a path segment ( a cubic or quadratic segment) and approximate it + // with 100 segemnts. This will good enough to calculate the length of the curve + function pathIterator(iterator, x1, y1) { + var tempP = { x: x1, y: y1 }, p, tmpLen = 0, perc; + for (perc = 1; perc <= 100; perc += 1) { + p = iterator(perc / 100); + tmpLen += calcLineLength(tempP.x, tempP.y, p.x, p.y); + tempP = p; + } + return tmpLen; + } + + /** + * Given a pathInfo, and a distance in pixels, find the percentage from 0 to 1 + * that correspond to that pixels run over the path. + * The percentage will be then used to find the correct point on the canvas for the path. + * @param {Array} segInfo fabricJS collection of information on a parsed path + * @param {Number} distance from starting point, in pixels. + * @return {Object} info object with x and y ( the point on canvas ) and angle, the tangent on that point; + */ + function findPercentageForDistance(segInfo, distance) { + var perc = 0, tmpLen = 0, iterator = segInfo.iterator, tempP = { x: segInfo.x, y: segInfo.y }, + p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc; + // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100 + // the path + while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) { + p = iterator(perc); + lastPerc = perc; + nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y); + // compare tmpLen each cycle with distance, decide next perc to test. + if ((nextLen + tmpLen) > distance) { + // we discard this step and we make smaller steps. + nextStep /= 2; + perc -= nextStep; + } + else { + tempP = p; + perc += nextStep; + tmpLen += nextLen; + } + } + p.angle = angleFinder(lastPerc); + return p; + } + + /** + * Run over a parsed and simplifed path and extrac some informations. + * informations are length of each command and starting point + * @param {Array} path fabricJS parsed path commands + * @return {Array} path commands informations + */ + function getPathSegmentsInfo(path) { + var totalLength = 0, len = path.length, current, + //x2 and y2 are the coords of segment start + //x1 and y1 are the coords of the current point + x1 = 0, y1 = 0, x2 = 0, y2 = 0, info = [], iterator, tempInfo, angleFinder; + for (var i = 0; i < len; i++) { + current = path[i]; + tempInfo = { + x: x1, + y: y1, + command: current[0], + }; + switch (current[0]) { //first letter + case 'M': + tempInfo.length = 0; + x2 = x1 = current[1]; + y2 = y1 = current[2]; + break; + case 'L': + tempInfo.length = calcLineLength(x1, y1, current[1], current[2]); + x1 = current[1]; + y1 = current[2]; + break; + case 'C': + iterator = getPointOnCubicBezierIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + angleFinder = getTangentCubicIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + tempInfo.iterator = iterator; + tempInfo.angleFinder = angleFinder; + tempInfo.length = pathIterator(iterator, x1, y1); + x1 = current[5]; + y1 = current[6]; + break; + case 'Q': + iterator = getPointOnQuadraticBezierIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4] + ); + angleFinder = getTangentQuadraticIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4] + ); + tempInfo.iterator = iterator; + tempInfo.angleFinder = angleFinder; + tempInfo.length = pathIterator(iterator, x1, y1); + x1 = current[3]; + y1 = current[4]; + break; + case 'Z': + case 'z': + // we add those in order to ease calculations later + tempInfo.destX = x2; + tempInfo.destY = y2; + tempInfo.length = calcLineLength(x1, y1, x2, y2); + x1 = x2; + y1 = y2; + break; + } + totalLength += tempInfo.length; + info.push(tempInfo); + } + info.push({ length: totalLength, x: x1, y: y1 }); + return info; + } + + function getPointOnPath(path, distance, infos) { + if (!infos) { + infos = getPathSegmentsInfo(path); + } + var i = 0; + while ((distance - infos[i].length > 0) && i < (infos.length - 2)) { + distance -= infos[i].length; + i++; + } + // var distance = infos[infos.length - 1] * perc; + var segInfo = infos[i], segPercent = distance / segInfo.length, + command = segInfo.command, segment = path[i], info; + + switch (command) { + case 'M': + return { x: segInfo.x, y: segInfo.y, angle: 0 }; + case 'Z': + case 'z': + info = new fabric.Point(segInfo.x, segInfo.y).lerp( + new fabric.Point(segInfo.destX, segInfo.destY), + segPercent + ); + info.angle = Math.atan2(segInfo.destY - segInfo.y, segInfo.destX - segInfo.x); + return info; + case 'L': + info = new fabric.Point(segInfo.x, segInfo.y).lerp( + new fabric.Point(segment[1], segment[2]), + segPercent + ); + info.angle = Math.atan2(segment[2] - segInfo.y, segment[1] - segInfo.x); + return info; + case 'C': + return findPercentageForDistance(segInfo, distance); + case 'Q': + return findPercentageForDistance(segInfo, distance); + } + } + + /** + * + * @param {string} pathString + * @return {(string|number)[][]} An array of SVG path commands + * @example Usage + * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [ + * ['M', 3, 4], + * ['Q', 3, 5, 2, 1, 4, 0], + * ['Q', 9, 12, 2, 1, 4, 0], + * ]; + * + */ + function parsePath(pathString) { + var result = [], + coords = [], + currentPath, + parsed, + re = fabric.rePathCommand, + rNumber = '[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?\\s*', + rNumberCommaWsp = '(' + rNumber + ')' + fabric.commaWsp, + rFlagCommaWsp = '([01])' + fabric.commaWsp + '?', + rArcSeq = rNumberCommaWsp + '?' + rNumberCommaWsp + '?' + rNumberCommaWsp + rFlagCommaWsp + rFlagCommaWsp + + rNumberCommaWsp + '?(' + rNumber + ')', + regArcArgumentSequence = new RegExp(rArcSeq, 'g'), + match, + coordsStr, + // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) + path; + if (!pathString || !pathString.match) { + return result; + } + path = pathString.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + + for (var i = 0, coordsParsed, len = path.length; i < len; i++) { + currentPath = path[i]; + + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + + var command = currentPath.charAt(0); + coordsParsed = [command]; + + if (command.toLowerCase() === 'a') { + // arcs have special flags that apparently don't require spaces so handle special + for (var args; (args = regArcArgumentSequence.exec(coordsStr));) { + for (var j = 1; j < args.length; j++) { + coords.push(args[j]); + } + } + } + else { + while ((match = re.exec(coordsStr))) { + coords.push(match[0]); + } + } + + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + + var commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; + + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([command].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } + else { + result.push(coordsParsed); + } + } + + return result; + }; + + /** + * + * Converts points to a smooth SVG path + * @param {{ x: number,y: number }[]} points Array of points + * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value. + * @return {(string|number)[][]} An array of SVG path commands + */ + function getSmoothPathFromPoints(points, correction) { + var path = [], i, + p1 = new fabric.Point(points[0].x, points[0].y), + p2 = new fabric.Point(points[1].x, points[1].y), + len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; + correction = correction || 0; + + if (manyPoints) { + multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; + multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; + } + path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]); + for (i = 1; i < len; i++) { + if (!p1.eq(p2)) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]); + } + p1 = points[i]; + if ((i + 1) < points.length) { + p2 = points[i + 1]; + } + } + if (manyPoints) { + multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; + multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; + } + path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]); + return path; + } + /** + * Transform a path by transforming each segment. + * it has to be a simplified path or it won't work. + * WARNING: this depends from pathOffset for correct operation + * @param {Array} path fabricJS parsed and simplified path commands + * @param {Array} transform matrix that represent the transformation + * @param {Object} [pathOffset] the fabric.Path pathOffset + * @param {Number} pathOffset.x + * @param {Number} pathOffset.y + * @returns {Array} the transformed path + */ + function transformPath(path, transform, pathOffset) { + if (pathOffset) { + transform = fabric.util.multiplyTransformMatrices( + transform, + [1, 0, 0, 1, -pathOffset.x, -pathOffset.y] + ); + } + return path.map(function(pathSegment) { + var newSegment = pathSegment.slice(0), point = {}; + for (var i = 1; i < pathSegment.length - 1; i += 2) { + point.x = pathSegment[i]; + point.y = pathSegment[i + 1]; + point = fabric.util.transformPoint(point, transform); + newSegment[i] = point.x; + newSegment[i + 1] = point.y; + } + return newSegment; + }); + } + + /** + * Calculate bounding box of a elliptic-arc + * @deprecated + * @param {Number} fx start point of arc + * @param {Number} fy + * @param {Number} rx horizontal radius + * @param {Number} ry vertical radius + * @param {Number} rot angle of horizontal axis + * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points + * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction + * @param {Number} tx end point of arc + * @param {Number} ty + */ + function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) { + + var fromX = 0, fromY = 0, bound, bounds = [], + segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + + for (var i = 0, len = segs.length; i < len; i++) { + bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]); + bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); + bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); + fromX = segs[i][5]; + fromY = segs[i][6]; + } + return bounds; + }; + + /** + * Draws arc + * @deprecated + * @param {CanvasRenderingContext2D} ctx + * @param {Number} fx + * @param {Number} fy + * @param {Array} coords coords of the arc, without the front 'A/a' + */ + function drawArc(ctx, fx, fy, coords) { + coords = coords.slice(0).unshift('X'); // command A or a does not matter + var beziers = fromArcToBeziers(fx, fy, coords); + beziers.forEach(function(bezier) { + ctx.bezierCurveTo.apply(ctx, bezier.slice(1)); + }); + }; + + /** + * Join path commands to go back to svg format + * @param {Array} pathData fabricJS parsed path commands + * @return {String} joined path 'M 0 0 L 20 30' + */ + fabric.util.joinPath = function(pathData) { + return pathData.map(function (segment) { return segment.join(' '); }).join(' '); + }; + fabric.util.parsePath = parsePath; + fabric.util.makePathSimpler = makePathSimpler; + fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints; + fabric.util.getPathSegmentsInfo = getPathSegmentsInfo; + fabric.util.getBoundsOfCurve = getBoundsOfCurve; + fabric.util.getPointOnPath = getPointOnPath; + fabric.util.transformPath = transformPath; + /** + * Typo of `fromArcToBeziers` kept for not breaking the api once corrected. + * Will be removed in fabric 5.0 + * @deprecated + */ + fabric.util.fromArcToBeizers = fromArcToBeziers; + // kept because we do not want to make breaking changes. + // but useless and deprecated. + fabric.util.getBoundsOfArc = getBoundsOfArc; + fabric.util.drawArc = drawArc; +})(); + + +(function() { + + var slice = Array.prototype.slice; + + /** + * Invokes method on all items in a given array + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} method Name of a method to invoke + * @return {Array} + */ + function invoke(array, method) { + var args = slice.call(arguments, 2), result = []; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + + /** + * Finds maximum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {*} + */ + function max(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 >= value2; + }); + } + + /** + * Finds minimum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {*} + */ + function min(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 < value2; + }); + } + + /** + * @private + */ + function fill(array, value) { + var k = array.length; + while (k--) { + array[k] = value; + } + return array; + } + + /** + * @private + */ + function find(array, byProperty, condition) { + if (!array || array.length === 0) { + return; + } + + var i = array.length - 1, + result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (condition(array[i][byProperty], result)) { + result = array[i][byProperty]; + } + } + } + else { + while (i--) { + if (condition(array[i], result)) { + result = array[i]; + } + } + } + return result; + } + + /** + * @namespace fabric.util.array + */ + fabric.util.array = { + fill: fill, + invoke: invoke, + min: min, + max: max + }; + +})(); + + +(function() { + /** + * Copies all enumerable properties of one js object to another + * this does not and cannot compete with generic utils. + * Does not clone or extend fabric.Object subclasses. + * This is mostly for internal use and has extra handling for fabricJS objects + * it skips the canvas and group properties in deep cloning. + * @memberOf fabric.util.object + * @param {Object} destination Where to copy to + * @param {Object} source Where to copy from + * @param {Boolean} [deep] Whether to extend nested objects + * @return {Object} + */ + + function extend(destination, source, deep) { + // JScript DontEnum bug is not taken care of + // the deep clone is for internal use, is not meant to avoid + // javascript traps or cloning html element or self referenced objects. + if (deep) { + if (!fabric.isLikelyNode && source instanceof Element) { + // avoid cloning deep images, canvases, + destination = source; + } + else if (source instanceof Array) { + destination = []; + for (var i = 0, len = source.length; i < len; i++) { + destination[i] = extend({ }, source[i], deep); + } + } + else if (source && typeof source === 'object') { + for (var property in source) { + if (property === 'canvas' || property === 'group') { + // we do not want to clone this props at all. + // we want to keep the keys in the copy + destination[property] = null; + } + else if (source.hasOwnProperty(property)) { + destination[property] = extend({ }, source[property], deep); + } + } + } + else { + // this sounds odd for an extend but is ok for recursive use + destination = source; + } + } + else { + for (var property in source) { + destination[property] = source[property]; + } + } + return destination; + } + + /** + * Creates an empty object and copies all enumerable properties of another object to it + * This method is mostly for internal use, and not intended for duplicating shapes in canvas. + * @memberOf fabric.util.object + * @param {Object} object Object to clone + * @param {Boolean} [deep] Whether to clone nested objects + * @return {Object} + */ + + //TODO: this function return an empty object if you try to clone null + function clone(object, deep) { + return extend({ }, object, deep); + } + + /** @namespace fabric.util.object */ + fabric.util.object = { + extend: extend, + clone: clone + }; + fabric.util.object.extend(fabric.util, fabric.Observable); +})(); + + +(function() { + + /** + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string + */ + function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ''; + }); + } + + /** + * Capitalizes a string + * @memberOf fabric.util.string + * @param {String} string String to capitalize + * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized + * and other letters stay untouched, if false first letter is capitalized + * and other letters are converted to lowercase. + * @return {String} Capitalized version of a string + */ + function capitalize(string, firstLetterOnly) { + return string.charAt(0).toUpperCase() + + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); + } + + /** + * Escapes XML in a string + * @memberOf fabric.util.string + * @param {String} string String to escape + * @return {String} Escaped version of a string + */ + function escapeXml(string) { + return string.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + + /** + * Divide a string in the user perceived single units + * @memberOf fabric.util.string + * @param {String} textstring String to escape + * @return {Array} array containing the graphemes + */ + function graphemeSplit(textstring) { + var i = 0, chr, graphemes = []; + for (i = 0, chr; i < textstring.length; i++) { + if ((chr = getWholeChar(textstring, i)) === false) { + continue; + } + graphemes.push(chr); + } + return graphemes; + } + + // taken from mdn in the charAt doc page. + function getWholeChar(str, i) { + var code = str.charCodeAt(i); + + if (isNaN(code)) { + return ''; // Position not found + } + if (code < 0xD800 || code > 0xDFFF) { + return str.charAt(i); + } + + // High surrogate (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 <= code && code <= 0xDBFF) { + if (str.length <= (i + 1)) { + throw 'High surrogate without following low surrogate'; + } + var next = str.charCodeAt(i + 1); + if (0xDC00 > next || next > 0xDFFF) { + throw 'High surrogate without following low surrogate'; + } + return str.charAt(i) + str.charAt(i + 1); + } + // Low surrogate (0xDC00 <= code && code <= 0xDFFF) + if (i === 0) { + throw 'Low surrogate without preceding high surrogate'; + } + var prev = str.charCodeAt(i - 1); + + // (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 > prev || prev > 0xDBFF) { + throw 'Low surrogate without preceding high surrogate'; + } + // We can pass over low surrogates now as the second component + // in a pair which we have already processed + return false; + } + + + /** + * String utilities + * @namespace fabric.util.string + */ + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml, + graphemeSplit: graphemeSplit + }; +})(); + + +(function() { + + var slice = Array.prototype.slice, emptyFunction = function() { }, + + IS_DONTENUM_BUGGY = (function() { + for (var p in { toString: 1 }) { + if (p === 'toString') { + return false; + } + } + return true; + })(), + + /** @ignore */ + addMethods = function(klass, source, parent) { + for (var property in source) { + + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { + + klass.prototype[property] = (function(property) { + return function() { + + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + + if (property !== 'initialize') { + return returnValue; + } + }; + })(property); + } + else { + klass.prototype[property] = source[property]; + } + + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; + } + } + } + }; + + function Subclass() { } + + function callSuper(methodName) { + var parentMethod = null, + _this = this; + + // climb prototype chain to find method not equal to callee's method + while (_this.constructor.superclass) { + var superClassMethod = _this.constructor.superclass.prototype[methodName]; + if (_this[methodName] !== superClassMethod) { + parentMethod = superClassMethod; + break; + } + // eslint-disable-next-line + _this = _this.constructor.superclass.prototype; + } + + if (!parentMethod) { + return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this); + } + + return (arguments.length > 1) + ? parentMethod.apply(this, slice.call(arguments, 1)) + : parentMethod.call(this); + } + + /** + * Helper for creation of "classes". + * @memberOf fabric.util + * @param {Function} [parent] optional "Class" to inherit from + * @param {Object} [properties] Properties shared by all instances of this class + * (be careful modifying objects defined here as this would affect all instances) + */ + function createClass() { + var parent = null, + properties = slice.call(arguments, 0); + + if (typeof properties[0] === 'function') { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + Subclass.prototype = parent.prototype; + klass.prototype = new Subclass(); + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + klass.prototype.callSuper = callSuper; + return klass; + } + + fabric.util.createClass = createClass; +})(); + + +(function () { + // since ie11 can use addEventListener but they do not support options, i need to check + var couldUseAttachEvent = !!fabric.document.createElement('div').attachEvent, + touchEvents = ['touchstart', 'touchmove', 'touchend']; + /** + * Adds an event listener to an element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.addListener = function(element, eventName, handler, options) { + element && element.addEventListener(eventName, handler, couldUseAttachEvent ? false : options); + }; + + /** + * Removes an event listener from an element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.removeListener = function(element, eventName, handler, options) { + element && element.removeEventListener(eventName, handler, couldUseAttachEvent ? false : options); + }; + + function getTouchInfo(event) { + var touchProp = event.changedTouches; + if (touchProp && touchProp[0]) { + return touchProp[0]; + } + return event; + } + + fabric.util.getPointer = function(event) { + var element = event.target, + scroll = fabric.util.getScrollLeftTop(element), + _evt = getTouchInfo(event); + return { + x: _evt.clientX + scroll.left, + y: _evt.clientY + scroll.top + }; + }; + + fabric.util.isTouchEvent = function(event) { + return touchEvents.indexOf(event.type) > -1 || event.pointerType === 'touch'; + }; +})(); + + +(function () { + + /** + * Cross-browser wrapper for setting element's style + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {Object} styles + * @return {HTMLElement} Element that was passed as a first argument + */ + function setStyle(element, styles) { + var elementStyle = element.style; + if (!elementStyle) { + return element; + } + if (typeof styles === 'string') { + element.style.cssText += ';' + styles; + return styles.indexOf('opacity') > -1 + ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) + : element; + } + for (var property in styles) { + if (property === 'opacity') { + setOpacity(element, styles[property]); + } + else { + var normalizedProperty = (property === 'float' || property === 'cssFloat') + ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') + : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + + var parseEl = fabric.document.createElement('div'), + supportsOpacity = typeof parseEl.style.opacity === 'string', + supportsFilters = typeof parseEl.style.filter === 'string', + reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, + + /** @ignore */ + setOpacity = function (element) { return element; }; + + if (supportsOpacity) { + /** @ignore */ + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } + else if (supportsFilters) { + /** @ignore */ + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); + es.filter = es.filter.replace(reOpacity, value); + } + else { + es.filter += ' alpha(opacity=' + (value * 100) + ')'; + } + return element; + }; + } + + fabric.util.setStyle = setStyle; + +})(); + + +(function() { + + var _slice = Array.prototype.slice; + + /** + * Takes id and returns an element with that id (if one exists in a document) + * @memberOf fabric.util + * @param {String|HTMLElement} id + * @return {HTMLElement|null} + */ + function getById(id) { + return typeof id === 'string' ? fabric.document.getElementById(id) : id; + } + + var sliceCanConvertNodelists, + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; + + try { + sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } + catch (err) { } + + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + /** + * Creates specified element with specified attributes + * @memberOf fabric.util + * @param {String} tagName Type of an element to create + * @param {Object} [attributes] Attributes to set on an element + * @return {HTMLElement} Newly created element + */ + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === 'class') { + el.className = attributes[prop]; + } + else if (prop === 'for') { + el.htmlFor = attributes[prop]; + } + else { + el.setAttribute(prop, attributes[prop]); + } + } + return el; + } + + /** + * Adds class to an element + * @memberOf fabric.util + * @param {HTMLElement} element Element to add class to + * @param {String} className Class to add to an element + */ + function addClass(element, className) { + if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + element.className += (element.className ? ' ' : '') + className; + } + } + + /** + * Wraps element with another element + * @memberOf fabric.util + * @param {HTMLElement} element Element to wrap + * @param {HTMLElement|String} wrapper Element to wrap with + * @param {Object} [attributes] Attributes to set on a wrapper + * @return {HTMLElement} wrapper + */ + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === 'string') { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + + /** + * Returns element scroll offsets + * @memberOf fabric.util + * @param {HTMLElement} element Element to operate on + * @return {Object} Object with left/top values + */ + function getScrollLeftTop(element) { + + var left = 0, + top = 0, + docElement = fabric.document.documentElement, + body = fabric.document.body || { + scrollLeft: 0, scrollTop: 0 + }; + + // While loop checks (and then sets element to) .parentNode OR .host + // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, + // but the .parentNode of a root ShadowDOM node will always be null, instead + // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 + while (element && (element.parentNode || element.host)) { + + // Set element to element parent, or 'host' in case of ShadowDOM + element = element.parentNode || element.host; + + if (element === fabric.document) { + left = body.scrollLeft || docElement.scrollLeft || 0; + top = body.scrollTop || docElement.scrollTop || 0; + } + else { + left += element.scrollLeft || 0; + top += element.scrollTop || 0; + } + + if (element.nodeType === 1 && element.style.position === 'fixed') { + break; + } + } + + return { left: left, top: top }; + } + + /** + * Returns offset for a given element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element Element to get offset for + * @return {Object} Object with "left" and "top" properties + */ + function getElementOffset(element) { + var docElem, + doc = element && element.ownerDocument, + box = { left: 0, top: 0 }, + offset = { left: 0, top: 0 }, + scrollLeftTop, + offsetAttributes = { + borderLeftWidth: 'left', + borderTopWidth: 'top', + paddingLeft: 'left', + paddingTop: 'top' + }; + + if (!doc) { + return offset; + } + + for (var attr in offsetAttributes) { + offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; + } + + docElem = doc.documentElement; + if ( typeof element.getBoundingClientRect !== 'undefined' ) { + box = element.getBoundingClientRect(); + } + + scrollLeftTop = getScrollLeftTop(element); + + return { + left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, + top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top + }; + } + + /** + * Returns style attribute value of a given element + * @memberOf fabric.util + * @param {HTMLElement} element Element to get style attribute for + * @param {String} attr Style attribute to get for element + * @return {String} Style attribute value of the given element. + */ + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { + var style = fabric.document.defaultView.getComputedStyle(element, null); + return style ? style[attr] : undefined; + }; + } + else { + getElementStyle = function(element, attr) { + var value = element.style[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } + return value; + }; + } + + (function () { + var style = fabric.document.documentElement.style, + selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; + + /** + * Makes element unselectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make unselectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = 'none'; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = 'on'; + } + return element; + } + + /** + * Makes element selectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make selectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementSelectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ''; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = ''; + } + return element; + } + + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + + function getNodeCanvas(element) { + var impl = fabric.jsdomImplForWrapper(element); + return impl._canvas || impl._image; + }; + + function cleanUpJsdomNode(element) { + if (!fabric.isLikelyNode) { + return; + } + var impl = fabric.jsdomImplForWrapper(element); + if (impl) { + impl._image = null; + impl._canvas = null; + // unsure if necessary + impl._currentSrc = null; + impl._attributes = null; + impl._classList = null; + } + } + + function setImageSmoothing(ctx, value) { + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled + || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled; + ctx.imageSmoothingEnabled = value; + } + + /** + * setImageSmoothing sets the context imageSmoothingEnabled property. + * Used by canvas and by ImageObject. + * @memberOf fabric.util + * @since 4.0.0 + * @param {HTMLRenderingContext2D} ctx to set on + * @param {Boolean} value true or false + */ + fabric.util.setImageSmoothing = setImageSmoothing; + fabric.util.getById = getById; + fabric.util.toArray = toArray; + fabric.util.addClass = addClass; + fabric.util.makeElement = makeElement; + fabric.util.wrapElement = wrapElement; + fabric.util.getScrollLeftTop = getScrollLeftTop; + fabric.util.getElementOffset = getElementOffset; + fabric.util.getNodeCanvas = getNodeCanvas; + fabric.util.cleanUpJsdomNode = cleanUpJsdomNode; + +})(); + + +(function() { + + function addParamToUrl(url, param) { + return url + (/\?/.test(url) ? '&' : '?') + param; + } + + function emptyFn() { } + + /** + * Cross-browser abstraction for sending XMLHttpRequest + * @memberOf fabric.util + * @param {String} url URL to send XMLHttpRequest to + * @param {Object} [options] Options object + * @param {String} [options.method="GET"] + * @param {String} [options.parameters] parameters to append to url in GET or in body + * @param {String} [options.body] body to send with POST or PUT request + * @param {Function} options.onComplete Callback to invoke when request is completed + * @return {XMLHttpRequest} request + */ + function request(url, options) { + options || (options = { }); + + var method = options.method ? options.method.toUpperCase() : 'GET', + onComplete = options.onComplete || function() { }, + xhr = new fabric.window.XMLHttpRequest(), + body = options.body || options.parameters; + + /** @ignore */ + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + onComplete(xhr); + xhr.onreadystatechange = emptyFn; + } + }; + + if (method === 'GET') { + body = null; + if (typeof options.parameters === 'string') { + url = addParamToUrl(url, options.parameters); + } + } + + xhr.open(method, url, true); + + if (method === 'POST' || method === 'PUT') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + xhr.send(body); + return xhr; + } + + fabric.util.request = request; +})(); + + +/** + * Wrapper around `console.log` (when available) + * @param {*} [values] Values to log + */ +fabric.log = console.log; + +/** + * Wrapper around `console.warn` (when available) + * @param {*} [values] Values to log as a warning + */ +fabric.warn = console.warn; + + +(function() { + + function noop() { + return false; + } + + function defaultEasing(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + + /** + * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. + * @memberOf fabric.util + * @param {Object} [options] Animation options + * @param {Function} [options.onChange] Callback; invoked on every value change + * @param {Function} [options.onComplete] Callback; invoked when value change is completed + * @param {Number} [options.startValue=0] Starting value + * @param {Number} [options.endValue=100] Ending value + * @param {Number} [options.byValue=100] Value to modify the property by + * @param {Function} [options.easing] Easing function + * @param {Number} [options.duration=500] Duration of change (in ms) + * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. + * @returns {Function} abort function + */ + function animate(options) { + var cancel = false; + requestAnimFrame(function(timestamp) { + options || (options = { }); + + var start = timestamp || +new Date(), + duration = options.duration || 500, + finish = start + duration, time, + onChange = options.onChange || noop, + abort = options.abort || noop, + onComplete = options.onComplete || noop, + easing = options.easing || defaultEasing, + startValue = 'startValue' in options ? options.startValue : 0, + endValue = 'endValue' in options ? options.endValue : 100, + byValue = options.byValue || endValue - startValue; + + options.onStart && options.onStart(); + + (function tick(ticktime) { + // TODO: move abort call after calculation + // and pass (current,valuePerc, timePerc) as arguments + time = ticktime || +new Date(); + var currentTime = time > finish ? duration : (time - start), + timePerc = currentTime / duration, + current = easing(currentTime, startValue, byValue, duration), + valuePerc = Math.abs((current - startValue) / byValue); + if (cancel) { + return; + } + if (abort(current, valuePerc, timePerc)) { + // remove this in 4.0 + // does to even make sense to abort and run onComplete? + onComplete(endValue, 1, 1); + return; + } + if (time > finish) { + onChange(endValue, 1, 1); + onComplete(endValue, 1, 1); + return; + } + else { + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } + })(start); + }); + return function() { + cancel = true; + }; + } + + var _requestAnimFrame = fabric.window.requestAnimationFrame || + fabric.window.webkitRequestAnimationFrame || + fabric.window.mozRequestAnimationFrame || + fabric.window.oRequestAnimationFrame || + fabric.window.msRequestAnimationFrame || + function(callback) { + return fabric.window.setTimeout(callback, 1000 / 60); + }; + + var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; + + /** + * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method + * @memberOf fabric.util + * @param {Function} callback Callback to invoke + * @param {DOMElement} element optional Element to associate with animation + */ + function requestAnimFrame() { + return _requestAnimFrame.apply(fabric.window, arguments); + } + + function cancelAnimFrame() { + return _cancelAnimFrame.apply(fabric.window, arguments); + } + + fabric.util.animate = animate; + fabric.util.requestAnimFrame = requestAnimFrame; + fabric.util.cancelAnimFrame = cancelAnimFrame; +})(); + + +(function() { + // Calculate an in-between color. Returns a "rgba()" string. + // Credit: Edwin Martin + // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js + function calculateColor(begin, end, pos) { + var color = 'rgba(' + + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' + + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' + + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); + + color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); + color += ')'; + return color; + } + + /** + * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. + * @memberOf fabric.util + * @param {String} fromColor The starting color in hex or rgb(a) format. + * @param {String} toColor The starting color in hex or rgb(a) format. + * @param {Number} [duration] Duration of change (in ms). + * @param {Object} [options] Animation options + * @param {Function} [options.onChange] Callback; invoked on every value change + * @param {Function} [options.onComplete] Callback; invoked when value change is completed + * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used. + * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. + * @returns {Function} abort function + */ + function animateColor(fromColor, toColor, duration, options) { + var startColor = new fabric.Color(fromColor).getSource(), + endColor = new fabric.Color(toColor).getSource(), + originalOnComplete = options.onComplete, + originalOnChange = options.onChange; + options = options || {}; + + return fabric.util.animate(fabric.util.object.extend(options, { + duration: duration || 500, + startValue: startColor, + endValue: endColor, + byValue: endColor, + easing: function (currentTime, startValue, byValue, duration) { + var posValue = options.colorEasing + ? options.colorEasing(currentTime, duration) + : 1 - Math.cos(currentTime / duration * (Math.PI / 2)); + return calculateColor(startValue, byValue, posValue); + }, + // has to take in account for color restoring; + onComplete: function(current, valuePerc, timePerc) { + if (originalOnComplete) { + return originalOnComplete( + calculateColor(endColor, endColor, 0), + valuePerc, + timePerc + ); + } + }, + onChange: function(current, valuePerc, timePerc) { + if (originalOnChange) { + if (Array.isArray(current)) { + return originalOnChange( + calculateColor(current, current, 0), + valuePerc, + timePerc + ); + } + originalOnChange(current, valuePerc, timePerc); + } + } + })); + } + + fabric.util.animateColor = animateColor; + +})(); + + +(function() { + + function normalize(a, c, p, s) { + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + //handle the 0/0 case: + if (c === 0 && a === 0) { + s = p / (2 * Math.PI) * Math.asin(1); + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + } + return { a: a, c: c, p: p, s: s }; + } + + function elastic(opts, t, d) { + return opts.a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); + } + + /** + * Cubic easing out + * @memberOf fabric.util.ease + */ + function easeOutCubic(t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + } + + /** + * Cubic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutCubic(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; + } + + /** + * Quartic easing in + * @memberOf fabric.util.ease + */ + function easeInQuart(t, b, c, d) { + return c * (t /= d) * t * t * t + b; + } + + /** + * Quartic easing out + * @memberOf fabric.util.ease + */ + function easeOutQuart(t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + } + + /** + * Quartic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutQuart(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + } + + /** + * Quintic easing in + * @memberOf fabric.util.ease + */ + function easeInQuint(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + } + + /** + * Quintic easing out + * @memberOf fabric.util.ease + */ + function easeOutQuint(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + } + + /** + * Quintic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutQuint(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + } + + /** + * Sinusoidal easing in + * @memberOf fabric.util.ease + */ + function easeInSine(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + + /** + * Sinusoidal easing out + * @memberOf fabric.util.ease + */ + function easeOutSine(t, b, c, d) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; + } + + /** + * Sinusoidal easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutSine(t, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + } + + /** + * Exponential easing in + * @memberOf fabric.util.ease + */ + function easeInExpo(t, b, c, d) { + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + } + + /** + * Exponential easing out + * @memberOf fabric.util.ease + */ + function easeOutExpo(t, b, c, d) { + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + } + + /** + * Exponential easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutExpo(t, b, c, d) { + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + } + + /** + * Circular easing in + * @memberOf fabric.util.ease + */ + function easeInCirc(t, b, c, d) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + } + + /** + * Circular easing out + * @memberOf fabric.util.ease + */ + function easeOutCirc(t, b, c, d) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + } + + /** + * Circular easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutCirc(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + } + + /** + * Elastic easing in + * @memberOf fabric.util.ease + */ + function easeInElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + var opts = normalize(a, c, p, s); + return -elastic(opts, t, d) + b; + } + + /** + * Elastic easing out + * @memberOf fabric.util.ease + */ + function easeOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + var opts = normalize(a, c, p, s); + return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; + } + + /** + * Elastic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d / 2; + if (t === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } + var opts = normalize(a, c, p, s); + if (t < 1) { + return -0.5 * elastic(opts, t, d) + b; + } + return opts.a * Math.pow(2, -10 * (t -= 1)) * + Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; + } + + /** + * Backwards easing in + * @memberOf fabric.util.ease + */ + function easeInBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; + } + + /** + * Backwards easing out + * @memberOf fabric.util.ease + */ + function easeOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + } + + /** + * Backwards easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + } + + /** + * Bouncing easing in + * @memberOf fabric.util.ease + */ + function easeInBounce(t, b, c, d) { + return c - easeOutBounce (d - t, 0, c, d) + b; + } + + /** + * Bouncing easing out + * @memberOf fabric.util.ease + */ + function easeOutBounce(t, b, c, d) { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if (t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + } + + /** + * Bouncing easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutBounce(t, b, c, d) { + if (t < d / 2) { + return easeInBounce (t * 2, 0, c, d) * 0.5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + + /** + * Easing functions + * See Easing Equations by Robert Penner + * @namespace fabric.util.ease + */ + fabric.util.ease = { + + /** + * Quadratic easing in + * @memberOf fabric.util.ease + */ + easeInQuad: function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + + /** + * Quadratic easing out + * @memberOf fabric.util.ease + */ + easeOutQuad: function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + + /** + * Quadratic easing in and out + * @memberOf fabric.util.ease + */ + easeInOutQuad: function(t, b, c, d) { + t /= (d / 2); + if (t < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + + /** + * Cubic easing in + * @memberOf fabric.util.ease + */ + easeInCubic: function(t, b, c, d) { + return c * (t /= d) * t * t + b; + }, + + easeOutCubic: easeOutCubic, + easeInOutCubic: easeInOutCubic, + easeInQuart: easeInQuart, + easeOutQuart: easeOutQuart, + easeInOutQuart: easeInOutQuart, + easeInQuint: easeInQuint, + easeOutQuint: easeOutQuint, + easeInOutQuint: easeInOutQuint, + easeInSine: easeInSine, + easeOutSine: easeOutSine, + easeInOutSine: easeInOutSine, + easeInExpo: easeInExpo, + easeOutExpo: easeOutExpo, + easeInOutExpo: easeInOutExpo, + easeInCirc: easeInCirc, + easeOutCirc: easeOutCirc, + easeInOutCirc: easeInOutCirc, + easeInElastic: easeInElastic, + easeOutElastic: easeOutElastic, + easeInOutElastic: easeInOutElastic, + easeInBack: easeInBack, + easeOutBack: easeOutBack, + easeInOutBack: easeInOutBack, + easeInBounce: easeInBounce, + easeOutBounce: easeOutBounce, + easeInOutBounce: easeInOutBounce + }; + +})(); + + +(function(global) { + + 'use strict'; + + /** + * @name fabric + * @namespace + */ + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + parseUnit = fabric.util.parseUnit, + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, + + svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line', + 'image', 'text'], + svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'], + svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'], + svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'], + + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'letter-spacing': 'charSpacing', + 'paint-order': 'paintFirst', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-dashoffset': 'strokeDashOffset', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'textAnchor', + opacity: 'opacity', + 'clip-path': 'clipPath', + 'clip-rule': 'clipRule', + 'vector-effect': 'strokeUniform', + 'image-rendering': 'imageSmoothing', + }, + + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }, + + fSize = 'font-size', cPath = 'clip-path'; + + fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames); + fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements); + fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors); + fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents); + + fabric.cssRules = { }; + fabric.gradientDefs = { }; + fabric.clipPaths = { }; + + function normalizeAttr(attr) { + // transform attribute names + if (attr in attributesMap) { + return attributesMap[attr]; + } + return attr; + } + + function normalizeValue(attr, value, parentAttributes, fontSize) { + var isArray = Object.prototype.toString.call(value) === '[object Array]', + parsed; + + if ((attr === 'fill' || attr === 'stroke') && value === 'none') { + value = ''; + } + else if (attr === 'strokeUniform') { + return (value === 'non-scaling-stroke'); + } + else if (attr === 'strokeDashArray') { + if (value === 'none') { + value = null; + } + else { + value = value.replace(/,/g, ' ').split(/\s+/).map(parseFloat); + } + } + else if (attr === 'transformMatrix') { + if (parentAttributes && parentAttributes.transformMatrix) { + value = multiplyTransformMatrices( + parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); + } + else { + value = fabric.parseTransformAttribute(value); + } + } + else if (attr === 'visible') { + value = value !== 'none' && value !== 'hidden'; + // display=none on parent element always takes precedence over child element + if (parentAttributes && parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'opacity') { + value = parseFloat(value); + if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') { + value *= parentAttributes.opacity; + } + } + else if (attr === 'textAnchor' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } + else if (attr === 'charSpacing') { + // parseUnit returns px and we convert it to em + parsed = parseUnit(value, fontSize) / fontSize * 1000; + } + else if (attr === 'paintFirst') { + var fillIndex = value.indexOf('fill'); + var strokeIndex = value.indexOf('stroke'); + var value = 'fill'; + if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) { + value = 'stroke'; + } + else if (fillIndex === -1 && strokeIndex > -1) { + value = 'stroke'; + } + } + else if (attr === 'href' || attr === 'xlink:href' || attr === 'font') { + return value; + } + else if (attr === 'imageSmoothing') { + return (value === 'optimizeQuality'); + } + else { + parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); + } + + return (!isArray && isNaN(parsed) ? value : parsed); + } + + /** + * @private + */ + function getSvgRegex(arr) { + return new RegExp('^(' + arr.join('|') + ')\\b', 'i'); + } + + /** + * @private + * @param {Object} attributes Array of attributes to parse + */ + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + + if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') { + continue; + } + + if (typeof attributes[attr] === 'undefined') { + if (!fabric.Object.prototype[attr]) { + continue; + } + attributes[attr] = fabric.Object.prototype[attr]; + } + + if (attributes[attr].indexOf('url(') === 0) { + continue; + } + + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + } + return attributes; + } + + /** + * @private + */ + function _getMultipleNodes(doc, nodeNames) { + var nodeName, nodeArray = [], nodeList, i, len; + for (i = 0, len = nodeNames.length; i < len; i++) { + nodeName = nodeNames[i]; + nodeList = doc.getElementsByTagName(nodeName); + nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); + } + return nodeArray; + } + + /** + * Parses "transform" attribute, returning an array of values + * @static + * @function + * @memberOf fabric + * @param {String} attributeValue String containing attribute value + * @return {Array} Array of 6 elements representing transformation matrix + */ + fabric.parseTransformAttribute = (function() { + function rotateMatrix(matrix, args) { + var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]), + x = 0, y = 0; + if (args.length === 3) { + x = args[1]; + y = args[2]; + } + + matrix[0] = cos; + matrix[1] = sin; + matrix[2] = -sin; + matrix[3] = cos; + matrix[4] = x - (cos * x - sin * y); + matrix[5] = y - (sin * x + cos * y); + } + + function scaleMatrix(matrix, args) { + var multiplierX = args[0], + multiplierY = (args.length === 2) ? args[1] : args[0]; + + matrix[0] = multiplierX; + matrix[3] = multiplierY; + } + + function skewMatrix(matrix, args, pos) { + matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0])); + } + + function translateMatrix(matrix, args) { + matrix[4] = args[0]; + if (args.length === 2) { + matrix[5] = args[1]; + } + } + + // identity matrix + var iMatrix = fabric.iMatrix, + + // == begin transform regexp + number = fabric.reNum, + + commaWsp = fabric.commaWsp, + + skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', + + skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', + + rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', + + scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + matrix = '(?:(matrix)\\s*\\(\\s*' + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + + '\\s*\\))', + + transform = '(?:' + + matrix + '|' + + translate + '|' + + scale + '|' + + rotate + '|' + + skewX + '|' + + skewY + + ')', + + transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')', + + transformList = '^\\s*(?:' + transforms + '?)\\s*$', + + // http://www.w3.org/TR/SVG/coords.html#TransformAttribute + reTransformList = new RegExp(transformList), + // == end transform regexp + + reTransform = new RegExp(transform, 'g'); + + return function(attributeValue) { + + // start with identity matrix + var matrix = iMatrix.concat(), + matrices = []; + + // return if no argument was given or + // an argument does not match transform attribute regexp + if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { + return matrix; + } + + attributeValue.replace(reTransform, function(match) { + + var m = new RegExp(transform).exec(match).filter(function (match) { + // match !== '' && match != null + return (!!match); + }), + operation = m[1], + args = m.slice(2).map(parseFloat); + + switch (operation) { + case 'translate': + translateMatrix(matrix, args); + break; + case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); + rotateMatrix(matrix, args); + break; + case 'scale': + scaleMatrix(matrix, args); + break; + case 'skewX': + skewMatrix(matrix, args, 2); + break; + case 'skewY': + skewMatrix(matrix, args, 1); + break; + case 'matrix': + matrix = args; + break; + } + + // snapshot current matrix into matrices array + matrices.push(matrix.concat()); + // reset + matrix = iMatrix.concat(); + }); + + var combinedMatrix = matrices[0]; + while (matrices.length > 1) { + matrices.shift(); + combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); + } + return combinedMatrix; + }; + })(); + + /** + * @private + */ + function parseStyleString(style, oStyle) { + var attr, value; + style.replace(/;\s*$/, '').split(';').forEach(function (chunk) { + var pair = chunk.split(':'); + + attr = pair[0].trim().toLowerCase(); + value = pair[1].trim(); + + oStyle[attr] = value; + }); + } + + /** + * @private + */ + function parseStyleObject(style, oStyle) { + var attr, value; + for (var prop in style) { + if (typeof style[prop] === 'undefined') { + continue; + } + + attr = prop.toLowerCase(); + value = style[prop]; + + oStyle[attr] = value; + } + } + + /** + * @private + */ + function getGlobalStylesForElement(element, svgUid) { + var styles = { }; + for (var rule in fabric.cssRules[svgUid]) { + if (elementMatchesRule(element, rule.split(' '))) { + for (var property in fabric.cssRules[svgUid][rule]) { + styles[property] = fabric.cssRules[svgUid][rule][property]; + } + } + } + return styles; + } + + /** + * @private + */ + function elementMatchesRule(element, selectors) { + var firstMatching, parentMatching = true; + //start from rightmost selector. + firstMatching = selectorMatches(element, selectors.pop()); + if (firstMatching && selectors.length) { + parentMatching = doesSomeParentMatch(element, selectors); + } + return firstMatching && parentMatching && (selectors.length === 0); + } + + function doesSomeParentMatch(element, selectors) { + var selector, parentMatching = true; + while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { + if (parentMatching) { + selector = selectors.pop(); + } + element = element.parentNode; + parentMatching = selectorMatches(element, selector); + } + return selectors.length === 0; + } + + /** + * @private + */ + function selectorMatches(element, selector) { + var nodeName = element.nodeName, + classNames = element.getAttribute('class'), + id = element.getAttribute('id'), matcher, i; + // i check if a selector matches slicing away part from it. + // if i get empty string i should match + matcher = new RegExp('^' + nodeName, 'i'); + selector = selector.replace(matcher, ''); + if (id && selector.length) { + matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + if (classNames && selector.length) { + classNames = classNames.split(' '); + for (i = classNames.length; i--;) { + matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + } + return selector.length === 0; + } + + /** + * @private + * to support IE8 missing getElementById on SVGdocument and on node xmlDOM + */ + function elementById(doc, id) { + var el; + doc.getElementById && (el = doc.getElementById(id)); + if (el) { + return el; + } + var node, i, len, nodelist = doc.getElementsByTagName('*'); + for (i = 0, len = nodelist.length; i < len; i++) { + node = nodelist[i]; + if (id === node.getAttribute('id')) { + return node; + } + } + } + + /** + * @private + */ + function parseUseDirectives(doc) { + var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0; + while (nodelist.length && i < nodelist.length) { + var el = nodelist[i], + xlinkAttribute = el.getAttribute('xlink:href') || el.getAttribute('href'); + + if (xlinkAttribute === null) { + return; + } + + var xlink = xlinkAttribute.substr(1), + x = el.getAttribute('x') || 0, + y = el.getAttribute('y') || 0, + el2 = elementById(doc, xlink).cloneNode(true), + currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', + parentNode, + oldLength = nodelist.length, attr, + j, + attrs, + len, + namespace = fabric.svgNS; + + applyViewboxTransform(el2); + if (/^svg$/i.test(el2.nodeName)) { + var el3 = el2.ownerDocument.createElementNS(namespace, 'g'); + for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) { + attr = attrs.item(j); + el3.setAttributeNS(namespace, attr.nodeName, attr.nodeValue); + } + // el2.firstChild != null + while (el2.firstChild) { + el3.appendChild(el2.firstChild); + } + el2 = el3; + } + + for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) { + attr = attrs.item(j); + if (attr.nodeName === 'x' || attr.nodeName === 'y' || + attr.nodeName === 'xlink:href' || attr.nodeName === 'href') { + continue; + } + + if (attr.nodeName === 'transform') { + currentTrans = attr.nodeValue + ' ' + currentTrans; + } + else { + el2.setAttribute(attr.nodeName, attr.nodeValue); + } + } + + el2.setAttribute('transform', currentTrans); + el2.setAttribute('instantiated_by_use', '1'); + el2.removeAttribute('id'); + parentNode = el.parentNode; + parentNode.replaceChild(el2, el); + // some browsers do not shorten nodelist after replaceChild (IE8) + if (nodelist.length === oldLength) { + i++; + } + } + } + + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // matches, e.g.: +14.56e-12, etc. + var reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*' + + '$' + ); + + /** + * Add a element that envelop all child elements and makes the viewbox transformMatrix descend on all elements + */ + function applyViewboxTransform(element) { + if (!fabric.svgViewBoxElementsRegEx.test(element.nodeName)) { + return {}; + } + var viewBoxAttr = element.getAttribute('viewBox'), + scaleX = 1, + scaleY = 1, + minX = 0, + minY = 0, + viewBoxWidth, viewBoxHeight, matrix, el, + widthAttr = element.getAttribute('width'), + heightAttr = element.getAttribute('height'), + x = element.getAttribute('x') || 0, + y = element.getAttribute('y') || 0, + preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', + missingViewBox = (!viewBoxAttr || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), + missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), + toBeParsed = missingViewBox && missingDimAttr, + parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0; + + parsedDim.width = 0; + parsedDim.height = 0; + parsedDim.toBeParsed = toBeParsed; + + if (missingViewBox) { + if (((x || y) && element.parentNode && element.parentNode.nodeName !== '#document')) { + translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; + matrix = (element.getAttribute('transform') || '') + translateMatrix; + element.setAttribute('transform', matrix); + element.removeAttribute('x'); + element.removeAttribute('y'); + } + } + + if (toBeParsed) { + return parsedDim; + } + + if (missingViewBox) { + parsedDim.width = parseUnit(widthAttr); + parsedDim.height = parseUnit(heightAttr); + // set a transform for elements that have x y and are inner(only) SVGs + return parsedDim; + } + minX = -parseFloat(viewBoxAttr[1]); + minY = -parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); + parsedDim.minX = minX; + parsedDim.minY = minY; + parsedDim.viewBoxWidth = viewBoxWidth; + parsedDim.viewBoxHeight = viewBoxHeight; + if (!missingDimAttr) { + parsedDim.width = parseUnit(widthAttr); + parsedDim.height = parseUnit(heightAttr); + scaleX = parsedDim.width / viewBoxWidth; + scaleY = parsedDim.height / viewBoxHeight; + } + else { + parsedDim.width = viewBoxWidth; + parsedDim.height = viewBoxHeight; + } + + // default is to preserve aspect ratio + preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); + if (preserveAspectRatio.alignX !== 'none') { + //translate all container for the effect of Mid, Min, Max + if (preserveAspectRatio.meetOrSlice === 'meet') { + scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); + // calculate additional translation to move the viewbox + } + if (preserveAspectRatio.meetOrSlice === 'slice') { + scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY); + // calculate additional translation to move the viewbox + } + widthDiff = parsedDim.width - viewBoxWidth * scaleX; + heightDiff = parsedDim.height - viewBoxHeight * scaleX; + if (preserveAspectRatio.alignX === 'Mid') { + widthDiff /= 2; + } + if (preserveAspectRatio.alignY === 'Mid') { + heightDiff /= 2; + } + if (preserveAspectRatio.alignX === 'Min') { + widthDiff = 0; + } + if (preserveAspectRatio.alignY === 'Min') { + heightDiff = 0; + } + } + + if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) { + return parsedDim; + } + if ((x || y) && element.parentNode.nodeName !== '#document') { + translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; + } + + matrix = translateMatrix + ' matrix(' + scaleX + + ' 0' + + ' 0 ' + + scaleY + ' ' + + (minX * scaleX + widthDiff) + ' ' + + (minY * scaleY + heightDiff) + ') '; + // seems unused. + // parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix); + if (element.nodeName === 'svg') { + el = element.ownerDocument.createElementNS(fabric.svgNS, 'g'); + // element.firstChild != null + while (element.firstChild) { + el.appendChild(element.firstChild); + } + element.appendChild(el); + } + else { + el = element; + el.removeAttribute('x'); + el.removeAttribute('y'); + matrix = el.getAttribute('transform') + matrix; + } + el.setAttribute('transform', matrix); + return parsedDim; + } + + function hasAncestorWithNodeName(element, nodeName) { + while (element && (element = element.parentNode)) { + if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', '')) + && !element.getAttribute('instantiated_by_use')) { + return true; + } + } + return false; + } + + /** + * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @param {Function} callback Callback to call when parsing is finished; + * It's being passed an array of elements (parsed from a document). + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [parsingOptions] options for parsing document + * @param {String} [parsingOptions.crossOrigin] crossOrigin settings + */ + fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) { + if (!doc) { + return; + } + + parseUseDirectives(doc); + + var svgUid = fabric.Object.__uid++, i, len, + options = applyViewboxTransform(doc), + descendants = fabric.util.toArray(doc.getElementsByTagName('*')); + options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; + options.svgUid = svgUid; + + if (descendants.length === 0 && fabric.isLikelyNode) { + // we're likely in node, where "o3-xml" library fails to gEBTN("*") + // https://github.com/ajaxorg/node-o3-xml/issues/21 + descendants = doc.selectNodes('//*[name(.)!="svg"]'); + var arr = []; + for (i = 0, len = descendants.length; i < len; i++) { + arr[i] = descendants[i]; + } + descendants = arr; + } + + var elements = descendants.filter(function(el) { + applyViewboxTransform(el); + return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) && + !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement + }); + if (!elements || (elements && !elements.length)) { + callback && callback([], {}); + return; + } + var clipPaths = { }; + descendants.filter(function(el) { + return el.nodeName.replace('svg:', '') === 'clipPath'; + }).forEach(function(el) { + var id = el.getAttribute('id'); + clipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) { + return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')); + }); + }); + fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); + fabric.cssRules[svgUid] = fabric.getCSSRules(doc); + fabric.clipPaths[svgUid] = clipPaths; + // Precedence of rules: style > class > attribute + fabric.parseElements(elements, function(instances, elements) { + if (callback) { + callback(instances, options, elements, descendants); + delete fabric.gradientDefs[svgUid]; + delete fabric.cssRules[svgUid]; + delete fabric.clipPaths[svgUid]; + } + }, clone(options), reviver, parsingOptions); + }; + + function recursivelyParseGradientsXlink(doc, gradient) { + var gradientsAttrs = ['gradientTransform', 'x1', 'x2', 'y1', 'y2', 'gradientUnits', 'cx', 'cy', 'r', 'fx', 'fy'], + xlinkAttr = 'xlink:href', + xLink = gradient.getAttribute(xlinkAttr).substr(1), + referencedGradient = elementById(doc, xLink); + if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) { + recursivelyParseGradientsXlink(doc, referencedGradient); + } + gradientsAttrs.forEach(function(attr) { + if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) { + gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); + } + }); + if (!gradient.children.length) { + var referenceClone = referencedGradient.cloneNode(true); + while (referenceClone.firstChild) { + gradient.appendChild(referenceClone.firstChild); + } + } + gradient.removeAttribute(xlinkAttr); + } + + var reFontDeclaration = new RegExp( + '(normal|italic)?\\s*(normal|small-caps)?\\s*' + + '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + + fabric.reNum + + '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); + + extend(fabric, { + /** + * Parses a short font declaration, building adding its properties to a style object + * @static + * @function + * @memberOf fabric + * @param {String} value font declaration + * @param {Object} oStyle definition + */ + parseFontDeclaration: function(value, oStyle) { + var match = value.match(reFontDeclaration); + + if (!match) { + return; + } + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; + + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseUnit(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; + } + }, + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element + */ + getGradientDefs: function(doc) { + var tagArray = [ + 'linearGradient', + 'radialGradient', + 'svg:linearGradient', + 'svg:radialGradient'], + elList = _getMultipleNodes(doc, tagArray), + el, j = 0, gradientDefs = { }; + j = elList.length; + while (j--) { + el = elList[j]; + if (el.getAttribute('xlink:href')) { + recursivelyParseGradientsXlink(doc, el); + } + gradientDefs[el.getAttribute('id')] = el; + } + return gradientDefs; + }, + + /** + * Returns an object of attributes' name/value, given element and an array of attribute names; + * Parses parent "g" nodes recursively upwards. + * @static + * @memberOf fabric + * @param {DOMElement} element Element to parse + * @param {Array} attributes Array of attributes to parse + * @return {Object} object containing parsed attributes' names/values + */ + parseAttributes: function(element, attributes, svgUid) { + + if (!element) { + return; + } + + var value, + parentAttributes = { }, + fontSize, parentFontSize; + + if (typeof svgUid === 'undefined') { + svgUid = element.getAttribute('svgUid'); + } + // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards + if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); + } + + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + if (value) { // eslint-disable-line + memo[attr] = value; + } + return memo; + }, { }); + // add values parsed from style, which take precedence over attributes + // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) + var cssAttrs = extend( + getGlobalStylesForElement(element, svgUid), + fabric.parseStyleAttribute(element) + ); + ownAttributes = extend( + ownAttributes, + cssAttrs + ); + if (cssAttrs[cPath]) { + element.setAttribute(cPath, cssAttrs[cPath]); + } + fontSize = parentFontSize = parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE; + if (ownAttributes[fSize]) { + // looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers. + ownAttributes[fSize] = fontSize = parseUnit(ownAttributes[fSize], parentFontSize); + } + + var normalizedAttr, normalizedValue, normalizedStyle = {}; + for (var attr in ownAttributes) { + normalizedAttr = normalizeAttr(attr); + normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize); + normalizedStyle[normalizedAttr] = normalizedValue; + } + if (normalizedStyle && normalizedStyle.font) { + fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle); + } + var mergedAttrs = extend(parentAttributes, normalizedStyle); + return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs); + }, + + /** + * Transforms an array of svg elements to corresponding fabric.* instances + * @static + * @memberOf fabric + * @param {Array} elements Array of elements to parse + * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) + * @param {Object} [options] Options object + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + parseElements: function(elements, callback, options, reviver, parsingOptions) { + new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse(); + }, + + /** + * Parses "style" attribute, retuning an object with values + * @static + * @memberOf fabric + * @param {SVGElement} element Element to parse + * @return {Object} Objects with values parsed from style attribute of an element + */ + parseStyleAttribute: function(element) { + var oStyle = { }, + style = element.getAttribute('style'); + + if (!style) { + return oStyle; + } + + if (typeof style === 'string') { + parseStyleString(style, oStyle); + } + else { + parseStyleObject(style, oStyle); + } + + return oStyle; + }, + + /** + * Parses "points" attribute, returning an array of values + * @static + * @memberOf fabric + * @param {String} points points attribute string + * @return {Array} array of points + */ + parsePointsAttribute: function(points) { + + // points attribute is required and must not be empty + if (!points) { + return null; + } + + // replace commas with whitespace and remove bookending whitespace + points = points.replace(/,/g, ' ').trim(); + + points = points.split(/\s+/); + var parsedPoints = [], i, len; + + for (i = 0, len = points.length; i < len; i += 2) { + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); + } + + // odd number of points is an error + // if (parsedPoints.length % 2 !== 0) { + // return null; + // } + + return parsedPoints; + }, + + /** + * Returns CSS rules for a given SVG document + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} CSS rules of this document + */ + getCSSRules: function(doc) { + var styles = doc.getElementsByTagName('style'), i, len, + allRules = { }, rules; + + // very crude parsing of style contents + for (i = 0, len = styles.length; i < len; i++) { + var styleContents = styles[i].textContent; + + // remove comments + styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); + if (styleContents.trim() === '') { + continue; + } + rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); + rules = rules.map(function(rule) { return rule.trim(); }); + // eslint-disable-next-line no-loop-func + rules.forEach(function(rule) { + + var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), + ruleObj = { }, declaration = match[2].trim(), + propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); + + for (i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), + property = pair[0], + value = pair[1]; + ruleObj[property] = value; + } + rule = match[1]; + rule.split(',').forEach(function(_rule) { + _rule = _rule.replace(/^svg/i, '').trim(); + if (_rule === '') { + return; + } + if (allRules[_rule]) { + fabric.util.object.extend(allRules[_rule], ruleObj); + } + else { + allRules[_rule] = fabric.util.object.clone(ruleObj); + } + }); + }); + } + return allRules; + }, + + /** + * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. + * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) + * @memberOf fabric + * @param {String} url + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources + */ + loadSVGFromURL: function(url, callback, reviver, options) { + + url = url.replace(/^\n\s*/, '').trim(); + new fabric.util.request(url, { + method: 'get', + onComplete: onComplete + }); + + function onComplete(r) { + + var xml = r.responseXML; + if (!xml || !xml.documentElement) { + callback && callback(null); + return false; + } + + fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) { + callback && callback(results, _options, elements, allElements); + }, reviver, options); + } + }, + + /** + * Takes string corresponding to an SVG document, and parses it into a set of fabric objects + * @memberOf fabric + * @param {String} string + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources + */ + loadSVGFromString: function(string, callback, reviver, options) { + var parser = new fabric.window.DOMParser(), + doc = parser.parseFromString(string.trim(), 'text/xml'); + fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) { + callback(results, _options, elements, allElements); + }, reviver, options); + } + }); + +})( true ? exports : 0); + + +fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions, doc) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; + this.svgUid = (options && options.svgUid) || 0; + this.parsingOptions = parsingOptions; + this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g; + this.doc = doc; +}; + +(function(proto) { + proto.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; + this.createObjects(); + }; + + proto.createObjects = function() { + var _this = this; + this.elements.forEach(function(element, i) { + element.setAttribute('svgUid', _this.svgUid); + _this.createObject(element, i); + }); + }; + + proto.findTag = function(el) { + return fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))]; + }; + + proto.createObject = function(el, index) { + var klass = this.findTag(el); + if (klass && klass.fromElement) { + try { + klass.fromElement(el, this.createCallback(index, el), this.options); + } + catch (err) { + fabric.log(err); + } + } + else { + this.checkIfDone(); + } + }; + + proto.createCallback = function(index, el) { + var _this = this; + return function(obj) { + var _options; + _this.resolveGradient(obj, el, 'fill'); + _this.resolveGradient(obj, el, 'stroke'); + if (obj instanceof fabric.Image && obj._originalElement) { + _options = obj.parsePreserveAspectRatioAttribute(el); + } + obj._removeTransformMatrix(_options); + _this.resolveClipPath(obj, el); + _this.reviver && _this.reviver(el, obj); + _this.instances[index] = obj; + _this.checkIfDone(); + }; + }; + + proto.extractPropertyDefinition = function(obj, property, storage) { + var value = obj[property], regex = this.regexUrl; + if (!regex.test(value)) { + return; + } + regex.lastIndex = 0; + var id = regex.exec(value)[1]; + regex.lastIndex = 0; + return fabric[storage][this.svgUid][id]; + }; + + proto.resolveGradient = function(obj, el, property) { + var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); + if (gradientDef) { + var opacityAttr = el.getAttribute(property + '-opacity'); + var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); + obj.set(property, gradient); + } + }; + + proto.createClipPathCallback = function(obj, container) { + return function(_newObj) { + _newObj._removeTransformMatrix(); + _newObj.fillRule = _newObj.clipRule; + container.push(_newObj); + }; + }; + + proto.resolveClipPath = function(obj, usingElement) { + var clipPath = this.extractPropertyDefinition(obj, 'clipPath', 'clipPaths'), + element, klass, objTransformInv, container, gTransform, options; + if (clipPath) { + container = []; + objTransformInv = fabric.util.invertTransform(obj.calcTransformMatrix()); + // move the clipPath tag as sibling to the real element that is using it + var clipPathTag = clipPath[0].parentNode; + var clipPathOwner = usingElement; + while (clipPathOwner.parentNode && clipPathOwner.getAttribute('clip-path') !== obj.clipPath) { + clipPathOwner = clipPathOwner.parentNode; + } + clipPathOwner.parentNode.appendChild(clipPathTag); + for (var i = 0; i < clipPath.length; i++) { + element = clipPath[i]; + klass = this.findTag(element); + klass.fromElement( + element, + this.createClipPathCallback(obj, container), + this.options + ); + } + if (container.length === 1) { + clipPath = container[0]; + } + else { + clipPath = new fabric.Group(container); + } + gTransform = fabric.util.multiplyTransformMatrices( + objTransformInv, + clipPath.calcTransformMatrix() + ); + if (clipPath.clipPath) { + this.resolveClipPath(clipPath, clipPathOwner); + } + var options = fabric.util.qrDecompose(gTransform); + clipPath.flipX = false; + clipPath.flipY = false; + clipPath.set('scaleX', options.scaleX); + clipPath.set('scaleY', options.scaleY); + clipPath.angle = options.angle; + clipPath.skewX = options.skewX; + clipPath.skewY = 0; + clipPath.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center'); + obj.clipPath = clipPath; + } + else { + // if clip-path does not resolve to any element, delete the property. + delete obj.clipPath; + } + }; + + proto.checkIfDone = function() { + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + // eslint-disable-next-line no-eq-null, eqeqeq + return el != null; + }); + this.callback(this.instances, this.elements); + } + }; +})(fabric.ElementsParser.prototype); + + +(function(global) { + + 'use strict'; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Point) { + fabric.warn('fabric.Point is already defined'); + return; + } + + fabric.Point = Point; + + /** + * Point class + * @class fabric.Point + * @memberOf fabric + * @constructor + * @param {Number} x + * @param {Number} y + * @return {fabric.Point} thisArg + */ + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype = /** @lends fabric.Point.prototype */ { + + type: 'point', + + constructor: Point, + + /** + * Adds another point to this one and returns another one + * @param {fabric.Point} that + * @return {fabric.Point} new Point instance with added values + */ + add: function (that) { + return new Point(this.x + that.x, this.y + that.y); + }, + + /** + * Adds another point to this one + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + * @chainable + */ + addEquals: function (that) { + this.x += that.x; + this.y += that.y; + return this; + }, + + /** + * Adds value to this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} new Point with added value + */ + scalarAdd: function (scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + + /** + * Adds value to this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + scalarAddEquals: function (scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + + /** + * Subtracts another point from this point and returns a new one + * @param {fabric.Point} that + * @return {fabric.Point} new Point object with subtracted values + */ + subtract: function (that) { + return new Point(this.x - that.x, this.y - that.y); + }, + + /** + * Subtracts another point from this point + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + * @chainable + */ + subtractEquals: function (that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + + /** + * Subtracts value from this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} + */ + scalarSubtract: function (scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + + /** + * Subtracts value from this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + scalarSubtractEquals: function (scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + + /** + * Multiplies this point by a value and returns a new one + * TODO: rename in scalarMultiply in 2.0 + * @param {Number} scalar + * @return {fabric.Point} + */ + multiply: function (scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + + /** + * Multiplies this point by a value + * TODO: rename in scalarMultiplyEquals in 2.0 + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + multiplyEquals: function (scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + + /** + * Divides this point by a value and returns a new one + * TODO: rename in scalarDivide in 2.0 + * @param {Number} scalar + * @return {fabric.Point} + */ + divide: function (scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + + /** + * Divides this point by a value + * TODO: rename in scalarDivideEquals in 2.0 + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + divideEquals: function (scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + + /** + * Returns true if this point is equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + eq: function (that) { + return (this.x === that.x && this.y === that.y); + }, + + /** + * Returns true if this point is less than another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lt: function (that) { + return (this.x < that.x && this.y < that.y); + }, + + /** + * Returns true if this point is less than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lte: function (that) { + return (this.x <= that.x && this.y <= that.y); + }, + + /** + + * Returns true if this point is greater another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gt: function (that) { + return (this.x > that.x && this.y > that.y); + }, + + /** + * Returns true if this point is greater than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gte: function (that) { + return (this.x >= that.x && this.y >= that.y); + }, + + /** + * Returns new point which is the result of linear interpolation with this one and another one + * @param {fabric.Point} that + * @param {Number} t , position of interpolation, between 0 and 1 default 0.5 + * @return {fabric.Point} + */ + lerp: function (that, t) { + if (typeof t === 'undefined') { + t = 0.5; + } + t = Math.max(Math.min(1, t), 0); + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + + /** + * Returns distance from this point and another one + * @param {fabric.Point} that + * @return {Number} + */ + distanceFrom: function (that) { + var dx = this.x - that.x, + dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * Returns the point between this point and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + midPointFrom: function (that) { + return this.lerp(that); + }, + + /** + * Returns a new point which is the min of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + min: function (that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + + /** + * Returns a new point which is the max of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + max: function (that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + + /** + * Returns string representation of this point + * @return {String} + */ + toString: function () { + return this.x + ',' + this.y; + }, + + /** + * Sets x/y of this point + * @param {Number} x + * @param {Number} y + * @chainable + */ + setXY: function (x, y) { + this.x = x; + this.y = y; + return this; + }, + + /** + * Sets x of this point + * @param {Number} x + * @chainable + */ + setX: function (x) { + this.x = x; + return this; + }, + + /** + * Sets y of this point + * @param {Number} y + * @chainable + */ + setY: function (y) { + this.y = y; + return this; + }, + + /** + * Sets x/y of this point from another point + * @param {fabric.Point} that + * @chainable + */ + setFromPoint: function (that) { + this.x = that.x; + this.y = that.y; + return this; + }, + + /** + * Swaps x/y of this point and another point + * @param {fabric.Point} that + */ + swap: function (that) { + var x = this.x, + y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; + }, + + /** + * return a cloned instance of the point + * @return {fabric.Point} + */ + clone: function () { + return new Point(this.x, this.y); + } + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Intersection) { + fabric.warn('fabric.Intersection is already defined'); + return; + } + + /** + * Intersection class + * @class fabric.Intersection + * @memberOf fabric + * @constructor + */ + function Intersection(status) { + this.status = status; + this.points = []; + } + + fabric.Intersection = Intersection; + + fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { + + constructor: Intersection, + + /** + * Appends a point to intersection + * @param {fabric.Point} point + * @return {fabric.Intersection} thisArg + * @chainable + */ + appendPoint: function (point) { + this.points.push(point); + return this; + }, + + /** + * Appends points to intersection + * @param {Array} points + * @return {fabric.Intersection} thisArg + * @chainable + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + return this; + } + }; + + /** + * Checks if one line intersects another + * TODO: rename in intersectSegmentSegment + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {fabric.Point} b1 + * @param {fabric.Point} b2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection('Intersection'); + result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection(); + } + } + else { + if (uaT === 0 || ubT === 0) { + result = new Intersection('Coincident'); + } + else { + result = new Intersection('Parallel'); + } + } + return result; + }; + + /** + * Checks if line intersects polygon + * TODO: rename in intersectSegmentPolygon + * fix detection of coincident + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {Array} points + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { + var result = new Intersection(), + length = points.length, + b1, b2, inter, i; + + for (i = 0; i < length; i++) { + b1 = points[i]; + b2 = points[(i + 1) % length]; + inter = Intersection.intersectLineLine(a1, a2, b1, b2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + + /** + * Checks if polygon intersects another polygon + * @static + * @param {Array} points1 + * @param {Array} points2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection(), + length = points1.length, i; + + for (i = 0; i < length; i++) { + var a1 = points1[i], + a2 = points1[(i + 1) % length], + inter = Intersection.intersectLinePolygon(a1, a2, points2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + + /** + * Checks if polygon intersects rectangle + * @static + * @param {Array} points + * @param {fabric.Point} r1 + * @param {fabric.Point} r2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection(); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Color) { + fabric.warn('fabric.Color is already defined.'); + return; + } + + /** + * Color class + * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; + * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. + * + * @class fabric.Color + * @param {String} color optional in hex or rgb(a) or hsl format or from known color list + * @return {fabric.Color} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} + */ + function Color(color) { + if (!color) { + this.setSource([0, 0, 0, 1]); + } + else { + this._tryParsingColor(color); + } + } + + fabric.Color = Color; + + fabric.Color.prototype = /** @lends fabric.Color.prototype */ { + + /** + * @private + * @param {String|Array} color Color value to parse + */ + _tryParsingColor: function(color) { + var source; + + if (color in Color.colorNameMap) { + color = Color.colorNameMap[color]; + } + + if (color === 'transparent') { + source = [255, 255, 255, 0]; + } + + if (!source) { + source = Color.sourceFromHex(color); + } + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (!source) { + //if color is not recognize let's make black as canvas does + source = [0, 0, 0, 1]; + } + if (source) { + this.setSource(source); + } + }, + + /** + * Adapted from https://github.com/mjijackson + * @private + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @return {Array} Hsl color + */ + _rgbToHsl: function(r, g, b) { + r /= 255; g /= 255; b /= 255; + + var h, s, l, + max = fabric.util.array.max([r, g, b]), + min = fabric.util.array.min([r, g, b]); + + l = (max + min) / 2; + + if (max === min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [ + Math.round(h * 360), + Math.round(s * 100), + Math.round(l * 100) + ]; + }, + + /** + * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @return {Array} + */ + getSource: function() { + return this._source; + }, + + /** + * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @param {Array} source + */ + setSource: function(source) { + this._source = source; + }, + + /** + * Returns color representation in RGB format + * @return {String} ex: rgb(0-255,0-255,0-255) + */ + toRgb: function() { + var source = this.getSource(); + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + }, + + /** + * Returns color representation in RGBA format + * @return {String} ex: rgba(0-255,0-255,0-255,0-1) + */ + toRgba: function() { + var source = this.getSource(); + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + }, + + /** + * Returns color representation in HSL format + * @return {String} ex: hsl(0-360,0%-100%,0%-100%) + */ + toHsl: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + }, + + /** + * Returns color representation in HSLA format + * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) + */ + toHsla: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + }, + + /** + * Returns color representation in HEX format + * @return {String} ex: FF5555 + */ + toHex: function() { + var source = this.getSource(), r, g, b; + + r = source[0].toString(16); + r = (r.length === 1) ? ('0' + r) : r; + + g = source[1].toString(16); + g = (g.length === 1) ? ('0' + g) : g; + + b = source[2].toString(16); + b = (b.length === 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + + /** + * Returns color representation in HEXA format + * @return {String} ex: FF5555CC + */ + toHexa: function() { + var source = this.getSource(), a; + + a = Math.round(source[3] * 255); + a = a.toString(16); + a = (a.length === 1) ? ('0' + a) : a; + + return this.toHex() + a.toUpperCase(); + }, + + /** + * Gets value of alpha channel for this color + * @return {Number} 0-1 + */ + getAlpha: function() { + return this.getSource()[3]; + }, + + /** + * Sets value of alpha channel for this color + * @param {Number} alpha Alpha value 0-1 + * @return {fabric.Color} thisArg + */ + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + + /** + * Transforms color to its grayscale representation + * @return {fabric.Color} thisArg + */ + toGrayscale: function() { + var source = this.getSource(), + average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), + currentAlpha = source[3]; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Transforms color to its black and white representation + * @param {Number} threshold + * @return {fabric.Color} thisArg + */ + toBlackWhite: function(threshold) { + var source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + currentAlpha = source[3]; + + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Overlays color with another color + * @param {String|fabric.Color} otherColor + * @return {fabric.Color} thisArg + */ + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + + var result = [], + alpha = this.getAlpha(), + otherAlpha = 0.5, + source = this.getSource(), + otherSource = otherColor.getSource(), i; + + for (i = 0; i < 3; i++) { + result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); + } + + result[3] = alpha; + this.setSource(result); + return this; + } + }; + + /** + * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) + * @static + * @field + * @memberOf fabric.Color + */ + // eslint-disable-next-line max-len + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/i; + + /** + * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; + + /** + * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; + + /** + * Map of the 148 color names with HEX code + * @static + * @field + * @memberOf fabric.Color + * @see: https://www.w3.org/TR/css3-color/#svg-color + */ + fabric.Color.colorNameMap = { + aliceblue: '#F0F8FF', + antiquewhite: '#FAEBD7', + aqua: '#00FFFF', + aquamarine: '#7FFFD4', + azure: '#F0FFFF', + beige: '#F5F5DC', + bisque: '#FFE4C4', + black: '#000000', + blanchedalmond: '#FFEBCD', + blue: '#0000FF', + blueviolet: '#8A2BE2', + brown: '#A52A2A', + burlywood: '#DEB887', + cadetblue: '#5F9EA0', + chartreuse: '#7FFF00', + chocolate: '#D2691E', + coral: '#FF7F50', + cornflowerblue: '#6495ED', + cornsilk: '#FFF8DC', + crimson: '#DC143C', + cyan: '#00FFFF', + darkblue: '#00008B', + darkcyan: '#008B8B', + darkgoldenrod: '#B8860B', + darkgray: '#A9A9A9', + darkgrey: '#A9A9A9', + darkgreen: '#006400', + darkkhaki: '#BDB76B', + darkmagenta: '#8B008B', + darkolivegreen: '#556B2F', + darkorange: '#FF8C00', + darkorchid: '#9932CC', + darkred: '#8B0000', + darksalmon: '#E9967A', + darkseagreen: '#8FBC8F', + darkslateblue: '#483D8B', + darkslategray: '#2F4F4F', + darkslategrey: '#2F4F4F', + darkturquoise: '#00CED1', + darkviolet: '#9400D3', + deeppink: '#FF1493', + deepskyblue: '#00BFFF', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1E90FF', + firebrick: '#B22222', + floralwhite: '#FFFAF0', + forestgreen: '#228B22', + fuchsia: '#FF00FF', + gainsboro: '#DCDCDC', + ghostwhite: '#F8F8FF', + gold: '#FFD700', + goldenrod: '#DAA520', + gray: '#808080', + grey: '#808080', + green: '#008000', + greenyellow: '#ADFF2F', + honeydew: '#F0FFF0', + hotpink: '#FF69B4', + indianred: '#CD5C5C', + indigo: '#4B0082', + ivory: '#FFFFF0', + khaki: '#F0E68C', + lavender: '#E6E6FA', + lavenderblush: '#FFF0F5', + lawngreen: '#7CFC00', + lemonchiffon: '#FFFACD', + lightblue: '#ADD8E6', + lightcoral: '#F08080', + lightcyan: '#E0FFFF', + lightgoldenrodyellow: '#FAFAD2', + lightgray: '#D3D3D3', + lightgrey: '#D3D3D3', + lightgreen: '#90EE90', + lightpink: '#FFB6C1', + lightsalmon: '#FFA07A', + lightseagreen: '#20B2AA', + lightskyblue: '#87CEFA', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#B0C4DE', + lightyellow: '#FFFFE0', + lime: '#00FF00', + limegreen: '#32CD32', + linen: '#FAF0E6', + magenta: '#FF00FF', + maroon: '#800000', + mediumaquamarine: '#66CDAA', + mediumblue: '#0000CD', + mediumorchid: '#BA55D3', + mediumpurple: '#9370DB', + mediumseagreen: '#3CB371', + mediumslateblue: '#7B68EE', + mediumspringgreen: '#00FA9A', + mediumturquoise: '#48D1CC', + mediumvioletred: '#C71585', + midnightblue: '#191970', + mintcream: '#F5FFFA', + mistyrose: '#FFE4E1', + moccasin: '#FFE4B5', + navajowhite: '#FFDEAD', + navy: '#000080', + oldlace: '#FDF5E6', + olive: '#808000', + olivedrab: '#6B8E23', + orange: '#FFA500', + orangered: '#FF4500', + orchid: '#DA70D6', + palegoldenrod: '#EEE8AA', + palegreen: '#98FB98', + paleturquoise: '#AFEEEE', + palevioletred: '#DB7093', + papayawhip: '#FFEFD5', + peachpuff: '#FFDAB9', + peru: '#CD853F', + pink: '#FFC0CB', + plum: '#DDA0DD', + powderblue: '#B0E0E6', + purple: '#800080', + rebeccapurple: '#663399', + red: '#FF0000', + rosybrown: '#BC8F8F', + royalblue: '#4169E1', + saddlebrown: '#8B4513', + salmon: '#FA8072', + sandybrown: '#F4A460', + seagreen: '#2E8B57', + seashell: '#FFF5EE', + sienna: '#A0522D', + silver: '#C0C0C0', + skyblue: '#87CEEB', + slateblue: '#6A5ACD', + slategray: '#708090', + slategrey: '#708090', + snow: '#FFFAFA', + springgreen: '#00FF7F', + steelblue: '#4682B4', + tan: '#D2B48C', + teal: '#008080', + thistle: '#D8BFD8', + tomato: '#FF6347', + turquoise: '#40E0D0', + violet: '#EE82EE', + wheat: '#F5DEB3', + white: '#FFFFFF', + whitesmoke: '#F5F5F5', + yellow: '#FFFF00', + yellowgreen: '#9ACD32' + }; + + /** + * @private + * @param {Number} p + * @param {Number} q + * @param {Number} t + * @return {Number} + */ + function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; + } + + /** + * Returns new color object, when given a color in RGB format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255) + * @return {fabric.Color} + */ + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) + * @return {Array} source + */ + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), + g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), + b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + + return [ + parseInt(r, 10), + parseInt(g, 10), + parseInt(b, 10), + match[4] ? parseFloat(match[4]) : 1 + ]; + } + }; + + /** + * Returns new color object, when given a color in RGBA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromRgba = Color.fromRgb; + + /** + * Returns new color object, when given a color in HSL format + * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) + * @memberOf fabric.Color + * @return {fabric.Color} + */ + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. + * Adapted from https://github.com/mjijackson + * @memberOf fabric.Color + * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) + * @return {Array} source + * @see http://http://www.w3.org/TR/css3-color/#hsl-color + */ + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) { + return; + } + + var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; + + if (s === 0) { + r = g = b = l; + } + else { + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + match[4] ? parseFloat(match[4]) : 1 + ]; + }; + + /** + * Returns new color object, when given a color in HSLA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromHsla = Color.fromHsl; + + /** + * Returns new color object, when given a color in HEX format + * @static + * @memberOf fabric.Color + * @param {String} color Color value ex: FF5555 + * @return {fabric.Color} + */ + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format + * @static + * @memberOf fabric.Color + * @param {String} color ex: FF5555 or FF5544CC (RGBa) + * @return {Array} source + */ + fabric.Color.sourceFromHex = function(color) { + if (color.match(Color.reHex)) { + var value = color.slice(color.indexOf('#') + 1), + isShortNotation = (value.length === 3 || value.length === 4), + isRGBa = (value.length === 8 || value.length === 4), + r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), + g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), + b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), + a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; + + return [ + parseInt(r, 16), + parseInt(g, 16), + parseInt(b, 16), + parseFloat((parseInt(a, 16) / 255).toFixed(2)) + ]; + } + }; + + /** + * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) + * @static + * @memberOf fabric.Color + * @param {Array} source + * @return {fabric.Color} + */ + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + scaleMap = ['e', 'se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'], + skewMap = ['ns', 'nesw', 'ew', 'nwse'], + controls = {}, + LEFT = 'left', TOP = 'top', RIGHT = 'right', BOTTOM = 'bottom', CENTER = 'center', + opposite = { + top: BOTTOM, + bottom: TOP, + left: RIGHT, + right: LEFT, + center: CENTER, + }, radiansToDegrees = fabric.util.radiansToDegrees, + sign = (Math.sign || function(x) { return ((x > 0) - (x < 0)) || +x; }); + + /** + * Combine control position and object angle to find the control direction compared + * to the object center. + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + * @param {fabric.Control} control the control class + * @return {Number} 0 - 7 a quadrant number + */ + function findCornerQuadrant(fabricObject, control) { + var cornerAngle = fabricObject.angle + radiansToDegrees(Math.atan2(control.y, control.x)) + 360; + return Math.round((cornerAngle % 360) / 45); + } + + function fireEvent(eventName, options) { + var target = options.transform.target, + canvas = target.canvas, + canvasOptions = fabric.util.object.clone(options); + canvasOptions.target = target; + canvas && canvas.fire('object:' + eventName, canvasOptions); + target.fire(eventName, options); + } + + /** + * Inspect event and fabricObject properties to understand if the scaling action + * @param {Event} eventData from the user action + * @param {fabric.Object} fabricObject the fabric object about to scale + * @return {Boolean} true if scale is proportional + */ + function scaleIsProportional(eventData, fabricObject) { + var canvas = fabricObject.canvas, uniScaleKey = canvas.uniScaleKey, + uniformIsToggled = eventData[uniScaleKey]; + return (canvas.uniformScaling && !uniformIsToggled) || + (!canvas.uniformScaling && uniformIsToggled); + } + + /** + * Checks if transform is centered + * @param {Object} transform transform data + * @return {Boolean} true if transform is centered + */ + function isTransformCentered(transform) { + return transform.originX === CENTER && transform.originY === CENTER; + } + + /** + * Inspect fabricObject to understand if the current scaling action is allowed + * @param {fabric.Object} fabricObject the fabric object about to scale + * @param {String} by 'x' or 'y' or '' + * @param {Boolean} scaleProportionally true if we are trying to scale proportionally + * @return {Boolean} true if scaling is not allowed at current conditions + */ + function scalingIsForbidden(fabricObject, by, scaleProportionally) { + var lockX = fabricObject.lockScalingX, lockY = fabricObject.lockScalingY; + if (lockX && lockY) { + return true; + } + if (!by && (lockX || lockY) && scaleProportionally) { + return true; + } + if (lockX && by === 'x') { + return true; + } + if (lockY && by === 'y') { + return true; + } + return false; + } + + /** + * return the correct cursor style for the scale action + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function scaleCursorStyleHandler(eventData, control, fabricObject) { + var notAllowed = 'not-allowed', + scaleProportionally = scaleIsProportional(eventData, fabricObject), + by = ''; + if (control.x !== 0 && control.y === 0) { + by = 'x'; + } + else if (control.x === 0 && control.y !== 0) { + by = 'y'; + } + if (scalingIsForbidden(fabricObject, by, scaleProportionally)) { + return notAllowed; + } + var n = findCornerQuadrant(fabricObject, control); + return scaleMap[n] + '-resize'; + } + + /** + * return the correct cursor style for the skew action + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function skewCursorStyleHandler(eventData, control, fabricObject) { + var notAllowed = 'not-allowed'; + if (control.x !== 0 && fabricObject.lockSkewingY) { + return notAllowed; + } + if (control.y !== 0 && fabricObject.lockSkewingX) { + return notAllowed; + } + var n = findCornerQuadrant(fabricObject, control) % 4; + return skewMap[n] + '-resize'; + } + + /** + * Combine skew and scale style handlers to cover fabric standard use case + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function scaleSkewCursorStyleHandler(eventData, control, fabricObject) { + if (eventData[fabricObject.canvas.altActionKey]) { + return controls.skewCursorStyleHandler(eventData, control, fabricObject); + } + return controls.scaleCursorStyleHandler(eventData, control, fabricObject); + } + + /** + * Inspect event, control and fabricObject to return the correct action name + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} an action name + */ + function scaleOrSkewActionName(eventData, control, fabricObject) { + var isAlternative = eventData[fabricObject.canvas.altActionKey]; + if (control.x === 0) { + // then is scaleY or skewX + return isAlternative ? 'skewX' : 'scaleY'; + } + if (control.y === 0) { + // then is scaleY or skewX + return isAlternative ? 'skewY' : 'scaleX'; + } + } + + /** + * Find the correct style for the control that is used for rotation. + * this function is very simple and it just take care of not-allowed or standard cursor + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function rotationStyleHandler(eventData, control, fabricObject) { + if (fabricObject.lockRotation) { + return 'not-allowed'; + } + return control.cursorStyle; + } + + function commonEventInfo(eventData, transform, x, y) { + return { + e: eventData, + transform: transform, + pointer: { + x: x, + y: y, + } + }; + } + + /** + * Wrap an action handler with saving/restoring object position on the transform. + * this is the code that permits to objects to keep their position while transforming. + * @param {Function} actionHandler the function to wrap + * @return {Function} a function with an action handler signature + */ + function wrapWithFixedAnchor(actionHandler) { + return function(eventData, transform, x, y) { + var target = transform.target, centerPoint = target.getCenterPoint(), + constraint = target.translateToOriginPoint(centerPoint, transform.originX, transform.originY), + actionPerformed = actionHandler(eventData, transform, x, y); + target.setPositionByOrigin(constraint, transform.originX, transform.originY); + return actionPerformed; + }; + } + + /** + * Wrap an action handler with firing an event if the action is performed + * @param {Function} actionHandler the function to wrap + * @return {Function} a function with an action handler signature + */ + function wrapWithFireEvent(eventName, actionHandler) { + return function(eventData, transform, x, y) { + var actionPerformed = actionHandler(eventData, transform, x, y); + if (actionPerformed) { + fireEvent(eventName, commonEventInfo(eventData, transform, x, y)); + } + return actionPerformed; + }; + } + + /** + * Transforms a point described by x and y in a distance from the top left corner of the object + * bounding box. + * @param {Object} transform + * @param {String} originX + * @param {String} originY + * @param {number} x + * @param {number} y + * @return {Fabric.Point} the normalized point + */ + function getLocalPoint(transform, originX, originY, x, y) { + var target = transform.target, + control = target.controls[transform.corner], + zoom = target.canvas.getZoom(), + padding = target.padding / zoom, + localPoint = target.toLocalPoint(new fabric.Point(x, y), originX, originY); + if (localPoint.x >= padding) { + localPoint.x -= padding; + } + if (localPoint.x <= -padding) { + localPoint.x += padding; + } + if (localPoint.y >= padding) { + localPoint.y -= padding; + } + if (localPoint.y <= padding) { + localPoint.y += padding; + } + localPoint.x -= control.offsetX; + localPoint.y -= control.offsetY; + return localPoint; + } + + /** + * Detect if the fabric object is flipped on one side. + * @param {fabric.Object} target + * @return {Boolean} true if one flip, but not two. + */ + function targetHasOneFlip(target) { + return target.flipX !== target.flipY; + } + + /** + * Utility function to compensate the scale factor when skew is applied on both axes + * @private + */ + function compensateScaleForSkew(target, oppositeSkew, scaleToCompensate, axis, reference) { + if (target[oppositeSkew] !== 0) { + var newDim = target._getTransformedDimensions()[axis]; + var newValue = reference / newDim * target[scaleToCompensate]; + target.set(scaleToCompensate, newValue); + } + } + + /** + * Action handler for skewing on the X axis + * @private + */ + function skewObjectX(eventData, transform, x, y) { + var target = transform.target, + // find how big the object would be, if there was no skewX. takes in account scaling + dimNoSkew = target._getTransformedDimensions(0, target.skewY), + localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + // the mouse is in the center of the object, and we want it to stay there. + // so the object will grow twice as much as the mouse. + // this makes the skew growth to localPoint * 2 - dimNoSkew. + totalSkewSize = Math.abs(localPoint.x * 2) - dimNoSkew.x, + currentSkew = target.skewX, newSkew; + if (totalSkewSize < 2) { + // let's make it easy to go back to position 0. + newSkew = 0; + } + else { + newSkew = radiansToDegrees( + Math.atan2((totalSkewSize / target.scaleX), (dimNoSkew.y / target.scaleY)) + ); + // now we have to find the sign of the skew. + // it mostly depend on the origin of transformation. + if (transform.originX === LEFT && transform.originY === BOTTOM) { + newSkew = -newSkew; + } + if (transform.originX === RIGHT && transform.originY === TOP) { + newSkew = -newSkew; + } + if (targetHasOneFlip(target)) { + newSkew = -newSkew; + } + } + var hasSkewed = currentSkew !== newSkew; + if (hasSkewed) { + var dimBeforeSkewing = target._getTransformedDimensions().y; + target.set('skewX', newSkew); + compensateScaleForSkew(target, 'skewY', 'scaleY', 'y', dimBeforeSkewing); + } + return hasSkewed; + } + + /** + * Action handler for skewing on the Y axis + * @private + */ + function skewObjectY(eventData, transform, x, y) { + var target = transform.target, + // find how big the object would be, if there was no skewX. takes in account scaling + dimNoSkew = target._getTransformedDimensions(target.skewX, 0), + localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + // the mouse is in the center of the object, and we want it to stay there. + // so the object will grow twice as much as the mouse. + // this makes the skew growth to localPoint * 2 - dimNoSkew. + totalSkewSize = Math.abs(localPoint.y * 2) - dimNoSkew.y, + currentSkew = target.skewY, newSkew; + if (totalSkewSize < 2) { + // let's make it easy to go back to position 0. + newSkew = 0; + } + else { + newSkew = radiansToDegrees( + Math.atan2((totalSkewSize / target.scaleY), (dimNoSkew.x / target.scaleX)) + ); + // now we have to find the sign of the skew. + // it mostly depend on the origin of transformation. + if (transform.originX === LEFT && transform.originY === BOTTOM) { + newSkew = -newSkew; + } + if (transform.originX === RIGHT && transform.originY === TOP) { + newSkew = -newSkew; + } + if (targetHasOneFlip(target)) { + newSkew = -newSkew; + } + } + var hasSkewed = currentSkew !== newSkew; + if (hasSkewed) { + var dimBeforeSkewing = target._getTransformedDimensions().x; + target.set('skewY', newSkew); + compensateScaleForSkew(target, 'skewX', 'scaleX', 'x', dimBeforeSkewing); + } + return hasSkewed; + } + + /** + * Wrapped Action handler for skewing on the Y axis, takes care of the + * skew direction and determine the correct transform origin for the anchor point + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function skewHandlerX(eventData, transform, x, y) { + // step1 figure out and change transform origin. + // if skewX > 0 and originY bottom we anchor on right + // if skewX > 0 and originY top we anchor on left + // if skewX < 0 and originY bottom we anchor on left + // if skewX < 0 and originY top we anchor on right + // if skewX is 0, we look for mouse position to understand where are we going. + var target = transform.target, currentSkew = target.skewX, originX, originY = transform.originY; + if (target.lockSkewingX) { + return false; + } + if (currentSkew === 0) { + var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); + if (localPointFromCenter.x > 0) { + // we are pulling right, anchor left; + originX = LEFT; + } + else { + // we are pulling right, anchor right + originX = RIGHT; + } + } + else { + if (currentSkew > 0) { + originX = originY === TOP ? LEFT : RIGHT; + } + if (currentSkew < 0) { + originX = originY === TOP ? RIGHT : LEFT; + } + // is the object flipped on one side only? swap the origin. + if (targetHasOneFlip(target)) { + originX = originX === LEFT ? RIGHT : LEFT; + } + } + + // once we have the origin, we find the anchor point + transform.originX = originX; + var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectX)); + return finalHandler(eventData, transform, x, y); + } + + /** + * Wrapped Action handler for skewing on the Y axis, takes care of the + * skew direction and determine the correct transform origin for the anchor point + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function skewHandlerY(eventData, transform, x, y) { + // step1 figure out and change transform origin. + // if skewY > 0 and originX left we anchor on top + // if skewY > 0 and originX right we anchor on bottom + // if skewY < 0 and originX left we anchor on bottom + // if skewY < 0 and originX right we anchor on top + // if skewY is 0, we look for mouse position to understand where are we going. + var target = transform.target, currentSkew = target.skewY, originY, originX = transform.originX; + if (target.lockSkewingY) { + return false; + } + if (currentSkew === 0) { + var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); + if (localPointFromCenter.y > 0) { + // we are pulling down, anchor up; + originY = TOP; + } + else { + // we are pulling up, anchor down + originY = BOTTOM; + } + } + else { + if (currentSkew > 0) { + originY = originX === LEFT ? TOP : BOTTOM; + } + if (currentSkew < 0) { + originY = originX === LEFT ? BOTTOM : TOP; + } + // is the object flipped on one side only? swap the origin. + if (targetHasOneFlip(target)) { + originY = originY === TOP ? BOTTOM : TOP; + } + } + + // once we have the origin, we find the anchor point + transform.originY = originY; + var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectY)); + return finalHandler(eventData, transform, x, y); + } + + /** + * Action handler for rotation and snapping, without anchor point. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + * @private + */ + function rotationWithSnapping(eventData, transform, x, y) { + var t = transform, + target = t.target, + pivotPoint = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); + + if (target.lockRotation) { + return false; + } + + var lastAngle = Math.atan2(t.ey - pivotPoint.y, t.ex - pivotPoint.x), + curAngle = Math.atan2(y - pivotPoint.y, x - pivotPoint.x), + angle = radiansToDegrees(curAngle - lastAngle + t.theta), + hasRotated = true; + + if (target.snapAngle > 0) { + var snapAngle = target.snapAngle, + snapThreshold = target.snapThreshold || snapAngle, + rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, + leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; + + if (Math.abs(angle - leftAngleLocked) < snapThreshold) { + angle = leftAngleLocked; + } + else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { + angle = rightAngleLocked; + } + } + + // normalize angle to positive value + if (angle < 0) { + angle = 360 + angle; + } + angle %= 360; + + hasRotated = target.angle !== angle; + target.angle = angle; + return hasRotated; + } + + /** + * Basic scaling logic, reused with different constrain for scaling X,Y, freely or equally. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @param {Object} options additional information for scaling + * @param {String} options.by 'x', 'y', 'equally' or '' to indicate type of scaling + * @return {Boolean} true if some change happened + * @private + */ + function scaleObject(eventData, transform, x, y, options) { + options = options || {}; + var target = transform.target, + lockScalingX = target.lockScalingX, lockScalingY = target.lockScalingY, + by = options.by, newPoint, scaleX, scaleY, dim, + scaleProportionally = scaleIsProportional(eventData, target), + forbidScaling = scalingIsForbidden(target, by, scaleProportionally), + signX, signY, gestureScale = transform.gestureScale; + + if (forbidScaling) { + return false; + } + if (gestureScale) { + scaleX = transform.scaleX * gestureScale; + scaleY = transform.scaleY * gestureScale; + } + else { + newPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y); + // use of sign: We use sign to detect change of direction of an action. sign usually change when + // we cross the origin point with the mouse. So a scale flip for example. There is an issue when scaling + // by center and scaling using one middle control ( default: mr, mt, ml, mb), the mouse movement can easily + // cross many time the origin point and flip the object. so we need a way to filter out the noise. + // This ternary here should be ok to filter out X scaling when we want Y only and vice versa. + signX = by !== 'y' ? sign(newPoint.x) : 1; + signY = by !== 'x' ? sign(newPoint.y) : 1; + if (!transform.signX) { + transform.signX = signX; + } + if (!transform.signY) { + transform.signY = signY; + } + + if (target.lockScalingFlip && + (transform.signX !== signX || transform.signY !== signY) + ) { + return false; + } + + dim = target._getTransformedDimensions(); + // missing detection of flip and logic to switch the origin + if (scaleProportionally && !by) { + // uniform scaling + var distance = Math.abs(newPoint.x) + Math.abs(newPoint.y), + original = transform.original, + originalDistance = Math.abs(dim.x * original.scaleX / target.scaleX) + + Math.abs(dim.y * original.scaleY / target.scaleY), + scale = distance / originalDistance; + scaleX = original.scaleX * scale; + scaleY = original.scaleY * scale; + } + else { + scaleX = Math.abs(newPoint.x * target.scaleX / dim.x); + scaleY = Math.abs(newPoint.y * target.scaleY / dim.y); + } + // if we are scaling by center, we need to double the scale + if (isTransformCentered(transform)) { + scaleX *= 2; + scaleY *= 2; + } + if (transform.signX !== signX && by !== 'y') { + transform.originX = opposite[transform.originX]; + scaleX *= -1; + transform.signX = signX; + } + if (transform.signY !== signY && by !== 'x') { + transform.originY = opposite[transform.originY]; + scaleY *= -1; + transform.signY = signY; + } + } + // minScale is taken are in the setter. + var oldScaleX = target.scaleX, oldScaleY = target.scaleY; + if (!by) { + !lockScalingX && target.set('scaleX', scaleX); + !lockScalingY && target.set('scaleY', scaleY); + } + else { + // forbidden cases already handled on top here. + by === 'x' && target.set('scaleX', scaleX); + by === 'y' && target.set('scaleY', scaleY); + } + return oldScaleX !== target.scaleX || oldScaleY !== target.scaleY; + } + + /** + * Generic scaling logic, to scale from corners either equally or freely. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectFromCorner(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y); + } + + /** + * Scaling logic for the X axis. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectX(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y , { by: 'x' }); + } + + /** + * Scaling logic for the Y axis. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectY(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y , { by: 'y' }); + } + + /** + * Composed action handler to either scale Y or skew X + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scalingYOrSkewingX(eventData, transform, x, y) { + // ok some safety needed here. + if (eventData[transform.target.canvas.altActionKey]) { + return controls.skewHandlerX(eventData, transform, x, y); + } + return controls.scalingY(eventData, transform, x, y); + } + + /** + * Composed action handler to either scale X or skew Y + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scalingXOrSkewingY(eventData, transform, x, y) { + // ok some safety needed here. + if (eventData[transform.target.canvas.altActionKey]) { + return controls.skewHandlerY(eventData, transform, x, y); + } + return controls.scalingX(eventData, transform, x, y); + } + + /** + * Action handler to change textbox width + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function changeWidth(eventData, transform, x, y) { + var target = transform.target, localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + strokePadding = target.strokeWidth / (target.strokeUniform ? target.scaleX : 1), + multiplier = isTransformCentered(transform) ? 2 : 1, + oldWidth = target.width, + newWidth = Math.abs(localPoint.x * multiplier / target.scaleX) - strokePadding; + target.set('width', Math.max(newWidth, 0)); + return oldWidth !== newWidth; + } + + /** + * Action handler + * @private + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if the translation occurred + */ + function dragHandler(eventData, transform, x, y) { + var target = transform.target, + newLeft = x - transform.offsetX, + newTop = y - transform.offsetY, + moveX = !target.get('lockMovementX') && target.left !== newLeft, + moveY = !target.get('lockMovementY') && target.top !== newTop; + moveX && target.set('left', newLeft); + moveY && target.set('top', newTop); + if (moveX || moveY) { + fireEvent('moving', commonEventInfo(eventData, transform, x, y)); + } + return moveX || moveY; + } + + controls.scaleCursorStyleHandler = scaleCursorStyleHandler; + controls.skewCursorStyleHandler = skewCursorStyleHandler; + controls.scaleSkewCursorStyleHandler = scaleSkewCursorStyleHandler; + controls.rotationWithSnapping = wrapWithFireEvent('rotating', wrapWithFixedAnchor(rotationWithSnapping)); + controls.scalingEqually = wrapWithFireEvent('scaling', wrapWithFixedAnchor( scaleObjectFromCorner)); + controls.scalingX = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectX)); + controls.scalingY = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectY)); + controls.scalingYOrSkewingX = scalingYOrSkewingX; + controls.scalingXOrSkewingY = scalingXOrSkewingY; + controls.changeWidth = wrapWithFireEvent('resizing', wrapWithFixedAnchor(changeWidth)); + controls.skewHandlerX = skewHandlerX; + controls.skewHandlerY = skewHandlerY; + controls.dragHandler = dragHandler; + controls.scaleOrSkewActionName = scaleOrSkewActionName; + controls.rotationStyleHandler = rotationStyleHandler; + controls.fireEvent = fireEvent; + controls.wrapWithFixedAnchor = wrapWithFixedAnchor; + controls.wrapWithFireEvent = wrapWithFireEvent; + controls.getLocalPoint = getLocalPoint; + fabric.controlsUtils = controls; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + degreesToRadians = fabric.util.degreesToRadians, + controls = fabric.controlsUtils; + + /** + * Render a round control, as per fabric features. + * This function is written to respect object properties like transparentCorners, cornerSize + * cornerColor, cornerStrokeColor + * plus the addition of offsetY and offsetX. + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Number} left x coordinate where the control center should be + * @param {Number} top y coordinate where the control center should be + * @param {Object} styleOverride override for fabric.Object controls style + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + */ + function renderCircleControl (ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + var xSize = this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, + ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, + transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ? + styleOverride.transparentCorners : fabricObject.transparentCorners, + methodName = transparentCorners ? 'stroke' : 'fill', + stroke = !transparentCorners && (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor), + myLeft = left, + myTop = top, size; + ctx.save(); + ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; + ctx.strokeStyle = styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; + // as soon as fabric react v5, remove ie11, use proper ellipse code. + if (xSize > ySize) { + size = xSize; + ctx.scale(1.0, ySize / xSize); + myTop = top * xSize / ySize; + } + else if (ySize > xSize) { + size = ySize; + ctx.scale(xSize / ySize, 1.0); + myLeft = left * ySize / xSize; + } + else { + size = xSize; + } + // this is still wrong + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(myLeft, myTop, size / 2, 0, 2 * Math.PI, false); + ctx[methodName](); + if (stroke) { + ctx.stroke(); + } + ctx.restore(); + } + + /** + * Render a square control, as per fabric features. + * This function is written to respect object properties like transparentCorners, cornerSize + * cornerColor, cornerStrokeColor + * plus the addition of offsetY and offsetX. + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Number} left x coordinate where the control center should be + * @param {Number} top y coordinate where the control center should be + * @param {Object} styleOverride override for fabric.Object controls style + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + */ + function renderSquareControl(ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + var xSize = this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, + ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, + transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ? + styleOverride.transparentCorners : fabricObject.transparentCorners, + methodName = transparentCorners ? 'stroke' : 'fill', + stroke = !transparentCorners && ( + styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor + ), xSizeBy2 = xSize / 2, ySizeBy2 = ySize / 2; + ctx.save(); + ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; + ctx.strokeStyle = styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; + // this is still wrong + ctx.lineWidth = 1; + ctx.translate(left, top); + ctx.rotate(degreesToRadians(fabricObject.angle)); + // this does not work, and fixed with ( && ) does not make sense. + // to have real transparent corners we need the controls on upperCanvas + // transparentCorners || ctx.clearRect(-xSizeBy2, -ySizeBy2, xSize, ySize); + ctx[methodName + 'Rect'](-xSizeBy2, -ySizeBy2, xSize, ySize); + if (stroke) { + ctx.strokeRect(-xSizeBy2, -ySizeBy2, xSize, ySize); + } + ctx.restore(); + } + + controls.renderCircleControl = renderCircleControl; + controls.renderSquareControl = renderSquareControl; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + function Control(options) { + for (var i in options) { + this[i] = options[i]; + } + } + + fabric.Control = Control; + + fabric.Control.prototype = /** @lends fabric.Control.prototype */ { + + /** + * keep track of control visibility. + * mainly for backward compatibility. + * if you do not want to see a control, you can remove it + * from the controlset. + * @type {Boolean} + * @default true + */ + visible: true, + + /** + * Name of the action that the control will likely execute. + * This is optional. FabricJS uses to identify what the user is doing for some + * extra optimizations. If you are writing a custom control and you want to know + * somewhere else in the code what is going on, you can use this string here. + * you can also provide a custom getActionName if your control run multiple actions + * depending on some external state. + * default to scale since is the most common, used on 4 corners by default + * @type {String} + * @default 'scale' + */ + actionName: 'scale', + + /** + * Drawing angle of the control. + * NOT used for now, but name marked as needed for internal logic + * example: to reuse the same drawing function for different rotated controls + * @type {Number} + * @default 0 + */ + angle: 0, + + /** + * Relative position of the control. X + * 0,0 is the center of the Object, while -0.5 (left) or 0.5 (right) are the extremities + * of the bounding box. + * @type {Number} + * @default 0 + */ + x: 0, + + /** + * Relative position of the control. Y + * 0,0 is the center of the Object, while -0.5 (top) or 0.5 (bottom) are the extremities + * of the bounding box. + * @type {Number} + * @default 0 + */ + y: 0, + + /** + * Horizontal offset of the control from the defined position. In pixels + * Positive offset moves the control to the right, negative to the left. + * It used when you want to have position of control that does not scale with + * the bounding box. Example: rotation control is placed at x:0, y: 0.5 on + * the boundindbox, with an offset of 30 pixels vertically. Those 30 pixels will + * stay 30 pixels no matter how the object is big. Another example is having 2 + * controls in the corner, that stay in the same position when the object scale. + * of the bounding box. + * @type {Number} + * @default 0 + */ + offsetX: 0, + + /** + * Vertical offset of the control from the defined position. In pixels + * Positive offset moves the control to the bottom, negative to the top. + * @type {Number} + * @default 0 + */ + offsetY: 0, + + /** + * Sets the length of the control. If null, defaults to object's cornerSize. + * Expects both sizeX and sizeY to be set when set. + * @type {?Number} + * @default null + */ + sizeX: null, + + /** + * Sets the height of the control. If null, defaults to object's cornerSize. + * Expects both sizeX and sizeY to be set when set. + * @type {?Number} + * @default null + */ + sizeY: null, + + /** + * Sets the length of the touch area of the control. If null, defaults to object's touchCornerSize. + * Expects both touchSizeX and touchSizeY to be set when set. + * @type {?Number} + * @default null + */ + touchSizeX: null, + + /** + * Sets the height of the touch area of the control. If null, defaults to object's touchCornerSize. + * Expects both touchSizeX and touchSizeY to be set when set. + * @type {?Number} + * @default null + */ + touchSizeY: null, + + /** + * Css cursor style to display when the control is hovered. + * if the method `cursorStyleHandler` is provided, this property is ignored. + * @type {String} + * @default 'crosshair' + */ + cursorStyle: 'crosshair', + + /** + * If controls has an offsetY or offsetX, draw a line that connects + * the control to the bounding box + * @type {Boolean} + * @default false + */ + withConnection: false, + + /** + * The control actionHandler, provide one to handle action ( control being moved ) + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + actionHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * The control handler for mouse down, provide one to handle mouse down on control + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + mouseDownHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * The control mouseUpHandler, provide one to handle an effect on mouse up. + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + mouseUpHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * Returns control actionHandler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getActionHandler: function(/* eventData, fabricObject, control */) { + return this.actionHandler; + }, + + /** + * Returns control mouseDown handler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getMouseDownHandler: function(/* eventData, fabricObject, control */) { + return this.mouseDownHandler; + }, + + /** + * Returns control mouseUp handler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getMouseUpHandler: function(/* eventData, fabricObject, control */) { + return this.mouseUpHandler; + }, + + /** + * Returns control cursorStyle for css using cursorStyle. If you need a more elaborate + * function you can pass one in the constructor + * the cursorStyle property + * @param {Event} eventData the native mouse event + * @param {fabric.Control} control the current control ( likely this) + * @param {fabric.Object} object on which the control is displayed + * @return {String} + */ + cursorStyleHandler: function(eventData, control /* fabricObject */) { + return control.cursorStyle; + }, + + /** + * Returns the action name. The basic implementation just return the actionName property. + * @param {Event} eventData the native mouse event + * @param {fabric.Control} control the current control ( likely this) + * @param {fabric.Object} object on which the control is displayed + * @return {String} + */ + getActionName: function(eventData, control /* fabricObject */) { + return control.actionName; + }, + + /** + * Returns controls visibility + * @param {fabric.Object} object on which the control is displayed + * @param {String} controlKey key where the control is memorized on the + * @return {Boolean} + */ + getVisibility: function(fabricObject, controlKey) { + var objectVisibility = fabricObject._controlsVisibility; + if (objectVisibility && typeof objectVisibility[controlKey] !== 'undefined') { + return objectVisibility[controlKey]; + } + return this.visible; + }, + + /** + * Sets controls visibility + * @param {Boolean} visibility for the object + * @return {Void} + */ + setVisibility: function(visibility /* name, fabricObject */) { + this.visible = visibility; + }, + + + positionHandler: function(dim, finalMatrix /*, fabricObject, currentControl */) { + var point = fabric.util.transformPoint({ + x: this.x * dim.x + this.offsetX, + y: this.y * dim.y + this.offsetY }, finalMatrix); + return point; + }, + + /** + * Returns the coords for this control based on object values. + * @param {Number} objectAngle angle from the fabric object holding the control + * @param {Number} objectCornerSize cornerSize from the fabric object holding the control (or touchCornerSize if + * isTouch is true) + * @param {Number} centerX x coordinate where the control center should be + * @param {Number} centerY y coordinate where the control center should be + * @param {boolean} isTouch true if touch corner, false if normal corner + */ + calcCornerCoords: function(objectAngle, objectCornerSize, centerX, centerY, isTouch) { + var cosHalfOffset, + sinHalfOffset, + cosHalfOffsetComp, + sinHalfOffsetComp, + xSize = (isTouch) ? this.touchSizeX : this.sizeX, + ySize = (isTouch) ? this.touchSizeY : this.sizeY; + if (xSize && ySize && xSize !== ySize) { + // handle rectangular corners + var controlTriangleAngle = Math.atan2(ySize, xSize); + var cornerHypotenuse = Math.sqrt(xSize * xSize + ySize * ySize) / 2; + var newTheta = controlTriangleAngle - fabric.util.degreesToRadians(objectAngle); + var newThetaComp = Math.PI / 2 - controlTriangleAngle - fabric.util.degreesToRadians(objectAngle); + cosHalfOffset = cornerHypotenuse * fabric.util.cos(newTheta); + sinHalfOffset = cornerHypotenuse * fabric.util.sin(newTheta); + // use complementary angle for two corners + cosHalfOffsetComp = cornerHypotenuse * fabric.util.cos(newThetaComp); + sinHalfOffsetComp = cornerHypotenuse * fabric.util.sin(newThetaComp); + } + else { + // handle square corners + // use default object corner size unless size is defined + var cornerSize = (xSize && ySize) ? xSize : objectCornerSize; + /* 0.7071067812 stands for sqrt(2)/2 */ + cornerHypotenuse = cornerSize * 0.7071067812; + // complementary angles are equal since they're both 45 degrees + var newTheta = fabric.util.degreesToRadians(45 - objectAngle); + cosHalfOffset = cosHalfOffsetComp = cornerHypotenuse * fabric.util.cos(newTheta); + sinHalfOffset = sinHalfOffsetComp = cornerHypotenuse * fabric.util.sin(newTheta); + } + + return { + tl: { + x: centerX - sinHalfOffsetComp, + y: centerY - cosHalfOffsetComp, + }, + tr: { + x: centerX + cosHalfOffset, + y: centerY - sinHalfOffset, + }, + bl: { + x: centerX - cosHalfOffset, + y: centerY + sinHalfOffset, + }, + br: { + x: centerX + sinHalfOffsetComp, + y: centerY + cosHalfOffsetComp, + }, + }; + }, + + /** + * Render function for the control. + * When this function runs the context is unscaled. unrotate. Just retina scaled. + * all the functions will have to translate to the point left,top before starting Drawing + * if they want to draw a control where the position is detected. + * left and top are the result of the positionHandler function + * @param {RenderingContext2D} ctx the context where the control will be drawn + * @param {Number} left position of the canvas where we are about to render the control. + * @param {Number} top position of the canvas where we are about to render the control. + * @param {Object} styleOverride + * @param {fabric.Object} fabricObject the object where the control is about to be rendered + */ + render: function(ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + switch (styleOverride.cornerStyle || fabricObject.cornerStyle) { + case 'circle': + fabric.controlsUtils.renderCircleControl.call(this, ctx, left, top, styleOverride, fabricObject); + break; + default: + fabric.controlsUtils.renderSquareControl.call(this, ctx, left, top, styleOverride, fabricObject); + } + }, + }; + +})( true ? exports : 0); + + +(function() { + + /* _FROM_SVG_START_ */ + function getColorStop(el, multiplier) { + var style = el.getAttribute('style'), + offset = el.getAttribute('offset') || 0, + color, colorAlpha, opacity, i; + + // convert percents to absolute values + offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); + offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; + if (style) { + var keyValuePairs = style.split(/\s*;\s*/); + + if (keyValuePairs[keyValuePairs.length - 1] === '') { + keyValuePairs.pop(); + } + + for (i = keyValuePairs.length; i--; ) { + + var split = keyValuePairs[i].split(/\s*:\s*/), + key = split[0].trim(), + value = split[1].trim(); + + if (key === 'stop-color') { + color = value; + } + else if (key === 'stop-opacity') { + opacity = value; + } + } + } + + if (!color) { + color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; + } + if (!opacity) { + opacity = el.getAttribute('stop-opacity'); + } + + color = new fabric.Color(color); + colorAlpha = color.getAlpha(); + opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); + opacity *= colorAlpha * multiplier; + + return { + offset: offset, + color: color.toRgb(), + opacity: opacity + }; + } + + function getLinearCoords(el) { + return { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0 + }; + } + + function getRadialCoords(el) { + return { + x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', + y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', + r1: 0, + x2: el.getAttribute('cx') || '50%', + y2: el.getAttribute('cy') || '50%', + r2: el.getAttribute('r') || '50%' + }; + } + /* _FROM_SVG_END_ */ + + var clone = fabric.util.object.clone; + + /** + * Gradient class + * @class fabric.Gradient + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients} + * @see {@link fabric.Gradient#initialize} for constructor definition + */ + fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { + + /** + * Horizontal offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetX: 0, + + /** + * Vertical offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetY: 0, + + /** + * A transform matrix to apply to the gradient before painting. + * Imported from svg gradients, is not applied with the current transform in the center. + * Before this transform is applied, the origin point is at the top left corner of the object + * plus the addition of offsetY and offsetX. + * @type Number[] + * @default null + */ + gradientTransform: null, + + /** + * coordinates units for coords. + * If `pixels`, the number of coords are in the same unit of width / height. + * If set as `percentage` the coords are still a number, but 1 means 100% of width + * for the X and 100% of the height for the y. It can be bigger than 1 and negative. + * allowed values pixels or percentage. + * @type String + * @default 'pixels' + */ + gradientUnits: 'pixels', + + /** + * Gradient type linear or radial + * @type String + * @default 'pixels' + */ + type: 'linear', + + /** + * Constructor + * @param {Object} options Options object with type, coords, gradientUnits and colorStops + * @param {Object} [options.type] gradient type linear or radial + * @param {Object} [options.gradientUnits] gradient units + * @param {Object} [options.offsetX] SVG import compatibility + * @param {Object} [options.offsetY] SVG import compatibility + * @param {Object[]} options.colorStops contains the colorstops. + * @param {Object} options.coords contains the coords of the gradient + * @param {Number} [options.coords.x1] X coordiante of the first point for linear or of the focal point for radial + * @param {Number} [options.coords.y1] Y coordiante of the first point for linear or of the focal point for radial + * @param {Number} [options.coords.x2] X coordiante of the second point for linear or of the center point for radial + * @param {Number} [options.coords.y2] Y coordiante of the second point for linear or of the center point for radial + * @param {Number} [options.coords.r1] only for radial gradient, radius of the inner circle + * @param {Number} [options.coords.r2] only for radial gradient, radius of the external circle + * @return {fabric.Gradient} thisArg + */ + initialize: function(options) { + options || (options = { }); + options.coords || (options.coords = { }); + + var coords, _this = this; + + // sets everything, then coords and colorstops get sets again + Object.keys(options).forEach(function(option) { + _this[option] = options[option]; + }); + + if (this.id) { + this.id += '_' + fabric.Object.__uid++; + } + else { + this.id = fabric.Object.__uid++; + } + + coords = { + x1: options.coords.x1 || 0, + y1: options.coords.y1 || 0, + x2: options.coords.x2 || 0, + y2: options.coords.y2 || 0 + }; + + if (this.type === 'radial') { + coords.r1 = options.coords.r1 || 0; + coords.r2 = options.coords.r2 || 0; + } + + this.coords = coords; + this.colorStops = options.colorStops.slice(); + }, + + /** + * Adds another colorStop + * @param {Object} colorStop Object with offset and color + * @return {fabric.Gradient} thisArg + */ + addColorStop: function(colorStops) { + for (var position in colorStops) { + var color = new fabric.Color(colorStops[position]); + this.colorStops.push({ + offset: parseFloat(position), + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + return this; + }, + + /** + * Returns object representation of a gradient + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} + */ + toObject: function(propertiesToInclude) { + var object = { + type: this.type, + coords: this.coords, + colorStops: this.colorStops, + offsetX: this.offsetX, + offsetY: this.offsetY, + gradientUnits: this.gradientUnits, + gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform + }; + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an gradient + * @param {Object} object Object to create a gradient for + * @return {String} SVG representation of an gradient (linear/radial) + */ + toSVG: function(object, options) { + var coords = clone(this.coords, true), i, len, options = options || {}, + markup, commonAttributes, colorStops = clone(this.colorStops, true), + needsSwap = coords.r1 > coords.r2, + transform = this.gradientTransform ? this.gradientTransform.concat() : fabric.iMatrix.concat(), + offsetX = -this.offsetX, offsetY = -this.offsetY, + withViewport = !!options.additionalTransform, + gradientUnits = this.gradientUnits === 'pixels' ? 'userSpaceOnUse' : 'objectBoundingBox'; + // colorStops must be sorted ascending + colorStops.sort(function(a, b) { + return a.offset - b.offset; + }); + + if (gradientUnits === 'objectBoundingBox') { + offsetX /= object.width; + offsetY /= object.height; + } + else { + offsetX += object.width / 2; + offsetY += object.height / 2; + } + if (object.type === 'path' && this.gradientUnits !== 'percentage') { + offsetX -= object.pathOffset.x; + offsetY -= object.pathOffset.y; + } + + + transform[4] -= offsetX; + transform[5] -= offsetY; + + commonAttributes = 'id="SVGID_' + this.id + + '" gradientUnits="' + gradientUnits + '"'; + commonAttributes += ' gradientTransform="' + (withViewport ? + options.additionalTransform + ' ' : '') + fabric.util.matrixToSVG(transform) + '" '; + + if (this.type === 'linear') { + markup = [ + '\n' + ]; + } + else if (this.type === 'radial') { + // svg radial gradient has just 1 radius. the biggest. + markup = [ + '\n' + ]; + } + + if (this.type === 'radial') { + if (needsSwap) { + // svg goes from internal to external radius. if radius are inverted, swap color stops. + colorStops = colorStops.concat(); + colorStops.reverse(); + for (i = 0, len = colorStops.length; i < len; i++) { + colorStops[i].offset = 1 - colorStops[i].offset; + } + } + var minRadius = Math.min(coords.r1, coords.r2); + if (minRadius > 0) { + // i have to shift all colorStops and add new one in 0. + var maxRadius = Math.max(coords.r1, coords.r2), + percentageShift = minRadius / maxRadius; + for (i = 0, len = colorStops.length; i < len; i++) { + colorStops[i].offset += percentageShift * (1 - colorStops[i].offset); + } + } + } + + for (i = 0, len = colorStops.length; i < len; i++) { + var colorStop = colorStops[i]; + markup.push( + '\n' + ); + } + + markup.push((this.type === 'linear' ? '\n' : '\n')); + + return markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns an instance of CanvasGradient + * @param {CanvasRenderingContext2D} ctx Context to render on + * @return {CanvasGradient} + */ + toLive: function(ctx) { + var gradient, coords = fabric.util.object.clone(this.coords), i, len; + + if (!this.type) { + return; + } + + if (this.type === 'linear') { + gradient = ctx.createLinearGradient( + coords.x1, coords.y1, coords.x2, coords.y2); + } + else if (this.type === 'radial') { + gradient = ctx.createRadialGradient( + coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); + } + + for (i = 0, len = this.colorStops.length; i < len; i++) { + var color = this.colorStops[i].color, + opacity = this.colorStops[i].opacity, + offset = this.colorStops[i].offset; + + if (typeof opacity !== 'undefined') { + color = new fabric.Color(color).setAlpha(opacity).toRgba(); + } + gradient.addColorStop(offset, color); + } + + return gradient; + } + }); + + fabric.util.object.extend(fabric.Gradient, { + + /* _FROM_SVG_START_ */ + /** + * Returns {@link fabric.Gradient} instance from an SVG element + * @static + * @memberOf fabric.Gradient + * @param {SVGGradientElement} el SVG gradient element + * @param {fabric.Object} instance + * @param {String} opacityAttr A fill-opacity or stroke-opacity attribute to multiply to each stop's opacity. + * @param {Object} svgOptions an object containing the size of the SVG in order to parse correctly gradients + * that uses gradientUnits as 'userSpaceOnUse' and percentages. + * @param {Object.number} viewBoxWidth width part of the viewBox attribute on svg + * @param {Object.number} viewBoxHeight height part of the viewBox attribute on svg + * @param {Object.number} width width part of the svg tag if viewBox is not specified + * @param {Object.number} height height part of the svg tag if viewBox is not specified + * @return {fabric.Gradient} Gradient instance + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement + */ + fromElement: function(el, instance, opacityAttr, svgOptions) { + /** + * @example: + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + */ + + var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); + multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; + if (isNaN(multiplier)) { + multiplier = 1; + } + + var colorStopEls = el.getElementsByTagName('stop'), + type, + gradientUnits = el.getAttribute('gradientUnits') === 'userSpaceOnUse' ? + 'pixels' : 'percentage', + gradientTransform = el.getAttribute('gradientTransform') || '', + colorStops = [], + coords, i, offsetX = 0, offsetY = 0, + transformMatrix; + if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') { + type = 'linear'; + coords = getLinearCoords(el); + } + else { + type = 'radial'; + coords = getRadialCoords(el); + } + + for (i = colorStopEls.length; i--; ) { + colorStops.push(getColorStop(colorStopEls[i], multiplier)); + } + + transformMatrix = fabric.parseTransformAttribute(gradientTransform); + + __convertPercentUnitsToValues(instance, coords, svgOptions, gradientUnits); + + if (gradientUnits === 'pixels') { + offsetX = -instance.left; + offsetY = -instance.top; + } + + var gradient = new fabric.Gradient({ + id: el.getAttribute('id'), + type: type, + coords: coords, + colorStops: colorStops, + gradientUnits: gradientUnits, + gradientTransform: transformMatrix, + offsetX: offsetX, + offsetY: offsetY, + }); + + return gradient; + } + /* _FROM_SVG_END_ */ + }); + + /** + * @private + */ + function __convertPercentUnitsToValues(instance, options, svgOptions, gradientUnits) { + var propValue, finalValue; + Object.keys(options).forEach(function(prop) { + propValue = options[prop]; + if (propValue === 'Infinity') { + finalValue = 1; + } + else if (propValue === '-Infinity') { + finalValue = 0; + } + else { + finalValue = parseFloat(options[prop], 10); + if (typeof propValue === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(propValue)) { + finalValue *= 0.01; + if (gradientUnits === 'pixels') { + // then we need to fix those percentages here in svg parsing + if (prop === 'x1' || prop === 'x2' || prop === 'r2') { + finalValue *= svgOptions.viewBoxWidth || svgOptions.width; + } + if (prop === 'y1' || prop === 'y2') { + finalValue *= svgOptions.viewBoxHeight || svgOptions.height; + } + } + } + } + options[prop] = finalValue; + }); + } +})(); + + +(function() { + + 'use strict'; + + var toFixed = fabric.util.toFixed; + + /** + * Pattern class + * @class fabric.Pattern + * @see {@link http://fabricjs.com/patterns|Pattern demo} + * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo} + * @see {@link fabric.Pattern#initialize} for constructor definition + */ + + + fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { + + /** + * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) + * @type String + * @default + */ + repeat: 'repeat', + + /** + * Pattern horizontal offset from object's left/top corner + * @type Number + * @default + */ + offsetX: 0, + + /** + * Pattern vertical offset from object's left/top corner + * @type Number + * @default + */ + offsetY: 0, + + /** + * crossOrigin value (one of "", "anonymous", "use-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @type String + * @default + */ + crossOrigin: '', + + /** + * transform matrix to change the pattern, imported from svgs. + * @type Array + * @default + */ + patternTransform: null, + + /** + * Constructor + * @param {Object} [options] Options object + * @param {Function} [callback] function to invoke after callback init. + * @return {fabric.Pattern} thisArg + */ + initialize: function(options, callback) { + options || (options = { }); + + this.id = fabric.Object.__uid++; + this.setOptions(options); + if (!options.source || (options.source && typeof options.source !== 'string')) { + callback && callback(this); + return; + } + else { + // img src string + var _this = this; + this.source = fabric.util.createImage(); + fabric.util.loadImage(options.source, function(img, isError) { + _this.source = img; + callback && callback(_this, isError); + }, null, this.crossOrigin); + } + }, + + /** + * Returns object representation of a pattern + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of a pattern instance + */ + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + source, object; + + // element + if (typeof this.source.src === 'string') { + source = this.source.src; + } + // element + else if (typeof this.source === 'object' && this.source.toDataURL) { + source = this.source.toDataURL(); + } + + object = { + type: 'pattern', + source: source, + repeat: this.repeat, + crossOrigin: this.crossOrigin, + offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS), + offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS), + patternTransform: this.patternTransform ? this.patternTransform.concat() : null + }; + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a pattern + * @param {fabric.Object} object + * @return {String} SVG representation of a pattern + */ + toSVG: function(object) { + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.width, + patternHeight = patternSource.height / object.height, + patternOffsetX = this.offsetX / object.width, + patternOffsetY = this.offsetY / object.height, + patternImgSrc = ''; + if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { + patternHeight = 1; + if (patternOffsetY) { + patternHeight += Math.abs(patternOffsetY); + } + } + if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { + patternWidth = 1; + if (patternOffsetX) { + patternWidth += Math.abs(patternOffsetX); + } + + } + if (patternSource.src) { + patternImgSrc = patternSource.src; + } + else if (patternSource.toDataURL) { + patternImgSrc = patternSource.toDataURL(); + } + + return '\n' + + '\n' + + '\n'; + }, + /* _TO_SVG_END_ */ + + setOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Returns an instance of CanvasPattern + * @param {CanvasRenderingContext2D} ctx Context to create pattern + * @return {CanvasPattern} + */ + toLive: function(ctx) { + var source = this.source; + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + + // if an image + if (typeof source.src !== 'undefined') { + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } + } + return ctx.createPattern(source, this.repeat); + } + }); +})(); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + toFixed = fabric.util.toFixed; + + if (fabric.Shadow) { + fabric.warn('fabric.Shadow is already defined.'); + return; + } + + /** + * Shadow class + * @class fabric.Shadow + * @see {@link http://fabricjs.com/shadows|Shadow demo} + * @see {@link fabric.Shadow#initialize} for constructor definition + */ + fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { + + /** + * Shadow color + * @type String + * @default + */ + color: 'rgb(0,0,0)', + + /** + * Shadow blur + * @type Number + */ + blur: 0, + + /** + * Shadow horizontal offset + * @type Number + * @default + */ + offsetX: 0, + + /** + * Shadow vertical offset + * @type Number + * @default + */ + offsetY: 0, + + /** + * Whether the shadow should affect stroke operations + * @type Boolean + * @default + */ + affectStroke: false, + + /** + * Indicates whether toObject should include default values + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * When `false`, the shadow will scale with the object. + * When `true`, the shadow's offsetX, offsetY, and blur will not be affected by the object's scale. + * default to false + * @type Boolean + * @default + */ + nonScaling: false, + + /** + * Constructor + * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px") + * @return {fabric.Shadow} thisArg + */ + initialize: function(options) { + + if (typeof options === 'string') { + options = this._parseShadow(options); + } + + for (var prop in options) { + this[prop] = options[prop]; + } + + this.id = fabric.Object.__uid++; + }, + + /** + * @private + * @param {String} shadow Shadow value to parse + * @return {Object} Shadow object with color, offsetX, offsetY and blur + */ + _parseShadow: function(shadow) { + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], + color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; + + return { + color: color.trim(), + offsetX: parseFloat(offsetsAndBlur[1], 10) || 0, + offsetY: parseFloat(offsetsAndBlur[2], 10) || 0, + blur: parseFloat(offsetsAndBlur[3], 10) || 0 + }; + }, + + /** + * Returns a string representation of an instance + * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow + * @return {String} Returns CSS3 text-shadow declaration + */ + toString: function() { + return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a shadow + * @param {fabric.Object} object + * @return {String} SVG representation of a shadow + */ + toSVG: function(object) { + var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + offset = fabric.util.rotateVector( + { x: this.offsetX, y: this.offsetY }, + fabric.util.degreesToRadians(-object.angle)), + BLUR_BOX = 20, color = new fabric.Color(this.color); + + if (object.width && object.height) { + //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion + // we add some extra space to filter box to contain the blur ( 20 ) + fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; + fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; + } + if (object.flipX) { + offset.x *= -1; + } + if (object.flipY) { + offset.y *= -1; + } + + return ( + '\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\t\n' + + '\t\t\n' + + '\t\n' + + '\n'); + }, + /* _TO_SVG_END_ */ + + /** + * Returns object representation of a shadow + * @return {Object} Object representation of a shadow instance + */ + toObject: function() { + if (this.includeDefaultValues) { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY, + affectStroke: this.affectStroke, + nonScaling: this.nonScaling + }; + } + var obj = { }, proto = fabric.Shadow.prototype; + + ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke', 'nonScaling'].forEach(function(prop) { + if (this[prop] !== proto[prop]) { + obj[prop] = this[prop]; + } + }, this); + + return obj; + } + }); + + /** + * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") + * @static + * @field + * @memberOf fabric.Shadow + */ + // eslint-disable-next-line max-len + fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/; + +})( true ? exports : 0); + + +(function () { + + 'use strict'; + + if (fabric.StaticCanvas) { + fabric.warn('fabric.StaticCanvas is already defined.'); + return; + } + + // aliases for faster resolution + var extend = fabric.util.object.extend, + getElementOffset = fabric.util.getElementOffset, + removeFromArray = fabric.util.removeFromArray, + toFixed = fabric.util.toFixed, + transformPoint = fabric.util.transformPoint, + invertTransform = fabric.util.invertTransform, + getNodeCanvas = fabric.util.getNodeCanvas, + createCanvasElement = fabric.util.createCanvasElement, + + CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); + + /** + * Static canvas class + * @class fabric.StaticCanvas + * @mixes fabric.Collection + * @mixes fabric.Observable + * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo} + * @see {@link fabric.StaticCanvas#initialize} for constructor definition + * @fires before:render + * @fires after:render + * @fires canvas:cleared + * @fires object:added + * @fires object:removed + */ + fabric.StaticCanvas = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(el, options) { + options || (options = { }); + this.renderAndResetBound = this.renderAndReset.bind(this); + this.requestRenderAllBound = this.requestRenderAll.bind(this); + this._initStatic(el, options); + }, + + /** + * Background color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. + * @type {(String|fabric.Pattern)} + * @default + */ + backgroundColor: '', + + /** + * Background image of canvas instance. + * since 2.4.0 image caching is active, please when putting an image as background, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching + * @type fabric.Image + * @default + */ + backgroundImage: null, + + /** + * Overlay color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setOverlayColor} + * @since 1.3.9 + * @type {(String|fabric.Pattern)} + * @default + */ + overlayColor: '', + + /** + * Overlay image of canvas instance. + * since 2.4.0 image caching is active, please when putting an image as overlay, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching + * @type fabric.Image + * @default + */ + overlayImage: null, + + /** + * Indicates whether toObject/toDatalessObject should include default values + * if set to false, takes precedence over the object value. + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Indicates whether objects' state should be saved + * @type Boolean + * @default + */ + stateful: false, + + /** + * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove}, + * {@link fabric.StaticCanvas.moveTo}, {@link fabric.StaticCanvas.clear} and many more, should also re-render canvas. + * Disabling this option will not give a performance boost when adding/removing a lot of objects to/from canvas at once + * since the renders are quequed and executed one per frame. + * Disabling is suggested anyway and managing the renders of the app manually is not a big effort ( canvas.requestRenderAll() ) + * Left default to true to do not break documentation and old app, fiddles. + * @type Boolean + * @default + */ + renderOnAddRemove: true, + + /** + * Indicates whether object controls (borders/controls) are rendered above overlay image + * @type Boolean + * @default + */ + controlsAboveOverlay: false, + + /** + * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas + * @type Boolean + * @default + */ + allowTouchScrolling: false, + + /** + * Indicates whether this canvas will use image smoothing, this is on by default in browsers + * @type Boolean + * @default + */ + imageSmoothingEnabled: true, + + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: fabric.iMatrix.concat(), + + /** + * if set to false background image is not affected by viewport transform + * @since 1.6.3 + * @type Boolean + * @default + */ + backgroundVpt: true, + + /** + * if set to false overlya image is not affected by viewport transform + * @since 1.6.3 + * @type Boolean + * @default + */ + overlayVpt: true, + + /** + * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens + * @type Boolean + * @default + */ + enableRetinaScaling: true, + + /** + * Describe canvas element extension over design + * properties are tl,tr,bl,br. + * if canvas is not zoomed/panned those points are the four corner of canvas + * if canvas is viewportTransformed you those points indicate the extension + * of canvas element in plain untrasformed coordinates + * The coordinates get updated with @method calcViewportBoundaries. + * @memberOf fabric.StaticCanvas.prototype + */ + vptCoords: { }, + + /** + * Based on vptCoords and object.aCoords, skip rendering of objects that + * are not included in current viewport. + * May greatly help in applications with crowded canvas and use of zoom/pan + * If One of the corner of the bounding box of the object is on the canvas + * the objects get rendered. + * @memberOf fabric.StaticCanvas.prototype + * @type Boolean + * @default + */ + skipOffscreen: true, + + /** + * a fabricObject that, without stroke define a clipping area with their shape. filled in black + * the clipPath object gets used when the canvas has rendered, and the context is placed in the + * top left corner of the canvas. + * clipPath will clip away controls, if you do not want this to happen use controlsAboveOverlay = true + * @type fabric.Object + */ + clipPath: undefined, + + /** + * @private + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + */ + _initStatic: function(el, options) { + var cb = this.requestRenderAllBound; + this._objects = []; + this._createLowerCanvas(el); + this._initOptions(options); + // only initialize retina scaling once + if (!this.interactive) { + this._initRetinaScaling(); + } + + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, cb); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, cb); + } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, cb); + } + if (options.overlayColor) { + this.setOverlayColor(options.overlayColor, cb); + } + this.calcOffset(); + }, + + /** + * @private + */ + _isRetinaScaling: function() { + return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); + }, + + /** + * @private + * @return {Number} retinaScaling if applied, otherwise 1; + */ + getRetinaScaling: function() { + return this._isRetinaScaling() ? fabric.devicePixelRatio : 1; + }, + + /** + * @private + */ + _initRetinaScaling: function() { + if (!this._isRetinaScaling()) { + return; + } + var scaleRatio = fabric.devicePixelRatio; + this.__initRetinaScaling(scaleRatio, this.lowerCanvasEl, this.contextContainer); + if (this.upperCanvasEl) { + this.__initRetinaScaling(scaleRatio, this.upperCanvasEl, this.contextTop); + } + }, + + __initRetinaScaling: function(scaleRatio, canvas, context) { + canvas.setAttribute('width', this.width * scaleRatio); + canvas.setAttribute('height', this.height * scaleRatio); + context.scale(scaleRatio, scaleRatio); + }, + + + /** + * Calculates canvas element offset relative to the document + * This method is also attached as "resize" event handler of window + * @return {fabric.Canvas} instance + * @chainable + */ + calcOffset: function () { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to + * @param {Function} callback callback to invoke when image is loaded and set as an overlay + * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} + * @example Normal overlayImage with left/top = 0 + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example overlayImage with different properties + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched overlayImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img, isError) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched overlayImage #2 - width/height correspond to canvas width/height + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example overlayImage loaded from cross-origin + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top', + * crossOrigin: 'anonymous' + * }); + */ + setOverlayImage: function (image, callback, options) { + return this.__setBgOverlayImage('overlayImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to + * @param {Function} callback Callback to invoke when image is loaded and set as background + * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/djnr8o7a/28/|jsFiddle demo} + * @example Normal backgroundImage with left/top = 0 + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example backgroundImage with different properties + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img, isError) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example backgroundImage loaded from cross-origin + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top', + * crossOrigin: 'anonymous' + * }); + */ + // TODO: fix stretched examples + setBackgroundImage: function (image, callback, options) { + return this.__setBgOverlayImage('backgroundImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayColor|foreground color} for this canvas + * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set foreground color to + * @param {Function} callback Callback to invoke when foreground color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} + * @example Normal overlayColor - color value + * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor with repeat and offset + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setOverlayColor: function(overlayColor, callback) { + return this.__setBgOverlayColor('overlayColor', overlayColor, callback); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas + * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to + * @param {Function} callback Callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} + * @example Normal backgroundColor - color value + * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor with repeat and offset + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setBackgroundColor: function(backgroundColor, callback) { + return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} + * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) + * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to + * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay. The first argument is the created image, the second argument is a flag indicating whether an error occurred or not. + * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. + */ + __setBgOverlayImage: function(property, image, callback, options) { + if (typeof image === 'string') { + fabric.util.loadImage(image, function(img, isError) { + if (img) { + var instance = new fabric.Image(img, options); + this[property] = instance; + instance.canvas = this; + } + callback && callback(img, isError); + }, this, options && options.crossOrigin); + } + else { + options && image.setOptions(options); + this[property] = image; + image && (image.canvas = this); + callback && callback(image, false); + } + + return this; + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} + * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) + * @param {(Object|String|null)} color Object with pattern information, color value or null + * @param {Function} [callback] Callback is invoked when color is set + */ + __setBgOverlayColor: function(property, color, callback) { + this[property] = color; + this._initGradient(color, property); + this._initPattern(color, property, callback); + return this; + }, + + /** + * @private + */ + _createCanvasElement: function() { + var element = createCanvasElement(); + if (!element) { + throw CANVAS_INIT_ERROR; + } + if (!element.style) { + element.style = { }; + } + if (typeof element.getContext === 'undefined') { + throw CANVAS_INIT_ERROR; + } + return element; + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initOptions: function (options) { + var lowerCanvasEl = this.lowerCanvasEl; + this._setOptions(options); + + this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0; + + if (!this.lowerCanvasEl.style) { + return; + } + + lowerCanvasEl.width = this.width; + lowerCanvasEl.height = this.height; + + lowerCanvasEl.style.width = this.width + 'px'; + lowerCanvasEl.style.height = this.height + 'px'; + + this.viewportTransform = this.viewportTransform.slice(); + }, + + /** + * Creates a bottom canvas + * @private + * @param {HTMLElement} [canvasEl] + */ + _createLowerCanvas: function (canvasEl) { + // canvasEl === 'HTMLCanvasElement' does not work on jsdom/node + if (canvasEl && canvasEl.getContext) { + this.lowerCanvasEl = canvasEl; + } + else { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + } + + fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); + this._originalCanvasStyle = this.lowerCanvasEl.style; + if (this.interactive) { + this._applyCanvasStyle(this.lowerCanvasEl); + } + + this.contextContainer = this.lowerCanvasEl.getContext('2d'); + }, + + /** + * Returns canvas width (in px) + * @return {Number} + */ + getWidth: function () { + return this.width; + }, + + /** + * Returns canvas height (in px) + * @return {Number} + */ + getHeight: function () { + return this.height; + }, + + /** + * Sets width of this canvas instance + * @param {Number|String} value Value to set width to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setWidth: function (value, options) { + return this.setDimensions({ width: value }, options); + }, + + /** + * Sets height of this canvas instance + * @param {Number|String} value Value to set height to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setHeight: function (value, options) { + return this.setDimensions({ height: value }, options); + }, + + /** + * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) + * @param {Object} dimensions Object with width/height properties + * @param {Number|String} [dimensions.width] Width of canvas element + * @param {Number|String} [dimensions.height] Height of canvas element + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} thisArg + * @chainable + */ + setDimensions: function (dimensions, options) { + var cssValue; + + options = options || {}; + + for (var prop in dimensions) { + cssValue = dimensions[prop]; + + if (!options.cssOnly) { + this._setBackstoreDimension(prop, dimensions[prop]); + cssValue += 'px'; + this.hasLostContext = true; + } + + if (!options.backstoreOnly) { + this._setCssDimension(prop, cssValue); + } + } + if (this._isCurrentlyDrawing) { + this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(); + } + this._initRetinaScaling(); + this.calcOffset(); + + if (!options.cssOnly) { + this.requestRenderAll(); + } + + return this; + }, + + /** + * Helper for setting width/height + * @private + * @param {String} prop property (width|height) + * @param {Number} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setBackstoreDimension: function (prop, value) { + this.lowerCanvasEl[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + } + + if (this.cacheCanvasEl) { + this.cacheCanvasEl[prop] = value; + } + + this[prop] = value; + + return this; + }, + + /** + * Helper for setting css width/height + * @private + * @param {String} prop property (width|height) + * @param {String} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setCssDimension: function (prop, value) { + this.lowerCanvasEl.style[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl.style[prop] = value; + } + + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value; + } + + return this; + }, + + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return this.viewportTransform[0]; + }, + + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + var activeObject = this._activeObject, + backgroundObject = this.backgroundImage, + overlayObject = this.overlayImage, + object, i, len; + this.viewportTransform = vpt; + for (i = 0, len = this._objects.length; i < len; i++) { + object = this._objects[i]; + object.group || object.setCoords(true); + } + if (activeObject) { + activeObject.setCoords(); + } + if (backgroundObject) { + backgroundObject.setCoords(true); + } + if (overlayObject) { + overlayObject.setCoords(true); + } + this.calcViewportBoundaries(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Sets zoom level of this canvas instance, the zoom centered around point + * meaning that following zoom to point with the same point will have the visual + * effect of the zoom originating from that point. The point won't move. + * It has nothing to do with canvas center or visual center of the viewport. + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = point, vpt = this.viewportTransform.slice(0); + point = transformPoint(point, invertTransform(this.viewportTransform)); + vpt[0] = value; + vpt[3] = value; + var after = transformPoint(point, vpt); + vpt[4] += before.x - after.x; + vpt[5] += before.y - after.y; + return this.setViewportTransform(vpt); + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + this.zoomToPoint(new fabric.Point(0, 0), value); + return this; + }, + + /** + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to + * @return {fabric.Canvas} instance + * @chainable true + */ + absolutePan: function (point) { + var vpt = this.viewportTransform.slice(0); + vpt[4] = -point.x; + vpt[5] = -point.y; + return this.setViewportTransform(vpt); + }, + + /** + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by + * @return {fabric.Canvas} instance + * @chainable true + */ + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); + }, + + /** + * Returns <canvas> element corresponding to this instance + * @return {HTMLCanvasElement} + */ + getElement: function () { + return this.lowerCanvasEl; + }, + + /** + * @private + * @param {fabric.Object} obj Object that was added + */ + _onObjectAdded: function(obj) { + this.stateful && obj.setupState(); + obj._set('canvas', this); + obj.setCoords(); + this.fire('object:added', { target: obj }); + obj.fire('added'); + }, + + /** + * @private + * @param {fabric.Object} obj Object that was removed + */ + _onObjectRemoved: function(obj) { + this.fire('object:removed', { target: obj }); + obj.fire('removed'); + delete obj.canvas; + }, + + /** + * Clears specified context of canvas element + * @param {CanvasRenderingContext2D} ctx Context to clear + * @return {fabric.Canvas} thisArg + * @chainable + */ + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + + /** + * Returns context of canvas where objects are drawn + * @return {CanvasRenderingContext2D} + */ + getContext: function () { + return this.contextContainer; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + this.remove.apply(this, this.getObjects()); + this.backgroundImage = null; + this.overlayImage = null; + this.backgroundColor = ''; + this.overlayColor = ''; + if (this._hasITextHandlers) { + this.off('mouse:up', this._mouseUpITextHandler); + this._iTextInstances = null; + this._hasITextHandlers = false; + } + this.clearContext(this.contextContainer); + this.fire('canvas:cleared'); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Renders the canvas + * @return {fabric.Canvas} instance + * @chainable + */ + renderAll: function () { + var canvasToDrawOn = this.contextContainer; + this.renderCanvas(canvasToDrawOn, this._objects); + return this; + }, + + /** + * Function created to be instance bound at initialization + * used in requestAnimationFrame rendering + * Let the fabricJS call it. If you call it manually you could have more + * animationFrame stacking on to of each other + * for an imperative rendering, use canvas.renderAll + * @private + * @return {fabric.Canvas} instance + * @chainable + */ + renderAndReset: function() { + this.isRendering = 0; + this.renderAll(); + }, + + /** + * Append a renderAll request to next animation frame. + * unless one is already in progress, in that case nothing is done + * a boolean flag will avoid appending more. + * @return {fabric.Canvas} instance + * @chainable + */ + requestRenderAll: function () { + if (!this.isRendering) { + this.isRendering = fabric.util.requestAnimFrame(this.renderAndResetBound); + } + return this; + }, + + /** + * Calculate the position of the 4 corner of canvas with current viewportTransform. + * helps to determinate when an object is in the current rendering viewport using + * object absolute coordinates ( aCoords ) + * @return {Object} points.tl + * @chainable + */ + calcViewportBoundaries: function() { + var points = { }, width = this.width, height = this.height, + iVpt = invertTransform(this.viewportTransform); + points.tl = transformPoint({ x: 0, y: 0 }, iVpt); + points.br = transformPoint({ x: width, y: height }, iVpt); + points.tr = new fabric.Point(points.br.x, points.tl.y); + points.bl = new fabric.Point(points.tl.x, points.br.y); + this.vptCoords = points; + return points; + }, + + cancelRequestedRender: function() { + if (this.isRendering) { + fabric.util.cancelAnimFrame(this.isRendering); + this.isRendering = 0; + } + }, + + /** + * Renders background, objects, overlay and controls. + * @param {CanvasRenderingContext2D} ctx + * @param {Array} objects to render + * @return {fabric.Canvas} instance + * @chainable + */ + renderCanvas: function(ctx, objects) { + var v = this.viewportTransform, path = this.clipPath; + this.cancelRequestedRender(); + this.calcViewportBoundaries(); + this.clearContext(ctx); + fabric.util.setImageSmoothing(ctx, this.imageSmoothingEnabled); + this.fire('before:render', { ctx: ctx, }); + this._renderBackground(ctx); + + ctx.save(); + //apply viewport transform once for all rendering process + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + this._renderObjects(ctx, objects); + ctx.restore(); + if (!this.controlsAboveOverlay && this.interactive) { + this.drawControls(ctx); + } + if (path) { + path.canvas = this; + // needed to setup a couple of variables + path.shouldCache(); + path._transformDone = true; + path.renderCache({ forClipping: true }); + this.drawClipPathOnCanvas(ctx); + } + this._renderOverlay(ctx); + if (this.controlsAboveOverlay && this.interactive) { + this.drawControls(ctx); + } + this.fire('after:render', { ctx: ctx, }); + }, + + /** + * Paint the cached clipPath on the lowerCanvasEl + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawClipPathOnCanvas: function(ctx) { + var v = this.viewportTransform, path = this.clipPath; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // DEBUG: uncomment this line, comment the following + // ctx.globalAlpha = 0.4; + ctx.globalCompositeOperation = 'destination-in'; + path.transform(ctx); + ctx.scale(1 / path.zoomX, 1 / path.zoomY); + ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} objects to render + */ + _renderObjects: function(ctx, objects) { + var i, len; + for (i = 0, len = objects.length; i < len; ++i) { + objects[i] && objects[i].render(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {string} property 'background' or 'overlay' + */ + _renderBackgroundOrOverlay: function(ctx, property) { + var fill = this[property + 'Color'], object = this[property + 'Image'], + v = this.viewportTransform, needsVpt = this[property + 'Vpt']; + if (!fill && !object) { + return; + } + if (fill) { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(this.width, 0); + ctx.lineTo(this.width, this.height); + ctx.lineTo(0, this.height); + ctx.closePath(); + ctx.fillStyle = fill.toLive + ? fill.toLive(ctx, this) + : fill; + if (needsVpt) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } + ctx.transform(1, 0, 0, 1, fill.offsetX || 0, fill.offsetY || 0); + var m = fill.gradientTransform || fill.patternTransform; + m && ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + ctx.fill(); + ctx.restore(); + } + if (object) { + ctx.save(); + if (needsVpt) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } + object.render(ctx); + ctx.restore(); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderBackground: function(ctx) { + this._renderBackgroundOrOverlay(ctx, 'background'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderOverlay: function(ctx) { + this._renderBackgroundOrOverlay(ctx, 'overlay'); + }, + + /** + * Returns coordinates of a center of canvas. + * Returned value is an object with top and left properties + * @return {Object} object with "top" and "left" number values + */ + getCenter: function () { + return { + top: this.height / 2, + left: this.width / 2 + }; + }, + + /** + * Centers object horizontally in the canvas + * @param {fabric.Object} object Object to center horizontally + * @return {fabric.Canvas} thisArg + */ + centerObjectH: function (object) { + return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); + }, + + /** + * Centers object vertically in the canvas + * @param {fabric.Object} object Object to center vertically + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObjectV: function (object) { + return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); + }, + + /** + * Centers object vertically and horizontally in the canvas + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObject: function(object) { + var center = this.getCenter(); + + return this._centerObject(object, new fabric.Point(center.left, center.top)); + }, + + /** + * Centers object vertically and horizontally in the viewport + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObject: function(object) { + var vpCenter = this.getVpCenter(); + + return this._centerObject(object, vpCenter); + }, + + /** + * Centers object horizontally in the viewport, object.top is unchanged + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObjectH: function(object) { + var vpCenter = this.getVpCenter(); + this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y)); + return this; + }, + + /** + * Centers object Vertically in the viewport, object.top is unchanged + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObjectV: function(object) { + var vpCenter = this.getVpCenter(); + + return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y)); + }, + + /** + * Calculate the point in canvas that correspond to the center of actual viewport. + * @return {fabric.Point} vpCenter, viewport center + * @chainable + */ + getVpCenter: function() { + var center = this.getCenter(), + iVpt = invertTransform(this.viewportTransform); + return transformPoint({ x: center.left, y: center.top }, iVpt); + }, + + /** + * @private + * @param {fabric.Object} object Object to center + * @param {fabric.Point} center Center point + * @return {fabric.Canvas} thisArg + * @chainable + */ + _centerObject: function(object, center) { + object.setPositionByOrigin(center, 'center', 'center'); + object.setCoords(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Returns dataless JSON representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {String} json string + */ + toDatalessJSON: function (propertiesToInclude) { + return this.toDatalessObject(propertiesToInclude); + }, + + /** + * Returns object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function (propertiesToInclude) { + return this._toObjectMethod('toObject', propertiesToInclude); + }, + + /** + * Returns dataless object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function (propertiesToInclude) { + return this._toObjectMethod('toDatalessObject', propertiesToInclude); + }, + + /** + * @private + */ + _toObjectMethod: function (methodName, propertiesToInclude) { + + var clipPath = this.clipPath, data = { + version: fabric.version, + objects: this._toObjects(methodName, propertiesToInclude), + }; + if (clipPath && !clipPath.excludeFromExport) { + data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude); + } + extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); + + fabric.util.populateWithProperties(this, data, propertiesToInclude); + + return data; + }, + + /** + * @private + */ + _toObjects: function(methodName, propertiesToInclude) { + return this._objects.filter(function(object) { + return !object.excludeFromExport; + }).map(function(instance) { + return this._toObject(instance, methodName, propertiesToInclude); + }, this); + }, + + /** + * @private + */ + _toObject: function(instance, methodName, propertiesToInclude) { + var originalValue; + + if (!this.includeDefaultValues) { + originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + + var object = instance[methodName](propertiesToInclude); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + return object; + }, + + /** + * @private + */ + __serializeBgOverlay: function(methodName, propertiesToInclude) { + var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage, + bgColor = this.backgroundColor, overlayColor = this.overlayColor; + + if (bgColor && bgColor.toObject) { + if (!bgColor.excludeFromExport) { + data.background = bgColor.toObject(propertiesToInclude); + } + } + else if (bgColor) { + data.background = bgColor; + } + + if (overlayColor && overlayColor.toObject) { + if (!overlayColor.excludeFromExport) { + data.overlay = overlayColor.toObject(propertiesToInclude); + } + } + else if (overlayColor) { + data.overlay = overlayColor; + } + + if (bgImage && !bgImage.excludeFromExport) { + data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude); + } + if (overlayImage && !overlayImage.excludeFromExport) { + data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude); + } + + return data; + }, + + /* _TO_SVG_START_ */ + /** + * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, + * a zoomed canvas will then produce zoomed SVG output. + * @type Boolean + * @default + */ + svgViewportTransformation: true, + + /** + * Returns SVG representation of canvas + * @function + * @param {Object} [options] Options object for SVG output + * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included + * @param {Object} [options.viewBox] SVG viewbox object + * @param {Number} [options.viewBox.x] x-coordinate of viewbox + * @param {Number} [options.viewBox.y] y-coordinate of viewbox + * @param {Number} [options.viewBox.width] Width of viewbox + * @param {Number} [options.viewBox.height] Height of viewbox + * @param {String} [options.encoding=UTF-8] Encoding of SVG output + * @param {String} [options.width] desired width of svg with or without units + * @param {String} [options.height] desired height of svg with or without units + * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. + * @return {String} SVG string + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} + * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} + * @example Normal SVG output + * var svg = canvas.toSVG(); + * @example SVG output without preamble (without <?xml ../>) + * var svg = canvas.toSVG({suppressPreamble: true}); + * @example SVG output with viewBox attribute + * var svg = canvas.toSVG({ + * viewBox: { + * x: 100, + * y: 100, + * width: 200, + * height: 300 + * } + * }); + * @example SVG output with different encoding (default: UTF-8) + * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); + * @example Modify SVG output with reviver function + * var svg = canvas.toSVG(null, function(svg) { + * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); + * }); + */ + toSVG: function(options, reviver) { + options || (options = { }); + options.reviver = reviver; + var markup = []; + + this._setSVGPreamble(markup, options); + this._setSVGHeader(markup, options); + if (this.clipPath) { + markup.push('\n'); + } + this._setSVGBgOverlayColor(markup, 'background'); + this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); + this._setSVGObjects(markup, reviver); + if (this.clipPath) { + markup.push('\n'); + } + this._setSVGBgOverlayColor(markup, 'overlay'); + this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); + + markup.push(''); + + return markup.join(''); + }, + + /** + * @private + */ + _setSVGPreamble: function(markup, options) { + if (options.suppressPreamble) { + return; + } + markup.push( + '\n', + '\n' + ); + }, + + /** + * @private + */ + _setSVGHeader: function(markup, options) { + var width = options.width || this.width, + height = options.height || this.height, + vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ', + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + + if (options.viewBox) { + viewBox = 'viewBox="' + + options.viewBox.x + ' ' + + options.viewBox.y + ' ' + + options.viewBox.width + ' ' + + options.viewBox.height + '" '; + } + else { + if (this.svgViewportTransformation) { + vpt = this.viewportTransform; + viewBox = 'viewBox="' + + toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' + + toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' + + toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' + + toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" '; + } + } + + markup.push( + '\n', + 'Created with Fabric.js ', fabric.version, '\n', + '\n', + this.createSVGFontFacesMarkup(), + this.createSVGRefElementsMarkup(), + this.createSVGClipPathMarkup(options), + '\n' + ); + }, + + createSVGClipPathMarkup: function(options) { + var clipPath = this.clipPath; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + return '\n' + + this.clipPath.toClipPathSVG(options.reviver) + + '\n'; + } + return ''; + }, + + /** + * Creates markup containing SVG referenced elements like patterns, gradients etc. + * @return {String} + */ + createSVGRefElementsMarkup: function() { + var _this = this, + markup = ['background', 'overlay'].map(function(prop) { + var fill = _this[prop + 'Color']; + if (fill && fill.toLive) { + var shouldTransform = _this[prop + 'Vpt'], vpt = _this.viewportTransform, + object = { + width: _this.width / (shouldTransform ? vpt[0] : 1), + height: _this.height / (shouldTransform ? vpt[3] : 1) + }; + return fill.toSVG( + object, + { additionalTransform: shouldTransform ? fabric.util.matrixToSVG(vpt) : '' } + ); + } + }); + return markup.join(''); + }, + + /** + * Creates markup containing SVG font faces, + * font URLs for font faces must be collected by developers + * and are not extracted from the DOM by fabricjs + * @param {Array} objects Array of fabric objects + * @return {String} + */ + createSVGFontFacesMarkup: function() { + var markup = '', fontList = { }, obj, fontFamily, + style, row, rowIndex, _char, charIndex, i, len, + fontPaths = fabric.fontPaths, objects = []; + + this._objects.forEach(function add(object) { + objects.push(object); + if (object._objects) { + object._objects.forEach(add); + } + }); + + for (i = 0, len = objects.length; i < len; i++) { + obj = objects[i]; + fontFamily = obj.fontFamily; + if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { + continue; + } + fontList[fontFamily] = true; + if (!obj.styles) { + continue; + } + style = obj.styles; + for (rowIndex in style) { + row = style[rowIndex]; + for (charIndex in row) { + _char = row[charIndex]; + fontFamily = _char.fontFamily; + if (!fontList[fontFamily] && fontPaths[fontFamily]) { + fontList[fontFamily] = true; + } + } + } + } + + for (var j in fontList) { + markup += [ + '\t\t@font-face {\n', + '\t\t\tfont-family: \'', j, '\';\n', + '\t\t\tsrc: url(\'', fontPaths[j], '\');\n', + '\t\t}\n' + ].join(''); + } + + if (markup) { + markup = [ + '\t\n' + ].join(''); + } + + return markup; + }, + + /** + * @private + */ + _setSVGObjects: function(markup, reviver) { + var instance, i, len, objects = this._objects; + for (i = 0, len = objects.length; i < len; i++) { + instance = objects[i]; + if (instance.excludeFromExport) { + continue; + } + this._setSVGObject(markup, instance, reviver); + } + }, + + /** + * @private + */ + _setSVGObject: function(markup, instance, reviver) { + markup.push(instance.toSVG(reviver)); + }, + + /** + * @private + */ + _setSVGBgOverlayImage: function(markup, property, reviver) { + if (this[property] && !this[property].excludeFromExport && this[property].toSVG) { + markup.push(this[property].toSVG(reviver)); + } + }, + + /** + * @private + */ + _setSVGBgOverlayColor: function(markup, property) { + var filler = this[property + 'Color'], vpt = this.viewportTransform, finalWidth = this.width, + finalHeight = this.height; + if (!filler) { + return; + } + if (filler.toLive) { + var repeat = filler.repeat, iVpt = fabric.util.invertTransform(vpt), shouldInvert = this[property + 'Vpt'], + additionalTransform = shouldInvert ? fabric.util.matrixToSVG(iVpt) : ''; + markup.push( + '\n' + ); + } + else { + markup.push( + '\n' + ); + } + }, + /* _TO_SVG_END_ */ + + /** + * Moves an object or the objects of a multiple selection + * to the bottom of the stack of drawn objects + * @param {fabric.Object} object Object to send to back + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendToBack: function (object) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, objs; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = objs.length; i--;) { + obj = objs[i]; + removeFromArray(this._objects, obj); + this._objects.unshift(obj); + } + } + else { + removeFromArray(this._objects, object); + this._objects.unshift(object); + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Moves an object or the objects of a multiple selection + * to the top of the stack of drawn objects + * @param {fabric.Object} object Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringToFront: function (object) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, objs; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = 0; i < objs.length; i++) { + obj = objs[i]; + removeFromArray(this._objects, obj); + this._objects.push(obj); + } + } + else { + removeFromArray(this._objects, object); + this._objects.push(object); + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Moves an object or a selection down in stack of drawn objects + * An optional parameter, intersecting allows to move the object in behind + * the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendBackwards: function (object, intersecting) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, idx, newIdx, objs, objsMoved = 0; + + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = 0; i < objs.length; i++) { + obj = objs[i]; + idx = this._objects.indexOf(obj); + if (idx > 0 + objsMoved) { + newIdx = idx - 1; + removeFromArray(this._objects, obj); + this._objects.splice(newIdx, 0, obj); + } + objsMoved++; + } + } + else { + idx = this._objects.indexOf(object); + if (idx !== 0) { + // if object is not on the bottom of stack + newIdx = this._findNewLowerIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * @private + */ + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx, i; + + if (intersecting) { + newIdx = idx; + + // traverse down the stack looking for the nearest intersecting object + for (i = idx - 1; i >= 0; --i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx - 1; + } + + return newIdx; + }, + + /** + * Moves an object or a selection up in stack of drawn objects + * An optional parameter, intersecting allows to move the object in front + * of the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringForward: function (object, intersecting) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, idx, newIdx, objs, objsMoved = 0; + + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = objs.length; i--;) { + obj = objs[i]; + idx = this._objects.indexOf(obj); + if (idx < this._objects.length - 1 - objsMoved) { + newIdx = idx + 1; + removeFromArray(this._objects, obj); + this._objects.splice(newIdx, 0, obj); + } + objsMoved++; + } + } + else { + idx = this._objects.indexOf(object); + if (idx !== this._objects.length - 1) { + // if object is not on top of stack (last item in an array) + newIdx = this._findNewUpperIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * @private + */ + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx, i, len; + + if (intersecting) { + newIdx = idx; + + // traverse up the stack looking for the nearest intersecting object + for (i = idx + 1, len = this._objects.length; i < len; ++i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx + 1; + } + + return newIdx; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Number} index Position to move to + * @return {fabric.Canvas} thisArg + * @chainable + */ + moveTo: function (object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderOnAddRemove && this.requestRenderAll(); + }, + + /** + * Clears a canvas element and dispose objects + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + // cancel eventually ongoing renders + if (this.isRendering) { + fabric.util.cancelAnimFrame(this.isRendering); + this.isRendering = 0; + } + this.forEachObject(function(object) { + object.dispose && object.dispose(); + }); + this._objects = []; + if (this.backgroundImage && this.backgroundImage.dispose) { + this.backgroundImage.dispose(); + } + this.backgroundImage = null; + if (this.overlayImage && this.overlayImage.dispose) { + this.overlayImage.dispose(); + } + this.overlayImage = null; + this._iTextInstances = null; + this.contextContainer = null; + // restore canvas style + this.lowerCanvasEl.classList.remove('lower-canvas'); + this.lowerCanvasEl.style = this._originalCanvasStyle; + delete this._originalCanvasStyle; + // restore canvas size to original size in case retina scaling was applied + this.lowerCanvasEl.setAttribute('width', this.width); + this.lowerCanvasEl.setAttribute('height', this.height); + fabric.util.cleanUpJsdomNode(this.lowerCanvasEl); + this.lowerCanvasEl = undefined; + return this; + }, + + /** + * Returns a string representation of an instance + * @return {String} string representation of an instance + */ + toString: function () { + return '#'; + } + }); + + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + + extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { + + /** + * @static + * @type String + * @default + */ + EMPTY_JSON: '{"objects": [], "background": "white"}', + + /** + * Provides a way to check support of some of the canvas methods + * (either those of HTMLCanvasElement itself, or rendering context) + * + * @param {String} methodName Method to check support for; + * Could be one of "setLineDash" + * @return {Boolean | null} `true` if method is supported (or at least exists), + * `null` if canvas element or context can not be initialized + */ + supports: function (methodName) { + var el = createCanvasElement(); + + if (!el || !el.getContext) { + return null; + } + + var ctx = el.getContext('2d'); + if (!ctx) { + return null; + } + + switch (methodName) { + + case 'setLineDash': + return typeof ctx.setLineDash !== 'undefined'; + + default: + return null; + } + } + }); + + /** + * Returns Object representation of canvas + * this alias is provided because if you call JSON.stringify on an instance, + * the toJSON object will be invoked if it exists. + * Having a toJSON method means you can do JSON.stringify(myCanvas) + * @function + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON compatible object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} + * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} + * @example JSON without additional properties + * var json = canvas.toJSON(); + * @example JSON with additional properties included + * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY']); + * @example JSON without default values + * canvas.includeDefaultValues = false; + * var json = canvas.toJSON(); + */ + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; + + if (fabric.isLikelyNode) { + fabric.StaticCanvas.prototype.createPNGStream = function() { + var impl = getNodeCanvas(this.lowerCanvasEl); + return impl && impl.createPNGStream(); + }; + fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { + var impl = getNodeCanvas(this.lowerCanvasEl); + return impl && impl.createJPEGStream(opts); + }; + } +})(); + + +/** + * BaseBrush class + * @class fabric.BaseBrush + * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo} + */ +fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { + + /** + * Color of a brush + * @type String + * @default + */ + color: 'rgb(0, 0, 0)', + + /** + * Width of a brush, has to be a Number, no string literals + * @type Number + * @default + */ + width: 1, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), + * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Line endings style of a brush (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'round', + + /** + * Corner style of a brush (one of "bevel", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'round', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of a brush's + * @type Number + * @default + */ + strokeMiterLimit: 10, + + /** + * Stroke Dash Array. + * @type Array + * @default + */ + strokeDashArray: null, + + /** + * When `true`, the free drawing is limited to the whiteboard size. Default to false. + * @type Boolean + * @default false + */ + + limitedToCanvasSize: false, + + + /** + * Sets brush styles + * @private + */ + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.lineCap = this.strokeLineCap; + ctx.miterLimit = this.strokeMiterLimit; + ctx.lineJoin = this.strokeLineJoin; + ctx.setLineDash(this.strokeDashArray || []); + }, + + /** + * Sets the transformation on given context + * @param {RenderingContext2d} ctx context to render on + * @private + */ + _saveAndTransform: function(ctx) { + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + }, + + /** + * Sets brush shadow styles + * @private + */ + _setShadow: function() { + if (!this.shadow) { + return; + } + + var canvas = this.canvas, + shadow = this.shadow, + ctx = canvas.contextTop, + zoom = canvas.getZoom(); + if (canvas && canvas._isRetinaScaling()) { + zoom *= fabric.devicePixelRatio; + } + + ctx.shadowColor = shadow.color; + ctx.shadowBlur = shadow.blur * zoom; + ctx.shadowOffsetX = shadow.offsetX * zoom; + ctx.shadowOffsetY = shadow.offsetY * zoom; + }, + + needsFullRender: function() { + var color = new fabric.Color(this.color); + return color.getAlpha() < 1 || !!this.shadow; + }, + + /** + * Removes brush shadow styles + * @private + */ + _resetShadow: function() { + var ctx = this.canvas.contextTop; + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * Check is pointer is outside canvas boundaries + * @param {Object} pointer + * @private + */ + _isOutSideCanvas: function(pointer) { + return pointer.x < 0 || pointer.x > this.canvas.getWidth() || pointer.y < 0 || pointer.y > this.canvas.getHeight(); + } +}); + + +(function() { + /** + * PencilBrush class + * @class fabric.PencilBrush + * @extends fabric.BaseBrush + */ + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { + + /** + * Discard points that are less than `decimate` pixel distant from each other + * @type Number + * @default 0.4 + */ + decimate: 0.4, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.PencilBrush} Instance of a pencil brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this._points = []; + }, + + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + _drawSegment: function (ctx, p1, p2) { + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + return midPoint; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer, options) { + if (!this.canvas._isMainEvent(options.e)) { + return; + } + this._prepareForDrawing(pointer); + // capture coordinates immediately + // this allows to draw dots (when movement never occurs) + this._captureDrawingPath(pointer); + this._render(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer, options) { + if (!this.canvas._isMainEvent(options.e)) { + return; + } + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + if (this._captureDrawingPath(pointer) && this._points.length > 1) { + if (this.needsFullRender()) { + // redraw curve + // clear top canvas + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + } + else { + var points = this._points, length = points.length, ctx = this.canvas.contextTop; + // draw the curve update + this._saveAndTransform(ctx); + if (this.oldEnd) { + ctx.beginPath(); + ctx.moveTo(this.oldEnd.x, this.oldEnd.y); + } + this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); + ctx.stroke(); + ctx.restore(); + } + } + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function(options) { + if (!this.canvas._isMainEvent(options.e)) { + return true; + } + this.oldEnd = undefined; + this._finalizeAndAddPath(); + return false; + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _prepareForDrawing: function(pointer) { + + var p = new fabric.Point(pointer.x, pointer.y); + + this._reset(); + this._addPoint(p); + this.canvas.contextTop.moveTo(p.x, p.y); + }, + + /** + * @private + * @param {fabric.Point} point Point to be added to points array + */ + _addPoint: function(point) { + if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) { + return false; + } + this._points.push(point); + return true; + }, + + /** + * Clear points array and set contextTop canvas style. + * @private + */ + _reset: function() { + this._points = []; + this._setBrushStyles(); + this._setShadow(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + return this._addPoint(pointerPoint); + }, + + /** + * Draw a smooth path on the topCanvas using quadraticCurveTo + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, i, len, + p1 = this._points[0], + p2 = this._points[1]; + + this._saveAndTransform(ctx); + ctx.beginPath(); + //if we only have 2 points in the path and they are the same + //it means that the user only clicked the canvas without moving the mouse + //then we should be drawing a dot. A path isn't drawn between two identical dots + //that's why we set them apart a bit + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + var width = this.width / 1000; + p1 = new fabric.Point(p1.x, p1.y); + p2 = new fabric.Point(p2.x, p2.y); + p1.x -= width; + p2.x += width; + } + ctx.moveTo(p1.x, p1.y); + + for (i = 1, len = this._points.length; i < len; i++) { + // we pick the point between pi + 1 & pi + 2 as the + // end point and p1 as our control point. + this._drawSegment(ctx, p1, p2); + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + // Draw last line as a straight line while + // we wait for the next point to be able to calculate + // the bezier control point + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + + /** + * Converts points to SVG path + * @param {Array} points Array of points + * @return {(string|number)[][]} SVG path commands + */ + convertPointsToSVGPath: function (points) { + var correction = this.width / 1000; + return fabric.util.getSmoothPathFromPoints(points, correction); + }, + + /** + * @private + * @param {(string|number)[][]} pathData SVG path commands + * @returns {boolean} + */ + _isEmptySVGPath: function (pathData) { + var pathString = fabric.util.joinPath(pathData); + return pathString === 'M 0 0 Q 0 0 0 0 L 0 0'; + }, + + /** + * Creates fabric.Path object to add on canvas + * @param {(string|number)[][]} pathData Path data + * @return {fabric.Path} Path to add on canvas + */ + createPath: function(pathData) { + var path = new fabric.Path(pathData, { + fill: null, + stroke: this.color, + strokeWidth: this.width, + strokeLineCap: this.strokeLineCap, + strokeMiterLimit: this.strokeMiterLimit, + strokeLineJoin: this.strokeLineJoin, + strokeDashArray: this.strokeDashArray, + }); + if (this.shadow) { + this.shadow.affectStroke = true; + path.shadow = new fabric.Shadow(this.shadow); + } + + return path; + }, + + /** + * Decimate points array with the decimate value + */ + decimatePoints: function(points, distance) { + if (points.length <= 2) { + return points; + } + var zoom = this.canvas.getZoom(), adjustedDistance = Math.pow(distance / zoom, 2), + i, l = points.length - 1, lastPoint = points[0], newPoints = [lastPoint], + cDistance; + for (i = 1; i < l - 1; i++) { + cDistance = Math.pow(lastPoint.x - points[i].x, 2) + Math.pow(lastPoint.y - points[i].y, 2); + if (cDistance >= adjustedDistance) { + lastPoint = points[i]; + newPoints.push(lastPoint); + } + } + /** + * Add the last point from the original line to the end of the array. + * This ensures decimate doesn't delete the last point on the line, and ensures the line is > 1 point. + */ + newPoints.push(points[l]); + return newPoints; + }, + + /** + * On mouseup after drawing the path on contextTop canvas + * we use the points captured to create an new fabric path object + * and add it to the fabric canvas. + */ + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + if (this.decimate) { + this._points = this.decimatePoints(this._points, this.decimate); + } + var pathData = this.convertPointsToSVGPath(this._points); + if (this._isEmptySVGPath(pathData)) { + // do not create 0 width/height paths, as they are + // rendered inconsistently across browsers + // Firefox 4, for example, renders a dot, + // whereas Chrome 10 renders nothing + this.canvas.requestRenderAll(); + return; + } + + var path = this.createPath(pathData); + this.canvas.clearContext(this.canvas.contextTop); + this.canvas.fire('before:path:created', { path: path }); + this.canvas.add(path); + this.canvas.requestRenderAll(); + path.setCoords(); + this._resetShadow(); + + + // fire event 'path' created + this.canvas.fire('path:created', { path: path }); + } + }); +})(); + + +/** + * CircleBrush class + * @class fabric.CircleBrush + */ +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { + + /** + * Width of a brush + * @type Number + * @default + */ + width: 10, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.CircleBrush} Instance of a circle brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.points = []; + }, + + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + drawDot: function(pointer) { + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop; + this._saveAndTransform(ctx); + this.dot(ctx, point); + ctx.restore(); + }, + + dot: function(ctx, point) { + ctx.fillStyle = point.fill; + ctx.beginPath(); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); + ctx.closePath(); + ctx.fill(); + }, + + /** + * Invoked on mouse down + */ + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); + }, + + /** + * Render the full state of the brush + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, i, len, + points = this.points; + this._saveAndTransform(ctx); + for (i = 0, len = points.length; i < len; i++) { + this.dot(ctx, points[i]); + } + ctx.restore(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + if (this.needsFullRender()) { + this.canvas.clearContext(this.canvas.contextTop); + this.addPoint(pointer); + this._render(); + } + else { + this.drawDot(pointer); + } + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len; + this.canvas.renderOnAddRemove = false; + + var circles = []; + + for (i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); + + this.shadow && (circle.shadow = new fabric.Shadow(this.shadow)); + + circles.push(circle); + } + var group = new fabric.Group(circles); + group.canvas = this.canvas; + + this.canvas.fire('before:path:created', { path: group }); + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.requestRenderAll(); + }, + + /** + * @param {Object} pointer + * @return {fabric.Point} Just added pointer point + */ + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), + + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, + + circleColor = new fabric.Color(this.color) + .setAlpha(fabric.util.getRandomInt(0, 100) / 100) + .toRgba(); + + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + + this.points.push(pointerPoint); + + return pointerPoint; + } +}); + + +/** + * SprayBrush class + * @class fabric.SprayBrush + */ +fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { + + /** + * Width of a spray + * @type Number + * @default + */ + width: 10, + + /** + * Density of a spray (number of dots per chunk) + * @type Number + * @default + */ + density: 20, + + /** + * Width of spray dots + * @type Number + * @default + */ + dotWidth: 1, + + /** + * Width variance of spray dots + * @type Number + * @default + */ + dotWidthVariance: 1, + + /** + * Whether opacity of a dot should be random + * @type Boolean + * @default + */ + randomOpacity: false, + + /** + * Whether overlapping dots (rectangles) should be removed (for performance reasons) + * @type Boolean + * @default + */ + optimizeOverlapping: true, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.SprayBrush} Instance of a spray brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = []; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + + this.addSprayChunk(pointer); + this.render(this.sprayChunkPoints); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + this.addSprayChunk(pointer); + this.render(this.sprayChunkPoints); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var rects = []; + + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: 'center', + originY: 'center', + fill: this.color + }); + rects.push(rect); + } + } + + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + + var group = new fabric.Group(rects); + this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); + this.canvas.fire('before:path:created', { path: group }); + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.requestRenderAll(); + }, + + /** + * @private + * @param {Array} rects + */ + _getOptimizedRects: function(rects) { + + // avoid creating duplicate rects at the same coordinates + var uniqueRects = { }, key, i, len; + + for (i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + '' + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = []; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + + return uniqueRectsArray; + }, + + /** + * Render new chunk of spray brush + */ + render: function(sprayChunk) { + var ctx = this.canvas.contextTop, i, len; + ctx.fillStyle = this.color; + + this._saveAndTransform(ctx); + + for (i = 0, len = sprayChunk.length; i < len; i++) { + var point = sprayChunk[i]; + if (typeof point.opacity !== 'undefined') { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + + /** + * Render all spray chunks + */ + _render: function() { + var ctx = this.canvas.contextTop, i, ilen; + ctx.fillStyle = this.color; + + this._saveAndTransform(ctx); + + for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + this.render(this.sprayChunks[i]); + } + ctx.restore(); + }, + + /** + * @param {Object} pointer + */ + addSprayChunk: function(pointer) { + this.sprayChunkPoints = []; + + var x, y, width, radius = this.width / 2, i; + + for (i = 0; i < this.density; i++) { + + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt( + // bottom clamp width to 1 + Math.max(1, this.dotWidth - this.dotWidthVariance), + this.dotWidth + this.dotWidthVariance); + } + else { + width = this.dotWidth; + } + + var point = new fabric.Point(x, y); + point.width = width; + + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + + this.sprayChunkPoints.push(point); + } + + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + + +/** + * PatternBrush class + * @class fabric.PatternBrush + * @extends fabric.BaseBrush + */ +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { + + getPatternSrc: function() { + + var dotWidth = 20, + dotDistance = 5, + patternCanvas = fabric.util.createCanvasElement(), + patternCtx = patternCanvas.getContext('2d'); + + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + + return patternCanvas; + }, + + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); + }, + + /** + * Creates "pattern" instance property + */ + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); + }, + + /** + * Sets brush styles + */ + _setBrushStyles: function() { + this.callSuper('_setBrushStyles'); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + + /** + * Creates path + */ + createPath: function(pathData) { + var path = this.callSuper('createPath', pathData), + topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); + + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction(), + offsetX: -topLeft.x, + offsetY: -topLeft.y + }); + return path; + } +}); + + +(function() { + + var getPointer = fabric.util.getPointer, + degreesToRadians = fabric.util.degreesToRadians, + isTouchEvent = fabric.util.isTouchEvent; + + /** + * Canvas class + * @class fabric.Canvas + * @extends fabric.StaticCanvas + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas} + * @see {@link fabric.Canvas#initialize} for constructor definition + * + * @fires object:modified at the end of a transform or any change when statefull is true + * @fires object:rotating while an object is being rotated from the control + * @fires object:scaling while an object is being scaled by controls + * @fires object:moving while an object is being dragged + * @fires object:skewing while an object is being skewed from the controls + * + * @fires before:transform before a transform is is started + * @fires before:selection:cleared + * @fires selection:cleared + * @fires selection:updated + * @fires selection:created + * + * @fires path:created after a drawing operation ends and the path is added + * @fires mouse:down + * @fires mouse:move + * @fires mouse:up + * @fires mouse:down:before on mouse down, before the inner fabric logic runs + * @fires mouse:move:before on mouse move, before the inner fabric logic runs + * @fires mouse:up:before on mouse up, before the inner fabric logic runs + * @fires mouse:over + * @fires mouse:out + * @fires mouse:dblclick whenever a native dbl click event fires on the canvas. + * + * @fires dragover + * @fires dragenter + * @fires dragleave + * @fires drop + * @fires after:render at the end of the render process, receives the context in the callback + * @fires before:render at start the render process, receives the context in the callback + * + * the following events are deprecated: + * @fires object:rotated at the end of a rotation transform + * @fires object:scaled at the end of a scale transform + * @fires object:moved at the end of translation transform + * @fires object:skewed at the end of a skew transform + */ + fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { + + /** + * Constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(el, options) { + options || (options = { }); + this.renderAndResetBound = this.renderAndReset.bind(this); + this.requestRenderAllBound = this.requestRenderAll.bind(this); + this._initStatic(el, options); + this._initInteractive(); + this._createCacheCanvas(); + }, + + /** + * When true, objects can be transformed by one side (unproportionally) + * when dragged on the corners that normally would not do that. + * @type Boolean + * @default + * @since fabric 4.0 // changed name and default value + */ + uniformScaling: true, + + /** + * Indicates which key switches uniform scaling. + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled. + * totally wrong named. this sounds like `uniform scaling` + * if Canvas.uniformScaling is true, pressing this will set it to false + * and viceversa. + * @since 1.6.2 + * @type String + * @default + */ + uniScaleKey: 'shiftKey', + + /** + * When true, objects use center point as the origin of scale transformation. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, objects use center point as the origin of rotate transformation. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: false, + + /** + * Indicates which key enable centered Transform + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled feature disabled. + * @since 1.6.2 + * @type String + * @default + */ + centeredKey: 'altKey', + + /** + * Indicates which key enable alternate action on corner + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled feature disabled. + * @since 1.6.2 + * @type String + * @default + */ + altActionKey: 'shiftKey', + + /** + * Indicates that canvas is interactive. This property should not be changed. + * @type Boolean + * @default + */ + interactive: true, + + /** + * Indicates whether group selection should be enabled + * @type Boolean + * @default + */ + selection: true, + + /** + * Indicates which key or keys enable multiple click selection + * Pass value as a string or array of strings + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or empty or containing any other string that is not a modifier key + * feature is disabled. + * @since 1.6.2 + * @type String|Array + * @default + */ + selectionKey: 'shiftKey', + + /** + * Indicates which key enable alternative selection + * in case of target overlapping with active object + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * For a series of reason that come from the general expectations on how + * things should work, this feature works only for preserveObjectStacking true. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled. + * @since 1.6.5 + * @type null|String + * @default + */ + altSelectionKey: null, + + /** + * Color of selection + * @type String + * @default + */ + selectionColor: 'rgba(100, 100, 255, 0.3)', // blue + + /** + * Default dash array pattern + * If not empty the selection border is dashed + * @type Array + */ + selectionDashArray: [], + + /** + * Color of the border of selection (usually slightly darker than color of selection itself) + * @type String + * @default + */ + selectionBorderColor: 'rgba(255, 255, 255, 0.3)', + + /** + * Width of a line used in object/group selection + * @type Number + * @default + */ + selectionLineWidth: 1, + + /** + * Select only shapes that are fully contained in the dragged selection rectangle. + * @type Boolean + * @default + */ + selectionFullyContained: false, + + /** + * Default cursor value used when hovering over an object on canvas + * @type String + * @default + */ + hoverCursor: 'move', + + /** + * Default cursor value used when moving an object on canvas + * @type String + * @default + */ + moveCursor: 'move', + + /** + * Default cursor value used for the entire canvas + * @type String + * @default + */ + defaultCursor: 'default', + + /** + * Cursor value used during free drawing + * @type String + * @default + */ + freeDrawingCursor: 'crosshair', + + /** + * Cursor value used for rotation point + * @type String + * @default + */ + rotationCursor: 'crosshair', + + /** + * Cursor value used for disabled elements ( corners with disabled action ) + * @type String + * @since 2.0.0 + * @default + */ + notAllowedCursor: 'not-allowed', + + /** + * Default element class that's given to wrapper (div) element of canvas + * @type String + * @default + */ + containerClass: 'canvas-container', + + /** + * When true, object detection happens on per-pixel basis rather than on per-bounding-box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * Number of pixels around target pixel to tolerate (consider active) during object detection + * @type Number + * @default + */ + targetFindTolerance: 0, + + /** + * When true, target detection is skipped. Target detection will return always undefined. + * click selection won't work anymore, events will fire with no targets. + * if something is selected before setting it to true, it will be deselected at the first click. + * area selection will still work. check the `selection` property too. + * if you deactivate both, you should look into staticCanvas. + * @type Boolean + * @default + */ + skipTargetFind: false, + + /** + * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. + * After mousedown, mousemove creates a shape, + * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. + * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing} + * @type Boolean + * @default + */ + isDrawingMode: false, + + /** + * Indicates whether objects should remain in current stack position when selected. + * When false objects are brought to top and rendered as part of the selection group + * @type Boolean + * @default + */ + preserveObjectStacking: false, + + /** + * Indicates the angle that an object will lock to while rotating. + * @type Number + * @since 1.6.7 + * @default + */ + snapAngle: 0, + + /** + * Indicates the distance from the snapAngle the rotation will lock to the snapAngle. + * When `null`, the snapThreshold will default to the snapAngle. + * @type null|Number + * @since 1.6.7 + * @default + */ + snapThreshold: null, + + /** + * Indicates if the right click on canvas can output the context menu or not + * @type Boolean + * @since 1.6.5 + * @default + */ + stopContextMenu: false, + + /** + * Indicates if the canvas can fire right click events + * @type Boolean + * @since 1.6.5 + * @default + */ + fireRightClick: false, + + /** + * Indicates if the canvas can fire middle click events + * @type Boolean + * @since 1.7.8 + * @default + */ + fireMiddleClick: false, + + /** + * Keep track of the subTargets for Mouse Events + * @type fabric.Object[] + */ + targets: [], + + /** + * Keep track of the hovered target + * @type fabric.Object + * @private + */ + _hoveredTarget: null, + + /** + * hold the list of nested targets hovered + * @type fabric.Object[] + * @private + */ + _hoveredTargets: [], + + /** + * @private + */ + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEventListeners(); + + this._initRetinaScaling(); + + this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); + + this.calcOffset(); + }, + + /** + * Divides objects in two groups, one to render immediately + * and one to render as activeGroup. + * @return {Array} objects to render immediately and pushes the other in the activeGroup. + */ + _chooseObjectsToRender: function() { + var activeObjects = this.getActiveObjects(), + object, objsToRender, activeGroupObjects; + + if (activeObjects.length > 0 && !this.preserveObjectStacking) { + objsToRender = []; + activeGroupObjects = []; + for (var i = 0, length = this._objects.length; i < length; i++) { + object = this._objects[i]; + if (activeObjects.indexOf(object) === -1 ) { + objsToRender.push(object); + } + else { + activeGroupObjects.push(object); + } + } + if (activeObjects.length > 1) { + this._activeObject._objects = activeGroupObjects; + } + objsToRender.push.apply(objsToRender, activeGroupObjects); + } + else { + objsToRender = this._objects; + } + return objsToRender; + }, + + /** + * Renders both the top canvas and the secondary container canvas. + * @return {fabric.Canvas} instance + * @chainable + */ + renderAll: function () { + if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { + this.clearContext(this.contextTop); + this.contextTopDirty = false; + } + if (this.hasLostContext) { + this.renderTopLayer(this.contextTop); + } + var canvasToDrawOn = this.contextContainer; + this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); + return this; + }, + + renderTopLayer: function(ctx) { + ctx.save(); + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this.freeDrawingBrush && this.freeDrawingBrush._render(); + this.contextTopDirty = true; + } + // we render the top context - last object + if (this.selection && this._groupSelector) { + this._drawSelection(ctx); + this.contextTopDirty = true; + } + ctx.restore(); + }, + + /** + * Method to render only the top canvas. + * Also used to render the group selection box. + * @return {fabric.Canvas} thisArg + * @chainable + */ + renderTop: function () { + var ctx = this.contextTop; + this.clearContext(ctx); + this.renderTopLayer(ctx); + this.fire('after:render'); + return this; + }, + + /** + * @private + */ + _normalizePointer: function (object, pointer) { + var m = object.calcTransformMatrix(), + invertedM = fabric.util.invertTransform(m), + vptPointer = this.restorePointerVpt(pointer); + return fabric.util.transformPoint(vptPointer, invertedM); + }, + + /** + * Returns true if object is transparent at a certain location + * @param {fabric.Object} target Object to check + * @param {Number} x Left coordinate + * @param {Number} y Top coordinate + * @return {Boolean} + */ + isTargetTransparent: function (target, x, y) { + // in case the target is the activeObject, we cannot execute this optimization + // because we need to draw controls too. + if (target.shouldCache() && target._cacheCanvas && target !== this._activeObject) { + var normalizedPointer = this._normalizePointer(target, {x: x, y: y}), + targetRelativeX = Math.max(target.cacheTranslationX + (normalizedPointer.x * target.zoomX), 0), + targetRelativeY = Math.max(target.cacheTranslationY + (normalizedPointer.y * target.zoomY), 0); + + var isTransparent = fabric.util.isTransparent( + target._cacheContext, Math.round(targetRelativeX), Math.round(targetRelativeY), this.targetFindTolerance); + + return isTransparent; + } + + var ctx = this.contextCache, + originalColor = target.selectionBackgroundColor, v = this.viewportTransform; + + target.selectionBackgroundColor = ''; + + this.clearContext(ctx); + + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + target.render(ctx); + ctx.restore(); + + target.selectionBackgroundColor = originalColor; + + var isTransparent = fabric.util.isTransparent( + ctx, x, y, this.targetFindTolerance); + + return isTransparent; + }, + + /** + * takes an event and determines if selection key has been pressed + * @private + * @param {Event} e Event object + */ + _isSelectionKeyPressed: function(e) { + var selectionKeyPressed = false; + + if (Object.prototype.toString.call(this.selectionKey) === '[object Array]') { + selectionKeyPressed = !!this.selectionKey.find(function(key) { return e[key] === true; }); + } + else { + selectionKeyPressed = e[this.selectionKey]; + } + + return selectionKeyPressed; + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _shouldClearSelection: function (e, target) { + var activeObjects = this.getActiveObjects(), + activeObject = this._activeObject; + + return ( + !target + || + (target && + activeObject && + activeObjects.length > 1 && + activeObjects.indexOf(target) === -1 && + activeObject !== target && + !this._isSelectionKeyPressed(e)) + || + (target && !target.evented) + || + (target && + !target.selectable && + activeObject && + activeObject !== target) + ); + }, + + /** + * centeredScaling from object can't override centeredScaling from canvas. + * this should be fixed, since object setting should take precedence over canvas. + * also this should be something that will be migrated in the control properties. + * as ability to define the origin of the transformation that the control provide. + * @private + * @param {fabric.Object} target + * @param {String} action + * @param {Boolean} altKey + */ + _shouldCenterTransform: function (target, action, altKey) { + if (!target) { + return; + } + + var centerTransform; + + if (action === 'scale' || action === 'scaleX' || action === 'scaleY' || action === 'resizing') { + centerTransform = this.centeredScaling || target.centeredScaling; + } + else if (action === 'rotate') { + centerTransform = this.centeredRotation || target.centeredRotation; + } + + return centerTransform ? !altKey : altKey; + }, + + /** + * should disappear before release 4.0 + * @private + */ + _getOriginFromCorner: function(target, corner) { + var origin = { + x: target.originX, + y: target.originY + }; + + if (corner === 'ml' || corner === 'tl' || corner === 'bl') { + origin.x = 'right'; + } + else if (corner === 'mr' || corner === 'tr' || corner === 'br') { + origin.x = 'left'; + } + + if (corner === 'tl' || corner === 'mt' || corner === 'tr') { + origin.y = 'bottom'; + } + else if (corner === 'bl' || corner === 'mb' || corner === 'br') { + origin.y = 'top'; + } + return origin; + }, + + /** + * @private + * @param {Boolean} alreadySelected true if target is already selected + * @param {String} corner a string representing the corner ml, mr, tl ... + * @param {Event} e Event object + * @param {fabric.Object} [target] inserted back to help overriding. Unused + */ + _getActionFromCorner: function(alreadySelected, corner, e, target) { + if (!corner || !alreadySelected) { + return 'drag'; + } + var control = target.controls[corner]; + return control.getActionName(e, control, target); + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _setupCurrentTransform: function (e, target, alreadySelected) { + if (!target) { + return; + } + + var pointer = this.getPointer(e), corner = target.__corner, + control = target.controls[corner], + actionHandler = (alreadySelected && corner) ? + control.getActionHandler(e, target, control) : fabric.controlsUtils.dragHandler, + action = this._getActionFromCorner(alreadySelected, corner, e, target), + origin = this._getOriginFromCorner(target, corner), + altKey = e[this.centeredKey], + transform = { + target: target, + action: action, + actionHandler: actionHandler, + corner: corner, + scaleX: target.scaleX, + scaleY: target.scaleY, + skewX: target.skewX, + skewY: target.skewY, + // used by transation + offsetX: pointer.x - target.left, + offsetY: pointer.y - target.top, + originX: origin.x, + originY: origin.y, + ex: pointer.x, + ey: pointer.y, + lastX: pointer.x, + lastY: pointer.y, + // unsure they are useful anymore. + // left: target.left, + // top: target.top, + theta: degreesToRadians(target.angle), + // end of unsure + width: target.width * target.scaleX, + shiftKey: e.shiftKey, + altKey: altKey, + original: fabric.util.saveObjectTransform(target), + }; + + if (this._shouldCenterTransform(target, action, altKey)) { + transform.originX = 'center'; + transform.originY = 'center'; + } + transform.original.originX = origin.x; + transform.original.originY = origin.y; + this._currentTransform = transform; + this._beforeTransform(e); + }, + + /** + * Set the cursor type of the canvas element + * @param {String} value Cursor type of the canvas element. + * @see http://www.w3.org/TR/css3-ui/#cursor + */ + setCursor: function (value) { + this.upperCanvasEl.style.cursor = value; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx to draw the selection on + */ + _drawSelection: function (ctx) { + var selector = this._groupSelector, + viewportStart = new fabric.Point(selector.ex, selector.ey), + start = fabric.util.transformPoint(viewportStart, this.viewportTransform), + viewportExtent = new fabric.Point(selector.ex + selector.left, selector.ey + selector.top), + extent = fabric.util.transformPoint(viewportExtent, this.viewportTransform), + minX = Math.min(start.x, extent.x), + minY = Math.min(start.y, extent.y), + maxX = Math.max(start.x, extent.x), + maxY = Math.max(start.y, extent.y), + strokeOffset = this.selectionLineWidth / 2; + + if (this.selectionColor) { + ctx.fillStyle = this.selectionColor; + ctx.fillRect(minX, minY, maxX - minX, maxY - minY); + } + + if (!this.selectionLineWidth || !this.selectionBorderColor) { + return; + } + ctx.lineWidth = this.selectionLineWidth; + ctx.strokeStyle = this.selectionBorderColor; + + minX += strokeOffset; + minY += strokeOffset; + maxX -= strokeOffset; + maxY -= strokeOffset; + // selection border + fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray); + ctx.strokeRect(minX, minY, maxX - minX, maxY - minY); + }, + + /** + * Method that determines what object we are clicking on + * the skipGroup parameter is for internal use, is needed for shift+click action + * 11/09/2018 TODO: would be cool if findTarget could discern between being a full target + * or the outside part of the corner. + * @param {Event} e mouse event + * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through + * @return {fabric.Object} the target found + */ + findTarget: function (e, skipGroup) { + if (this.skipTargetFind) { + return; + } + + var ignoreZoom = true, + pointer = this.getPointer(e, ignoreZoom), + activeObject = this._activeObject, + aObjects = this.getActiveObjects(), + activeTarget, activeTargetSubs, + isTouch = isTouchEvent(e), + shouldLookForActive = (aObjects.length > 1 && !skipGroup) || aObjects.length === 1; + + // first check current group (if one exists) + // active group does not check sub targets like normal groups. + // if active group just exits. + this.targets = []; + + // if we hit the corner of an activeObject, let's return that. + if (shouldLookForActive && activeObject._findTargetCorner(pointer, isTouch)) { + return activeObject; + } + if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) { + return activeObject; + } + if (aObjects.length === 1 && + activeObject === this._searchPossibleTargets([activeObject], pointer)) { + if (!this.preserveObjectStacking) { + return activeObject; + } + else { + activeTarget = activeObject; + activeTargetSubs = this.targets; + this.targets = []; + } + } + var target = this._searchPossibleTargets(this._objects, pointer); + if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) { + target = activeTarget; + this.targets = activeTargetSubs; + } + return target; + }, + + /** + * Checks point is inside the object. + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @param {fabric.Object} obj Object to test against + * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target. + * @return {Boolean} true if point is contained within an area of given object + * @private + */ + _checkTarget: function(pointer, obj, globalPointer) { + if (obj && + obj.visible && + obj.evented && + // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html + // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html + obj.containsPoint(pointer) + ) { + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y); + if (!isTransparent) { + return true; + } + } + else { + return true; + } + } + }, + + /** + * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted + * @param {Array} [objects] objects array to look into + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @return {fabric.Object} object that contains pointer + * @private + */ + _searchPossibleTargets: function(objects, pointer) { + // Cache all targets where their bounding box contains point. + var target, i = objects.length, subTarget; + // Do not check for currently grouped objects, since we check the parent group itself. + // until we call this function specifically to search inside the activeGroup + while (i--) { + var objToCheck = objects[i]; + var pointerToUse = objToCheck.group ? + this._normalizePointer(objToCheck.group, pointer) : pointer; + if (this._checkTarget(pointerToUse, objToCheck, pointer)) { + target = objects[i]; + if (target.subTargetCheck && target instanceof fabric.Group) { + subTarget = this._searchPossibleTargets(target._objects, pointer); + subTarget && this.targets.push(subTarget); + } + break; + } + } + return target; + }, + + /** + * Returns pointer coordinates without the effect of the viewport + * @param {Object} pointer with "x" and "y" number values + * @return {Object} object with "x" and "y" number values + */ + restorePointerVpt: function(pointer) { + return fabric.util.transformPoint( + pointer, + fabric.util.invertTransform(this.viewportTransform) + ); + }, + + /** + * Returns pointer coordinates relative to canvas. + * Can return coordinates with or without viewportTransform. + * ignoreZoom false gives back coordinates that represent + * the point clicked on canvas element. + * ignoreZoom true gives back coordinates after being processed + * by the viewportTransform ( sort of coordinates of what is displayed + * on the canvas where you are clicking. + * ignoreZoom true = HTMLElement coordinates relative to top,left + * ignoreZoom false, default = fabric space coordinates, the same used for shape position + * To interact with your shapes top and left you want to use ignoreZoom true + * most of the time, while ignoreZoom false will give you coordinates + * compatible with the object.oCoords system. + * of the time. + * @param {Event} e + * @param {Boolean} ignoreZoom + * @return {Object} object with "x" and "y" number values + */ + getPointer: function (e, ignoreZoom) { + // return cached values if we are in the event processing chain + if (this._absolutePointer && !ignoreZoom) { + return this._absolutePointer; + } + if (this._pointer && ignoreZoom) { + return this._pointer; + } + + var pointer = getPointer(e), + upperCanvasEl = this.upperCanvasEl, + bounds = upperCanvasEl.getBoundingClientRect(), + boundsWidth = bounds.width || 0, + boundsHeight = bounds.height || 0, + cssScale; + + if (!boundsWidth || !boundsHeight ) { + if ('top' in bounds && 'bottom' in bounds) { + boundsHeight = Math.abs( bounds.top - bounds.bottom ); + } + if ('right' in bounds && 'left' in bounds) { + boundsWidth = Math.abs( bounds.right - bounds.left ); + } + } + + this.calcOffset(); + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; + if (!ignoreZoom) { + pointer = this.restorePointerVpt(pointer); + } + + var retinaScaling = this.getRetinaScaling(); + if (retinaScaling !== 1) { + pointer.x /= retinaScaling; + pointer.y /= retinaScaling; + } + + if (boundsWidth === 0 || boundsHeight === 0) { + // If bounds are not available (i.e. not visible), do not apply scale. + cssScale = { width: 1, height: 1 }; + } + else { + cssScale = { + width: upperCanvasEl.width / boundsWidth, + height: upperCanvasEl.height / boundsHeight + }; + } + + return { + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height + }; + }, + + /** + * @private + * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized + */ + _createUpperCanvas: function () { + var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''), + lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl; + + // there is no need to create a new upperCanvas element if we have already one. + if (upperCanvasEl) { + upperCanvasEl.className = ''; + } + else { + upperCanvasEl = this._createCanvasElement(); + this.upperCanvasEl = upperCanvasEl; + } + fabric.util.addClass(upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); + + this.wrapperEl.appendChild(upperCanvasEl); + + this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl); + this._applyCanvasStyle(upperCanvasEl); + this.contextTop = upperCanvasEl.getContext('2d'); + }, + + /** + * @private + */ + _createCacheCanvas: function () { + this.cacheCanvasEl = this._createCanvasElement(); + this.cacheCanvasEl.setAttribute('width', this.width); + this.cacheCanvasEl.setAttribute('height', this.height); + this.contextCache = this.cacheCanvasEl.getContext('2d'); + }, + + /** + * @private + */ + _initWrapperElement: function () { + this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { + 'class': this.containerClass + }); + fabric.util.setStyle(this.wrapperEl, { + width: this.width + 'px', + height: this.height + 'px', + position: 'relative' + }); + fabric.util.makeElementUnselectable(this.wrapperEl); + }, + + /** + * @private + * @param {HTMLElement} element canvas element to apply styles on + */ + _applyCanvasStyle: function (element) { + var width = this.width || element.width, + height = this.height || element.height; + + fabric.util.setStyle(element, { + position: 'absolute', + width: width + 'px', + height: height + 'px', + left: 0, + top: 0, + 'touch-action': this.allowTouchScrolling ? 'manipulation' : 'none', + '-ms-touch-action': this.allowTouchScrolling ? 'manipulation' : 'none' + }); + element.width = width; + element.height = height; + fabric.util.makeElementUnselectable(element); + }, + + /** + * Copy the entire inline style from one element (fromEl) to another (toEl) + * @private + * @param {Element} fromEl Element style is copied from + * @param {Element} toEl Element copied style is applied to + */ + _copyCanvasStyle: function (fromEl, toEl) { + toEl.style.cssText = fromEl.style.cssText; + }, + + /** + * Returns context of canvas where object selection is drawn + * @return {CanvasRenderingContext2D} + */ + getSelectionContext: function() { + return this.contextTop; + }, + + /** + * Returns <canvas> element on which object selection is drawn + * @return {HTMLCanvasElement} + */ + getSelectionElement: function () { + return this.upperCanvasEl; + }, + + /** + * Returns currently active object + * @return {fabric.Object} active object + */ + getActiveObject: function () { + return this._activeObject; + }, + + /** + * Returns an array with the current selected objects + * @return {fabric.Object} active object + */ + getActiveObjects: function () { + var active = this._activeObject; + if (active) { + if (active.type === 'activeSelection' && active._objects) { + return active._objects.slice(0); + } + else { + return [active]; + } + } + return []; + }, + + /** + * @private + * @param {fabric.Object} obj Object that was removed + */ + _onObjectRemoved: function(obj) { + // removing active object should fire "selection:cleared" events + if (obj === this._activeObject) { + this.fire('before:selection:cleared', { target: obj }); + this._discardActiveObject(); + this.fire('selection:cleared', { target: obj }); + obj.fire('deselected'); + } + if (obj === this._hoveredTarget){ + this._hoveredTarget = null; + this._hoveredTargets = []; + } + this.callSuper('_onObjectRemoved', obj); + }, + + /** + * @private + * Compares the old activeObject with the current one and fires correct events + * @param {fabric.Object} obj old activeObject + */ + _fireSelectionEvents: function(oldObjects, e) { + var somethingChanged = false, objects = this.getActiveObjects(), + added = [], removed = []; + oldObjects.forEach(function(oldObject) { + if (objects.indexOf(oldObject) === -1) { + somethingChanged = true; + oldObject.fire('deselected', { + e: e, + target: oldObject + }); + removed.push(oldObject); + } + }); + objects.forEach(function(object) { + if (oldObjects.indexOf(object) === -1) { + somethingChanged = true; + object.fire('selected', { + e: e, + target: object + }); + added.push(object); + } + }); + if (oldObjects.length > 0 && objects.length > 0) { + somethingChanged && this.fire('selection:updated', { + e: e, + selected: added, + deselected: removed, + // added for backward compatibility + // deprecated + updated: added[0] || removed[0], + target: this._activeObject, + }); + } + else if (objects.length > 0) { + this.fire('selection:created', { + e: e, + selected: added, + target: this._activeObject, + }); + } + else if (oldObjects.length > 0) { + this.fire('selection:cleared', { + e: e, + deselected: removed, + }); + } + }, + + /** + * Sets given object as the only active object on canvas + * @param {fabric.Object} object Object to set as an active one + * @param {Event} [e] Event (passed along when firing "object:selected") + * @return {fabric.Canvas} thisArg + * @chainable + */ + setActiveObject: function (object, e) { + var currentActives = this.getActiveObjects(); + this._setActiveObject(object, e); + this._fireSelectionEvents(currentActives, e); + return this; + }, + + /** + * This is a private method for now. + * This is supposed to be equivalent to setActiveObject but without firing + * any event. There is commitment to have this stay this way. + * This is the functional part of setActiveObject. + * @private + * @param {Object} object to set as active + * @param {Event} [e] Event (passed along when firing "object:selected") + * @return {Boolean} true if the selection happened + */ + _setActiveObject: function(object, e) { + if (this._activeObject === object) { + return false; + } + if (!this._discardActiveObject(e, object)) { + return false; + } + if (object.onSelect({ e: e })) { + return false; + } + this._activeObject = object; + return true; + }, + + /** + * This is a private method for now. + * This is supposed to be equivalent to discardActiveObject but without firing + * any events. There is commitment to have this stay this way. + * This is the functional part of discardActiveObject. + * @param {Event} [e] Event (passed along when firing "object:deselected") + * @param {Object} object to set as active + * @return {Boolean} true if the selection happened + * @private + */ + _discardActiveObject: function(e, object) { + var obj = this._activeObject; + if (obj) { + // onDeselect return TRUE to cancel selection; + if (obj.onDeselect({ e: e, object: object })) { + return false; + } + this._activeObject = null; + } + return true; + }, + + /** + * Discards currently active object and fire events. If the function is called by fabric + * as a consequence of a mouse event, the event is passed as a parameter and + * sent to the fire function for the custom events. When used as a method the + * e param does not have any application. + * @param {event} e + * @return {fabric.Canvas} thisArg + * @chainable + */ + discardActiveObject: function (e) { + var currentActives = this.getActiveObjects(), activeObject = this.getActiveObject(); + if (currentActives.length) { + this.fire('before:selection:cleared', { target: activeObject, e: e }); + } + this._discardActiveObject(e); + this._fireSelectionEvents(currentActives, e); + return this; + }, + + /** + * Clears a canvas element and removes all event listeners + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + var wrapper = this.wrapperEl; + this.removeListeners(); + wrapper.removeChild(this.upperCanvasEl); + wrapper.removeChild(this.lowerCanvasEl); + this.contextCache = null; + this.contextTop = null; + ['upperCanvasEl', 'cacheCanvasEl'].forEach((function(element) { + fabric.util.cleanUpJsdomNode(this[element]); + this[element] = undefined; + }).bind(this)); + if (wrapper.parentNode) { + wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); + } + delete this.wrapperEl; + fabric.StaticCanvas.prototype.dispose.call(this); + return this; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + // this.discardActiveGroup(); + this.discardActiveObject(); + this.clearContext(this.contextTop); + return this.callSuper('clear'); + }, + + /** + * Draws objects' controls (borders/controls) + * @param {CanvasRenderingContext2D} ctx Context to render controls on + */ + drawControls: function(ctx) { + var activeObject = this._activeObject; + + if (activeObject) { + activeObject._renderControls(ctx); + } + }, + + /** + * @private + */ + _toObject: function(instance, methodName, propertiesToInclude) { + //If the object is part of the current selection group, it should + //be transformed appropriately + //i.e. it should be serialised as it would appear if the selection group + //were to be destroyed. + var originalProperties = this._realizeGroupTransformOnObject(instance), + object = this.callSuper('_toObject', instance, methodName, propertiesToInclude); + //Undo the damage we did by changing all of its properties + this._unwindGroupTransformOnObject(instance, originalProperties); + return object; + }, + + /** + * Realises an object's group transformation on it + * @private + * @param {fabric.Object} [instance] the object to transform (gets mutated) + * @returns the original values of instance which were changed + */ + _realizeGroupTransformOnObject: function(instance) { + if (instance.group && instance.group.type === 'activeSelection' && this._activeObject === instance.group) { + var layoutProps = ['angle', 'flipX', 'flipY', 'left', 'scaleX', 'scaleY', 'skewX', 'skewY', 'top']; + //Copy all the positionally relevant properties across now + var originalValues = {}; + layoutProps.forEach(function(prop) { + originalValues[prop] = instance[prop]; + }); + fabric.util.addTransformToObject(instance, this._activeObject.calcOwnMatrix()); + return originalValues; + } + else { + return null; + } + }, + + /** + * Restores the changed properties of instance + * @private + * @param {fabric.Object} [instance] the object to un-transform (gets mutated) + * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject + */ + _unwindGroupTransformOnObject: function(instance, originalValues) { + if (originalValues) { + instance.set(originalValues); + } + }, + + /** + * @private + */ + _setSVGObject: function(markup, instance, reviver) { + //If the object is in a selection group, simulate what would happen to that + //object when the group is deselected + var originalProperties = this._realizeGroupTransformOnObject(instance); + this.callSuper('_setSVGObject', markup, instance, reviver); + this._unwindGroupTransformOnObject(instance, originalProperties); + }, + + setViewportTransform: function (vpt) { + if (this.renderOnAddRemove && this._activeObject && this._activeObject.isEditing) { + this._activeObject.clearContextTop(); + } + fabric.StaticCanvas.prototype.setViewportTransform.call(this, vpt); + } + }); + + // copying static properties manually to work around Opera's bug, + // where "prototype" property is enumerable and overrides existing prototype + for (var prop in fabric.StaticCanvas) { + if (prop !== 'prototype') { + fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + } + } +})(); + + +(function() { + + var addListener = fabric.util.addListener, + removeListener = fabric.util.removeListener, + RIGHT_CLICK = 3, MIDDLE_CLICK = 2, LEFT_CLICK = 1, + addEventOptions = { passive: false }; + + function checkClick(e, value) { + return e.button && (e.button === value - 1); + } + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * Contains the id of the touch event that owns the fabric transform + * @type Number + * @private + */ + mainTouchId: null, + + /** + * Adds mouse listeners to canvas + * @private + */ + _initEventListeners: function () { + // in case we initialized the class twice. This should not happen normally + // but in some kind of applications where the canvas element may be changed + // this is a workaround to having double listeners. + this.removeListeners(); + this._bindEvents(); + this.addOrRemove(addListener, 'add'); + }, + + /** + * return an event prefix pointer or mouse. + * @private + */ + _getEventPrefix: function () { + return this.enablePointerEvents ? 'pointer' : 'mouse'; + }, + + addOrRemove: function(functor, eventjsFunctor) { + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + functor(fabric.window, 'resize', this._onResize); + functor(canvasElement, eventTypePrefix + 'down', this._onMouseDown); + functor(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + functor(canvasElement, eventTypePrefix + 'out', this._onMouseOut); + functor(canvasElement, eventTypePrefix + 'enter', this._onMouseEnter); + functor(canvasElement, 'wheel', this._onMouseWheel); + functor(canvasElement, 'contextmenu', this._onContextMenu); + functor(canvasElement, 'dblclick', this._onDoubleClick); + functor(canvasElement, 'dragover', this._onDragOver); + functor(canvasElement, 'dragenter', this._onDragEnter); + functor(canvasElement, 'dragleave', this._onDragLeave); + functor(canvasElement, 'drop', this._onDrop); + if (!this.enablePointerEvents) { + functor(canvasElement, 'touchstart', this._onTouchStart, addEventOptions); + } + if (typeof eventjs !== 'undefined' && eventjsFunctor in eventjs) { + eventjs[eventjsFunctor](canvasElement, 'gesture', this._onGesture); + eventjs[eventjsFunctor](canvasElement, 'drag', this._onDrag); + eventjs[eventjsFunctor](canvasElement, 'orientation', this._onOrientationChange); + eventjs[eventjsFunctor](canvasElement, 'shake', this._onShake); + eventjs[eventjsFunctor](canvasElement, 'longpress', this._onLongPress); + } + }, + + /** + * Removes all event listeners + */ + removeListeners: function() { + this.addOrRemove(removeListener, 'remove'); + // if you dispose on a mouseDown, before mouse up, you need to clean document to... + var eventTypePrefix = this._getEventPrefix(); + removeListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + removeListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + removeListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + }, + + /** + * @private + */ + _bindEvents: function() { + if (this.eventsBound) { + // for any reason we pass here twice we do not want to bind events twice. + return; + } + this._onMouseDown = this._onMouseDown.bind(this); + this._onTouchStart = this._onTouchStart.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._onResize = this._onResize.bind(this); + this._onGesture = this._onGesture.bind(this); + this._onDrag = this._onDrag.bind(this); + this._onShake = this._onShake.bind(this); + this._onLongPress = this._onLongPress.bind(this); + this._onOrientationChange = this._onOrientationChange.bind(this); + this._onMouseWheel = this._onMouseWheel.bind(this); + this._onMouseOut = this._onMouseOut.bind(this); + this._onMouseEnter = this._onMouseEnter.bind(this); + this._onContextMenu = this._onContextMenu.bind(this); + this._onDoubleClick = this._onDoubleClick.bind(this); + this._onDragOver = this._onDragOver.bind(this); + this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter'); + this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave'); + this._onDrop = this._simpleEventHandler.bind(this, 'drop'); + this.eventsBound = true; + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js gesture + * @param {Event} [self] Inner Event object + */ + _onGesture: function(e, self) { + this.__onTransformGesture && this.__onTransformGesture(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js drag + * @param {Event} [self] Inner Event object + */ + _onDrag: function(e, self) { + this.__onDrag && this.__onDrag(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on wheel event + */ + _onMouseWheel: function(e) { + this.__onMouseWheel(e); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseOut: function(e) { + var target = this._hoveredTarget; + this.fire('mouse:out', { target: target, e: e }); + this._hoveredTarget = null; + target && target.fire('mouseout', { e: e }); + + var _this = this; + this._hoveredTargets.forEach(function(_target){ + _this.fire('mouse:out', { target: target, e: e }); + _target && target.fire('mouseout', { e: e }); + }); + this._hoveredTargets = []; + + if (this._iTextInstances) { + this._iTextInstances.forEach(function(obj) { + if (obj.isEditing) { + obj.hiddenTextarea.focus(); + } + }); + } + }, + + /** + * @private + * @param {Event} e Event object fired on mouseenter + */ + _onMouseEnter: function(e) { + // This find target and consequent 'mouse:over' is used to + // clear old instances on hovered target. + // calling findTarget has the side effect of killing target.__corner. + // as a short term fix we are not firing this if we are currently transforming. + // as a long term fix we need to separate the action of finding a target with the + // side effects we added to it. + if (!this._currentTransform && !this.findTarget(e)) { + this.fire('mouse:over', { target: null, e: e }); + this._hoveredTarget = null; + this._hoveredTargets = []; + } + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js orientation change + * @param {Event} [self] Inner Event object + */ + _onOrientationChange: function(e, self) { + this.__onOrientationChange && this.__onOrientationChange(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onShake: function(e, self) { + this.__onShake && this.__onShake(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onLongPress: function(e, self) { + this.__onLongPress && this.__onLongPress(e, self); + }, + + /** + * prevent default to allow drop event to be fired + * @private + * @param {Event} [e] Event object fired on Event.js shake + */ + _onDragOver: function(e) { + e.preventDefault(); + var target = this._simpleEventHandler('dragover', e); + this._fireEnterLeaveEvents(target, e); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onContextMenu: function (e) { + if (this.stopContextMenu) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onDoubleClick: function (e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'dblclick'); + this._resetTransformEventData(e); + }, + + /** + * Return a the id of an event. + * returns either the pointerId or the identifier or 0 for the mouse event + * @private + * @param {Event} evt Event object + */ + getPointerId: function(evt) { + var changedTouches = evt.changedTouches; + + if (changedTouches) { + return changedTouches[0] && changedTouches[0].identifier; + } + + if (this.enablePointerEvents) { + return evt.pointerId; + } + + return -1; + }, + + /** + * Determines if an event has the id of the event that is considered main + * @private + * @param {evt} event Event object + */ + _isMainEvent: function(evt) { + if (evt.isPrimary === true) { + return true; + } + if (evt.isPrimary === false) { + return false; + } + if (evt.type === 'touchend' && evt.touches.length === 0) { + return true; + } + if (evt.changedTouches) { + return evt.changedTouches[0].identifier === this.mainTouchId; + } + return true; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onTouchStart: function(e) { + e.preventDefault(); + if (this.mainTouchId === null) { + this.mainTouchId = this.getPointerId(e); + } + this.__onMouseDown(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + addListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + addListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + // Unbind mousedown to prevent double triggers from touch devices + removeListener(canvasElement, eventTypePrefix + 'down', this._onMouseDown); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseDown: function (e) { + this.__onMouseDown(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + removeListener(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + addListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + addListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onTouchEnd: function(e) { + if (e.touches.length > 0) { + // if there are still touches stop here + return; + } + this.__onMouseUp(e); + this._resetTransformEventData(); + this.mainTouchId = null; + var eventTypePrefix = this._getEventPrefix(); + removeListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + var _this = this; + if (this._willAddMouseDown) { + clearTimeout(this._willAddMouseDown); + } + this._willAddMouseDown = setTimeout(function() { + // Wait 400ms before rebinding mousedown to prevent double triggers + // from touch devices + addListener(_this.upperCanvasEl, eventTypePrefix + 'down', _this._onMouseDown); + _this._willAddMouseDown = 0; + }, 400); + }, + + /** + * @private + * @param {Event} e Event object fired on mouseup + */ + _onMouseUp: function (e) { + this.__onMouseUp(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + if (this._isMainEvent(e)) { + removeListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + removeListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + addListener(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + } + }, + + /** + * @private + * @param {Event} e Event object fired on mousemove + */ + _onMouseMove: function (e) { + !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + + /** + * @private + */ + _onResize: function () { + this.calcOffset(); + }, + + /** + * Decides whether the canvas should be redrawn in mouseup and mousedown events. + * @private + * @param {Object} target + */ + _shouldRender: function(target) { + var activeObject = this._activeObject; + + if ( + !!activeObject !== !!target || + (activeObject && target && (activeObject !== target)) + ) { + // this covers: switch of target, from target to no target, selection of target + // multiSelection with key and mouse + return true; + } + else if (activeObject && activeObject.isEditing) { + // if we mouse up/down over a editing textbox a cursor change, + // there is no need to re render + return false; + } + return false; + }, + + /** + * Method that defines the actions when mouse is released on canvas. + * The method resets the currentTransform parameters, store the image corner + * position in the image object and render the canvas on top. + * @private + * @param {Event} e Event object fired on mouseup + */ + __onMouseUp: function (e) { + var target, transform = this._currentTransform, + groupSelector = this._groupSelector, shouldRender = false, + isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0)); + this._cacheTransformEventData(e); + target = this._target; + this._handleEvent(e, 'up:before'); + // if right/middle click just fire events and return + // target undefined will make the _handleEvent search the target + if (checkClick(e, RIGHT_CLICK)) { + if (this.fireRightClick) { + this._handleEvent(e, 'up', RIGHT_CLICK, isClick); + } + return; + } + + if (checkClick(e, MIDDLE_CLICK)) { + if (this.fireMiddleClick) { + this._handleEvent(e, 'up', MIDDLE_CLICK, isClick); + } + this._resetTransformEventData(); + return; + } + + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._onMouseUpInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + if (transform) { + this._finalizeCurrentTransform(e); + shouldRender = transform.actionPerformed; + } + if (!isClick) { + var targetWasActive = target === this._activeObject; + this._maybeGroupObjects(e); + if (!shouldRender) { + shouldRender = ( + this._shouldRender(target) || + (!targetWasActive && target === this._activeObject) + ); + } + } + if (target) { + if (target.selectable && target !== this._activeObject && target.activeOn === 'up') { + this.setActiveObject(target, e); + shouldRender = true; + } + else { + var corner = target._findTargetCorner( + this.getPointer(e, true), + fabric.util.isTouchEvent(e) + ); + var control = target.controls[corner], + mouseUpHandler = control && control.getMouseUpHandler(e, target, control); + if (mouseUpHandler) { + var pointer = this.getPointer(e); + mouseUpHandler(e, transform, pointer.x, pointer.y); + } + } + target.isMoving = false; + } + this._setCursorFromEvent(e, target); + this._handleEvent(e, 'up', LEFT_CLICK, isClick); + this._groupSelector = null; + this._currentTransform = null; + // reset the target information about which corner is selected + target && (target.__corner = 0); + if (shouldRender) { + this.requestRenderAll(); + } + else if (!isClick) { + this.renderTop(); + } + }, + + /** + * @private + * Handle event firing for target and subtargets + * @param {Event} e event from mouse + * @param {String} eventType event to fire (up, down or move) + * @return {Fabric.Object} target return the the target found, for internal reasons. + */ + _simpleEventHandler: function(eventType, e) { + var target = this.findTarget(e), + targets = this.targets, + options = { + e: e, + target: target, + subTargets: targets, + }; + this.fire(eventType, options); + target && target.fire(eventType, options); + if (!targets) { + return target; + } + for (var i = 0; i < targets.length; i++) { + targets[i].fire(eventType, options); + } + return target; + }, + + /** + * @private + * Handle event firing for target and subtargets + * @param {Event} e event from mouse + * @param {String} eventType event to fire (up, down or move) + * @param {fabric.Object} targetObj receiving event + * @param {Number} [button] button used in the event 1 = left, 2 = middle, 3 = right + * @param {Boolean} isClick for left button only, indicates that the mouse up happened without move. + */ + _handleEvent: function(e, eventType, button, isClick) { + var target = this._target, + targets = this.targets || [], + options = { + e: e, + target: target, + subTargets: targets, + button: button || LEFT_CLICK, + isClick: isClick || false, + pointer: this._pointer, + absolutePointer: this._absolutePointer, + transform: this._currentTransform + }; + if (eventType === 'up') { + options.currentTarget = this.findTarget(e); + options.currentSubTargets = this.targets; + } + this.fire('mouse:' + eventType, options); + target && target.fire('mouse' + eventType, options); + for (var i = 0; i < targets.length; i++) { + targets[i].fire('mouse' + eventType, options); + } + }, + + /** + * @private + * @param {Event} e send the mouse event that generate the finalize down, so it can be used in the event + */ + _finalizeCurrentTransform: function(e) { + + var transform = this._currentTransform, + target = transform.target, + eventName, + options = { + e: e, + target: target, + transform: transform, + action: transform.action, + }; + + if (target._scaling) { + target._scaling = false; + } + + target.setCoords(); + + if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) { + if (transform.actionPerformed) { + // this is not friendly to the new control api. + // is deprecated. + eventName = this._addEventOptions(options, transform); + this._fire(eventName, options); + } + this._fire('modified', options); + } + }, + + /** + * Mutate option object in order to add by property and give back the event name. + * @private + * @deprecated since 4.2.0 + * @param {Object} options to mutate + * @param {Object} transform to inspect action from + */ + _addEventOptions: function(options, transform) { + // we can probably add more details at low cost + // scale change, rotation changes, translation changes + var eventName, by; + switch (transform.action) { + case 'scaleX': + eventName = 'scaled'; + by = 'x'; + break; + case 'scaleY': + eventName = 'scaled'; + by = 'y'; + break; + case 'skewX': + eventName = 'skewed'; + by = 'x'; + break; + case 'skewY': + eventName = 'skewed'; + by = 'y'; + break; + case 'scale': + eventName = 'scaled'; + by = 'equally'; + break; + case 'rotate': + eventName = 'rotated'; + break; + case 'drag': + eventName = 'moved'; + break; + } + options.by = by; + return eventName; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseDownInDrawingMode: function(e) { + this._isCurrentlyDrawing = true; + if (this.getActiveObject()) { + this.discardActiveObject(e).requestRenderAll(); + } + var pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseDown(pointer, { e: e, pointer: pointer }); + this._handleEvent(e, 'down'); + }, + + /** + * @private + * @param {Event} e Event object fired on mousemove + */ + _onMouseMoveInDrawingMode: function(e) { + if (this._isCurrentlyDrawing) { + var pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseMove(pointer, { e: e, pointer: pointer }); + } + this.setCursor(this.freeDrawingCursor); + this._handleEvent(e, 'move'); + }, + + /** + * @private + * @param {Event} e Event object fired on mouseup + */ + _onMouseUpInDrawingMode: function(e) { + var pointer = this.getPointer(e); + this._isCurrentlyDrawing = this.freeDrawingBrush.onMouseUp({ e: e, pointer: pointer }); + this._handleEvent(e, 'up'); + }, + + /** + * Method that defines the actions when mouse is clicked on canvas. + * The method inits the currentTransform parameters and renders all the + * canvas so the current image can be placed on the top canvas and the rest + * in on the container one. + * @private + * @param {Event} e Event object fired on mousedown + */ + __onMouseDown: function (e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'down:before'); + var target = this._target; + // if right click just fire events + if (checkClick(e, RIGHT_CLICK)) { + if (this.fireRightClick) { + this._handleEvent(e, 'down', RIGHT_CLICK); + } + return; + } + + if (checkClick(e, MIDDLE_CLICK)) { + if (this.fireMiddleClick) { + this._handleEvent(e, 'down', MIDDLE_CLICK); + } + return; + } + + if (this.isDrawingMode) { + this._onMouseDownInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + + // ignore if some object is being transformed at this moment + if (this._currentTransform) { + return; + } + + var pointer = this._pointer; + // save pointer for check in __onMouseUp event + this._previousPointer = pointer; + var shouldRender = this._shouldRender(target), + shouldGroup = this._shouldGroup(e, target); + if (this._shouldClearSelection(e, target)) { + this.discardActiveObject(e); + } + else if (shouldGroup) { + this._handleGrouping(e, target); + target = this._activeObject; + } + + if (this.selection && (!target || + (!target.selectable && !target.isEditing && target !== this._activeObject))) { + this._groupSelector = { + ex: this._absolutePointer.x, + ey: this._absolutePointer.y, + top: 0, + left: 0 + }; + } + + if (target) { + var alreadySelected = target === this._activeObject; + if (target.selectable && target.activeOn === 'down') { + this.setActiveObject(target, e); + } + var corner = target._findTargetCorner( + this.getPointer(e, true), + fabric.util.isTouchEvent(e) + ); + target.__corner = corner; + if (target === this._activeObject && (corner || !shouldGroup)) { + this._setupCurrentTransform(e, target, alreadySelected); + var control = target.controls[corner], + pointer = this.getPointer(e), + mouseDownHandler = control && control.getMouseDownHandler(e, target, control); + if (mouseDownHandler) { + mouseDownHandler(e, this._currentTransform, pointer.x, pointer.y); + } + } + } + this._handleEvent(e, 'down'); + // we must renderAll so that we update the visuals + (shouldRender || shouldGroup) && this.requestRenderAll(); + }, + + /** + * reset cache form common information needed during event processing + * @private + */ + _resetTransformEventData: function() { + this._target = null; + this._pointer = null; + this._absolutePointer = null; + }, + + /** + * Cache common information needed during event processing + * @private + * @param {Event} e Event object fired on event + */ + _cacheTransformEventData: function(e) { + // reset in order to avoid stale caching + this._resetTransformEventData(); + this._pointer = this.getPointer(e, true); + this._absolutePointer = this.restorePointerVpt(this._pointer); + this._target = this._currentTransform ? this._currentTransform.target : this.findTarget(e) || null; + }, + + /** + * @private + */ + _beforeTransform: function(e) { + var t = this._currentTransform; + this.stateful && t.target.saveState(); + this.fire('before:transform', { + e: e, + transform: t, + }); + }, + + /** + * Method that defines the actions when mouse is hovering the canvas. + * The currentTransform parameter will define whether the user is rotating/scaling/translating + * an image or neither of them (only hovering). A group selection is also possible and would cancel + * all any other type of action. + * In case of an image transformation only the top canvas will be rendered. + * @private + * @param {Event} e Event object fired on mousemove + */ + __onMouseMove: function (e) { + this._handleEvent(e, 'move:before'); + this._cacheTransformEventData(e); + var target, pointer; + + if (this.isDrawingMode) { + this._onMouseMoveInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + + var groupSelector = this._groupSelector; + + // We initially clicked in an empty area, so we draw a box for multiple selection + if (groupSelector) { + pointer = this._absolutePointer; + + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; + + this.renderTop(); + } + else if (!this._currentTransform) { + target = this.findTarget(e) || null; + this._setCursorFromEvent(e, target); + this._fireOverOutEvents(target, e); + } + else { + this._transformObject(e); + } + this._handleEvent(e, 'move'); + this._resetTransformEventData(); + }, + + /** + * Manage the mouseout, mouseover events for the fabric object on the canvas + * @param {Fabric.Object} target the target where the target from the mousemove event + * @param {Event} e Event object fired on mousemove + * @private + */ + _fireOverOutEvents: function(target, e) { + var _hoveredTarget = this._hoveredTarget, + _hoveredTargets = this._hoveredTargets, targets = this.targets, + length = Math.max(_hoveredTargets.length, targets.length); + + this.fireSyntheticInOutEvents(target, e, { + oldTarget: _hoveredTarget, + evtOut: 'mouseout', + canvasEvtOut: 'mouse:out', + evtIn: 'mouseover', + canvasEvtIn: 'mouse:over', + }); + for (var i = 0; i < length; i++){ + this.fireSyntheticInOutEvents(targets[i], e, { + oldTarget: _hoveredTargets[i], + evtOut: 'mouseout', + evtIn: 'mouseover', + }); + } + this._hoveredTarget = target; + this._hoveredTargets = this.targets.concat(); + }, + + /** + * Manage the dragEnter, dragLeave events for the fabric objects on the canvas + * @param {Fabric.Object} target the target where the target from the onDrag event + * @param {Event} e Event object fired on ondrag + * @private + */ + _fireEnterLeaveEvents: function(target, e) { + var _draggedoverTarget = this._draggedoverTarget, + _hoveredTargets = this._hoveredTargets, targets = this.targets, + length = Math.max(_hoveredTargets.length, targets.length); + + this.fireSyntheticInOutEvents(target, e, { + oldTarget: _draggedoverTarget, + evtOut: 'dragleave', + evtIn: 'dragenter', + }); + for (var i = 0; i < length; i++) { + this.fireSyntheticInOutEvents(targets[i], e, { + oldTarget: _hoveredTargets[i], + evtOut: 'dragleave', + evtIn: 'dragenter', + }); + } + this._draggedoverTarget = target; + }, + + /** + * Manage the synthetic in/out events for the fabric objects on the canvas + * @param {Fabric.Object} target the target where the target from the supported events + * @param {Event} e Event object fired + * @param {Object} config configuration for the function to work + * @param {String} config.targetName property on the canvas where the old target is stored + * @param {String} [config.canvasEvtOut] name of the event to fire at canvas level for out + * @param {String} config.evtOut name of the event to fire for out + * @param {String} [config.canvasEvtIn] name of the event to fire at canvas level for in + * @param {String} config.evtIn name of the event to fire for in + * @private + */ + fireSyntheticInOutEvents: function(target, e, config) { + var inOpt, outOpt, oldTarget = config.oldTarget, outFires, inFires, + targetChanged = oldTarget !== target, canvasEvtIn = config.canvasEvtIn, canvasEvtOut = config.canvasEvtOut; + if (targetChanged) { + inOpt = { e: e, target: target, previousTarget: oldTarget }; + outOpt = { e: e, target: oldTarget, nextTarget: target }; + } + inFires = target && targetChanged; + outFires = oldTarget && targetChanged; + if (outFires) { + canvasEvtOut && this.fire(canvasEvtOut, outOpt); + oldTarget.fire(config.evtOut, outOpt); + } + if (inFires) { + canvasEvtIn && this.fire(canvasEvtIn, inOpt); + target.fire(config.evtIn, inOpt); + } + }, + + /** + * Method that defines actions when an Event Mouse Wheel + * @param {Event} e Event object fired on mouseup + */ + __onMouseWheel: function(e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'wheel'); + this._resetTransformEventData(); + }, + + /** + * @private + * @param {Event} e Event fired on mousemove + */ + _transformObject: function(e) { + var pointer = this.getPointer(e), + transform = this._currentTransform; + + transform.reset = false; + transform.shiftKey = e.shiftKey; + transform.altKey = e[this.centeredKey]; + + this._performTransformAction(e, transform, pointer); + transform.actionPerformed && this.requestRenderAll(); + }, + + /** + * @private + */ + _performTransformAction: function(e, transform, pointer) { + var x = pointer.x, + y = pointer.y, + action = transform.action, + actionPerformed = false, + actionHandler = transform.actionHandler; + // this object could be created from the function in the control handlers + + + if (actionHandler) { + actionPerformed = actionHandler(e, transform, x, y); + } + if (action === 'drag' && actionPerformed) { + transform.target.isMoving = true; + this.setCursor(transform.target.moveCursor || this.moveCursor); + } + transform.actionPerformed = transform.actionPerformed || actionPerformed; + }, + + /** + * @private + */ + _fire: fabric.controlsUtils.fireEvent, + + /** + * Sets the cursor depending on where the canvas is being hovered. + * Note: very buggy in Opera + * @param {Event} e Event object + * @param {Object} target Object that the mouse is hovering, if so. + */ + _setCursorFromEvent: function (e, target) { + if (!target) { + this.setCursor(this.defaultCursor); + return false; + } + var hoverCursor = target.hoverCursor || this.hoverCursor, + activeSelection = this._activeObject && this._activeObject.type === 'activeSelection' ? + this._activeObject : null, + // only show proper corner when group selection is not active + corner = (!activeSelection || !activeSelection.contains(target)) + // here we call findTargetCorner always with undefined for the touch parameter. + // we assume that if you are using a cursor you do not need to interact with + // the bigger touch area. + && target._findTargetCorner(this.getPointer(e, true)); + + if (!corner) { + if (target.subTargetCheck){ + // hoverCursor should come from top-most subTarget, + // so we walk the array backwards + this.targets.concat().reverse().map(function(_target){ + hoverCursor = _target.hoverCursor || hoverCursor; + }); + } + this.setCursor(hoverCursor); + } + else { + this.setCursor(this.getCornerCursor(corner, target, e)); + } + }, + + /** + * @private + */ + getCornerCursor: function(corner, target, e) { + var control = target.controls[corner]; + return control.cursorStyleHandler(e, control, target); + } + }); +})(); + + +(function() { + + var min = Math.min, + max = Math.max; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + * @return {Boolean} + */ + _shouldGroup: function(e, target) { + var activeObject = this._activeObject; + return activeObject && this._isSelectionKeyPressed(e) && target && target.selectable && this.selection && + (activeObject !== target || activeObject.type === 'activeSelection') && !target.onSelect({ e: e }); + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _handleGrouping: function (e, target) { + var activeObject = this._activeObject; + // avoid multi select when shift click on a corner + if (activeObject.__corner) { + return; + } + if (target === activeObject) { + // if it's a group, find target again, using activeGroup objects + target = this.findTarget(e, true); + // if even object is not found or we are on activeObjectCorner, bail out + if (!target || !target.selectable) { + return; + } + } + if (activeObject && activeObject.type === 'activeSelection') { + this._updateActiveSelection(target, e); + } + else { + this._createActiveSelection(target, e); + } + }, + + /** + * @private + */ + _updateActiveSelection: function(target, e) { + var activeSelection = this._activeObject, + currentActiveObjects = activeSelection._objects.slice(0); + if (activeSelection.contains(target)) { + activeSelection.removeWithUpdate(target); + this._hoveredTarget = target; + this._hoveredTargets = this.targets.concat(); + if (activeSelection.size() === 1) { + // activate last remaining object + this._setActiveObject(activeSelection.item(0), e); + } + } + else { + activeSelection.addWithUpdate(target); + this._hoveredTarget = activeSelection; + this._hoveredTargets = this.targets.concat(); + } + this._fireSelectionEvents(currentActiveObjects, e); + }, + + /** + * @private + */ + _createActiveSelection: function(target, e) { + var currentActives = this.getActiveObjects(), group = this._createGroup(target); + this._hoveredTarget = group; + // ISSUE 4115: should we consider subTargets here? + // this._hoveredTargets = []; + // this._hoveredTargets = this.targets.concat(); + this._setActiveObject(group, e); + this._fireSelectionEvents(currentActives, e); + }, + + /** + * @private + * @param {Object} target + */ + _createGroup: function(target) { + var objects = this._objects, + isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), + groupObjects = isActiveLower + ? [this._activeObject, target] + : [target, this._activeObject]; + this._activeObject.isEditing && this._activeObject.exitEditing(); + return new fabric.ActiveSelection(groupObjects, { + canvas: this + }); + }, + + /** + * @private + * @param {Event} e mouse event + */ + _groupSelectedObjects: function (e) { + + var group = this._collectObjects(e), + aGroup; + + // do not create group for 1 element only + if (group.length === 1) { + this.setActiveObject(group[0], e); + } + else if (group.length > 1) { + aGroup = new fabric.ActiveSelection(group.reverse(), { + canvas: this + }); + this.setActiveObject(aGroup, e); + } + }, + + /** + * @private + */ + _collectObjects: function(e) { + var group = [], + currentObject, + x1 = this._groupSelector.ex, + y1 = this._groupSelector.ey, + x2 = x1 + this._groupSelector.left, + y2 = y1 + this._groupSelector.top, + selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), + selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), + allowIntersect = !this.selectionFullyContained, + isClick = x1 === x2 && y1 === y2; + // we iterate reverse order to collect top first in case of click. + for (var i = this._objects.length; i--; ) { + currentObject = this._objects[i]; + + if (!currentObject || !currentObject.selectable || !currentObject.visible) { + continue; + } + + if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2, true)) || + currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2, true) || + (allowIntersect && currentObject.containsPoint(selectionX1Y1, null, true)) || + (allowIntersect && currentObject.containsPoint(selectionX2Y2, null, true)) + ) { + group.push(currentObject); + // only add one object if it's a click + if (isClick) { + break; + } + } + } + + if (group.length > 1) { + group = group.filter(function(object) { + return !object.onSelect({ e: e }); + }); + } + + return group; + }, + + /** + * @private + */ + _maybeGroupObjects: function(e) { + if (this.selection && this._groupSelector) { + this._groupSelectedObjects(e); + } + this.setCursor(this.defaultCursor); + // clear selection and current transformation + this._groupSelector = null; + } + }); + +})(); + + +(function () { + fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately + * @param {Object} [options] Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by, to have consistent + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 2.0.0 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} + * @example Generate jpeg dataURL with lower quality + * var dataURL = canvas.toDataURL({ + * format: 'jpeg', + * quality: 0.8 + * }); + * @example Generate cropped png dataURL (clipping of canvas) + * var dataURL = canvas.toDataURL({ + * format: 'png', + * left: 100, + * top: 100, + * width: 200, + * height: 200 + * }); + * @example Generate double scaled png dataURL + * var dataURL = canvas.toDataURL({ + * format: 'png', + * multiplier: 2 + * }); + */ + toDataURL: function (options) { + options || (options = { }); + + var format = options.format || 'png', + quality = options.quality || 1, + multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? this.getRetinaScaling() : 1), + canvasEl = this.toCanvasElement(multiplier, options); + return fabric.util.toDataURL(canvasEl, format, quality); + }, + + /** + * Create a new HTMLCanvas element painted with the current canvas content. + * No need to resize the actual one or repaint it. + * Will transfer object ownership to a new canvas, paint it, and set everything back. + * This is an intermediary step used to get to a dataUrl but also it is useful to + * create quick image copies of a canvas without passing for the dataUrl string + * @param {Number} [multiplier] a zoom factor. + * @param {Object} [cropping] Cropping informations + * @param {Number} [cropping.left] Cropping left offset. + * @param {Number} [cropping.top] Cropping top offset. + * @param {Number} [cropping.width] Cropping width. + * @param {Number} [cropping.height] Cropping height. + */ + toCanvasElement: function(multiplier, cropping) { + multiplier = multiplier || 1; + cropping = cropping || { }; + var scaledWidth = (cropping.width || this.width) * multiplier, + scaledHeight = (cropping.height || this.height) * multiplier, + zoom = this.getZoom(), + originalWidth = this.width, + originalHeight = this.height, + newZoom = zoom * multiplier, + vp = this.viewportTransform, + translateX = (vp[4] - (cropping.left || 0)) * multiplier, + translateY = (vp[5] - (cropping.top || 0)) * multiplier, + originalInteractive = this.interactive, + newVp = [newZoom, 0, 0, newZoom, translateX, translateY], + originalRetina = this.enableRetinaScaling, + canvasEl = fabric.util.createCanvasElement(), + originalContextTop = this.contextTop; + canvasEl.width = scaledWidth; + canvasEl.height = scaledHeight; + this.contextTop = null; + this.enableRetinaScaling = false; + this.interactive = false; + this.viewportTransform = newVp; + this.width = scaledWidth; + this.height = scaledHeight; + this.calcViewportBoundaries(); + this.renderCanvas(canvasEl.getContext('2d'), this._objects); + this.viewportTransform = vp; + this.width = originalWidth; + this.height = originalHeight; + this.calcViewportBoundaries(); + this.interactive = originalInteractive; + this.enableRetinaScaling = originalRetina; + this.contextTop = originalContextTop; + return canvasEl; + }, + }); + +})(); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + /** + * Populates canvas with data from the specified JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toJSON} + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} + * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} + * @example loadFromJSON + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); + * @example loadFromJSON with reviver + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { + * // `o` = json object + * // `object` = fabric.Object instance + * // ... do some stuff ... + * }); + */ + loadFromJSON: function (json, callback, reviver) { + if (!json) { + return; + } + + // serialize if it wasn't already + var serialized = (typeof json === 'string') + ? JSON.parse(json) + : fabric.util.object.clone(json); + + var _this = this, + clipPath = serialized.clipPath, + renderOnAddRemove = this.renderOnAddRemove; + + this.renderOnAddRemove = false; + + delete serialized.clipPath; + + this._enlivenObjects(serialized.objects, function (enlivenedObjects) { + _this.clear(); + _this._setBgOverlay(serialized, function () { + if (clipPath) { + _this._enlivenObjects([clipPath], function (enlivenedCanvasClip) { + _this.clipPath = enlivenedCanvasClip[0]; + _this.__setupCanvas.call(_this, serialized, enlivenedObjects, renderOnAddRemove, callback); + }); + } + else { + _this.__setupCanvas.call(_this, serialized, enlivenedObjects, renderOnAddRemove, callback); + } + }); + }, reviver); + return this; + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Array} restored canvas objects + * @param {Function} cached renderOnAddRemove callback + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + __setupCanvas: function(serialized, enlivenedObjects, renderOnAddRemove, callback) { + var _this = this; + enlivenedObjects.forEach(function(obj, index) { + // we splice the array just in case some custom classes restored from JSON + // will add more object to canvas at canvas init. + _this.insertAt(obj, index); + }); + this.renderOnAddRemove = renderOnAddRemove; + // remove parts i cannot set as options + delete serialized.objects; + delete serialized.backgroundImage; + delete serialized.overlayImage; + delete serialized.background; + delete serialized.overlay; + // this._initOptions does too many things to just + // call it. Normally loading an Object from JSON + // create the Object instance. Here the Canvas is + // already an instance and we are just loading things over it + this._setOptions(serialized); + this.renderAll(); + callback && callback(); + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + _setBgOverlay: function(serialized, callback) { + var loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; + } + + var cbIfLoaded = function () { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + callback && callback(); + } + }; + + this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); + }, + + /** + * @private + * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) + * @param {(Object|String)} value Value to set + * @param {Object} loaded Set loaded property to true if property is set + * @param {Object} callback Callback function to invoke after property is set + */ + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + + if (!value) { + loaded[property] = true; + callback && callback(); + return; + } + + if (property === 'backgroundImage' || property === 'overlayImage') { + fabric.util.enlivenObjects([value], function(enlivedObject){ + _this[property] = enlivedObject[0]; + loaded[property] = true; + callback && callback(); + }); + } + else { + this['set' + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); + } + }, + + /** + * @private + * @param {Array} objects + * @param {Function} callback + * @param {Function} [reviver] + */ + _enlivenObjects: function (objects, callback, reviver) { + if (!objects || objects.length === 0) { + callback && callback([]); + return; + } + + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, null, reviver); + }, + + /** + * @private + * @param {String} format + * @param {Function} callback + */ + _toDataURL: function (format, callback) { + this.clone(function (clone) { + callback(clone.toDataURL(format)); + }); + }, + + /** + * @private + * @param {String} format + * @param {Number} multiplier + * @param {Function} callback + */ + _toDataURLWithMultiplier: function (format, multiplier, callback) { + this.clone(function (clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + + /** + * Clones canvas instance + * @param {Object} [callback] Receives cloned instance as a first argument + * @param {Array} [properties] Array of properties to include in the cloned canvas and children + */ + clone: function (callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + + /** + * Clones canvas instance without cloning existing data. + * This essentially copies canvas dimensions, clipping properties, etc. + * but leaves data empty (so that you can populate it with your own) + * @param {Object} [callback] Receives cloned instance as a first argument + */ + cloneWithoutData: function(callback) { + var el = fabric.util.createCanvasElement(); + + el.width = this.width; + el.height = this.height; + + var clone = new fabric.Canvas(el); + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } + else { + callback && callback(clone); + } + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + capitalize = fabric.util.string.capitalize, + degreesToRadians = fabric.util.degreesToRadians, + objectCaching = !fabric.isLikelyNode, + ALIASING_LIMIT = 2; + + if (fabric.Object) { + return; + } + + /** + * Root object class from which all 2d shape classes inherit from + * @class fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects} + * @see {@link fabric.Object#initialize} for constructor definition + * + * @fires added + * @fires removed + * + * @fires selected + * @fires deselected + * @fires modified + * @fires modified + * @fires moved + * @fires scaled + * @fires rotated + * @fires skewed + * + * @fires rotating + * @fires scaling + * @fires moving + * @fires skewing + * + * @fires mousedown + * @fires mouseup + * @fires mouseover + * @fires mouseout + * @fires mousewheel + * @fires mousedblclick + * + * @fires dragover + * @fires dragenter + * @fires dragleave + * @fires drop + */ + fabric.Object = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.Object.prototype */ { + + /** + * Type of an object (rect, circle, path, etc.). + * Note that this property is meant to be read-only and not meant to be modified. + * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. + * @type String + * @default + */ + type: 'object', + + /** + * Horizontal origin of transformation of an object (one of "left", "right", "center") + * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups + * @type String + * @default + */ + originX: 'left', + + /** + * Vertical origin of transformation of an object (one of "top", "bottom", "center") + * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups + * @type String + * @default + */ + originY: 'top', + + /** + * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} + * @type Number + * @default + */ + top: 0, + + /** + * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} + * @type Number + * @default + */ + left: 0, + + /** + * Object width + * @type Number + * @default + */ + width: 0, + + /** + * Object height + * @type Number + * @default + */ + height: 0, + + /** + * Object scale factor (horizontal) + * @type Number + * @default + */ + scaleX: 1, + + /** + * Object scale factor (vertical) + * @type Number + * @default + */ + scaleY: 1, + + /** + * When true, an object is rendered as flipped horizontally + * @type Boolean + * @default + */ + flipX: false, + + /** + * When true, an object is rendered as flipped vertically + * @type Boolean + * @default + */ + flipY: false, + + /** + * Opacity of an object + * @type Number + * @default + */ + opacity: 1, + + /** + * Angle of rotation of an object (in degrees) + * @type Number + * @default + */ + angle: 0, + + /** + * Angle of skew on x axes of an object (in degrees) + * @type Number + * @default + */ + skewX: 0, + + /** + * Angle of skew on y axes of an object (in degrees) + * @type Number + * @default + */ + skewY: 0, + + /** + * Size of object's controlling corners (in pixels) + * @type Number + * @default + */ + cornerSize: 13, + + /** + * Size of object's controlling corners when touch interaction is detected + * @type Number + * @default + */ + touchCornerSize: 24, + + /** + * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) + * @type Boolean + * @default + */ + transparentCorners: true, + + /** + * Default cursor value used when hovering over this object on canvas + * @type String + * @default + */ + hoverCursor: null, + + /** + * Default cursor value used when moving this object on canvas + * @type String + * @default + */ + moveCursor: null, + + /** + * Padding between object and its controlling borders (in pixels) + * @type Number + * @default + */ + padding: 0, + + /** + * Color of controlling borders of an object (when it's active) + * @type String + * @default + */ + borderColor: 'rgb(178,204,255)', + + /** + * Array specifying dash pattern of an object's borders (hasBorder must be true) + * @since 1.6.2 + * @type Array + */ + borderDashArray: null, + + /** + * Color of controlling corners of an object (when it's active) + * @type String + * @default + */ + cornerColor: 'rgb(178,204,255)', + + /** + * Color of controlling corners of an object (when it's active and transparentCorners false) + * @since 1.6.2 + * @type String + * @default + */ + cornerStrokeColor: null, + + /** + * Specify style of control, 'rect' or 'circle' + * @since 1.6.2 + * @type String + */ + cornerStyle: 'rect', + + /** + * Array specifying dash pattern of an object's control (hasBorder must be true) + * @since 1.6.2 + * @type Array + */ + cornerDashArray: null, + + /** + * When true, this object will use center point as the origin of transformation + * when being scaled via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, this object will use center point as the origin of transformation + * when being rotated via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: true, + + /** + * Color of object's fill + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + fill: 'rgb(0,0,0)', + + /** + * Fill rule used to fill an object + * accepted values are nonzero, evenodd + * Backwards incompatibility note: This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) + * @type String + * @default + */ + fillRule: 'nonzero', + + /** + * Composite rule used for canvas globalCompositeOperation + * @type String + * @default + */ + globalCompositeOperation: 'source-over', + + /** + * Background color of an object. + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + backgroundColor: '', + + /** + * Selection Background color of an object. colored layer behind the object when it is active. + * does not mix good with globalCompositeOperation methods. + * @type String + * @default + */ + selectionBackgroundColor: '', + + /** + * When defined, an object is rendered via stroke and this property specifies its color + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + stroke: null, + + /** + * Width of a stroke used to render this object + * @type Number + * @default + */ + strokeWidth: 1, + + /** + * Array specifying dash pattern of an object's stroke (stroke must be defined) + * @type Array + */ + strokeDashArray: null, + + /** + * Line offset of an object's stroke + * @type Number + * @default + */ + strokeDashOffset: 0, + + /** + * Line endings style of an object's stroke (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'butt', + + /** + * Corner style of an object's stroke (one of "bevel", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'miter', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke + * @type Number + * @default + */ + strokeMiterLimit: 4, + + /** + * Shadow object representing shadow of this shape + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Opacity of object's controlling borders when object is active and moving + * @type Number + * @default + */ + borderOpacityWhenMoving: 0.4, + + /** + * Scale factor of object's controlling borders + * bigger number will make a thicker border + * border is 1, so this is basically a border thickness + * since there is no way to change the border itself. + * @type Number + * @default + */ + borderScaleFactor: 1, + + /** + * Minimum allowed scale value of an object + * @type Number + * @default + */ + minScaleLimit: 0, + + /** + * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). + * But events still fire on it. + * @type Boolean + * @default + */ + selectable: true, + + /** + * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 + * @type Boolean + * @default + */ + evented: true, + + /** + * When set to `false`, an object is not rendered on canvas + * @type Boolean + * @default + */ + visible: true, + + /** + * When set to `false`, object's controls are not displayed and can not be used to manipulate object + * @type Boolean + * @default + */ + hasControls: true, + + /** + * When set to `false`, object's controlling borders are not rendered + * @type Boolean + * @default + */ + hasBorders: true, + + /** + * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * When `false`, default object's values are not included in its serialization + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * When `true`, object horizontal movement is locked + * @type Boolean + * @default + */ + lockMovementX: false, + + /** + * When `true`, object vertical movement is locked + * @type Boolean + * @default + */ + lockMovementY: false, + + /** + * When `true`, object rotation is locked + * @type Boolean + * @default + */ + lockRotation: false, + + /** + * When `true`, object horizontal scaling is locked + * @type Boolean + * @default + */ + lockScalingX: false, + + /** + * When `true`, object vertical scaling is locked + * @type Boolean + * @default + */ + lockScalingY: false, + + /** + * When `true`, object horizontal skewing is locked + * @type Boolean + * @default + */ + lockSkewingX: false, + + /** + * When `true`, object vertical skewing is locked + * @type Boolean + * @default + */ + lockSkewingY: false, + + /** + * When `true`, object cannot be flipped by scaling into negative values + * @type Boolean + * @default + */ + lockScalingFlip: false, + + /** + * When `true`, object is not exported in OBJECT/JSON + * @since 1.6.3 + * @type Boolean + * @default + */ + excludeFromExport: false, + + /** + * When `true`, object is cached on an additional canvas. + * When `false`, object is not cached unless necessary ( clipPath ) + * default to true + * @since 1.7.0 + * @type Boolean + * @default true + */ + objectCaching: objectCaching, + + /** + * When `true`, object properties are checked for cache invalidation. In some particular + * situation you may want this to be disabled ( spray brush, very big, groups) + * or if your application does not allow you to modify properties for groups child you want + * to disable it for groups. + * default to false + * since 1.7.0 + * @type Boolean + * @default false + */ + statefullCache: false, + + /** + * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled + * too much and will be redrawn with correct details at the end of scaling. + * this setting is performance and application dependant. + * default to true + * since 1.7.0 + * @type Boolean + * @default true + */ + noScaleCache: true, + + /** + * When `false`, the stoke width will scale with the object. + * When `true`, the stroke will always match the exact pixel size entered for stroke width. + * default to false + * @since 2.6.0 + * @type Boolean + * @default false + * @type Boolean + * @default false + */ + strokeUniform: false, + + /** + * When set to `true`, object's cache will be rerendered next render call. + * since 1.7.0 + * @type Boolean + * @default true + */ + dirty: true, + + /** + * keeps the value of the last hovered corner during mouse move. + * 0 is no corner, or 'mt', 'ml', 'mtr' etc.. + * It should be private, but there is no harm in using it as + * a read-only property. + * @type number|string|any + * @default 0 + */ + __corner: 0, + + /** + * Determines if the fill or the stroke is drawn first (one of "fill" or "stroke") + * @type String + * @default + */ + paintFirst: 'fill', + + /** + * When 'down', object is set to active on mousedown/touchstart + * When 'up', object is set to active on mouseup/touchend + * Experimental. Let's see if this breaks anything before supporting officially + * @private + * since 4.4.0 + * @type String + * @default 'down' + */ + activeOn: 'down', + + /** + * List of properties to consider when checking if state + * of an object is changed (fabric.Object#hasStateChanged) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: ( + 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + + 'stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit ' + + 'angle opacity fill globalCompositeOperation shadow visible backgroundColor ' + + 'skewX skewY fillRule paintFirst clipPath strokeUniform' + ).split(' '), + + /** + * List of properties to consider when checking if cache needs refresh + * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single + * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty + * and refreshed at the next render + * @type Array + */ + cacheProperties: ( + 'fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform' + + ' strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath' + ).split(' '), + + /** + * List of properties to consider for animating colors. + * @type Array + */ + colorProperties: ( + 'fill stroke backgroundColor' + ).split(' '), + + /** + * a fabricObject that, without stroke define a clipping area with their shape. filled in black + * the clipPath object gets used when the object has rendered, and the context is placed in the center + * of the object cacheCanvas. + * If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center' + * @type fabric.Object + */ + clipPath: undefined, + + /** + * Meaningful ONLY when the object is used as clipPath. + * if true, the clipPath will make the object clip to the outside of the clipPath + * since 2.4.0 + * @type boolean + * @default false + */ + inverted: false, + + /** + * Meaningful ONLY when the object is used as clipPath. + * if true, the clipPath will have its top and left relative to canvas, and will + * not be influenced by the object transform. This will make the clipPath relative + * to the canvas, but clipping just a particular object. + * WARNING this is beta, this feature may change or be renamed. + * since 2.4.0 + * @type boolean + * @default false + */ + absolutePositioned: false, + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * Create a the canvas used to keep the cached copy of the object + * @private + */ + _createCacheCanvas: function() { + this._cacheProperties = {}; + this._cacheCanvas = fabric.util.createCanvasElement(); + this._cacheContext = this._cacheCanvas.getContext('2d'); + this._updateCacheCanvas(); + // if canvas gets created, is empty, so dirty. + this.dirty = true; + }, + + /** + * Limit the cache dimensions so that X * Y do not cross fabric.perfLimitSizeTotal + * and each side do not cross fabric.cacheSideLimit + * those numbers are configurable so that you can get as much detail as you want + * making bargain with performances. + * @param {Object} dims + * @param {Object} dims.width width of canvas + * @param {Object} dims.height height of canvas + * @param {Object} dims.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @param {Object} dims.zoomY zoomY zoom value to unscale the canvas before drawing cache + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _limitCacheSize: function(dims) { + var perfLimitSizeTotal = fabric.perfLimitSizeTotal, + width = dims.width, height = dims.height, + max = fabric.maxCacheSideLimit, min = fabric.minCacheSideLimit; + if (width <= max && height <= max && width * height <= perfLimitSizeTotal) { + if (width < min) { + dims.width = min; + } + if (height < min) { + dims.height = min; + } + return dims; + } + var ar = width / height, limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal), + capValue = fabric.util.capValue, + x = capValue(min, limitedDims.x, max), + y = capValue(min, limitedDims.y, max); + if (width > x) { + dims.zoomX /= width / x; + dims.width = x; + dims.capped = true; + } + if (height > y) { + dims.zoomY /= height / y; + dims.height = y; + dims.capped = true; + } + return dims; + }, + + /** + * Return the dimension and the zoom level needed to create a cache canvas + * big enough to host the object to be cached. + * @private + * @return {Object}.x width of object to be cached + * @return {Object}.y height of object to be cached + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _getCacheCanvasDimensions: function() { + var objectScale = this.getTotalObjectScaling(), + // caculate dimensions without skewing + dim = this._getTransformedDimensions(0, 0), + neededX = dim.x * objectScale.scaleX / this.scaleX, + neededY = dim.y * objectScale.scaleY / this.scaleY; + return { + // for sure this ALIASING_LIMIT is slightly creating problem + // in situation in which the cache canvas gets an upper limit + // also objectScale contains already scaleX and scaleY + width: neededX + ALIASING_LIMIT, + height: neededY + ALIASING_LIMIT, + zoomX: objectScale.scaleX, + zoomY: objectScale.scaleY, + x: neededX, + y: neededY + }; + }, + + /** + * Update width and height of the canvas for cache + * returns true or false if canvas needed resize. + * @private + * @return {Boolean} true if the canvas has been resized + */ + _updateCacheCanvas: function() { + var targetCanvas = this.canvas; + if (this.noScaleCache && targetCanvas && targetCanvas._currentTransform) { + var target = targetCanvas._currentTransform.target, + action = targetCanvas._currentTransform.action; + if (this === target && action.slice && action.slice(0, 5) === 'scale') { + return false; + } + } + var canvas = this._cacheCanvas, + dims = this._limitCacheSize(this._getCacheCanvasDimensions()), + minCacheSize = fabric.minCacheSideLimit, + width = dims.width, height = dims.height, drawingWidth, drawingHeight, + zoomX = dims.zoomX, zoomY = dims.zoomY, + dimensionsChanged = width !== this.cacheWidth || height !== this.cacheHeight, + zoomChanged = this.zoomX !== zoomX || this.zoomY !== zoomY, + shouldRedraw = dimensionsChanged || zoomChanged, + additionalWidth = 0, additionalHeight = 0, shouldResizeCanvas = false; + if (dimensionsChanged) { + var canvasWidth = this._cacheCanvas.width, + canvasHeight = this._cacheCanvas.height, + sizeGrowing = width > canvasWidth || height > canvasHeight, + sizeShrinking = (width < canvasWidth * 0.9 || height < canvasHeight * 0.9) && + canvasWidth > minCacheSize && canvasHeight > minCacheSize; + shouldResizeCanvas = sizeGrowing || sizeShrinking; + if (sizeGrowing && !dims.capped && (width > minCacheSize || height > minCacheSize)) { + additionalWidth = width * 0.1; + additionalHeight = height * 0.1; + } + } + if (this instanceof fabric.Text && this.path) { + shouldRedraw = true; + shouldResizeCanvas = true; + additionalWidth += this.getHeightOfLine(0) * this.zoomX; + additionalHeight += this.getHeightOfLine(0) * this.zoomY; + } + if (shouldRedraw) { + if (shouldResizeCanvas) { + canvas.width = Math.ceil(width + additionalWidth); + canvas.height = Math.ceil(height + additionalHeight); + } + else { + this._cacheContext.setTransform(1, 0, 0, 1, 0, 0); + this._cacheContext.clearRect(0, 0, canvas.width, canvas.height); + } + drawingWidth = dims.x / 2; + drawingHeight = dims.y / 2; + this.cacheTranslationX = Math.round(canvas.width / 2 - drawingWidth) + drawingWidth; + this.cacheTranslationY = Math.round(canvas.height / 2 - drawingHeight) + drawingHeight; + this.cacheWidth = width; + this.cacheHeight = height; + this._cacheContext.translate(this.cacheTranslationX, this.cacheTranslationY); + this._cacheContext.scale(zoomX, zoomY); + this.zoomX = zoomX; + this.zoomY = zoomY; + return true; + } + return false; + }, + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + this._setOptions(options); + this._initGradient(options.fill, 'fill'); + this._initGradient(options.stroke, 'stroke'); + this._initPattern(options.fill, 'fill'); + this._initPattern(options.stroke, 'stroke'); + }, + + /** + * Transforms context when rendering an object + * @param {CanvasRenderingContext2D} ctx Context + */ + transform: function(ctx) { + var needFullTransform = (this.group && !this.group._transformDone) || + (this.group && this.canvas && ctx === this.canvas.contextTop); + var m = this.calcTransformMatrix(!needFullTransform); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + }, + + /** + * Returns an object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + object = { + type: this.type, + version: fabric.version, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeDashOffset: this.strokeDashOffset, + strokeLineJoin: this.strokeLineJoin, + strokeUniform: this.strokeUniform, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.angle, NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + backgroundColor: this.backgroundColor, + fillRule: this.fillRule, + paintFirst: this.paintFirst, + globalCompositeOperation: this.globalCompositeOperation, + skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), + skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), + }; + + if (this.clipPath && !this.clipPath.excludeFromExport) { + object.clipPath = this.clipPath.toObject(propertiesToInclude); + object.clipPath.inverted = this.clipPath.inverted; + object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; + } + + fabric.util.populateWithProperties(this, object, propertiesToInclude); + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + + return object; + }, + + /** + * Returns (dataless) object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + // will be overwritten by subclasses + return this.toObject(propertiesToInclude); + }, + + /** + * @private + * @param {Object} object + */ + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; + stateProperties.forEach(function(prop) { + if (prop === 'left' || prop === 'top') { + return; + } + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' && + Object.prototype.toString.call(prototype[prop]) === '[object Array]'; + + // basically a check for [] === [] + if (isArray && object[prop].length === 0 && prototype[prop].length === 0) { + delete object[prop]; + } + }); + + return object; + }, + + /** + * Returns a string representation of an instance + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Return the object scale factor counting also the group scaling + * @return {Object} object with scaleX and scaleY properties + */ + getObjectScaling: function() { + // if the object is a top level one, on the canvas, we go for simple aritmetic + // otherwise the complex method with angles will return approximations and decimals + // and will likely kill the cache when not needed + // https://github.com/fabricjs/fabric.js/issues/7157 + if (!this.group) { + return { + scaleX: this.scaleX, + scaleY: this.scaleY, + }; + } + // if we are inside a group total zoom calculation is complex, we defer to generic matrices + var options = fabric.util.qrDecompose(this.calcTransformMatrix()); + return { scaleX: Math.abs(options.scaleX), scaleY: Math.abs(options.scaleY) }; + }, + + /** + * Return the object scale factor counting also the group scaling, zoom and retina + * @return {Object} object with scaleX and scaleY properties + */ + getTotalObjectScaling: function() { + var scale = this.getObjectScaling(), scaleX = scale.scaleX, scaleY = scale.scaleY; + if (this.canvas) { + var zoom = this.canvas.getZoom(); + var retina = this.canvas.getRetinaScaling(); + scaleX *= zoom * retina; + scaleY *= zoom * retina; + } + return { scaleX: scaleX, scaleY: scaleY }; + }, + + /** + * Return the object opacity counting also the group property + * @return {Number} + */ + getObjectOpacity: function() { + var opacity = this.opacity; + if (this.group) { + opacity *= this.group.getObjectOpacity(); + } + return opacity; + }, + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Object} thisArg + */ + _set: function(key, value) { + var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'), + isChanged = this[key] !== value, groupNeedsUpdate = false; + + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === 'scaleX' && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } + else if (key === 'scaleY' && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } + else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + else if (key === 'dirty' && this.group) { + this.group.set('dirty', value); + } + + this[key] = value; + + if (isChanged) { + groupNeedsUpdate = this.group && this.group.isOnACache(); + if (this.cacheProperties.indexOf(key) > -1) { + this.dirty = true; + groupNeedsUpdate && this.group.set('dirty', true); + } + else if (groupNeedsUpdate && this.stateProperties.indexOf(key) > -1) { + this.group.set('dirty', true); + } + } + return this; + }, + + /** + * This callback function is called by the parent group of an object every + * time a non-delegated property changes on the group. It is passed the key + * and value as parameters. Not adding in this function's signature to avoid + * Travis build error about unused variables. + */ + setOnGroup: function() { + // implemented by sub-classes, as needed. + }, + + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Array} + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) { + return this.canvas.viewportTransform; + } + return fabric.iMatrix.concat(); + }, + + /* + * @private + * return if the object would be visible in rendering + * @memberOf fabric.Object.prototype + * @return {Boolean} + */ + isNotVisible: function() { + return this.opacity === 0 || + (!this.width && !this.height && this.strokeWidth === 0) || + !this.visible; + }, + + /** + * Renders an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + // do not render if width/height are zeros or object is not visible + if (this.isNotVisible()) { + return; + } + if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { + return; + } + ctx.save(); + this._setupCompositeOperation(ctx); + this.drawSelectionBackground(ctx); + this.transform(ctx); + this._setOpacity(ctx); + this._setShadow(ctx, this); + if (this.shouldCache()) { + this.renderCache(); + this.drawCacheOnCanvas(ctx); + } + else { + this._removeCacheCanvas(); + this.dirty = false; + this.drawObject(ctx); + if (this.objectCaching && this.statefullCache) { + this.saveState({ propertySet: 'cacheProperties' }); + } + } + ctx.restore(); + }, + + renderCache: function(options) { + options = options || {}; + if (!this._cacheCanvas) { + this._createCacheCanvas(); + } + if (this.isCacheDirty()) { + this.statefullCache && this.saveState({ propertySet: 'cacheProperties' }); + this.drawObject(this._cacheContext, options.forClipping); + this.dirty = false; + } + }, + + /** + * Remove cacheCanvas and its dimensions from the objects + */ + _removeCacheCanvas: function() { + this._cacheCanvas = null; + this.cacheWidth = 0; + this.cacheHeight = 0; + }, + + /** + * return true if the object will draw a stroke + * Does not consider text styles. This is just a shortcut used at rendering time + * We want it to be an approximation and be fast. + * wrote to avoid extra caching, it has to return true when stroke happens, + * can guess when it will not happen at 100% chance, does not matter if it misses + * some use case where the stroke is invisible. + * @since 3.0.0 + * @returns Boolean + */ + hasStroke: function() { + return this.stroke && this.stroke !== 'transparent' && this.strokeWidth !== 0; + }, + + /** + * return true if the object will draw a fill + * Does not consider text styles. This is just a shortcut used at rendering time + * We want it to be an approximation and be fast. + * wrote to avoid extra caching, it has to return true when fill happens, + * can guess when it will not happen at 100% chance, does not matter if it misses + * some use case where the fill is invisible. + * @since 3.0.0 + * @returns Boolean + */ + hasFill: function() { + return this.fill && this.fill !== 'transparent'; + }, + + /** + * When set to `true`, force the object to have its own cache, even if it is inside a group + * it may be needed when your object behave in a particular way on the cache and always needs + * its own isolated canvas to render correctly. + * Created to be overridden + * since 1.7.12 + * @returns Boolean + */ + needsItsOwnCache: function() { + if (this.paintFirst === 'stroke' && + this.hasFill() && this.hasStroke() && typeof this.shadow === 'object') { + return true; + } + if (this.clipPath) { + return true; + } + return false; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * objectCaching is a global flag, wins over everything + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * Read as: cache if is needed, or if the feature is enabled but we are not already caching. + * @return {Boolean} + */ + shouldCache: function() { + this.ownCaching = this.needsItsOwnCache() || ( + this.objectCaching && + (!this.group || !this.group.isOnACache()) + ); + return this.ownCaching; + }, + + /** + * Check if this object or a child object will cast a shadow + * used by Group.shouldCache to know if child has a shadow recursively + * @return {Boolean} + */ + willDrawShadow: function() { + return !!this.shadow && (this.shadow.offsetX !== 0 || this.shadow.offsetY !== 0); + }, + + /** + * Execute the drawing operation for an object clipPath + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawClipPathOnCache: function(ctx) { + var path = this.clipPath; + ctx.save(); + // DEBUG: uncomment this line, comment the following + // ctx.globalAlpha = 0.4 + if (path.inverted) { + ctx.globalCompositeOperation = 'destination-out'; + } + else { + ctx.globalCompositeOperation = 'destination-in'; + } + //ctx.scale(1 / 2, 1 / 2); + if (path.absolutePositioned) { + var m = fabric.util.invertTransform(this.calcTransformMatrix()); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + path.transform(ctx); + ctx.scale(1 / path.zoomX, 1 / path.zoomY); + ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); + ctx.restore(); + }, + + /** + * Execute the drawing operation for an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawObject: function(ctx, forClipping) { + var originalFill = this.fill, originalStroke = this.stroke; + if (forClipping) { + this.fill = 'black'; + this.stroke = ''; + this._setClippingProperties(ctx); + } + else { + this._renderBackground(ctx); + } + this._render(ctx); + this._drawClipPath(ctx); + this.fill = originalFill; + this.stroke = originalStroke; + }, + + _drawClipPath: function(ctx) { + var path = this.clipPath; + if (!path) { return; } + // needed to setup a couple of variables + // path canvas gets overridden with this one. + // TODO find a better solution? + path.canvas = this.canvas; + path.shouldCache(); + path._transformDone = true; + path.renderCache({ forClipping: true }); + this.drawClipPathOnCache(ctx); + }, + + /** + * Paint the cached copy of the object on the target context. + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawCacheOnCanvas: function(ctx) { + ctx.scale(1 / this.zoomX, 1 / this.zoomY); + ctx.drawImage(this._cacheCanvas, -this.cacheTranslationX, -this.cacheTranslationY); + }, + + /** + * Check if cache is dirty + * @param {Boolean} skipCanvas skip canvas checks because this object is painted + * on parent canvas. + */ + isCacheDirty: function(skipCanvas) { + if (this.isNotVisible()) { + return false; + } + if (this._cacheCanvas && !skipCanvas && this._updateCacheCanvas()) { + // in this case the context is already cleared. + return true; + } + else { + if (this.dirty || + (this.clipPath && this.clipPath.absolutePositioned) || + (this.statefullCache && this.hasStateChanged('cacheProperties')) + ) { + if (this._cacheCanvas && !skipCanvas) { + var width = this.cacheWidth / this.zoomX; + var height = this.cacheHeight / this.zoomY; + this._cacheContext.clearRect(-width / 2, -height / 2, width, height); + } + return true; + } + } + return false; + }, + + /** + * Draws a background for the object big as its untransformed dimensions + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + var dim = this._getNonTransformedDimensions(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + -dim.x / 2, + -dim.y / 2, + dim.x, + dim.y + ); + // if there is background color no other shadows + // should be casted + this._removeShadow(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setOpacity: function(ctx) { + if (this.group && !this.group._transformDone) { + ctx.globalAlpha = this.getObjectOpacity(); + } + else { + ctx.globalAlpha *= this.opacity; + } + }, + + _setStrokeStyles: function(ctx, decl) { + var stroke = decl.stroke; + if (stroke) { + ctx.lineWidth = decl.strokeWidth; + ctx.lineCap = decl.strokeLineCap; + ctx.lineDashOffset = decl.strokeDashOffset; + ctx.lineJoin = decl.strokeLineJoin; + ctx.miterLimit = decl.strokeMiterLimit; + if (stroke.toLive) { + if (stroke.gradientUnits === 'percentage' || stroke.gradientTransform || stroke.patternTransform) { + // need to transform gradient in a pattern. + // this is a slow process. If you are hitting this codepath, and the object + // is not using caching, you should consider switching it on. + // we need a canvas as big as the current object caching canvas. + this._applyPatternForTransformedGradient(ctx, stroke); + } + else { + // is a simple gradient or pattern + ctx.strokeStyle = stroke.toLive(ctx, this); + this._applyPatternGradientTransform(ctx, stroke); + } + } + else { + // is a color + ctx.strokeStyle = decl.stroke; + } + } + }, + + _setFillStyles: function(ctx, decl) { + var fill = decl.fill; + if (fill) { + if (fill.toLive) { + ctx.fillStyle = fill.toLive(ctx, this); + this._applyPatternGradientTransform(ctx, decl.fill); + } + else { + ctx.fillStyle = fill; + } + } + }, + + _setClippingProperties: function(ctx) { + ctx.globalAlpha = 1; + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = '#000000'; + }, + + /** + * @private + * Sets line dash + * @param {CanvasRenderingContext2D} ctx Context to set the dash line on + * @param {Array} dashArray array representing dashes + */ + _setLineDash: function(ctx, dashArray) { + if (!dashArray || dashArray.length === 0) { + return; + } + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & dashArray.length) { + dashArray.push.apply(dashArray, dashArray); + } + ctx.setLineDash(dashArray); + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [styleOverride] properties to override the object style + */ + _renderControls: function(ctx, styleOverride) { + var vpt = this.getViewportTransform(), + matrix = this.calcTransformMatrix(), + options, drawBorders, drawControls; + styleOverride = styleOverride || { }; + drawBorders = typeof styleOverride.hasBorders !== 'undefined' ? styleOverride.hasBorders : this.hasBorders; + drawControls = typeof styleOverride.hasControls !== 'undefined' ? styleOverride.hasControls : this.hasControls; + matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); + options = fabric.util.qrDecompose(matrix); + ctx.save(); + ctx.translate(options.translateX, options.translateY); + ctx.lineWidth = 1 * this.borderScaleFactor; + if (!this.group) { + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + } + ctx.rotate(degreesToRadians(options.angle)); + if (styleOverride.forActiveSelection || this.group) { + drawBorders && this.drawBordersInGroup(ctx, options, styleOverride); + } + else { + drawBorders && this.drawBorders(ctx, styleOverride); + } + drawControls && this.drawControls(ctx, styleOverride); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setShadow: function(ctx) { + if (!this.shadow) { + return; + } + + var shadow = this.shadow, canvas = this.canvas, scaling, + multX = (canvas && canvas.viewportTransform[0]) || 1, + multY = (canvas && canvas.viewportTransform[3]) || 1; + if (shadow.nonScaling) { + scaling = { scaleX: 1, scaleY: 1 }; + } + else { + scaling = this.getObjectScaling(); + } + if (canvas && canvas._isRetinaScaling()) { + multX *= fabric.devicePixelRatio; + multY *= fabric.devicePixelRatio; + } + ctx.shadowColor = shadow.color; + ctx.shadowBlur = shadow.blur * fabric.browserShadowBlurConstant * + (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4; + ctx.shadowOffsetX = shadow.offsetX * multX * scaling.scaleX; + ctx.shadowOffsetY = shadow.offsetY * multY * scaling.scaleY; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _removeShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} filler fabric.Pattern or fabric.Gradient + * @return {Object} offset.offsetX offset for text rendering + * @return {Object} offset.offsetY offset for text rendering + */ + _applyPatternGradientTransform: function(ctx, filler) { + if (!filler || !filler.toLive) { + return { offsetX: 0, offsetY: 0 }; + } + var t = filler.gradientTransform || filler.patternTransform; + var offsetX = -this.width / 2 + filler.offsetX || 0, + offsetY = -this.height / 2 + filler.offsetY || 0; + + if (filler.gradientUnits === 'percentage') { + ctx.transform(this.width, 0, 0, this.height, offsetX, offsetY); + } + else { + ctx.transform(1, 0, 0, 1, offsetX, offsetY); + } + if (t) { + ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); + } + return { offsetX: offsetX, offsetY: offsetY }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderPaintInOrder: function(ctx) { + if (this.paintFirst === 'stroke') { + this._renderStroke(ctx); + this._renderFill(ctx); + } + else { + this._renderFill(ctx); + this._renderStroke(ctx); + } + }, + + /** + * @private + * function that actually render something on the context. + * empty here to allow Obects to work on tests to benchmark fabric functionalites + * not related to rendering + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(/* ctx */) { + + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderFill: function(ctx) { + if (!this.fill) { + return; + } + + ctx.save(); + this._setFillStyles(ctx, this); + if (this.fillRule === 'evenodd') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderStroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + + ctx.save(); + if (this.strokeUniform && this.group) { + var scaling = this.getObjectScaling(); + ctx.scale(1 / scaling.scaleX, 1 / scaling.scaleY); + } + else if (this.strokeUniform) { + ctx.scale(1 / this.scaleX, 1 / this.scaleY); + } + this._setLineDash(ctx, this.strokeDashArray); + this._setStrokeStyles(ctx, this); + ctx.stroke(); + ctx.restore(); + }, + + /** + * This function try to patch the missing gradientTransform on canvas gradients. + * transforming a context to transform the gradient, is going to transform the stroke too. + * we want to transform the gradient but not the stroke operation, so we create + * a transformed gradient on a pattern and then we use the pattern instead of the gradient. + * this method has drwabacks: is slow, is in low resolution, needs a patch for when the size + * is limited. + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {fabric.Gradient} filler a fabric gradient instance + */ + _applyPatternForTransformedGradient: function(ctx, filler) { + var dims = this._limitCacheSize(this._getCacheCanvasDimensions()), + pCanvas = fabric.util.createCanvasElement(), pCtx, retinaScaling = this.canvas.getRetinaScaling(), + width = dims.x / this.scaleX / retinaScaling, height = dims.y / this.scaleY / retinaScaling; + pCanvas.width = width; + pCanvas.height = height; + pCtx = pCanvas.getContext('2d'); + pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); + pCtx.lineTo(0, height); pCtx.closePath(); + pCtx.translate(width / 2, height / 2); + pCtx.scale( + dims.zoomX / this.scaleX / retinaScaling, + dims.zoomY / this.scaleY / retinaScaling + ); + this._applyPatternGradientTransform(pCtx, filler); + pCtx.fillStyle = filler.toLive(ctx); + pCtx.fill(); + ctx.translate(-this.width / 2 - this.strokeWidth / 2, -this.height / 2 - this.strokeWidth / 2); + ctx.scale( + retinaScaling * this.scaleX / dims.zoomX, + retinaScaling * this.scaleY / dims.zoomY + ); + ctx.strokeStyle = pCtx.createPattern(pCanvas, 'no-repeat'); + }, + + /** + * This function is an helper for svg import. it returns the center of the object in the svg + * untransformed coordinates + * @private + * @return {Object} center point from element coordinates + */ + _findCenterFromElement: function() { + return { x: this.left + this.width / 2, y: this.top + this.height / 2 }; + }, + + /** + * This function is an helper for svg import. it decompose the transformMatrix + * and assign properties to object. + * untransformed coordinates + * @private + * @chainable + */ + _assignTransformMatrixProps: function() { + if (this.transformMatrix) { + var options = fabric.util.qrDecompose(this.transformMatrix); + this.flipX = false; + this.flipY = false; + this.set('scaleX', options.scaleX); + this.set('scaleY', options.scaleY); + this.angle = options.angle; + this.skewX = options.skewX; + this.skewY = 0; + } + }, + + /** + * This function is an helper for svg import. it removes the transform matrix + * and set to object properties that fabricjs can handle + * @private + * @param {Object} preserveAspectRatioOptions + * @return {thisArg} + */ + _removeTransformMatrix: function(preserveAspectRatioOptions) { + var center = this._findCenterFromElement(); + if (this.transformMatrix) { + this._assignTransformMatrixProps(); + center = fabric.util.transformPoint(center, this.transformMatrix); + } + this.transformMatrix = null; + if (preserveAspectRatioOptions) { + this.scaleX *= preserveAspectRatioOptions.scaleX; + this.scaleY *= preserveAspectRatioOptions.scaleY; + this.cropX = preserveAspectRatioOptions.cropX; + this.cropY = preserveAspectRatioOptions.cropY; + center.x += preserveAspectRatioOptions.offsetLeft; + center.y += preserveAspectRatioOptions.offsetTop; + this.width = preserveAspectRatioOptions.width; + this.height = preserveAspectRatioOptions.height; + } + this.setPositionByOrigin(center, 'center', 'center'); + }, + + /** + * Clones an instance, using a callback method will work for every object. + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + */ + clone: function(callback, propertiesToInclude) { + var objectForm = this.toObject(propertiesToInclude); + if (this.constructor.fromObject) { + this.constructor.fromObject(objectForm, callback); + } + else { + fabric.Object._fromObject('Object', objectForm, callback); + } + }, + + /** + * Creates an instance of fabric.Image out of an object + * makes use of toCanvasElement. + * Once this method was based on toDataUrl and loadImage, so it also had a quality + * and format option. toCanvasElement is faster and produce no loss of quality. + * If you need to get a real Jpeg or Png from an object, using toDataURL is the right way to do it. + * toCanvasElement and then toBlob from the obtained canvas is also a good option. + * This method is sync now, but still support the callback because we did not want to break. + * When fabricJS 5.0 will be planned, this will probably be changed to not have a callback. + * @param {Function} callback callback, invoked with an instance as a first argument + * @param {Object} [options] for clone as image, passed to toDataURL + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {fabric.Object} thisArg + */ + cloneAsImage: function(callback, options) { + var canvasEl = this.toCanvasElement(options); + if (callback) { + callback(new fabric.Image(canvasEl)); + } + return this; + }, + + /** + * Converts an object into a HTMLCanvas element + * @param {Object} options Options object + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {HTMLCanvasElement} Returns DOM element with the fabric.Object + */ + toCanvasElement: function(options) { + options || (options = { }); + + var utils = fabric.util, origParams = utils.saveObjectTransform(this), + originalGroup = this.group, + originalShadow = this.shadow, abs = Math.abs, + multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? fabric.devicePixelRatio : 1); + delete this.group; + if (options.withoutTransform) { + utils.resetObjectTransform(this); + } + if (options.withoutShadow) { + this.shadow = null; + } + + var el = fabric.util.createCanvasElement(), + // skip canvas zoom and calculate with setCoords now. + boundingRect = this.getBoundingRect(true, true), + shadow = this.shadow, scaling, + shadowOffset = { x: 0, y: 0 }, shadowBlur, + width, height; + + if (shadow) { + shadowBlur = shadow.blur; + if (shadow.nonScaling) { + scaling = { scaleX: 1, scaleY: 1 }; + } + else { + scaling = this.getObjectScaling(); + } + // consider non scaling shadow. + shadowOffset.x = 2 * Math.round(abs(shadow.offsetX) + shadowBlur) * (abs(scaling.scaleX)); + shadowOffset.y = 2 * Math.round(abs(shadow.offsetY) + shadowBlur) * (abs(scaling.scaleY)); + } + width = boundingRect.width + shadowOffset.x; + height = boundingRect.height + shadowOffset.y; + // if the current width/height is not an integer + // we need to make it so. + el.width = Math.ceil(width); + el.height = Math.ceil(height); + var canvas = new fabric.StaticCanvas(el, { + enableRetinaScaling: false, + renderOnAddRemove: false, + skipOffscreen: false, + }); + if (options.format === 'jpeg') { + canvas.backgroundColor = '#fff'; + } + this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center'); + + var originalCanvas = this.canvas; + canvas.add(this); + var canvasEl = canvas.toCanvasElement(multiplier || 1, options); + this.shadow = originalShadow; + this.set('canvas', originalCanvas); + if (originalGroup) { + this.group = originalGroup; + } + this.set(origParams).setCoords(); + // canvas.dispose will call image.dispose that will nullify the elements + // since this canvas is a simple element for the process, we remove references + // to objects in this way in order to avoid object trashing. + canvas._objects = []; + canvas.dispose(); + canvas = null; + + return canvasEl; + }, + + /** + * Converts an object into a data-url-like string + * @param {Object} options Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + */ + toDataURL: function(options) { + options || (options = { }); + return fabric.util.toDataURL(this.toCanvasElement(options), options.format || 'png', options.quality || 1); + }, + + /** + * Returns true if specified type is identical to the type of an instance + * @param {String} type Type to check against + * @return {Boolean} + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance (is 1 unless subclassed) + */ + complexity: function() { + return 1; + }, + + /** + * Returns a JSON representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON + */ + toJSON: function(propertiesToInclude) { + // delegate, not alias + return this.toObject(propertiesToInclude); + }, + + /** + * Sets "angle" of an instance with centered rotation + * @param {Number} angle Angle value (in degrees) + * @return {fabric.Object} thisArg + * @chainable + */ + rotate: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + + /** + * Centers object horizontally on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerH: function () { + this.canvas && this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object horizontally on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenterH: function () { + this.canvas && this.canvas.viewportCenterObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerV: function () { + this.canvas && this.canvas.centerObjectV(this); + return this; + }, + + /** + * Centers object vertically on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenterV: function () { + this.canvas && this.canvas.viewportCenterObjectV(this); + return this; + }, + + /** + * Centers object vertically and horizontally on canvas to which is was added last + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + this.canvas && this.canvas.centerObject(this); + return this; + }, + + /** + * Centers object on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenter: function () { + this.canvas && this.canvas.viewportCenterObject(this); + return this; + }, + + /** + * Returns coordinates of a pointer relative to an object + * @param {Event} e Event to operate upon + * @param {Object} [pointer] Pointer to operate upon (instead of event) + * @return {Object} Coordinates of a pointer (x, y) + */ + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var pClicked = new fabric.Point(pointer.x, pointer.y), + objectLeftTop = this._getLeftTopCoords(); + if (this.angle) { + pClicked = fabric.util.rotatePoint( + pClicked, objectLeftTop, degreesToRadians(-this.angle)); + } + return { + x: pClicked.x - objectLeftTop.x, + y: pClicked.y - objectLeftTop.y + }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specified using globalCompositeOperation property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupCompositeOperation: function (ctx) { + if (this.globalCompositeOperation) { + ctx.globalCompositeOperation = this.globalCompositeOperation; + } + } + }); + + fabric.util.createAccessors && fabric.util.createAccessors(fabric.Object); + + extend(fabric.Object.prototype, fabric.Observable); + + /** + * Defines the number of fraction digits to use when serializing object values. + * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. + * @static + * @memberOf fabric.Object + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; + + fabric.Object._fromObject = function(className, object, callback, extraParam) { + var klass = fabric[className]; + object = clone(object, true); + fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) { + if (typeof patterns[0] !== 'undefined') { + object.fill = patterns[0]; + } + if (typeof patterns[1] !== 'undefined') { + object.stroke = patterns[1]; + } + fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { + object.clipPath = enlivedProps[0]; + var instance = extraParam ? new klass(object[extraParam], object) : new klass(object); + callback && callback(instance); + }); + }); + }; + + /** + * Unique id used internally when creating SVG elements + * @static + * @memberOf fabric.Object + * @type Number + */ + fabric.Object.__uid = 0; +})( true ? exports : 0); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians, + originXOffset = { + left: -0.5, + center: 0, + right: 0.5 + }, + originYOffset = { + top: -0.5, + center: 0, + bottom: 0.5 + }; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Translates the coordinates from a set of origin to another (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' + * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' + * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' + * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) { + var x = point.x, + y = point.y, + offsetX, offsetY, dim; + + if (typeof fromOriginX === 'string') { + fromOriginX = originXOffset[fromOriginX]; + } + else { + fromOriginX -= 0.5; + } + + if (typeof toOriginX === 'string') { + toOriginX = originXOffset[toOriginX]; + } + else { + toOriginX -= 0.5; + } + + offsetX = toOriginX - fromOriginX; + + if (typeof fromOriginY === 'string') { + fromOriginY = originYOffset[fromOriginY]; + } + else { + fromOriginY -= 0.5; + } + + if (typeof toOriginY === 'string') { + toOriginY = originYOffset[toOriginY]; + } + else { + toOriginY -= 0.5; + } + + offsetY = toOriginY - fromOriginY; + + if (offsetX || offsetY) { + dim = this._getTransformedDimensions(); + x = point.x + offsetX * dim.x; + y = point.y + offsetY * dim.y; + } + + return new fabric.Point(x, y); + }, + + /** + * Translates the coordinates from origin to center coordinates (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToCenterPoint: function(point, originX, originY) { + var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center'); + if (this.angle) { + return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle)); + } + return p; + }, + + /** + * Translates the coordinates from center to origin coordinates (based on the object's dimensions) + * @param {fabric.Point} center The point which corresponds to center of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToOriginPoint: function(center, originX, originY) { + var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); + if (this.angle) { + return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle)); + } + return p; + }, + + /** + * Returns the real center coordinates of the object + * @return {fabric.Point} + */ + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + + /** + * Returns the coordinates of the object based on center coordinates + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @return {fabric.Point} + */ + // getOriginPoint: function(center) { + // return this.translateToOriginPoint(center, this.originX, this.originY); + // }, + + /** + * Returns the coordinates of the object as if it has a different origin + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + + /** + * Returns the point in local coordinates + * @param {fabric.Point} point The point relative to the global coordinate system + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), + p, p2; + + if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) { + p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); + } + else { + p = new fabric.Point(this.left, this.top); + } + + p2 = new fabric.Point(point.x, point.y); + if (this.angle) { + p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle)); + } + return p2.subtractEquals(p); + }, + + /** + * Returns the point in global coordinates + * @param {fabric.Point} The point relative to the local coordinate system + * @return {fabric.Point} + */ + // toGlobalPoint: function(point) { + // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); + // }, + + /** + * Sets the position of the object taking into consideration the object's origin + * @param {fabric.Point} pos The new position of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {void} + */ + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); + this.set('left', position.x); + this.set('top', position.y); + }, + + /** + * @param {String} to One of 'left', 'center', 'right' + */ + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), + hypotFull = this.getScaledWidth(), + xFull = fabric.util.cos(angle) * hypotFull, + yFull = fabric.util.sin(angle) * hypotFull, + offsetFrom, offsetTo; + + //TODO: this function does not consider mixed situation like top, center. + if (typeof this.originX === 'string') { + offsetFrom = originXOffset[this.originX]; + } + else { + offsetFrom = this.originX - 0.5; + } + if (typeof to === 'string') { + offsetTo = originXOffset[to]; + } + else { + offsetTo = to - 0.5; + } + this.left += xFull * (offsetTo - offsetFrom); + this.top += yFull * (offsetTo - offsetFrom); + this.setCoords(); + this.originX = to; + }, + + /** + * Sets the origin/position of the object to it's center point + * @private + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * Resets the origin/position of the object to it's original origin + * @private + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + + /** + * @private + */ + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); + }, + }); + +})(); + + +(function() { + + function arrayFromCoords(coords) { + return [ + new fabric.Point(coords.tl.x, coords.tl.y), + new fabric.Point(coords.tr.x, coords.tr.y), + new fabric.Point(coords.br.x, coords.br.y), + new fabric.Point(coords.bl.x, coords.bl.y) + ]; + } + + var util = fabric.util, + degreesToRadians = util.degreesToRadians, + multiplyMatrices = util.multiplyTransformMatrices, + transformPoint = util.transformPoint; + + util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Describe object's corner position in canvas element coordinates. + * properties are depending on control keys and padding the main controls. + * each property is an object with x, y and corner. + * The `corner` property contains in a similar manner the 4 points of the + * interactive area of the corner. + * The coordinates depends from the controls positionHandler and are used + * to draw and locate controls + * @memberOf fabric.Object.prototype + */ + oCoords: null, + + /** + * Describe object's corner position in canvas object absolute coordinates + * properties are tl,tr,bl,br and describe the four main corner. + * each property is an object with x, y, instance of Fabric.Point. + * The coordinates depends from this properties: width, height, scaleX, scaleY + * skewX, skewY, angle, strokeWidth, top, left. + * Those coordinates are useful to understand where an object is. They get updated + * with oCoords but they do not need to be updated when zoom or panning change. + * The coordinates get updated with @method setCoords. + * You can calculate them without updating with @method calcACoords(); + * @memberOf fabric.Object.prototype + */ + aCoords: null, + + /** + * Describe object's corner position in canvas element coordinates. + * includes padding. Used of object detection. + * set and refreshed with setCoords and calcCoords. + * @memberOf fabric.Object.prototype + */ + lineCoords: null, + + /** + * storage for object transform matrix + */ + ownMatrixCache: null, + + /** + * storage for object full transform matrix + */ + matrixCache: null, + + /** + * custom controls interface + * controls are added by default_controls.js + */ + controls: { }, + + /** + * return correct set of coordinates for intersection + * this will return either aCoords or lineCoords. + * @param {Boolean} absolute will return aCoords if true or lineCoords + * @return {Object} {tl, tr, br, bl} points + */ + _getCoords: function(absolute, calculate) { + if (calculate) { + return (absolute ? this.calcACoords() : this.calcLineCoords()); + } + if (!this.aCoords || !this.lineCoords) { + this.setCoords(true); + } + return (absolute ? this.aCoords : this.lineCoords); + }, + + /** + * return correct set of coordinates for intersection + * this will return either aCoords or lineCoords. + * The coords are returned in an array. + * @return {Array} [tl, tr, br, bl] of points + */ + getCoords: function(absolute, calculate) { + return arrayFromCoords(this._getCoords(absolute, calculate)); + }, + + /** + * Checks if object intersects with an area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object intersects with an area formed by 2 points + */ + intersectsWithRect: function(pointTL, pointBR, absolute, calculate) { + var coords = this.getCoords(absolute, calculate), + intersection = fabric.Intersection.intersectPolygonRectangle( + coords, + pointTL, + pointBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object intersects with another object + * @param {Object} other Object to test + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object intersects with another object + */ + intersectsWithObject: function(other, absolute, calculate) { + var intersection = fabric.Intersection.intersectPolygonPolygon( + this.getCoords(absolute, calculate), + other.getCoords(absolute, calculate) + ); + + return intersection.status === 'Intersection' + || other.isContainedWithinObject(this, absolute, calculate) + || this.isContainedWithinObject(other, absolute, calculate); + }, + + /** + * Checks if object is fully contained within area of another object + * @param {Object} other Object to test + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is fully contained within area of another object + */ + isContainedWithinObject: function(other, absolute, calculate) { + var points = this.getCoords(absolute, calculate), + otherCoords = absolute ? other.aCoords : other.lineCoords, + i = 0, lines = other._getImageLines(otherCoords); + for (; i < 4; i++) { + if (!other.containsPoint(points[i], lines)) { + return false; + } + } + return true; + }, + + /** + * Checks if object is fully contained within area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is fully contained within area formed by 2 points + */ + isContainedWithinRect: function(pointTL, pointBR, absolute, calculate) { + var boundingRect = this.getBoundingRect(absolute, calculate); + + return ( + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y + ); + }, + + /** + * Checks if point is inside the object + * @param {fabric.Point} point Point to check against + * @param {Object} [lines] object returned from @method _getImageLines + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if point is inside the object + */ + containsPoint: function(point, lines, absolute, calculate) { + var coords = this._getCoords(absolute, calculate), + lines = lines || this._getImageLines(coords), + xPoints = this._findCrossPoints(point, lines); + // if xPoints is odd then point is inside the object + return (xPoints !== 0 && xPoints % 2 === 1); + }, + + /** + * Checks if object is contained within the canvas with current viewportTransform + * the check is done stopping at first point that appears on screen + * @param {Boolean} [calculate] use coordinates of current position instead of .aCoords + * @return {Boolean} true if object is fully or partially contained within canvas + */ + isOnScreen: function(calculate) { + if (!this.canvas) { + return false; + } + var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; + var points = this.getCoords(true, calculate); + // if some point is on screen, the object is on screen. + if (points.some(function(point) { + return point.x <= pointBR.x && point.x >= pointTL.x && + point.y <= pointBR.y && point.y >= pointTL.y; + })) { + return true; + } + // no points on screen, check intersection with absolute coordinates + if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { + return true; + } + return this._containsCenterOfCanvas(pointTL, pointBR, calculate); + }, + + /** + * Checks if the object contains the midpoint between canvas extremities + * Does not make sense outside the context of isOnScreen and isPartiallyOnScreen + * @private + * @param {Fabric.Point} pointTL Top Left point + * @param {Fabric.Point} pointBR Top Right point + * @param {Boolean} calculate use coordinates of current position instead of .oCoords + * @return {Boolean} true if the object contains the point + */ + _containsCenterOfCanvas: function(pointTL, pointBR, calculate) { + // worst case scenario the object is so big that contains the screen + var centerPoint = { x: (pointTL.x + pointBR.x) / 2, y: (pointTL.y + pointBR.y) / 2 }; + if (this.containsPoint(centerPoint, null, true, calculate)) { + return true; + } + return false; + }, + + /** + * Checks if object is partially contained within the canvas with current viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is partially contained within canvas + */ + isPartiallyOnScreen: function(calculate) { + if (!this.canvas) { + return false; + } + var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; + if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { + return true; + } + var allPointsAreOutside = this.getCoords(true, calculate).every(function(point) { + return (point.x >= pointBR.x || point.x <= pointTL.x) && + (point.y >= pointBR.y || point.y <= pointTL.y); + }); + return allPointsAreOutside && this._containsCenterOfCanvas(pointTL, pointBR, calculate); + }, + + /** + * Method that returns an object with the object edges in it, given the coordinates of the corners + * @private + * @param {Object} oCoords Coordinates of the object corners + */ + _getImageLines: function(oCoords) { + + var lines = { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + }; + + // // debugging + // if (this.canvas.contextTop) { + // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); + // } + + return lines; + }, + + /** + * Helper method to determine how many cross points are between the 4 object edges + * and the horizontal line determined by a point on canvas + * @private + * @param {fabric.Point} point Point to check + * @param {Object} lines Coordinates of the object being evaluated + */ + // remove yi, not used but left code here just in case. + _findCrossPoints: function(point, lines) { + var b1, b2, a1, a2, xi, // yi, + xcount = 0, + iLine; + + for (var lineKey in lines) { + iLine = lines[lineKey]; + // optimisation 1: line below point. no cross + if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { + continue; + } + // optimisation 2: line above point. no cross + if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { + xi = iLine.o.x; + // yi = point.y; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + + xi = -(a1 - a2) / (b1 - b2); + // yi = a1 + b1 * xi; + } + // dont count xi < point.x cases + if (xi >= point.x) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount === 2) { + break; + } + } + return xcount; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * the box is intended as aligned to axis of canvas. + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords / .aCoords + * @return {Object} Object with left, top, width, height properties + */ + getBoundingRect: function(absolute, calculate) { + var coords = this.getCoords(absolute, calculate); + return util.makeBoundingBoxFromPoints(coords); + }, + + /** + * Returns width of an object's bounding box counting transformations + * before 2.0 it was named getWidth(); + * @return {Number} width value + */ + getScaledWidth: function() { + return this._getTransformedDimensions().x; + }, + + /** + * Returns height of an object bounding box counting transformations + * before 2.0 it was named getHeight(); + * @return {Number} height value + */ + getScaledHeight: function() { + return this._getTransformedDimensions().y; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @param {Number} value + * @return {Number} + */ + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } + else { + return this.minScaleLimit; + } + } + else if (value === 0) { + return 0.0001; + } + return value; + }, + + /** + * Scales an object (equally by x and y) + * @param {Number} value Scale factor + * @return {fabric.Object} thisArg + * @chainable + */ + scale: function(value) { + this._set('scaleX', value); + this._set('scaleY', value); + return this.setCoords(); + }, + + /** + * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New width value + * @param {Boolean} absolute ignore viewport + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToWidth: function(value, absolute) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRect(absolute).width / this.getScaledWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + + /** + * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New height value + * @param {Boolean} absolute ignore viewport + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToHeight: function(value, absolute) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRect(absolute).height / this.getScaledHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + + /** + * Calculates and returns the .coords of an object. + * unused by the library, only for the end dev. + * @return {Object} Object with tl, tr, br, bl .... + * @chainable + * @deprecated + */ + calcCoords: function(absolute) { + // this is a compatibility function to avoid removing calcCoords now. + if (absolute) { + return this.calcACoords(); + } + return this.calcOCoords(); + }, + + calcLineCoords: function() { + var vpt = this.getViewportTransform(), + padding = this.padding, angle = degreesToRadians(this.angle), + cos = util.cos(angle), sin = util.sin(angle), + cosP = cos * padding, sinP = sin * padding, cosPSinP = cosP + sinP, + cosPMinusSinP = cosP - sinP, aCoords = this.calcACoords(); + + var lineCoords = { + tl: transformPoint(aCoords.tl, vpt), + tr: transformPoint(aCoords.tr, vpt), + bl: transformPoint(aCoords.bl, vpt), + br: transformPoint(aCoords.br, vpt), + }; + + if (padding) { + lineCoords.tl.x -= cosPMinusSinP; + lineCoords.tl.y -= cosPSinP; + lineCoords.tr.x += cosPSinP; + lineCoords.tr.y -= cosPMinusSinP; + lineCoords.bl.x -= cosPSinP; + lineCoords.bl.y += cosPMinusSinP; + lineCoords.br.x += cosPMinusSinP; + lineCoords.br.y += cosPSinP; + } + + return lineCoords; + }, + + calcOCoords: function() { + var rotateMatrix = this._calcRotateMatrix(), + translateMatrix = this._calcTranslateMatrix(), + vpt = this.getViewportTransform(), + startMatrix = multiplyMatrices(vpt, translateMatrix), + finalMatrix = multiplyMatrices(startMatrix, rotateMatrix), + finalMatrix = multiplyMatrices(finalMatrix, [1 / vpt[0], 0, 0, 1 / vpt[3], 0, 0]), + dim = this._calculateCurrentDimensions(), + coords = {}; + this.forEachControl(function(control, key, fabricObject) { + coords[key] = control.positionHandler(dim, finalMatrix, fabricObject); + }); + + // debug code + // var canvas = this.canvas; + // setTimeout(function() { + // canvas.contextTop.clearRect(0, 0, 700, 700); + // canvas.contextTop.fillStyle = 'green'; + // Object.keys(coords).forEach(function(key) { + // var control = coords[key]; + // canvas.contextTop.fillRect(control.x, control.y, 3, 3); + // }); + // }, 50); + return coords; + }, + + calcACoords: function() { + var rotateMatrix = this._calcRotateMatrix(), + translateMatrix = this._calcTranslateMatrix(), + finalMatrix = multiplyMatrices(translateMatrix, rotateMatrix), + dim = this._getTransformedDimensions(), + w = dim.x / 2, h = dim.y / 2; + return { + // corners + tl: transformPoint({ x: -w, y: -h }, finalMatrix), + tr: transformPoint({ x: w, y: -h }, finalMatrix), + bl: transformPoint({ x: -w, y: h }, finalMatrix), + br: transformPoint({ x: w, y: h }, finalMatrix) + }; + }, + + /** + * Sets corner and controls position coordinates based on current angle, width and height, left and top. + * oCoords are used to find the corners + * aCoords are used to quickly find an object on the canvas + * lineCoords are used to quickly find object during pointer events. + * See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords} + * @param {Boolean} [skipCorners] skip calculation of oCoords. + * @return {fabric.Object} thisArg + * @chainable + */ + setCoords: function(skipCorners) { + this.aCoords = this.calcACoords(); + // in case we are in a group, for how the inner group target check works, + // lineCoords are exactly aCoords. Since the vpt gets absorbed by the normalized pointer. + this.lineCoords = this.group ? this.aCoords : this.calcLineCoords(); + if (skipCorners) { + return this; + } + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this.oCoords = this.calcOCoords(); + this._setCornerCoords && this._setCornerCoords(); + return this; + }, + + /** + * calculate rotation matrix of an object + * @return {Array} rotation matrix for the object + */ + _calcRotateMatrix: function() { + return util.calcRotateMatrix(this); + }, + + /** + * calculate the translation matrix for an object transform + * @return {Array} rotation matrix for the object + */ + _calcTranslateMatrix: function() { + var center = this.getCenterPoint(); + return [1, 0, 0, 1, center.x, center.y]; + }, + + transformMatrixKey: function(skipGroup) { + var sep = '_', prefix = ''; + if (!skipGroup && this.group) { + prefix = this.group.transformMatrixKey(skipGroup) + sep; + }; + return prefix + this.top + sep + this.left + sep + this.scaleX + sep + this.scaleY + + sep + this.skewX + sep + this.skewY + sep + this.angle + sep + this.originX + sep + this.originY + + sep + this.width + sep + this.height + sep + this.strokeWidth + this.flipX + this.flipY; + }, + + /** + * calculate transform matrix that represents the current transformations from the + * object's properties. + * @param {Boolean} [skipGroup] return transform matrix for object not counting parent transformations + * There are some situation in which this is useful to avoid the fake rotation. + * @return {Array} transform matrix for the object + */ + calcTransformMatrix: function(skipGroup) { + var matrix = this.calcOwnMatrix(); + if (skipGroup || !this.group) { + return matrix; + } + var key = this.transformMatrixKey(skipGroup), cache = this.matrixCache || (this.matrixCache = {}); + if (cache.key === key) { + return cache.value; + } + if (this.group) { + matrix = multiplyMatrices(this.group.calcTransformMatrix(false), matrix); + } + cache.key = key; + cache.value = matrix; + return matrix; + }, + + /** + * calculate transform matrix that represents the current transformations from the + * object's properties, this matrix does not include the group transformation + * @return {Array} transform matrix for the object + */ + calcOwnMatrix: function() { + var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {}); + if (cache.key === key) { + return cache.value; + } + var tMatrix = this._calcTranslateMatrix(), + options = { + angle: this.angle, + translateX: tMatrix[4], + translateY: tMatrix[5], + scaleX: this.scaleX, + scaleY: this.scaleY, + skewX: this.skewX, + skewY: this.skewY, + flipX: this.flipX, + flipY: this.flipY, + }; + cache.key = key; + cache.value = util.composeMatrix(options); + return cache.value; + }, + + /* + * Calculate object dimensions from its properties + * @private + * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix + * not including or including flipX, flipY to emulate the flipping boolean + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { + return util.calcDimensionsMatrix({ + skewX: skewX, + skewY: skewY, + scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1), + scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1) + }); + }, + + /* + * Calculate object dimensions from its properties + * @private + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _getNonTransformedDimensions: function() { + var strokeWidth = this.strokeWidth, + w = this.width + strokeWidth, + h = this.height + strokeWidth; + return { x: w, y: h }; + }, + + /* + * Calculate object bounding box dimensions from its properties scale, skew. + * @param {Number} skewX, a value to override current skewX + * @param {Number} skewY, a value to override current skewY + * @private + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _getTransformedDimensions: function(skewX, skewY) { + if (typeof skewX === 'undefined') { + skewX = this.skewX; + } + if (typeof skewY === 'undefined') { + skewY = this.skewY; + } + var dimensions, dimX, dimY, + noSkew = skewX === 0 && skewY === 0; + + if (this.strokeUniform) { + dimX = this.width; + dimY = this.height; + } + else { + dimensions = this._getNonTransformedDimensions(); + dimX = dimensions.x; + dimY = dimensions.y; + } + if (noSkew) { + return this._finalizeDimensions(dimX * this.scaleX, dimY * this.scaleY); + } + var bbox = util.sizeAfterTransform(dimX, dimY, { + scaleX: this.scaleX, + scaleY: this.scaleY, + skewX: skewX, + skewY: skewY, + }); + return this._finalizeDimensions(bbox.x, bbox.y); + }, + + /* + * Calculate object bounding box dimensions from its properties scale, skew. + * @param Number width width of the bbox + * @param Number height height of the bbox + * @private + * @return {Object} .x finalized width dimension + * @return {Object} .y finalized height dimension + */ + _finalizeDimensions: function(width, height) { + return this.strokeUniform ? + { x: width + this.strokeWidth, y: height + this.strokeWidth } + : + { x: width, y: height }; + }, + + /* + * Calculate object dimensions for controls box, including padding and canvas zoom. + * and active selection + * private + */ + _calculateCurrentDimensions: function() { + var vpt = this.getViewportTransform(), + dim = this._getTransformedDimensions(), + p = transformPoint(dim, vpt, true); + return p.scalarAdd(2 * this.padding); + }, + }); +})(); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Moves an object to the bottom of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } + else if (this.canvas) { + this.canvas.sendToBack(this); + } + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } + else if (this.canvas) { + this.canvas.bringToFront(this); + } + return this; + }, + + /** + * Moves an object down in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } + else if (this.canvas) { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } + else if (this.canvas) { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {Number} index New position of object + * @return {fabric.Object} thisArg + * @chainable + */ + moveTo: function(index) { + if (this.group && this.group.type !== 'activeSelection') { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } + else if (this.canvas) { + this.canvas.moveTo(this, index); + } + return this; + } +}); + + +/* _TO_SVG_START_ */ +(function() { + function getSvgColorString(prop, value) { + if (!value) { + return prop + ': none; '; + } + else if (value.toLive) { + return prop + ': url(#SVGID_' + value.id + '); '; + } + else { + var color = new fabric.Color(value), + str = prop + ': ' + color.toRgb() + '; ', + opacity = color.getAlpha(); + if (opacity !== 1) { + //change the color in rgb + opacity + str += prop + '-opacity: ' + opacity.toString() + '; '; + } + return str; + } + } + + var toFixed = fabric.util.toFixed; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Returns styles-string for svg-export + * @param {Boolean} skipShadow a boolean to skip shadow filter output + * @return {String} + */ + getSvgStyles: function(skipShadow) { + + var fillRule = this.fillRule ? this.fillRule : 'nonzero', + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', + strokeDashOffset = this.strokeDashOffset ? this.strokeDashOffset : '0', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', + visibility = this.visible ? '' : ' visibility: hidden;', + filter = skipShadow ? '' : this.getSvgFilter(), + fill = getSvgColorString('fill', this.fill), + stroke = getSvgColorString('stroke', this.stroke); + + return [ + stroke, + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-dashoffset: ', strokeDashOffset, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + fill, + 'fill-rule: ', fillRule, '; ', + 'opacity: ', opacity, ';', + filter, + visibility + ].join(''); + }, + + /** + * Returns styles-string for svg-export + * @param {Object} style the object from which to retrieve style properties + * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style. + * @return {String} + */ + getSvgSpanStyles: function(style, useWhiteSpace) { + var term = '; '; + var fontFamily = style.fontFamily ? + 'font-family: ' + (((style.fontFamily.indexOf('\'') === -1 && style.fontFamily.indexOf('"') === -1) ? + '\'' + style.fontFamily + '\'' : style.fontFamily)) + term : ''; + var strokeWidth = style.strokeWidth ? 'stroke-width: ' + style.strokeWidth + term : '', + fontFamily = fontFamily, + fontSize = style.fontSize ? 'font-size: ' + style.fontSize + 'px' + term : '', + fontStyle = style.fontStyle ? 'font-style: ' + style.fontStyle + term : '', + fontWeight = style.fontWeight ? 'font-weight: ' + style.fontWeight + term : '', + fill = style.fill ? getSvgColorString('fill', style.fill) : '', + stroke = style.stroke ? getSvgColorString('stroke', style.stroke) : '', + textDecoration = this.getSvgTextDecoration(style), + deltaY = style.deltaY ? 'baseline-shift: ' + (-style.deltaY) + '; ' : ''; + if (textDecoration) { + textDecoration = 'text-decoration: ' + textDecoration + term; + } + + return [ + stroke, + strokeWidth, + fontFamily, + fontSize, + fontStyle, + fontWeight, + textDecoration, + fill, + deltaY, + useWhiteSpace ? 'white-space: pre; ' : '' + ].join(''); + }, + + /** + * Returns text-decoration property for svg-export + * @param {Object} style the object from which to retrieve style properties + * @return {String} + */ + getSvgTextDecoration: function(style) { + return ['overline', 'underline', 'line-through'].filter(function(decoration) { + return style[decoration.replace('-', '')]; + }).join(' '); + }, + + /** + * Returns filter for svg shadow + * @return {String} + */ + getSvgFilter: function() { + return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + }, + + /** + * Returns id attribute for svg output + * @return {String} + */ + getSvgCommons: function() { + return [ + this.id ? 'id="' + this.id + '" ' : '', + this.clipPath ? 'clip-path="url(#' + this.clipPath.clipPathId + ')" ' : '', + ].join(''); + }, + + /** + * Returns transform-string for svg-export + * @param {Boolean} use the full transform or the single object one. + * @return {String} + */ + getSvgTransform: function(full, additionalTransform) { + var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(), + svgTransform = 'transform="' + fabric.util.matrixToSVG(transform); + return svgTransform + + (additionalTransform || '') + '" '; + }, + + _setSVGBg: function(textBgRects) { + if (this.backgroundColor) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + textBgRects.push( + '\t\t\n'); + } + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + return this._createBaseSVGMarkup(this._toSVG(reviver), { reviver: reviver }); + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(reviver), { reviver: reviver }); + }, + + /** + * @private + */ + _createBaseClipPathSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var reviver = options.reviver, + additionalTransform = options.additionalTransform || '', + commonPieces = [ + this.getSvgTransform(true, additionalTransform), + this.getSvgCommons(), + ].join(''), + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'); + objectMarkup[index] = commonPieces; + return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join(''); + }, + + /** + * @private + */ + _createBaseSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var noStyle = options.noStyle, + reviver = options.reviver, + styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ', + shadowInfo = options.withShadow ? 'style="' + this.getSvgFilter() + '" ' : '', + clipPath = this.clipPath, + vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '', + absoluteClipPath = clipPath && clipPath.absolutePositioned, + stroke = this.stroke, fill = this.fill, shadow = this.shadow, + commonPieces, markup = [], clipPathMarkup, + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'), + additionalTransform = options.additionalTransform; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + clipPathMarkup = '\n' + + clipPath.toClipPathSVG(reviver) + + '\n'; + } + if (absoluteClipPath) { + markup.push( + '\n' + ); + } + markup.push( + '\n' + ); + commonPieces = [ + styleInfo, + vectorEffect, + noStyle ? '' : this.addPaintOrder(), ' ', + additionalTransform ? 'transform="' + additionalTransform + '" ' : '', + ].join(''); + objectMarkup[index] = commonPieces; + if (fill && fill.toLive) { + markup.push(fill.toSVG(this)); + } + if (stroke && stroke.toLive) { + markup.push(stroke.toSVG(this)); + } + if (shadow) { + markup.push(shadow.toSVG(this)); + } + if (clipPath) { + markup.push(clipPathMarkup); + } + markup.push(objectMarkup.join('')); + markup.push('\n'); + absoluteClipPath && markup.push('\n'); + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + + addPaintOrder: function() { + return this.paintFirst !== 'fill' ? ' paint-order="' + this.paintFirst + '" ' : ''; + } + }); +})(); +/* _TO_SVG_END_ */ + + +(function() { + + var extend = fabric.util.object.extend, + originalSet = 'stateProperties'; + + /* + Depends on `stateProperties` + */ + function saveProps(origin, destination, props) { + var tmpObj = { }, deep = true; + props.forEach(function(prop) { + tmpObj[prop] = origin[prop]; + }); + + extend(origin[destination], tmpObj, deep); + } + + function _isEqual(origValue, currentValue, firstPass) { + if (origValue === currentValue) { + // if the objects are identical, return + return true; + } + else if (Array.isArray(origValue)) { + if (!Array.isArray(currentValue) || origValue.length !== currentValue.length) { + return false; + } + for (var i = 0, len = origValue.length; i < len; i++) { + if (!_isEqual(origValue[i], currentValue[i])) { + return false; + } + } + return true; + } + else if (origValue && typeof origValue === 'object') { + var keys = Object.keys(origValue), key; + if (!currentValue || + typeof currentValue !== 'object' || + (!firstPass && keys.length !== Object.keys(currentValue).length) + ) { + return false; + } + for (var i = 0, len = keys.length; i < len; i++) { + key = keys[i]; + // since clipPath is in the statefull cache list and the clipPath objects + // would be iterated as an object, this would lead to possible infinite recursion + // we do not want to compare those. + if (key === 'canvas' || key === 'group') { + continue; + } + if (!_isEqual(origValue[key], currentValue[key])) { + return false; + } + } + return true; + } + } + + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns true if object state (one of its state properties) was changed + * @param {String} [propertySet] optional name for the set of property we want to save + * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called + */ + hasStateChanged: function(propertySet) { + propertySet = propertySet || originalSet; + var dashedPropertySet = '_' + propertySet; + if (Object.keys(this[dashedPropertySet]).length < this[propertySet].length) { + return true; + } + return !_isEqual(this[dashedPropertySet], this, true); + }, + + /** + * Saves state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + saveState: function(options) { + var propertySet = options && options.propertySet || originalSet, + destination = '_' + propertySet; + if (!this[destination]) { + return this.setupState(options); + } + saveProps(this, destination, this[propertySet]); + if (options && options.stateProperties) { + saveProps(this, destination, options.stateProperties); + } + return this; + }, + + /** + * Setups state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + setupState: function(options) { + options = options || { }; + var propertySet = options.propertySet || originalSet; + options.propertySet = propertySet; + this['_' + propertySet] = { }; + this.saveState(options); + return this; + } + }); +})(); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Determines which corner has been clicked + * @private + * @param {Object} pointer The pointer indicating the mouse position + * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found + */ + _findTargetCorner: function(pointer, forTouch) { + // objects in group, anykind, are not self modificable, + // must not return an hovered corner. + if (!this.hasControls || this.group || (!this.canvas || this.canvas._activeObject !== this)) { + return false; + } + + var ex = pointer.x, + ey = pointer.y, + xPoints, + lines, keys = Object.keys(this.oCoords), + j = keys.length - 1, i; + this.__corner = 0; + + // cycle in reverse order so we pick first the one on top + for (; j >= 0; j--) { + i = keys[j]; + if (!this.isControlVisible(i)) { + continue; + } + + lines = this._getImageLines(forTouch ? this.oCoords[i].touchCorner : this.oCoords[i].corner); + // // debugging + // + // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); + + xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); + if (xPoints !== 0 && xPoints % 2 === 1) { + this.__corner = i; + return i; + } + } + return false; + }, + + /** + * Calls a function for each control. The function gets called, + * with the control, the object that is calling the iterator and the control's key + * @param {Function} fn function to iterate over the controls over + */ + forEachControl: function(fn) { + for (var i in this.controls) { + fn(this.controls[i], i, this); + }; + }, + + /** + * Sets the coordinates of the draggable boxes in the corners of + * the image used to scale/rotate it. + * note: if we would switch to ROUND corner area, all of this would disappear. + * everything would resolve to a single point and a pythagorean theorem for the distance + * @private + */ + _setCornerCoords: function() { + var coords = this.oCoords; + + for (var control in coords) { + var controlObject = this.controls[control]; + coords[control].corner = controlObject.calcCornerCoords( + this.angle, this.cornerSize, coords[control].x, coords[control].y, false); + coords[control].touchCorner = controlObject.calcCornerCoords( + this.angle, this.touchCornerSize, coords[control].x, coords[control].y, true); + } + }, + + /** + * Draws a colored layer behind the object, inside its selection borders. + * Requires public options: padding, selectionBackgroundColor + * this function is called when the context is transformed + * has checks to be skipped when the object is on a staticCanvas + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawSelectionBackground: function(ctx) { + if (!this.selectionBackgroundColor || + (this.canvas && !this.canvas.interactive) || + (this.canvas && this.canvas._activeObject !== this) + ) { + return this; + } + ctx.save(); + var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(), + vpt = this.canvas.viewportTransform; + ctx.translate(center.x, center.y); + ctx.scale(1 / vpt[0], 1 / vpt[3]); + ctx.rotate(degreesToRadians(this.angle)); + ctx.fillStyle = this.selectionBackgroundColor; + ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y); + ctx.restore(); + return this; + }, + + /** + * Draws borders of an object's bounding box. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawBorders: function(ctx, styleOverride) { + styleOverride = styleOverride || {}; + var wh = this._calculateCurrentDimensions(), + strokeWidth = this.borderScaleFactor, + width = wh.x + strokeWidth, + height = wh.y + strokeWidth, + hasControls = typeof styleOverride.hasControls !== 'undefined' ? + styleOverride.hasControls : this.hasControls, + shouldStroke = false; + + ctx.save(); + ctx.strokeStyle = styleOverride.borderColor || this.borderColor; + this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray); + + ctx.strokeRect( + -width / 2, + -height / 2, + width, + height + ); + + if (hasControls) { + ctx.beginPath(); + this.forEachControl(function(control, key, fabricObject) { + // in this moment, the ctx is centered on the object. + // width and height of the above function are the size of the bbox. + if (control.withConnection && control.getVisibility(fabricObject, key)) { + // reset movement for each control + shouldStroke = true; + ctx.moveTo(control.x * width, control.y * height); + ctx.lineTo( + control.x * width + control.offsetX, + control.y * height + control.offsetY + ); + } + }); + if (shouldStroke) { + ctx.stroke(); + } + } + ctx.restore(); + return this; + }, + + /** + * Draws borders of an object's bounding box when it is inside a group. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {object} options object representing current object parameters + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawBordersInGroup: function(ctx, options, styleOverride) { + styleOverride = styleOverride || {}; + var bbox = fabric.util.sizeAfterTransform(this.width, this.height, options), + strokeWidth = this.strokeWidth, + strokeUniform = this.strokeUniform, + borderScaleFactor = this.borderScaleFactor, + width = + bbox.x + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleX) + borderScaleFactor, + height = + bbox.y + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleY) + borderScaleFactor; + ctx.save(); + this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray); + ctx.strokeStyle = styleOverride.borderColor || this.borderColor; + ctx.strokeRect( + -width / 2, + -height / 2, + width, + height + ); + + ctx.restore(); + return this; + }, + + /** + * Draws corners of an object's bounding box. + * Requires public properties: width, height + * Requires public options: cornerSize, padding + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawControls: function(ctx, styleOverride) { + styleOverride = styleOverride || {}; + ctx.save(); + var retinaScaling = this.canvas.getRetinaScaling(), matrix, p; + ctx.setTransform(retinaScaling, 0, 0, retinaScaling, 0, 0); + ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor; + if (!this.transparentCorners) { + ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor; + } + this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray); + this.setCoords(); + if (this.group) { + // fabricJS does not really support drawing controls inside groups, + // this piece of code here helps having at least the control in places. + // If an application needs to show some objects as selected because of some UI state + // can still call Object._renderControls() on any object they desire, independently of groups. + // using no padding, circular controls and hiding the rotating cursor is higly suggested, + matrix = this.group.calcTransformMatrix(); + } + this.forEachControl(function(control, key, fabricObject) { + p = fabricObject.oCoords[key]; + if (control.getVisibility(fabricObject, key)) { + if (matrix) { + p = fabric.util.transformPoint(p, matrix); + } + control.render(ctx, p.x, p.y, styleOverride, fabricObject); + } + }); + ctx.restore(); + + return this; + }, + + /** + * Returns true if the specified control is visible, false otherwise. + * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. + * @returns {Boolean} true if the specified control is visible, false otherwise + */ + isControlVisible: function(controlKey) { + return this.controls[controlKey] && this.controls[controlKey].getVisibility(this, controlKey); + }, + + /** + * Sets the visibility of the specified control. + * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. + * @param {Boolean} visible true to set the specified control visible, false otherwise + * @return {fabric.Object} thisArg + * @chainable + */ + setControlVisible: function(controlKey, visible) { + if (!this._controlsVisibility) { + this._controlsVisibility = {}; + } + this._controlsVisibility[controlKey] = visible; + return this; + }, + + /** + * Sets the visibility state of object controls. + * @param {Object} [options] Options object + * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it + * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it + * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it + * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it + * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it + * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it + * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it + * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it + * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it + * @return {fabric.Object} thisArg + * @chainable + */ + setControlsVisibility: function(options) { + options || (options = { }); + + for (var p in options) { + this.setControlVisible(p, options[p]); + } + return this; + }, + + + /** + * This callback function is called every time _discardActiveObject or _setActiveObject + * try to to deselect this object. If the function returns true, the process is cancelled + * @param {Object} [options] options sent from the upper functions + * @param {Event} [options.e] event if the process is generated by an event + */ + onDeselect: function() { + // implemented by sub-classes, as needed. + }, + + + /** + * This callback function is called every time _discardActiveObject or _setActiveObject + * try to to select this object. If the function returns true, the process is cancelled + * @param {Object} [options] options sent from the upper functions + * @param {Event} [options.e] event if the process is generated by an event + */ + onSelect: function() { + // implemented by sub-classes, as needed. + } + }); +})(); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Animation duration (in ms) for fx* methods + * @type Number + * @default + */ + FX_DURATION: 500, + + /** + * Centers object horizontally with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectH: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.left, + endValue: this.getCenter().left, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('left', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Centers object vertically with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectV: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.top, + endValue: this.getCenter().top, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('top', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Same as `fabric.Canvas#remove` but animated + * @param {fabric.Object} object Object to remove + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxRemove: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.opacity, + endValue: 0, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('opacity', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function () { + _this.remove(object); + onComplete(); + } + }); + + return this; + } +}); + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Animates object's properties + * @param {String|Object} property Property to animate (if string) or properties to animate (if object) + * @param {Number|Object} value Value to animate property to (if string was given first) or options object + * @return {fabric.Object} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} + * @chainable + * + * As object — multiple properties + * + * object.animate({ left: ..., top: ... }); + * object.animate({ left: ..., top: ... }, { duration: ... }); + * + * As string — one property + * + * object.animate('left', ...); + * object.animate('left', { duration: ... }); + * + */ + animate: function() { + if (arguments[0] && typeof arguments[0] === 'object') { + var propsToAnimate = [], prop, skipCallbacks; + for (prop in arguments[0]) { + propsToAnimate.push(prop); + } + for (var i = 0, len = propsToAnimate.length; i < len; i++) { + prop = propsToAnimate[i]; + skipCallbacks = i !== len - 1; + this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); + } + } + else { + this._animate.apply(this, arguments); + } + return this; + }, + + /** + * @private + * @param {String} property Property to animate + * @param {String} to Value to animate to + * @param {Object} [options] Options object + * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked + */ + _animate: function(property, to, options, skipCallbacks) { + var _this = this, propPair; + + to = to.toString(); + + if (!options) { + options = { }; + } + else { + options = fabric.util.object.clone(options); + } + + if (~property.indexOf('.')) { + propPair = property.split('.'); + } + + var propIsColor = + _this.colorProperties.indexOf(property) > -1 || + (propPair && _this.colorProperties.indexOf(propPair[1]) > -1); + + var currentValue = propPair + ? this.get(propPair[0])[propPair[1]] + : this.get(property); + + if (!('from' in options)) { + options.from = currentValue; + } + + if (!propIsColor) { + if (~to.indexOf('=')) { + to = currentValue + parseFloat(to.replace('=', '')); + } + else { + to = parseFloat(to); + } + } + + var _options = { + startValue: options.from, + endValue: to, + byValue: options.by, + easing: options.easing, + duration: options.duration, + abort: options.abort && function(value, valueProgress, timeProgress) { + return options.abort.call(_this, value, valueProgress, timeProgress); + }, + onChange: function (value, valueProgress, timeProgress) { + if (propPair) { + _this[propPair[0]][propPair[1]] = value; + } + else { + _this.set(property, value); + } + if (skipCallbacks) { + return; + } + options.onChange && options.onChange(value, valueProgress, timeProgress); + }, + onComplete: function (value, valueProgress, timeProgress) { + if (skipCallbacks) { + return; + } + + _this.setCoords(); + options.onComplete && options.onComplete(value, valueProgress, timeProgress); + } + }; + + if (propIsColor) { + return fabric.util.animateColor(_options.startValue, _options.endValue, _options.duration, _options); + } + else { + return fabric.util.animate(_options); + } + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }; + + if (fabric.Line) { + fabric.warn('fabric.Line is already defined'); + return; + } + + /** + * Line class + * @class fabric.Line + * @extends fabric.Object + * @see {@link fabric.Line#initialize} for constructor definition + */ + fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'line', + + /** + * x value or first line edge + * @type Number + * @default + */ + x1: 0, + + /** + * y value or first line edge + * @type Number + * @default + */ + y1: 0, + + /** + * x value or second line edge + * @type Number + * @default + */ + x2: 0, + + /** + * y value or second line edge + * @type Number + * @default + */ + y2: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('x1', 'x2', 'y1', 'y2'), + + /** + * Constructor + * @param {Array} [points] Array of points + * @param {Object} [options] Options object + * @return {fabric.Line} thisArg + */ + initialize: function(points, options) { + if (!points) { + points = [0, 0, 0, 0]; + } + + this.callSuper('initialize', options); + + this.set('x1', points[0]); + this.set('y1', points[1]); + this.set('x2', points[2]); + this.set('y2', points[3]); + + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Object} [options] Options + */ + _setWidthHeight: function(options) { + options || (options = { }); + + this.width = Math.abs(this.x2 - this.x1); + this.height = Math.abs(this.y2 - this.y1); + + this.left = 'left' in options + ? options.left + : this._getLeftToOriginX(); + + this.top = 'top' in options + ? options.top + : this._getTopToOriginY(); + }, + + /** + * @private + * @param {String} key + * @param {*} value + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + if (typeof coordProps[key] !== 'undefined') { + this._setWidthHeight(); + } + return this; + }, + + /** + * @private + * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. + */ + _getLeftToOriginX: makeEdgeToOriginGetter( + { // property names + origin: 'originX', + axis1: 'x1', + axis2: 'x2', + dimension: 'width' + }, + { // possible values of origin + nearest: 'left', + center: 'center', + farthest: 'right' + } + ), + + /** + * @private + * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. + */ + _getTopToOriginY: makeEdgeToOriginGetter( + { // property names + origin: 'originY', + axis1: 'y1', + axis2: 'y2', + dimension: 'height' + }, + { // possible values of origin + nearest: 'top', + center: 'center', + farthest: 'bottom' + } + ), + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + + + var p = this.calcLinePoints(); + ctx.moveTo(p.x1, p.y1); + ctx.lineTo(p.x2, p.y2); + + ctx.lineWidth = this.strokeWidth; + + // TODO: test this + // make sure setting "fill" changes color of a line + // (by copying fillStyle to strokeStyle, since line is stroked, not filled) + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + + /** + * This function is an helper for svg import. it returns the center of the object in the svg + * untransformed coordinates + * @private + * @return {Object} center point from element coordinates + */ + _findCenterFromElement: function() { + return { + x: (this.x1 + this.x2) / 2, + y: (this.y1 + this.y2) / 2, + }; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); + }, + + /* + * Calculate object dimensions from its properties + * @private + */ + _getNonTransformedDimensions: function() { + var dim = this.callSuper('_getNonTransformedDimensions'); + if (this.strokeLineCap === 'butt') { + if (this.width === 0) { + dim.y -= this.strokeWidth; + } + if (this.height === 0) { + dim.x -= this.strokeWidth; + } + } + return dim; + }, + + /** + * Recalculates line points given width and height + * @private + */ + calcLinePoints: function() { + var xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1, + x1 = (xMult * this.width * 0.5), + y1 = (yMult * this.height * 0.5), + x2 = (xMult * this.width * -0.5), + y2 = (yMult * this.height * -0.5); + + return { + x1: x1, + x2: x2, + y1: y1, + y2: y2 + }; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var p = this.calcLinePoints(); + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) + * @static + * @memberOf fabric.Line + * @see http://www.w3.org/TR/SVG/shapes.html#LineElement + */ + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); + + /** + * Returns fabric.Line instance from an SVG element + * @static + * @memberOf fabric.Line + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @param {Function} [callback] callback function invoked after parsing + */ + fabric.Line.fromElement = function(element, callback, options) { + options = options || { }; + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), + points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; + callback(new fabric.Line(points, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Line instance from an object representation + * @static + * @memberOf fabric.Line + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + */ + fabric.Line.fromObject = function(object, callback) { + function _callback(instance) { + delete instance.points; + callback && callback(instance); + }; + var options = clone(object, true); + options.points = [object.x1, object.y1, object.x2, object.y2]; + fabric.Object._fromObject('Line', options, _callback, 'points'); + }; + + /** + * Produces a function that calculates distance from canvas edge to Line origin. + */ + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, + axis1 = propertyNames.axis1, + axis2 = propertyNames.axis2, + dimension = propertyNames.dimension, + nearest = originValues.nearest, + center = originValues.center, + farthest = originValues.farthest; + + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + + } + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + pi = Math.PI; + + if (fabric.Circle) { + fabric.warn('fabric.Circle is already defined.'); + return; + } + + /** + * Circle class + * @class fabric.Circle + * @extends fabric.Object + * @see {@link fabric.Circle#initialize} for constructor definition + */ + fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'circle', + + /** + * Radius of this circle + * @type Number + * @default + */ + radius: 0, + + /** + * Start angle of the circle, moving clockwise + * deprecated type, this should be in degree, this was an oversight. + * probably will change to degrees in next major version + * @type Number + * @default 0 + */ + startAngle: 0, + + /** + * End angle of the circle + * deprecated type, this should be in degree, this was an oversight. + * probably will change to degrees in next major version + * @type Number + * @default 2Pi + */ + endAngle: pi * 2, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'), + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Circle} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + + if (key === 'radius') { + this.setRadius(value); + } + + return this; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var svgString, x = 0, y = 0, + angle = (this.endAngle - this.startAngle) % ( 2 * pi); + + if (angle === 0) { + svgString = [ + '\n' + ]; + } + else { + var startX = fabric.util.cos(this.startAngle) * this.radius, + startY = fabric.util.sin(this.startAngle) * this.radius, + endX = fabric.util.cos(this.endAngle) * this.radius, + endY = fabric.util.sin(this.endAngle) * this.radius, + largeFlag = angle > pi ? '1' : '0'; + svgString = [ + '\n' + ]; + } + return svgString; + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + ctx.arc( + 0, + 0, + this.radius, + this.startAngle, + this.endAngle, false); + this._renderPaintInOrder(ctx); + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusX: function() { + return this.get('radius') * this.get('scaleX'); + }, + + /** + * Returns vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusY: function() { + return this.get('radius') * this.get('scaleY'); + }, + + /** + * Sets radius of an object (and updates width accordingly) + * @return {fabric.Circle} thisArg + */ + setRadius: function(value) { + this.radius = value; + return this.set('width', value * 2).set('height', value * 2); + }, + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) + * @static + * @memberOf fabric.Circle + * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement + */ + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); + + /** + * Returns {@link fabric.Circle} instance from an SVG element + * @static + * @memberOf fabric.Circle + * @param {SVGElement} element Element to parse + * @param {Function} [callback] Options callback invoked after parsing is finished + * @param {Object} [options] Options object + * @throws {Error} If value of `r` attribute is missing or invalid + */ + fabric.Circle.fromElement = function(element, callback) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + + if (!isValidRadius(parsedAttributes)) { + throw new Error('value of `r` attribute is required and can not be negative'); + } + + parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.radius; + parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.radius; + callback(new fabric.Circle(parsedAttributes)); + }; + + /** + * @private + */ + function isValidRadius(attributes) { + return (('radius' in attributes) && (attributes.radius >= 0)); + } + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Circle} instance from an object representation + * @static + * @memberOf fabric.Circle + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + * @return {void} + */ + fabric.Circle.fromObject = function(object, callback) { + fabric.Object._fromObject('Circle', object, callback); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Triangle) { + fabric.warn('fabric.Triangle is already defined'); + return; + } + + /** + * Triangle class + * @class fabric.Triangle + * @extends fabric.Object + * @return {fabric.Triangle} thisArg + * @see {@link fabric.Triangle#initialize} for constructor definition + */ + fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'triangle', + + /** + * Width is set to 100 to compensate the old initialize code that was setting it to 100 + * @type Number + * @default + */ + width: 100, + + /** + * Height is set to 100 to compensate the old initialize code that was setting it to 100 + * @type Number + * @default + */ + height: 100, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + + this._renderPaintInOrder(ctx); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2, + points = [ + -widthBy2 + ' ' + heightBy2, + '0 ' + -heightBy2, + widthBy2 + ' ' + heightBy2 + ].join(','); + return [ + '' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /** + * Returns {@link fabric.Triangle} instance from an object representation + * @static + * @memberOf fabric.Triangle + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + */ + fabric.Triangle.fromObject = function(object, callback) { + return fabric.Object._fromObject('Triangle', object, callback); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2; + + if (fabric.Ellipse) { + fabric.warn('fabric.Ellipse is already defined.'); + return; + } + + /** + * Ellipse class + * @class fabric.Ellipse + * @extends fabric.Object + * @return {fabric.Ellipse} thisArg + * @see {@link fabric.Ellipse#initialize} for constructor definition + */ + fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'ellipse', + + /** + * Horizontal radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical radius + * @type Number + * @default + */ + ry: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Ellipse} thisArg + */ + initialize: function(options) { + this.callSuper('initialize', options); + this.set('rx', options && options.rx || 0); + this.set('ry', options && options.ry || 0); + }, + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Ellipse} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + switch (key) { + + case 'rx': + this.rx = value; + this.set('width', value * 2); + break; + + case 'ry': + this.ry = value; + this.set('height', value * 2); + break; + + } + return this; + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRx: function() { + return this.get('rx') * this.get('scaleX'); + }, + + /** + * Returns Vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRy: function() { + return this.get('ry') * this.get('scaleY'); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + ctx.save(); + ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); + ctx.arc( + 0, + 0, + this.rx, + 0, + piBy2, + false); + ctx.restore(); + this._renderPaintInOrder(ctx); + }, + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) + * @static + * @memberOf fabric.Ellipse + * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); + + /** + * Returns {@link fabric.Ellipse} instance from an SVG element + * @static + * @memberOf fabric.Ellipse + * @param {SVGElement} element Element to parse + * @param {Function} [callback] Options callback invoked after parsing is finished + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromElement = function(element, callback) { + + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + + parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.rx; + parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.ry; + callback(new fabric.Ellipse(parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Ellipse} instance from an object representation + * @static + * @memberOf fabric.Ellipse + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + * @return {void} + */ + fabric.Ellipse.fromObject = function(object, callback) { + fabric.Object._fromObject('Ellipse', object, callback); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + if (fabric.Rect) { + fabric.warn('fabric.Rect is already defined'); + return; + } + + /** + * Rectangle class + * @class fabric.Rect + * @extends fabric.Object + * @return {fabric.Rect} thisArg + * @see {@link fabric.Rect#initialize} for constructor definition + */ + fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { + + /** + * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat('rx', 'ry'), + + /** + * Type of an object + * @type String + * @default + */ + type: 'rect', + + /** + * Horizontal border radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical border radius + * @type Number + * @default + */ + ry: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), + + /** + * Constructor + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + this.callSuper('initialize', options); + this._initRxRy(); + }, + + /** + * Initializes rx/ry attributes + * @private + */ + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } + else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + + // 1x1 case (used in spray brush) optimization was removed because + // with caching and higher zoom level this makes more damage than help + + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, + ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, + w = this.width, + h = this.height, + x = -this.width / 2, + y = -this.height / 2, + isRounded = rx !== 0 || ry !== 0, + /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */ + k = 1 - 0.5522847498; + ctx.beginPath(); + + ctx.moveTo(x + rx, y); + + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + + ctx.closePath(); + + this._renderPaintInOrder(ctx); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var x = -this.width / 2, y = -this.height / 2; + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) + * @static + * @memberOf fabric.Rect + * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement + */ + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); + + /** + * Returns {@link fabric.Rect} instance from an SVG element + * @static + * @memberOf fabric.Rect + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Rect.fromElement = function(element, callback, options) { + if (!element) { + return callback(null); + } + options = options || { }; + + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + parsedAttributes.height = parsedAttributes.height || 0; + parsedAttributes.width = parsedAttributes.width || 0; + var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + rect.visible = rect.visible && rect.width > 0 && rect.height > 0; + callback(rect); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Rect} instance from an object representation + * @static + * @memberOf fabric.Rect + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created + */ + fabric.Rect.fromObject = function(object, callback) { + return fabric.Object._fromObject('Rect', object, callback); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + min = fabric.util.array.min, + max = fabric.util.array.max, + toFixed = fabric.util.toFixed; + + if (fabric.Polyline) { + fabric.warn('fabric.Polyline is already defined'); + return; + } + + /** + * Polyline class + * @class fabric.Polyline + * @extends fabric.Object + * @see {@link fabric.Polyline#initialize} for constructor definition + */ + fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polyline', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), + + /** + * Constructor + * @param {Array} points Array of points (where each point is an object with x and y) + * @param {Object} [options] Options object + * @return {fabric.Polyline} thisArg + * @example + * var poly = new fabric.Polyline([ + * { x: 10, y: 10 }, + * { x: 50, y: 30 }, + * { x: 40, y: 70 }, + * { x: 60, y: 50 }, + * { x: 100, y: 150 }, + * { x: 40, y: 100 } + * ], { + * stroke: 'red', + * left: 100, + * top: 100 + * }); + */ + initialize: function(points, options) { + options = options || {}; + this.points = points || []; + this.callSuper('initialize', options); + this._setPositionDimensions(options); + }, + + _setPositionDimensions: function(options) { + var calcDim = this._calcDimensions(options), correctLeftTop; + this.width = calcDim.width; + this.height = calcDim.height; + if (!options.fromSVG) { + correctLeftTop = this.translateToGivenOrigin( + { x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 }, + 'left', + 'top', + this.originX, + this.originY + ); + } + if (typeof options.left === 'undefined') { + this.left = options.fromSVG ? calcDim.left : correctLeftTop.x; + } + if (typeof options.top === 'undefined') { + this.top = options.fromSVG ? calcDim.top : correctLeftTop.y; + } + this.pathOffset = { + x: calcDim.left + this.width / 2, + y: calcDim.top + this.height / 2 + }; + }, + + /** + * Calculate the polygon min and max point from points array, + * returning an object with left, top, width, height to measure the + * polygon size + * @return {Object} object.left X coordinate of the polygon leftmost point + * @return {Object} object.top Y coordinate of the polygon topmost point + * @return {Object} object.width distance between X coordinates of the polygon leftmost and rightmost point + * @return {Object} object.height distance between Y coordinates of the polygon topmost and bottommost point + * @private + */ + _calcDimensions: function() { + + var points = this.points, + minX = min(points, 'x') || 0, + minY = min(points, 'y') || 0, + maxX = max(points, 'x') || 0, + maxY = max(points, 'y') || 0, + width = (maxX - minX), + height = (maxY - minY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + points: this.points.concat() + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y, + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + + for (var i = 0, len = this.points.length; i < len; i++) { + points.push( + toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', + toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ' + ); + } + return [ + '<' + this.type + ' ', 'COMMON_PARTS', + 'points="', points.join(''), + '" />\n' + ]; + }, + /* _TO_SVG_END_ */ + + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + commonRender: function(ctx) { + var point, len = this.points.length, + x = this.pathOffset.x, + y = this.pathOffset.y; + + if (!len || isNaN(this.points[len - 1].y)) { + // do not draw if no points or odd points + // NaN comes from parseFloat of a empty string in parser + return false; + } + ctx.beginPath(); + ctx.moveTo(this.points[0].x - x, this.points[0].y - y); + for (var i = 0; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x - x, point.y - y); + } + return true; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + if (!this.commonRender(ctx)) { + return; + } + this._renderPaintInOrder(ctx); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.get('points').length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) + * @static + * @memberOf fabric.Polyline + * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns fabric.Polyline instance from an SVG element + * @static + * @memberOf fabric.Polyline + * @param {SVGElement} element Element to parser + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Polyline.fromElementGenerator = function(_class) { + return function(element, callback, options) { + if (!element) { + return callback(null); + } + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric[_class].ATTRIBUTE_NAMES); + parsedAttributes.fromSVG = true; + callback(new fabric[_class](points, extend(parsedAttributes, options))); + }; + }; + + fabric.Polyline.fromElement = fabric.Polyline.fromElementGenerator('Polyline'); + + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polyline instance from an object representation + * @static + * @memberOf fabric.Polyline + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + */ + fabric.Polyline.fromObject = function(object, callback) { + return fabric.Object._fromObject('Polyline', object, callback, 'points'); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Polygon) { + fabric.warn('fabric.Polygon is already defined'); + return; + } + + /** + * Polygon class + * @class fabric.Polygon + * @extends fabric.Polyline + * @see {@link fabric.Polygon#initialize} for constructor definition + */ + fabric.Polygon = fabric.util.createClass(fabric.Polyline, /** @lends fabric.Polygon.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polygon', + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + if (!this.commonRender(ctx)) { + return; + } + ctx.closePath(); + this._renderPaintInOrder(ctx); + }, + + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) + * @static + * @memberOf fabric.Polygon + * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement + */ + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns {@link fabric.Polygon} instance from an SVG element + * @static + * @memberOf fabric.Polygon + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Polygon.fromElement = fabric.Polyline.fromElementGenerator('Polygon'); + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polygon instance from an object representation + * @static + * @memberOf fabric.Polygon + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + * @return {void} + */ + fabric.Polygon.fromObject = function(object, callback) { + fabric.Object._fromObject('Polygon', object, callback, 'points'); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + min = fabric.util.array.min, + max = fabric.util.array.max, + extend = fabric.util.object.extend, + _toString = Object.prototype.toString, + toFixed = fabric.util.toFixed; + + if (fabric.Path) { + fabric.warn('fabric.Path is already defined'); + return; + } + + /** + * Path class + * @class fabric.Path + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} + * @see {@link fabric.Path#initialize} for constructor definition + */ + fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path', + + /** + * Array of path points + * @type Array + * @default + */ + path: null, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('path', 'fillRule'), + + stateProperties: fabric.Object.prototype.stateProperties.concat('path'), + + /** + * Constructor + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + * @return {fabric.Path} thisArg + */ + initialize: function(path, options) { + options = options || { }; + this.callSuper('initialize', options); + if (!path) { + path = []; + } + + var fromArray = _toString.call(path) === '[object Array]'; + + this.path = fabric.util.makePathSimpler( + fromArray ? path : fabric.util.parsePath(path) + ); + + if (!this.path) { + return; + } + fabric.Polyline.prototype._setPositionDimensions.call(this, options); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _renderPathCommands: function(ctx) { + var current, // current instruction + subpathStartX = 0, + subpathStartY = 0, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + l = -this.pathOffset.x, + t = -this.pathOffset.y; + + ctx.beginPath(); + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo( + current[1] + l, + current[2] + t, + controlX + l, + controlY + t, + x + l, + y + t + ); + break; + + case 'Q': // quadraticCurveTo, absolute + ctx.quadraticCurveTo( + current[1] + l, + current[2] + t, + current[3] + l, + current[4] + t + ); + x = current[3]; + y = current[4]; + controlX = current[1]; + controlY = current[2]; + break; + + case 'z': + case 'Z': + x = subpathStartX; + y = subpathStartY; + ctx.closePath(); + break; + } + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _render: function(ctx) { + this._renderPathCommands(ctx); + this._renderPaintInOrder(ctx); + }, + + /** + * Returns string representation of an instance + * @return {String} string representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + path: this.path.map(function(item) { return item.slice(); }), + }); + }, + + /** + * Returns dataless object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(['sourcePath'].concat(propertiesToInclude)); + if (o.sourcePath) { + delete o.path; + } + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var path = fabric.util.joinPath(this.path); + return [ + '\n' + ]; + }, + + _getOffsetTransform: function() { + var digits = fabric.Object.NUM_FRACTION_DIGITS; + return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' + + toFixed(-this.pathOffset.y, digits) + ')'; + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + var additionalTransform = this._getOffsetTransform(); + return '\t' + this._createBaseClipPathSVGMarkup( + this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform } + ); + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var additionalTransform = this._getOffsetTransform(); + return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform }); + }, + /* _TO_SVG_END_ */ + + /** + * Returns number representation of an instance complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.path.length; + }, + + /** + * @private + */ + _calcDimensions: function() { + + var aX = [], + aY = [], + current, // current instruction + subpathStartX = 0, + subpathStartY = 0, + x = 0, // current x + y = 0, // current y + bounds; + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + bounds = []; + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + bounds = []; + break; + + case 'C': // bezierCurveTo, absolute + bounds = fabric.util.getBoundsOfCurve(x, y, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + x = current[5]; + y = current[6]; + break; + + case 'Q': // quadraticCurveTo, absolute + bounds = fabric.util.getBoundsOfCurve(x, y, + current[1], + current[2], + current[1], + current[2], + current[3], + current[4] + ); + x = current[3]; + y = current[4]; + break; + + case 'z': + case 'Z': + x = subpathStartX; + y = subpathStartY; + break; + } + bounds.forEach(function (point) { + aX.push(point.x); + aY.push(point.y); + }); + aX.push(x); + aY.push(y); + } + + var minX = min(aX) || 0, + minY = min(aY) || 0, + maxX = max(aX) || 0, + maxY = max(aY) || 0, + deltaX = maxX - minX, + deltaY = maxY - minY; + + return { + left: minX, + top: minY, + width: deltaX, + height: deltaY + }; + } + }); + + /** + * Creates an instance of fabric.Path from an object + * @static + * @memberOf fabric.Path + * @param {Object} object + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + */ + fabric.Path.fromObject = function(object, callback) { + if (typeof object.sourcePath === 'string') { + var pathUrl = object.sourcePath; + fabric.loadSVGFromURL(pathUrl, function (elements) { + var path = elements[0]; + path.setOptions(object); + callback && callback(path); + }); + } + else { + fabric.Object._fromObject('Path', object, callback, 'path'); + } + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) + * @static + * @memberOf fabric.Path + * @see http://www.w3.org/TR/SVG/paths.html#PathElement + */ + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); + + /** + * Creates an instance of fabric.Path from an SVG element + * @static + * @memberOf fabric.Path + * @param {SVGElement} element to parse + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + * @param {Object} [options] Options object + * @param {Function} [callback] Options callback invoked after parsing is finished + */ + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + parsedAttributes.fromSVG = true; + callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + min = fabric.util.array.min, + max = fabric.util.array.max; + + if (fabric.Group) { + return; + } + + /** + * Group class + * @class fabric.Group + * @extends fabric.Object + * @mixes fabric.Collection + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} + * @see {@link fabric.Group#initialize} for constructor definition + */ + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'group', + + /** + * Width of stroke + * @type Number + * @default + */ + strokeWidth: 0, + + /** + * Indicates if click, mouseover, mouseout events & hoverCursor should also check for subtargets + * @type Boolean + * @default + */ + subTargetCheck: false, + + /** + * Groups are container, do not render anything on theyr own, ence no cache properties + * @type Array + * @default + */ + cacheProperties: [], + + /** + * setOnGroup is a method used for TextBox that is no more used since 2.0.0 The behavior is still + * available setting this boolean to true. + * @type Boolean + * @since 2.0.0 + * @default + */ + useSetOnGroup: false, + + /** + * Constructor + * @param {Object} objects Group objects + * @param {Object} [options] Options object + * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. + * @return {Object} thisArg + */ + initialize: function(objects, options, isAlreadyGrouped) { + options = options || {}; + this._objects = []; + // if objects enclosed in a group have been grouped already, + // we cannot change properties of objects. + // Thus we need to set options to group without objects, + isAlreadyGrouped && this.callSuper('initialize', options); + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + if (!isAlreadyGrouped) { + var center = options && options.centerPoint; + // we want to set origins before calculating the bounding box. + // so that the topleft can be set with that in mind. + // if specific top and left are passed, are overwritten later + // with the callSuper('initialize', options) + if (options.originX !== undefined) { + this.originX = options.originX; + } + if (options.originY !== undefined) { + this.originY = options.originY; + } + // if coming from svg i do not want to calc bounds. + // i assume width and height are passed along options + center || this._calcBounds(); + this._updateObjectsCoords(center); + delete options.centerPoint; + this.callSuper('initialize', options); + } + else { + this._updateObjectsACoords(); + } + + this.setCoords(); + }, + + /** + * @private + */ + _updateObjectsACoords: function() { + var skipControls = true; + for (var i = this._objects.length; i--; ){ + this._objects[i].setCoords(skipControls); + } + }, + + /** + * @private + * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change + */ + _updateObjectsCoords: function(center) { + var center = center || this.getCenterPoint(); + for (var i = this._objects.length; i--; ){ + this._updateObjectCoords(this._objects[i], center); + } + }, + + /** + * @private + * @param {Object} object + * @param {fabric.Point} center, current center of group. + */ + _updateObjectCoords: function(object, center) { + var objectLeft = object.left, + objectTop = object.top, + skipControls = true; + + object.set({ + left: objectLeft - center.x, + top: objectTop - center.y + }); + object.group = this; + object.setCoords(skipControls); + }, + + /** + * Returns string represenation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Adds an object to a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + addWithUpdate: function(object) { + var nested = !!this.group; + this._restoreObjectsState(); + fabric.util.resetObjectTransform(this); + if (object) { + if (nested) { + // if this group is inside another group, we need to pre transform the object + fabric.util.removeTransformFromObject(object, this.group.calcTransformMatrix()); + } + this._objects.push(object); + object.group = this; + object._set('canvas', this.canvas); + } + this._calcBounds(); + this._updateObjectsCoords(); + this.dirty = true; + if (nested) { + this.group.addWithUpdate(); + } + else { + this.setCoords(); + } + return this; + }, + + /** + * Removes an object from a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + removeWithUpdate: function(object) { + this._restoreObjectsState(); + fabric.util.resetObjectTransform(this); + + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + this.setCoords(); + this.dirty = true; + return this; + }, + + /** + * @private + */ + _onObjectAdded: function(object) { + this.dirty = true; + object.group = this; + object._set('canvas', this.canvas); + }, + + /** + * @private + */ + _onObjectRemoved: function(object) { + this.dirty = true; + delete object.group; + }, + + /** + * @private + */ + _set: function(key, value) { + var i = this._objects.length; + if (this.useSetOnGroup) { + while (i--) { + this._objects[i].setOnGroup(key, value); + } + } + if (key === 'canvas') { + while (i--) { + this._objects[i]._set(key, value); + } + } + fabric.Object.prototype._set.call(this, key, value); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + var _includeDefaultValues = this.includeDefaultValues; + var objsToObject = this._objects + .filter(function (obj) { + return !obj.excludeFromExport; + }) + .map(function (obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + /** + * Returns object representation of an instance, in dataless mode. + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var objsToObject, sourcePath = this.sourcePath; + if (sourcePath) { + objsToObject = sourcePath; + } + else { + var _includeDefaultValues = this.includeDefaultValues; + objsToObject = this._objects.map(function(obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toDatalessObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + } + var obj = fabric.Object.prototype.toDatalessObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + /** + * Renders instance on a given context + * @param {CanvasRenderingContext2D} ctx context to render instance on + */ + render: function(ctx) { + this._transformDone = true; + this.callSuper('render', ctx); + this._transformDone = false; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group is already cached. + * @return {Boolean} + */ + shouldCache: function() { + var ownCache = fabric.Object.prototype.shouldCache.call(this); + if (ownCache) { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].willDrawShadow()) { + this.ownCaching = false; + return false; + } + } + } + return ownCache; + }, + + /** + * Check if this object or a child object will cast a shadow + * @return {Boolean} + */ + willDrawShadow: function() { + if (fabric.Object.prototype.willDrawShadow.call(this)) { + return true; + } + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].willDrawShadow()) { + return true; + } + } + return false; + }, + + /** + * Check if this group or its parent group are caching, recursively up + * @return {Boolean} + */ + isOnACache: function() { + return this.ownCaching || (this.group && this.group.isOnACache()); + }, + + /** + * Execute the drawing operation for an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawObject: function(ctx) { + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].render(ctx); + } + this._drawClipPath(ctx); + }, + + /** + * Check if cache is dirty + */ + isCacheDirty: function(skipCanvas) { + if (this.callSuper('isCacheDirty', skipCanvas)) { + return true; + } + if (!this.statefullCache) { + return false; + } + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].isCacheDirty(true)) { + if (this._cacheCanvas) { + // if this group has not a cache canvas there is nothing to clean + var x = this.cacheWidth / this.zoomX, y = this.cacheHeight / this.zoomY; + this._cacheContext.clearRect(-x / 2, -y / 2, x, y); + } + return true; + } + } + return false; + }, + + /** + * Restores original state of each of group objects (original state is that which was before group was created). + * if the nested boolean is true, the original state will be restored just for the + * first group and not for all the group chain + * @private + * @param {Boolean} nested tell the function to restore object state up to the parent group and not more + * @return {fabric.Group} thisArg + * @chainable + */ + _restoreObjectsState: function() { + var groupMatrix = this.calcOwnMatrix(); + this._objects.forEach(function(object) { + // instead of using _this = this; + fabric.util.addTransformToObject(object, groupMatrix); + delete object.group; + object.setCoords(); + }); + return this; + }, + + /** + * Realises the transform from this group onto the supplied object + * i.e. it tells you what would happen if the supplied object was in + * the group, and then the group was destroyed. It mutates the supplied + * object. + * Warning: this method is not useful anymore, it has been kept to no break the api. + * is not used in the fabricJS codebase + * this method will be reduced to using the utility. + * @private + * @deprecated + * @param {fabric.Object} object + * @param {Array} parentMatrix parent transformation + * @return {fabric.Object} transformedObject + */ + realizeTransform: function(object, parentMatrix) { + fabric.util.addTransformToObject(object, parentMatrix); + return object; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + destroy: function() { + // when group is destroyed objects needs to get a repaint to be eventually + // displayed on canvas. + this._objects.forEach(function(object) { + object.set('dirty', true); + }); + return this._restoreObjectsState(); + }, + + /** + * make a group an active selection, remove the group from canvas + * the group has to be on canvas for this to work. + * @return {fabric.ActiveSelection} thisArg + * @chainable + */ + toActiveSelection: function() { + if (!this.canvas) { + return; + } + var objects = this._objects, canvas = this.canvas; + this._objects = []; + var options = this.toObject(); + delete options.objects; + var activeSelection = new fabric.ActiveSelection([]); + activeSelection.set(options); + activeSelection.type = 'activeSelection'; + canvas.remove(this); + objects.forEach(function(object) { + object.group = activeSelection; + object.dirty = true; + canvas.add(object); + }); + activeSelection.canvas = canvas; + activeSelection._objects = objects; + canvas._activeObject = activeSelection; + activeSelection.setCoords(); + return activeSelection; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + ungroupOnCanvas: function() { + return this._restoreObjectsState(); + }, + + /** + * Sets coordinates of all objects inside group + * @return {fabric.Group} thisArg + * @chainable + */ + setObjectsCoords: function() { + var skipControls = true; + this.forEachObject(function(object) { + object.setCoords(skipControls); + }); + return this; + }, + + /** + * @private + */ + _calcBounds: function(onlyWidthHeight) { + var aX = [], + aY = [], + o, prop, coords, + props = ['tr', 'br', 'bl', 'tl'], + i = 0, iLen = this._objects.length, + j, jLen = props.length; + + for ( ; i < iLen; ++i) { + o = this._objects[i]; + coords = o.calcACoords(); + for (j = 0; j < jLen; j++) { + prop = props[j]; + aX.push(coords[prop].x); + aY.push(coords[prop].y); + } + o.aCoords = coords; + } + + this._getBounds(aX, aY, onlyWidthHeight); + }, + + /** + * @private + */ + _getBounds: function(aX, aY, onlyWidthHeight) { + var minXY = new fabric.Point(min(aX), min(aY)), + maxXY = new fabric.Point(max(aX), max(aY)), + top = minXY.y || 0, left = minXY.x || 0, + width = (maxXY.x - minXY.x) || 0, + height = (maxXY.y - minXY.y) || 0; + this.width = width; + this.height = height; + if (!onlyWidthHeight) { + // the bounding box always finds the topleft most corner. + // whatever is the group origin, we set up here the left/top position. + this.setPositionByOrigin({ x: left, y: top }, 'left', 'top'); + } + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG: function(reviver) { + var svgString = ['\n']; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t\t', this._objects[i].toSVG(reviver)); + } + svgString.push('\n'); + return svgString; + }, + + /** + * Returns styles-string for svg-export, specific version for group + * @return {String} + */ + getSvgStyles: function() { + var opacity = typeof this.opacity !== 'undefined' && this.opacity !== 1 ? + 'opacity: ' + this.opacity + ';' : '', + visibility = this.visible ? '' : ' visibility: hidden;'; + return [ + opacity, + this.getSvgFilter(), + visibility + ].join(''); + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + var svgString = []; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t', this._objects[i].toClipPathSVG(reviver)); + } + + return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver }); + }, + /* _TO_SVG_END_ */ + }); + + /** + * Returns {@link fabric.Group} instance from an object representation + * @static + * @memberOf fabric.Group + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an group instance is created + */ + fabric.Group.fromObject = function(object, callback) { + var objects = object.objects, + options = fabric.util.object.clone(object, true); + delete options.objects; + if (typeof objects === 'string') { + // it has to be an url or something went wrong. + fabric.loadSVGFromURL(objects, function (elements) { + var group = fabric.util.groupSVGElements(elements, object, objects); + group.set(options); + callback && callback(group); + }); + return; + } + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) { + var options = fabric.util.object.clone(object, true); + options.clipPath = enlivedClipPath[0]; + delete options.objects; + callback && callback(new fabric.Group(enlivenedObjects, options, true)); + }); + }); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.ActiveSelection) { + return; + } + + /** + * Group class + * @class fabric.ActiveSelection + * @extends fabric.Group + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} + * @see {@link fabric.ActiveSelection#initialize} for constructor definition + */ + fabric.ActiveSelection = fabric.util.createClass(fabric.Group, /** @lends fabric.ActiveSelection.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'activeSelection', + + /** + * Constructor + * @param {Object} objects ActiveSelection objects + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(objects, options) { + options = options || {}; + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + if (options.originX) { + this.originX = options.originX; + } + if (options.originY) { + this.originY = options.originY; + } + this._calcBounds(); + this._updateObjectsCoords(); + fabric.Object.prototype.initialize.call(this, options); + this.setCoords(); + }, + + /** + * Change te activeSelection to a normal group, + * High level function that automatically adds it to canvas as + * active object. no events fired. + * @since 2.0.0 + * @return {fabric.Group} + */ + toGroup: function() { + var objects = this._objects.concat(); + this._objects = []; + var options = fabric.Object.prototype.toObject.call(this); + var newGroup = new fabric.Group([]); + delete options.type; + newGroup.set(options); + objects.forEach(function(object) { + object.canvas.remove(object); + object.group = newGroup; + }); + newGroup._objects = objects; + if (!this.canvas) { + return newGroup; + } + var canvas = this.canvas; + canvas.add(newGroup); + canvas._activeObject = newGroup; + newGroup.setCoords(); + return newGroup; + }, + + /** + * If returns true, deselection is cancelled. + * @since 2.0.0 + * @return {Boolean} [cancel] + */ + onDeselect: function() { + this.destroy(); + return false; + }, + + /** + * Returns string representation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * objectCaching is a global flag, wins over everything + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * @return {Boolean} + */ + shouldCache: function() { + return false; + }, + + /** + * Check if this group or its parent group are caching, recursively up + * @return {Boolean} + */ + isOnACache: function() { + return false; + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [styleOverride] properties to override the object style + * @param {Object} [childrenOverride] properties to override the children overrides + */ + _renderControls: function(ctx, styleOverride, childrenOverride) { + ctx.save(); + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + this.callSuper('_renderControls', ctx, styleOverride); + childrenOverride = childrenOverride || { }; + if (typeof childrenOverride.hasControls === 'undefined') { + childrenOverride.hasControls = false; + } + childrenOverride.forActiveSelection = true; + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx, childrenOverride); + } + ctx.restore(); + }, + }); + + /** + * Returns {@link fabric.ActiveSelection} instance from an object representation + * @static + * @memberOf fabric.ActiveSelection + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an ActiveSelection instance is created + */ + fabric.ActiveSelection.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.ActiveSelection(enlivenedObjects, object, true)); + }); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var extend = fabric.util.object.extend; + + if (!global.fabric) { + global.fabric = { }; + } + + if (global.fabric.Image) { + fabric.warn('fabric.Image is already defined.'); + return; + } + + /** + * Image class + * @class fabric.Image + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#images} + * @see {@link fabric.Image#initialize} for constructor definition + */ + fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'image', + + /** + * Width of a stroke. + * For image quality a stroke multiple of 2 gives better results. + * @type Number + * @default + */ + strokeWidth: 0, + + /** + * When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`. + * This allows for relative urls as image src. + * @since 2.7.0 + * @type Boolean + * @default + */ + srcFromAttribute: false, + + /** + * private + * contains last value of scaleX to detect + * if the Image got resized after the last Render + * @type Number + */ + _lastScaleX: 1, + + /** + * private + * contains last value of scaleY to detect + * if the Image got resized after the last Render + * @type Number + */ + _lastScaleY: 1, + + /** + * private + * contains last value of scaling applied by the apply filter chain + * @type Number + */ + _filterScalingX: 1, + + /** + * private + * contains last value of scaling applied by the apply filter chain + * @type Number + */ + _filterScalingY: 1, + + /** + * minimum scale factor under which any resizeFilter is triggered to resize the image + * 0 will disable the automatic resize. 1 will trigger automatically always. + * number bigger than 1 are not implemented yet. + * @type Number + */ + minimumScaleTrigger: 0.5, + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat('cropX', 'cropY'), + + /** + * List of properties to consider when checking if cache needs refresh + * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single + * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty + * and refreshed at the next render + * @type Array + */ + cacheProperties: fabric.Object.prototype.cacheProperties.concat('cropX', 'cropY'), + + /** + * key used to retrieve the texture representing this image + * @since 2.0.0 + * @type String + * @default + */ + cacheKey: '', + + /** + * Image crop in pixels from original image size. + * @since 2.0.0 + * @type Number + * @default + */ + cropX: 0, + + /** + * Image crop in pixels from original image size. + * @since 2.0.0 + * @type Number + * @default + */ + cropY: 0, + + /** + * Indicates whether this canvas will use image smoothing when painting this image. + * Also influence if the cacheCanvas for this image uses imageSmoothing + * @since 4.0.0-beta.11 + * @type Boolean + * @default + */ + imageSmoothing: true, + + /** + * Constructor + * Image can be initialized with any canvas drawable or a string. + * The string should be a url and will be loaded as an image. + * Canvas and Image element work out of the box, while videos require extra code to work. + * Please check video element events for seeking. + * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String} element Image element + * @param {Object} [options] Options object + * @param {function} [callback] callback function to call after eventual filters applied. + * @return {fabric.Image} thisArg + */ + initialize: function(element, options) { + options || (options = { }); + this.filters = []; + this.cacheKey = 'texture' + fabric.Object.__uid++; + this.callSuper('initialize', options); + this._initElement(element, options); + }, + + /** + * Returns image element which this instance if based on + * @return {HTMLImageElement} Image element + */ + getElement: function() { + return this._element || {}; + }, + + /** + * Sets image element for this instance to a specified one. + * If filters defined they are applied to new image. + * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. + * @param {HTMLImageElement} element + * @param {Object} [options] Options object + * @return {fabric.Image} thisArg + * @chainable + */ + setElement: function(element, options) { + this.removeTexture(this.cacheKey); + this.removeTexture(this.cacheKey + '_filtered'); + this._element = element; + this._originalElement = element; + this._initConfig(options); + if (this.filters.length !== 0) { + this.applyFilters(); + } + // resizeFilters work on the already filtered copy. + // we need to apply resizeFilters AFTER normal filters. + // applyResizeFilters is run more often than normal filters + // and is triggered by user interactions rather than dev code + if (this.resizeFilter) { + this.applyResizeFilters(); + } + return this; + }, + + /** + * Delete a single texture if in webgl mode + */ + removeTexture: function(key) { + var backend = fabric.filterBackend; + if (backend && backend.evictCachesForKey) { + backend.evictCachesForKey(key); + } + }, + + /** + * Delete textures, reference to elements and eventually JSDOM cleanup + */ + dispose: function() { + this.removeTexture(this.cacheKey); + this.removeTexture(this.cacheKey + '_filtered'); + this._cacheContext = undefined; + ['_originalElement', '_element', '_filteredEl', '_cacheCanvas'].forEach((function(element) { + fabric.util.cleanUpJsdomNode(this[element]); + this[element] = undefined; + }).bind(this)); + }, + + /** + * Get the crossOrigin value (of the corresponding image element) + */ + getCrossOrigin: function() { + return this._originalElement && (this._originalElement.crossOrigin || null); + }, + + /** + * Returns original size of an image + * @return {Object} Object with "width" and "height" properties + */ + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.naturalWidth || element.width, + height: element.naturalHeight || element.height + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _stroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + var w = this.width / 2, h = this.height / 2; + ctx.beginPath(); + ctx.moveTo(-w, -h); + ctx.lineTo(w, -h); + ctx.lineTo(w, h); + ctx.lineTo(-w, h); + ctx.lineTo(-w, -h); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var filters = []; + + this.filters.forEach(function(filterObj) { + if (filterObj) { + filters.push(filterObj.toObject()); + } + }); + var object = extend( + this.callSuper( + 'toObject', + ['cropX', 'cropY'].concat(propertiesToInclude) + ), { + src: this.getSrc(), + crossOrigin: this.getCrossOrigin(), + filters: filters, + }); + if (this.resizeFilter) { + object.resizeFilter = this.resizeFilter.toObject(); + } + return object; + }, + + /** + * Returns true if an image has crop applied, inspecting values of cropX,cropY,width,height. + * @return {Boolean} + */ + hasCrop: function() { + return this.cropX || this.cropY || this.width < this._element.width || this.height < this._element.height; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var svgString = [], imageMarkup = [], strokeSvg, element = this._element, + x = -this.width / 2, y = -this.height / 2, clipPath = '', imageRendering = ''; + if (!element) { + return []; + } + if (this.hasCrop()) { + var clipPathId = fabric.Object.__uid++; + svgString.push( + '\n', + '\t\n', + '\n' + ); + clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; + } + if (!this.imageSmoothing) { + imageRendering = '" image-rendering="optimizeSpeed'; + } + imageMarkup.push('\t\n'); + + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + strokeSvg = [ + '\t\n' + ]; + this.fill = origFill; + } + if (this.paintFirst !== 'fill') { + svgString = svgString.concat(strokeSvg, imageMarkup); + } + else { + svgString = svgString.concat(imageMarkup, strokeSvg); + } + return svgString; + }, + /* _TO_SVG_END_ */ + + /** + * Returns source of an image + * @param {Boolean} filtered indicates if the src is needed for svg + * @return {String} Source of an image + */ + getSrc: function(filtered) { + var element = filtered ? this._element : this._originalElement; + if (element) { + if (element.toDataURL) { + return element.toDataURL(); + } + + if (this.srcFromAttribute) { + return element.getAttribute('src'); + } + else { + return element.src; + } + } + else { + return this.src || ''; + } + }, + + /** + * Sets source of an image + * @param {String} src Source string (URL) + * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) + * @param {Object} [options] Options object + * @param {String} [options.crossOrigin] crossOrigin value (one of "", "anonymous", "use-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @return {fabric.Image} thisArg + * @chainable + */ + setSrc: function(src, callback, options) { + fabric.util.loadImage(src, function(img, isError) { + this.setElement(img, options); + this._setWidthHeight(); + callback && callback(this, isError); + }, this, options && options.crossOrigin); + return this; + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of an instance + */ + toString: function() { + return '#'; + }, + + applyResizeFilters: function() { + var filter = this.resizeFilter, + minimumScale = this.minimumScaleTrigger, + objectScale = this.getTotalObjectScaling(), + scaleX = objectScale.scaleX, + scaleY = objectScale.scaleY, + elementToFilter = this._filteredEl || this._originalElement; + if (this.group) { + this.set('dirty', true); + } + if (!filter || (scaleX > minimumScale && scaleY > minimumScale)) { + this._element = elementToFilter; + this._filterScalingX = 1; + this._filterScalingY = 1; + this._lastScaleX = scaleX; + this._lastScaleY = scaleY; + return; + } + if (!fabric.filterBackend) { + fabric.filterBackend = fabric.initFilterBackend(); + } + var canvasEl = fabric.util.createCanvasElement(), + cacheKey = this._filteredEl ? (this.cacheKey + '_filtered') : this.cacheKey, + sourceWidth = elementToFilter.width, sourceHeight = elementToFilter.height; + canvasEl.width = sourceWidth; + canvasEl.height = sourceHeight; + this._element = canvasEl; + this._lastScaleX = filter.scaleX = scaleX; + this._lastScaleY = filter.scaleY = scaleY; + fabric.filterBackend.applyFilters( + [filter], elementToFilter, sourceWidth, sourceHeight, this._element, cacheKey); + this._filterScalingX = canvasEl.width / this._originalElement.width; + this._filterScalingY = canvasEl.height / this._originalElement.height; + }, + + /** + * Applies filters assigned to this image (from "filters" array) or from filter param + * @method applyFilters + * @param {Array} filters to be applied + * @param {Boolean} forResizing specify if the filter operation is a resize operation + * @return {thisArg} return the fabric.Image object + * @chainable + */ + applyFilters: function(filters) { + + filters = filters || this.filters || []; + filters = filters.filter(function(filter) { return filter && !filter.isNeutralState(); }); + this.set('dirty', true); + + // needs to clear out or WEBGL will not resize correctly + this.removeTexture(this.cacheKey + '_filtered'); + + if (filters.length === 0) { + this._element = this._originalElement; + this._filteredEl = null; + this._filterScalingX = 1; + this._filterScalingY = 1; + return this; + } + + var imgElement = this._originalElement, + sourceWidth = imgElement.naturalWidth || imgElement.width, + sourceHeight = imgElement.naturalHeight || imgElement.height; + + if (this._element === this._originalElement) { + // if the element is the same we need to create a new element + var canvasEl = fabric.util.createCanvasElement(); + canvasEl.width = sourceWidth; + canvasEl.height = sourceHeight; + this._element = canvasEl; + this._filteredEl = canvasEl; + } + else { + // clear the existing element to get new filter data + // also dereference the eventual resized _element + this._element = this._filteredEl; + this._filteredEl.getContext('2d').clearRect(0, 0, sourceWidth, sourceHeight); + // we also need to resize again at next renderAll, so remove saved _lastScaleX/Y + this._lastScaleX = 1; + this._lastScaleY = 1; + } + if (!fabric.filterBackend) { + fabric.filterBackend = fabric.initFilterBackend(); + } + fabric.filterBackend.applyFilters( + filters, this._originalElement, sourceWidth, sourceHeight, this._element, this.cacheKey); + if (this._originalElement.width !== this._element.width || + this._originalElement.height !== this._element.height) { + this._filterScalingX = this._element.width / this._originalElement.width; + this._filterScalingY = this._element.height / this._originalElement.height; + } + return this; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + fabric.util.setImageSmoothing(ctx, this.imageSmoothing); + if (this.isMoving !== true && this.resizeFilter && this._needsResize()) { + this.applyResizeFilters(); + } + this._stroke(ctx); + this._renderPaintInOrder(ctx); + }, + + /** + * Paint the cached copy of the object on the target context. + * it will set the imageSmoothing for the draw operation + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawCacheOnCanvas: function(ctx) { + fabric.util.setImageSmoothing(ctx, this.imageSmoothing); + fabric.Object.prototype.drawCacheOnCanvas.call(this, ctx); + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * This is the special image version where we would like to avoid caching where possible. + * Essentially images do not benefit from caching. They may require caching, and in that + * case we do it. Also caching an image usually ends in a loss of details. + * A full performance audit should be done. + * @return {Boolean} + */ + shouldCache: function() { + return this.needsItsOwnCache(); + }, + + _renderFill: function(ctx) { + var elementToDraw = this._element; + if (!elementToDraw) { + return; + } + var scaleX = this._filterScalingX, scaleY = this._filterScalingY, + w = this.width, h = this.height, min = Math.min, max = Math.max, + // crop values cannot be lesser than 0. + cropX = max(this.cropX, 0), cropY = max(this.cropY, 0), + elWidth = elementToDraw.naturalWidth || elementToDraw.width, + elHeight = elementToDraw.naturalHeight || elementToDraw.height, + sX = cropX * scaleX, + sY = cropY * scaleY, + // the width height cannot exceed element width/height, starting from the crop offset. + sW = min(w * scaleX, elWidth - sX), + sH = min(h * scaleY, elHeight - sY), + x = -w / 2, y = -h / 2, + maxDestW = min(w, elWidth / scaleX - cropX), + maxDestH = min(h, elHeight / scaleY - cropY); + + elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, maxDestW, maxDestH); + }, + + /** + * needed to check if image needs resize + * @private + */ + _needsResize: function() { + var scale = this.getTotalObjectScaling(); + return (scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY); + }, + + /** + * @private + */ + _resetWidthHeight: function() { + this.set(this.getOriginalSize()); + }, + + /** + * The Image class's initialization method. This method is automatically + * called by the constructor. + * @private + * @param {HTMLImageElement|String} element The element representing the image + * @param {Object} [options] Options object + */ + _initElement: function(element, options) { + this.setElement(fabric.util.getById(element), options); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initConfig: function(options) { + options || (options = { }); + this.setOptions(options); + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Array} filters to be initialized + * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created + */ + _initFilters: function(filters, callback) { + if (filters && filters.length) { + fabric.util.enlivenObjects(filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, 'fabric.Image.filters'); + } + else { + callback && callback(); + } + }, + + /** + * @private + * Set the width and the height of the image object, using the element or the + * options. + * @param {Object} [options] Object with width/height properties + */ + _setWidthHeight: function(options) { + options || (options = { }); + var el = this.getElement(); + this.width = options.width || el.naturalWidth || el.width || 0; + this.height = options.height || el.naturalHeight || el.height || 0; + }, + + /** + * Calculate offset for center and scale factor for the image in order to respect + * the preserveAspectRatio attribute + * @private + * @return {Object} + */ + parsePreserveAspectRatioAttribute: function() { + var pAR = fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio || ''), + rWidth = this._element.width, rHeight = this._element.height, + scaleX = 1, scaleY = 1, offsetLeft = 0, offsetTop = 0, cropX = 0, cropY = 0, + offset, pWidth = this.width, pHeight = this.height, parsedAttributes = { width: pWidth, height: pHeight }; + if (pAR && (pAR.alignX !== 'none' || pAR.alignY !== 'none')) { + if (pAR.meetOrSlice === 'meet') { + scaleX = scaleY = fabric.util.findScaleToFit(this._element, parsedAttributes); + offset = (pWidth - rWidth * scaleX) / 2; + if (pAR.alignX === 'Min') { + offsetLeft = -offset; + } + if (pAR.alignX === 'Max') { + offsetLeft = offset; + } + offset = (pHeight - rHeight * scaleY) / 2; + if (pAR.alignY === 'Min') { + offsetTop = -offset; + } + if (pAR.alignY === 'Max') { + offsetTop = offset; + } + } + if (pAR.meetOrSlice === 'slice') { + scaleX = scaleY = fabric.util.findScaleToCover(this._element, parsedAttributes); + offset = rWidth - pWidth / scaleX; + if (pAR.alignX === 'Mid') { + cropX = offset / 2; + } + if (pAR.alignX === 'Max') { + cropX = offset; + } + offset = rHeight - pHeight / scaleY; + if (pAR.alignY === 'Mid') { + cropY = offset / 2; + } + if (pAR.alignY === 'Max') { + cropY = offset; + } + rWidth = pWidth / scaleX; + rHeight = pHeight / scaleY; + } + } + else { + scaleX = pWidth / rWidth; + scaleY = pHeight / rHeight; + } + return { + width: rWidth, + height: rHeight, + scaleX: scaleX, + scaleY: scaleY, + offsetLeft: offsetLeft, + offsetTop: offsetTop, + cropX: cropX, + cropY: cropY + }; + } + }); + + /** + * Default CSS class name for canvas + * @static + * @type String + * @default + */ + fabric.Image.CSS_CANVAS = 'canvas-img'; + + /** + * Alias for getSrc + * @static + */ + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + + /** + * Creates an instance of fabric.Image from its object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} callback Callback to invoke when an image instance is created + */ + fabric.Image.fromObject = function(_object, callback) { + var object = fabric.util.object.clone(_object); + fabric.util.loadImage(object.src, function(img, isError) { + if (isError) { + callback && callback(null, true); + return; + } + fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) { + object.filters = filters || []; + fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) { + object.resizeFilter = resizeFilters[0]; + fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { + object.clipPath = enlivedProps[0]; + var image = new fabric.Image(img, object); + callback(image, false); + }); + }); + }); + }, null, object.crossOrigin); + }; + + /** + * Creates an instance of fabric.Image from an URL string + * @static + * @param {String} url URL to create an image from + * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument). Second argument is a boolean indicating if an error occurred or not. + * @param {Object} [imgOptions] Options object + */ + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img, isError) { + callback && callback(new fabric.Image(img, imgOptions), isError); + }, null, imgOptions && imgOptions.crossOrigin); + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) + * @static + * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} + */ + fabric.Image.ATTRIBUTE_NAMES = + fabric.SHARED_ATTRIBUTES.concat( + 'x y width height preserveAspectRatio xlink:href crossOrigin image-rendering'.split(' ') + ); + + /** + * Returns {@link fabric.Image} instance from an SVG element + * @static + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @param {Function} callback Callback to execute when fabric.Image object is created + * @return {fabric.Image} Instance of fabric.Image + */ + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, + extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + +})( true ? exports : 0); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * @private + * @return {Number} angle value + */ + _getAngleValueForStraighten: function() { + var angle = this.angle % 360; + if (angle > 0) { + return Math.round((angle - 1) / 90) * 90; + } + return Math.round(angle / 90) * 90; + }, + + /** + * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) + * @return {fabric.Object} thisArg + * @chainable + */ + straighten: function() { + this.rotate(this._getAngleValueForStraighten()); + return this; + }, + + /** + * Same as {@link fabric.Object.prototype.straighten} but with animation + * @param {Object} callbacks Object with callback functions + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Object} thisArg + * @chainable + */ + fxStraighten: function(callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: this.get('angle'), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.rotate(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + }); + + return this; + } +}); + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Straightens object, then rerenders canvas + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + straightenObject: function (object) { + object.straighten(); + this.requestRenderAll(); + return this; + }, + + /** + * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxStraightenObject: function (object) { + object.fxStraighten({ + onChange: this.requestRenderAllBound + }); + return this; + } +}); + + +(function() { + + 'use strict'; + + /** + * Tests if webgl supports certain precision + * @param {WebGL} Canvas WebGL context to test on + * @param {String} Precision to test can be any of following: 'lowp', 'mediump', 'highp' + * @returns {Boolean} Whether the user's browser WebGL supports given precision. + */ + function testPrecision(gl, precision){ + var fragmentSource = 'precision ' + precision + ' float;\nvoid main(){}'; + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + return false; + } + return true; + } + + /** + * Indicate whether this filtering backend is supported by the user's browser. + * @param {Number} tileSize check if the tileSize is supported + * @returns {Boolean} Whether the user's browser supports WebGL. + */ + fabric.isWebglSupported = function(tileSize) { + if (fabric.isLikelyNode) { + return false; + } + tileSize = tileSize || fabric.WebglFilterBackend.prototype.tileSize; + var canvas = document.createElement('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + var isSupported = false; + // eslint-disable-next-line + if (gl) { + fabric.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); + isSupported = fabric.maxTextureSize >= tileSize; + var precisions = ['highp', 'mediump', 'lowp']; + for (var i = 0; i < 3; i++){ + if (testPrecision(gl, precisions[i])){ + fabric.webGlPrecision = precisions[i]; + break; + }; + } + } + this.isSupported = isSupported; + return isSupported; + }; + + fabric.WebglFilterBackend = WebglFilterBackend; + + /** + * WebGL filter backend. + */ + function WebglFilterBackend(options) { + if (options && options.tileSize) { + this.tileSize = options.tileSize; + } + this.setupGLContext(this.tileSize, this.tileSize); + this.captureGPUInfo(); + }; + + WebglFilterBackend.prototype = /** @lends fabric.WebglFilterBackend.prototype */ { + + tileSize: 2048, + + /** + * Experimental. This object is a sort of repository of help layers used to avoid + * of recreating them during frequent filtering. If you are previewing a filter with + * a slider you probably do not want to create help layers every filter step. + * in this object there will be appended some canvases, created once, resized sometimes + * cleared never. Clearing is left to the developer. + **/ + resources: { + + }, + + /** + * Setup a WebGL context suitable for filtering, and bind any needed event handlers. + */ + setupGLContext: function(width, height) { + this.dispose(); + this.createWebGLCanvas(width, height); + // eslint-disable-next-line + this.aPosition = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]); + this.chooseFastestCopyGLTo2DMethod(width, height); + }, + + /** + * Pick a method to copy data from GL context to 2d canvas. In some browsers using + * putImageData is faster than drawImage for that specific operation. + */ + chooseFastestCopyGLTo2DMethod: function(width, height) { + var canMeasurePerf = typeof window.performance !== 'undefined', canUseImageData; + try { + new ImageData(1, 1); + canUseImageData = true; + } + catch (e) { + canUseImageData = false; + } + // eslint-disable-next-line no-undef + var canUseArrayBuffer = typeof ArrayBuffer !== 'undefined'; + // eslint-disable-next-line no-undef + var canUseUint8Clamped = typeof Uint8ClampedArray !== 'undefined'; + + if (!(canMeasurePerf && canUseImageData && canUseArrayBuffer && canUseUint8Clamped)) { + return; + } + + var targetCanvas = fabric.util.createCanvasElement(); + // eslint-disable-next-line no-undef + var imageBuffer = new ArrayBuffer(width * height * 4); + if (fabric.forceGLPutImageData) { + this.imageBuffer = imageBuffer; + this.copyGLTo2D = copyGLTo2DPutImageData; + return; + } + var testContext = { + imageBuffer: imageBuffer, + destinationWidth: width, + destinationHeight: height, + targetCanvas: targetCanvas + }; + var startTime, drawImageTime, putImageDataTime; + targetCanvas.width = width; + targetCanvas.height = height; + + startTime = window.performance.now(); + copyGLTo2DDrawImage.call(testContext, this.gl, testContext); + drawImageTime = window.performance.now() - startTime; + + startTime = window.performance.now(); + copyGLTo2DPutImageData.call(testContext, this.gl, testContext); + putImageDataTime = window.performance.now() - startTime; + + if (drawImageTime > putImageDataTime) { + this.imageBuffer = imageBuffer; + this.copyGLTo2D = copyGLTo2DPutImageData; + } + else { + this.copyGLTo2D = copyGLTo2DDrawImage; + } + }, + + /** + * Create a canvas element and associated WebGL context and attaches them as + * class properties to the GLFilterBackend class. + */ + createWebGLCanvas: function(width, height) { + var canvas = fabric.util.createCanvasElement(); + canvas.width = width; + canvas.height = height; + var glOptions = { + alpha: true, + premultipliedAlpha: false, + depth: false, + stencil: false, + antialias: false + }, + gl = canvas.getContext('webgl', glOptions); + if (!gl) { + gl = canvas.getContext('experimental-webgl', glOptions); + } + if (!gl) { + return; + } + gl.clearColor(0, 0, 0, 0); + // this canvas can fire webglcontextlost and webglcontextrestored + this.canvas = canvas; + this.gl = gl; + }, + + /** + * Attempts to apply the requested filters to the source provided, drawing the filtered output + * to the provided target canvas. + * + * @param {Array} filters The filters to apply. + * @param {HTMLImageElement|HTMLCanvasElement} source The source to be filtered. + * @param {Number} width The width of the source input. + * @param {Number} height The height of the source input. + * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. + * @param {String|undefined} cacheKey A key used to cache resources related to the source. If + * omitted, caching will be skipped. + */ + applyFilters: function(filters, source, width, height, targetCanvas, cacheKey) { + var gl = this.gl; + var cachedTexture; + if (cacheKey) { + cachedTexture = this.getCachedTexture(cacheKey, source); + } + var pipelineState = { + originalWidth: source.width || source.originalWidth, + originalHeight: source.height || source.originalHeight, + sourceWidth: width, + sourceHeight: height, + destinationWidth: width, + destinationHeight: height, + context: gl, + sourceTexture: this.createTexture(gl, width, height, !cachedTexture && source), + targetTexture: this.createTexture(gl, width, height), + originalTexture: cachedTexture || + this.createTexture(gl, width, height, !cachedTexture && source), + passes: filters.length, + webgl: true, + aPosition: this.aPosition, + programCache: this.programCache, + pass: 0, + filterBackend: this, + targetCanvas: targetCanvas + }; + var tempFbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo); + filters.forEach(function(filter) { filter && filter.applyTo(pipelineState); }); + resizeCanvasIfNeeded(pipelineState); + this.copyGLTo2D(gl, pipelineState); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.deleteTexture(pipelineState.sourceTexture); + gl.deleteTexture(pipelineState.targetTexture); + gl.deleteFramebuffer(tempFbo); + targetCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); + return pipelineState; + }, + + /** + * Detach event listeners, remove references, and clean up caches. + */ + dispose: function() { + if (this.canvas) { + this.canvas = null; + this.gl = null; + } + this.clearWebGLCaches(); + }, + + /** + * Wipe out WebGL-related caches. + */ + clearWebGLCaches: function() { + this.programCache = {}; + this.textureCache = {}; + }, + + /** + * Create a WebGL texture object. + * + * Accepts specific dimensions to initialize the texture to or a source image. + * + * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. + * @param {Number} width The width to initialize the texture at. + * @param {Number} height The height to initialize the texture. + * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source for the texture data. + * @returns {WebGLTexture} + */ + createTexture: function(gl, width, height, textureImageSource) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + if (textureImageSource) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImageSource); + } + else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + return texture; + }, + + /** + * Can be optionally used to get a texture from the cache array + * + * If an existing texture is not found, a new texture is created and cached. + * + * @param {String} uniqueId A cache key to use to find an existing texture. + * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the + * texture cache entry if one does not already exist. + */ + getCachedTexture: function(uniqueId, textureImageSource) { + if (this.textureCache[uniqueId]) { + return this.textureCache[uniqueId]; + } + else { + var texture = this.createTexture( + this.gl, textureImageSource.width, textureImageSource.height, textureImageSource); + this.textureCache[uniqueId] = texture; + return texture; + } + }, + + /** + * Clear out cached resources related to a source image that has been + * filtered previously. + * + * @param {String} cacheKey The cache key provided when the source image was filtered. + */ + evictCachesForKey: function(cacheKey) { + if (this.textureCache[cacheKey]) { + this.gl.deleteTexture(this.textureCache[cacheKey]); + delete this.textureCache[cacheKey]; + } + }, + + copyGLTo2D: copyGLTo2DDrawImage, + + /** + * Attempt to extract GPU information strings from a WebGL context. + * + * Useful information when debugging or blacklisting specific GPUs. + * + * @returns {Object} A GPU info object with renderer and vendor strings. + */ + captureGPUInfo: function() { + if (this.gpuInfo) { + return this.gpuInfo; + } + var gl = this.gl, gpuInfo = { renderer: '', vendor: '' }; + if (!gl) { + return gpuInfo; + } + var ext = gl.getExtension('WEBGL_debug_renderer_info'); + if (ext) { + var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); + var vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); + if (renderer) { + gpuInfo.renderer = renderer.toLowerCase(); + } + if (vendor) { + gpuInfo.vendor = vendor.toLowerCase(); + } + } + this.gpuInfo = gpuInfo; + return gpuInfo; + }, + }; +})(); + +function resizeCanvasIfNeeded(pipelineState) { + var targetCanvas = pipelineState.targetCanvas, + width = targetCanvas.width, height = targetCanvas.height, + dWidth = pipelineState.destinationWidth, + dHeight = pipelineState.destinationHeight; + + if (width !== dWidth || height !== dHeight) { + targetCanvas.width = dWidth; + targetCanvas.height = dHeight; + } +} + +/** + * Copy an input WebGL canvas on to an output 2D canvas. + * + * The WebGL canvas is assumed to be upside down, with the top-left pixel of the + * desired output image appearing in the bottom-left corner of the WebGL canvas. + * + * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. + * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. + * @param {Object} pipelineState The 2D target canvas to copy on to. + */ +function copyGLTo2DDrawImage(gl, pipelineState) { + var glCanvas = gl.canvas, targetCanvas = pipelineState.targetCanvas, + ctx = targetCanvas.getContext('2d'); + ctx.translate(0, targetCanvas.height); // move it down again + ctx.scale(1, -1); // vertical flip + // where is my image on the big glcanvas? + var sourceY = glCanvas.height - targetCanvas.height; + ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0, + targetCanvas.width, targetCanvas.height); +} + +/** + * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData + * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra). + * + * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. + * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. + * @param {Object} pipelineState The 2D target canvas to copy on to. + */ +function copyGLTo2DPutImageData(gl, pipelineState) { + var targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext('2d'), + dWidth = pipelineState.destinationWidth, + dHeight = pipelineState.destinationHeight, + numBytes = dWidth * dHeight * 4; + + // eslint-disable-next-line no-undef + var u8 = new Uint8Array(this.imageBuffer, 0, numBytes); + // eslint-disable-next-line no-undef + var u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes); + + gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8); + var imgData = new ImageData(u8Clamped, dWidth, dHeight); + ctx.putImageData(imgData, 0, 0); +} + + +(function() { + + 'use strict'; + + var noop = function() {}; + + fabric.Canvas2dFilterBackend = Canvas2dFilterBackend; + + /** + * Canvas 2D filter backend. + */ + function Canvas2dFilterBackend() {}; + + Canvas2dFilterBackend.prototype = /** @lends fabric.Canvas2dFilterBackend.prototype */ { + evictCachesForKey: noop, + dispose: noop, + clearWebGLCaches: noop, + + /** + * Experimental. This object is a sort of repository of help layers used to avoid + * of recreating them during frequent filtering. If you are previewing a filter with + * a slider you probably do not want to create help layers every filter step. + * in this object there will be appended some canvases, created once, resized sometimes + * cleared never. Clearing is left to the developer. + **/ + resources: { + + }, + + /** + * Apply a set of filters against a source image and draw the filtered output + * to the provided destination canvas. + * + * @param {EnhancedFilter} filters The filter to apply. + * @param {HTMLImageElement|HTMLCanvasElement} sourceElement The source to be filtered. + * @param {Number} sourceWidth The width of the source input. + * @param {Number} sourceHeight The height of the source input. + * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. + */ + applyFilters: function(filters, sourceElement, sourceWidth, sourceHeight, targetCanvas) { + var ctx = targetCanvas.getContext('2d'); + ctx.drawImage(sourceElement, 0, 0, sourceWidth, sourceHeight); + var imageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); + var originalImageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); + var pipelineState = { + sourceWidth: sourceWidth, + sourceHeight: sourceHeight, + imageData: imageData, + originalEl: sourceElement, + originalImageData: originalImageData, + canvasEl: targetCanvas, + ctx: ctx, + filterBackend: this, + }; + filters.forEach(function(filter) { filter.applyTo(pipelineState); }); + if (pipelineState.imageData.width !== sourceWidth || pipelineState.imageData.height !== sourceHeight) { + targetCanvas.width = pipelineState.imageData.width; + targetCanvas.height = pipelineState.imageData.height; + } + ctx.putImageData(pipelineState.imageData, 0, 0); + return pipelineState; + }, + + }; +})(); + + +/** + * @namespace fabric.Image.filters + * @memberOf fabric.Image + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters} + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + */ +fabric.Image = fabric.Image || { }; +fabric.Image.filters = fabric.Image.filters || { }; + +/** + * Root filter class from which all filter classes inherit from + * @class fabric.Image.filters.BaseFilter + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'BaseFilter', + + /** + * Array of attributes to send with buffers. do not modify + * @private + */ + + vertexSource: 'attribute vec2 aPosition;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vTexCoord = aPosition;\n' + + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + + '}', + + fragmentSource: 'precision highp float;\n' + + 'varying vec2 vTexCoord;\n' + + 'uniform sampler2D uTexture;\n' + + 'void main() {\n' + + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + + '}', + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * Sets filter's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Compile this filter's shader program. + * + * @param {WebGLRenderingContext} gl The GL canvas context to use for shader compilation. + * @param {String} fragmentSource fragmentShader source for compilation + * @param {String} vertexSource vertexShader source for compilation + */ + createProgram: function(gl, fragmentSource, vertexSource) { + fragmentSource = fragmentSource || this.fragmentSource; + vertexSource = vertexSource || this.vertexSource; + if (fabric.webGlPrecision !== 'highp'){ + fragmentSource = fragmentSource.replace( + /precision highp float/g, + 'precision ' + fabric.webGlPrecision + ' float' + ); + } + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, vertexSource); + gl.compileShader(vertexShader); + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Vertex shader compile error for ' + this.type + ': ' + + gl.getShaderInfoLog(vertexShader) + ); + } + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Fragment shader compile error for ' + this.type + ': ' + + gl.getShaderInfoLog(fragmentShader) + ); + } + + var program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Shader link error for "${this.type}" ' + + gl.getProgramInfoLog(program) + ); + } + + var attributeLocations = this.getAttributeLocations(gl, program); + var uniformLocations = this.getUniformLocations(gl, program) || { }; + uniformLocations.uStepW = gl.getUniformLocation(program, 'uStepW'); + uniformLocations.uStepH = gl.getUniformLocation(program, 'uStepH'); + return { + program: program, + attributeLocations: attributeLocations, + uniformLocations: uniformLocations + }; + }, + + /** + * Return a map of attribute names to WebGLAttributeLocation objects. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {WebGLShaderProgram} program The shader program from which to take attribute locations. + * @returns {Object} A map of attribute names to attribute locations. + */ + getAttributeLocations: function(gl, program) { + return { + aPosition: gl.getAttribLocation(program, 'aPosition'), + }; + }, + + /** + * Return a map of uniform names to WebGLUniformLocation objects. + * + * Intended to be overridden by subclasses. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {WebGLShaderProgram} program The shader program from which to take uniform locations. + * @returns {Object} A map of uniform names to uniform locations. + */ + getUniformLocations: function (/* gl, program */) { + // in case i do not need any special uniform i need to return an empty object + return { }; + }, + + /** + * Send attribute data from this filter to its shader program on the GPU. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {Object} attributeLocations A map of shader attribute names to their locations. + */ + sendAttributeData: function(gl, attributeLocations, aPositionData) { + var attributeLocation = attributeLocations.aPosition; + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.enableVertexAttribArray(attributeLocation); + gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, aPositionData, gl.STATIC_DRAW); + }, + + _setupFrameBuffer: function(options) { + var gl = options.context, width, height; + if (options.passes > 1) { + width = options.destinationWidth; + height = options.destinationHeight; + if (options.sourceWidth !== width || options.sourceHeight !== height) { + gl.deleteTexture(options.targetTexture); + options.targetTexture = options.filterBackend.createTexture(gl, width, height); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, + options.targetTexture, 0); + } + else { + // draw last filter on canvas and not to framebuffer. + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.finish(); + } + }, + + _swapTextures: function(options) { + options.passes--; + options.pass++; + var temp = options.targetTexture; + options.targetTexture = options.sourceTexture; + options.sourceTexture = temp; + }, + + /** + * Generic isNeutral implementation for one parameter based filters. + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * Other filters may need their own version ( ColorMatrix, HueRotation, gamma, ComposedFilter ) + * @param {Object} options + **/ + isNeutralState: function(/* options */) { + var main = this.mainParameter, + _class = fabric.Image.filters[this.type].prototype; + if (main) { + if (Array.isArray(_class[main])) { + for (var i = _class[main].length; i--;) { + if (this[main][i] !== _class[main][i]) { + return false; + } + } + return true; + } + else { + return _class[main] === this[main]; + } + } + else { + return false; + } + }, + + /** + * Apply this filter to the input image data provided. + * + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + if (options.webgl) { + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + } + else { + this.applyTo2d(options); + } + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + if (!options.programCache.hasOwnProperty(this.type)) { + options.programCache[this.type] = this.createProgram(options.context); + } + return options.programCache[this.type]; + }, + + /** + * Apply this filter using webgl. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.originalTexture The texture of the original input image. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyToWebGL: function(options) { + var gl = options.context; + var shader = this.retrieveShader(options); + if (options.pass === 0 && options.originalTexture) { + gl.bindTexture(gl.TEXTURE_2D, options.originalTexture); + } + else { + gl.bindTexture(gl.TEXTURE_2D, options.sourceTexture); + } + gl.useProgram(shader.program); + this.sendAttributeData(gl, shader.attributeLocations, options.aPosition); + + gl.uniform1f(shader.uniformLocations.uStepW, 1 / options.sourceWidth); + gl.uniform1f(shader.uniformLocations.uStepH, 1 / options.sourceHeight); + + this.sendUniformData(gl, shader.uniformLocations); + gl.viewport(0, 0, options.destinationWidth, options.destinationHeight); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + }, + + bindAdditionalTexture: function(gl, texture, textureUnit) { + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_2D, texture); + // reset active texture to 0 as usual + gl.activeTexture(gl.TEXTURE0); + }, + + unbindAdditionalTexture: function(gl, textureUnit) { + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.activeTexture(gl.TEXTURE0); + }, + + getMainParameter: function() { + return this[this.mainParameter]; + }, + + setMainParameter: function(value) { + this[this.mainParameter] = value; + }, + + /** + * Send uniform data from this filter to its shader program on the GPU. + * + * Intended to be overridden by subclasses. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {Object} uniformLocations A map of shader uniform names to their locations. + */ + sendUniformData: function(/* gl, uniformLocations */) { + // Intentionally left blank. Override me in subclasses. + }, + + /** + * If needed by a 2d filter, this functions can create an helper canvas to be used + * remember that options.targetCanvas is available for use till end of chain. + */ + createHelpLayer: function(options) { + if (!options.helpLayer) { + var helpLayer = document.createElement('canvas'); + helpLayer.width = options.sourceWidth; + helpLayer.height = options.sourceHeight; + options.helpLayer = helpLayer; + } + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + var object = { type: this.type }, mainP = this.mainParameter; + if (mainP) { + object[mainP] = this[mainP]; + } + return object; + }, + + /** + * Returns a JSON representation of an instance + * @return {Object} JSON + */ + toJSON: function() { + // delegate, not alias + return this.toObject(); + } +}); + +fabric.Image.filters.BaseFilter.fromObject = function(object, callback) { + var filter = new fabric.Image.filters[object.type](object); + callback && callback(filter); + return filter; +}; + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Color Matrix filter class + * @class fabric.Image.filters.ColorMatrix + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php} + * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl} + * @example Kodachrome filter + * var filter = new fabric.Image.filters.ColorMatrix({ + * matrix: [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'ColorMatrix', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'varying vec2 vTexCoord;\n' + + 'uniform mat4 uColorMatrix;\n' + + 'uniform vec4 uConstants;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color *= uColorMatrix;\n' + + 'color += uConstants;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Colormatrix for pixels. + * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning + * outside the -1, 1 range. + * 0.0039215686 is the part of 1 that get translated to 1 in 2d + * @param {Array} matrix array of 20 numbers. + * @default + */ + matrix: [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ], + + mainParameter: 'matrix', + + /** + * Lock the colormatrix on the color part, skipping alpha, manly for non webgl scenario + * to save some calculation + */ + colorsOnly: true, + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.callSuper('initialize', options); + // create a new array instead mutating the prototype with push + this.matrix = this.matrix.slice(0); + }, + + /** + * Apply the ColorMatrix operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + iLen = data.length, + m = this.matrix, + r, g, b, a, i, colorsOnly = this.colorsOnly; + + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + if (colorsOnly) { + data[i] = r * m[0] + g * m[1] + b * m[2] + m[4] * 255; + data[i + 1] = r * m[5] + g * m[6] + b * m[7] + m[9] * 255; + data[i + 2] = r * m[10] + g * m[11] + b * m[12] + m[14] * 255; + } + else { + a = data[i + 3]; + data[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255; + data[i + 1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255; + data[i + 2] = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255; + data[i + 3] = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), + uConstants: gl.getUniformLocation(program, 'uConstants'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var m = this.matrix, + matrix = [ + m[0], m[1], m[2], m[3], + m[5], m[6], m[7], m[8], + m[10], m[11], m[12], m[13], + m[15], m[16], m[17], m[18] + ], + constants = [m[4], m[9], m[14], m[19]]; + gl.uniformMatrix4fv(uniformLocations.uColorMatrix, false, matrix); + gl.uniform4fv(uniformLocations.uConstants, constants); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] function to invoke after filter creation + * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix + */ + fabric.Image.filters.ColorMatrix.fromObject = fabric.Image.filters.BaseFilter.fromObject; +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Brightness filter class + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Brightness({ + * brightness: 0.05 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Brightness', + + /** + * Fragment source for the brightness program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uBrightness;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color.rgb += uBrightness;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Brightness value, from -1 to 1. + * translated to -255 to 255 for 2d + * 0.0039215686 is the part of 1 that get translated to 1 in 2d + * @param {Number} brightness + * @default + */ + brightness: 0, + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'brightness', + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.brightness === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, i, len = data.length, + brightness = Math.round(this.brightness * 255); + for (i = 0; i < len; i += 4) { + data[i] = data[i] + brightness; + data[i + 1] = data[i + 1] + brightness; + data[i + 2] = data[i + 2] + brightness; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uBrightness: gl.getUniformLocation(program, 'uBrightness'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uBrightness, this.brightness); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness + */ + fabric.Image.filters.Brightness.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Adapted from html5rocks article + * @class fabric.Image.filters.Convolute + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example Sharpen filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 0, -1, 0, + * -1, 5, -1, + * 0, -1, 0 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Blur filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Emboss filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Emboss filter with opaqueness + * var filter = new fabric.Image.filters.Convolute({ + * opaque: true, + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Convolute', + + /* + * Opaque value (true/false) + */ + opaque: false, + + /* + * matrix for the filter, max 9x9 + */ + matrix: [0, 0, 0, 0, 1, 0, 0, 0, 0], + + /** + * Fragment source for the brightness program + */ + fragmentSource: { + Convolute_3_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[9];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 1), uStepH * (h - 1));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 3.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_3_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[9];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 1.0), uStepH * (h - 1.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 3.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_5_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[25];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 5.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_5_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[25];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 5.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_7_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[49];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 7.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_7_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[49];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 7.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_9_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[81];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 9.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_9_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[81];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 9.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + }, + + /** + * Constructor + * @memberOf fabric.Image.filters.Convolute.prototype + * @param {Object} [options] Options object + * @param {Boolean} [options.opaque=false] Opaque value (true/false) + * @param {Array} [options.matrix] Filter matrix + */ + + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var size = Math.sqrt(this.matrix.length); + var cacheKey = this.type + '_' + size + '_' + (this.opaque ? 1 : 0); + var shaderSource = this.fragmentSource[cacheKey]; + if (!options.programCache.hasOwnProperty(cacheKey)) { + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + weights = this.matrix, + side = Math.round(Math.sqrt(weights.length)), + halfSide = Math.floor(side / 2), + sw = imageData.width, + sh = imageData.height, + output = options.ctx.createImageData(sw, sh), + dst = output.data, + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0, + r, g, b, a, dstOff, + scx, scy, srcOff, wt, + x, y, cx, cy; + + for (y = 0; y < sh; y++) { + for (x = 0; x < sw; x++) { + dstOff = (y * sw + x) * 4; + // calculate the weighed sum of the source image pixels that + // fall under the convolution matrix + r = 0; g = 0; b = 0; a = 0; + + for (cy = 0; cy < side; cy++) { + for (cx = 0; cx < side; cx++) { + scy = y + cy - halfSide; + scx = x + cx - halfSide; + + // eslint-disable-next-line max-depth + if (scy < 0 || scy >= sh || scx < 0 || scx >= sw) { + continue; + } + + srcOff = (scy * sw + scx) * 4; + wt = weights[cy * side + cx]; + + r += data[srcOff] * wt; + g += data[srcOff + 1] * wt; + b += data[srcOff + 2] * wt; + // eslint-disable-next-line max-depth + if (!alphaFac) { + a += data[srcOff + 3] * wt; + } + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + if (!alphaFac) { + dst[dstOff + 3] = a; + } + else { + dst[dstOff + 3] = data[dstOff + 3]; + } + } + } + options.imageData = output; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uMatrix: gl.getUniformLocation(program, 'uMatrix'), + uOpaque: gl.getUniformLocation(program, 'uOpaque'), + uHalfSize: gl.getUniformLocation(program, 'uHalfSize'), + uSize: gl.getUniformLocation(program, 'uSize'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1fv(uniformLocations.uMatrix, this.matrix); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + opaque: this.opaque, + matrix: this.matrix + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute + */ + fabric.Image.filters.Convolute.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Grayscale image filter class + * @class fabric.Image.filters.Grayscale + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Grayscale(); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Grayscale', + + fragmentSource: { + average: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float average = (color.r + color.b + color.g) / 3.0;\n' + + 'gl_FragColor = vec4(average, average, average, color.a);\n' + + '}', + lightness: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uMode;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + + 'float average = (max(max(col.r, col.g),col.b) + min(min(col.r, col.g),col.b)) / 2.0;\n' + + 'gl_FragColor = vec4(average, average, average, col.a);\n' + + '}', + luminosity: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uMode;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + + 'float average = 0.21 * col.r + 0.72 * col.g + 0.07 * col.b;\n' + + 'gl_FragColor = vec4(average, average, average, col.a);\n' + + '}', + }, + + + /** + * Grayscale mode, between 'average', 'lightness', 'luminosity' + * @param {String} type + * @default + */ + mode: 'average', + + mainParameter: 'mode', + + /** + * Apply the Grayscale operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + len = data.length, value, + mode = this.mode; + for (i = 0; i < len; i += 4) { + if (mode === 'average') { + value = (data[i] + data[i + 1] + data[i + 2]) / 3; + } + else if (mode === 'lightness') { + value = (Math.min(data[i], data[i + 1], data[i + 2]) + + Math.max(data[i], data[i + 1], data[i + 2])) / 2; + } + else if (mode === 'luminosity') { + value = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; + } + data[i] = value; + data[i + 1] = value; + data[i + 2] = value; + } + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode; + if (!options.programCache.hasOwnProperty(cacheKey)) { + var shaderSource = this.fragmentSource[this.mode]; + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uMode: gl.getUniformLocation(program, 'uMode'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + // default average mode. + var mode = 1; + gl.uniform1i(uniformLocations.uMode, mode); + }, + + /** + * Grayscale filter isNeutralState implementation + * The filter is never neutral + * on the image + **/ + isNeutralState: function() { + return false; + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale + */ + fabric.Image.filters.Grayscale.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Invert filter class + * @class fabric.Image.filters.Invert + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Invert(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Invert', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uInvert;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'if (uInvert == 1) {\n' + + 'gl_FragColor = vec4(1.0 - color.r,1.0 -color.g,1.0 -color.b,color.a);\n' + + '} else {\n' + + 'gl_FragColor = color;\n' + + '}\n' + + '}', + + /** + * Filter invert. if false, does nothing + * @param {Boolean} invert + * @default + */ + invert: true, + + mainParameter: 'invert', + + /** + * Apply the Invert operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + len = data.length; + for (i = 0; i < len; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + }, + + /** + * Invert filter isNeutralState implementation + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * @param {Object} options + **/ + isNeutralState: function() { + return !this.invert; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uInvert: gl.getUniformLocation(program, 'uInvert'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1i(uniformLocations.uInvert, this.invert); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert + */ + fabric.Image.filters.Invert.fromObject = fabric.Image.filters.BaseFilter.fromObject; + + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Noise filter class + * @class fabric.Image.filters.Noise + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Noise({ + * noise: 700 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Noise', + + /** + * Fragment source for the noise program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uStepH;\n' + + 'uniform float uNoise;\n' + + 'uniform float uSeed;\n' + + 'varying vec2 vTexCoord;\n' + + 'float rand(vec2 co, float seed, float vScale) {\n' + + 'return fract(sin(dot(co.xy * vScale ,vec2(12.9898 , 78.233))) * 43758.5453 * (seed + 0.01) / 2.0);\n' + + '}\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color.rgb += (0.5 - rand(vTexCoord, uSeed, 0.1 / uStepH)) * uNoise;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'noise', + + /** + * Noise value, from + * @param {Number} noise + * @default + */ + noise: 0, + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.noise === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, i, len = data.length, + noise = this.noise, rand; + + for (i = 0, len = data.length; i < len; i += 4) { + + rand = (0.5 - Math.random()) * noise; + + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uNoise: gl.getUniformLocation(program, 'uNoise'), + uSeed: gl.getUniformLocation(program, 'uSeed'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uNoise, this.noise / 255); + gl.uniform1f(uniformLocations.uSeed, Math.random()); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + noise: this.noise + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise + */ + fabric.Image.filters.Noise.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Pixelate filter class + * @class fabric.Image.filters.Pixelate + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Pixelate({ + * blocksize: 8 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Pixelate', + + blocksize: 4, + + mainParameter: 'blocksize', + + /** + * Fragment source for the Pixelate program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uBlocksize;\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'float blockW = uBlocksize * uStepW;\n' + + 'float blockH = uBlocksize * uStepW;\n' + + 'int posX = int(vTexCoord.x / blockW);\n' + + 'int posY = int(vTexCoord.y / blockH);\n' + + 'float fposX = float(posX);\n' + + 'float fposY = float(posY);\n' + + 'vec2 squareCoords = vec2(fposX * blockW, fposY * blockH);\n' + + 'vec4 color = texture2D(uTexture, squareCoords);\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Apply the Pixelate operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + iLen = imageData.height, + jLen = imageData.width, + index, i, j, r, g, b, a, + _i, _j, _iLen, _jLen; + + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + + index = (i * 4) * jLen + (j * 4); + + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + + _iLen = Math.min(i + this.blocksize, iLen); + _jLen = Math.min(j + this.blocksize, jLen); + for (_i = i; _i < _iLen; _i++) { + for (_j = j; _j < _jLen; _j++) { + index = (_i * 4) * jLen + (_j * 4); + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + }, + + /** + * Indicate when the filter is not gonna apply changes to the image + **/ + isNeutralState: function() { + return this.blocksize === 1; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uBlocksize: gl.getUniformLocation(program, 'uBlocksize'), + uStepW: gl.getUniformLocation(program, 'uStepW'), + uStepH: gl.getUniformLocation(program, 'uStepH'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uBlocksize, this.blocksize); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate + */ + fabric.Image.filters.Pixelate.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Remove white filter class + * @class fabric.Image.filters.RemoveColor + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.RemoveColor#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.RemoveColor({ + * threshold: 0.2, + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.RemoveColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveColor.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'RemoveColor', + + /** + * Color to remove, in any format understood by fabric.Color. + * @param {String} type + * @default + */ + color: '#FFFFFF', + + /** + * Fragment source for the brightness program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec4 uLow;\n' + + 'uniform vec4 uHigh;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + + 'if(all(greaterThan(gl_FragColor.rgb,uLow.rgb)) && all(greaterThan(uHigh.rgb,gl_FragColor.rgb))) {\n' + + 'gl_FragColor.a = 0.0;\n' + + '}\n' + + '}', + + /** + * distance to actual color, as value up or down from each r,g,b + * between 0 and 1 + **/ + distance: 0.02, + + /** + * For color to remove inside distance, use alpha channel for a smoother deletion + * NOT IMPLEMENTED YET + **/ + useAlpha: false, + + /** + * Constructor + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + * @param {Number} [options.color=#RRGGBB] Threshold value + * @param {Number} [options.distance=10] Distance value + */ + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + distance = this.distance * 255, + r, g, b, + source = new fabric.Color(this.color).getSource(), + lowC = [ + source[0] - distance, + source[1] - distance, + source[2] - distance, + ], + highC = [ + source[0] + distance, + source[1] + distance, + source[2] + distance, + ]; + + + for (i = 0; i < data.length; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + if (r > lowC[0] && + g > lowC[1] && + b > lowC[2] && + r < highC[0] && + g < highC[1] && + b < highC[2]) { + data[i + 3] = 0; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uLow: gl.getUniformLocation(program, 'uLow'), + uHigh: gl.getUniformLocation(program, 'uHigh'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var source = new fabric.Color(this.color).getSource(), + distance = parseFloat(this.distance), + lowC = [ + 0 + source[0] / 255 - distance, + 0 + source[1] / 255 - distance, + 0 + source[2] / 255 - distance, + 1 + ], + highC = [ + source[0] / 255 + distance, + source[1] / 255 + distance, + source[2] / 255 + distance, + 1 + ]; + gl.uniform4fv(uniformLocations.uLow, lowC); + gl.uniform4fv(uniformLocations.uHigh, highC); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color, + distance: this.distance + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.RemoveColor} Instance of fabric.Image.filters.RemoveWhite + */ + fabric.Image.filters.RemoveColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + var matrices = { + Brownie: [ + 0.59970,0.34553,-0.27082,0,0.186, + -0.03770,0.86095,0.15059,0,-0.1449, + 0.24113,-0.07441,0.44972,0,-0.02965, + 0,0,0,1,0 + ], + Vintage: [ + 0.62793,0.32021,-0.03965,0,0.03784, + 0.02578,0.64411,0.03259,0,0.02926, + 0.04660,-0.08512,0.52416,0,0.02023, + 0,0,0,1,0 + ], + Kodachrome: [ + 1.12855,-0.39673,-0.03992,0,0.24991, + -0.16404,1.08352,-0.05498,0,0.09698, + -0.16786,-0.56034,1.60148,0,0.13972, + 0,0,0,1,0 + ], + Technicolor: [ + 1.91252,-0.85453,-0.09155,0,0.04624, + -0.30878,1.76589,-0.10601,0,-0.27589, + -0.23110,-0.75018,1.84759,0,0.12137, + 0,0,0,1,0 + ], + Polaroid: [ + 1.438,-0.062,-0.062,0,0, + -0.122,1.378,-0.122,0,0, + -0.016,-0.016,1.483,0,0, + 0,0,0,1,0 + ], + Sepia: [ + 0.393, 0.769, 0.189, 0, 0, + 0.349, 0.686, 0.168, 0, 0, + 0.272, 0.534, 0.131, 0, 0, + 0, 0, 0, 1, 0 + ], + BlackWhite: [ + 1.5, 1.5, 1.5, 0, -1, + 1.5, 1.5, 1.5, 0, -1, + 1.5, 1.5, 1.5, 0, -1, + 0, 0, 0, 1, 0, + ] + }; + + for (var key in matrices) { + filters[key] = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.Sepia.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: key, + + /** + * Colormatrix for the effect + * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning + * outside the -1, 1 range. + * @param {Array} matrix array of 20 numbers. + * @default + */ + matrix: matrices[key], + + /** + * Lock the matrix export for this kind of static, parameter less filters. + */ + mainParameter: false, + /** + * Lock the colormatrix on the color part, skipping alpha + */ + colorsOnly: true, + + }); + fabric.Image.filters[key].fromObject = fabric.Image.filters.BaseFilter.fromObject; + } +})( true ? exports : 0); + + +(function(global) { + 'use strict'; + + var fabric = global.fabric, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Color Blend filter class + * @class fabric.Image.filter.BlendColor + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.BlendColor({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.BlendImage({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + + filters.BlendColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ { + type: 'BlendColor', + + /** + * Color to make the blend operation with. default to a reddish color since black or white + * gives always strong result. + **/ + color: '#F95C63', + + /** + * Blend mode for the filter: one of multiply, add, diff, screen, subtract, + * darken, lighten, overlay, exclusion, tint. + **/ + mode: 'multiply', + + /** + * alpha value. represent the strength of the blend color operation. + **/ + alpha: 1, + + /** + * Fragment source for the Multiply program + */ + fragmentSource: { + multiply: 'gl_FragColor.rgb *= uColor.rgb;\n', + screen: 'gl_FragColor.rgb = 1.0 - (1.0 - gl_FragColor.rgb) * (1.0 - uColor.rgb);\n', + add: 'gl_FragColor.rgb += uColor.rgb;\n', + diff: 'gl_FragColor.rgb = abs(gl_FragColor.rgb - uColor.rgb);\n', + subtract: 'gl_FragColor.rgb -= uColor.rgb;\n', + lighten: 'gl_FragColor.rgb = max(gl_FragColor.rgb, uColor.rgb);\n', + darken: 'gl_FragColor.rgb = min(gl_FragColor.rgb, uColor.rgb);\n', + exclusion: 'gl_FragColor.rgb += uColor.rgb - 2.0 * (uColor.rgb * gl_FragColor.rgb);\n', + overlay: 'if (uColor.r < 0.5) {\n' + + 'gl_FragColor.r *= 2.0 * uColor.r;\n' + + '} else {\n' + + 'gl_FragColor.r = 1.0 - 2.0 * (1.0 - gl_FragColor.r) * (1.0 - uColor.r);\n' + + '}\n' + + 'if (uColor.g < 0.5) {\n' + + 'gl_FragColor.g *= 2.0 * uColor.g;\n' + + '} else {\n' + + 'gl_FragColor.g = 1.0 - 2.0 * (1.0 - gl_FragColor.g) * (1.0 - uColor.g);\n' + + '}\n' + + 'if (uColor.b < 0.5) {\n' + + 'gl_FragColor.b *= 2.0 * uColor.b;\n' + + '} else {\n' + + 'gl_FragColor.b = 1.0 - 2.0 * (1.0 - gl_FragColor.b) * (1.0 - uColor.b);\n' + + '}\n', + tint: 'gl_FragColor.rgb *= (1.0 - uColor.a);\n' + + 'gl_FragColor.rgb += uColor.rgb;\n', + }, + + /** + * build the fragment source for the filters, joining the common part with + * the specific one. + * @param {String} mode the mode of the filter, a key of this.fragmentSource + * @return {String} the source to be compiled + * @private + */ + buildSource: function(mode) { + return 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'gl_FragColor = color;\n' + + 'if (color.a > 0.0) {\n' + + this.fragmentSource[mode] + + '}\n' + + '}'; + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode, shaderSource; + if (!options.programCache.hasOwnProperty(cacheKey)) { + shaderSource = this.buildSource(this.mode); + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, iLen = data.length, + tr, tg, tb, + r, g, b, + source, alpha1 = 1 - this.alpha; + + source = new fabric.Color(this.color).getSource(); + tr = source[0] * this.alpha; + tg = source[1] * this.alpha; + tb = source[2] * this.alpha; + + for (var i = 0; i < iLen; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + break; + case 'screen': + data[i] = 255 - (255 - r) * (255 - tr) / 255; + data[i + 1] = 255 - (255 - g) * (255 - tg) / 255; + data[i + 2] = 255 - (255 - b) * (255 - tb) / 255; + break; + case 'add': + data[i] = r + tr; + data[i + 1] = g + tg; + data[i + 2] = b + tb; + break; + case 'diff': + case 'difference': + data[i] = Math.abs(r - tr); + data[i + 1] = Math.abs(g - tg); + data[i + 2] = Math.abs(b - tb); + break; + case 'subtract': + data[i] = r - tr; + data[i + 1] = g - tg; + data[i + 2] = b - tb; + break; + case 'darken': + data[i] = Math.min(r, tr); + data[i + 1] = Math.min(g, tg); + data[i + 2] = Math.min(b, tb); + break; + case 'lighten': + data[i] = Math.max(r, tr); + data[i + 1] = Math.max(g, tg); + data[i + 2] = Math.max(b, tb); + break; + case 'overlay': + data[i] = tr < 128 ? (2 * r * tr / 255) : (255 - 2 * (255 - r) * (255 - tr) / 255); + data[i + 1] = tg < 128 ? (2 * g * tg / 255) : (255 - 2 * (255 - g) * (255 - tg) / 255); + data[i + 2] = tb < 128 ? (2 * b * tb / 255) : (255 - 2 * (255 - b) * (255 - tb) / 255); + break; + case 'exclusion': + data[i] = tr + r - ((2 * tr * r) / 255); + data[i + 1] = tg + g - ((2 * tg * g) / 255); + data[i + 2] = tb + b - ((2 * tb * b) / 255); + break; + case 'tint': + data[i] = tr + r * alpha1; + data[i + 1] = tg + g * alpha1; + data[i + 2] = tb + b * alpha1; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uColor: gl.getUniformLocation(program, 'uColor'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var source = new fabric.Color(this.color).getSource(); + source[0] = this.alpha * source[0] / 255; + source[1] = this.alpha * source[1] / 255; + source[2] = this.alpha * source[2] / 255; + source[3] = this.alpha; + gl.uniform4fv(uniformLocations.uColor, source); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + color: this.color, + mode: this.mode, + alpha: this.alpha + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.BlendColor} Instance of fabric.Image.filters.BlendColor + */ + fabric.Image.filters.BlendColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + 'use strict'; + + var fabric = global.fabric, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Image Blend filter class + * @class fabric.Image.filter.BlendImage + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.BlendColor({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.BlendImage({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + + filters.BlendImage = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.BlendImage.prototype */ { + type: 'BlendImage', + + /** + * Color to make the blend operation with. default to a reddish color since black or white + * gives always strong result. + **/ + image: null, + + /** + * Blend mode for the filter: one of multiply, add, diff, screen, subtract, + * darken, lighten, overlay, exclusion, tint. + **/ + mode: 'multiply', + + /** + * alpha value. represent the strength of the blend image operation. + * not implemented. + **/ + alpha: 1, + + vertexSource: 'attribute vec2 aPosition;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'uniform mat3 uTransformMatrix;\n' + + 'void main() {\n' + + 'vTexCoord = aPosition;\n' + + 'vTexCoord2 = (uTransformMatrix * vec3(aPosition, 1.0)).xy;\n' + + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + + '}', + + /** + * Fragment source for the Multiply program + */ + fragmentSource: { + multiply: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform sampler2D uImage;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + + 'color.rgba *= color2.rgba;\n' + + 'gl_FragColor = color;\n' + + '}', + mask: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform sampler2D uImage;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + + 'color.a = color2.a;\n' + + 'gl_FragColor = color;\n' + + '}', + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode; + var shaderSource = this.fragmentSource[this.mode]; + if (!options.programCache.hasOwnProperty(cacheKey)) { + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + applyToWebGL: function(options) { + // load texture to blend. + var gl = options.context, + texture = this.createTexture(options.filterBackend, this.image); + this.bindAdditionalTexture(gl, texture, gl.TEXTURE1); + this.callSuper('applyToWebGL', options); + this.unbindAdditionalTexture(gl, gl.TEXTURE1); + }, + + createTexture: function(backend, image) { + return backend.getCachedTexture(image.cacheKey, image._element); + }, + + /** + * Calculate a transformMatrix to adapt the image to blend over + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + calculateMatrix: function() { + var image = this.image, + width = image._element.width, + height = image._element.height; + return [ + 1 / image.scaleX, 0, 0, + 0, 1 / image.scaleY, 0, + -image.left / width, -image.top / height, 1 + ]; + }, + + /** + * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + resources = options.filterBackend.resources, + data = imageData.data, iLen = data.length, + width = imageData.width, + height = imageData.height, + tr, tg, tb, ta, + r, g, b, a, + canvas1, context, image = this.image, blendData; + + if (!resources.blendImage) { + resources.blendImage = fabric.util.createCanvasElement(); + } + canvas1 = resources.blendImage; + context = canvas1.getContext('2d'); + if (canvas1.width !== width || canvas1.height !== height) { + canvas1.width = width; + canvas1.height = height; + } + else { + context.clearRect(0, 0, width, height); + } + context.setTransform(image.scaleX, 0, 0, image.scaleY, image.left, image.top); + context.drawImage(image._element, 0, 0, width, height); + blendData = context.getImageData(0, 0, width, height).data; + for (var i = 0; i < iLen; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + tr = blendData[i]; + tg = blendData[i + 1]; + tb = blendData[i + 2]; + ta = blendData[i + 3]; + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + data[i + 3] = a * ta / 255; + break; + case 'mask': + data[i + 3] = ta; + break; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uTransformMatrix: gl.getUniformLocation(program, 'uTransformMatrix'), + uImage: gl.getUniformLocation(program, 'uImage'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var matrix = this.calculateMatrix(); + gl.uniform1i(uniformLocations.uImage, 1); // texture unit 1. + gl.uniformMatrix3fv(uniformLocations.uTransformMatrix, false, matrix); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + image: this.image && this.image.toObject(), + mode: this.mode, + alpha: this.alpha + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} callback to be invoked after filter creation + * @return {fabric.Image.filters.BlendImage} Instance of fabric.Image.filters.BlendImage + */ + fabric.Image.filters.BlendImage.fromObject = function(object, callback) { + fabric.Image.fromObject(object.image, function(image) { + var options = fabric.util.object.clone(object); + options.image = image; + callback(new fabric.Image.filters.BlendImage(options)); + }); + }; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, + sqrt = Math.sqrt, abs = Math.abs, round = Math.round, sin = Math.sin, + ceil = Math.ceil, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Resize image filter class + * @class fabric.Image.filters.Resize + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Resize(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Resize', + + /** + * Resize type + * for webgl resizeType is just lanczos, for canvas2d can be: + * bilinear, hermite, sliceHack, lanczos. + * @param {String} resizeType + * @default + */ + resizeType: 'hermite', + + /** + * Scale factor for resizing, x axis + * @param {Number} scaleX + * @default + */ + scaleX: 1, + + /** + * Scale factor for resizing, y axis + * @param {Number} scaleY + * @default + */ + scaleY: 1, + + /** + * LanczosLobes parameter for lanczos filter, valid for resizeType lanczos + * @param {Number} lanczosLobes + * @default + */ + lanczosLobes: 3, + + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uDelta: gl.getUniformLocation(program, 'uDelta'), + uTaps: gl.getUniformLocation(program, 'uTaps'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform2fv(uniformLocations.uDelta, this.horizontal ? [1 / this.width, 0] : [0, 1 / this.height]); + gl.uniform1fv(uniformLocations.uTaps, this.taps); + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var filterWindow = this.getFilterWindow(), cacheKey = this.type + '_' + filterWindow; + if (!options.programCache.hasOwnProperty(cacheKey)) { + var fragmentShader = this.generateShader(filterWindow); + options.programCache[cacheKey] = this.createProgram(options.context, fragmentShader); + } + return options.programCache[cacheKey]; + }, + + getFilterWindow: function() { + var scale = this.tempScale; + return Math.ceil(this.lanczosLobes / scale); + }, + + getTaps: function() { + var lobeFunction = this.lanczosCreate(this.lanczosLobes), scale = this.tempScale, + filterWindow = this.getFilterWindow(), taps = new Array(filterWindow); + for (var i = 1; i <= filterWindow; i++) { + taps[i - 1] = lobeFunction(i * scale); + } + return taps; + }, + + /** + * Generate vertex and shader sources from the necessary steps numbers + * @param {Number} filterWindow + */ + generateShader: function(filterWindow) { + var offsets = new Array(filterWindow), + fragmentShader = this.fragmentSourceTOP, filterWindow; + + for (var i = 1; i <= filterWindow; i++) { + offsets[i - 1] = i + '.0 * uDelta'; + } + + fragmentShader += 'uniform float uTaps[' + filterWindow + '];\n'; + fragmentShader += 'void main() {\n'; + fragmentShader += ' vec4 color = texture2D(uTexture, vTexCoord);\n'; + fragmentShader += ' float sum = 1.0;\n'; + + offsets.forEach(function(offset, i) { + fragmentShader += ' color += texture2D(uTexture, vTexCoord + ' + offset + ') * uTaps[' + i + '];\n'; + fragmentShader += ' color += texture2D(uTexture, vTexCoord - ' + offset + ') * uTaps[' + i + '];\n'; + fragmentShader += ' sum += 2.0 * uTaps[' + i + '];\n'; + }); + fragmentShader += ' gl_FragColor = color / sum;\n'; + fragmentShader += '}'; + return fragmentShader; + }, + + fragmentSourceTOP: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec2 uDelta;\n' + + 'varying vec2 vTexCoord;\n', + + /** + * Apply the resize filter to the image + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + if (options.webgl) { + options.passes++; + this.width = options.sourceWidth; + this.horizontal = true; + this.dW = Math.round(this.width * this.scaleX); + this.dH = options.sourceHeight; + this.tempScale = this.dW / this.width; + this.taps = this.getTaps(); + options.destinationWidth = this.dW; + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + options.sourceWidth = options.destinationWidth; + + this.height = options.sourceHeight; + this.horizontal = false; + this.dH = Math.round(this.height * this.scaleY); + this.tempScale = this.dH / this.height; + this.taps = this.getTaps(); + options.destinationHeight = this.dH; + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + options.sourceHeight = options.destinationHeight; + } + else { + this.applyTo2d(options); + } + }, + + isNeutralState: function() { + return this.scaleX === 1 && this.scaleY === 1; + }, + + lanczosCreate: function(lobes) { + return function(x) { + if (x >= lobes || x <= -lobes) { + return 0.0; + } + if (x < 1.19209290E-07 && x > -1.19209290E-07) { + return 1.0; + } + x *= Math.PI; + var xx = x / lobes; + return (sin(x) / x) * sin(xx) / xx; + }; + }, + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Resize.prototype + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} scaleX + * @param {Number} scaleY + */ + applyTo2d: function(options) { + var imageData = options.imageData, + scaleX = this.scaleX, + scaleY = this.scaleY; + + this.rcpScaleX = 1 / scaleX; + this.rcpScaleY = 1 / scaleY; + + var oW = imageData.width, oH = imageData.height, + dW = round(oW * scaleX), dH = round(oH * scaleY), + newData; + + if (this.resizeType === 'sliceHack') { + newData = this.sliceByTwo(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'hermite') { + newData = this.hermiteFastResize(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'bilinear') { + newData = this.bilinearFiltering(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'lanczos') { + newData = this.lanczosResize(options, oW, oH, dW, dH); + } + options.imageData = newData; + }, + + /** + * Filter sliceByTwo + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + sliceByTwo: function(options, oW, oH, dW, dH) { + var imageData = options.imageData, + mult = 0.5, doneW = false, doneH = false, stepW = oW * mult, + stepH = oH * mult, resources = fabric.filterBackend.resources, + tmpCanvas, ctx, sX = 0, sY = 0, dX = oW, dY = 0; + if (!resources.sliceByTwo) { + resources.sliceByTwo = document.createElement('canvas'); + } + tmpCanvas = resources.sliceByTwo; + if (tmpCanvas.width < oW * 1.5 || tmpCanvas.height < oH) { + tmpCanvas.width = oW * 1.5; + tmpCanvas.height = oH; + } + ctx = tmpCanvas.getContext('2d'); + ctx.clearRect(0, 0, oW * 1.5, oH); + ctx.putImageData(imageData, 0, 0); + + dW = floor(dW); + dH = floor(dH); + + while (!doneW || !doneH) { + oW = stepW; + oH = stepH; + if (dW < floor(stepW * mult)) { + stepW = floor(stepW * mult); + } + else { + stepW = dW; + doneW = true; + } + if (dH < floor(stepH * mult)) { + stepH = floor(stepH * mult); + } + else { + stepH = dH; + doneH = true; + } + ctx.drawImage(tmpCanvas, sX, sY, oW, oH, dX, dY, stepW, stepH); + sX = dX; + sY = dY; + dY += stepH; + } + return ctx.getImageData(sX, sY, dW, dH); + }, + + /** + * Filter lanczosResize + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + lanczosResize: function(options, oW, oH, dW, dH) { + + function process(u) { + var v, i, weight, idx, a, red, green, + blue, alpha, fX, fY; + center.x = (u + 0.5) * ratioX; + icenter.x = floor(center.x); + for (v = 0; v < dH; v++) { + center.y = (v + 0.5) * ratioY; + icenter.y = floor(center.y); + a = 0; red = 0; green = 0; blue = 0; alpha = 0; + for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { + if (i < 0 || i >= oW) { + continue; + } + fX = floor(1000 * abs(i - center.x)); + if (!cacheLanc[fX]) { + cacheLanc[fX] = { }; + } + for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { + if (j < 0 || j >= oH) { + continue; + } + fY = floor(1000 * abs(j - center.y)); + if (!cacheLanc[fX][fY]) { + cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); + } + weight = cacheLanc[fX][fY]; + if (weight > 0) { + idx = (j * oW + i) * 4; + a += weight; + red += weight * srcData[idx]; + green += weight * srcData[idx + 1]; + blue += weight * srcData[idx + 2]; + alpha += weight * srcData[idx + 3]; + } + } + } + idx = (v * dW + u) * 4; + destData[idx] = red / a; + destData[idx + 1] = green / a; + destData[idx + 2] = blue / a; + destData[idx + 3] = alpha / a; + } + + if (++u < dW) { + return process(u); + } + else { + return destImg; + } + } + + var srcData = options.imageData.data, + destImg = options.ctx.createImageData(dW, dH), + destData = destImg.data, + lanczos = this.lanczosCreate(this.lanczosLobes), + ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, + rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, + range2X = ceil(ratioX * this.lanczosLobes / 2), + range2Y = ceil(ratioY * this.lanczosLobes / 2), + cacheLanc = { }, center = { }, icenter = { }; + + return process(0); + }, + + /** + * bilinearFiltering + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + bilinearFiltering: function(options, oW, oH, dW, dH) { + var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, + color, offset = 0, origPix, ratioX = this.rcpScaleX, + ratioY = this.rcpScaleY, + w4 = 4 * (oW - 1), img = options.imageData, + pixels = img.data, destImage = options.ctx.createImageData(dW, dH), + destPixels = destImage.data; + for (i = 0; i < dH; i++) { + for (j = 0; j < dW; j++) { + x = floor(ratioX * j); + y = floor(ratioY * i); + xDiff = ratioX * j - x; + yDiff = ratioY * i - y; + origPix = 4 * (y * oW + x); + + for (chnl = 0; chnl < 4; chnl++) { + a = pixels[origPix + chnl]; + b = pixels[origPix + 4 + chnl]; + c = pixels[origPix + w4 + chnl]; + d = pixels[origPix + w4 + 4 + chnl]; + color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + + c * yDiff * (1 - xDiff) + d * xDiff * yDiff; + destPixels[offset++] = color; + } + } + } + return destImage; + }, + + /** + * hermiteFastResize + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + hermiteFastResize: function(options, oW, oH, dW, dH) { + var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, + ratioWHalf = ceil(ratioW / 2), + ratioHHalf = ceil(ratioH / 2), + img = options.imageData, data = img.data, + img2 = options.ctx.createImageData(dW, dH), data2 = img2.data; + for (var j = 0; j < dH; j++) { + for (var i = 0; i < dW; i++) { + var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, + gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; + for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { + var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, + centerX = (i + 0.5) * ratioW, w0 = dy * dy; + for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { + var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, + w = sqrt(w0 + dx * dx); + /* eslint-disable max-depth */ + if (w > 1 && w < -1) { + continue; + } + //hermite filter + weight = 2 * w * w * w - 3 * w * w + 1; + if (weight > 0) { + dx = 4 * (xx + yy * oW); + //alpha + gxA += weight * data[dx + 3]; + weightsAlpha += weight; + //colors + if (data[dx + 3] < 255) { + weight = weight * data[dx + 3] / 250; + } + gxR += weight * data[dx]; + gxG += weight * data[dx + 1]; + gxB += weight * data[dx + 2]; + weights += weight; + } + /* eslint-enable max-depth */ + } + } + data2[x2] = gxR / weights; + data2[x2 + 1] = gxG / weights; + data2[x2 + 2] = gxB / weights; + data2[x2 + 3] = gxA / weightsAlpha; + } + } + return img2; + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + scaleX: this.scaleX, + scaleY: this.scaleY, + resizeType: this.resizeType, + lanczosLobes: this.lanczosLobes + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize + */ + fabric.Image.filters.Resize.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Contrast filter class + * @class fabric.Image.filters.Contrast + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Contrast({ + * contrast: 0.25 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Contrast', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uContrast;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));\n' + + 'color.rgb = contrastF * (color.rgb - 0.5) + 0.5;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * contrast value, range from -1 to 1. + * @param {Number} contrast + * @default 0 + */ + contrast: 0, + + mainParameter: 'contrast', + + /** + * Constructor + * @memberOf fabric.Image.filters.Contrast.prototype + * @param {Object} [options] Options object + * @param {Number} [options.contrast=0] Value to contrast the image up (-1...1) + */ + + /** + * Apply the Contrast operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + if (this.contrast === 0) { + return; + } + var imageData = options.imageData, i, len, + data = imageData.data, len = data.length, + contrast = Math.floor(this.contrast * 255), + contrastF = 259 * (contrast + 255) / (255 * (259 - contrast)); + + for (i = 0; i < len; i += 4) { + data[i] = contrastF * (data[i] - 128) + 128; + data[i + 1] = contrastF * (data[i + 1] - 128) + 128; + data[i + 2] = contrastF * (data[i + 2] - 128) + 128; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uContrast: gl.getUniformLocation(program, 'uContrast'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uContrast, this.contrast); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast + */ + fabric.Image.filters.Contrast.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Saturate filter class + * @class fabric.Image.filters.Saturation + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Saturation#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Saturation({ + * saturation: 1 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Saturation = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturation.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Saturation', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uSaturation;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float rgMax = max(color.r, color.g);\n' + + 'float rgbMax = max(rgMax, color.b);\n' + + 'color.r += rgbMax != color.r ? (rgbMax - color.r) * uSaturation : 0.00;\n' + + 'color.g += rgbMax != color.g ? (rgbMax - color.g) * uSaturation : 0.00;\n' + + 'color.b += rgbMax != color.b ? (rgbMax - color.b) * uSaturation : 0.00;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Saturation value, from -1 to 1. + * Increases/decreases the color saturation. + * A value of 0 has no effect. + * + * @param {Number} saturation + * @default + */ + saturation: 0, + + mainParameter: 'saturation', + + /** + * Constructor + * @memberOf fabric.Image.filters.Saturate.prototype + * @param {Object} [options] Options object + * @param {Number} [options.saturate=0] Value to saturate the image (-1...1) + */ + + /** + * Apply the Saturation operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.saturation === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, len = data.length, + adjust = -this.saturation, i, max; + + for (i = 0; i < len; i += 4) { + max = Math.max(data[i], data[i + 1], data[i + 2]); + data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; + data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; + data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uSaturation: gl.getUniformLocation(program, 'uSaturation'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uSaturation, -this.saturation); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Saturation} Instance of fabric.Image.filters.Saturate + */ + fabric.Image.filters.Saturation.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Vibrance filter class + * @class fabric.Image.filters.Vibrance + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Vibrance#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Vibrance({ + * vibrance: 1 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Vibrance = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Vibrance.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Vibrance', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uVibrance;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float max = max(color.r, max(color.g, color.b));\n' + + 'float avg = (color.r + color.g + color.b) / 3.0;\n' + + 'float amt = (abs(max - avg) * 2.0) * uVibrance;\n' + + 'color.r += max != color.r ? (max - color.r) * amt : 0.00;\n' + + 'color.g += max != color.g ? (max - color.g) * amt : 0.00;\n' + + 'color.b += max != color.b ? (max - color.b) * amt : 0.00;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Vibrance value, from -1 to 1. + * Increases/decreases the saturation of more muted colors with less effect on saturated colors. + * A value of 0 has no effect. + * + * @param {Number} vibrance + * @default + */ + vibrance: 0, + + mainParameter: 'vibrance', + + /** + * Constructor + * @memberOf fabric.Image.filters.Vibrance.prototype + * @param {Object} [options] Options object + * @param {Number} [options.vibrance=0] Vibrance value for the image (between -1 and 1) + */ + + /** + * Apply the Vibrance operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.vibrance === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, len = data.length, + adjust = -this.vibrance, i, max, avg, amt; + + for (i = 0; i < len; i += 4) { + max = Math.max(data[i], data[i + 1], data[i + 2]); + avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + amt = ((Math.abs(max - avg) * 2 / 255) * adjust); + data[i] += max !== data[i] ? (max - data[i]) * amt : 0; + data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * amt : 0; + data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * amt : 0; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uVibrance: gl.getUniformLocation(program, 'uVibrance'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uVibrance, -this.vibrance); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Vibrance} Instance of fabric.Image.filters.Vibrance + */ + fabric.Image.filters.Vibrance.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Blur filter class + * @class fabric.Image.filters.Blur + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Blur#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Blur({ + * blur: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Blur = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blur.prototype */ { + + type: 'Blur', + + /* +'gl_FragColor = vec4(0.0);', +'gl_FragColor += texture2D(texture, vTexCoord + -7 * uDelta)*0.0044299121055113265;', +'gl_FragColor += texture2D(texture, vTexCoord + -6 * uDelta)*0.00895781211794;', +'gl_FragColor += texture2D(texture, vTexCoord + -5 * uDelta)*0.0215963866053;', +'gl_FragColor += texture2D(texture, vTexCoord + -4 * uDelta)*0.0443683338718;', +'gl_FragColor += texture2D(texture, vTexCoord + -3 * uDelta)*0.0776744219933;', +'gl_FragColor += texture2D(texture, vTexCoord + -2 * uDelta)*0.115876621105;', +'gl_FragColor += texture2D(texture, vTexCoord + -1 * uDelta)*0.147308056121;', +'gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;', +'gl_FragColor += texture2D(texture, vTexCoord + 1 * uDelta)*0.147308056121;', +'gl_FragColor += texture2D(texture, vTexCoord + 2 * uDelta)*0.115876621105;', +'gl_FragColor += texture2D(texture, vTexCoord + 3 * uDelta)*0.0776744219933;', +'gl_FragColor += texture2D(texture, vTexCoord + 4 * uDelta)*0.0443683338718;', +'gl_FragColor += texture2D(texture, vTexCoord + 5 * uDelta)*0.0215963866053;', +'gl_FragColor += texture2D(texture, vTexCoord + 6 * uDelta)*0.00895781211794;', +'gl_FragColor += texture2D(texture, vTexCoord + 7 * uDelta)*0.0044299121055113265;', +*/ + + /* eslint-disable max-len */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec2 uDelta;\n' + + 'varying vec2 vTexCoord;\n' + + 'const float nSamples = 15.0;\n' + + 'vec3 v3offset = vec3(12.9898, 78.233, 151.7182);\n' + + 'float random(vec3 scale) {\n' + + /* use the fragment position for a different seed per-pixel */ + 'return fract(sin(dot(gl_FragCoord.xyz, scale)) * 43758.5453);\n' + + '}\n' + + 'void main() {\n' + + 'vec4 color = vec4(0.0);\n' + + 'float total = 0.0;\n' + + 'float offset = random(v3offset);\n' + + 'for (float t = -nSamples; t <= nSamples; t++) {\n' + + 'float percent = (t + offset - 0.5) / nSamples;\n' + + 'float weight = 1.0 - abs(percent);\n' + + 'color += texture2D(uTexture, vTexCoord + uDelta * percent) * weight;\n' + + 'total += weight;\n' + + '}\n' + + 'gl_FragColor = color / total;\n' + + '}', + /* eslint-enable max-len */ + + /** + * blur value, in percentage of image dimensions. + * specific to keep the image blur constant at different resolutions + * range between 0 and 1. + */ + blur: 0, + + mainParameter: 'blur', + + applyTo: function(options) { + if (options.webgl) { + // this aspectRatio is used to give the same blur to vertical and horizontal + this.aspectRatio = options.sourceWidth / options.sourceHeight; + options.passes++; + this._setupFrameBuffer(options); + this.horizontal = true; + this.applyToWebGL(options); + this._swapTextures(options); + this._setupFrameBuffer(options); + this.horizontal = false; + this.applyToWebGL(options); + this._swapTextures(options); + } + else { + this.applyTo2d(options); + } + }, + + applyTo2d: function(options) { + // paint canvasEl with current image data. + //options.ctx.putImageData(options.imageData, 0, 0); + options.imageData = this.simpleBlur(options); + }, + + simpleBlur: function(options) { + var resources = options.filterBackend.resources, canvas1, canvas2, + width = options.imageData.width, + height = options.imageData.height; + + if (!resources.blurLayer1) { + resources.blurLayer1 = fabric.util.createCanvasElement(); + resources.blurLayer2 = fabric.util.createCanvasElement(); + } + canvas1 = resources.blurLayer1; + canvas2 = resources.blurLayer2; + if (canvas1.width !== width || canvas1.height !== height) { + canvas2.width = canvas1.width = width; + canvas2.height = canvas1.height = height; + } + var ctx1 = canvas1.getContext('2d'), + ctx2 = canvas2.getContext('2d'), + nSamples = 15, + random, percent, j, i, + blur = this.blur * 0.06 * 0.5; + + // load first canvas + ctx1.putImageData(options.imageData, 0, 0); + ctx2.clearRect(0, 0, width, height); + + for (i = -nSamples; i <= nSamples; i++) { + random = (Math.random() - 0.5) / 4; + percent = i / nSamples; + j = blur * percent * width + random; + ctx2.globalAlpha = 1 - Math.abs(percent); + ctx2.drawImage(canvas1, j, random); + ctx1.drawImage(canvas2, 0, 0); + ctx2.globalAlpha = 1; + ctx2.clearRect(0, 0, canvas2.width, canvas2.height); + } + for (i = -nSamples; i <= nSamples; i++) { + random = (Math.random() - 0.5) / 4; + percent = i / nSamples; + j = blur * percent * height + random; + ctx2.globalAlpha = 1 - Math.abs(percent); + ctx2.drawImage(canvas1, random, j); + ctx1.drawImage(canvas2, 0, 0); + ctx2.globalAlpha = 1; + ctx2.clearRect(0, 0, canvas2.width, canvas2.height); + } + options.ctx.drawImage(canvas1, 0, 0); + var newImageData = options.ctx.getImageData(0, 0, canvas1.width, canvas1.height); + ctx1.globalAlpha = 1; + ctx1.clearRect(0, 0, canvas1.width, canvas1.height); + return newImageData; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + delta: gl.getUniformLocation(program, 'uDelta'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var delta = this.chooseRightDelta(); + gl.uniform2fv(uniformLocations.delta, delta); + }, + + /** + * choose right value of image percentage to blur with + * @returns {Array} a numeric array with delta values + */ + chooseRightDelta: function() { + var blurScale = 1, delta = [0, 0], blur; + if (this.horizontal) { + if (this.aspectRatio > 1) { + // image is wide, i want to shrink radius horizontal + blurScale = 1 / this.aspectRatio; + } + } + else { + if (this.aspectRatio < 1) { + // image is tall, i want to shrink radius vertical + blurScale = this.aspectRatio; + } + } + blur = blurScale * this.blur * 0.12; + if (this.horizontal) { + delta[0] = blur; + } + else { + delta[1] = blur; + } + return delta; + }, + }); + + /** + * Deserialize a JSON definition of a BlurFilter into a concrete instance. + */ + filters.Blur.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Gamma filter class + * @class fabric.Image.filters.Gamma + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Gamma#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Gamma({ + * gamma: [1, 0.5, 2.1] + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Gamma = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Gamma.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Gamma', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec3 uGamma;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec3 correction = (1.0 / uGamma);\n' + + 'color.r = pow(color.r, correction.r);\n' + + 'color.g = pow(color.g, correction.g);\n' + + 'color.b = pow(color.b, correction.b);\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.rgb *= color.a;\n' + + '}', + + /** + * Gamma array value, from 0.01 to 2.2. + * @param {Array} gamma + * @default + */ + gamma: [1, 1, 1], + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'gamma', + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.gamma = [1, 1, 1]; + filters.BaseFilter.prototype.initialize.call(this, options); + }, + + /** + * Apply the Gamma operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, data = imageData.data, + gamma = this.gamma, len = data.length, + rInv = 1 / gamma[0], gInv = 1 / gamma[1], + bInv = 1 / gamma[2], i; + + if (!this.rVals) { + // eslint-disable-next-line + this.rVals = new Uint8Array(256); + // eslint-disable-next-line + this.gVals = new Uint8Array(256); + // eslint-disable-next-line + this.bVals = new Uint8Array(256); + } + + // This is an optimization - pre-compute a look-up table for each color channel + // instead of performing these pow calls for each pixel in the image. + for (i = 0, len = 256; i < len; i++) { + this.rVals[i] = Math.pow(i / 255, rInv) * 255; + this.gVals[i] = Math.pow(i / 255, gInv) * 255; + this.bVals[i] = Math.pow(i / 255, bInv) * 255; + } + for (i = 0, len = data.length; i < len; i += 4) { + data[i] = this.rVals[data[i]]; + data[i + 1] = this.gVals[data[i + 1]]; + data[i + 2] = this.bVals[data[i + 2]]; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uGamma: gl.getUniformLocation(program, 'uGamma'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform3fv(uniformLocations.uGamma, this.gamma); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Gamma} Instance of fabric.Image.filters.Gamma + */ + fabric.Image.filters.Gamma.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * A container class that knows how to apply a sequence of filters to an input image. + */ + filters.Composed = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Composed.prototype */ { + + type: 'Composed', + + /** + * A non sparse array of filters to apply + */ + subFilters: [], + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.callSuper('initialize', options); + // create a new array instead mutating the prototype with push + this.subFilters = this.subFilters.slice(0); + }, + + /** + * Apply this container's filters to the input image provided. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be applied. + */ + applyTo: function(options) { + options.passes += this.subFilters.length - 1; + this.subFilters.forEach(function(filter) { + filter.applyTo(options); + }); + }, + + /** + * Serialize this filter into JSON. + * + * @returns {Object} A JSON representation of this filter. + */ + toObject: function() { + return fabric.util.object.extend(this.callSuper('toObject'), { + subFilters: this.subFilters.map(function(filter) { return filter.toObject(); }), + }); + }, + + isNeutralState: function() { + return !this.subFilters.some(function(filter) { return !filter.isNeutralState(); }); + } + }); + + /** + * Deserialize a JSON definition of a ComposedFilter into a concrete instance. + */ + fabric.Image.filters.Composed.fromObject = function(object, callback) { + var filters = object.subFilters || [], + subFilters = filters.map(function(filter) { + return new fabric.Image.filters[filter.type](filter); + }), + instance = new fabric.Image.filters.Composed({ subFilters: subFilters }); + callback && callback(instance); + return instance; + }; +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * HueRotation filter class + * @class fabric.Image.filters.HueRotation + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.HueRotation#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.HueRotation({ + * rotation: -0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.HueRotation = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.HueRotation.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'HueRotation', + + /** + * HueRotation value, from -1 to 1. + * the unit is radians + * @param {Number} myParameter + * @default + */ + rotation: 0, + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'rotation', + + calculateMatrix: function() { + var rad = this.rotation * Math.PI, cos = fabric.util.cos(rad), sin = fabric.util.sin(rad), + aThird = 1 / 3, aThirdSqtSin = Math.sqrt(aThird) * sin, OneMinusCos = 1 - cos; + this.matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + this.matrix[0] = cos + OneMinusCos / 3; + this.matrix[1] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[2] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[5] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[6] = cos + aThird * OneMinusCos; + this.matrix[7] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[10] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[11] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[12] = cos + aThird * OneMinusCos; + }, + + /** + * HueRotation isNeutralState implementation + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * @param {Object} options + **/ + isNeutralState: function(options) { + this.calculateMatrix(); + return filters.BaseFilter.prototype.isNeutralState.call(this, options); + }, + + /** + * Apply this filter to the input image data provided. + * + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + this.calculateMatrix(); + filters.BaseFilter.prototype.applyTo.call(this, options); + }, + + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.HueRotation} Instance of fabric.Image.filters.HueRotation + */ + fabric.Image.filters.HueRotation.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})( true ? exports : 0); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + clone = fabric.util.object.clone; + + if (fabric.Text) { + fabric.warn('fabric.Text is already defined'); + return; + } + + var additionalProps = + ('fontFamily fontWeight fontSize text underline overline linethrough' + + ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' + + ' direction path pathStartOffset pathSide').split(' '); + + /** + * Text class + * @class fabric.Text + * @extends fabric.Object + * @return {fabric.Text} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} + * @see {@link fabric.Text#initialize} for constructor definition + */ + fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { + + /** + * Properties which when set cause object to change dimensions + * @type Array + * @private + */ + _dimensionAffectingProps: [ + 'fontSize', + 'fontWeight', + 'fontFamily', + 'fontStyle', + 'lineHeight', + 'text', + 'charSpacing', + 'textAlign', + 'styles', + 'path', + 'pathStartOffset', + 'pathSide' + ], + + /** + * @private + */ + _reNewline: /\r?\n/, + + /** + * Use this regular expression to filter for whitespaces that is not a new line. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reSpacesAndTabs: /[ \t\r]/g, + + /** + * Use this regular expression to filter for whitespace that is not a new line. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reSpaceAndTab: /[ \t\r]/, + + /** + * Use this regular expression to filter consecutive groups of non spaces. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reWords: /\S+/g, + + /** + * Type of an object + * @type String + * @default + */ + type: 'text', + + /** + * Font size (in pixels) + * @type Number + * @default + */ + fontSize: 40, + + /** + * Font weight (e.g. bold, normal, 400, 600, 800) + * @type {(Number|String)} + * @default + */ + fontWeight: 'normal', + + /** + * Font family + * @type String + * @default + */ + fontFamily: 'Times New Roman', + + /** + * Text decoration underline. + * @type Boolean + * @default + */ + underline: false, + + /** + * Text decoration overline. + * @type Boolean + * @default + */ + overline: false, + + /** + * Text decoration linethrough. + * @type Boolean + * @default + */ + linethrough: false, + + /** + * Text alignment. Possible values: "left", "center", "right", "justify", + * "justify-left", "justify-center" or "justify-right". + * @type String + * @default + */ + textAlign: 'left', + + /** + * Font style . Possible values: "", "normal", "italic" or "oblique". + * @type String + * @default + */ + fontStyle: 'normal', + + /** + * Line height + * @type Number + * @default + */ + lineHeight: 1.16, + + /** + * Superscript schema object (minimum overlap) + * @type {Object} + * @default + */ + superscript: { + size: 0.60, // fontSize factor + baseline: -0.35 // baseline-shift factor (upwards) + }, + + /** + * Subscript schema object (minimum overlap) + * @type {Object} + * @default + */ + subscript: { + size: 0.60, // fontSize factor + baseline: 0.11 // baseline-shift factor (downwards) + }, + + /** + * Background color of text lines + * @type String + * @default + */ + textBackgroundColor: '', + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat(additionalProps), + + /** + * List of properties to consider when checking if cache needs refresh + * @type Array + */ + cacheProperties: fabric.Object.prototype.cacheProperties.concat(additionalProps), + + /** + * When defined, an object is rendered via stroke and this property specifies its color. + * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 + * @type String + * @default + */ + stroke: null, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * fabric.Path that the text should follow. + * since 4.6.0 the path will be drawn automatically. + * if you want to make the path visible, give it a stroke and strokeWidth or fill value + * if you want it to be hidden, assign visible = false to the path. + * This feature is in BETA, and SVG import/export is not yet supported. + * @type fabric.Path + * @example + * var textPath = new fabric.Text('Text on a path', { + * top: 150, + * left: 150, + * textAlign: 'center', + * charSpacing: -50, + * path: new fabric.Path('M 0 0 C 50 -100 150 -100 200 0', { + * strokeWidth: 1, + * visible: false + * }), + * pathSide: 'left', + * pathStartOffset: 0 + * }); + * @default + */ + path: null, + + /** + * Offset amount for text path starting position + * Only used when text has a path + * @type Number + * @default + */ + pathStartOffset: 0, + + /** + * Which side of the path the text should be drawn on. + * Only used when text has a path + * @type {String} 'left|right' + * @default + */ + pathSide: 'left', + + /** + * @private + */ + _fontSizeFraction: 0.222, + + /** + * @private + */ + offsets: { + underline: 0.10, + linethrough: -0.315, + overline: -0.88 + }, + + /** + * Text Line proportion to font Size (in pixels) + * @type Number + * @default + */ + _fontSizeMult: 1.13, + + /** + * additional space between characters + * expressed in thousands of em unit + * @type Number + * @default + */ + charSpacing: 0, + + /** + * Object containing character styles - top-level properties -> line numbers, + * 2nd-level properties - character numbers + * @type Object + * @default + */ + styles: null, + + /** + * Reference to a context to measure text char or couple of chars + * the cacheContext of the canvas will be used or a freshly created one if the object is not on canvas + * once created it will be referenced on fabric._measuringContext to avoid creating a canvas for every + * text object created. + * @type {CanvasRenderingContext2D} + * @default + */ + _measuringContext: null, + + /** + * Baseline shift, styles only, keep at 0 for the main text object + * @type {Number} + * @default + */ + deltaY: 0, + + /** + * WARNING: EXPERIMENTAL. NOT SUPPORTED YET + * determine the direction of the text. + * This has to be set manually together with textAlign and originX for proper + * experience. + * some interesting link for the future + * https://www.w3.org/International/questions/qa-bidi-unicode-controls + * @since 4.5.0 + * @type {String} 'ltr|rtl' + * @default + */ + direction: 'ltr', + + /** + * Array of properties that define a style unit (of 'styles'). + * @type {Array} + * @default + */ + _styleProperties: [ + 'stroke', + 'strokeWidth', + 'fill', + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontStyle', + 'underline', + 'overline', + 'linethrough', + 'deltaY', + 'textBackgroundColor', + ], + + /** + * contains characters bounding boxes + */ + __charBounds: [], + + /** + * use this size when measuring text. To avoid IE11 rounding errors + * @type {Number} + * @default + * @readonly + * @private + */ + CACHE_FONT_SIZE: 400, + + /** + * contains the min text width to avoid getting 0 + * @type {Number} + * @default + */ + MIN_TEXT_WIDTH: 2, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + initialize: function(text, options) { + this.styles = options ? (options.styles || { }) : { }; + this.text = text; + this.__skipDimension = true; + this.callSuper('initialize', options); + if (this.path) { + this.setPathInfo(); + } + this.__skipDimension = false; + this.initDimensions(); + this.setCoords(); + this.setupState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * If text has a path, it will add the extra information needed + * for path and text calculations + * @return {fabric.Text} thisArg + */ + setPathInfo: function() { + var path = this.path; + if (path) { + path.segmentsInfo = fabric.util.getPathSegmentsInfo(path.path); + } + }, + + /** + * Return a context for measurement of text string. + * if created it gets stored for reuse + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + getMeasuringContext: function() { + // if we did not return we have to measure something. + if (!fabric._measuringContext) { + fabric._measuringContext = this.canvas && this.canvas.contextCache || + fabric.util.createCanvasElement().getContext('2d'); + } + return fabric._measuringContext; + }, + + /** + * @private + * Divides text into lines of text and lines of graphemes. + */ + _splitText: function() { + var newLines = this._splitTextIntoLines(this.text); + this.textLines = newLines.lines; + this._textLines = newLines.graphemeLines; + this._unwrappedTextLines = newLines._unwrappedLines; + this._text = newLines.graphemeText; + return newLines; + }, + + /** + * Initialize or update text dimensions. + * Updates this.width and this.height with the proper values. + * Does not return dimensions. + */ + initDimensions: function() { + if (this.__skipDimension) { + return; + } + this._splitText(); + this._clearCache(); + if (this.path) { + this.width = this.path.width; + this.height = this.path.height; + } + else { + this.width = this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH; + this.height = this.calcTextHeight(); + } + if (this.textAlign.indexOf('justify') !== -1) { + // once text is measured we need to make space fatter to make justified text. + this.enlargeSpaces(); + } + this.saveState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * Enlarge space boxes and shift the others + */ + enlargeSpaces: function() { + var diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { + continue; + } + accumulatedSpace = 0; + line = this._textLines[i]; + currentLineWidth = this.getLineWidth(i); + if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { + numberOfSpaces = spaces.length; + diffSpace = (this.width - currentLineWidth) / numberOfSpaces; + for (var j = 0, jlen = line.length; j <= jlen; j++) { + charBound = this.__charBounds[i][j]; + if (this._reSpaceAndTab.test(line[j])) { + charBound.width += diffSpace; + charBound.kernedWidth += diffSpace; + charBound.left += accumulatedSpace; + accumulatedSpace += diffSpace; + } + else { + charBound.left += accumulatedSpace; + } + } + } + } + }, + + /** + * Detect if the text line is ended with an hard break + * text and itext do not have wrapping, return false + * @return {Boolean} + */ + isEndOfWrapping: function(lineIndex) { + return lineIndex === this._textLines.length - 1; + }, + + /** + * Detect if a line has a linebreak and so we need to account for it when moving + * and counting style. + * It return always for text and Itext. + * @return Number + */ + missingNewlineOffset: function() { + return 1; + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of text object + */ + toString: function() { + return '#'; + }, + + /** + * Return the dimension and the zoom level needed to create a cache canvas + * big enough to host the object to be cached. + * @private + * @param {Object} dim.x width of object to be cached + * @param {Object} dim.y height of object to be cached + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _getCacheCanvasDimensions: function() { + var dims = this.callSuper('_getCacheCanvasDimensions'); + var fontSize = this.fontSize; + dims.width += fontSize * dims.zoomX; + dims.height += fontSize * dims.zoomY; + return dims; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var path = this.path; + path && !path.isNotVisible() && path._render(ctx); + this._setTextStyles(ctx); + this._renderTextLinesBackground(ctx); + this._renderTextDecoration(ctx, 'underline'); + this._renderText(ctx); + this._renderTextDecoration(ctx, 'overline'); + this._renderTextDecoration(ctx, 'linethrough'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderText: function(ctx) { + if (this.paintFirst === 'stroke') { + this._renderTextStroke(ctx); + this._renderTextFill(ctx); + } + else { + this._renderTextFill(ctx); + this._renderTextStroke(ctx); + } + }, + + /** + * Set the font parameter of the context with the object properties or with charStyle + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [charStyle] object with font style properties + * @param {String} [charStyle.fontFamily] Font Family + * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix ) + * @param {String} [charStyle.fontWeight] Font weight + * @param {String} [charStyle.fontStyle] Font style (italic|normal) + */ + _setTextStyles: function(ctx, charStyle, forMeasuring) { + ctx.textBaseline = 'alphabetic'; + ctx.font = this._getFontDeclaration(charStyle, forMeasuring); + }, + + /** + * calculate and return the text Width measuring each line. + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @return {Number} Maximum width of fabric.Text object + */ + calcTextWidth: function() { + var maxWidth = this.getLineWidth(0); + + for (var i = 1, len = this._textLines.length; i < len; i++) { + var currentLineWidth = this.getLineWidth(i); + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + + /** + * @private + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + * @param {Number} lineIndex Index of a line in a text + */ + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + this._renderChars(method, ctx, line, left, top, lineIndex); + }, + + /** + * Renders the text background for lines, taking care of style + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextLinesBackground: function(ctx) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) { + return; + } + var heightOfLine, + lineLeftOffset, originalFill = ctx.fillStyle, + line, lastColor, + leftOffset = this._getLeftOffset(), + lineTopOffset = this._getTopOffset(), + boxStart = 0, boxWidth = 0, charBox, currentColor, path = this.path, + drawStart; + + for (var i = 0, len = this._textLines.length; i < len; i++) { + heightOfLine = this.getHeightOfLine(i); + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { + lineTopOffset += heightOfLine; + continue; + } + line = this._textLines[i]; + lineLeftOffset = this._getLineLeftOffset(i); + boxWidth = 0; + boxStart = 0; + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (path) { + ctx.save(); + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + ctx.fillStyle = currentColor; + currentColor && ctx.fillRect( + -charBox.width / 2, + -heightOfLine / this.lineHeight * (1 - this._fontSizeFraction), + charBox.width, + heightOfLine / this.lineHeight + ); + ctx.restore(); + } + else if (currentColor !== lastColor) { + drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = lastColor; + lastColor && ctx.fillRect( + drawStart, + lineTopOffset, + boxWidth, + heightOfLine / this.lineHeight + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } + else { + boxWidth += charBox.kernedWidth; + } + } + if (currentColor && !path) { + drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = currentColor; + ctx.fillRect( + drawStart, + lineTopOffset, + boxWidth, + heightOfLine / this.lineHeight + ); + } + lineTopOffset += heightOfLine; + } + ctx.fillStyle = originalFill; + // if there is text background color no + // other shadows should be casted + this._removeShadow(ctx); + }, + + /** + * @private + * @param {Object} decl style declaration for cache + * @param {String} decl.fontFamily fontFamily + * @param {String} decl.fontStyle fontStyle + * @param {String} decl.fontWeight fontWeight + * @return {Object} reference to cache + */ + getFontCache: function(decl) { + var fontFamily = decl.fontFamily.toLowerCase(); + if (!fabric.charWidthsCache[fontFamily]) { + fabric.charWidthsCache[fontFamily] = { }; + } + var cache = fabric.charWidthsCache[fontFamily], + cacheProp = decl.fontStyle.toLowerCase() + '_' + (decl.fontWeight + '').toLowerCase(); + if (!cache[cacheProp]) { + cache[cacheProp] = { }; + } + return cache[cacheProp]; + }, + + /** + * measure and return the width of a single character. + * possibly overridden to accommodate different measure logic or + * to hook some external lib for character measurement + * @private + * @param {String} _char, char to be measured + * @param {Object} charStyle style of char to be measured + * @param {String} [previousChar] previous char + * @param {Object} [prevCharStyle] style of previous char + */ + _measureChar: function(_char, charStyle, previousChar, prevCharStyle) { + // first i try to return from cache + var fontCache = this.getFontCache(charStyle), fontDeclaration = this._getFontDeclaration(charStyle), + previousFontDeclaration = this._getFontDeclaration(prevCharStyle), couple = previousChar + _char, + stylesAreEqual = fontDeclaration === previousFontDeclaration, width, coupleWidth, previousWidth, + fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE, kernedWidth; + + if (previousChar && fontCache[previousChar] !== undefined) { + previousWidth = fontCache[previousChar]; + } + if (fontCache[_char] !== undefined) { + kernedWidth = width = fontCache[_char]; + } + if (stylesAreEqual && fontCache[couple] !== undefined) { + coupleWidth = fontCache[couple]; + kernedWidth = coupleWidth - previousWidth; + } + if (width === undefined || previousWidth === undefined || coupleWidth === undefined) { + var ctx = this.getMeasuringContext(); + // send a TRUE to specify measuring font size CACHE_FONT_SIZE + this._setTextStyles(ctx, charStyle, true); + } + if (width === undefined) { + kernedWidth = width = ctx.measureText(_char).width; + fontCache[_char] = width; + } + if (previousWidth === undefined && stylesAreEqual && previousChar) { + previousWidth = ctx.measureText(previousChar).width; + fontCache[previousChar] = previousWidth; + } + if (stylesAreEqual && coupleWidth === undefined) { + // we can measure the kerning couple and subtract the width of the previous character + coupleWidth = ctx.measureText(couple).width; + fontCache[couple] = coupleWidth; + kernedWidth = coupleWidth - previousWidth; + } + return { width: width * fontMultiplier, kernedWidth: kernedWidth * fontMultiplier }; + }, + + /** + * Computes height of character at given position + * @param {Number} line the line index number + * @param {Number} _char the character index number + * @return {Number} fontSize of the character + */ + getHeightOfChar: function(line, _char) { + return this.getValueOfPropertyAt(line, _char, 'fontSize'); + }, + + /** + * measure a text line measuring all characters. + * @param {Number} lineIndex line number + * @return {Number} Line width + */ + measureLine: function(lineIndex) { + var lineInfo = this._measureLine(lineIndex); + if (this.charSpacing !== 0) { + lineInfo.width -= this._getWidthOfCharSpacing(); + } + if (lineInfo.width < 0) { + lineInfo.width = 0; + } + return lineInfo; + }, + + /** + * measure every grapheme of a line, populating __charBounds + * @param {Number} lineIndex + * @return {Object} object.width total width of characters + * @return {Object} object.widthOfSpaces length of chars that match this._reSpacesAndTabs + */ + _measureLine: function(lineIndex) { + var width = 0, i, grapheme, line = this._textLines[lineIndex], prevGrapheme, + graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length), + positionInPath = 0, startingPoint, totalPathLength, path = this.path, + reverse = this.pathSide === 'right'; + + this.__charBounds[lineIndex] = lineBounds; + for (i = 0; i < line.length; i++) { + grapheme = line[i]; + graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme); + lineBounds[i] = graphemeInfo; + width += graphemeInfo.kernedWidth; + prevGrapheme = grapheme; + } + // this latest bound box represent the last character of the line + // to simplify cursor handling in interactive mode. + lineBounds[i] = { + left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, + width: 0, + kernedWidth: 0, + height: this.fontSize + }; + if (path) { + totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length; + startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo); + startingPoint.x += path.pathOffset.x; + startingPoint.y += path.pathOffset.y; + switch (this.textAlign) { + case 'left': + positionInPath = reverse ? (totalPathLength - width) : 0; + break; + case 'center': + positionInPath = (totalPathLength - width) / 2; + break; + case 'right': + positionInPath = reverse ? 0 : (totalPathLength - width); + break; + //todo - add support for justify + } + positionInPath += this.pathStartOffset * (reverse ? -1 : 1); + for (i = reverse ? line.length - 1 : 0; + reverse ? i >= 0 : i < line.length; + reverse ? i-- : i++) { + graphemeInfo = lineBounds[i]; + if (positionInPath > totalPathLength) { + positionInPath %= totalPathLength; + } + else if (positionInPath < 0) { + positionInPath += totalPathLength; + } + // it would probably much faster to send all the grapheme position for a line + // and calculate path position/angle at once. + this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint); + positionInPath += graphemeInfo.kernedWidth; + } + } + return { width: width, numOfSpaces: numOfSpaces }; + }, + + /** + * Calculate the angle and the left,top position of the char that follow a path. + * It appends it to graphemeInfo to be reused later at rendering + * @private + * @param {Number} positionInPath to be measured + * @param {Object} graphemeInfo current grapheme box information + * @param {Object} startingPoint position of the point + */ + _setGraphemeOnPath: function(positionInPath, graphemeInfo, startingPoint) { + var centerPosition = positionInPath + graphemeInfo.kernedWidth / 2, + path = this.path; + + // we are at currentPositionOnPath. we want to know what point on the path is. + var info = fabric.util.getPointOnPath(path.path, centerPosition, path.segmentsInfo); + graphemeInfo.renderLeft = info.x - startingPoint.x; + graphemeInfo.renderTop = info.y - startingPoint.y; + graphemeInfo.angle = info.angle + (this.pathSide === 'right' ? Math.PI : 0); + }, + + /** + * Measure and return the info of a single grapheme. + * needs the the info of previous graphemes already filled + * @private + * @param {String} grapheme to be measured + * @param {Number} lineIndex index of the line where the char is + * @param {Number} charIndex position in the line + * @param {String} [prevGrapheme] character preceding the one to be measured + */ + _getGraphemeBox: function(grapheme, lineIndex, charIndex, prevGrapheme, skipLeft) { + var style = this.getCompleteStyleDeclaration(lineIndex, charIndex), + prevStyle = prevGrapheme ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) : { }, + info = this._measureChar(grapheme, style, prevGrapheme, prevStyle), + kernedWidth = info.kernedWidth, + width = info.width, charSpacing; + + if (this.charSpacing !== 0) { + charSpacing = this._getWidthOfCharSpacing(); + width += charSpacing; + kernedWidth += charSpacing; + } + + var box = { + width: width, + left: 0, + height: style.fontSize, + kernedWidth: kernedWidth, + deltaY: style.deltaY, + }; + if (charIndex > 0 && !skipLeft) { + var previousBox = this.__charBounds[lineIndex][charIndex - 1]; + box.left = previousBox.left + previousBox.width + info.kernedWidth - info.width; + } + return box; + }, + + /** + * Calculate height of line at 'lineIndex' + * @param {Number} lineIndex index of line to calculate + * @return {Number} + */ + getHeightOfLine: function(lineIndex) { + if (this.__lineHeights[lineIndex]) { + return this.__lineHeights[lineIndex]; + } + + var line = this._textLines[lineIndex], + // char 0 is measured before the line cycle because it nneds to char + // emptylines + maxHeight = this.getHeightOfChar(lineIndex, 0); + for (var i = 1, len = line.length; i < len; i++) { + maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); + } + + return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; + }, + + /** + * Calculate text box height + */ + calcTextHeight: function() { + var lineHeight, height = 0; + for (var i = 0, len = this._textLines.length; i < len; i++) { + lineHeight = this.getHeightOfLine(i); + height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); + } + return height; + }, + + /** + * @private + * @return {Number} Left offset + */ + _getLeftOffset: function() { + return this.direction === 'ltr' ? -this.width / 2 : this.width / 2; + }, + + /** + * @private + * @return {Number} Top offset + */ + _getTopOffset: function() { + return -this.height / 2; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} method Method name ("fillText" or "strokeText") + */ + _renderTextCommon: function(ctx, method) { + ctx.save(); + var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset(); + for (var i = 0, len = this._textLines.length; i < len; i++) { + var heightOfLine = this.getHeightOfLine(i), + maxHeight = heightOfLine / this.lineHeight, + leftOffset = this._getLineLeftOffset(i); + this._renderTextLine( + method, + ctx, + this._textLines[i], + left + leftOffset, + top + lineHeights + maxHeight, + i + ); + lineHeights += heightOfLine; + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextFill: function(ctx) { + if (!this.fill && !this.styleHas('fill')) { + return; + } + + this._renderTextCommon(ctx, 'fillText'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextStroke: function(ctx) { + if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { + return; + } + + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + + ctx.save(); + this._setLineDash(ctx, this.strokeDashArray); + ctx.beginPath(); + this._renderTextCommon(ctx, 'strokeText'); + ctx.closePath(); + ctx.restore(); + }, + + /** + * @private + * @param {String} method fillText or strokeText. + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} line Content of the line, splitted in an array by grapheme + * @param {Number} left + * @param {Number} top + * @param {Number} lineIndex + */ + _renderChars: function(method, ctx, line, left, top, lineIndex) { + // set proper line offset + var lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, + boxWidth = 0, + timeToRender, + path = this.path, + shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path, + isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1, + drawingLeft; + + ctx.save(); + top -= lineHeight * this._fontSizeFraction / this.lineHeight; + if (shortCut) { + // render all the line in one pass without checking + // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex); + ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); + ctx.direction = isLtr ? 'ltr' : 'rtl'; + ctx.textAlign = isLtr ? 'left' : 'right'; + this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight); + ctx.restore(); + return; + } + for (var i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing || path; + charsToRender += line[i]; + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + left += sign * (charBox.kernedWidth - charBox.width); + boxWidth += charBox.width; + } + else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + // if we have charSpacing, we render char by char + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChanged(actualStyle, nextStyle); + } + if (timeToRender) { + if (path) { + ctx.save(); + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + this._renderChar(method, ctx, lineIndex, i, charsToRender, -boxWidth / 2, 0, lineHeight); + ctx.restore(); + } + else { + drawingLeft = left; + ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); + ctx.direction = isLtr ? 'ltr' : 'rtl'; + ctx.textAlign = isLtr ? 'left' : 'right'; + this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight); + } + charsToRender = ''; + actualStyle = nextStyle; + left += sign * boxWidth; + boxWidth = 0; + } + } + ctx.restore(); + }, + + /** + * This function try to patch the missing gradientTransform on canvas gradients. + * transforming a context to transform the gradient, is going to transform the stroke too. + * we want to transform the gradient but not the stroke operation, so we create + * a transformed gradient on a pattern and then we use the pattern instead of the gradient. + * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size + * is limited. + * @private + * @param {fabric.Gradient} filler a fabric gradient instance + * @return {CanvasPattern} a pattern to use as fill/stroke style + */ + _applyPatternGradientTransformText: function(filler) { + var pCanvas = fabric.util.createCanvasElement(), pCtx, + // TODO: verify compatibility with strokeUniform + width = this.width + this.strokeWidth, height = this.height + this.strokeWidth; + pCanvas.width = width; + pCanvas.height = height; + pCtx = pCanvas.getContext('2d'); + pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); + pCtx.lineTo(0, height); pCtx.closePath(); + pCtx.translate(width / 2, height / 2); + pCtx.fillStyle = filler.toLive(pCtx); + this._applyPatternGradientTransform(pCtx, filler); + pCtx.fill(); + return pCtx.createPattern(pCanvas, 'no-repeat'); + }, + + handleFiller: function(ctx, property, filler) { + var offsetX, offsetY; + if (filler.toLive) { + if (filler.gradientUnits === 'percentage' || filler.gradientTransform || filler.patternTransform) { + // need to transform gradient in a pattern. + // this is a slow process. If you are hitting this codepath, and the object + // is not using caching, you should consider switching it on. + // we need a canvas as big as the current object caching canvas. + offsetX = -this.width / 2; + offsetY = -this.height / 2; + ctx.translate(offsetX, offsetY); + ctx[property] = this._applyPatternGradientTransformText(filler); + return { offsetX: offsetX, offsetY: offsetY }; + } + else { + // is a simple gradient or pattern + ctx[property] = filler.toLive(ctx, this); + return this._applyPatternGradientTransform(ctx, filler); + } + } + else { + // is a color + ctx[property] = filler; + } + return { offsetX: 0, offsetY: 0 }; + }, + + _setStrokeStyles: function(ctx, decl) { + ctx.lineWidth = decl.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineDashOffset = this.strokeDashOffset; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + return this.handleFiller(ctx, 'strokeStyle', decl.stroke); + }, + + _setFillStyles: function(ctx, decl) { + return this.handleFiller(ctx, 'fillStyle', decl.fill); + }, + + /** + * @private + * @param {String} method + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {String} _char + * @param {Number} left Left coordinate + * @param {Number} top Top coordinate + * @param {Number} lineHeight Height of the line + */ + _renderChar: function(method, ctx, lineIndex, charIndex, _char, left, top) { + var decl = this._getStyleDeclaration(lineIndex, charIndex), + fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), + shouldFill = method === 'fillText' && fullDecl.fill, + shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth, + fillOffsets, strokeOffsets; + + if (!shouldStroke && !shouldFill) { + return; + } + ctx.save(); + + shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); + shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); + + ctx.font = this._getFontDeclaration(fullDecl); + + + if (decl && decl.textBackgroundColor) { + this._removeShadow(ctx); + } + if (decl && decl.deltaY) { + top += decl.deltaY; + } + shouldFill && ctx.fillText(_char, left - fillOffsets.offsetX, top - fillOffsets.offsetY); + shouldStroke && ctx.strokeText(_char, left - strokeOffsets.offsetX, top - strokeOffsets.offsetY); + ctx.restore(); + }, + + /** + * Turns the character into a 'superior figure' (i.e. 'superscript') + * @param {Number} start selection start + * @param {Number} end selection end + * @returns {fabric.Text} thisArg + * @chainable + */ + setSuperscript: function(start, end) { + return this._setScript(start, end, this.superscript); + }, + + /** + * Turns the character into an 'inferior figure' (i.e. 'subscript') + * @param {Number} start selection start + * @param {Number} end selection end + * @returns {fabric.Text} thisArg + * @chainable + */ + setSubscript: function(start, end) { + return this._setScript(start, end, this.subscript); + }, + + /** + * Applies 'schema' at given position + * @private + * @param {Number} start selection start + * @param {Number} end selection end + * @param {Number} schema + * @returns {fabric.Text} thisArg + * @chainable + */ + _setScript: function(start, end, schema) { + var loc = this.get2DCursorLocation(start, true), + fontSize = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'fontSize'), + dy = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'deltaY'), + style = { fontSize: fontSize * schema.size, deltaY: dy + fontSize * schema.baseline }; + this.setSelectionStyles(style, start, end); + return this; + }, + + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChanged: function(prevStyle, thisStyle) { + return prevStyle.fill !== thisStyle.fill || + prevStyle.stroke !== thisStyle.stroke || + prevStyle.strokeWidth !== thisStyle.strokeWidth || + prevStyle.fontSize !== thisStyle.fontSize || + prevStyle.fontFamily !== thisStyle.fontFamily || + prevStyle.fontWeight !== thisStyle.fontWeight || + prevStyle.fontStyle !== thisStyle.fontStyle || + prevStyle.deltaY !== thisStyle.deltaY; + }, + + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChangedForSvg: function(prevStyle, thisStyle) { + return this._hasStyleChanged(prevStyle, thisStyle) || + prevStyle.overline !== thisStyle.overline || + prevStyle.underline !== thisStyle.underline || + prevStyle.linethrough !== thisStyle.linethrough; + }, + + /** + * @private + * @param {Number} lineIndex index text line + * @return {Number} Line left offset + */ + _getLineLeftOffset: function(lineIndex) { + var lineWidth = this.getLineWidth(lineIndex), + lineDiff = this.width - lineWidth, textAlign = this.textAlign, direction = this.direction, + isEndOfWrapping, leftOffset = 0, isEndOfWrapping = this.isEndOfWrapping(lineIndex); + if (textAlign === 'justify' + || (textAlign === 'justify-center' && !isEndOfWrapping) + || (textAlign === 'justify-right' && !isEndOfWrapping) + || (textAlign === 'justify-left' && !isEndOfWrapping) + ) { + return 0; + } + if (textAlign === 'center') { + leftOffset = lineDiff / 2; + } + if (textAlign === 'right') { + leftOffset = lineDiff; + } + if (textAlign === 'justify-center') { + leftOffset = lineDiff / 2; + } + if (textAlign === 'justify-right') { + leftOffset = lineDiff; + } + if (direction === 'rtl') { + leftOffset -= lineDiff; + } + return leftOffset; + }, + + /** + * @private + */ + _clearCache: function() { + this.__lineWidths = []; + this.__lineHeights = []; + this.__charBounds = []; + }, + + /** + * @private + */ + _shouldClearDimensionCache: function() { + var shouldClear = this._forceClearCache; + shouldClear || (shouldClear = this.hasStateChanged('_dimensionAffectingProps')); + if (shouldClear) { + this.dirty = true; + this._forceClearCache = false; + } + return shouldClear; + }, + + /** + * Measure a single line given its index. Used to calculate the initial + * text bounding box. The values are calculated and stored in __lineWidths cache. + * @private + * @param {Number} lineIndex line number + * @return {Number} Line width + */ + getLineWidth: function(lineIndex) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + + var width, line = this._textLines[lineIndex], lineInfo; + + if (line === '') { + width = 0; + } + else { + lineInfo = this.measureLine(lineIndex); + width = lineInfo.width; + } + this.__lineWidths[lineIndex] = width; + return width; + }, + + _getWidthOfCharSpacing: function() { + if (this.charSpacing !== 0) { + return this.fontSize * this.charSpacing / 1000; + } + return 0; + }, + + /** + * Retrieves the value of property at given character position + * @param {Number} lineIndex the line number + * @param {Number} charIndex the character number + * @param {String} property the property name + * @returns the value of 'property' + */ + getValueOfPropertyAt: function(lineIndex, charIndex, property) { + var charStyle = this._getStyleDeclaration(lineIndex, charIndex); + if (charStyle && typeof charStyle[property] !== 'undefined') { + return charStyle[property]; + } + return this[property]; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextDecoration: function(ctx, type) { + if (!this[type] && !this.styleHas(type)) { + return; + } + var heightOfLine, size, _size, + lineLeftOffset, dy, _dy, + line, lastDecoration, + leftOffset = this._getLeftOffset(), + topOffset = this._getTopOffset(), top, + boxStart, boxWidth, charBox, currentDecoration, + maxHeight, currentFill, lastFill, path = this.path, + charSpacing = this._getWidthOfCharSpacing(), + offsetY = this.offsets[type]; + + for (var i = 0, len = this._textLines.length; i < len; i++) { + heightOfLine = this.getHeightOfLine(i); + if (!this[type] && !this.styleHas(type, i)) { + topOffset += heightOfLine; + continue; + } + line = this._textLines[i]; + maxHeight = heightOfLine / this.lineHeight; + lineLeftOffset = this._getLineLeftOffset(i); + boxStart = 0; + boxWidth = 0; + lastDecoration = this.getValueOfPropertyAt(i, 0, type); + lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); + top = topOffset + maxHeight * (1 - this._fontSizeFraction); + size = this.getHeightOfChar(i, 0); + dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentDecoration = this.getValueOfPropertyAt(i, j, type); + currentFill = this.getValueOfPropertyAt(i, j, 'fill'); + _size = this.getHeightOfChar(i, j); + _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); + if (path && currentDecoration && currentFill) { + ctx.save(); + ctx.fillStyle = lastFill; + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + ctx.fillRect( + -charBox.kernedWidth / 2, + offsetY * _size + _dy, + charBox.kernedWidth, + this.fontSize / 15 + ); + ctx.restore(); + } + else if ( + (currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy) + && boxWidth > 0 + ) { + var drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + if (lastDecoration && lastFill) { + ctx.fillStyle = lastFill; + ctx.fillRect( + drawStart, + top + offsetY * size + dy, + boxWidth, + this.fontSize / 15 + ); + } + boxStart = charBox.left; + boxWidth = charBox.width; + lastDecoration = currentDecoration; + lastFill = currentFill; + size = _size; + dy = _dy; + } + else { + boxWidth += charBox.kernedWidth; + } + } + var drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = currentFill; + currentDecoration && currentFill && ctx.fillRect( + drawStart, + top + offsetY * size + dy, + boxWidth - charSpacing, + this.fontSize / 15 + ); + topOffset += heightOfLine; + } + // if there is text background color no + // other shadows should be casted + this._removeShadow(ctx); + }, + + /** + * return font declaration string for canvas context + * @param {Object} [styleObject] object + * @returns {String} font declaration formatted for canvas context. + */ + _getFontDeclaration: function(styleObject, forMeasuring) { + var style = styleObject || this, family = this.fontFamily, + fontIsGeneric = fabric.Text.genericFonts.indexOf(family.toLowerCase()) > -1; + var fontFamily = family === undefined || + family.indexOf('\'') > -1 || family.indexOf(',') > -1 || + family.indexOf('"') > -1 || fontIsGeneric + ? style.fontFamily : '"' + style.fontFamily + '"'; + return [ + // node-canvas needs "weight style", while browsers need "style weight" + // verify if this can be fixed in JSDOM + (fabric.isLikelyNode ? style.fontWeight : style.fontStyle), + (fabric.isLikelyNode ? style.fontStyle : style.fontWeight), + forMeasuring ? this.CACHE_FONT_SIZE + 'px' : style.fontSize + 'px', + fontFamily + ].join(' '); + }, + + /** + * Renders text instance on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { + return; + } + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + } + this.callSuper('render', ctx); + }, + + /** + * Returns the text as an array of lines. + * @param {String} text text to split + * @returns {Array} Lines in the text + */ + _splitTextIntoLines: function(text) { + var lines = text.split(this._reNewline), + newLines = new Array(lines.length), + newLine = ['\n'], + newText = []; + for (var i = 0; i < lines.length; i++) { + newLines[i] = fabric.util.string.graphemeSplit(lines[i]); + newText = newText.concat(newLines[i], newLine); + } + newText.pop(); + return { _unwrappedLines: newLines, lines: lines, graphemeText: newText, graphemeLines: newLines }; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var allProperties = additionalProps.concat(propertiesToInclude); + var obj = this.callSuper('toObject', allProperties); + // styles will be overridden with a properly cloned structure + obj.styles = clone(this.styles, true); + if (obj.path) { + obj.path = this.path.toObject(); + } + return obj; + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + this.callSuper('set', key, value); + var needsDims = false; + var isAddingPath = false; + if (typeof key === 'object') { + for (var _key in key) { + if (_key === 'path') { + this.setPathInfo(); + } + needsDims = needsDims || this._dimensionAffectingProps.indexOf(_key) !== -1; + isAddingPath = isAddingPath || _key === 'path'; + } + } + else { + needsDims = this._dimensionAffectingProps.indexOf(key) !== -1; + isAddingPath = key === 'path'; + } + if (isAddingPath) { + this.setPathInfo(); + } + if (needsDims) { + this.initDimensions(); + this.setCoords(); + } + return this; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) + * @static + * @memberOf fabric.Text + * @see: http://www.w3.org/TR/SVG/text.html#TextElement + */ + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( + 'x y dx dy font-family font-style font-weight font-size letter-spacing text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + + /** + * Returns fabric.Text instance from an SVG element (not yet implemented) + * @static + * @memberOf fabric.Text + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Text.fromElement = function(element, callback, options) { + if (!element) { + return callback(null); + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES), + parsedAnchor = parsedAttributes.textAnchor || 'left'; + options = fabric.util.object.extend((options ? clone(options) : { }), parsedAttributes); + + options.top = options.top || 0; + options.left = options.left || 0; + if (parsedAttributes.textDecoration) { + var textDecoration = parsedAttributes.textDecoration; + if (textDecoration.indexOf('underline') !== -1) { + options.underline = true; + } + if (textDecoration.indexOf('overline') !== -1) { + options.overline = true; + } + if (textDecoration.indexOf('line-through') !== -1) { + options.linethrough = true; + } + delete options.textDecoration; + } + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + var textContent = ''; + + // The XML is not properly parsed in IE9 so a workaround to get + // textContent is through firstChild.data. Another workaround would be + // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) + if (!('textContent' in element)) { + if ('firstChild' in element && element.firstChild !== null) { + if ('data' in element.firstChild && element.firstChild.data !== null) { + textContent = element.firstChild.data; + } + } + } + else { + textContent = element.textContent; + } + + textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '); + var originalStrokeWidth = options.strokeWidth; + options.strokeWidth = 0; + + var text = new fabric.Text(textContent, options), + textHeightScaleFactor = text.getScaledHeight() / text.height, + lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height, + scaledDiff = lineHeightDiff * textHeightScaleFactor, + textHeight = text.getScaledHeight() + scaledDiff, + offX = 0; + /* + Adjust positioning: + x/y attributes in SVG correspond to the bottom-left corner of text bounding box + fabric output by default at top, left. + */ + if (parsedAnchor === 'center') { + offX = text.getScaledWidth() / 2; + } + if (parsedAnchor === 'right') { + offX = text.getScaledWidth(); + } + text.set({ + left: text.left - offX, + top: text.top - (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) / text.lineHeight, + strokeWidth: typeof originalStrokeWidth !== 'undefined' ? originalStrokeWidth : 1, + }); + callback(text); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Text instance from an object representation + * @static + * @memberOf fabric.Text + * @param {Object} object plain js Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created + */ + fabric.Text.fromObject = function(object, callback) { + var objectCopy = clone(object), path = object.path; + delete objectCopy.path; + return fabric.Object._fromObject('Text', objectCopy, function(textInstance) { + if (path) { + fabric.Object._fromObject('Path', path, function(pathInstance) { + textInstance.set('path', pathInstance); + callback(textInstance); + }, 'path'); + } + else { + callback(textInstance); + } + }, 'text'); + }; + + fabric.Text.genericFonts = ['sans-serif', 'serif', 'cursive', 'fantasy', 'monospace']; + + fabric.util.createAccessors && fabric.util.createAccessors(fabric.Text); + +})( true ? exports : 0); + + +(function() { + fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { + /** + * Returns true if object has no styling or no styling in a line + * @param {Number} lineIndex , lineIndex is on wrapped lines. + * @return {Boolean} + */ + isEmptyStyles: function(lineIndex) { + if (!this.styles) { + return true; + } + if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { + return true; + } + var obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + // eslint-disable-next-line no-unused-vars + for (var p3 in obj[p1][p2]) { + return false; + } + } + } + return true; + }, + + /** + * Returns true if object has a style property or has it ina specified line + * This function is used to detect if a text will use a particular property or not. + * @param {String} property to check for + * @param {Number} lineIndex to check the style on + * @return {Boolean} + */ + styleHas: function(property, lineIndex) { + if (!this.styles || !property || property === '') { + return false; + } + if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { + return false; + } + var obj = typeof lineIndex === 'undefined' ? this.styles : { 0: this.styles[lineIndex] }; + // eslint-disable-next-line + for (var p1 in obj) { + // eslint-disable-next-line + for (var p2 in obj[p1]) { + if (typeof obj[p1][p2][property] !== 'undefined') { + return true; + } + } + } + return false; + }, + + /** + * Check if characters in a text have a value for a property + * whose value matches the textbox's value for that property. If so, + * the character-level property is deleted. If the character + * has no other properties, then it is also deleted. Finally, + * if the line containing that character has no other characters + * then it also is deleted. + * + * @param {string} property The property to compare between characters and text. + */ + cleanStyle: function(property) { + if (!this.styles || !property || property === '') { + return false; + } + var obj = this.styles, stylesCount = 0, letterCount, stylePropertyValue, + allStyleObjectPropertiesMatch = true, graphemeCount = 0, styleObject; + // eslint-disable-next-line + for (var p1 in obj) { + letterCount = 0; + // eslint-disable-next-line + for (var p2 in obj[p1]) { + var styleObject = obj[p1][p2], + stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); + + stylesCount++; + + if (stylePropertyHasBeenSet) { + if (!stylePropertyValue) { + stylePropertyValue = styleObject[property]; + } + else if (styleObject[property] !== stylePropertyValue) { + allStyleObjectPropertiesMatch = false; + } + + if (styleObject[property] === this[property]) { + delete styleObject[property]; + } + } + else { + allStyleObjectPropertiesMatch = false; + } + + if (Object.keys(styleObject).length !== 0) { + letterCount++; + } + else { + delete obj[p1][p2]; + } + } + + if (letterCount === 0) { + delete obj[p1]; + } + } + // if every grapheme has the same style set then + // delete those styles and set it on the parent + for (var i = 0; i < this._textLines.length; i++) { + graphemeCount += this._textLines[i].length; + } + if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { + this[property] = stylePropertyValue; + this.removeStyle(property); + } + }, + + /** + * Remove a style property or properties from all individual character styles + * in a text object. Deletes the character style object if it contains no other style + * props. Deletes a line style object if it contains no other character styles. + * + * @param {String} props The property to remove from character styles. + */ + removeStyle: function(property) { + if (!this.styles || !property || property === '') { + return; + } + var obj = this.styles, line, lineNum, charNum; + for (lineNum in obj) { + line = obj[lineNum]; + for (charNum in line) { + delete line[charNum][property]; + if (Object.keys(line[charNum]).length === 0) { + delete line[charNum]; + } + } + if (Object.keys(line).length === 0) { + delete obj[lineNum]; + } + } + }, + + /** + * @private + */ + _extendStyles: function(index, styles) { + var loc = this.get2DCursorLocation(index); + + if (!this._getLineStyle(loc.lineIndex)) { + this._setLineStyle(loc.lineIndex); + } + + if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { + this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); + } + + fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); + }, + + /** + * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) + * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. + * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles. + */ + get2DCursorLocation: function(selectionStart, skipWrapping) { + if (typeof selectionStart === 'undefined') { + selectionStart = this.selectionStart; + } + var lines = skipWrapping ? this._unwrappedTextLines : this._textLines, + len = lines.length; + for (var i = 0; i < len; i++) { + if (selectionStart <= lines[i].length) { + return { + lineIndex: i, + charIndex: selectionStart + }; + } + selectionStart -= lines[i].length + this.missingNewlineOffset(i); + } + return { + lineIndex: i - 1, + charIndex: lines[i - 1].length < selectionStart ? lines[i - 1].length : selectionStart + }; + }, + + /** + * Gets style of a current selection/cursor (at the start position) + * if startIndex or endIndex are not provided, selectionStart or selectionEnd will be used. + * @param {Number} [startIndex] Start index to get styles at + * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 + * @param {Boolean} [complete] get full style or not + * @return {Array} styles an array with one, zero or more Style objects + */ + getSelectionStyles: function(startIndex, endIndex, complete) { + if (typeof startIndex === 'undefined') { + startIndex = this.selectionStart || 0; + } + if (typeof endIndex === 'undefined') { + endIndex = this.selectionEnd || startIndex; + } + var styles = []; + for (var i = startIndex; i < endIndex; i++) { + styles.push(this.getStyleAtPosition(i, complete)); + } + return styles; + }, + + /** + * Gets style of a current selection/cursor position + * @param {Number} position to get styles at + * @param {Boolean} [complete] full style if true + * @return {Object} style Style object at a specified index + * @private + */ + getStyleAtPosition: function(position, complete) { + var loc = this.get2DCursorLocation(position), + style = complete ? this.getCompleteStyleDeclaration(loc.lineIndex, loc.charIndex) : + this._getStyleDeclaration(loc.lineIndex, loc.charIndex); + return style || {}; + }, + + /** + * Sets style of a current selection, if no selection exist, do not set anything. + * @param {Object} [styles] Styles object + * @param {Number} [startIndex] Start index to get styles at + * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 + * @return {fabric.IText} thisArg + * @chainable + */ + setSelectionStyles: function(styles, startIndex, endIndex) { + if (typeof startIndex === 'undefined') { + startIndex = this.selectionStart || 0; + } + if (typeof endIndex === 'undefined') { + endIndex = this.selectionEnd || startIndex; + } + for (var i = startIndex; i < endIndex; i++) { + this._extendStyles(i, styles); + } + /* not included in _extendStyles to avoid clearing cache more than once */ + this._forceClearCache = true; + return this; + }, + + /** + * get the reference, not a clone, of the style object for a given character + * @param {Number} lineIndex + * @param {Number} charIndex + * @return {Object} style object + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + var lineStyle = this.styles && this.styles[lineIndex]; + if (!lineStyle) { + return null; + } + return lineStyle[charIndex]; + }, + + /** + * return a new object that contains all the style property for a character + * the object returned is newly created + * @param {Number} lineIndex of the line where the character is + * @param {Number} charIndex position of the character on the line + * @return {Object} style object + */ + getCompleteStyleDeclaration: function(lineIndex, charIndex) { + var style = this._getStyleDeclaration(lineIndex, charIndex) || { }, + styleObject = { }, prop; + for (var i = 0; i < this._styleProperties.length; i++) { + prop = this._styleProperties[i]; + styleObject[prop] = typeof style[prop] === 'undefined' ? this[prop] : style[prop]; + } + return styleObject; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {Object} style + * @private + */ + _setStyleDeclaration: function(lineIndex, charIndex, style) { + this.styles[lineIndex][charIndex] = style; + }, + + /** + * + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _deleteStyleDeclaration: function(lineIndex, charIndex) { + delete this.styles[lineIndex][charIndex]; + }, + + /** + * @param {Number} lineIndex + * @return {Boolean} if the line exists or not + * @private + */ + _getLineStyle: function(lineIndex) { + return !!this.styles[lineIndex]; + }, + + /** + * Set the line style to an empty object so that is initialized + * @param {Number} lineIndex + * @private + */ + _setLineStyle: function(lineIndex) { + this.styles[lineIndex] = {}; + }, + + /** + * @param {Number} lineIndex + * @private + */ + _deleteLineStyle: function(lineIndex) { + delete this.styles[lineIndex]; + } + }); +})(); + + +(function() { + + function parseDecoration(object) { + if (object.textDecoration) { + object.textDecoration.indexOf('underline') > -1 && (object.underline = true); + object.textDecoration.indexOf('line-through') > -1 && (object.linethrough = true); + object.textDecoration.indexOf('overline') > -1 && (object.overline = true); + delete object.textDecoration; + } + } + + /** + * IText class (introduced in v1.4) Events are also fired with "text:" + * prefix when observing canvas. + * @class fabric.IText + * @extends fabric.Text + * @mixes fabric.Observable + * + * @fires changed + * @fires selection:changed + * @fires editing:entered + * @fires editing:exited + * + * @return {fabric.IText} thisArg + * @see {@link fabric.IText#initialize} for constructor definition + * + *

    Supported key combinations:

    + *
    +   *   Move cursor:                    left, right, up, down
    +   *   Select character:               shift + left, shift + right
    +   *   Select text vertically:         shift + up, shift + down
    +   *   Move cursor by word:            alt + left, alt + right
    +   *   Select words:                   shift + alt + left, shift + alt + right
    +   *   Move cursor to line start/end:  cmd + left, cmd + right or home, end
    +   *   Select till start/end of line:  cmd + shift + left, cmd + shift + right or shift + home, shift + end
    +   *   Jump to start/end of text:      cmd + up, cmd + down
    +   *   Select till start/end of text:  cmd + shift + up, cmd + shift + down or shift + pgUp, shift + pgDown
    +   *   Delete character:               backspace
    +   *   Delete word:                    alt + backspace
    +   *   Delete line:                    cmd + backspace
    +   *   Forward delete:                 delete
    +   *   Copy text:                      ctrl/cmd + c
    +   *   Paste text:                     ctrl/cmd + v
    +   *   Cut text:                       ctrl/cmd + x
    +   *   Select entire text:             ctrl/cmd + a
    +   *   Quit editing                    tab or esc
    +   * 
    + * + *

    Supported mouse/touch combination

    + *
    +   *   Position cursor:                click/touch
    +   *   Create selection:               click/touch & drag
    +   *   Create selection:               click & shift + click
    +   *   Select word:                    double click
    +   *   Select line:                    triple click
    +   * 
    + */ + fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'i-text', + + /** + * Index where text selection starts (or where cursor is when there is no selection) + * @type Number + * @default + */ + selectionStart: 0, + + /** + * Index where text selection ends + * @type Number + * @default + */ + selectionEnd: 0, + + /** + * Color of text selection + * @type String + * @default + */ + selectionColor: 'rgba(17,119,255,0.3)', + + /** + * Indicates whether text is in editing mode + * @type Boolean + * @default + */ + isEditing: false, + + /** + * Indicates whether a text can be edited + * @type Boolean + * @default + */ + editable: true, + + /** + * Border color of text object while it's in editing mode + * @type String + * @default + */ + editingBorderColor: 'rgba(102,153,255,0.25)', + + /** + * Width of cursor (in px) + * @type Number + * @default + */ + cursorWidth: 2, + + /** + * Color of text cursor color in editing mode. + * if not set (default) will take color from the text. + * if set to a color value that fabric can understand, it will + * be used instead of the color of the text at the current position. + * @type String + * @default + */ + cursorColor: '', + + /** + * Delay between cursor blink (in ms) + * @type Number + * @default + */ + cursorDelay: 1000, + + /** + * Duration of cursor fadein (in ms) + * @type Number + * @default + */ + cursorDuration: 600, + + /** + * Indicates whether internal text char widths can be cached + * @type Boolean + * @default + */ + caching: true, + + /** + * DOM container to append the hiddenTextarea. + * An alternative to attaching to the document.body. + * Useful to reduce laggish redraw of the full document.body tree and + * also with modals event capturing that won't let the textarea take focus. + * @type HTMLElement + * @default + */ + hiddenTextareaContainer: null, + + /** + * @private + */ + _reSpace: /\s|\n/, + + /** + * @private + */ + _currentCursorOpacity: 0, + + /** + * @private + */ + _selectionDirection: null, + + /** + * @private + */ + _abortCursorAnimation: false, + + /** + * @private + */ + __widthOfSpace: [], + + /** + * Helps determining when the text is in composition, so that the cursor + * rendering is altered. + */ + inCompositionMode: false, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.IText} thisArg + */ + initialize: function(text, options) { + this.callSuper('initialize', text, options); + this.initBehavior(); + }, + + /** + * Sets selection start (left boundary of a selection) + * @param {Number} index Index to set selection start to + */ + setSelectionStart: function(index) { + index = Math.max(index, 0); + this._updateAndFire('selectionStart', index); + }, + + /** + * Sets selection end (right boundary of a selection) + * @param {Number} index Index to set selection end to + */ + setSelectionEnd: function(index) { + index = Math.min(index, this.text.length); + this._updateAndFire('selectionEnd', index); + }, + + /** + * @private + * @param {String} property 'selectionStart' or 'selectionEnd' + * @param {Number} index new position of property + */ + _updateAndFire: function(property, index) { + if (this[property] !== index) { + this._fireSelectionChanged(); + this[property] = index; + } + this._updateTextarea(); + }, + + /** + * Fires the even of selection changed + * @private + */ + _fireSelectionChanged: function() { + this.fire('selection:changed'); + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + }, + + /** + * Initialize text dimensions. Render all text on given context + * or on a offscreen canvas to get the text width with measureText. + * Updates this.width and this.height with the proper values. + * Does not return dimensions. + * @private + */ + initDimensions: function() { + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this.callSuper('initDimensions'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + this.clearContextTop(); + this.callSuper('render', ctx); + // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor + // the correct position but not at every cursor animation. + this.cursorOffsetCache = { }; + this.renderCursorOrSelection(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + this.callSuper('_render', ctx); + }, + + /** + * Prepare and clean the contextTop + */ + clearContextTop: function(skipRestore) { + if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { + return; + } + var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + this.transform(ctx); + this._clearTextArea(ctx); + skipRestore || ctx.restore(); + }, + /** + * Renders cursor or selection (depending on what exists) + * it does on the contextTop. If contextTop is not available, do nothing. + */ + renderCursorOrSelection: function() { + if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { + return; + } + var boundaries = this._getCursorBoundaries(), + ctx = this.canvas.contextTop; + this.clearContextTop(true); + if (this.selectionStart === this.selectionEnd) { + this.renderCursor(boundaries, ctx); + } + else { + this.renderSelection(boundaries, ctx); + } + ctx.restore(); + }, + + _clearTextArea: function(ctx) { + // we add 4 pixel, to be sure to do not leave any pixel out + var width = this.width + 4, height = this.height + 4; + ctx.clearRect(-width / 2, -height / 2, width, height); + }, + + /** + * Returns cursor boundaries (left, top, leftOffset, topOffset) + * @private + * @param {Array} chars Array of characters + * @param {String} typeOfBoundaries + */ + _getCursorBoundaries: function(position) { + + // left/top are left/top of entire text box + // leftOffset/topOffset are offset from that left/top point of a text box + + if (typeof position === 'undefined') { + position = this.selectionStart; + } + + var left = this._getLeftOffset(), + top = this._getTopOffset(), + offsets = this._getCursorBoundariesOffsets(position); + return { + left: left, + top: top, + leftOffset: offsets.left, + topOffset: offsets.top + }; + }, + + /** + * @private + */ + _getCursorBoundariesOffsets: function(position) { + if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) { + return this.cursorOffsetCache; + } + var lineLeftOffset, + lineIndex, + charIndex, + topOffset = 0, + leftOffset = 0, + boundaries, + cursorPosition = this.get2DCursorLocation(position); + charIndex = cursorPosition.charIndex; + lineIndex = cursorPosition.lineIndex; + for (var i = 0; i < lineIndex; i++) { + topOffset += this.getHeightOfLine(i); + } + lineLeftOffset = this._getLineLeftOffset(lineIndex); + var bound = this.__charBounds[lineIndex][charIndex]; + bound && (leftOffset = bound.left); + if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) { + leftOffset -= this._getWidthOfCharSpacing(); + } + boundaries = { + top: topOffset, + left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), + }; + if (this.direction === 'rtl') { + boundaries.left *= -1; + } + this.cursorOffsetCache = boundaries; + return this.cursorOffsetCache; + }, + + /** + * Renders cursor + * @param {Object} boundaries + * @param {CanvasRenderingContext2D} ctx transformed context to draw on + */ + renderCursor: function(boundaries, ctx) { + var cursorLocation = this.get2DCursorLocation(), + lineIndex = cursorLocation.lineIndex, + charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), + multiplier = this.scaleX * this.canvas.getZoom(), + cursorWidth = this.cursorWidth / multiplier, + topOffset = boundaries.topOffset, + dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'); + topOffset += (1 - this._fontSizeFraction) * this.getHeightOfLine(lineIndex) / this.lineHeight + - charHeight * (1 - this._fontSizeFraction); + + if (this.inCompositionMode) { + this.renderSelection(boundaries, ctx); + } + ctx.fillStyle = this.cursorColor || this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + ctx.fillRect( + boundaries.left + boundaries.leftOffset - cursorWidth / 2, + topOffset + boundaries.top + dy, + cursorWidth, + charHeight); + }, + + /** + * Renders text selection + * @param {Object} boundaries Object with left/top/leftOffset/topOffset + * @param {CanvasRenderingContext2D} ctx transformed context to draw on + */ + renderSelection: function(boundaries, ctx) { + + var selectionStart = this.inCompositionMode ? this.hiddenTextarea.selectionStart : this.selectionStart, + selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, + isJustify = this.textAlign.indexOf('justify') !== -1, + start = this.get2DCursorLocation(selectionStart), + end = this.get2DCursorLocation(selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + startChar = start.charIndex < 0 ? 0 : start.charIndex, + endChar = end.charIndex < 0 ? 0 : end.charIndex; + + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getLineLeftOffset(i) || 0, + lineHeight = this.getHeightOfLine(i), + realLineHeight = 0, boxStart = 0, boxEnd = 0; + + if (i === startLine) { + boxStart = this.__charBounds[startLine][startChar].left; + } + if (i >= startLine && i < endLine) { + boxEnd = isJustify && !this.isEndOfWrapping(i) ? this.width : this.getLineWidth(i) || 5; // WTF is this 5? + } + else if (i === endLine) { + if (endChar === 0) { + boxEnd = this.__charBounds[endLine][endChar].left; + } + else { + var charSpacing = this._getWidthOfCharSpacing(); + boxEnd = this.__charBounds[endLine][endChar - 1].left + + this.__charBounds[endLine][endChar - 1].width - charSpacing; + } + } + realLineHeight = lineHeight; + if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) { + lineHeight /= this.lineHeight; + } + var drawStart = boundaries.left + lineOffset + boxStart, + drawWidth = boxEnd - boxStart, + drawHeight = lineHeight, extraTop = 0; + if (this.inCompositionMode) { + ctx.fillStyle = this.compositionColor || 'black'; + drawHeight = 1; + extraTop = lineHeight; + } + else { + ctx.fillStyle = this.selectionColor; + } + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - drawWidth; + } + ctx.fillRect( + drawStart, + boundaries.top + boundaries.topOffset + extraTop, + drawWidth, + drawHeight); + boundaries.topOffset += realLineHeight; + } + }, + + /** + * High level function to know the height of the cursor. + * the currentChar is the one that precedes the cursor + * Returns fontSize of char at the current cursor + * Unused from the library, is for the end user + * @return {Number} Character font size + */ + getCurrentCharFontSize: function() { + var cp = this._getCurrentCharIndex(); + return this.getValueOfPropertyAt(cp.l, cp.c, 'fontSize'); + }, + + /** + * High level function to know the color of the cursor. + * the currentChar is the one that precedes the cursor + * Returns color (fill) of char at the current cursor + * if the text object has a pattern or gradient for filler, it will return that. + * Unused by the library, is for the end user + * @return {String | fabric.Gradient | fabric.Pattern} Character color (fill) + */ + getCurrentCharColor: function() { + var cp = this._getCurrentCharIndex(); + return this.getValueOfPropertyAt(cp.l, cp.c, 'fill'); + }, + + /** + * Returns the cursor position for the getCurrent.. functions + * @private + */ + _getCurrentCharIndex: function() { + var cursorPosition = this.get2DCursorLocation(this.selectionStart, true), + charIndex = cursorPosition.charIndex > 0 ? cursorPosition.charIndex - 1 : 0; + return { l: cursorPosition.lineIndex, c: charIndex }; + } + }); + + /** + * Returns fabric.IText instance from an object representation + * @static + * @memberOf fabric.IText + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as argument + */ + fabric.IText.fromObject = function(object, callback) { + parseDecoration(object); + if (object.styles) { + for (var i in object.styles) { + for (var j in object.styles[i]) { + parseDecoration(object.styles[i][j]); + } + } + } + fabric.Object._fromObject('IText', object, callback, 'text'); + }; +})(); + + +(function() { + + var clone = fabric.util.object.clone; + + fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + + /** + * Initializes all the interactive behavior of IText + */ + initBehavior: function() { + this.initAddedHandler(); + this.initRemovedHandler(); + this.initCursorSelectionHandlers(); + this.initDoubleClickSimulation(); + this.mouseMoveHandler = this.mouseMoveHandler.bind(this); + }, + + onDeselect: function() { + this.isEditing && this.exitEditing(); + this.selected = false; + }, + + /** + * Initializes "added" event handler + */ + initAddedHandler: function() { + var _this = this; + this.on('added', function() { + var canvas = _this.canvas; + if (canvas) { + if (!canvas._hasITextHandlers) { + canvas._hasITextHandlers = true; + _this._initCanvasHandlers(canvas); + } + canvas._iTextInstances = canvas._iTextInstances || []; + canvas._iTextInstances.push(_this); + } + }); + }, + + initRemovedHandler: function() { + var _this = this; + this.on('removed', function() { + var canvas = _this.canvas; + if (canvas) { + canvas._iTextInstances = canvas._iTextInstances || []; + fabric.util.removeFromArray(canvas._iTextInstances, _this); + if (canvas._iTextInstances.length === 0) { + canvas._hasITextHandlers = false; + _this._removeCanvasHandlers(canvas); + } + } + }); + }, + + /** + * register canvas event to manage exiting on other instances + * @private + */ + _initCanvasHandlers: function(canvas) { + canvas._mouseUpITextHandler = function() { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { + obj.__isMousedown = false; + }); + } + }; + canvas.on('mouse:up', canvas._mouseUpITextHandler); + }, + + /** + * remove canvas event to manage exiting on other instances + * @private + */ + _removeCanvasHandlers: function(canvas) { + canvas.off('mouse:up', canvas._mouseUpITextHandler); + }, + + /** + * @private + */ + _tick: function() { + this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); + }, + + /** + * @private + */ + _animateCursor: function(obj, targetOpacity, duration, completeMethod) { + + var tickState; + + tickState = { + isAborted: false, + abort: function() { + this.isAborted = true; + }, + }; + + obj.animate('_currentCursorOpacity', targetOpacity, { + duration: duration, + onComplete: function() { + if (!tickState.isAborted) { + obj[completeMethod](); + } + }, + onChange: function() { + // we do not want to animate a selection, only cursor + if (obj.canvas && obj.selectionStart === obj.selectionEnd) { + obj.renderCursorOrSelection(); + } + }, + abort: function() { + return tickState.isAborted; + } + }); + return tickState; + }, + + /** + * @private + */ + _onTickComplete: function() { + + var _this = this; + + if (this._cursorTimeout1) { + clearTimeout(this._cursorTimeout1); + } + this._cursorTimeout1 = setTimeout(function() { + _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); + }, 100); + }, + + /** + * Initializes delayed cursor + */ + initDelayedCursor: function(restart) { + var _this = this, + delay = restart ? 0 : this.cursorDelay; + + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + this._cursorTimeout2 = setTimeout(function() { + _this._tick(); + }, delay); + }, + + /** + * Aborts cursor animation and clears all timeouts + */ + abortCursorAnimation: function() { + var shouldClear = this._currentTickState || this._currentTickCompleteState, + canvas = this.canvas; + this._currentTickState && this._currentTickState.abort(); + this._currentTickCompleteState && this._currentTickCompleteState.abort(); + + clearTimeout(this._cursorTimeout1); + clearTimeout(this._cursorTimeout2); + + this._currentCursorOpacity = 0; + // to clear just itext area we need to transform the context + // it may not be worth it + if (shouldClear && canvas) { + canvas.clearContext(canvas.contextTop || canvas.contextContainer); + } + + }, + + /** + * Selects entire text + * @return {fabric.IText} thisArg + * @chainable + */ + selectAll: function() { + this.selectionStart = 0; + this.selectionEnd = this._text.length; + this._fireSelectionChanged(); + this._updateTextarea(); + return this; + }, + + /** + * Returns selected text + * @return {String} + */ + getSelectedText: function() { + return this._text.slice(this.selectionStart, this.selectionEnd).join(''); + }, + + /** + * Find new selection index representing start of current word according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findWordBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + + // remove space before cursor first + if (this._reSpace.test(this._text[index])) { + while (this._reSpace.test(this._text[index])) { + offset++; + index--; + } + } + while (/\S/.test(this._text[index]) && index > -1) { + offset++; + index--; + } + + return startFrom - offset; + }, + + /** + * Find new selection index representing end of current word according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findWordBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + + // remove space after cursor first + if (this._reSpace.test(this._text[index])) { + while (this._reSpace.test(this._text[index])) { + offset++; + index++; + } + } + while (/\S/.test(this._text[index]) && index < this._text.length) { + offset++; + index++; + } + + return startFrom + offset; + }, + + /** + * Find new selection index representing start of current line according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findLineBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + + while (!/\n/.test(this._text[index]) && index > -1) { + offset++; + index--; + } + + return startFrom - offset; + }, + + /** + * Find new selection index representing end of current line according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findLineBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + + while (!/\n/.test(this._text[index]) && index < this._text.length) { + offset++; + index++; + } + + return startFrom + offset; + }, + + /** + * Finds index corresponding to beginning or end of a word + * @param {Number} selectionStart Index of a character + * @param {Number} direction 1 or -1 + * @return {Number} Index of the beginning or end of a word + */ + searchWordBoundary: function(selectionStart, direction) { + var text = this._text, + index = this._reSpace.test(text[selectionStart]) ? selectionStart - 1 : selectionStart, + _char = text[index], + // wrong + reNonWord = fabric.reNonWord; + + while (!reNonWord.test(_char) && index > 0 && index < text.length) { + index += direction; + _char = text[index]; + } + if (reNonWord.test(_char)) { + index += direction === 1 ? 0 : 1; + } + return index; + }, + + /** + * Selects a word based on the index + * @param {Number} selectionStart Index of a character + */ + selectWord: function(selectionStart) { + selectionStart = selectionStart || this.selectionStart; + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ + newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ + + this.selectionStart = newSelectionStart; + this.selectionEnd = newSelectionEnd; + this._fireSelectionChanged(); + this._updateTextarea(); + this.renderCursorOrSelection(); + }, + + /** + * Selects a line based on the index + * @param {Number} selectionStart Index of a character + * @return {fabric.IText} thisArg + * @chainable + */ + selectLine: function(selectionStart) { + selectionStart = selectionStart || this.selectionStart; + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), + newSelectionEnd = this.findLineBoundaryRight(selectionStart); + + this.selectionStart = newSelectionStart; + this.selectionEnd = newSelectionEnd; + this._fireSelectionChanged(); + this._updateTextarea(); + return this; + }, + + /** + * Enters editing state + * @return {fabric.IText} thisArg + * @chainable + */ + enterEditing: function(e) { + if (this.isEditing || !this.editable) { + return; + } + + if (this.canvas) { + this.canvas.calcOffset(); + this.exitEditingOnOthers(this.canvas); + } + + this.isEditing = true; + + this.initHiddenTextarea(e); + this.hiddenTextarea.focus(); + this.hiddenTextarea.value = this.text; + this._updateTextarea(); + this._saveEditingProps(); + this._setEditingProps(); + this._textBeforeEdit = this.text; + + this._tick(); + this.fire('editing:entered'); + this._fireSelectionChanged(); + if (!this.canvas) { + return this; + } + this.canvas.fire('text:editing:entered', { target: this }); + this.initMouseMoveHandler(); + this.canvas.requestRenderAll(); + return this; + }, + + exitEditingOnOthers: function(canvas) { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } + }); + } + }, + + /** + * Initializes "mousemove" event handler + */ + initMouseMoveHandler: function() { + this.canvas.on('mouse:move', this.mouseMoveHandler); + }, + + /** + * @private + */ + mouseMoveHandler: function(options) { + if (!this.__isMousedown || !this.isEditing) { + return; + } + + var newSelectionStart = this.getSelectionStartFromPointer(options.e), + currentStart = this.selectionStart, + currentEnd = this.selectionEnd; + if ( + (newSelectionStart !== this.__selectionStartOnMouseDown || currentStart === currentEnd) + && + (currentStart === newSelectionStart || currentEnd === newSelectionStart) + ) { + return; + } + if (newSelectionStart > this.__selectionStartOnMouseDown) { + this.selectionStart = this.__selectionStartOnMouseDown; + this.selectionEnd = newSelectionStart; + } + else { + this.selectionStart = newSelectionStart; + this.selectionEnd = this.__selectionStartOnMouseDown; + } + if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) { + this.restartCursorIfNeeded(); + this._fireSelectionChanged(); + this._updateTextarea(); + this.renderCursorOrSelection(); + } + }, + + /** + * @private + */ + _setEditingProps: function() { + this.hoverCursor = 'text'; + + if (this.canvas) { + this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; + } + + this.borderColor = this.editingBorderColor; + this.hasControls = this.selectable = false; + this.lockMovementX = this.lockMovementY = true; + }, + + /** + * convert from textarea to grapheme indexes + */ + fromStringToGraphemeSelection: function(start, end, text) { + var smallerTextStart = text.slice(0, start), + graphemeStart = fabric.util.string.graphemeSplit(smallerTextStart).length; + if (start === end) { + return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; + } + var smallerTextEnd = text.slice(start, end), + graphemeEnd = fabric.util.string.graphemeSplit(smallerTextEnd).length; + return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; + }, + + /** + * convert from fabric to textarea values + */ + fromGraphemeToStringSelection: function(start, end, _text) { + var smallerTextStart = _text.slice(0, start), + graphemeStart = smallerTextStart.join('').length; + if (start === end) { + return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; + } + var smallerTextEnd = _text.slice(start, end), + graphemeEnd = smallerTextEnd.join('').length; + return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; + }, + + /** + * @private + */ + _updateTextarea: function() { + this.cursorOffsetCache = { }; + if (!this.hiddenTextarea) { + return; + } + if (!this.inCompositionMode) { + var newSelection = this.fromGraphemeToStringSelection(this.selectionStart, this.selectionEnd, this._text); + this.hiddenTextarea.selectionStart = newSelection.selectionStart; + this.hiddenTextarea.selectionEnd = newSelection.selectionEnd; + } + this.updateTextareaPosition(); + }, + + /** + * @private + */ + updateFromTextArea: function() { + if (!this.hiddenTextarea) { + return; + } + this.cursorOffsetCache = { }; + this.text = this.hiddenTextarea.value; + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + var newSelection = this.fromStringToGraphemeSelection( + this.hiddenTextarea.selectionStart, this.hiddenTextarea.selectionEnd, this.hiddenTextarea.value); + this.selectionEnd = this.selectionStart = newSelection.selectionEnd; + if (!this.inCompositionMode) { + this.selectionStart = newSelection.selectionStart; + } + this.updateTextareaPosition(); + }, + + /** + * @private + */ + updateTextareaPosition: function() { + if (this.selectionStart === this.selectionEnd) { + var style = this._calcTextareaPosition(); + this.hiddenTextarea.style.left = style.left; + this.hiddenTextarea.style.top = style.top; + } + }, + + /** + * @private + * @return {Object} style contains style for hiddenTextarea + */ + _calcTextareaPosition: function() { + if (!this.canvas) { + return { x: 1, y: 1 }; + } + var desiredPosition = this.inCompositionMode ? this.compositionStart : this.selectionStart, + boundaries = this._getCursorBoundaries(desiredPosition), + cursorLocation = this.get2DCursorLocation(desiredPosition), + lineIndex = cursorLocation.lineIndex, + charIndex = cursorLocation.charIndex, + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize') * this.lineHeight, + leftOffset = boundaries.leftOffset, + m = this.calcTransformMatrix(), + p = { + x: boundaries.left + leftOffset, + y: boundaries.top + boundaries.topOffset + charHeight + }, + retinaScaling = this.canvas.getRetinaScaling(), + upperCanvas = this.canvas.upperCanvasEl, + upperCanvasWidth = upperCanvas.width / retinaScaling, + upperCanvasHeight = upperCanvas.height / retinaScaling, + maxWidth = upperCanvasWidth - charHeight, + maxHeight = upperCanvasHeight - charHeight, + scaleX = upperCanvas.clientWidth / upperCanvasWidth, + scaleY = upperCanvas.clientHeight / upperCanvasHeight; + + p = fabric.util.transformPoint(p, m); + p = fabric.util.transformPoint(p, this.canvas.viewportTransform); + p.x *= scaleX; + p.y *= scaleY; + if (p.x < 0) { + p.x = 0; + } + if (p.x > maxWidth) { + p.x = maxWidth; + } + if (p.y < 0) { + p.y = 0; + } + if (p.y > maxHeight) { + p.y = maxHeight; + } + + // add canvas offset on document + p.x += this.canvas._offset.left; + p.y += this.canvas._offset.top; + + return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight + 'px', charHeight: charHeight }; + }, + + /** + * @private + */ + _saveEditingProps: function() { + this._savedProps = { + hasControls: this.hasControls, + borderColor: this.borderColor, + lockMovementX: this.lockMovementX, + lockMovementY: this.lockMovementY, + hoverCursor: this.hoverCursor, + selectable: this.selectable, + defaultCursor: this.canvas && this.canvas.defaultCursor, + moveCursor: this.canvas && this.canvas.moveCursor + }; + }, + + /** + * @private + */ + _restoreEditingProps: function() { + if (!this._savedProps) { + return; + } + + this.hoverCursor = this._savedProps.hoverCursor; + this.hasControls = this._savedProps.hasControls; + this.borderColor = this._savedProps.borderColor; + this.selectable = this._savedProps.selectable; + this.lockMovementX = this._savedProps.lockMovementX; + this.lockMovementY = this._savedProps.lockMovementY; + + if (this.canvas) { + this.canvas.defaultCursor = this._savedProps.defaultCursor; + this.canvas.moveCursor = this._savedProps.moveCursor; + } + }, + + /** + * Exits from editing state + * @return {fabric.IText} thisArg + * @chainable + */ + exitEditing: function() { + var isTextChanged = (this._textBeforeEdit !== this.text); + var hiddenTextarea = this.hiddenTextarea; + this.selected = false; + this.isEditing = false; + + this.selectionEnd = this.selectionStart; + + if (hiddenTextarea) { + hiddenTextarea.blur && hiddenTextarea.blur(); + hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea); + } + this.hiddenTextarea = null; + this.abortCursorAnimation(); + this._restoreEditingProps(); + this._currentCursorOpacity = 0; + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this.fire('editing:exited'); + isTextChanged && this.fire('modified'); + if (this.canvas) { + this.canvas.off('mouse:move', this.mouseMoveHandler); + this.canvas.fire('text:editing:exited', { target: this }); + isTextChanged && this.canvas.fire('object:modified', { target: this }); + } + return this; + }, + + /** + * @private + */ + _removeExtraneousStyles: function() { + for (var prop in this.styles) { + if (!this._textLines[prop]) { + delete this.styles[prop]; + } + } + }, + + /** + * remove and reflow a style block from start to end. + * @param {Number} start linear start position for removal (included in removal) + * @param {Number} end linear end position for removal ( excluded from removal ) + */ + removeStyleFromTo: function(start, end) { + var cursorStart = this.get2DCursorLocation(start, true), + cursorEnd = this.get2DCursorLocation(end, true), + lineStart = cursorStart.lineIndex, + charStart = cursorStart.charIndex, + lineEnd = cursorEnd.lineIndex, + charEnd = cursorEnd.charIndex, + i, styleObj; + if (lineStart !== lineEnd) { + // step1 remove the trailing of lineStart + if (this.styles[lineStart]) { + for (i = charStart; i < this._unwrappedTextLines[lineStart].length; i++) { + delete this.styles[lineStart][i]; + } + } + // step2 move the trailing of lineEnd to lineStart if needed + if (this.styles[lineEnd]) { + for (i = charEnd; i < this._unwrappedTextLines[lineEnd].length; i++) { + styleObj = this.styles[lineEnd][i]; + if (styleObj) { + this.styles[lineStart] || (this.styles[lineStart] = { }); + this.styles[lineStart][charStart + i - charEnd] = styleObj; + } + } + } + // step3 detects lines will be completely removed. + for (i = lineStart + 1; i <= lineEnd; i++) { + delete this.styles[i]; + } + // step4 shift remaining lines. + this.shiftLineStyles(lineEnd, lineStart - lineEnd); + } + else { + // remove and shift left on the same line + if (this.styles[lineStart]) { + styleObj = this.styles[lineStart]; + var diff = charEnd - charStart, numericChar, _char; + for (i = charStart; i < charEnd; i++) { + delete styleObj[i]; + } + for (_char in this.styles[lineStart]) { + numericChar = parseInt(_char, 10); + if (numericChar >= charEnd) { + styleObj[numericChar - diff] = styleObj[_char]; + delete styleObj[_char]; + } + } + } + } + }, + + /** + * Shifts line styles up or down + * @param {Number} lineIndex Index of a line + * @param {Number} offset Can any number? + */ + shiftLineStyles: function(lineIndex, offset) { + // shift all line styles by offset upward or downward + // do not clone deep. we need new array, not new style objects + var clonedStyles = clone(this.styles); + for (var line in this.styles) { + var numericLine = parseInt(line, 10); + if (numericLine > lineIndex) { + this.styles[numericLine + offset] = clonedStyles[numericLine]; + if (!clonedStyles[numericLine - offset]) { + delete this.styles[numericLine]; + } + } + } + }, + + restartCursorIfNeeded: function() { + if (!this._currentTickState || this._currentTickState.isAborted + || !this._currentTickCompleteState || this._currentTickCompleteState.isAborted + ) { + this.initDelayedCursor(); + } + }, + + /** + * Handle insertion of more consecutive style lines for when one or more + * newlines gets added to the text. Since current style needs to be shifted + * first we shift the current style of the number lines needed, then we add + * new lines from the last to the first. + * @param {Number} lineIndex Index of a line + * @param {Number} charIndex Index of a char + * @param {Number} qty number of lines to add + * @param {Array} copiedStyle Array of objects styles + */ + insertNewlineStyleObject: function(lineIndex, charIndex, qty, copiedStyle) { + var currentCharStyle, + newLineStyles = {}, + somethingAdded = false, + isEndOfLine = this._unwrappedTextLines[lineIndex].length === charIndex; + + qty || (qty = 1); + this.shiftLineStyles(lineIndex, qty); + if (this.styles[lineIndex]) { + currentCharStyle = this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1]; + } + // we clone styles of all chars + // after cursor onto the current line + for (var index in this.styles[lineIndex]) { + var numIndex = parseInt(index, 10); + if (numIndex >= charIndex) { + somethingAdded = true; + newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index]; + // remove lines from the previous line since they're on a new line now + if (!(isEndOfLine && charIndex === 0)) { + delete this.styles[lineIndex][index]; + } + } + } + var styleCarriedOver = false; + if (somethingAdded && !isEndOfLine) { + // if is end of line, the extra style we copied + // is probably not something we want + this.styles[lineIndex + qty] = newLineStyles; + styleCarriedOver = true; + } + if (styleCarriedOver) { + // skip the last line of since we already prepared it. + qty--; + } + // for the all the lines or all the other lines + // we clone current char style onto the next (otherwise empty) line + while (qty > 0) { + if (copiedStyle && copiedStyle[qty - 1]) { + this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty - 1]) }; + } + else if (currentCharStyle) { + this.styles[lineIndex + qty] = { 0: clone(currentCharStyle) }; + } + else { + delete this.styles[lineIndex + qty]; + } + qty--; + } + this._forceClearCache = true; + }, + + /** + * Inserts style object for a given line/char index + * @param {Number} lineIndex Index of a line + * @param {Number} charIndex Index of a char + * @param {Number} quantity number Style object to insert, if given + * @param {Array} copiedStyle array of style objects + */ + insertCharStyleObject: function(lineIndex, charIndex, quantity, copiedStyle) { + if (!this.styles) { + this.styles = {}; + } + var currentLineStyles = this.styles[lineIndex], + currentLineStylesCloned = currentLineStyles ? clone(currentLineStyles) : {}; + + quantity || (quantity = 1); + // shift all char styles by quantity forward + // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 + for (var index in currentLineStylesCloned) { + var numericIndex = parseInt(index, 10); + if (numericIndex >= charIndex) { + currentLineStyles[numericIndex + quantity] = currentLineStylesCloned[numericIndex]; + // only delete the style if there was nothing moved there + if (!currentLineStylesCloned[numericIndex - quantity]) { + delete currentLineStyles[numericIndex]; + } + } + } + this._forceClearCache = true; + if (copiedStyle) { + while (quantity--) { + if (!Object.keys(copiedStyle[quantity]).length) { + continue; + } + if (!this.styles[lineIndex]) { + this.styles[lineIndex] = {}; + } + this.styles[lineIndex][charIndex + quantity] = clone(copiedStyle[quantity]); + } + return; + } + if (!currentLineStyles) { + return; + } + var newStyle = currentLineStyles[charIndex ? charIndex - 1 : 1]; + while (newStyle && quantity--) { + this.styles[lineIndex][charIndex + quantity] = clone(newStyle); + } + }, + + /** + * Inserts style object(s) + * @param {Array} insertedText Characters at the location where style is inserted + * @param {Number} start cursor index for inserting style + * @param {Array} [copiedStyle] array of style objects to insert. + */ + insertNewStyleBlock: function(insertedText, start, copiedStyle) { + var cursorLoc = this.get2DCursorLocation(start, true), + addedLines = [0], linesLength = 0; + // get an array of how many char per lines are being added. + for (var i = 0; i < insertedText.length; i++) { + if (insertedText[i] === '\n') { + linesLength++; + addedLines[linesLength] = 0; + } + else { + addedLines[linesLength]++; + } + } + // for the first line copy the style from the current char position. + if (addedLines[0] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex, cursorLoc.charIndex, addedLines[0], copiedStyle); + copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1); + } + linesLength && this.insertNewlineStyleObject( + cursorLoc.lineIndex, cursorLoc.charIndex + addedLines[0], linesLength); + for (var i = 1; i < linesLength; i++) { + if (addedLines[i] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + } + else if (copiedStyle) { + this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; + } + copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); + } + // we use i outside the loop to get it like linesLength + if (addedLines[i] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + } + }, + + /** + * Set the selectionStart and selectionEnd according to the new position of cursor + * mimic the key - mouse navigation when shift is pressed. + */ + setSelectionStartEndWithShift: function(start, end, newSelection) { + if (newSelection <= start) { + if (end === start) { + this._selectionDirection = 'left'; + } + else if (this._selectionDirection === 'right') { + this._selectionDirection = 'left'; + this.selectionEnd = start; + } + this.selectionStart = newSelection; + } + else if (newSelection > start && newSelection < end) { + if (this._selectionDirection === 'right') { + this.selectionEnd = newSelection; + } + else { + this.selectionStart = newSelection; + } + } + else { + // newSelection is > selection start and end + if (end === start) { + this._selectionDirection = 'right'; + } + else if (this._selectionDirection === 'left') { + this._selectionDirection = 'right'; + this.selectionStart = end; + } + this.selectionEnd = newSelection; + } + }, + + setSelectionInBoundaries: function() { + var length = this.text.length; + if (this.selectionStart > length) { + this.selectionStart = length; + } + else if (this.selectionStart < 0) { + this.selectionStart = 0; + } + if (this.selectionEnd > length) { + this.selectionEnd = length; + } + else if (this.selectionEnd < 0) { + this.selectionEnd = 0; + } + } + }); +})(); + + +fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + /** + * Initializes "dbclick" event handler + */ + initDoubleClickSimulation: function() { + + // for double click + this.__lastClickTime = +new Date(); + + // for triple click + this.__lastLastClickTime = +new Date(); + + this.__lastPointer = { }; + + this.on('mousedown', this.onMouseDown); + }, + + /** + * Default event handler to simulate triple click + * @private + */ + onMouseDown: function(options) { + if (!this.canvas) { + return; + } + this.__newClickTime = +new Date(); + var newPointer = options.pointer; + if (this.isTripleClick(newPointer)) { + this.fire('tripleclick', options); + this._stopEvent(options.e); + } + this.__lastLastClickTime = this.__lastClickTime; + this.__lastClickTime = this.__newClickTime; + this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; + this.__lastSelected = this.selected; + }, + + isTripleClick: function(newPointer) { + return this.__newClickTime - this.__lastClickTime < 500 && + this.__lastClickTime - this.__lastLastClickTime < 500 && + this.__lastPointer.x === newPointer.x && + this.__lastPointer.y === newPointer.y; + }, + + /** + * @private + */ + _stopEvent: function(e) { + e.preventDefault && e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + }, + + /** + * Initializes event handlers related to cursor or selection + */ + initCursorSelectionHandlers: function() { + this.initMousedownHandler(); + this.initMouseupHandler(); + this.initClicks(); + }, + + /** + * Default handler for double click, select a word + */ + doubleClickHandler: function(options) { + if (!this.isEditing) { + return; + } + this.selectWord(this.getSelectionStartFromPointer(options.e)); + }, + + /** + * Default handler for triple click, select a line + */ + tripleClickHandler: function(options) { + if (!this.isEditing) { + return; + } + this.selectLine(this.getSelectionStartFromPointer(options.e)); + }, + + /** + * Initializes double and triple click event handlers + */ + initClicks: function() { + this.on('mousedblclick', this.doubleClickHandler); + this.on('tripleclick', this.tripleClickHandler); + }, + + /** + * Default event handler for the basic functionalities needed on _mouseDown + * can be overridden to do something different. + * Scope of this implementation is: find the click position, set selectionStart + * find selectionEnd, initialize the drawing of either cursor or selection area + * initializing a mousedDown on a text area will cancel fabricjs knowledge of + * current compositionMode. It will be set to false. + */ + _mouseDownHandler: function(options) { + if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { + return; + } + + this.__isMousedown = true; + + if (this.selected) { + this.inCompositionMode = false; + this.setCursorByClick(options.e); + } + + if (this.isEditing) { + this.__selectionStartOnMouseDown = this.selectionStart; + if (this.selectionStart === this.selectionEnd) { + this.abortCursorAnimation(); + } + this.renderCursorOrSelection(); + } + }, + + /** + * Default event handler for the basic functionalities needed on mousedown:before + * can be overridden to do something different. + * Scope of this implementation is: verify the object is already selected when mousing down + */ + _mouseDownHandlerBefore: function(options) { + if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { + return; + } + // we want to avoid that an object that was selected and then becomes unselectable, + // may trigger editing mode in some way. + this.selected = this === this.canvas._activeObject; + }, + + /** + * Initializes "mousedown" event handler + */ + initMousedownHandler: function() { + this.on('mousedown', this._mouseDownHandler); + this.on('mousedown:before', this._mouseDownHandlerBefore); + }, + + /** + * Initializes "mouseup" event handler + */ + initMouseupHandler: function() { + this.on('mouseup', this.mouseUpHandler); + }, + + /** + * standard handler for mouse up, overridable + * @private + */ + mouseUpHandler: function(options) { + this.__isMousedown = false; + if (!this.editable || this.group || + (options.transform && options.transform.actionPerformed) || + (options.e.button && options.e.button !== 1)) { + return; + } + + if (this.canvas) { + var currentActive = this.canvas._activeObject; + if (currentActive && currentActive !== this) { + // avoid running this logic when there is an active object + // this because is possible with shift click and fast clicks, + // to rapidly deselect and reselect this object and trigger an enterEdit + return; + } + } + + if (this.__lastSelected && !this.__corner) { + this.selected = false; + this.__lastSelected = false; + this.enterEditing(options.e); + if (this.selectionStart === this.selectionEnd) { + this.initDelayedCursor(true); + } + else { + this.renderCursorOrSelection(); + } + } + else { + this.selected = true; + } + }, + + /** + * Changes cursor location in a text depending on passed pointer (x/y) object + * @param {Event} e Event object + */ + setCursorByClick: function(e) { + var newSelection = this.getSelectionStartFromPointer(e), + start = this.selectionStart, end = this.selectionEnd; + if (e.shiftKey) { + this.setSelectionStartEndWithShift(start, end, newSelection); + } + else { + this.selectionStart = newSelection; + this.selectionEnd = newSelection; + } + if (this.isEditing) { + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Returns index of a character corresponding to where an object was clicked + * @param {Event} e Event object + * @return {Number} Index of a character + */ + getSelectionStartFromPointer: function(e) { + var mouseOffset = this.getLocalPointer(e), + prevWidth = 0, + width = 0, + height = 0, + charIndex = 0, + lineIndex = 0, + lineLeftOffset, + line; + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (height <= mouseOffset.y) { + height += this.getHeightOfLine(i) * this.scaleY; + lineIndex = i; + if (i > 0) { + charIndex += this._textLines[i - 1].length + this.missingNewlineOffset(i - 1); + } + } + else { + break; + } + } + lineLeftOffset = this._getLineLeftOffset(lineIndex); + width = lineLeftOffset * this.scaleX; + line = this._textLines[lineIndex]; + // handling of RTL: in order to get things work correctly, + // we assume RTL writing is mirrored compared to LTR writing. + // so in position detection we mirror the X offset, and when is time + // of rendering it, we mirror it again. + if (this.direction === 'rtl') { + mouseOffset.x = this.width * this.scaleX - mouseOffset.x + width; + } + for (var j = 0, jlen = line.length; j < jlen; j++) { + prevWidth = width; + // i removed something about flipX here, check. + width += this.__charBounds[lineIndex][j].kernedWidth * this.scaleX; + if (width <= mouseOffset.x) { + charIndex++; + } + else { + break; + } + } + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex, jlen); + }, + + /** + * @private + */ + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { + // we need Math.abs because when width is after the last char, the offset is given as 1, while is 0 + var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, + distanceBtwNextCharAndCursor = width - mouseOffset.x, + offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor || + distanceBtwNextCharAndCursor < 0 ? 0 : 1, + newSelectionStart = index + offset; + // if object is horizontally flipped, mirror cursor location from the end + if (this.flipX) { + newSelectionStart = jlen - newSelectionStart; + } + + if (newSelectionStart > this._text.length) { + newSelectionStart = this._text.length; + } + + return newSelectionStart; + } +}); + + +fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + + /** + * Initializes hidden textarea (needed to bring up keyboard in iOS) + */ + initHiddenTextarea: function() { + this.hiddenTextarea = fabric.document.createElement('textarea'); + this.hiddenTextarea.setAttribute('autocapitalize', 'off'); + this.hiddenTextarea.setAttribute('autocorrect', 'off'); + this.hiddenTextarea.setAttribute('autocomplete', 'off'); + this.hiddenTextarea.setAttribute('spellcheck', 'false'); + this.hiddenTextarea.setAttribute('data-fabric-hiddentextarea', ''); + this.hiddenTextarea.setAttribute('wrap', 'off'); + var style = this._calcTextareaPosition(); + // line-height: 1px; was removed from the style to fix this: + // https://bugs.chromium.org/p/chromium/issues/detail?id=870966 + this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + + '; left: ' + style.left + '; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px;' + + ' paddingーtop: ' + style.fontSize + ';'; + + if (this.hiddenTextareaContainer) { + this.hiddenTextareaContainer.appendChild(this.hiddenTextarea); + } + else { + fabric.document.body.appendChild(this.hiddenTextarea); + } + + fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'cut', this.copy.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this)); + + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } + }, + + /** + * For functionalities on keyDown + * Map a special key to a function of the instance/prototype + * If you need different behaviour for ESC or TAB or arrows, you have to change + * this map setting the name of a function that you build on the fabric.Itext or + * your prototype. + * the map change will affect all Instances unless you need for only some text Instances + * in that case you have to clone this object and assign your Instance. + * this.keysMap = fabric.util.object.clone(this.keysMap); + * The function must be in fabric.Itext.prototype.myFunction And will receive event as args[0] + */ + keysMap: { + 9: 'exitEditing', + 27: 'exitEditing', + 33: 'moveCursorUp', + 34: 'moveCursorDown', + 35: 'moveCursorRight', + 36: 'moveCursorLeft', + 37: 'moveCursorLeft', + 38: 'moveCursorUp', + 39: 'moveCursorRight', + 40: 'moveCursorDown', + }, + + keysMapRtl: { + 9: 'exitEditing', + 27: 'exitEditing', + 33: 'moveCursorUp', + 34: 'moveCursorDown', + 35: 'moveCursorLeft', + 36: 'moveCursorRight', + 37: 'moveCursorRight', + 38: 'moveCursorUp', + 39: 'moveCursorLeft', + 40: 'moveCursorDown', + }, + + /** + * For functionalities on keyUp + ctrl || cmd + */ + ctrlKeysMapUp: { + 67: 'copy', + 88: 'cut' + }, + + /** + * For functionalities on keyDown + ctrl || cmd + */ + ctrlKeysMapDown: { + 65: 'selectAll' + }, + + onClick: function() { + // No need to trigger click event here, focus is enough to have the keyboard appear on Android + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + + /** + * Handles keydown event + * only used for arrows and combination of modifier keys. + * @param {Event} e Event object + */ + onKeyDown: function(e) { + if (!this.isEditing) { + return; + } + var keyMap = this.direction === 'rtl' ? this.keysMapRtl : this.keysMap; + if (e.keyCode in keyMap) { + this[keyMap[e.keyCode]](e); + } + else if ((e.keyCode in this.ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) { + this[this.ctrlKeysMapDown[e.keyCode]](e); + } + else { + return; + } + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.keyCode >= 33 && e.keyCode <= 40) { + // if i press an arrow key just update selection + this.inCompositionMode = false; + this.clearContextTop(); + this.renderCursorOrSelection(); + } + else { + this.canvas && this.canvas.requestRenderAll(); + } + }, + + /** + * Handles keyup event + * We handle KeyUp because ie11 and edge have difficulties copy/pasting + * if a copy/cut event fired, keyup is dismissed + * @param {Event} e Event object + */ + onKeyUp: function(e) { + if (!this.isEditing || this._copyDone || this.inCompositionMode) { + this._copyDone = false; + return; + } + if ((e.keyCode in this.ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) { + this[this.ctrlKeysMapUp[e.keyCode]](e); + } + else { + return; + } + e.stopImmediatePropagation(); + e.preventDefault(); + this.canvas && this.canvas.requestRenderAll(); + }, + + /** + * Handles onInput event + * @param {Event} e Event object + */ + onInput: function(e) { + var fromPaste = this.fromPaste; + this.fromPaste = false; + e && e.stopPropagation(); + if (!this.isEditing) { + return; + } + // decisions about style changes. + var nextText = this._splitTextIntoLines(this.hiddenTextarea.value).graphemeText, + charCount = this._text.length, + nextCharCount = nextText.length, + removedText, insertedText, + charDiff = nextCharCount - charCount, + selectionStart = this.selectionStart, selectionEnd = this.selectionEnd, + selection = selectionStart !== selectionEnd, + copiedStyle, removeFrom, removeTo; + if (this.hiddenTextarea.value === '') { + this.styles = { }; + this.updateFromTextArea(); + this.fire('changed'); + if (this.canvas) { + this.canvas.fire('text:changed', { target: this }); + this.canvas.requestRenderAll(); + } + return; + } + + var textareaSelection = this.fromStringToGraphemeSelection( + this.hiddenTextarea.selectionStart, + this.hiddenTextarea.selectionEnd, + this.hiddenTextarea.value + ); + var backDelete = selectionStart > textareaSelection.selectionStart; + + if (selection) { + removedText = this._text.slice(selectionStart, selectionEnd); + charDiff += selectionEnd - selectionStart; + } + else if (nextCharCount < charCount) { + if (backDelete) { + removedText = this._text.slice(selectionEnd + charDiff, selectionEnd); + } + else { + removedText = this._text.slice(selectionStart, selectionStart - charDiff); + } + } + insertedText = nextText.slice(textareaSelection.selectionEnd - charDiff, textareaSelection.selectionEnd); + if (removedText && removedText.length) { + if (insertedText.length) { + // let's copy some style before deleting. + // we want to copy the style before the cursor OR the style at the cursor if selection + // is bigger than 0. + copiedStyle = this.getSelectionStyles(selectionStart, selectionStart + 1, false); + // now duplicate the style one for each inserted text. + copiedStyle = insertedText.map(function() { + // this return an array of references, but that is fine since we are + // copying the style later. + return copiedStyle[0]; + }); + } + if (selection) { + removeFrom = selectionStart; + removeTo = selectionEnd; + } + else if (backDelete) { + // detect differences between forwardDelete and backDelete + removeFrom = selectionEnd - removedText.length; + removeTo = selectionEnd; + } + else { + removeFrom = selectionEnd; + removeTo = selectionEnd + removedText.length; + } + this.removeStyleFromTo(removeFrom, removeTo); + } + if (insertedText.length) { + if (fromPaste && insertedText.join('') === fabric.copiedText && !fabric.disableStyleCopyPaste) { + copiedStyle = fabric.copiedTextStyle; + } + this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle); + } + this.updateFromTextArea(); + this.fire('changed'); + if (this.canvas) { + this.canvas.fire('text:changed', { target: this }); + this.canvas.requestRenderAll(); + } + }, + /** + * Composition start + */ + onCompositionStart: function() { + this.inCompositionMode = true; + }, + + /** + * Composition end + */ + onCompositionEnd: function() { + this.inCompositionMode = false; + }, + + // /** + // * Composition update + // */ + onCompositionUpdate: function(e) { + this.compositionStart = e.target.selectionStart; + this.compositionEnd = e.target.selectionEnd; + this.updateTextareaPosition(); + }, + + /** + * Copies selected text + * @param {Event} e Event object + */ + copy: function() { + if (this.selectionStart === this.selectionEnd) { + //do not cut-copy if no selection + return; + } + + fabric.copiedText = this.getSelectedText(); + if (!fabric.disableStyleCopyPaste) { + fabric.copiedTextStyle = this.getSelectionStyles(this.selectionStart, this.selectionEnd, true); + } + else { + fabric.copiedTextStyle = null; + } + this._copyDone = true; + }, + + /** + * Pastes text + * @param {Event} e Event object + */ + paste: function() { + this.fromPaste = true; + }, + + /** + * @private + * @param {Event} e Event object + * @return {Object} Clipboard data object + */ + _getClipboardData: function(e) { + return (e && e.clipboardData) || fabric.window.clipboardData; + }, + + /** + * Finds the width in pixels before the cursor on the same line + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + * @return {Number} widthBeforeCursor width before cursor + */ + _getWidthBeforeCursor: function(lineIndex, charIndex) { + var widthBeforeCursor = this._getLineLeftOffset(lineIndex), bound; + + if (charIndex > 0) { + bound = this.__charBounds[lineIndex][charIndex - 1]; + widthBeforeCursor += bound.left + bound.width; + } + return widthBeforeCursor; + }, + + /** + * Gets start offset of a selection + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + getDownCursorOffset: function(e, isRight) { + var selectionProp = this._getSelectionForOffset(e, isRight), + cursorLocation = this.get2DCursorLocation(selectionProp), + lineIndex = cursorLocation.lineIndex; + // if on last line, down cursor goes to end of line + if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { + // move to the end of a text + return this._text.length - selectionProp; + } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor), + textAfterCursor = this._textLines[lineIndex].slice(charIndex); + return textAfterCursor.length + indexOnOtherLine + 1 + this.missingNewlineOffset(lineIndex); + }, + + /** + * private + * Helps finding if the offset should be counted from Start or End + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + _getSelectionForOffset: function(e, isRight) { + if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { + return this.selectionEnd; + } + else { + return this.selectionStart; + } + }, + + /** + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + getUpCursorOffset: function(e, isRight) { + var selectionProp = this._getSelectionForOffset(e, isRight), + cursorLocation = this.get2DCursorLocation(selectionProp), + lineIndex = cursorLocation.lineIndex; + if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { + // if on first line, up cursor goes to start of line + return -selectionProp; + } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor), + textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex), + missingNewlineOffset = this.missingNewlineOffset(lineIndex - 1); + // return a negative offset + return -this._textLines[lineIndex - 1].length + + indexOnOtherLine - textBeforeCursor.length + (1 - missingNewlineOffset); + }, + + /** + * for a given width it founds the matching character. + * @private + */ + _getIndexOnLine: function(lineIndex, width) { + + var line = this._textLines[lineIndex], + lineLeftOffset = this._getLineLeftOffset(lineIndex), + widthOfCharsOnLine = lineLeftOffset, + indexOnLine = 0, charWidth, foundMatch; + + for (var j = 0, jlen = line.length; j < jlen; j++) { + charWidth = this.__charBounds[lineIndex][j].width; + widthOfCharsOnLine += charWidth; + if (widthOfCharsOnLine > width) { + foundMatch = true; + var leftEdge = widthOfCharsOnLine - charWidth, + rightEdge = widthOfCharsOnLine, + offsetFromLeftEdge = Math.abs(leftEdge - width), + offsetFromRightEdge = Math.abs(rightEdge - width); + + indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); + break; + } + } + + // reached end + if (!foundMatch) { + indexOnLine = line.length - 1; + } + + return indexOnLine; + }, + + + /** + * Moves cursor down + * @param {Event} e Event object + */ + moveCursorDown: function(e) { + if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { + return; + } + this._moveCursorUpOrDown('Down', e); + }, + + /** + * Moves cursor up + * @param {Event} e Event object + */ + moveCursorUp: function(e) { + if (this.selectionStart === 0 && this.selectionEnd === 0) { + return; + } + this._moveCursorUpOrDown('Up', e); + }, + + /** + * Moves cursor up or down, fires the events + * @param {String} direction 'Up' or 'Down' + * @param {Event} e Event object + */ + _moveCursorUpOrDown: function(direction, e) { + // getUpCursorOffset + // getDownCursorOffset + var action = 'get' + direction + 'CursorOffset', + offset = this[action](e, this._selectionDirection === 'right'); + if (e.shiftKey) { + this.moveCursorWithShift(offset); + } + else { + this.moveCursorWithoutShift(offset); + } + if (offset !== 0) { + this.setSelectionInBoundaries(); + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + this.initDelayedCursor(); + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Moves cursor with shift + * @param {Number} offset + */ + moveCursorWithShift: function(offset) { + var newSelection = this._selectionDirection === 'left' + ? this.selectionStart + offset + : this.selectionEnd + offset; + this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); + return offset !== 0; + }, + + /** + * Moves cursor up without shift + * @param {Number} offset + */ + moveCursorWithoutShift: function(offset) { + if (offset < 0) { + this.selectionStart += offset; + this.selectionEnd = this.selectionStart; + } + else { + this.selectionEnd += offset; + this.selectionStart = this.selectionEnd; + } + return offset !== 0; + }, + + /** + * Moves cursor left + * @param {Event} e Event object + */ + moveCursorLeft: function(e) { + if (this.selectionStart === 0 && this.selectionEnd === 0) { + return; + } + this._moveCursorLeftOrRight('Left', e); + }, + + /** + * @private + * @return {Boolean} true if a change happened + */ + _move: function(e, prop, direction) { + var newValue; + if (e.altKey) { + newValue = this['findWordBoundary' + direction](this[prop]); + } + else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { + newValue = this['findLineBoundary' + direction](this[prop]); + } + else { + this[prop] += direction === 'Left' ? -1 : 1; + return true; + } + if (typeof newValue !== undefined && this[prop] !== newValue) { + this[prop] = newValue; + return true; + } + }, + + /** + * @private + */ + _moveLeft: function(e, prop) { + return this._move(e, prop, 'Left'); + }, + + /** + * @private + */ + _moveRight: function(e, prop) { + return this._move(e, prop, 'Right'); + }, + + /** + * Moves cursor left without keeping selection + * @param {Event} e + */ + moveCursorLeftWithoutShift: function(e) { + var change = true; + this._selectionDirection = 'left'; + + // only move cursor when there is no selection, + // otherwise we discard it, and leave cursor on same place + if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) { + change = this._moveLeft(e, 'selectionStart'); + + } + this.selectionEnd = this.selectionStart; + return change; + }, + + /** + * Moves cursor left while keeping selection + * @param {Event} e + */ + moveCursorLeftWithShift: function(e) { + if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { + return this._moveLeft(e, 'selectionEnd'); + } + else if (this.selectionStart !== 0){ + this._selectionDirection = 'left'; + return this._moveLeft(e, 'selectionStart'); + } + }, + + /** + * Moves cursor right + * @param {Event} e Event object + */ + moveCursorRight: function(e) { + if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { + return; + } + this._moveCursorLeftOrRight('Right', e); + }, + + /** + * Moves cursor right or Left, fires event + * @param {String} direction 'Left', 'Right' + * @param {Event} e Event object + */ + _moveCursorLeftOrRight: function(direction, e) { + var actionName = 'moveCursor' + direction + 'With'; + this._currentCursorOpacity = 1; + + if (e.shiftKey) { + actionName += 'Shift'; + } + else { + actionName += 'outShift'; + } + if (this[actionName](e)) { + this.abortCursorAnimation(); + this.initDelayedCursor(); + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Moves cursor right while keeping selection + * @param {Event} e + */ + moveCursorRightWithShift: function(e) { + if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { + return this._moveRight(e, 'selectionStart'); + } + else if (this.selectionEnd !== this._text.length) { + this._selectionDirection = 'right'; + return this._moveRight(e, 'selectionEnd'); + } + }, + + /** + * Moves cursor right without keeping selection + * @param {Event} e Event object + */ + moveCursorRightWithoutShift: function(e) { + var changed = true; + this._selectionDirection = 'right'; + + if (this.selectionStart === this.selectionEnd) { + changed = this._moveRight(e, 'selectionStart'); + this.selectionEnd = this.selectionStart; + } + else { + this.selectionStart = this.selectionEnd; + } + return changed; + }, + + /** + * Removes characters from start/end + * start/end ar per grapheme position in _text array. + * + * @param {Number} start + * @param {Number} end default to start + 1 + */ + removeChars: function(start, end) { + if (typeof end === 'undefined') { + end = start + 1; + } + this.removeStyleFromTo(start, end); + this._text.splice(start, end - start); + this.text = this._text.join(''); + this.set('dirty', true); + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this._removeExtraneousStyles(); + }, + + /** + * insert characters at start position, before start position. + * start equal 1 it means the text get inserted between actual grapheme 0 and 1 + * if style array is provided, it must be as the same length of text in graphemes + * if end is provided and is bigger than start, old text is replaced. + * start/end ar per grapheme position in _text array. + * + * @param {String} text text to insert + * @param {Array} style array of style objects + * @param {Number} start + * @param {Number} end default to start + 1 + */ + insertChars: function(text, style, start, end) { + if (typeof end === 'undefined') { + end = start; + } + if (end > start) { + this.removeStyleFromTo(start, end); + } + var graphemes = fabric.util.string.graphemeSplit(text); + this.insertNewStyleBlock(graphemes, start, style); + this._text = [].concat(this._text.slice(0, start), graphemes, this._text.slice(end)); + this.text = this._text.join(''); + this.set('dirty', true); + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this._removeExtraneousStyles(); + }, + +}); + + +/* _TO_SVG_START_ */ +(function() { + var toFixed = fabric.util.toFixed, + multipleSpacesRegex = / +/g; + + fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { + + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG: function() { + var offsets = this._getSVGLeftTopOffsets(), + textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + return this._createBaseSVGMarkup( + this._toSVG(), + { reviver: reviver, noStyle: true, withShadow: true } + ); + }, + + /** + * @private + */ + _getSVGLeftTopOffsets: function() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0) + }; + }, + + /** + * @private + */ + _wrapSVGTextAndBg: function(textAndBg) { + var noShadow = true, + textDecoration = this.getSvgTextDecoration(this); + return [ + textAndBg.textBgRects.join(''), + '\t\t', + textAndBg.textSpans.join(''), + '\n' + ]; + }, + + /** + * @private + * @param {Number} textTopOffset Text top offset + * @param {Number} textLeftOffset Text left offset + * @return {Object} + */ + _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { + var textSpans = [], + textBgRects = [], + height = textTopOffset, lineOffset; + // bounding-box background + this._setSVGBg(textBgRects); + + // text and text-background + for (var i = 0, len = this._textLines.length; i < len; i++) { + lineOffset = this._getLineLeftOffset(i); + if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { + this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); + } + this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); + height += this.getHeightOfLine(i); + } + + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + /** + * @private + */ + _createTextCharSpan: function(_char, styleDecl, left, top) { + var shouldUseWhitespace = _char !== _char.trim() || _char.match(multipleSpacesRegex), + styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), + fillStyles = styleProps ? 'style="' + styleProps + '"' : '', + dy = styleDecl.deltaY, dySpan = '', + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + if (dy) { + dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + } + return [ + '', + fabric.util.string.escapeXml(_char), + '' + ].join(''); + }, + + _setSVGTextLineText: function(textSpans, lineIndex, textLeftOffset, textTopOffset) { + // set proper line offset + var lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, style, + boxWidth = 0, + line = this._textLines[lineIndex], + timeToRender; + + textTopOffset += lineHeight * (1 - this._fontSizeFraction) / this.lineHeight; + for (var i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + charsToRender += line[i]; + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + textLeftOffset += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; + } + else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + // if we have charSpacing, we render char by char + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); + } + if (timeToRender) { + style = this._getStyleDeclaration(lineIndex, i) || { }; + textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); + charsToRender = ''; + actualStyle = nextStyle; + textLeftOffset += boxWidth; + boxWidth = 0; + } + } + }, + + _pushTextBgRect: function(textBgRects, color, left, top, width, height) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + textBgRects.push( + '\t\t\n'); + }, + + _setSVGTextLineBg: function(textBgRects, i, leftOffset, textTopOffset) { + var line = this._textLines[i], + heightOfLine = this.getHeightOfLine(i) / this.lineHeight, + boxWidth = 0, + boxStart = 0, + charBox, currentColor, + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + lastColor && this._pushTextBgRect(textBgRects, lastColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } + else { + boxWidth += charBox.kernedWidth; + } + } + currentColor && this._pushTextBgRect(textBgRects, currentColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + }, + + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @private + * @param {*} value + * @return {String} + */ + _getFillAttributes: function(value) { + var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + + /** + * @private + */ + _getSVGLineTopOffset: function(lineIndex) { + var lineTopOffset = 0, lastHeight = 0; + for (var j = 0; j < lineIndex; j++) { + lineTopOffset += this.getHeightOfLine(j); + } + lastHeight = this.getHeightOfLine(j); + return { + lineTop: lineTopOffset, + offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) + }; + }, + + /** + * Returns styles-string for svg-export + * @param {Boolean} skipShadow a boolean to skip shadow filter output + * @return {String} + */ + getSvgStyles: function(skipShadow) { + var svgStyle = fabric.Object.prototype.getSvgStyles.call(this, skipShadow); + return svgStyle + ' white-space: pre;'; + }, + }); +})(); +/* _TO_SVG_END_ */ + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = {}); + + /** + * Textbox class, based on IText, allows the user to resize the text rectangle + * and wraps lines automatically. Textboxes have their Y scaling locked, the + * user can only change width. Height is adjusted automatically based on the + * wrapping of lines. + * @class fabric.Textbox + * @extends fabric.IText + * @mixes fabric.Observable + * @return {fabric.Textbox} thisArg + * @see {@link fabric.Textbox#initialize} for constructor definition + */ + fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { + + /** + * Type of an object + * @type String + * @default + */ + type: 'textbox', + + /** + * Minimum width of textbox, in pixels. + * @type Number + * @default + */ + minWidth: 20, + + /** + * Minimum calculated width of a textbox, in pixels. + * fixed to 2 so that an empty textbox cannot go to 0 + * and is still selectable without text. + * @type Number + * @default + */ + dynamicMinWidth: 2, + + /** + * Cached array of text wrapping. + * @type Array + */ + __cachedLines: null, + + /** + * Override standard Object class values + */ + lockScalingFlip: true, + + /** + * Override standard Object class values + * Textbox needs this on false + */ + noScaleCache: false, + + /** + * Properties which when set cause object to change dimensions + * @type Object + * @private + */ + _dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat('width'), + + /** + * Use this regular expression to split strings in breakable lines + * @private + */ + _wordJoiners: /[ \t\r]/, + + /** + * Use this boolean property in order to split strings that have no white space concept. + * this is a cheap way to help with chinese/japanese + * @type Boolean + * @since 2.6.0 + */ + splitByGrapheme: false, + + /** + * Unlike superclass's version of this function, Textbox does not update + * its width. + * @private + * @override + */ + initDimensions: function() { + if (this.__skipDimension) { + return; + } + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this._clearCache(); + // clear dynamicMinWidth as it will be different after we re-wrap line + this.dynamicMinWidth = 0; + // wrap lines + this._styleMap = this._generateStyleMap(this._splitText()); + // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap + if (this.dynamicMinWidth > this.width) { + this._set('width', this.dynamicMinWidth); + } + if (this.textAlign.indexOf('justify') !== -1) { + // once text is measured we need to make space fatter to make justified text. + this.enlargeSpaces(); + } + // clear cache and re-calculate height + this.height = this.calcTextHeight(); + this.saveState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * Generate an object that translates the style object so that it is + * broken up by visual lines (new lines and automatic wrapping). + * The original text styles object is broken up by actual lines (new lines only), + * which is only sufficient for Text / IText + * @private + */ + _generateStyleMap: function(textInfo) { + var realLineCount = 0, + realLineCharCount = 0, + charCount = 0, + map = {}; + + for (var i = 0; i < textInfo.graphemeLines.length; i++) { + if (textInfo.graphemeText[charCount] === '\n' && i > 0) { + realLineCharCount = 0; + charCount++; + realLineCount++; + } + else if (!this.splitByGrapheme && this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) && i > 0) { + // this case deals with space's that are removed from end of lines when wrapping + realLineCharCount++; + charCount++; + } + + map[i] = { line: realLineCount, offset: realLineCharCount }; + + charCount += textInfo.graphemeLines[i].length; + realLineCharCount += textInfo.graphemeLines[i].length; + } + + return map; + }, + + /** + * Returns true if object has a style property or has it on a specified line + * @param {Number} lineIndex + * @return {Boolean} + */ + styleHas: function(property, lineIndex) { + if (this._styleMap && !this.isWrapping) { + var map = this._styleMap[lineIndex]; + if (map) { + lineIndex = map.line; + } + } + return fabric.Text.prototype.styleHas.call(this, property, lineIndex); + }, + + /** + * Returns true if object has no styling or no styling in a line + * @param {Number} lineIndex , lineIndex is on wrapped lines. + * @return {Boolean} + */ + isEmptyStyles: function(lineIndex) { + if (!this.styles) { + return true; + } + var offset = 0, nextLineIndex = lineIndex + 1, nextOffset, obj, shouldLimit = false, + map = this._styleMap[lineIndex], mapNextLine = this._styleMap[lineIndex + 1]; + if (map) { + lineIndex = map.line; + offset = map.offset; + } + if (mapNextLine) { + nextLineIndex = mapNextLine.line; + shouldLimit = nextLineIndex === lineIndex; + nextOffset = mapNextLine.offset; + } + obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + if (p2 >= offset && (!shouldLimit || p2 < nextOffset)) { + // eslint-disable-next-line no-unused-vars + for (var p3 in obj[p1][p2]) { + return false; + } + } + } + } + return true; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + if (this._styleMap && !this.isWrapping) { + var map = this._styleMap[lineIndex]; + if (!map) { + return null; + } + lineIndex = map.line; + charIndex = map.offset + charIndex; + } + return this.callSuper('_getStyleDeclaration', lineIndex, charIndex); + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {Object} style + * @private + */ + _setStyleDeclaration: function(lineIndex, charIndex, style) { + var map = this._styleMap[lineIndex]; + lineIndex = map.line; + charIndex = map.offset + charIndex; + + this.styles[lineIndex][charIndex] = style; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _deleteStyleDeclaration: function(lineIndex, charIndex) { + var map = this._styleMap[lineIndex]; + lineIndex = map.line; + charIndex = map.offset + charIndex; + delete this.styles[lineIndex][charIndex]; + }, + + /** + * probably broken need a fix + * Returns the real style line that correspond to the wrapped lineIndex line + * Used just to verify if the line does exist or not. + * @param {Number} lineIndex + * @returns {Boolean} if the line exists or not + * @private + */ + _getLineStyle: function(lineIndex) { + var map = this._styleMap[lineIndex]; + return !!this.styles[map.line]; + }, + + /** + * Set the line style to an empty object so that is initialized + * @param {Number} lineIndex + * @param {Object} style + * @private + */ + _setLineStyle: function(lineIndex) { + var map = this._styleMap[lineIndex]; + this.styles[map.line] = {}; + }, + + /** + * Wraps text using the 'width' property of Textbox. First this function + * splits text on newlines, so we preserve newlines entered by the user. + * Then it wraps each line using the width of the Textbox by calling + * _wrapLine(). + * @param {Array} lines The string array of text that is split into lines + * @param {Number} desiredWidth width you want to wrap to + * @returns {Array} Array of lines + */ + _wrapText: function(lines, desiredWidth) { + var wrapped = [], i; + this.isWrapping = true; + for (i = 0; i < lines.length; i++) { + wrapped = wrapped.concat(this._wrapLine(lines[i], i, desiredWidth)); + } + this.isWrapping = false; + return wrapped; + }, + + /** + * Helper function to measure a string of text, given its lineIndex and charIndex offset + * it gets called when charBounds are not available yet. + * @param {CanvasRenderingContext2D} ctx + * @param {String} text + * @param {number} lineIndex + * @param {number} charOffset + * @returns {number} + * @private + */ + _measureWord: function(word, lineIndex, charOffset) { + var width = 0, prevGrapheme, skipLeft = true; + charOffset = charOffset || 0; + for (var i = 0, len = word.length; i < len; i++) { + var box = this._getGraphemeBox(word[i], lineIndex, i + charOffset, prevGrapheme, skipLeft); + width += box.kernedWidth; + prevGrapheme = word[i]; + } + return width; + }, + + /** + * Wraps a line of text using the width of the Textbox and a context. + * @param {Array} line The grapheme array that represent the line + * @param {Number} lineIndex + * @param {Number} desiredWidth width you want to wrap the line to + * @param {Number} reservedSpace space to remove from wrapping for custom functionalities + * @returns {Array} Array of line(s) into which the given text is wrapped + * to. + */ + _wrapLine: function(_line, lineIndex, desiredWidth, reservedSpace) { + var lineWidth = 0, + splitByGrapheme = this.splitByGrapheme, + graphemeLines = [], + line = [], + // spaces in different languages? + words = splitByGrapheme ? fabric.util.string.graphemeSplit(_line) : _line.split(this._wordJoiners), + word = '', + offset = 0, + infix = splitByGrapheme ? '' : ' ', + wordWidth = 0, + infixWidth = 0, + largestWordWidth = 0, + lineJustStarted = true, + additionalSpace = this._getWidthOfCharSpacing(), + reservedSpace = reservedSpace || 0; + // fix a difference between split and graphemeSplit + if (words.length === 0) { + words.push([]); + } + desiredWidth -= reservedSpace; + for (var i = 0; i < words.length; i++) { + // if using splitByGrapheme words are already in graphemes. + word = splitByGrapheme ? words[i] : fabric.util.string.graphemeSplit(words[i]); + wordWidth = this._measureWord(word, lineIndex, offset); + offset += word.length; + + lineWidth += infixWidth + wordWidth - additionalSpace; + if (lineWidth > desiredWidth && !lineJustStarted) { + graphemeLines.push(line); + line = []; + lineWidth = wordWidth; + lineJustStarted = true; + } + else { + lineWidth += additionalSpace; + } + + if (!lineJustStarted && !splitByGrapheme) { + line.push(infix); + } + line = line.concat(word); + + infixWidth = splitByGrapheme ? 0 : this._measureWord([infix], lineIndex, offset); + offset++; + lineJustStarted = false; + // keep track of largest word + if (wordWidth > largestWordWidth) { + largestWordWidth = wordWidth; + } + } + + i && graphemeLines.push(line); + + if (largestWordWidth + reservedSpace > this.dynamicMinWidth) { + this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace; + } + return graphemeLines; + }, + + /** + * Detect if the text line is ended with an hard break + * text and itext do not have wrapping, return false + * @param {Number} lineIndex text to split + * @return {Boolean} + */ + isEndOfWrapping: function(lineIndex) { + if (!this._styleMap[lineIndex + 1]) { + // is last line, return true; + return true; + } + if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) { + // this is last line before a line break, return true; + return true; + } + return false; + }, + + /** + * Detect if a line has a linebreak and so we need to account for it when moving + * and counting style. + * @return Number + */ + missingNewlineOffset: function(lineIndex) { + if (this.splitByGrapheme) { + return this.isEndOfWrapping(lineIndex) ? 1 : 0; + } + return 1; + }, + + /** + * Gets lines of text to render in the Textbox. This function calculates + * text wrapping on the fly every time it is called. + * @param {String} text text to split + * @returns {Array} Array of lines in the Textbox. + * @override + */ + _splitTextIntoLines: function(text) { + var newText = fabric.Text.prototype._splitTextIntoLines.call(this, text), + graphemeLines = this._wrapText(newText.lines, this.width), + lines = new Array(graphemeLines.length); + for (var i = 0; i < graphemeLines.length; i++) { + lines[i] = graphemeLines[i].join(''); + } + newText.lines = lines; + newText.graphemeLines = graphemeLines; + return newText; + }, + + getMinWidth: function() { + return Math.max(this.minWidth, this.dynamicMinWidth); + }, + + _removeExtraneousStyles: function() { + var linesToKeep = {}; + for (var prop in this._styleMap) { + if (this._textLines[prop]) { + linesToKeep[this._styleMap[prop].line] = 1; + } + } + for (var prop in this.styles) { + if (!linesToKeep[prop]) { + delete this.styles[prop]; + } + } + }, + + /** + * Returns object representation of an instance + * @method toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['minWidth', 'splitByGrapheme'].concat(propertiesToInclude)); + } + }); + + /** + * Returns fabric.Textbox instance from an object representation + * @static + * @memberOf fabric.Textbox + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created + */ + fabric.Textbox.fromObject = function(object, callback) { + return fabric.Object._fromObject('Textbox', object, callback, 'text'); + }; +})( true ? exports : 0); + + +(function() { + + var controlsUtils = fabric.controlsUtils, + scaleSkewStyleHandler = controlsUtils.scaleSkewCursorStyleHandler, + scaleStyleHandler = controlsUtils.scaleCursorStyleHandler, + scalingEqually = controlsUtils.scalingEqually, + scalingYOrSkewingX = controlsUtils.scalingYOrSkewingX, + scalingXOrSkewingY = controlsUtils.scalingXOrSkewingY, + scaleOrSkewActionName = controlsUtils.scaleOrSkewActionName, + objectControls = fabric.Object.prototype.controls; + + objectControls.ml = new fabric.Control({ + x: -0.5, + y: 0, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingXOrSkewingY, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mr = new fabric.Control({ + x: 0.5, + y: 0, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingXOrSkewingY, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mb = new fabric.Control({ + x: 0, + y: 0.5, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingYOrSkewingX, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mt = new fabric.Control({ + x: 0, + y: -0.5, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingYOrSkewingX, + getActionName: scaleOrSkewActionName, + }); + + objectControls.tl = new fabric.Control({ + x: -0.5, + y: -0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.tr = new fabric.Control({ + x: 0.5, + y: -0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.bl = new fabric.Control({ + x: -0.5, + y: 0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.br = new fabric.Control({ + x: 0.5, + y: 0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.mtr = new fabric.Control({ + x: 0, + y: -0.5, + actionHandler: controlsUtils.rotationWithSnapping, + cursorStyleHandler: controlsUtils.rotationStyleHandler, + offsetY: -40, + withConnection: true, + actionName: 'rotate', + }); + + if (fabric.Textbox) { + // this is breaking the prototype inheritance, no time / ideas to fix it. + // is important to document that if you want to have all objects to have a + // specific custom control, you have to add it to Object prototype and to Textbox + // prototype. The controls are shared as references. So changes to control `tr` + // can still apply to all objects if needed. + var textBoxControls = fabric.Textbox.prototype.controls = { }; + + textBoxControls.mtr = objectControls.mtr; + textBoxControls.tr = objectControls.tr; + textBoxControls.br = objectControls.br; + textBoxControls.tl = objectControls.tl; + textBoxControls.bl = objectControls.bl; + textBoxControls.mt = objectControls.mt; + textBoxControls.mb = objectControls.mb; + + textBoxControls.mr = new fabric.Control({ + x: 0.5, + y: 0, + actionHandler: controlsUtils.changeWidth, + cursorStyleHandler: scaleSkewStyleHandler, + actionName: 'resizing', + }); + + textBoxControls.ml = new fabric.Control({ + x: -0.5, + y: 0, + actionHandler: controlsUtils.changeWidth, + cursorStyleHandler: scaleSkewStyleHandler, + actionName: 'resizing', + }); + } +})(); + + + +/***/ }), + +/***/ 3053: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/* eslint-disable complexity */ +/** + * @fileoverview Returns the first index at which a given element can be found in the array. + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(602); + +/** + * @module array + */ + +/** + * Returns the first index at which a given element can be found in the array + * from start index(default 0), or -1 if it is not present. + * It compares searchElement to elements of the Array using strict equality + * (the same method used by the ===, or triple-equals, operator). + * @param {*} searchElement Element to locate in the array + * @param {Array} array Array that will be traversed. + * @param {number} startIndex Start index in array for searching (default 0) + * @returns {number} the First index at which a given element, or -1 if it is not present + * @memberof module:array + * @example + * // ES6 + * import inArray from 'tui-code-snippet/array/inArray'; + * + * // CommonJS + * const inArray = require('tui-code-snippet/array/inArray'); + * + * const arr = ['one', 'two', 'three', 'four']; + * const idx1 = inArray('one', arr, 3); // -1 + * const idx2 = inArray('one', arr); // 0 + */ +function inArray(searchElement, array, startIndex) { + var i; + var length; + startIndex = startIndex || 0; + + if (!isArray(array)) { + return -1; + } + + if (Array.prototype.indexOf) { + return Array.prototype.indexOf.call(array, searchElement, startIndex); + } + + length = array.length; + for (i = startIndex; startIndex >= 0 && i < length; i += 1) { + if (array[i] === searchElement) { + return i; + } + } + + return -1; +} + +module.exports = inArray; + + +/***/ }), + +/***/ 8592: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each property of object(or element of array) which actually exist. + * @author NHN FE Development Lab + */ + + + +var isArray = __webpack_require__(602); +var forEachArray = __webpack_require__(6092); +var forEachOwnProperties = __webpack_require__(5573); + +/** + * @module collection + */ + +/** + * Execute the provided callback once for each property of object(or element of array) which actually exist. + * If the object is Array-like object(ex-arguments object), It needs to transform to Array.(see 'ex2' of example). + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the property(or The value of the element) + * 2) The name of the property(or The index of the element) + * 3) The object being traversed + * @param {Object} obj The object that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * // ES6 + * import forEach from 'tui-code-snippet/collection/forEach'; + * + * // CommonJS + * const forEach = require('tui-code-snippet/collection/forEach'); + * + * let sum = 0; + * + * forEach([1,2,3], function(value){ + * sum += value; + * }); + * alert(sum); // 6 + * + * // In case of Array-like object + * const array = Array.prototype.slice.call(arrayLike); // change to array + * forEach(array, function(value){ + * sum += value; + * }); + */ +function forEach(obj, iteratee, context) { + if (isArray(obj)) { + forEachArray(obj, iteratee, context); + } else { + forEachOwnProperties(obj, iteratee, context); + } +} + +module.exports = forEach; + + +/***/ }), + +/***/ 6092: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each element present in the array(or Array-like object) in ascending order. + * @author NHN FE Development Lab + */ + + + +/** + * Execute the provided callback once for each element present + * in the array(or Array-like object) in ascending order. + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the element + * 2) The index of the element + * 3) The array(or Array-like object) being traversed + * @param {Array|Arguments|NodeList} arr The array(or Array-like object) that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * // ES6 + * import forEachArray from 'tui-code-snippet/collection/forEachArray'; + * + * // CommonJS + * const forEachArray = require('tui-code-snippet/collection/forEachArray'); + * + * let sum = 0; + * + * forEachArray([1,2,3], function(value){ + * sum += value; + * }); + * alert(sum); // 6 + */ +function forEachArray(arr, iteratee, context) { + var index = 0; + var len = arr.length; + + context = context || null; + + for (; index < len; index += 1) { + if (iteratee.call(context, arr[index], index, arr) === false) { + break; + } + } +} + +module.exports = forEachArray; + + +/***/ }), + +/***/ 5573: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Execute the provided callback once for each property of object which actually exist. + * @author NHN FE Development Lab + */ + + + +/** + * Execute the provided callback once for each property of object which actually exist. + * If the callback function returns false, the loop will be stopped. + * Callback function(iteratee) is invoked with three arguments: + * 1) The value of the property + * 2) The name of the property + * 3) The object being traversed + * @param {Object} obj The object that will be traversed + * @param {function} iteratee Callback function + * @param {Object} [context] Context(this) of callback function + * @memberof module:collection + * @example + * // ES6 + * import forEachOwnProperties from 'tui-code-snippet/collection/forEachOwnProperties'; + * + * // CommonJS + * const forEachOwnProperties = require('tui-code-snippet/collection/forEachOwnProperties'); + * + * let sum = 0; + * + * forEachOwnProperties({a:1,b:2,c:3}, function(value){ + * sum += value; + * }); + * alert(sum); // 6 + */ +function forEachOwnProperties(obj, iteratee, context) { + var key; + + context = context || null; + + for (key in obj) { + if (obj.hasOwnProperty(key)) { + if (iteratee.call(context, obj[key], key, obj) === false) { + break; + } + } + } +} + +module.exports = forEachOwnProperties; + + +/***/ }), + +/***/ 9052: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview This module provides some functions for custom events. And it is implemented in the observer design pattern. + * @author NHN FE Development Lab + */ + + + +var extend = __webpack_require__(961); +var isExisty = __webpack_require__(9886); +var isString = __webpack_require__(2560); +var isObject = __webpack_require__(5393); +var isArray = __webpack_require__(602); +var isFunction = __webpack_require__(5183); +var forEach = __webpack_require__(8592); + +var R_EVENTNAME_SPLIT = /\s+/g; + +/** + * @class + * @example + * // ES6 + * import CustomEvents from 'tui-code-snippet/customEvents/customEvents'; + * + * // CommonJS + * const CustomEvents = require('tui-code-snippet/customEvents/customEvents'); + */ +function CustomEvents() { + /** + * @type {HandlerItem[]} + */ + this.events = null; + + /** + * only for checking specific context event was binded + * @type {object[]} + */ + this.contexts = null; +} + +/** + * Mixin custom events feature to specific constructor + * @param {function} func - constructor + * @example + * //ES6 + * import CustomEvents from 'tui-code-snippet/customEvents/customEvents'; + * + * // CommonJS + * const CustomEvents = require('tui-code-snippet/customEvents/customEvents'); + * + * function Model() { + * this.name = ''; + * } + * CustomEvents.mixin(Model); + * + * const model = new Model(); + * model.on('change', function() { this.name = 'model'; }, this); + * model.fire('change'); + * alert(model.name); // 'model'; + */ +CustomEvents.mixin = function(func) { + extend(func.prototype, CustomEvents.prototype); +}; + +/** + * Get HandlerItem object + * @param {function} handler - handler function + * @param {object} [context] - context for handler + * @returns {HandlerItem} HandlerItem object + * @private + */ +CustomEvents.prototype._getHandlerItem = function(handler, context) { + var item = {handler: handler}; + + if (context) { + item.context = context; + } + + return item; +}; + +/** + * Get event object safely + * @param {string} [eventName] - create sub event map if not exist. + * @returns {(object|array)} event object. if you supplied `eventName` + * parameter then make new array and return it + * @private + */ +CustomEvents.prototype._safeEvent = function(eventName) { + var events = this.events; + var byName; + + if (!events) { + events = this.events = {}; + } + + if (eventName) { + byName = events[eventName]; + + if (!byName) { + byName = []; + events[eventName] = byName; + } + + events = byName; + } + + return events; +}; + +/** + * Get context array safely + * @returns {array} context array + * @private + */ +CustomEvents.prototype._safeContext = function() { + var context = this.contexts; + + if (!context) { + context = this.contexts = []; + } + + return context; +}; + +/** + * Get index of context + * @param {object} ctx - context that used for bind custom event + * @returns {number} index of context + * @private + */ +CustomEvents.prototype._indexOfContext = function(ctx) { + var context = this._safeContext(); + var index = 0; + + while (context[index]) { + if (ctx === context[index][0]) { + return index; + } + + index += 1; + } + + return -1; +}; + +/** + * Memorize supplied context for recognize supplied object is context or + * name: handler pair object when off() + * @param {object} ctx - context object to memorize + * @private + */ +CustomEvents.prototype._memorizeContext = function(ctx) { + var context, index; + + if (!isExisty(ctx)) { + return; + } + + context = this._safeContext(); + index = this._indexOfContext(ctx); + + if (index > -1) { + context[index][1] += 1; + } else { + context.push([ctx, 1]); + } +}; + +/** + * Forget supplied context object + * @param {object} ctx - context object to forget + * @private + */ +CustomEvents.prototype._forgetContext = function(ctx) { + var context, contextIndex; + + if (!isExisty(ctx)) { + return; + } + + context = this._safeContext(); + contextIndex = this._indexOfContext(ctx); + + if (contextIndex > -1) { + context[contextIndex][1] -= 1; + + if (context[contextIndex][1] <= 0) { + context.splice(contextIndex, 1); + } + } +}; + +/** + * Bind event handler + * @param {(string|{name:string, handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {(function|object)} [handler] - handler function or context + * @param {object} [context] - context for binding + * @private + */ +CustomEvents.prototype._bindEvent = function(eventName, handler, context) { + var events = this._safeEvent(eventName); + this._memorizeContext(context); + events.push(this._getHandlerItem(handler, context)); +}; + +/** + * Bind event handlers + * @param {(string|{name:string, handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {(function|object)} [handler] - handler function or context + * @param {object} [context] - context for binding + * //-- #1. Get Module --// + * // ES6 + * import CustomEvents from 'tui-code-snippet/customEvents/customEvents'; + * + * // CommonJS + * const CustomEvents = require('tui-code-snippet/customEvents/customEvents'); + * + * //-- #2. Use method --// + * // # 2.1 Basic Usage + * CustomEvents.on('onload', handler); + * + * // # 2.2 With context + * CustomEvents.on('onload', handler, myObj); + * + * // # 2.3 Bind by object that name, handler pairs + * CustomEvents.on({ + * 'play': handler, + * 'pause': handler2 + * }); + * + * // # 2.4 Bind by object that name, handler pairs with context object + * CustomEvents.on({ + * 'play': handler + * }, myObj); + */ +CustomEvents.prototype.on = function(eventName, handler, context) { + var self = this; + + if (isString(eventName)) { + // [syntax 1, 2] + eventName = eventName.split(R_EVENTNAME_SPLIT); + forEach(eventName, function(name) { + self._bindEvent(name, handler, context); + }); + } else if (isObject(eventName)) { + // [syntax 3, 4] + context = handler; + forEach(eventName, function(func, name) { + self.on(name, func, context); + }); + } +}; + +/** + * Bind one-shot event handlers + * @param {(string|{name:string,handler:function})} eventName - custom + * event name or an object {eventName: handler} + * @param {function|object} [handler] - handler function or context + * @param {object} [context] - context for binding + */ +CustomEvents.prototype.once = function(eventName, handler, context) { + var self = this; + + if (isObject(eventName)) { + context = handler; + forEach(eventName, function(func, name) { + self.once(name, func, context); + }); + + return; + } + + function onceHandler() { // eslint-disable-line require-jsdoc + handler.apply(context, arguments); + self.off(eventName, onceHandler, context); + } + + this.on(eventName, onceHandler, context); +}; + +/** + * Splice supplied array by callback result + * @param {array} arr - array to splice + * @param {function} predicate - function return boolean + * @private + */ +CustomEvents.prototype._spliceMatches = function(arr, predicate) { + var i = 0; + var len; + + if (!isArray(arr)) { + return; + } + + for (len = arr.length; i < len; i += 1) { + if (predicate(arr[i]) === true) { + arr.splice(i, 1); + len -= 1; + i -= 1; + } + } +}; + +/** + * Get matcher for unbind specific handler events + * @param {function} handler - handler function + * @returns {function} handler matcher + * @private + */ +CustomEvents.prototype._matchHandler = function(handler) { + var self = this; + + return function(item) { + var needRemove = handler === item.handler; + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Get matcher for unbind specific context events + * @param {object} context - context + * @returns {function} object matcher + * @private + */ +CustomEvents.prototype._matchContext = function(context) { + var self = this; + + return function(item) { + var needRemove = context === item.context; + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Get matcher for unbind specific hander, context pair events + * @param {function} handler - handler function + * @param {object} context - context + * @returns {function} handler, context matcher + * @private + */ +CustomEvents.prototype._matchHandlerAndContext = function(handler, context) { + var self = this; + + return function(item) { + var matchHandler = (handler === item.handler); + var matchContext = (context === item.context); + var needRemove = (matchHandler && matchContext); + + if (needRemove) { + self._forgetContext(item.context); + } + + return needRemove; + }; +}; + +/** + * Unbind event by event name + * @param {string} eventName - custom event name to unbind + * @param {function} [handler] - handler function + * @private + */ +CustomEvents.prototype._offByEventName = function(eventName, handler) { + var self = this; + var andByHandler = isFunction(handler); + var matchHandler = self._matchHandler(handler); + + eventName = eventName.split(R_EVENTNAME_SPLIT); + + forEach(eventName, function(name) { + var handlerItems = self._safeEvent(name); + + if (andByHandler) { + self._spliceMatches(handlerItems, matchHandler); + } else { + forEach(handlerItems, function(item) { + self._forgetContext(item.context); + }); + + self.events[name] = []; + } + }); +}; + +/** + * Unbind event by handler function + * @param {function} handler - handler function + * @private + */ +CustomEvents.prototype._offByHandler = function(handler) { + var self = this; + var matchHandler = this._matchHandler(handler); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchHandler); + }); +}; + +/** + * Unbind event by object(name: handler pair object or context object) + * @param {object} obj - context or {name: handler} pair object + * @param {function} handler - handler function + * @private + */ +CustomEvents.prototype._offByObject = function(obj, handler) { + var self = this; + var matchFunc; + + if (this._indexOfContext(obj) < 0) { + forEach(obj, function(func, name) { + self.off(name, func); + }); + } else if (isString(handler)) { + matchFunc = this._matchContext(obj); + + self._spliceMatches(this._safeEvent(handler), matchFunc); + } else if (isFunction(handler)) { + matchFunc = this._matchHandlerAndContext(handler, obj); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchFunc); + }); + } else { + matchFunc = this._matchContext(obj); + + forEach(this._safeEvent(), function(handlerItems) { + self._spliceMatches(handlerItems, matchFunc); + }); + } +}; + +/** + * Unbind custom events + * @param {(string|object|function)} eventName - event name or context or + * {name: handler} pair object or handler function + * @param {(function)} handler - handler function + * @example + * //-- #1. Get Module --// + * // ES6 + * import CustomEvents from 'tui-code-snippet/customEvents/customEvents'; + * + * // CommonJS + * const CustomEvents = require('tui-code-snippet/customEvents/customEvents'); + * + * //-- #2. Use method --// + * // # 2.1 off by event name + * CustomEvents.off('onload'); + * + * // # 2.2 off by event name and handler + * CustomEvents.off('play', handler); + * + * // # 2.3 off by handler + * CustomEvents.off(handler); + * + * // # 2.4 off by context + * CustomEvents.off(myObj); + * + * // # 2.5 off by context and handler + * CustomEvents.off(myObj, handler); + * + * // # 2.6 off by context and event name + * CustomEvents.off(myObj, 'onload'); + * + * // # 2.7 off by an Object. that is {eventName: handler} + * CustomEvents.off({ + * 'play': handler, + * 'pause': handler2 + * }); + * + * // # 2.8 off the all events + * CustomEvents.off(); + */ +CustomEvents.prototype.off = function(eventName, handler) { + if (isString(eventName)) { + // [syntax 1, 2] + this._offByEventName(eventName, handler); + } else if (!arguments.length) { + // [syntax 8] + this.events = {}; + this.contexts = []; + } else if (isFunction(eventName)) { + // [syntax 3] + this._offByHandler(eventName); + } else if (isObject(eventName)) { + // [syntax 4, 5, 6] + this._offByObject(eventName, handler); + } +}; + +/** + * Fire custom event + * @param {string} eventName - name of custom event + */ +CustomEvents.prototype.fire = function(eventName) { // eslint-disable-line + this.invoke.apply(this, arguments); +}; + +/** + * Fire a event and returns the result of operation 'boolean AND' with all + * listener's results. + * + * So, It is different from {@link CustomEvents#fire}. + * + * In service code, use this as a before event in component level usually + * for notifying that the event is cancelable. + * @param {string} eventName - Custom event name + * @param {...*} data - Data for event + * @returns {boolean} The result of operation 'boolean AND' + * @example + * const map = new Map(); + * map.on({ + * 'beforeZoom': function() { + * // It should cancel the 'zoom' event by some conditions. + * if (that.disabled && this.getState()) { + * return false; + * } + * return true; + * } + * }); + * + * if (this.invoke('beforeZoom')) { // check the result of 'beforeZoom' + * // if true, + * // doSomething + * } + */ +CustomEvents.prototype.invoke = function(eventName) { + var events, args, index, item; + + if (!this.hasListener(eventName)) { + return true; + } + + events = this._safeEvent(eventName); + args = Array.prototype.slice.call(arguments, 1); + index = 0; + + while (events[index]) { + item = events[index]; + + if (item.handler.apply(item.context, args) === false) { + return false; + } + + index += 1; + } + + return true; +}; + +/** + * Return whether at least one of the handlers is registered in the given + * event name. + * @param {string} eventName - Custom event name + * @returns {boolean} Is there at least one handler in event name? + */ +CustomEvents.prototype.hasListener = function(eventName) { + return this.getListenerLength(eventName) > 0; +}; + +/** + * Return a count of events registered. + * @param {string} eventName - Custom event name + * @returns {number} number of event + */ +CustomEvents.prototype.getListenerLength = function(eventName) { + var events = this._safeEvent(eventName); + + return events.length; +}; + +module.exports = CustomEvents; + + +/***/ }), + +/***/ 961: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Extend the target object from other objects. + * @author NHN FE Development Lab + */ + + + +/** + * @module object + */ + +/** + * Extend the target object from other objects. + * @param {object} target - Object that will be extended + * @param {...object} objects - Objects as sources + * @returns {object} Extended object + * @memberof module:object + */ +function extend(target, objects) { // eslint-disable-line no-unused-vars + var hasOwnProp = Object.prototype.hasOwnProperty; + var source, prop, i, len; + + for (i = 1, len = arguments.length; i < len; i += 1) { + source = arguments[i]; + for (prop in source) { + if (hasOwnProp.call(source, prop)) { + target[prop] = source[prop]; + } + } + } + + return target; +} + +module.exports = extend; + + +/***/ }), + +/***/ 1610: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Retrieve a nested item from the given object/array. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(5695); +var isNull = __webpack_require__(3778); + +/** + * Retrieve a nested item from the given object/array. + * @param {object|Array} obj - Object for retrieving + * @param {...string|number} paths - Paths of property + * @returns {*} Value + * @memberof module:object + * @example + * // ES6 + * import pick from 'tui-code-snippet/object/pick'; + * + * // CommonJS + * const pick = require('tui-code-snippet/object/pick'); + * + * cosnt obj = { + * 'key1': 1, + * 'nested' : { + * 'key1': 11, + * 'nested': { + * 'key1': 21 + * } + * } + * }; + * pick(obj, 'nested', 'nested', 'key1'); // 21 + * pick(obj, 'nested', 'nested', 'key2'); // undefined + * + * const arr = ['a', 'b', 'c']; + * pick(arr, 1); // 'b' + */ +function pick(obj, paths) { // eslint-disable-line no-unused-vars + var args = arguments; + var target = args[0]; + var i = 1; + var length = args.length; + + for (; i < length; i += 1) { + if (isUndefined(target) || + isNull(target)) { + return; + } + + target = target[args[i]]; + } + + return target; // eslint-disable-line consistent-return +} + +module.exports = pick; + + +/***/ }), + +/***/ 4564: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Request image ping. + * @author NHN FE Development Lab + */ + + + +var forEachOwnProperties = __webpack_require__(5573); + +/** + * @module request + */ + +/** + * Request image ping. + * @param {String} url url for ping request + * @param {Object} trackingInfo infos for make query string + * @returns {HTMLElement} + * @memberof module:request + * @example + * // ES6 + * import imagePing from 'tui-code-snippet/request/imagePing'; + * + * // CommonJS + * const imagePing = require('tui-code-snippet/request/imagePing'); + * + * imagePing('https://www.google-analytics.com/collect', { + * v: 1, + * t: 'event', + * tid: 'trackingid', + * cid: 'cid', + * dp: 'dp', + * dh: 'dh' + * }); + */ +function imagePing(url, trackingInfo) { + var trackingElement = document.createElement('img'); + var queryString = ''; + forEachOwnProperties(trackingInfo, function(value, key) { + queryString += '&' + key + '=' + value; + }); + queryString = queryString.substring(1); + + trackingElement.src = url + '?' + queryString; + + trackingElement.style.display = 'none'; + document.body.appendChild(trackingElement); + document.body.removeChild(trackingElement); + + return trackingElement; +} + +module.exports = imagePing; + + +/***/ }), + +/***/ 4729: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Send hostname on DOMContentLoaded. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(5695); +var imagePing = __webpack_require__(4564); + +var ms7days = 7 * 24 * 60 * 60 * 1000; + +/** + * Check if the date has passed 7 days + * @param {number} date - milliseconds + * @returns {boolean} + * @private + */ +function isExpired(date) { + var now = new Date().getTime(); + + return now - date > ms7days; +} + +/** + * Send hostname on DOMContentLoaded. + * To prevent hostname set tui.usageStatistics to false. + * @param {string} appName - application name + * @param {string} trackingId - GA tracking ID + * @ignore + */ +function sendHostname(appName, trackingId) { + var url = 'https://www.google-analytics.com/collect'; + var hostname = location.hostname; + var hitType = 'event'; + var eventCategory = 'use'; + var applicationKeyForStorage = 'TOAST UI ' + appName + ' for ' + hostname + ': Statistics'; + var date = window.localStorage.getItem(applicationKeyForStorage); + + // skip if the flag is defined and is set to false explicitly + if (!isUndefined(window.tui) && window.tui.usageStatistics === false) { + return; + } + + // skip if not pass seven days old + if (date && !isExpired(date)) { + return; + } + + window.localStorage.setItem(applicationKeyForStorage, new Date().getTime()); + + setTimeout(function() { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + imagePing(url, { + v: 1, + t: hitType, + tid: trackingId, + cid: hostname, + dp: hostname, + dh: appName, + el: appName, + ec: eventCategory + }); + } + }, 1000); +} + +module.exports = sendHostname; + + +/***/ }), + +/***/ 602: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is an instance of Array or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is an instance of Array or not. + * If the given variable is an instance of Array, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is array instance? + * @memberof module:type + */ +function isArray(obj) { + return obj instanceof Array; +} + +module.exports = isArray; + + +/***/ }), + +/***/ 9886: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is existing or not. + * @author NHN FE Development Lab + */ + + + +var isUndefined = __webpack_require__(5695); +var isNull = __webpack_require__(3778); + +/** + * Check whether the given variable is existing or not. + * If the given variable is not null and not undefined, returns true. + * @param {*} param - Target for checking + * @returns {boolean} Is existy? + * @memberof module:type + * @example + * // ES6 + * import isExisty from 'tui-code-snippet/type/isExisty'); + * + * // CommonJS + * const isExisty = require('tui-code-snippet/type/isExisty'); + * + * isExisty(''); //true + * isExisty(0); //true + * isExisty([]); //true + * isExisty({}); //true + * isExisty(null); //false + * isExisty(undefined); //false +*/ +function isExisty(param) { + return !isUndefined(param) && !isNull(param); +} + +module.exports = isExisty; + + +/***/ }), + +/***/ 5183: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is a function or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is a function or not. + * If the given variable is a function, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is function? + * @memberof module:type + */ +function isFunction(obj) { + return obj instanceof Function; +} + +module.exports = isFunction; + + +/***/ }), + +/***/ 3778: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is null or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is null or not. + * If the given variable(arguments[0]) is null, returns true. + * @param {*} obj - Target for checking + * @returns {boolean} Is null? + * @memberof module:type + */ +function isNull(obj) { + return obj === null; +} + +module.exports = isNull; + + +/***/ }), + +/***/ 5393: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is an object or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is an object or not. + * If the given variable is an object, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is object? + * @memberof module:type + */ +function isObject(obj) { + return obj === Object(obj); +} + +module.exports = isObject; + + +/***/ }), + +/***/ 2560: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is a string or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is a string or not. + * If the given variable is a string, return true. + * @param {*} obj - Target for checking + * @returns {boolean} Is string? + * @memberof module:type + */ +function isString(obj) { + return typeof obj === 'string' || obj instanceof String; +} + +module.exports = isString; + + +/***/ }), + +/***/ 5695: +/***/ (function(module) { + +"use strict"; +/** + * @fileoverview Check whether the given variable is undefined or not. + * @author NHN FE Development Lab + */ + + + +/** + * Check whether the given variable is undefined or not. + * If the given variable is undefined, returns true. + * @param {*} obj - Target for checking + * @returns {boolean} Is undefined? + * @memberof module:type + */ +function isUndefined(obj) { + return obj === undefined; // eslint-disable-line no-undefined +} + +module.exports = isUndefined; + + +/***/ }), + +/***/ 4426: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(4486); + +/***/ }), + +/***/ 9406: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(4877); + +/***/ }), + +/***/ 789: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(7178); + +/***/ }), + +/***/ 381: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(5603); + +/***/ }), + +/***/ 7636: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(1206); + +/***/ }), + +/***/ 1899: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(6174); + +/***/ }), + +/***/ 899: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(57); + +/***/ }), + +/***/ 8005: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(4741); + +/***/ }), + +/***/ 6562: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(8368); + +/***/ }), + +/***/ 9131: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(3739); + +/***/ }), + +/***/ 4383: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(172); + +/***/ }), + +/***/ 6065: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(4963); + +/***/ }), + +/***/ 1734: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(7820); + +/***/ }), + +/***/ 2461: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(5636); + +/***/ }), + +/***/ 5214: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(5059); + +/***/ }), + +/***/ 6397: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(3969); + +/***/ }), + +/***/ 8189: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(6618); + +/***/ }), + +/***/ 9146: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(5279); + +/***/ }), + +/***/ 4496: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(9562); + +/***/ }), + +/***/ 3972: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(652); + +/***/ }), + +/***/ 7172: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(2813); + +/***/ }), + +/***/ 1845: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(8664); + +/***/ }), + +/***/ 662: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(1457); + +/***/ }), + +/***/ 711: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(2937); + +/***/ }), + +/***/ 6623: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(9297); + +/***/ }), + +/***/ 7077: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(8026); + +/***/ }), + +/***/ 9856: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(2044); + +/***/ }), + +/***/ 4230: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(2214); + +/***/ }), + +/***/ 184: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(9256); + +/***/ }), + +/***/ 3742: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +module.exports = __webpack_require__(5659); + +/***/ }), + +/***/ 1801: +/***/ (function(module) { + +var DIVISOR = { + rect: 1, + circle: 2, + triangle: 1 +}; +var DIMENSION_KEYS = { + rect: { + w: 'width', + h: 'height' + }, + circle: { + w: 'rx', + h: 'ry' + }, + triangle: { + w: 'width', + h: 'height' + } +}; +/** + * Set the start point value to the shape object + * @param {fabric.Object} shape - Shape object + * @ignore + */ + +function setStartPoint(shape) { + var originX = shape.originX, + originY = shape.originY; + var originKey = originX.substring(0, 1) + originY.substring(0, 1); + shape.startPoint = shape.origins[originKey]; +} +/** + * Get the positions of ratated origin by the pointer value + * @param {{x: number, y: number}} origin - Origin value + * @param {{x: number, y: number}} pointer - Pointer value + * @param {number} angle - Rotating angle + * @returns {Object} Postions of origin + * @ignore + */ + + +function getPositionsOfRotatedOrigin(origin, pointer, angle) { + var sx = origin.x; + var sy = origin.y; + var px = pointer.x; + var py = pointer.y; + var r = angle * Math.PI / 180; + var rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx; + var ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy; + return { + originX: sx > rx ? 'right' : 'left', + originY: sy > ry ? 'bottom' : 'top' + }; +} +/** + * Whether the shape has the center origin or not + * @param {fabric.Object} shape - Shape object + * @returns {boolean} State + * @ignore + */ + + +function hasCenterOrigin(shape) { + return shape.originX === 'center' && shape.originY === 'center'; +} +/** + * Adjust the origin of shape by the start point + * @param {{x: number, y: number}} pointer - Pointer value + * @param {fabric.Object} shape - Shape object + * @ignore + */ + + +function adjustOriginByStartPoint(pointer, shape) { + var centerPoint = shape.getPointByOrigin('center', 'center'); + var angle = -shape.angle; + var originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle); + var originX = originPositions.originX, + originY = originPositions.originY; + var origin = shape.getPointByOrigin(originX, originY); + var left = shape.left - (centerPoint.x - origin.x); + var top = shape.top - (centerPoint.y - origin.y); + shape.set({ + originX: originX, + originY: originY, + left: left, + top: top + }); + shape.setCoords(); +} +/** + * Adjust the origin of shape by the moving pointer value + * @param {{x: number, y: number}} pointer - Pointer value + * @param {fabric.Object} shape - Shape object + * @ignore + */ + + +function adjustOriginByMovingPointer(pointer, shape) { + var origin = shape.startPoint; + var angle = -shape.angle; + var originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle); + var originX = originPositions.originX, + originY = originPositions.originY; + shape.setPositionByOrigin(origin, originX, originY); + shape.setCoords(); +} +/** + * Adjust the dimension of shape on firing scaling event + * @param {fabric.Object} shape - Shape object + * @ignore + */ + + +function adjustDimensionOnScaling(shape) { + var type = shape.type, + scaleX = shape.scaleX, + scaleY = shape.scaleY; + var dimensionKeys = DIMENSION_KEYS[type]; + var width = shape[dimensionKeys.w] * scaleX; + var height = shape[dimensionKeys.h] * scaleY; + + if (shape.isRegular) { + var maxScale = Math.max(scaleX, scaleY); + width = shape[dimensionKeys.w] * maxScale; + height = shape[dimensionKeys.h] * maxScale; + } + + var options = { + hasControls: false, + hasBorders: false, + scaleX: 1, + scaleY: 1 + }; + options[dimensionKeys.w] = width; + options[dimensionKeys.h] = height; + shape.set(options); +} +/** + * Adjust the dimension of shape on firing mouse move event + * @param {{x: number, y: number}} pointer - Pointer value + * @param {fabric.Object} shape - Shape object + * @ignore + */ + + +function adjustDimensionOnMouseMove(pointer, shape) { + var type = shape.type, + strokeWidth = shape.strokeWidth, + origin = shape.startPoint; + var divisor = DIVISOR[type]; + var dimensionKeys = DIMENSION_KEYS[type]; + var isTriangle = !!(shape.type === 'triangle'); + var options = {}; + var width = Math.abs(origin.x - pointer.x) / divisor; + var height = Math.abs(origin.y - pointer.y) / divisor; + + if (width > strokeWidth) { + width -= strokeWidth / divisor; + } + + if (height > strokeWidth) { + height -= strokeWidth / divisor; + } + + if (shape.isRegular) { + width = height = Math.max(width, height); + + if (isTriangle) { + height = Math.sqrt(3) / 2 * width; + } + } + + options[dimensionKeys.w] = width; + options[dimensionKeys.h] = height; + shape.set(options); +} + +module.exports = { + /** + * Set each origin value to shape + * @param {fabric.Object} shape - Shape object + */ + setOrigins: function setOrigins(shape) { + var leftTopPoint = shape.getPointByOrigin('left', 'top'); + var rightTopPoint = shape.getPointByOrigin('right', 'top'); + var rightBottomPoint = shape.getPointByOrigin('right', 'bottom'); + var leftBottomPoint = shape.getPointByOrigin('left', 'bottom'); + shape.origins = { + lt: leftTopPoint, + rt: rightTopPoint, + rb: rightBottomPoint, + lb: leftBottomPoint + }; + }, + + /** + * Resize the shape + * @param {fabric.Object} shape - Shape object + * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas + * @param {boolean} isScaling - Whether the resizing action is scaling or not + */ + resize: function resize(shape, pointer, isScaling) { + if (hasCenterOrigin(shape)) { + adjustOriginByStartPoint(pointer, shape); + setStartPoint(shape); + } + + if (isScaling) { + adjustDimensionOnScaling(shape, pointer); + } else { + adjustDimensionOnMouseMove(pointer, shape); + } + + adjustOriginByMovingPointer(pointer, shape); + }, + + /** + * Adjust the origin position of shape to center + * @param {fabric.Object} shape - Shape object + */ + adjustOriginToCenter: function adjustOriginToCenter(shape) { + var centerPoint = shape.getPointByOrigin('center', 'center'); + var originX = shape.originX, + originY = shape.originY; + var origin = shape.getPointByOrigin(originX, originY); + var left = shape.left + (centerPoint.x - origin.x); + var top = shape.top + (centerPoint.y - origin.y); + shape.set({ + hasControls: true, + hasBorders: true, + originX: 'center', + originY: 'center', + left: left, + top: top + }); + shape.setCoords(); // For left, top properties + } +}; + +/***/ }), + +/***/ 2221: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(5454); +__webpack_require__(9173); +var path = __webpack_require__(7545); + +module.exports = path.Array.from; + + +/***/ }), + +/***/ 5078: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(8118); +var path = __webpack_require__(7545); + +module.exports = path.Array.isArray; + + +/***/ }), + +/***/ 6135: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(9106); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').concat; + + +/***/ }), + +/***/ 9510: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(1710); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').fill; + + +/***/ }), + +/***/ 3971: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(3436); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').filter; + + +/***/ }), + +/***/ 98: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(9823); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').forEach; + + +/***/ }), + +/***/ 2089: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(2276); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').indexOf; + + +/***/ }), + +/***/ 6209: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(3838); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').map; + + +/***/ }), + +/***/ 2671: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(5818); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').slice; + + +/***/ }), + +/***/ 1375: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(2178); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Array').splice; + + +/***/ }), + +/***/ 3528: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(665); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('Function').bind; + + +/***/ }), + +/***/ 5739: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(8939); +__webpack_require__(5454); +var getIteratorMethod = __webpack_require__(8703); + +module.exports = getIteratorMethod; + + +/***/ }), + +/***/ 278: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var bind = __webpack_require__(3528); + +var FunctionPrototype = Function.prototype; + +module.exports = function (it) { + var own = it.bind; + return it === FunctionPrototype || (it instanceof Function && own === FunctionPrototype.bind) ? bind : own; +}; + + +/***/ }), + +/***/ 1484: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var concat = __webpack_require__(6135); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.concat; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.concat) ? concat : own; +}; + + +/***/ }), + +/***/ 7731: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fill = __webpack_require__(9510); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.fill; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.fill) ? fill : own; +}; + + +/***/ }), + +/***/ 3669: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var filter = __webpack_require__(3971); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.filter; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.filter) ? filter : own; +}; + + +/***/ }), + +/***/ 2604: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var indexOf = __webpack_require__(2089); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.indexOf; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.indexOf) ? indexOf : own; +}; + + +/***/ }), + +/***/ 263: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var map = __webpack_require__(6209); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.map; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.map) ? map : own; +}; + + +/***/ }), + +/***/ 7663: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var slice = __webpack_require__(2671); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.slice; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.slice) ? slice : own; +}; + + +/***/ }), + +/***/ 5063: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var splice = __webpack_require__(1375); + +var ArrayPrototype = Array.prototype; + +module.exports = function (it) { + var own = it.splice; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.splice) ? splice : own; +}; + + +/***/ }), + +/***/ 6813: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var trim = __webpack_require__(3842); + +var StringPrototype = String.prototype; + +module.exports = function (it) { + var own = it.trim; + return typeof it === 'string' || it === StringPrototype + || (it instanceof String && own === StringPrototype.trim) ? trim : own; +}; + + +/***/ }), + +/***/ 6285: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(2666); +var path = __webpack_require__(7545); + +module.exports = path.Number.parseInt; + + +/***/ }), + +/***/ 3213: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(3113); +var path = __webpack_require__(7545); + +var Object = path.Object; + +module.exports = function create(P, D) { + return Object.create(P, D); +}; + + +/***/ }), + +/***/ 3512: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(297); +var path = __webpack_require__(7545); + +var Object = path.Object; + +var defineProperty = module.exports = function defineProperty(it, key, desc) { + return Object.defineProperty(it, key, desc); +}; + +if (Object.defineProperty.sham) defineProperty.sham = true; + + +/***/ }), + +/***/ 8168: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(9234); +var path = __webpack_require__(7545); + +module.exports = path.Object.getPrototypeOf; + + +/***/ }), + +/***/ 8651: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(2647); +var path = __webpack_require__(7545); + +module.exports = path.Object.keys; + + +/***/ }), + +/***/ 3083: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(3222); +var path = __webpack_require__(7545); + +module.exports = path.Object.setPrototypeOf; + + +/***/ }), + +/***/ 2987: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(4859); +var path = __webpack_require__(7545); + +module.exports = path.parseFloat; + + +/***/ }), + +/***/ 2239: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(5706); +var path = __webpack_require__(7545); + +module.exports = path.parseInt; + + +/***/ }), + +/***/ 3154: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(4242); +__webpack_require__(8939); +__webpack_require__(6663); +__webpack_require__(9021); +__webpack_require__(7884); +__webpack_require__(8885); +__webpack_require__(1868); +__webpack_require__(5454); +var path = __webpack_require__(7545); + +module.exports = path.Promise; + + +/***/ }), + +/***/ 6577: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(5397); +var path = __webpack_require__(7545); + +module.exports = path.Reflect.construct; + + +/***/ }), + +/***/ 3842: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(957); +var entryVirtual = __webpack_require__(5607); + +module.exports = entryVirtual('String').trim; + + +/***/ }), + +/***/ 5008: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(9106); +__webpack_require__(6663); +__webpack_require__(6187); +__webpack_require__(9781); +__webpack_require__(492); +__webpack_require__(6681); +__webpack_require__(9594); +__webpack_require__(3665); +__webpack_require__(9017); +__webpack_require__(1250); +__webpack_require__(9786); +__webpack_require__(503); +__webpack_require__(6565); +__webpack_require__(9322); +__webpack_require__(3610); +__webpack_require__(6886); +__webpack_require__(3514); +__webpack_require__(8671); +__webpack_require__(8556); +__webpack_require__(1367); +var path = __webpack_require__(7545); + +module.exports = path.Symbol; + + +/***/ }), + +/***/ 994: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(8939); +__webpack_require__(6663); +__webpack_require__(5454); +__webpack_require__(3665); +var WrappedWellKnownSymbolModule = __webpack_require__(9207); + +module.exports = WrappedWellKnownSymbolModule.f('iterator'); + + +/***/ }), + +/***/ 2813: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3822); + +module.exports = parent; + + +/***/ }), + +/***/ 8664: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(1434); + +module.exports = parent; + + +/***/ }), + +/***/ 1457: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(7710); + +module.exports = parent; + + +/***/ }), + +/***/ 2937: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(4741); + +module.exports = parent; + + +/***/ }), + +/***/ 9297: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(4963); + +module.exports = parent; + + +/***/ }), + +/***/ 8026: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(7820); + +module.exports = parent; + + +/***/ }), + +/***/ 2044: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(8980); + +module.exports = parent; + + +/***/ }), + +/***/ 2214: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(6672); + +module.exports = parent; + + +/***/ }), + +/***/ 9256: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(2285); +__webpack_require__(177); +__webpack_require__(9031); +__webpack_require__(6658); +__webpack_require__(1875); +__webpack_require__(8658); +// TODO: Remove from `core-js@4` +__webpack_require__(4592); +// TODO: Remove from `core-js@4` +__webpack_require__(6680); + +module.exports = parent; + + +/***/ }), + +/***/ 5659: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(8535); + +module.exports = parent; + + +/***/ }), + +/***/ 6235: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); +var tryToString = __webpack_require__(9288); + +// `Assert: IsCallable(argument) is true` +module.exports = function (argument) { + if (isCallable(argument)) return argument; + throw TypeError(tryToString(argument) + ' is not a function'); +}; + + +/***/ }), + +/***/ 1404: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isConstructor = __webpack_require__(2091); +var tryToString = __webpack_require__(9288); + +// `Assert: IsConstructor(argument) is true` +module.exports = function (argument) { + if (isConstructor(argument)) return argument; + throw TypeError(tryToString(argument) + ' is not a constructor'); +}; + + +/***/ }), + +/***/ 7757: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); + +module.exports = function (argument) { + if (typeof argument === 'object' || isCallable(argument)) return argument; + throw TypeError("Can't set " + String(argument) + ' as a prototype'); +}; + + +/***/ }), + +/***/ 7423: +/***/ (function(module) { + +module.exports = function () { /* empty */ }; + + +/***/ }), + +/***/ 6961: +/***/ (function(module) { + +module.exports = function (it, Constructor, name) { + if (it instanceof Constructor) return it; + throw TypeError('Incorrect ' + (name ? name + ' ' : '') + 'invocation'); +}; + + +/***/ }), + +/***/ 1138: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isObject = __webpack_require__(5744); + +// `Assert: Type(argument) is Object` +module.exports = function (argument) { + if (isObject(argument)) return argument; + throw TypeError(String(argument) + ' is not an object'); +}; + + +/***/ }), + +/***/ 2724: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var toObject = __webpack_require__(1795); +var toAbsoluteIndex = __webpack_require__(7739); +var lengthOfArrayLike = __webpack_require__(4104); + +// `Array.prototype.fill` method implementation +// https://tc39.es/ecma262/#sec-array.prototype.fill +module.exports = function fill(value /* , start = 0, end = @length */) { + var O = toObject(this); + var length = lengthOfArrayLike(O); + var argumentsLength = arguments.length; + var index = toAbsoluteIndex(argumentsLength > 1 ? arguments[1] : undefined, length); + var end = argumentsLength > 2 ? arguments[2] : undefined; + var endPos = end === undefined ? length : toAbsoluteIndex(end, length); + while (endPos > index) O[index++] = value; + return O; +}; + + +/***/ }), + +/***/ 7397: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $forEach = __webpack_require__(454).forEach; +var arrayMethodIsStrict = __webpack_require__(424); + +var STRICT_METHOD = arrayMethodIsStrict('forEach'); + +// `Array.prototype.forEach` method implementation +// https://tc39.es/ecma262/#sec-array.prototype.foreach +module.exports = !STRICT_METHOD ? function forEach(callbackfn /* , thisArg */) { + return $forEach(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); +// eslint-disable-next-line es/no-array-prototype-foreach -- safe +} : [].forEach; + + +/***/ }), + +/***/ 841: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var bind = __webpack_require__(8043); +var toObject = __webpack_require__(1795); +var callWithSafeIterationClosing = __webpack_require__(1635); +var isArrayIteratorMethod = __webpack_require__(6109); +var isConstructor = __webpack_require__(2091); +var lengthOfArrayLike = __webpack_require__(4104); +var createProperty = __webpack_require__(9361); +var getIterator = __webpack_require__(1669); +var getIteratorMethod = __webpack_require__(8703); + +// `Array.from` method implementation +// https://tc39.es/ecma262/#sec-array.from +module.exports = function from(arrayLike /* , mapfn = undefined, thisArg = undefined */) { + var O = toObject(arrayLike); + var IS_CONSTRUCTOR = isConstructor(this); + var argumentsLength = arguments.length; + var mapfn = argumentsLength > 1 ? arguments[1] : undefined; + var mapping = mapfn !== undefined; + if (mapping) mapfn = bind(mapfn, argumentsLength > 2 ? arguments[2] : undefined, 2); + var iteratorMethod = getIteratorMethod(O); + var index = 0; + var length, result, step, iterator, next, value; + // if the target is not iterable or it's an array with the default iterator - use a simple case + if (iteratorMethod && !(this == Array && isArrayIteratorMethod(iteratorMethod))) { + iterator = getIterator(O, iteratorMethod); + next = iterator.next; + result = IS_CONSTRUCTOR ? new this() : []; + for (;!(step = next.call(iterator)).done; index++) { + value = mapping ? callWithSafeIterationClosing(iterator, mapfn, [step.value, index], true) : step.value; + createProperty(result, index, value); + } + } else { + length = lengthOfArrayLike(O); + result = IS_CONSTRUCTOR ? new this(length) : Array(length); + for (;length > index; index++) { + value = mapping ? mapfn(O[index], index) : O[index]; + createProperty(result, index, value); + } + } + result.length = index; + return result; +}; + + +/***/ }), + +/***/ 8180: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toIndexedObject = __webpack_require__(101); +var toAbsoluteIndex = __webpack_require__(7739); +var lengthOfArrayLike = __webpack_require__(4104); + +// `Array.prototype.{ indexOf, includes }` methods implementation +var createMethod = function (IS_INCLUDES) { + return function ($this, el, fromIndex) { + var O = toIndexedObject($this); + var length = lengthOfArrayLike(O); + var index = toAbsoluteIndex(fromIndex, length); + var value; + // Array#includes uses SameValueZero equality algorithm + // eslint-disable-next-line no-self-compare -- NaN check + if (IS_INCLUDES && el != el) while (length > index) { + value = O[index++]; + // eslint-disable-next-line no-self-compare -- NaN check + if (value != value) return true; + // Array#indexOf ignores holes, Array#includes - not + } else for (;length > index; index++) { + if ((IS_INCLUDES || index in O) && O[index] === el) return IS_INCLUDES || index || 0; + } return !IS_INCLUDES && -1; + }; +}; + +module.exports = { + // `Array.prototype.includes` method + // https://tc39.es/ecma262/#sec-array.prototype.includes + includes: createMethod(true), + // `Array.prototype.indexOf` method + // https://tc39.es/ecma262/#sec-array.prototype.indexof + indexOf: createMethod(false) +}; + + +/***/ }), + +/***/ 454: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var bind = __webpack_require__(8043); +var IndexedObject = __webpack_require__(2202); +var toObject = __webpack_require__(1795); +var lengthOfArrayLike = __webpack_require__(4104); +var arraySpeciesCreate = __webpack_require__(1321); + +var push = [].push; + +// `Array.prototype.{ forEach, map, filter, some, every, find, findIndex, filterReject }` methods implementation +var createMethod = function (TYPE) { + var IS_MAP = TYPE == 1; + var IS_FILTER = TYPE == 2; + var IS_SOME = TYPE == 3; + var IS_EVERY = TYPE == 4; + var IS_FIND_INDEX = TYPE == 6; + var IS_FILTER_REJECT = TYPE == 7; + var NO_HOLES = TYPE == 5 || IS_FIND_INDEX; + return function ($this, callbackfn, that, specificCreate) { + var O = toObject($this); + var self = IndexedObject(O); + var boundFunction = bind(callbackfn, that, 3); + var length = lengthOfArrayLike(self); + var index = 0; + var create = specificCreate || arraySpeciesCreate; + var target = IS_MAP ? create($this, length) : IS_FILTER || IS_FILTER_REJECT ? create($this, 0) : undefined; + var value, result; + for (;length > index; index++) if (NO_HOLES || index in self) { + value = self[index]; + result = boundFunction(value, index, O); + if (TYPE) { + if (IS_MAP) target[index] = result; // map + else if (result) switch (TYPE) { + case 3: return true; // some + case 5: return value; // find + case 6: return index; // findIndex + case 2: push.call(target, value); // filter + } else switch (TYPE) { + case 4: return false; // every + case 7: push.call(target, value); // filterReject + } + } + } + return IS_FIND_INDEX ? -1 : IS_SOME || IS_EVERY ? IS_EVERY : target; + }; +}; + +module.exports = { + // `Array.prototype.forEach` method + // https://tc39.es/ecma262/#sec-array.prototype.foreach + forEach: createMethod(0), + // `Array.prototype.map` method + // https://tc39.es/ecma262/#sec-array.prototype.map + map: createMethod(1), + // `Array.prototype.filter` method + // https://tc39.es/ecma262/#sec-array.prototype.filter + filter: createMethod(2), + // `Array.prototype.some` method + // https://tc39.es/ecma262/#sec-array.prototype.some + some: createMethod(3), + // `Array.prototype.every` method + // https://tc39.es/ecma262/#sec-array.prototype.every + every: createMethod(4), + // `Array.prototype.find` method + // https://tc39.es/ecma262/#sec-array.prototype.find + find: createMethod(5), + // `Array.prototype.findIndex` method + // https://tc39.es/ecma262/#sec-array.prototype.findIndex + findIndex: createMethod(6), + // `Array.prototype.filterReject` method + // https://github.com/tc39/proposal-array-filtering + filterReject: createMethod(7) +}; + + +/***/ }), + +/***/ 242: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); +var wellKnownSymbol = __webpack_require__(8182); +var V8_VERSION = __webpack_require__(4218); + +var SPECIES = wellKnownSymbol('species'); + +module.exports = function (METHOD_NAME) { + // We can't use this feature detection in V8 since it causes + // deoptimization and serious performance degradation + // https://github.com/zloirock/core-js/issues/677 + return V8_VERSION >= 51 || !fails(function () { + var array = []; + var constructor = array.constructor = {}; + constructor[SPECIES] = function () { + return { foo: 1 }; + }; + return array[METHOD_NAME](Boolean).foo !== 1; + }); +}; + + +/***/ }), + +/***/ 424: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var fails = __webpack_require__(6192); + +module.exports = function (METHOD_NAME, argument) { + var method = [][METHOD_NAME]; + return !!method && fails(function () { + // eslint-disable-next-line no-useless-call,no-throw-literal -- required for testing + method.call(null, argument || function () { throw 1; }, 1); + }); +}; + + +/***/ }), + +/***/ 3712: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isArray = __webpack_require__(4770); +var isConstructor = __webpack_require__(2091); +var isObject = __webpack_require__(5744); +var wellKnownSymbol = __webpack_require__(8182); + +var SPECIES = wellKnownSymbol('species'); + +// a part of `ArraySpeciesCreate` abstract operation +// https://tc39.es/ecma262/#sec-arrayspeciescreate +module.exports = function (originalArray) { + var C; + if (isArray(originalArray)) { + C = originalArray.constructor; + // cross-realm fallback + if (isConstructor(C) && (C === Array || isArray(C.prototype))) C = undefined; + else if (isObject(C)) { + C = C[SPECIES]; + if (C === null) C = undefined; + } + } return C === undefined ? Array : C; +}; + + +/***/ }), + +/***/ 1321: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var arraySpeciesConstructor = __webpack_require__(3712); + +// `ArraySpeciesCreate` abstract operation +// https://tc39.es/ecma262/#sec-arrayspeciescreate +module.exports = function (originalArray, length) { + return new (arraySpeciesConstructor(originalArray))(length === 0 ? 0 : length); +}; + + +/***/ }), + +/***/ 1635: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var anObject = __webpack_require__(1138); +var iteratorClose = __webpack_require__(6639); + +// call something on iterator step with safe closing on error +module.exports = function (iterator, fn, value, ENTRIES) { + try { + return ENTRIES ? fn(anObject(value)[0], value[1]) : fn(value); + } catch (error) { + iteratorClose(iterator, 'throw', error); + } +}; + + +/***/ }), + +/***/ 9770: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var wellKnownSymbol = __webpack_require__(8182); + +var ITERATOR = wellKnownSymbol('iterator'); +var SAFE_CLOSING = false; + +try { + var called = 0; + var iteratorWithReturn = { + next: function () { + return { done: !!called++ }; + }, + 'return': function () { + SAFE_CLOSING = true; + } + }; + iteratorWithReturn[ITERATOR] = function () { + return this; + }; + // eslint-disable-next-line es/no-array-from, no-throw-literal -- required for testing + Array.from(iteratorWithReturn, function () { throw 2; }); +} catch (error) { /* empty */ } + +module.exports = function (exec, SKIP_CLOSING) { + if (!SKIP_CLOSING && !SAFE_CLOSING) return false; + var ITERATION_SUPPORT = false; + try { + var object = {}; + object[ITERATOR] = function () { + return { + next: function () { + return { done: ITERATION_SUPPORT = true }; + } + }; + }; + exec(object); + } catch (error) { /* empty */ } + return ITERATION_SUPPORT; +}; + + +/***/ }), + +/***/ 9272: +/***/ (function(module) { + +var toString = {}.toString; + +module.exports = function (it) { + return toString.call(it).slice(8, -1); +}; + + +/***/ }), + +/***/ 4696: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var TO_STRING_TAG_SUPPORT = __webpack_require__(3471); +var isCallable = __webpack_require__(6447); +var classofRaw = __webpack_require__(9272); +var wellKnownSymbol = __webpack_require__(8182); + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); +// ES3 wrong here +var CORRECT_ARGUMENTS = classofRaw(function () { return arguments; }()) == 'Arguments'; + +// fallback for IE11 Script Access Denied error +var tryGet = function (it, key) { + try { + return it[key]; + } catch (error) { /* empty */ } +}; + +// getting tag from ES6+ `Object.prototype.toString` +module.exports = TO_STRING_TAG_SUPPORT ? classofRaw : function (it) { + var O, tag, result; + return it === undefined ? 'Undefined' : it === null ? 'Null' + // @@toStringTag case + : typeof (tag = tryGet(O = Object(it), TO_STRING_TAG)) == 'string' ? tag + // builtinTag case + : CORRECT_ARGUMENTS ? classofRaw(O) + // ES3 arguments fallback + : (result = classofRaw(O)) == 'Object' && isCallable(O.callee) ? 'Arguments' : result; +}; + + +/***/ }), + +/***/ 4635: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); + +module.exports = !fails(function () { + function F() { /* empty */ } + F.prototype.constructor = null; + // eslint-disable-next-line es/no-object-getprototypeof -- required for testing + return Object.getPrototypeOf(new F()) !== F.prototype; +}); + + +/***/ }), + +/***/ 5148: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var IteratorPrototype = __webpack_require__(4413).IteratorPrototype; +var create = __webpack_require__(2853); +var createPropertyDescriptor = __webpack_require__(774); +var setToStringTag = __webpack_require__(1284); +var Iterators = __webpack_require__(7771); + +var returnThis = function () { return this; }; + +module.exports = function (IteratorConstructor, NAME, next) { + var TO_STRING_TAG = NAME + ' Iterator'; + IteratorConstructor.prototype = create(IteratorPrototype, { next: createPropertyDescriptor(1, next) }); + setToStringTag(IteratorConstructor, TO_STRING_TAG, false, true); + Iterators[TO_STRING_TAG] = returnThis; + return IteratorConstructor; +}; + + +/***/ }), + +/***/ 8711: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var definePropertyModule = __webpack_require__(2760); +var createPropertyDescriptor = __webpack_require__(774); + +module.exports = DESCRIPTORS ? function (object, key, value) { + return definePropertyModule.f(object, key, createPropertyDescriptor(1, value)); +} : function (object, key, value) { + object[key] = value; + return object; +}; + + +/***/ }), + +/***/ 774: +/***/ (function(module) { + +module.exports = function (bitmap, value) { + return { + enumerable: !(bitmap & 1), + configurable: !(bitmap & 2), + writable: !(bitmap & 4), + value: value + }; +}; + + +/***/ }), + +/***/ 9361: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var toPropertyKey = __webpack_require__(77); +var definePropertyModule = __webpack_require__(2760); +var createPropertyDescriptor = __webpack_require__(774); + +module.exports = function (object, key, value) { + var propertyKey = toPropertyKey(key); + if (propertyKey in object) definePropertyModule.f(object, propertyKey, createPropertyDescriptor(0, value)); + else object[propertyKey] = value; +}; + + +/***/ }), + +/***/ 7218: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var IS_PURE = __webpack_require__(5546); +var FunctionName = __webpack_require__(2282); +var isCallable = __webpack_require__(6447); +var createIteratorConstructor = __webpack_require__(5148); +var getPrototypeOf = __webpack_require__(9341); +var setPrototypeOf = __webpack_require__(4469); +var setToStringTag = __webpack_require__(1284); +var createNonEnumerableProperty = __webpack_require__(8711); +var redefine = __webpack_require__(9482); +var wellKnownSymbol = __webpack_require__(8182); +var Iterators = __webpack_require__(7771); +var IteratorsCore = __webpack_require__(4413); + +var PROPER_FUNCTION_NAME = FunctionName.PROPER; +var CONFIGURABLE_FUNCTION_NAME = FunctionName.CONFIGURABLE; +var IteratorPrototype = IteratorsCore.IteratorPrototype; +var BUGGY_SAFARI_ITERATORS = IteratorsCore.BUGGY_SAFARI_ITERATORS; +var ITERATOR = wellKnownSymbol('iterator'); +var KEYS = 'keys'; +var VALUES = 'values'; +var ENTRIES = 'entries'; + +var returnThis = function () { return this; }; + +module.exports = function (Iterable, NAME, IteratorConstructor, next, DEFAULT, IS_SET, FORCED) { + createIteratorConstructor(IteratorConstructor, NAME, next); + + var getIterationMethod = function (KIND) { + if (KIND === DEFAULT && defaultIterator) return defaultIterator; + if (!BUGGY_SAFARI_ITERATORS && KIND in IterablePrototype) return IterablePrototype[KIND]; + switch (KIND) { + case KEYS: return function keys() { return new IteratorConstructor(this, KIND); }; + case VALUES: return function values() { return new IteratorConstructor(this, KIND); }; + case ENTRIES: return function entries() { return new IteratorConstructor(this, KIND); }; + } return function () { return new IteratorConstructor(this); }; + }; + + var TO_STRING_TAG = NAME + ' Iterator'; + var INCORRECT_VALUES_NAME = false; + var IterablePrototype = Iterable.prototype; + var nativeIterator = IterablePrototype[ITERATOR] + || IterablePrototype['@@iterator'] + || DEFAULT && IterablePrototype[DEFAULT]; + var defaultIterator = !BUGGY_SAFARI_ITERATORS && nativeIterator || getIterationMethod(DEFAULT); + var anyNativeIterator = NAME == 'Array' ? IterablePrototype.entries || nativeIterator : nativeIterator; + var CurrentIteratorPrototype, methods, KEY; + + // fix native + if (anyNativeIterator) { + CurrentIteratorPrototype = getPrototypeOf(anyNativeIterator.call(new Iterable())); + if (CurrentIteratorPrototype !== Object.prototype && CurrentIteratorPrototype.next) { + if (!IS_PURE && getPrototypeOf(CurrentIteratorPrototype) !== IteratorPrototype) { + if (setPrototypeOf) { + setPrototypeOf(CurrentIteratorPrototype, IteratorPrototype); + } else if (!isCallable(CurrentIteratorPrototype[ITERATOR])) { + redefine(CurrentIteratorPrototype, ITERATOR, returnThis); + } + } + // Set @@toStringTag to native iterators + setToStringTag(CurrentIteratorPrototype, TO_STRING_TAG, true, true); + if (IS_PURE) Iterators[TO_STRING_TAG] = returnThis; + } + } + + // fix Array.prototype.{ values, @@iterator }.name in V8 / FF + if (PROPER_FUNCTION_NAME && DEFAULT == VALUES && nativeIterator && nativeIterator.name !== VALUES) { + if (!IS_PURE && CONFIGURABLE_FUNCTION_NAME) { + createNonEnumerableProperty(IterablePrototype, 'name', VALUES); + } else { + INCORRECT_VALUES_NAME = true; + defaultIterator = function values() { return nativeIterator.call(this); }; + } + } + + // export additional methods + if (DEFAULT) { + methods = { + values: getIterationMethod(VALUES), + keys: IS_SET ? defaultIterator : getIterationMethod(KEYS), + entries: getIterationMethod(ENTRIES) + }; + if (FORCED) for (KEY in methods) { + if (BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME || !(KEY in IterablePrototype)) { + redefine(IterablePrototype, KEY, methods[KEY]); + } + } else $({ target: NAME, proto: true, forced: BUGGY_SAFARI_ITERATORS || INCORRECT_VALUES_NAME }, methods); + } + + // define iterator + if ((!IS_PURE || FORCED) && IterablePrototype[ITERATOR] !== defaultIterator) { + redefine(IterablePrototype, ITERATOR, defaultIterator, { name: DEFAULT }); + } + Iterators[NAME] = defaultIterator; + + return methods; +}; + + +/***/ }), + +/***/ 1488: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var path = __webpack_require__(7545); +var hasOwn = __webpack_require__(4500); +var wrappedWellKnownSymbolModule = __webpack_require__(9207); +var defineProperty = __webpack_require__(2760).f; + +module.exports = function (NAME) { + var Symbol = path.Symbol || (path.Symbol = {}); + if (!hasOwn(Symbol, NAME)) defineProperty(Symbol, NAME, { + value: wrappedWellKnownSymbolModule.f(NAME) + }); +}; + + +/***/ }), + +/***/ 69: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); + +// Detect IE8's incomplete defineProperty implementation +module.exports = !fails(function () { + // eslint-disable-next-line es/no-object-defineproperty -- required for testing + return Object.defineProperty({}, 1, { get: function () { return 7; } })[1] != 7; +}); + + +/***/ }), + +/***/ 7449: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var isObject = __webpack_require__(5744); + +var document = global.document; +// typeof document.createElement is 'object' in old IE +var EXISTS = isObject(document) && isObject(document.createElement); + +module.exports = function (it) { + return EXISTS ? document.createElement(it) : {}; +}; + + +/***/ }), + +/***/ 7365: +/***/ (function(module) { + +// iterable DOM collections +// flag - `iterable` interface - 'entries', 'keys', 'values', 'forEach' methods +module.exports = { + CSSRuleList: 0, + CSSStyleDeclaration: 0, + CSSValueList: 0, + ClientRectList: 0, + DOMRectList: 0, + DOMStringList: 0, + DOMTokenList: 1, + DataTransferItemList: 0, + FileList: 0, + HTMLAllCollection: 0, + HTMLCollection: 0, + HTMLFormElement: 0, + HTMLSelectElement: 0, + MediaList: 0, + MimeTypeArray: 0, + NamedNodeMap: 0, + NodeList: 1, + PaintRequestList: 0, + Plugin: 0, + PluginArray: 0, + SVGLengthList: 0, + SVGNumberList: 0, + SVGPathSegList: 0, + SVGPointList: 0, + SVGStringList: 0, + SVGTransformList: 0, + SourceBufferList: 0, + StyleSheetList: 0, + TextTrackCueList: 0, + TextTrackList: 0, + TouchList: 0 +}; + + +/***/ }), + +/***/ 2957: +/***/ (function(module) { + +module.exports = typeof window == 'object'; + + +/***/ }), + +/***/ 9347: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var userAgent = __webpack_require__(8989); +var global = __webpack_require__(8576); + +module.exports = /ipad|iphone|ipod/i.test(userAgent) && global.Pebble !== undefined; + + +/***/ }), + +/***/ 9536: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var userAgent = __webpack_require__(8989); + +module.exports = /(?:ipad|iphone|ipod).*applewebkit/i.test(userAgent); + + +/***/ }), + +/***/ 224: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var classof = __webpack_require__(9272); +var global = __webpack_require__(8576); + +module.exports = classof(global.process) == 'process'; + + +/***/ }), + +/***/ 5914: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var userAgent = __webpack_require__(8989); + +module.exports = /web0s(?!.*chrome)/i.test(userAgent); + + +/***/ }), + +/***/ 8989: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var getBuiltIn = __webpack_require__(150); + +module.exports = getBuiltIn('navigator', 'userAgent') || ''; + + +/***/ }), + +/***/ 4218: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var userAgent = __webpack_require__(8989); + +var process = global.process; +var Deno = global.Deno; +var versions = process && process.versions || Deno && Deno.version; +var v8 = versions && versions.v8; +var match, version; + +if (v8) { + match = v8.split('.'); + version = match[0] < 4 ? 1 : match[0] + match[1]; +} else if (userAgent) { + match = userAgent.match(/Edge\/(\d+)/); + if (!match || match[1] >= 74) { + match = userAgent.match(/Chrome\/(\d+)/); + if (match) version = match[1]; + } +} + +module.exports = version && +version; + + +/***/ }), + +/***/ 5607: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var path = __webpack_require__(7545); + +module.exports = function (CONSTRUCTOR) { + return path[CONSTRUCTOR + 'Prototype']; +}; + + +/***/ }), + +/***/ 2952: +/***/ (function(module) { + +// IE8- don't enum bug keys +module.exports = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' +]; + + +/***/ }), + +/***/ 3085: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var global = __webpack_require__(8576); +var isCallable = __webpack_require__(6447); +var getOwnPropertyDescriptor = __webpack_require__(5141).f; +var isForced = __webpack_require__(9245); +var path = __webpack_require__(7545); +var bind = __webpack_require__(8043); +var createNonEnumerableProperty = __webpack_require__(8711); +var hasOwn = __webpack_require__(4500); + +var wrapConstructor = function (NativeConstructor) { + var Wrapper = function (a, b, c) { + if (this instanceof NativeConstructor) { + switch (arguments.length) { + case 0: return new NativeConstructor(); + case 1: return new NativeConstructor(a); + case 2: return new NativeConstructor(a, b); + } return new NativeConstructor(a, b, c); + } return NativeConstructor.apply(this, arguments); + }; + Wrapper.prototype = NativeConstructor.prototype; + return Wrapper; +}; + +/* + options.target - name of the target object + options.global - target is the global object + options.stat - export as static methods of target + options.proto - export as prototype methods of target + options.real - real prototype method for the `pure` version + options.forced - export even if the native feature is available + options.bind - bind methods to the target, required for the `pure` version + options.wrap - wrap constructors to preventing global pollution, required for the `pure` version + options.unsafe - use the simple assignment of property instead of delete + defineProperty + options.sham - add a flag to not completely full polyfills + options.enumerable - export as enumerable property + options.noTargetGet - prevent calling a getter on target + options.name - the .name of the function if it does not match the key +*/ +module.exports = function (options, source) { + var TARGET = options.target; + var GLOBAL = options.global; + var STATIC = options.stat; + var PROTO = options.proto; + + var nativeSource = GLOBAL ? global : STATIC ? global[TARGET] : (global[TARGET] || {}).prototype; + + var target = GLOBAL ? path : path[TARGET] || createNonEnumerableProperty(path, TARGET, {})[TARGET]; + var targetPrototype = target.prototype; + + var FORCED, USE_NATIVE, VIRTUAL_PROTOTYPE; + var key, sourceProperty, targetProperty, nativeProperty, resultProperty, descriptor; + + for (key in source) { + FORCED = isForced(GLOBAL ? key : TARGET + (STATIC ? '.' : '#') + key, options.forced); + // contains in native + USE_NATIVE = !FORCED && nativeSource && hasOwn(nativeSource, key); + + targetProperty = target[key]; + + if (USE_NATIVE) if (options.noTargetGet) { + descriptor = getOwnPropertyDescriptor(nativeSource, key); + nativeProperty = descriptor && descriptor.value; + } else nativeProperty = nativeSource[key]; + + // export native or implementation + sourceProperty = (USE_NATIVE && nativeProperty) ? nativeProperty : source[key]; + + if (USE_NATIVE && typeof targetProperty === typeof sourceProperty) continue; + + // bind timers to global for call from export context + if (options.bind && USE_NATIVE) resultProperty = bind(sourceProperty, global); + // wrap global constructors for prevent changs in this version + else if (options.wrap && USE_NATIVE) resultProperty = wrapConstructor(sourceProperty); + // make static versions for prototype methods + else if (PROTO && isCallable(sourceProperty)) resultProperty = bind(Function.call, sourceProperty); + // default case + else resultProperty = sourceProperty; + + // add a flag to not completely full polyfills + if (options.sham || (sourceProperty && sourceProperty.sham) || (targetProperty && targetProperty.sham)) { + createNonEnumerableProperty(resultProperty, 'sham', true); + } + + createNonEnumerableProperty(target, key, resultProperty); + + if (PROTO) { + VIRTUAL_PROTOTYPE = TARGET + 'Prototype'; + if (!hasOwn(path, VIRTUAL_PROTOTYPE)) { + createNonEnumerableProperty(path, VIRTUAL_PROTOTYPE, {}); + } + // export virtual prototype methods + createNonEnumerableProperty(path[VIRTUAL_PROTOTYPE], key, sourceProperty); + // export real prototype methods + if (options.real && targetPrototype && !targetPrototype[key]) { + createNonEnumerableProperty(targetPrototype, key, sourceProperty); + } + } + } +}; + + +/***/ }), + +/***/ 6192: +/***/ (function(module) { + +module.exports = function (exec) { + try { + return !!exec(); + } catch (error) { + return true; + } +}; + + +/***/ }), + +/***/ 8043: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var aCallable = __webpack_require__(6235); + +// optional / simple context binding +module.exports = function (fn, that, length) { + aCallable(fn); + if (that === undefined) return fn; + switch (length) { + case 0: return function () { + return fn.call(that); + }; + case 1: return function (a) { + return fn.call(that, a); + }; + case 2: return function (a, b) { + return fn.call(that, a, b); + }; + case 3: return function (a, b, c) { + return fn.call(that, a, b, c); + }; + } + return function (/* ...args */) { + return fn.apply(that, arguments); + }; +}; + + +/***/ }), + +/***/ 6782: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var aCallable = __webpack_require__(6235); +var isObject = __webpack_require__(5744); + +var slice = [].slice; +var factories = {}; + +var construct = function (C, argsLength, args) { + if (!(argsLength in factories)) { + for (var list = [], i = 0; i < argsLength; i++) list[i] = 'a[' + i + ']'; + // eslint-disable-next-line no-new-func -- we have no proper alternatives, IE8- only + factories[argsLength] = Function('C,a', 'return new C(' + list.join(',') + ')'); + } return factories[argsLength](C, args); +}; + +// `Function.prototype.bind` method implementation +// https://tc39.es/ecma262/#sec-function.prototype.bind +module.exports = Function.bind || function bind(that /* , ...args */) { + var fn = aCallable(this); + var partArgs = slice.call(arguments, 1); + var boundFunction = function bound(/* args... */) { + var args = partArgs.concat(slice.call(arguments)); + return this instanceof boundFunction ? construct(fn, args.length, args) : fn.apply(that, args); + }; + if (isObject(fn.prototype)) boundFunction.prototype = fn.prototype; + return boundFunction; +}; + + +/***/ }), + +/***/ 2282: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var hasOwn = __webpack_require__(4500); + +var FunctionPrototype = Function.prototype; +// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe +var getDescriptor = DESCRIPTORS && Object.getOwnPropertyDescriptor; + +var EXISTS = hasOwn(FunctionPrototype, 'name'); +// additional protection from minified / mangled / dropped function names +var PROPER = EXISTS && (function something() { /* empty */ }).name === 'something'; +var CONFIGURABLE = EXISTS && (!DESCRIPTORS || (DESCRIPTORS && getDescriptor(FunctionPrototype, 'name').configurable)); + +module.exports = { + EXISTS: EXISTS, + PROPER: PROPER, + CONFIGURABLE: CONFIGURABLE +}; + + +/***/ }), + +/***/ 150: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var path = __webpack_require__(7545); +var global = __webpack_require__(8576); +var isCallable = __webpack_require__(6447); + +var aFunction = function (variable) { + return isCallable(variable) ? variable : undefined; +}; + +module.exports = function (namespace, method) { + return arguments.length < 2 ? aFunction(path[namespace]) || aFunction(global[namespace]) + : path[namespace] && path[namespace][method] || global[namespace] && global[namespace][method]; +}; + + +/***/ }), + +/***/ 8703: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var classof = __webpack_require__(4696); +var getMethod = __webpack_require__(5037); +var Iterators = __webpack_require__(7771); +var wellKnownSymbol = __webpack_require__(8182); + +var ITERATOR = wellKnownSymbol('iterator'); + +module.exports = function (it) { + if (it != undefined) return getMethod(it, ITERATOR) + || getMethod(it, '@@iterator') + || Iterators[classof(it)]; +}; + + +/***/ }), + +/***/ 1669: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var aCallable = __webpack_require__(6235); +var anObject = __webpack_require__(1138); +var getIteratorMethod = __webpack_require__(8703); + +module.exports = function (argument, usingIterator) { + var iteratorMethod = arguments.length < 2 ? getIteratorMethod(argument) : usingIterator; + if (aCallable(iteratorMethod)) return anObject(iteratorMethod.call(argument)); + throw TypeError(String(argument) + ' is not iterable'); +}; + + +/***/ }), + +/***/ 5037: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var aCallable = __webpack_require__(6235); + +// `GetMethod` abstract operation +// https://tc39.es/ecma262/#sec-getmethod +module.exports = function (V, P) { + var func = V[P]; + return func == null ? undefined : aCallable(func); +}; + + +/***/ }), + +/***/ 8576: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var check = function (it) { + return it && it.Math == Math && it; +}; + +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +module.exports = + // eslint-disable-next-line es/no-global-this -- safe + check(typeof globalThis == 'object' && globalThis) || + check(typeof window == 'object' && window) || + // eslint-disable-next-line no-restricted-globals -- safe + check(typeof self == 'object' && self) || + check(typeof __webpack_require__.g == 'object' && __webpack_require__.g) || + // eslint-disable-next-line no-new-func -- fallback + (function () { return this; })() || Function('return this')(); + + +/***/ }), + +/***/ 4500: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toObject = __webpack_require__(1795); + +var hasOwnProperty = {}.hasOwnProperty; + +// `HasOwnProperty` abstract operation +// https://tc39.es/ecma262/#sec-hasownproperty +module.exports = Object.hasOwn || function hasOwn(it, key) { + return hasOwnProperty.call(toObject(it), key); +}; + + +/***/ }), + +/***/ 4535: +/***/ (function(module) { + +module.exports = {}; + + +/***/ }), + +/***/ 3681: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); + +module.exports = function (a, b) { + var console = global.console; + if (console && console.error) { + arguments.length === 1 ? console.error(a) : console.error(a, b); + } +}; + + +/***/ }), + +/***/ 7403: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var getBuiltIn = __webpack_require__(150); + +module.exports = getBuiltIn('document', 'documentElement'); + + +/***/ }), + +/***/ 188: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var fails = __webpack_require__(6192); +var createElement = __webpack_require__(7449); + +// Thank's IE8 for his funny defineProperty +module.exports = !DESCRIPTORS && !fails(function () { + // eslint-disable-next-line es/no-object-defineproperty -- requied for testing + return Object.defineProperty(createElement('div'), 'a', { + get: function () { return 7; } + }).a != 7; +}); + + +/***/ }), + +/***/ 2202: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); +var classof = __webpack_require__(9272); + +var split = ''.split; + +// fallback for non-array-like ES3 and non-enumerable old V8 strings +module.exports = fails(function () { + // throws an error in rhino, see https://github.com/mozilla/rhino/issues/346 + // eslint-disable-next-line no-prototype-builtins -- safe + return !Object('z').propertyIsEnumerable(0); +}) ? function (it) { + return classof(it) == 'String' ? split.call(it, '') : Object(it); +} : Object; + + +/***/ }), + +/***/ 9516: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); +var store = __webpack_require__(6434); + +var functionToString = Function.toString; + +// this helper broken in `core-js@3.4.1-3.4.4`, so we can't use `shared` helper +if (!isCallable(store.inspectSource)) { + store.inspectSource = function (it) { + return functionToString.call(it); + }; +} + +module.exports = store.inspectSource; + + +/***/ }), + +/***/ 273: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isObject = __webpack_require__(5744); +var createNonEnumerableProperty = __webpack_require__(8711); + +// `InstallErrorCause` abstract operation +// https://tc39.es/proposal-error-cause/#sec-errorobjects-install-error-cause +module.exports = function (O, options) { + if (isObject(options) && 'cause' in options) { + createNonEnumerableProperty(O, 'cause', O.cause); + } +}; + + +/***/ }), + +/***/ 3326: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var NATIVE_WEAK_MAP = __webpack_require__(8921); +var global = __webpack_require__(8576); +var isObject = __webpack_require__(5744); +var createNonEnumerableProperty = __webpack_require__(8711); +var hasOwn = __webpack_require__(4500); +var shared = __webpack_require__(6434); +var sharedKey = __webpack_require__(9766); +var hiddenKeys = __webpack_require__(4535); + +var OBJECT_ALREADY_INITIALIZED = 'Object already initialized'; +var WeakMap = global.WeakMap; +var set, get, has; + +var enforce = function (it) { + return has(it) ? get(it) : set(it, {}); +}; + +var getterFor = function (TYPE) { + return function (it) { + var state; + if (!isObject(it) || (state = get(it)).type !== TYPE) { + throw TypeError('Incompatible receiver, ' + TYPE + ' required'); + } return state; + }; +}; + +if (NATIVE_WEAK_MAP || shared.state) { + var store = shared.state || (shared.state = new WeakMap()); + var wmget = store.get; + var wmhas = store.has; + var wmset = store.set; + set = function (it, metadata) { + if (wmhas.call(store, it)) throw new TypeError(OBJECT_ALREADY_INITIALIZED); + metadata.facade = it; + wmset.call(store, it, metadata); + return metadata; + }; + get = function (it) { + return wmget.call(store, it) || {}; + }; + has = function (it) { + return wmhas.call(store, it); + }; +} else { + var STATE = sharedKey('state'); + hiddenKeys[STATE] = true; + set = function (it, metadata) { + if (hasOwn(it, STATE)) throw new TypeError(OBJECT_ALREADY_INITIALIZED); + metadata.facade = it; + createNonEnumerableProperty(it, STATE, metadata); + return metadata; + }; + get = function (it) { + return hasOwn(it, STATE) ? it[STATE] : {}; + }; + has = function (it) { + return hasOwn(it, STATE); + }; +} + +module.exports = { + set: set, + get: get, + has: has, + enforce: enforce, + getterFor: getterFor +}; + + +/***/ }), + +/***/ 6109: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var wellKnownSymbol = __webpack_require__(8182); +var Iterators = __webpack_require__(7771); + +var ITERATOR = wellKnownSymbol('iterator'); +var ArrayPrototype = Array.prototype; + +// check on default Array iterator +module.exports = function (it) { + return it !== undefined && (Iterators.Array === it || ArrayPrototype[ITERATOR] === it); +}; + + +/***/ }), + +/***/ 4770: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var classof = __webpack_require__(9272); + +// `IsArray` abstract operation +// https://tc39.es/ecma262/#sec-isarray +// eslint-disable-next-line es/no-array-isarray -- safe +module.exports = Array.isArray || function isArray(argument) { + return classof(argument) == 'Array'; +}; + + +/***/ }), + +/***/ 6447: +/***/ (function(module) { + +// `IsCallable` abstract operation +// https://tc39.es/ecma262/#sec-iscallable +module.exports = function (argument) { + return typeof argument === 'function'; +}; + + +/***/ }), + +/***/ 2091: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); +var isCallable = __webpack_require__(6447); +var classof = __webpack_require__(4696); +var getBuiltIn = __webpack_require__(150); +var inspectSource = __webpack_require__(9516); + +var empty = []; +var construct = getBuiltIn('Reflect', 'construct'); +var constructorRegExp = /^\s*(?:class|function)\b/; +var exec = constructorRegExp.exec; +var INCORRECT_TO_STRING = !constructorRegExp.exec(function () { /* empty */ }); + +var isConstructorModern = function (argument) { + if (!isCallable(argument)) return false; + try { + construct(Object, empty, argument); + return true; + } catch (error) { + return false; + } +}; + +var isConstructorLegacy = function (argument) { + if (!isCallable(argument)) return false; + switch (classof(argument)) { + case 'AsyncFunction': + case 'GeneratorFunction': + case 'AsyncGeneratorFunction': return false; + // we can't check .prototype since constructors produced by .bind haven't it + } return INCORRECT_TO_STRING || !!exec.call(constructorRegExp, inspectSource(argument)); +}; + +// `IsConstructor` abstract operation +// https://tc39.es/ecma262/#sec-isconstructor +module.exports = !construct || fails(function () { + var called; + return isConstructorModern(isConstructorModern.call) + || !isConstructorModern(Object) + || !isConstructorModern(function () { called = true; }) + || called; +}) ? isConstructorLegacy : isConstructorModern; + + +/***/ }), + +/***/ 9245: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); +var isCallable = __webpack_require__(6447); + +var replacement = /#|\.prototype\./; + +var isForced = function (feature, detection) { + var value = data[normalize(feature)]; + return value == POLYFILL ? true + : value == NATIVE ? false + : isCallable(detection) ? fails(detection) + : !!detection; +}; + +var normalize = isForced.normalize = function (string) { + return String(string).replace(replacement, '.').toLowerCase(); +}; + +var data = isForced.data = {}; +var NATIVE = isForced.NATIVE = 'N'; +var POLYFILL = isForced.POLYFILL = 'P'; + +module.exports = isForced; + + +/***/ }), + +/***/ 5744: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); + +module.exports = function (it) { + return typeof it === 'object' ? it !== null : isCallable(it); +}; + + +/***/ }), + +/***/ 5546: +/***/ (function(module) { + +module.exports = true; + + +/***/ }), + +/***/ 3236: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); +var getBuiltIn = __webpack_require__(150); +var USE_SYMBOL_AS_UID = __webpack_require__(615); + +module.exports = USE_SYMBOL_AS_UID ? function (it) { + return typeof it == 'symbol'; +} : function (it) { + var $Symbol = getBuiltIn('Symbol'); + return isCallable($Symbol) && Object(it) instanceof $Symbol; +}; + + +/***/ }), + +/***/ 3442: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var anObject = __webpack_require__(1138); +var isArrayIteratorMethod = __webpack_require__(6109); +var lengthOfArrayLike = __webpack_require__(4104); +var bind = __webpack_require__(8043); +var getIterator = __webpack_require__(1669); +var getIteratorMethod = __webpack_require__(8703); +var iteratorClose = __webpack_require__(6639); + +var Result = function (stopped, result) { + this.stopped = stopped; + this.result = result; +}; + +module.exports = function (iterable, unboundFunction, options) { + var that = options && options.that; + var AS_ENTRIES = !!(options && options.AS_ENTRIES); + var IS_ITERATOR = !!(options && options.IS_ITERATOR); + var INTERRUPTED = !!(options && options.INTERRUPTED); + var fn = bind(unboundFunction, that, 1 + AS_ENTRIES + INTERRUPTED); + var iterator, iterFn, index, length, result, next, step; + + var stop = function (condition) { + if (iterator) iteratorClose(iterator, 'normal', condition); + return new Result(true, condition); + }; + + var callFn = function (value) { + if (AS_ENTRIES) { + anObject(value); + return INTERRUPTED ? fn(value[0], value[1], stop) : fn(value[0], value[1]); + } return INTERRUPTED ? fn(value, stop) : fn(value); + }; + + if (IS_ITERATOR) { + iterator = iterable; + } else { + iterFn = getIteratorMethod(iterable); + if (!iterFn) throw TypeError(String(iterable) + ' is not iterable'); + // optimisation for array iterators + if (isArrayIteratorMethod(iterFn)) { + for (index = 0, length = lengthOfArrayLike(iterable); length > index; index++) { + result = callFn(iterable[index]); + if (result && result instanceof Result) return result; + } return new Result(false); + } + iterator = getIterator(iterable, iterFn); + } + + next = iterator.next; + while (!(step = next.call(iterator)).done) { + try { + result = callFn(step.value); + } catch (error) { + iteratorClose(iterator, 'throw', error); + } + if (typeof result == 'object' && result && result instanceof Result) return result; + } return new Result(false); +}; + + +/***/ }), + +/***/ 6639: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var anObject = __webpack_require__(1138); +var getMethod = __webpack_require__(5037); + +module.exports = function (iterator, kind, value) { + var innerResult, innerError; + anObject(iterator); + try { + innerResult = getMethod(iterator, 'return'); + if (!innerResult) { + if (kind === 'throw') throw value; + return value; + } + innerResult = innerResult.call(iterator); + } catch (error) { + innerError = true; + innerResult = error; + } + if (kind === 'throw') throw value; + if (innerError) throw innerResult; + anObject(innerResult); + return value; +}; + + +/***/ }), + +/***/ 4413: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var fails = __webpack_require__(6192); +var isCallable = __webpack_require__(6447); +var create = __webpack_require__(2853); +var getPrototypeOf = __webpack_require__(9341); +var redefine = __webpack_require__(9482); +var wellKnownSymbol = __webpack_require__(8182); +var IS_PURE = __webpack_require__(5546); + +var ITERATOR = wellKnownSymbol('iterator'); +var BUGGY_SAFARI_ITERATORS = false; + +// `%IteratorPrototype%` object +// https://tc39.es/ecma262/#sec-%iteratorprototype%-object +var IteratorPrototype, PrototypeOfArrayIteratorPrototype, arrayIterator; + +/* eslint-disable es/no-array-prototype-keys -- safe */ +if ([].keys) { + arrayIterator = [].keys(); + // Safari 8 has buggy iterators w/o `next` + if (!('next' in arrayIterator)) BUGGY_SAFARI_ITERATORS = true; + else { + PrototypeOfArrayIteratorPrototype = getPrototypeOf(getPrototypeOf(arrayIterator)); + if (PrototypeOfArrayIteratorPrototype !== Object.prototype) IteratorPrototype = PrototypeOfArrayIteratorPrototype; + } +} + +var NEW_ITERATOR_PROTOTYPE = IteratorPrototype == undefined || fails(function () { + var test = {}; + // FF44- legacy iterators case + return IteratorPrototype[ITERATOR].call(test) !== test; +}); + +if (NEW_ITERATOR_PROTOTYPE) IteratorPrototype = {}; +else if (IS_PURE) IteratorPrototype = create(IteratorPrototype); + +// `%IteratorPrototype%[@@iterator]()` method +// https://tc39.es/ecma262/#sec-%iteratorprototype%-@@iterator +if (!isCallable(IteratorPrototype[ITERATOR])) { + redefine(IteratorPrototype, ITERATOR, function () { + return this; + }); +} + +module.exports = { + IteratorPrototype: IteratorPrototype, + BUGGY_SAFARI_ITERATORS: BUGGY_SAFARI_ITERATORS +}; + + +/***/ }), + +/***/ 7771: +/***/ (function(module) { + +module.exports = {}; + + +/***/ }), + +/***/ 4104: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toLength = __webpack_require__(8445); + +// `LengthOfArrayLike` abstract operation +// https://tc39.es/ecma262/#sec-lengthofarraylike +module.exports = function (obj) { + return toLength(obj.length); +}; + + +/***/ }), + +/***/ 2950: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var getOwnPropertyDescriptor = __webpack_require__(5141).f; +var macrotask = __webpack_require__(7160).set; +var IS_IOS = __webpack_require__(9536); +var IS_IOS_PEBBLE = __webpack_require__(9347); +var IS_WEBOS_WEBKIT = __webpack_require__(5914); +var IS_NODE = __webpack_require__(224); + +var MutationObserver = global.MutationObserver || global.WebKitMutationObserver; +var document = global.document; +var process = global.process; +var Promise = global.Promise; +// Node.js 11 shows ExperimentalWarning on getting `queueMicrotask` +var queueMicrotaskDescriptor = getOwnPropertyDescriptor(global, 'queueMicrotask'); +var queueMicrotask = queueMicrotaskDescriptor && queueMicrotaskDescriptor.value; + +var flush, head, last, notify, toggle, node, promise, then; + +// modern engines have queueMicrotask method +if (!queueMicrotask) { + flush = function () { + var parent, fn; + if (IS_NODE && (parent = process.domain)) parent.exit(); + while (head) { + fn = head.fn; + head = head.next; + try { + fn(); + } catch (error) { + if (head) notify(); + else last = undefined; + throw error; + } + } last = undefined; + if (parent) parent.enter(); + }; + + // browsers with MutationObserver, except iOS - https://github.com/zloirock/core-js/issues/339 + // also except WebOS Webkit https://github.com/zloirock/core-js/issues/898 + if (!IS_IOS && !IS_NODE && !IS_WEBOS_WEBKIT && MutationObserver && document) { + toggle = true; + node = document.createTextNode(''); + new MutationObserver(flush).observe(node, { characterData: true }); + notify = function () { + node.data = toggle = !toggle; + }; + // environments with maybe non-completely correct, but existent Promise + } else if (!IS_IOS_PEBBLE && Promise && Promise.resolve) { + // Promise.resolve without an argument throws an error in LG WebOS 2 + promise = Promise.resolve(undefined); + // workaround of WebKit ~ iOS Safari 10.1 bug + promise.constructor = Promise; + then = promise.then; + notify = function () { + then.call(promise, flush); + }; + // Node.js without promises + } else if (IS_NODE) { + notify = function () { + process.nextTick(flush); + }; + // for other environments - macrotask based on: + // - setImmediate + // - MessageChannel + // - window.postMessag + // - onreadystatechange + // - setTimeout + } else { + notify = function () { + // strange IE + webpack dev server bug - use .call(global) + macrotask.call(global, flush); + }; + } +} + +module.exports = queueMicrotask || function (fn) { + var task = { fn: fn, next: undefined }; + if (last) last.next = task; + if (!head) { + head = task; + notify(); + } last = task; +}; + + +/***/ }), + +/***/ 4471: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); + +module.exports = global.Promise; + + +/***/ }), + +/***/ 3045: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/* eslint-disable es/no-symbol -- required for testing */ +var V8_VERSION = __webpack_require__(4218); +var fails = __webpack_require__(6192); + +// eslint-disable-next-line es/no-object-getownpropertysymbols -- required for testing +module.exports = !!Object.getOwnPropertySymbols && !fails(function () { + var symbol = Symbol(); + // Chrome 38 Symbol has incorrect toString conversion + // `get-own-property-symbols` polyfill symbols converted to object are not Symbol instances + return !String(symbol) || !(Object(symbol) instanceof Symbol) || + // Chrome 38-40 symbols are not inherited from DOM collections prototypes to instances + !Symbol.sham && V8_VERSION && V8_VERSION < 41; +}); + + +/***/ }), + +/***/ 4551: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var fails = __webpack_require__(6192); +var wellKnownSymbol = __webpack_require__(8182); +var IS_PURE = __webpack_require__(5546); + +var ITERATOR = wellKnownSymbol('iterator'); + +module.exports = !fails(function () { + var url = new URL('b?a=1&b=2&c=3', 'http://a'); + var searchParams = url.searchParams; + var result = ''; + url.pathname = 'c%20d'; + searchParams.forEach(function (value, key) { + searchParams['delete']('b'); + result += key + value; + }); + return (IS_PURE && !url.toJSON) + || !searchParams.sort + || url.href !== 'http://a/c%20d?a=1&c=3' + || searchParams.get('c') !== '3' + || String(new URLSearchParams('?a=1')) !== 'a=1' + || !searchParams[ITERATOR] + // throws in Edge + || new URL('https://a@b').username !== 'a' + || new URLSearchParams(new URLSearchParams('a=b')).get('a') !== 'b' + // not punycoded in Edge + || new URL('http://тест').host !== 'xn--e1aybc' + // not escaped in Chrome 62- + || new URL('http://a#б').hash !== '#%D0%B1' + // fails in Chrome 66- + || result !== 'a1c3' + // throws in Safari + || new URL('http://x', undefined).host !== 'x'; +}); + + +/***/ }), + +/***/ 8921: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var isCallable = __webpack_require__(6447); +var inspectSource = __webpack_require__(9516); + +var WeakMap = global.WeakMap; + +module.exports = isCallable(WeakMap) && /native code/.test(inspectSource(WeakMap)); + + +/***/ }), + +/***/ 9438: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var aCallable = __webpack_require__(6235); + +var PromiseCapability = function (C) { + var resolve, reject; + this.promise = new C(function ($$resolve, $$reject) { + if (resolve !== undefined || reject !== undefined) throw TypeError('Bad Promise constructor'); + resolve = $$resolve; + reject = $$reject; + }); + this.resolve = aCallable(resolve); + this.reject = aCallable(reject); +}; + +// `NewPromiseCapability` abstract operation +// https://tc39.es/ecma262/#sec-newpromisecapability +module.exports.f = function (C) { + return new PromiseCapability(C); +}; + + +/***/ }), + +/***/ 15: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var fails = __webpack_require__(6192); +var toString = __webpack_require__(4845); +var trim = __webpack_require__(4277).trim; +var whitespaces = __webpack_require__(1450); + +var $parseFloat = global.parseFloat; +var Symbol = global.Symbol; +var ITERATOR = Symbol && Symbol.iterator; +var FORCED = 1 / $parseFloat(whitespaces + '-0') !== -Infinity + // MS Edge 18- broken with boxed symbols + || (ITERATOR && !fails(function () { $parseFloat(Object(ITERATOR)); })); + +// `parseFloat` method +// https://tc39.es/ecma262/#sec-parsefloat-string +module.exports = FORCED ? function parseFloat(string) { + var trimmedString = trim(toString(string)); + var result = $parseFloat(trimmedString); + return result === 0 && trimmedString.charAt(0) == '-' ? -0 : result; +} : $parseFloat; + + +/***/ }), + +/***/ 2558: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var fails = __webpack_require__(6192); +var toString = __webpack_require__(4845); +var trim = __webpack_require__(4277).trim; +var whitespaces = __webpack_require__(1450); + +var $parseInt = global.parseInt; +var Symbol = global.Symbol; +var ITERATOR = Symbol && Symbol.iterator; +var hex = /^[+-]?0[Xx]/; +var FORCED = $parseInt(whitespaces + '08') !== 8 || $parseInt(whitespaces + '0x16') !== 22 + // MS Edge 18- broken with boxed symbols + || (ITERATOR && !fails(function () { $parseInt(Object(ITERATOR)); })); + +// `parseInt` method +// https://tc39.es/ecma262/#sec-parseint-string-radix +module.exports = FORCED ? function parseInt(string, radix) { + var S = trim(toString(string)); + return $parseInt(S, (radix >>> 0) || (hex.test(S) ? 16 : 10)); +} : $parseInt; + + +/***/ }), + +/***/ 2503: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var DESCRIPTORS = __webpack_require__(69); +var fails = __webpack_require__(6192); +var objectKeys = __webpack_require__(7653); +var getOwnPropertySymbolsModule = __webpack_require__(4750); +var propertyIsEnumerableModule = __webpack_require__(6007); +var toObject = __webpack_require__(1795); +var IndexedObject = __webpack_require__(2202); + +// eslint-disable-next-line es/no-object-assign -- safe +var $assign = Object.assign; +// eslint-disable-next-line es/no-object-defineproperty -- required for testing +var defineProperty = Object.defineProperty; + +// `Object.assign` method +// https://tc39.es/ecma262/#sec-object.assign +module.exports = !$assign || fails(function () { + // should have correct order of operations (Edge bug) + if (DESCRIPTORS && $assign({ b: 1 }, $assign(defineProperty({}, 'a', { + enumerable: true, + get: function () { + defineProperty(this, 'b', { + value: 3, + enumerable: false + }); + } + }), { b: 2 })).b !== 1) return true; + // should work with symbols and should have deterministic property order (V8 bug) + var A = {}; + var B = {}; + // eslint-disable-next-line es/no-symbol -- safe + var symbol = Symbol(); + var alphabet = 'abcdefghijklmnopqrst'; + A[symbol] = 7; + alphabet.split('').forEach(function (chr) { B[chr] = chr; }); + return $assign({}, A)[symbol] != 7 || objectKeys($assign({}, B)).join('') != alphabet; +}) ? function assign(target, source) { // eslint-disable-line no-unused-vars -- required for `.length` + var T = toObject(target); + var argumentsLength = arguments.length; + var index = 1; + var getOwnPropertySymbols = getOwnPropertySymbolsModule.f; + var propertyIsEnumerable = propertyIsEnumerableModule.f; + while (argumentsLength > index) { + var S = IndexedObject(arguments[index++]); + var keys = getOwnPropertySymbols ? objectKeys(S).concat(getOwnPropertySymbols(S)) : objectKeys(S); + var length = keys.length; + var j = 0; + var key; + while (length > j) { + key = keys[j++]; + if (!DESCRIPTORS || propertyIsEnumerable.call(S, key)) T[key] = S[key]; + } + } return T; +} : $assign; + + +/***/ }), + +/***/ 2853: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/* global ActiveXObject -- old IE, WSH */ +var anObject = __webpack_require__(1138); +var defineProperties = __webpack_require__(1187); +var enumBugKeys = __webpack_require__(2952); +var hiddenKeys = __webpack_require__(4535); +var html = __webpack_require__(7403); +var documentCreateElement = __webpack_require__(7449); +var sharedKey = __webpack_require__(9766); + +var GT = '>'; +var LT = '<'; +var PROTOTYPE = 'prototype'; +var SCRIPT = 'script'; +var IE_PROTO = sharedKey('IE_PROTO'); + +var EmptyConstructor = function () { /* empty */ }; + +var scriptTag = function (content) { + return LT + SCRIPT + GT + content + LT + '/' + SCRIPT + GT; +}; + +// Create object with fake `null` prototype: use ActiveX Object with cleared prototype +var NullProtoObjectViaActiveX = function (activeXDocument) { + activeXDocument.write(scriptTag('')); + activeXDocument.close(); + var temp = activeXDocument.parentWindow.Object; + activeXDocument = null; // avoid memory leak + return temp; +}; + +// Create object with fake `null` prototype: use iframe Object with cleared prototype +var NullProtoObjectViaIFrame = function () { + // Thrash, waste and sodomy: IE GC bug + var iframe = documentCreateElement('iframe'); + var JS = 'java' + SCRIPT + ':'; + var iframeDocument; + iframe.style.display = 'none'; + html.appendChild(iframe); + // https://github.com/zloirock/core-js/issues/475 + iframe.src = String(JS); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(scriptTag('document.F=Object')); + iframeDocument.close(); + return iframeDocument.F; +}; + +// Check for document.domain and active x support +// No need to use active x approach when document.domain is not set +// see https://github.com/es-shims/es5-shim/issues/150 +// variation of https://github.com/kitcambridge/es5-shim/commit/4f738ac066346 +// avoid IE GC bug +var activeXDocument; +var NullProtoObject = function () { + try { + activeXDocument = new ActiveXObject('htmlfile'); + } catch (error) { /* ignore */ } + NullProtoObject = typeof document != 'undefined' + ? document.domain && activeXDocument + ? NullProtoObjectViaActiveX(activeXDocument) // old IE + : NullProtoObjectViaIFrame() + : NullProtoObjectViaActiveX(activeXDocument); // WSH + var length = enumBugKeys.length; + while (length--) delete NullProtoObject[PROTOTYPE][enumBugKeys[length]]; + return NullProtoObject(); +}; + +hiddenKeys[IE_PROTO] = true; + +// `Object.create` method +// https://tc39.es/ecma262/#sec-object.create +module.exports = Object.create || function create(O, Properties) { + var result; + if (O !== null) { + EmptyConstructor[PROTOTYPE] = anObject(O); + result = new EmptyConstructor(); + EmptyConstructor[PROTOTYPE] = null; + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O; + } else result = NullProtoObject(); + return Properties === undefined ? result : defineProperties(result, Properties); +}; + + +/***/ }), + +/***/ 1187: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var definePropertyModule = __webpack_require__(2760); +var anObject = __webpack_require__(1138); +var objectKeys = __webpack_require__(7653); + +// `Object.defineProperties` method +// https://tc39.es/ecma262/#sec-object.defineproperties +// eslint-disable-next-line es/no-object-defineproperties -- safe +module.exports = DESCRIPTORS ? Object.defineProperties : function defineProperties(O, Properties) { + anObject(O); + var keys = objectKeys(Properties); + var length = keys.length; + var index = 0; + var key; + while (length > index) definePropertyModule.f(O, key = keys[index++], Properties[key]); + return O; +}; + + +/***/ }), + +/***/ 2760: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var IE8_DOM_DEFINE = __webpack_require__(188); +var anObject = __webpack_require__(1138); +var toPropertyKey = __webpack_require__(77); + +// eslint-disable-next-line es/no-object-defineproperty -- safe +var $defineProperty = Object.defineProperty; + +// `Object.defineProperty` method +// https://tc39.es/ecma262/#sec-object.defineproperty +exports.f = DESCRIPTORS ? $defineProperty : function defineProperty(O, P, Attributes) { + anObject(O); + P = toPropertyKey(P); + anObject(Attributes); + if (IE8_DOM_DEFINE) try { + return $defineProperty(O, P, Attributes); + } catch (error) { /* empty */ } + if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported'); + if ('value' in Attributes) O[P] = Attributes.value; + return O; +}; + + +/***/ }), + +/***/ 5141: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +var DESCRIPTORS = __webpack_require__(69); +var propertyIsEnumerableModule = __webpack_require__(6007); +var createPropertyDescriptor = __webpack_require__(774); +var toIndexedObject = __webpack_require__(101); +var toPropertyKey = __webpack_require__(77); +var hasOwn = __webpack_require__(4500); +var IE8_DOM_DEFINE = __webpack_require__(188); + +// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe +var $getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + +// `Object.getOwnPropertyDescriptor` method +// https://tc39.es/ecma262/#sec-object.getownpropertydescriptor +exports.f = DESCRIPTORS ? $getOwnPropertyDescriptor : function getOwnPropertyDescriptor(O, P) { + O = toIndexedObject(O); + P = toPropertyKey(P); + if (IE8_DOM_DEFINE) try { + return $getOwnPropertyDescriptor(O, P); + } catch (error) { /* empty */ } + if (hasOwn(O, P)) return createPropertyDescriptor(!propertyIsEnumerableModule.f.call(O, P), O[P]); +}; + + +/***/ }), + +/***/ 4052: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/* eslint-disable es/no-object-getownpropertynames -- safe */ +var toIndexedObject = __webpack_require__(101); +var $getOwnPropertyNames = __webpack_require__(2092).f; + +var toString = {}.toString; + +var windowNames = typeof window == 'object' && window && Object.getOwnPropertyNames + ? Object.getOwnPropertyNames(window) : []; + +var getWindowNames = function (it) { + try { + return $getOwnPropertyNames(it); + } catch (error) { + return windowNames.slice(); + } +}; + +// fallback for IE11 buggy Object.getOwnPropertyNames with iframe and window +module.exports.f = function getOwnPropertyNames(it) { + return windowNames && toString.call(it) == '[object Window]' + ? getWindowNames(it) + : $getOwnPropertyNames(toIndexedObject(it)); +}; + + +/***/ }), + +/***/ 2092: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +var internalObjectKeys = __webpack_require__(7934); +var enumBugKeys = __webpack_require__(2952); + +var hiddenKeys = enumBugKeys.concat('length', 'prototype'); + +// `Object.getOwnPropertyNames` method +// https://tc39.es/ecma262/#sec-object.getownpropertynames +// eslint-disable-next-line es/no-object-getownpropertynames -- safe +exports.f = Object.getOwnPropertyNames || function getOwnPropertyNames(O) { + return internalObjectKeys(O, hiddenKeys); +}; + + +/***/ }), + +/***/ 4750: +/***/ (function(__unused_webpack_module, exports) { + +// eslint-disable-next-line es/no-object-getownpropertysymbols -- safe +exports.f = Object.getOwnPropertySymbols; + + +/***/ }), + +/***/ 9341: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var hasOwn = __webpack_require__(4500); +var isCallable = __webpack_require__(6447); +var toObject = __webpack_require__(1795); +var sharedKey = __webpack_require__(9766); +var CORRECT_PROTOTYPE_GETTER = __webpack_require__(4635); + +var IE_PROTO = sharedKey('IE_PROTO'); +var ObjectPrototype = Object.prototype; + +// `Object.getPrototypeOf` method +// https://tc39.es/ecma262/#sec-object.getprototypeof +// eslint-disable-next-line es/no-object-getprototypeof -- safe +module.exports = CORRECT_PROTOTYPE_GETTER ? Object.getPrototypeOf : function (O) { + var object = toObject(O); + if (hasOwn(object, IE_PROTO)) return object[IE_PROTO]; + var constructor = object.constructor; + if (isCallable(constructor) && object instanceof constructor) { + return constructor.prototype; + } return object instanceof Object ? ObjectPrototype : null; +}; + + +/***/ }), + +/***/ 7934: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var hasOwn = __webpack_require__(4500); +var toIndexedObject = __webpack_require__(101); +var indexOf = __webpack_require__(8180).indexOf; +var hiddenKeys = __webpack_require__(4535); + +module.exports = function (object, names) { + var O = toIndexedObject(object); + var i = 0; + var result = []; + var key; + for (key in O) !hasOwn(hiddenKeys, key) && hasOwn(O, key) && result.push(key); + // Don't enum bug & hidden keys + while (names.length > i) if (hasOwn(O, key = names[i++])) { + ~indexOf(result, key) || result.push(key); + } + return result; +}; + + +/***/ }), + +/***/ 7653: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var internalObjectKeys = __webpack_require__(7934); +var enumBugKeys = __webpack_require__(2952); + +// `Object.keys` method +// https://tc39.es/ecma262/#sec-object.keys +// eslint-disable-next-line es/no-object-keys -- safe +module.exports = Object.keys || function keys(O) { + return internalObjectKeys(O, enumBugKeys); +}; + + +/***/ }), + +/***/ 6007: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var $propertyIsEnumerable = {}.propertyIsEnumerable; +// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe +var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + +// Nashorn ~ JDK8 bug +var NASHORN_BUG = getOwnPropertyDescriptor && !$propertyIsEnumerable.call({ 1: 2 }, 1); + +// `Object.prototype.propertyIsEnumerable` method implementation +// https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable +exports.f = NASHORN_BUG ? function propertyIsEnumerable(V) { + var descriptor = getOwnPropertyDescriptor(this, V); + return !!descriptor && descriptor.enumerable; +} : $propertyIsEnumerable; + + +/***/ }), + +/***/ 4469: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/* eslint-disable no-proto -- safe */ +var anObject = __webpack_require__(1138); +var aPossiblePrototype = __webpack_require__(7757); + +// `Object.setPrototypeOf` method +// https://tc39.es/ecma262/#sec-object.setprototypeof +// Works with __proto__ only. Old v8 can't work with null proto objects. +// eslint-disable-next-line es/no-object-setprototypeof -- safe +module.exports = Object.setPrototypeOf || ('__proto__' in {} ? function () { + var CORRECT_SETTER = false; + var test = {}; + var setter; + try { + // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe + setter = Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set; + setter.call(test, []); + CORRECT_SETTER = test instanceof Array; + } catch (error) { /* empty */ } + return function setPrototypeOf(O, proto) { + anObject(O); + aPossiblePrototype(proto); + if (CORRECT_SETTER) setter.call(O, proto); + else O.__proto__ = proto; + return O; + }; +}() : undefined); + + +/***/ }), + +/***/ 158: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var TO_STRING_TAG_SUPPORT = __webpack_require__(3471); +var classof = __webpack_require__(4696); + +// `Object.prototype.toString` method implementation +// https://tc39.es/ecma262/#sec-object.prototype.tostring +module.exports = TO_STRING_TAG_SUPPORT ? {}.toString : function toString() { + return '[object ' + classof(this) + ']'; +}; + + +/***/ }), + +/***/ 380: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isCallable = __webpack_require__(6447); +var isObject = __webpack_require__(5744); + +// `OrdinaryToPrimitive` abstract operation +// https://tc39.es/ecma262/#sec-ordinarytoprimitive +module.exports = function (input, pref) { + var fn, val; + if (pref === 'string' && isCallable(fn = input.toString) && !isObject(val = fn.call(input))) return val; + if (isCallable(fn = input.valueOf) && !isObject(val = fn.call(input))) return val; + if (pref !== 'string' && isCallable(fn = input.toString) && !isObject(val = fn.call(input))) return val; + throw TypeError("Can't convert object to primitive value"); +}; + + +/***/ }), + +/***/ 7545: +/***/ (function(module) { + +module.exports = {}; + + +/***/ }), + +/***/ 892: +/***/ (function(module) { + +module.exports = function (exec) { + try { + return { error: false, value: exec() }; + } catch (error) { + return { error: true, value: error }; + } +}; + + +/***/ }), + +/***/ 9126: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var anObject = __webpack_require__(1138); +var isObject = __webpack_require__(5744); +var newPromiseCapability = __webpack_require__(9438); + +module.exports = function (C, x) { + anObject(C); + if (isObject(x) && x.constructor === C) return x; + var promiseCapability = newPromiseCapability.f(C); + var resolve = promiseCapability.resolve; + resolve(x); + return promiseCapability.promise; +}; + + +/***/ }), + +/***/ 533: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var redefine = __webpack_require__(9482); + +module.exports = function (target, src, options) { + for (var key in src) { + if (options && options.unsafe && target[key]) target[key] = src[key]; + else redefine(target, key, src[key], options); + } return target; +}; + + +/***/ }), + +/***/ 9482: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var createNonEnumerableProperty = __webpack_require__(8711); + +module.exports = function (target, key, value, options) { + if (options && options.enumerable) target[key] = value; + else createNonEnumerableProperty(target, key, value); +}; + + +/***/ }), + +/***/ 3209: +/***/ (function(module) { + +// `RequireObjectCoercible` abstract operation +// https://tc39.es/ecma262/#sec-requireobjectcoercible +module.exports = function (it) { + if (it == undefined) throw TypeError("Can't call method on " + it); + return it; +}; + + +/***/ }), + +/***/ 7613: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); + +module.exports = function (key, value) { + try { + // eslint-disable-next-line es/no-object-defineproperty -- safe + Object.defineProperty(global, key, { value: value, configurable: true, writable: true }); + } catch (error) { + global[key] = value; + } return value; +}; + + +/***/ }), + +/***/ 3656: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var getBuiltIn = __webpack_require__(150); +var definePropertyModule = __webpack_require__(2760); +var wellKnownSymbol = __webpack_require__(8182); +var DESCRIPTORS = __webpack_require__(69); + +var SPECIES = wellKnownSymbol('species'); + +module.exports = function (CONSTRUCTOR_NAME) { + var Constructor = getBuiltIn(CONSTRUCTOR_NAME); + var defineProperty = definePropertyModule.f; + + if (DESCRIPTORS && Constructor && !Constructor[SPECIES]) { + defineProperty(Constructor, SPECIES, { + configurable: true, + get: function () { return this; } + }); + } +}; + + +/***/ }), + +/***/ 1284: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var TO_STRING_TAG_SUPPORT = __webpack_require__(3471); +var defineProperty = __webpack_require__(2760).f; +var createNonEnumerableProperty = __webpack_require__(8711); +var hasOwn = __webpack_require__(4500); +var toString = __webpack_require__(158); +var wellKnownSymbol = __webpack_require__(8182); + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +module.exports = function (it, TAG, STATIC, SET_METHOD) { + if (it) { + var target = STATIC ? it : it.prototype; + if (!hasOwn(target, TO_STRING_TAG)) { + defineProperty(target, TO_STRING_TAG, { configurable: true, value: TAG }); + } + if (SET_METHOD && !TO_STRING_TAG_SUPPORT) { + createNonEnumerableProperty(target, 'toString', toString); + } + } +}; + + +/***/ }), + +/***/ 9766: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var shared = __webpack_require__(8717); +var uid = __webpack_require__(2759); + +var keys = shared('keys'); + +module.exports = function (key) { + return keys[key] || (keys[key] = uid(key)); +}; + + +/***/ }), + +/***/ 6434: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var setGlobal = __webpack_require__(7613); + +var SHARED = '__core-js_shared__'; +var store = global[SHARED] || setGlobal(SHARED, {}); + +module.exports = store; + + +/***/ }), + +/***/ 8717: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var IS_PURE = __webpack_require__(5546); +var store = __webpack_require__(6434); + +(module.exports = function (key, value) { + return store[key] || (store[key] = value !== undefined ? value : {}); +})('versions', []).push({ + version: '3.18.2', + mode: IS_PURE ? 'pure' : 'global', + copyright: '© 2021 Denis Pushkarev (zloirock.ru)' +}); + + +/***/ }), + +/***/ 4743: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var anObject = __webpack_require__(1138); +var aConstructor = __webpack_require__(1404); +var wellKnownSymbol = __webpack_require__(8182); + +var SPECIES = wellKnownSymbol('species'); + +// `SpeciesConstructor` abstract operation +// https://tc39.es/ecma262/#sec-speciesconstructor +module.exports = function (O, defaultConstructor) { + var C = anObject(O).constructor; + var S; + return C === undefined || (S = anObject(C)[SPECIES]) == undefined ? defaultConstructor : aConstructor(S); +}; + + +/***/ }), + +/***/ 863: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toIntegerOrInfinity = __webpack_require__(1941); +var toString = __webpack_require__(4845); +var requireObjectCoercible = __webpack_require__(3209); + +var createMethod = function (CONVERT_TO_STRING) { + return function ($this, pos) { + var S = toString(requireObjectCoercible($this)); + var position = toIntegerOrInfinity(pos); + var size = S.length; + var first, second; + if (position < 0 || position >= size) return CONVERT_TO_STRING ? '' : undefined; + first = S.charCodeAt(position); + return first < 0xD800 || first > 0xDBFF || position + 1 === size + || (second = S.charCodeAt(position + 1)) < 0xDC00 || second > 0xDFFF + ? CONVERT_TO_STRING ? S.charAt(position) : first + : CONVERT_TO_STRING ? S.slice(position, position + 2) : (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000; + }; +}; + +module.exports = { + // `String.prototype.codePointAt` method + // https://tc39.es/ecma262/#sec-string.prototype.codepointat + codeAt: createMethod(false), + // `String.prototype.at` method + // https://github.com/mathiasbynens/String.prototype.at + charAt: createMethod(true) +}; + + +/***/ }), + +/***/ 7977: +/***/ (function(module) { + +"use strict"; + +// based on https://github.com/bestiejs/punycode.js/blob/master/punycode.js +var maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1 +var base = 36; +var tMin = 1; +var tMax = 26; +var skew = 38; +var damp = 700; +var initialBias = 72; +var initialN = 128; // 0x80 +var delimiter = '-'; // '\x2D' +var regexNonASCII = /[^\0-\u007E]/; // non-ASCII chars +var regexSeparators = /[.\u3002\uFF0E\uFF61]/g; // RFC 3490 separators +var OVERFLOW_ERROR = 'Overflow: input needs wider integers to process'; +var baseMinusTMin = base - tMin; +var floor = Math.floor; +var stringFromCharCode = String.fromCharCode; + +/** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + */ +var ucs2decode = function (string) { + var output = []; + var counter = 0; + var length = string.length; + while (counter < length) { + var value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // It's a high surrogate, and there is a next character. + var extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // Low surrogate. + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // It's an unmatched surrogate; only append this code unit, in case the + // next code unit is the high surrogate of a surrogate pair. + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; +}; + +/** + * Converts a digit/integer into a basic code point. + */ +var digitToBasic = function (digit) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26); +}; + +/** + * Bias adaptation function as per section 3.4 of RFC 3492. + * https://tools.ietf.org/html/rfc3492#section-3.4 + */ +var adapt = function (delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); +}; + +/** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + */ +// eslint-disable-next-line max-statements -- TODO +var encode = function (input) { + var output = []; + + // Convert the input in UCS-2 to an array of Unicode code points. + input = ucs2decode(input); + + // Cache the length. + var inputLength = input.length; + + // Initialize the state. + var n = initialN; + var delta = 0; + var bias = initialBias; + var i, currentValue; + + // Handle the basic code points. + for (i = 0; i < input.length; i++) { + currentValue = input[i]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + var basicLength = output.length; // number of basic code points. + var handledCPCount = basicLength; // number of code points that have been handled; + + // Finish the basic string with a delimiter unless it's empty. + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + // All non-basic code points < n have been handled already. Find the next larger one: + var m = maxInt; + for (i = 0; i < input.length; i++) { + currentValue = input[i]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , but guard against overflow. + var handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + throw RangeError(OVERFLOW_ERROR); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (i = 0; i < input.length; i++) { + currentValue = input[i]; + if (currentValue < n && ++delta > maxInt) { + throw RangeError(OVERFLOW_ERROR); + } + if (currentValue == n) { + // Represent delta as a generalized variable-length integer. + var q = delta; + for (var k = base; /* no condition */; k += base) { + var t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) break; + var qMinusT = q - t; + var baseMinusT = base - t; + output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT))); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + } + return output.join(''); +}; + +module.exports = function (input) { + var encoded = []; + var labels = input.toLowerCase().replace(regexSeparators, '\u002E').split('.'); + var i, label; + for (i = 0; i < labels.length; i++) { + label = labels[i]; + encoded.push(regexNonASCII.test(label) ? 'xn--' + encode(label) : label); + } + return encoded.join('.'); +}; + + +/***/ }), + +/***/ 6815: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var PROPER_FUNCTION_NAME = __webpack_require__(2282).PROPER; +var fails = __webpack_require__(6192); +var whitespaces = __webpack_require__(1450); + +var non = '\u200B\u0085\u180E'; + +// check that a method works with the correct list +// of whitespaces and has a correct name +module.exports = function (METHOD_NAME) { + return fails(function () { + return !!whitespaces[METHOD_NAME]() + || non[METHOD_NAME]() !== non + || (PROPER_FUNCTION_NAME && whitespaces[METHOD_NAME].name !== METHOD_NAME); + }); +}; + + +/***/ }), + +/***/ 4277: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var requireObjectCoercible = __webpack_require__(3209); +var toString = __webpack_require__(4845); +var whitespaces = __webpack_require__(1450); + +var whitespace = '[' + whitespaces + ']'; +var ltrim = RegExp('^' + whitespace + whitespace + '*'); +var rtrim = RegExp(whitespace + whitespace + '*$'); + +// `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation +var createMethod = function (TYPE) { + return function ($this) { + var string = toString(requireObjectCoercible($this)); + if (TYPE & 1) string = string.replace(ltrim, ''); + if (TYPE & 2) string = string.replace(rtrim, ''); + return string; + }; +}; + +module.exports = { + // `String.prototype.{ trimLeft, trimStart }` methods + // https://tc39.es/ecma262/#sec-string.prototype.trimstart + start: createMethod(1), + // `String.prototype.{ trimRight, trimEnd }` methods + // https://tc39.es/ecma262/#sec-string.prototype.trimend + end: createMethod(2), + // `String.prototype.trim` method + // https://tc39.es/ecma262/#sec-string.prototype.trim + trim: createMethod(3) +}; + + +/***/ }), + +/***/ 7160: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var isCallable = __webpack_require__(6447); +var fails = __webpack_require__(6192); +var bind = __webpack_require__(8043); +var html = __webpack_require__(7403); +var createElement = __webpack_require__(7449); +var IS_IOS = __webpack_require__(9536); +var IS_NODE = __webpack_require__(224); + +var set = global.setImmediate; +var clear = global.clearImmediate; +var process = global.process; +var MessageChannel = global.MessageChannel; +var Dispatch = global.Dispatch; +var counter = 0; +var queue = {}; +var ONREADYSTATECHANGE = 'onreadystatechange'; +var location, defer, channel, port; + +try { + // Deno throws a ReferenceError on `location` access without `--location` flag + location = global.location; +} catch (error) { /* empty */ } + +var run = function (id) { + // eslint-disable-next-line no-prototype-builtins -- safe + if (queue.hasOwnProperty(id)) { + var fn = queue[id]; + delete queue[id]; + fn(); + } +}; + +var runner = function (id) { + return function () { + run(id); + }; +}; + +var listener = function (event) { + run(event.data); +}; + +var post = function (id) { + // old engines have not location.origin + global.postMessage(String(id), location.protocol + '//' + location.host); +}; + +// Node.js 0.9+ & IE10+ has setImmediate, otherwise: +if (!set || !clear) { + set = function setImmediate(fn) { + var args = []; + var argumentsLength = arguments.length; + var i = 1; + while (argumentsLength > i) args.push(arguments[i++]); + queue[++counter] = function () { + // eslint-disable-next-line no-new-func -- spec requirement + (isCallable(fn) ? fn : Function(fn)).apply(undefined, args); + }; + defer(counter); + return counter; + }; + clear = function clearImmediate(id) { + delete queue[id]; + }; + // Node.js 0.8- + if (IS_NODE) { + defer = function (id) { + process.nextTick(runner(id)); + }; + // Sphere (JS game engine) Dispatch API + } else if (Dispatch && Dispatch.now) { + defer = function (id) { + Dispatch.now(runner(id)); + }; + // Browsers with MessageChannel, includes WebWorkers + // except iOS - https://github.com/zloirock/core-js/issues/624 + } else if (MessageChannel && !IS_IOS) { + channel = new MessageChannel(); + port = channel.port2; + channel.port1.onmessage = listener; + defer = bind(port.postMessage, port, 1); + // Browsers with postMessage, skip WebWorkers + // IE8 has postMessage, but it's sync & typeof its postMessage is 'object' + } else if ( + global.addEventListener && + isCallable(global.postMessage) && + !global.importScripts && + location && location.protocol !== 'file:' && + !fails(post) + ) { + defer = post; + global.addEventListener('message', listener, false); + // IE8- + } else if (ONREADYSTATECHANGE in createElement('script')) { + defer = function (id) { + html.appendChild(createElement('script'))[ONREADYSTATECHANGE] = function () { + html.removeChild(this); + run(id); + }; + }; + // Rest old browsers + } else { + defer = function (id) { + setTimeout(runner(id), 0); + }; + } +} + +module.exports = { + set: set, + clear: clear +}; + + +/***/ }), + +/***/ 7739: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toIntegerOrInfinity = __webpack_require__(1941); + +var max = Math.max; +var min = Math.min; + +// Helper for a popular repeating case of the spec: +// Let integer be ? ToInteger(index). +// If integer < 0, let result be max((length + integer), 0); else let result be min(integer, length). +module.exports = function (index, length) { + var integer = toIntegerOrInfinity(index); + return integer < 0 ? max(integer + length, 0) : min(integer, length); +}; + + +/***/ }), + +/***/ 101: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +// toObject with fallback for non-array-like ES3 strings +var IndexedObject = __webpack_require__(2202); +var requireObjectCoercible = __webpack_require__(3209); + +module.exports = function (it) { + return IndexedObject(requireObjectCoercible(it)); +}; + + +/***/ }), + +/***/ 1941: +/***/ (function(module) { + +var ceil = Math.ceil; +var floor = Math.floor; + +// `ToIntegerOrInfinity` abstract operation +// https://tc39.es/ecma262/#sec-tointegerorinfinity +module.exports = function (argument) { + var number = +argument; + // eslint-disable-next-line no-self-compare -- safe + return number !== number || number === 0 ? 0 : (number > 0 ? floor : ceil)(number); +}; + + +/***/ }), + +/***/ 8445: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toIntegerOrInfinity = __webpack_require__(1941); + +var min = Math.min; + +// `ToLength` abstract operation +// https://tc39.es/ecma262/#sec-tolength +module.exports = function (argument) { + return argument > 0 ? min(toIntegerOrInfinity(argument), 0x1FFFFFFFFFFFFF) : 0; // 2 ** 53 - 1 == 9007199254740991 +}; + + +/***/ }), + +/***/ 1795: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var requireObjectCoercible = __webpack_require__(3209); + +// `ToObject` abstract operation +// https://tc39.es/ecma262/#sec-toobject +module.exports = function (argument) { + return Object(requireObjectCoercible(argument)); +}; + + +/***/ }), + +/***/ 7888: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var isObject = __webpack_require__(5744); +var isSymbol = __webpack_require__(3236); +var getMethod = __webpack_require__(5037); +var ordinaryToPrimitive = __webpack_require__(380); +var wellKnownSymbol = __webpack_require__(8182); + +var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); + +// `ToPrimitive` abstract operation +// https://tc39.es/ecma262/#sec-toprimitive +module.exports = function (input, pref) { + if (!isObject(input) || isSymbol(input)) return input; + var exoticToPrim = getMethod(input, TO_PRIMITIVE); + var result; + if (exoticToPrim) { + if (pref === undefined) pref = 'default'; + result = exoticToPrim.call(input, pref); + if (!isObject(result) || isSymbol(result)) return result; + throw TypeError("Can't convert object to primitive value"); + } + if (pref === undefined) pref = 'number'; + return ordinaryToPrimitive(input, pref); +}; + + +/***/ }), + +/***/ 77: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var toPrimitive = __webpack_require__(7888); +var isSymbol = __webpack_require__(3236); + +// `ToPropertyKey` abstract operation +// https://tc39.es/ecma262/#sec-topropertykey +module.exports = function (argument) { + var key = toPrimitive(argument, 'string'); + return isSymbol(key) ? key : String(key); +}; + + +/***/ }), + +/***/ 3471: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var wellKnownSymbol = __webpack_require__(8182); + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); +var test = {}; + +test[TO_STRING_TAG] = 'z'; + +module.exports = String(test) === '[object z]'; + + +/***/ }), + +/***/ 4845: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var classof = __webpack_require__(4696); + +module.exports = function (argument) { + if (classof(argument) === 'Symbol') throw TypeError('Cannot convert a Symbol value to a string'); + return String(argument); +}; + + +/***/ }), + +/***/ 9288: +/***/ (function(module) { + +module.exports = function (argument) { + try { + return String(argument); + } catch (error) { + return 'Object'; + } +}; + + +/***/ }), + +/***/ 2759: +/***/ (function(module) { + +var id = 0; +var postfix = Math.random(); + +module.exports = function (key) { + return 'Symbol(' + String(key === undefined ? '' : key) + ')_' + (++id + postfix).toString(36); +}; + + +/***/ }), + +/***/ 615: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +/* eslint-disable es/no-symbol -- required for testing */ +var NATIVE_SYMBOL = __webpack_require__(3045); + +module.exports = NATIVE_SYMBOL + && !Symbol.sham + && typeof Symbol.iterator == 'symbol'; + + +/***/ }), + +/***/ 9207: +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + +var wellKnownSymbol = __webpack_require__(8182); + +exports.f = wellKnownSymbol; + + +/***/ }), + +/***/ 8182: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var shared = __webpack_require__(8717); +var hasOwn = __webpack_require__(4500); +var uid = __webpack_require__(2759); +var NATIVE_SYMBOL = __webpack_require__(3045); +var USE_SYMBOL_AS_UID = __webpack_require__(615); + +var WellKnownSymbolsStore = shared('wks'); +var Symbol = global.Symbol; +var createWellKnownSymbol = USE_SYMBOL_AS_UID ? Symbol : Symbol && Symbol.withoutSetter || uid; + +module.exports = function (name) { + if (!hasOwn(WellKnownSymbolsStore, name) || !(NATIVE_SYMBOL || typeof WellKnownSymbolsStore[name] == 'string')) { + if (NATIVE_SYMBOL && hasOwn(Symbol, name)) { + WellKnownSymbolsStore[name] = Symbol[name]; + } else { + WellKnownSymbolsStore[name] = createWellKnownSymbol('Symbol.' + name); + } + } return WellKnownSymbolsStore[name]; +}; + + +/***/ }), + +/***/ 1450: +/***/ (function(module) { + +// a string of all valid unicode whitespaces +module.exports = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u2000\u2001\u2002' + + '\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028\u2029\uFEFF'; + + +/***/ }), + +/***/ 4242: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var getPrototypeOf = __webpack_require__(9341); +var setPrototypeOf = __webpack_require__(4469); +var create = __webpack_require__(2853); +var createNonEnumerableProperty = __webpack_require__(8711); +var createPropertyDescriptor = __webpack_require__(774); +var installErrorCause = __webpack_require__(273); +var iterate = __webpack_require__(3442); +var toString = __webpack_require__(4845); + +var $AggregateError = function AggregateError(errors, message /* , options */) { + var that = this; + var options = arguments.length > 2 ? arguments[2] : undefined; + if (!(that instanceof $AggregateError)) return new $AggregateError(errors, message, options); + if (setPrototypeOf) { + // eslint-disable-next-line unicorn/error-message -- expected + that = setPrototypeOf(new Error(undefined), getPrototypeOf(that)); + } + if (message !== undefined) createNonEnumerableProperty(that, 'message', toString(message)); + installErrorCause(that, options); + var errorsArray = []; + iterate(errors, errorsArray.push, { that: errorsArray }); + createNonEnumerableProperty(that, 'errors', errorsArray); + return that; +}; + +$AggregateError.prototype = create(Error.prototype, { + constructor: createPropertyDescriptor(5, $AggregateError), + message: createPropertyDescriptor(5, ''), + name: createPropertyDescriptor(5, 'AggregateError') +}); + +// `AggregateError` constructor +// https://tc39.es/ecma262/#sec-aggregate-error-constructor +$({ global: true }, { + AggregateError: $AggregateError +}); + + +/***/ }), + +/***/ 9106: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var fails = __webpack_require__(6192); +var isArray = __webpack_require__(4770); +var isObject = __webpack_require__(5744); +var toObject = __webpack_require__(1795); +var lengthOfArrayLike = __webpack_require__(4104); +var createProperty = __webpack_require__(9361); +var arraySpeciesCreate = __webpack_require__(1321); +var arrayMethodHasSpeciesSupport = __webpack_require__(242); +var wellKnownSymbol = __webpack_require__(8182); +var V8_VERSION = __webpack_require__(4218); + +var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable'); +var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF; +var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded'; + +// We can't use this feature detection in V8 since it causes +// deoptimization and serious performance degradation +// https://github.com/zloirock/core-js/issues/679 +var IS_CONCAT_SPREADABLE_SUPPORT = V8_VERSION >= 51 || !fails(function () { + var array = []; + array[IS_CONCAT_SPREADABLE] = false; + return array.concat()[0] !== array; +}); + +var SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('concat'); + +var isConcatSpreadable = function (O) { + if (!isObject(O)) return false; + var spreadable = O[IS_CONCAT_SPREADABLE]; + return spreadable !== undefined ? !!spreadable : isArray(O); +}; + +var FORCED = !IS_CONCAT_SPREADABLE_SUPPORT || !SPECIES_SUPPORT; + +// `Array.prototype.concat` method +// https://tc39.es/ecma262/#sec-array.prototype.concat +// with adding support of @@isConcatSpreadable and @@species +$({ target: 'Array', proto: true, forced: FORCED }, { + // eslint-disable-next-line no-unused-vars -- required for `.length` + concat: function concat(arg) { + var O = toObject(this); + var A = arraySpeciesCreate(O, 0); + var n = 0; + var i, k, length, len, E; + for (i = -1, length = arguments.length; i < length; i++) { + E = i === -1 ? O : arguments[i]; + if (isConcatSpreadable(E)) { + len = lengthOfArrayLike(E); + if (n + len > MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED); + for (k = 0; k < len; k++, n++) if (k in E) createProperty(A, n, E[k]); + } else { + if (n >= MAX_SAFE_INTEGER) throw TypeError(MAXIMUM_ALLOWED_INDEX_EXCEEDED); + createProperty(A, n++, E); + } + } + A.length = n; + return A; + } +}); + + +/***/ }), + +/***/ 1710: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var fill = __webpack_require__(2724); +var addToUnscopables = __webpack_require__(7423); + +// `Array.prototype.fill` method +// https://tc39.es/ecma262/#sec-array.prototype.fill +$({ target: 'Array', proto: true }, { + fill: fill +}); + +// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables +addToUnscopables('fill'); + + +/***/ }), + +/***/ 3436: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var $filter = __webpack_require__(454).filter; +var arrayMethodHasSpeciesSupport = __webpack_require__(242); + +var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('filter'); + +// `Array.prototype.filter` method +// https://tc39.es/ecma262/#sec-array.prototype.filter +// with adding support of @@species +$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { + filter: function filter(callbackfn /* , thisArg */) { + return $filter(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); + } +}); + + +/***/ }), + +/***/ 9823: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var forEach = __webpack_require__(7397); + +// `Array.prototype.forEach` method +// https://tc39.es/ecma262/#sec-array.prototype.foreach +// eslint-disable-next-line es/no-array-prototype-foreach -- safe +$({ target: 'Array', proto: true, forced: [].forEach != forEach }, { + forEach: forEach +}); + + +/***/ }), + +/***/ 9173: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var from = __webpack_require__(841); +var checkCorrectnessOfIteration = __webpack_require__(9770); + +var INCORRECT_ITERATION = !checkCorrectnessOfIteration(function (iterable) { + // eslint-disable-next-line es/no-array-from -- required for testing + Array.from(iterable); +}); + +// `Array.from` method +// https://tc39.es/ecma262/#sec-array.from +$({ target: 'Array', stat: true, forced: INCORRECT_ITERATION }, { + from: from +}); + + +/***/ }), + +/***/ 2276: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +/* eslint-disable es/no-array-prototype-indexof -- required for testing */ +var $ = __webpack_require__(3085); +var $indexOf = __webpack_require__(8180).indexOf; +var arrayMethodIsStrict = __webpack_require__(424); + +var nativeIndexOf = [].indexOf; + +var NEGATIVE_ZERO = !!nativeIndexOf && 1 / [1].indexOf(1, -0) < 0; +var STRICT_METHOD = arrayMethodIsStrict('indexOf'); + +// `Array.prototype.indexOf` method +// https://tc39.es/ecma262/#sec-array.prototype.indexof +$({ target: 'Array', proto: true, forced: NEGATIVE_ZERO || !STRICT_METHOD }, { + indexOf: function indexOf(searchElement /* , fromIndex = 0 */) { + return NEGATIVE_ZERO + // convert -0 to +0 + ? nativeIndexOf.apply(this, arguments) || 0 + : $indexOf(this, searchElement, arguments.length > 1 ? arguments[1] : undefined); + } +}); + + +/***/ }), + +/***/ 8118: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var isArray = __webpack_require__(4770); + +// `Array.isArray` method +// https://tc39.es/ecma262/#sec-array.isarray +$({ target: 'Array', stat: true }, { + isArray: isArray +}); + + +/***/ }), + +/***/ 8939: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var toIndexedObject = __webpack_require__(101); +var addToUnscopables = __webpack_require__(7423); +var Iterators = __webpack_require__(7771); +var InternalStateModule = __webpack_require__(3326); +var defineIterator = __webpack_require__(7218); + +var ARRAY_ITERATOR = 'Array Iterator'; +var setInternalState = InternalStateModule.set; +var getInternalState = InternalStateModule.getterFor(ARRAY_ITERATOR); + +// `Array.prototype.entries` method +// https://tc39.es/ecma262/#sec-array.prototype.entries +// `Array.prototype.keys` method +// https://tc39.es/ecma262/#sec-array.prototype.keys +// `Array.prototype.values` method +// https://tc39.es/ecma262/#sec-array.prototype.values +// `Array.prototype[@@iterator]` method +// https://tc39.es/ecma262/#sec-array.prototype-@@iterator +// `CreateArrayIterator` internal method +// https://tc39.es/ecma262/#sec-createarrayiterator +module.exports = defineIterator(Array, 'Array', function (iterated, kind) { + setInternalState(this, { + type: ARRAY_ITERATOR, + target: toIndexedObject(iterated), // target + index: 0, // next index + kind: kind // kind + }); +// `%ArrayIteratorPrototype%.next` method +// https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next +}, function () { + var state = getInternalState(this); + var target = state.target; + var kind = state.kind; + var index = state.index++; + if (!target || index >= target.length) { + state.target = undefined; + return { value: undefined, done: true }; + } + if (kind == 'keys') return { value: index, done: false }; + if (kind == 'values') return { value: target[index], done: false }; + return { value: [index, target[index]], done: false }; +}, 'values'); + +// argumentsList[@@iterator] is %ArrayProto_values% +// https://tc39.es/ecma262/#sec-createunmappedargumentsobject +// https://tc39.es/ecma262/#sec-createmappedargumentsobject +Iterators.Arguments = Iterators.Array; + +// https://tc39.es/ecma262/#sec-array.prototype-@@unscopables +addToUnscopables('keys'); +addToUnscopables('values'); +addToUnscopables('entries'); + + +/***/ }), + +/***/ 3838: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var $map = __webpack_require__(454).map; +var arrayMethodHasSpeciesSupport = __webpack_require__(242); + +var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('map'); + +// `Array.prototype.map` method +// https://tc39.es/ecma262/#sec-array.prototype.map +// with adding support of @@species +$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { + map: function map(callbackfn /* , thisArg */) { + return $map(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined); + } +}); + + +/***/ }), + +/***/ 5818: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var isArray = __webpack_require__(4770); +var isConstructor = __webpack_require__(2091); +var isObject = __webpack_require__(5744); +var toAbsoluteIndex = __webpack_require__(7739); +var lengthOfArrayLike = __webpack_require__(4104); +var toIndexedObject = __webpack_require__(101); +var createProperty = __webpack_require__(9361); +var wellKnownSymbol = __webpack_require__(8182); +var arrayMethodHasSpeciesSupport = __webpack_require__(242); + +var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('slice'); + +var SPECIES = wellKnownSymbol('species'); +var nativeSlice = [].slice; +var max = Math.max; + +// `Array.prototype.slice` method +// https://tc39.es/ecma262/#sec-array.prototype.slice +// fallback for not array-like ES3 strings and DOM objects +$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { + slice: function slice(start, end) { + var O = toIndexedObject(this); + var length = lengthOfArrayLike(O); + var k = toAbsoluteIndex(start, length); + var fin = toAbsoluteIndex(end === undefined ? length : end, length); + // inline `ArraySpeciesCreate` for usage native `Array#slice` where it's possible + var Constructor, result, n; + if (isArray(O)) { + Constructor = O.constructor; + // cross-realm fallback + if (isConstructor(Constructor) && (Constructor === Array || isArray(Constructor.prototype))) { + Constructor = undefined; + } else if (isObject(Constructor)) { + Constructor = Constructor[SPECIES]; + if (Constructor === null) Constructor = undefined; + } + if (Constructor === Array || Constructor === undefined) { + return nativeSlice.call(O, k, fin); + } + } + result = new (Constructor === undefined ? Array : Constructor)(max(fin - k, 0)); + for (n = 0; k < fin; k++, n++) if (k in O) createProperty(result, n, O[k]); + result.length = n; + return result; + } +}); + + +/***/ }), + +/***/ 2178: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var toAbsoluteIndex = __webpack_require__(7739); +var toIntegerOrInfinity = __webpack_require__(1941); +var lengthOfArrayLike = __webpack_require__(4104); +var toObject = __webpack_require__(1795); +var arraySpeciesCreate = __webpack_require__(1321); +var createProperty = __webpack_require__(9361); +var arrayMethodHasSpeciesSupport = __webpack_require__(242); + +var HAS_SPECIES_SUPPORT = arrayMethodHasSpeciesSupport('splice'); + +var max = Math.max; +var min = Math.min; +var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF; +var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded'; + +// `Array.prototype.splice` method +// https://tc39.es/ecma262/#sec-array.prototype.splice +// with adding support of @@species +$({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT }, { + splice: function splice(start, deleteCount /* , ...items */) { + var O = toObject(this); + var len = lengthOfArrayLike(O); + var actualStart = toAbsoluteIndex(start, len); + var argumentsLength = arguments.length; + var insertCount, actualDeleteCount, A, k, from, to; + if (argumentsLength === 0) { + insertCount = actualDeleteCount = 0; + } else if (argumentsLength === 1) { + insertCount = 0; + actualDeleteCount = len - actualStart; + } else { + insertCount = argumentsLength - 2; + actualDeleteCount = min(max(toIntegerOrInfinity(deleteCount), 0), len - actualStart); + } + if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER) { + throw TypeError(MAXIMUM_ALLOWED_LENGTH_EXCEEDED); + } + A = arraySpeciesCreate(O, actualDeleteCount); + for (k = 0; k < actualDeleteCount; k++) { + from = actualStart + k; + if (from in O) createProperty(A, k, O[from]); + } + A.length = actualDeleteCount; + if (insertCount < actualDeleteCount) { + for (k = actualStart; k < len - actualDeleteCount; k++) { + from = k + actualDeleteCount; + to = k + insertCount; + if (from in O) O[to] = O[from]; + else delete O[to]; + } + for (k = len; k > len - actualDeleteCount + insertCount; k--) delete O[k - 1]; + } else if (insertCount > actualDeleteCount) { + for (k = len - actualDeleteCount; k > actualStart; k--) { + from = k + actualDeleteCount - 1; + to = k + insertCount - 1; + if (from in O) O[to] = O[from]; + else delete O[to]; + } + } + for (k = 0; k < insertCount; k++) { + O[k + actualStart] = arguments[k + 2]; + } + O.length = len - actualDeleteCount + insertCount; + return A; + } +}); + + +/***/ }), + +/***/ 665: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var bind = __webpack_require__(6782); + +// `Function.prototype.bind` method +// https://tc39.es/ecma262/#sec-function.prototype.bind +$({ target: 'Function', proto: true }, { + bind: bind +}); + + +/***/ }), + +/***/ 8671: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var global = __webpack_require__(8576); +var setToStringTag = __webpack_require__(1284); + +// JSON[@@toStringTag] property +// https://tc39.es/ecma262/#sec-json-@@tostringtag +setToStringTag(global.JSON, 'JSON', true); + + +/***/ }), + +/***/ 8556: +/***/ (function() { + +// empty + + +/***/ }), + +/***/ 2666: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var parseInt = __webpack_require__(2558); + +// `Number.parseInt` method +// https://tc39.es/ecma262/#sec-number.parseint +// eslint-disable-next-line es/no-number-parseint -- required for testing +$({ target: 'Number', stat: true, forced: Number.parseInt != parseInt }, { + parseInt: parseInt +}); + + +/***/ }), + +/***/ 3113: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var DESCRIPTORS = __webpack_require__(69); +var create = __webpack_require__(2853); + +// `Object.create` method +// https://tc39.es/ecma262/#sec-object.create +$({ target: 'Object', stat: true, sham: !DESCRIPTORS }, { + create: create +}); + + +/***/ }), + +/***/ 297: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var DESCRIPTORS = __webpack_require__(69); +var objectDefinePropertyModile = __webpack_require__(2760); + +// `Object.defineProperty` method +// https://tc39.es/ecma262/#sec-object.defineproperty +$({ target: 'Object', stat: true, forced: !DESCRIPTORS, sham: !DESCRIPTORS }, { + defineProperty: objectDefinePropertyModile.f +}); + + +/***/ }), + +/***/ 9234: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var fails = __webpack_require__(6192); +var toObject = __webpack_require__(1795); +var nativeGetPrototypeOf = __webpack_require__(9341); +var CORRECT_PROTOTYPE_GETTER = __webpack_require__(4635); + +var FAILS_ON_PRIMITIVES = fails(function () { nativeGetPrototypeOf(1); }); + +// `Object.getPrototypeOf` method +// https://tc39.es/ecma262/#sec-object.getprototypeof +$({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES, sham: !CORRECT_PROTOTYPE_GETTER }, { + getPrototypeOf: function getPrototypeOf(it) { + return nativeGetPrototypeOf(toObject(it)); + } +}); + + + +/***/ }), + +/***/ 2647: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var toObject = __webpack_require__(1795); +var nativeKeys = __webpack_require__(7653); +var fails = __webpack_require__(6192); + +var FAILS_ON_PRIMITIVES = fails(function () { nativeKeys(1); }); + +// `Object.keys` method +// https://tc39.es/ecma262/#sec-object.keys +$({ target: 'Object', stat: true, forced: FAILS_ON_PRIMITIVES }, { + keys: function keys(it) { + return nativeKeys(toObject(it)); + } +}); + + +/***/ }), + +/***/ 3222: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var setPrototypeOf = __webpack_require__(4469); + +// `Object.setPrototypeOf` method +// https://tc39.es/ecma262/#sec-object.setprototypeof +$({ target: 'Object', stat: true }, { + setPrototypeOf: setPrototypeOf +}); + + +/***/ }), + +/***/ 6663: +/***/ (function() { + +// empty + + +/***/ }), + +/***/ 4859: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var $parseFloat = __webpack_require__(15); + +// `parseFloat` method +// https://tc39.es/ecma262/#sec-parsefloat-string +$({ global: true, forced: parseFloat != $parseFloat }, { + parseFloat: $parseFloat +}); + + +/***/ }), + +/***/ 5706: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var $parseInt = __webpack_require__(2558); + +// `parseInt` method +// https://tc39.es/ecma262/#sec-parseint-string-radix +$({ global: true, forced: parseInt != $parseInt }, { + parseInt: $parseInt +}); + + +/***/ }), + +/***/ 7884: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var aCallable = __webpack_require__(6235); +var newPromiseCapabilityModule = __webpack_require__(9438); +var perform = __webpack_require__(892); +var iterate = __webpack_require__(3442); + +// `Promise.allSettled` method +// https://tc39.es/ecma262/#sec-promise.allsettled +$({ target: 'Promise', stat: true }, { + allSettled: function allSettled(iterable) { + var C = this; + var capability = newPromiseCapabilityModule.f(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var promiseResolve = aCallable(C.resolve); + var values = []; + var counter = 0; + var remaining = 1; + iterate(iterable, function (promise) { + var index = counter++; + var alreadyCalled = false; + values.push(undefined); + remaining++; + promiseResolve.call(C, promise).then(function (value) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = { status: 'fulfilled', value: value }; + --remaining || resolve(values); + }, function (error) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = { status: 'rejected', reason: error }; + --remaining || resolve(values); + }); + }); + --remaining || resolve(values); + }); + if (result.error) reject(result.value); + return capability.promise; + } +}); + + +/***/ }), + +/***/ 8885: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var aCallable = __webpack_require__(6235); +var getBuiltIn = __webpack_require__(150); +var newPromiseCapabilityModule = __webpack_require__(9438); +var perform = __webpack_require__(892); +var iterate = __webpack_require__(3442); + +var PROMISE_ANY_ERROR = 'No one promise resolved'; + +// `Promise.any` method +// https://tc39.es/ecma262/#sec-promise.any +$({ target: 'Promise', stat: true }, { + any: function any(iterable) { + var C = this; + var capability = newPromiseCapabilityModule.f(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var promiseResolve = aCallable(C.resolve); + var errors = []; + var counter = 0; + var remaining = 1; + var alreadyResolved = false; + iterate(iterable, function (promise) { + var index = counter++; + var alreadyRejected = false; + errors.push(undefined); + remaining++; + promiseResolve.call(C, promise).then(function (value) { + if (alreadyRejected || alreadyResolved) return; + alreadyResolved = true; + resolve(value); + }, function (error) { + if (alreadyRejected || alreadyResolved) return; + alreadyRejected = true; + errors[index] = error; + --remaining || reject(new (getBuiltIn('AggregateError'))(errors, PROMISE_ANY_ERROR)); + }); + }); + --remaining || reject(new (getBuiltIn('AggregateError'))(errors, PROMISE_ANY_ERROR)); + }); + if (result.error) reject(result.value); + return capability.promise; + } +}); + + +/***/ }), + +/***/ 1868: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var IS_PURE = __webpack_require__(5546); +var NativePromise = __webpack_require__(4471); +var fails = __webpack_require__(6192); +var getBuiltIn = __webpack_require__(150); +var isCallable = __webpack_require__(6447); +var speciesConstructor = __webpack_require__(4743); +var promiseResolve = __webpack_require__(9126); +var redefine = __webpack_require__(9482); + +// Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829 +var NON_GENERIC = !!NativePromise && fails(function () { + NativePromise.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ }); +}); + +// `Promise.prototype.finally` method +// https://tc39.es/ecma262/#sec-promise.prototype.finally +$({ target: 'Promise', proto: true, real: true, forced: NON_GENERIC }, { + 'finally': function (onFinally) { + var C = speciesConstructor(this, getBuiltIn('Promise')); + var isFunction = isCallable(onFinally); + return this.then( + isFunction ? function (x) { + return promiseResolve(C, onFinally()).then(function () { return x; }); + } : onFinally, + isFunction ? function (e) { + return promiseResolve(C, onFinally()).then(function () { throw e; }); + } : onFinally + ); + } +}); + +// makes sure that native promise-based APIs `Promise#finally` properly works with patched `Promise#then` +if (!IS_PURE && isCallable(NativePromise)) { + var method = getBuiltIn('Promise').prototype['finally']; + if (NativePromise.prototype['finally'] !== method) { + redefine(NativePromise.prototype, 'finally', method, { unsafe: true }); + } +} + + +/***/ }), + +/***/ 9021: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var IS_PURE = __webpack_require__(5546); +var global = __webpack_require__(8576); +var getBuiltIn = __webpack_require__(150); +var NativePromise = __webpack_require__(4471); +var redefine = __webpack_require__(9482); +var redefineAll = __webpack_require__(533); +var setPrototypeOf = __webpack_require__(4469); +var setToStringTag = __webpack_require__(1284); +var setSpecies = __webpack_require__(3656); +var aCallable = __webpack_require__(6235); +var isCallable = __webpack_require__(6447); +var isObject = __webpack_require__(5744); +var anInstance = __webpack_require__(6961); +var inspectSource = __webpack_require__(9516); +var iterate = __webpack_require__(3442); +var checkCorrectnessOfIteration = __webpack_require__(9770); +var speciesConstructor = __webpack_require__(4743); +var task = __webpack_require__(7160).set; +var microtask = __webpack_require__(2950); +var promiseResolve = __webpack_require__(9126); +var hostReportErrors = __webpack_require__(3681); +var newPromiseCapabilityModule = __webpack_require__(9438); +var perform = __webpack_require__(892); +var InternalStateModule = __webpack_require__(3326); +var isForced = __webpack_require__(9245); +var wellKnownSymbol = __webpack_require__(8182); +var IS_BROWSER = __webpack_require__(2957); +var IS_NODE = __webpack_require__(224); +var V8_VERSION = __webpack_require__(4218); + +var SPECIES = wellKnownSymbol('species'); +var PROMISE = 'Promise'; +var getInternalState = InternalStateModule.get; +var setInternalState = InternalStateModule.set; +var getInternalPromiseState = InternalStateModule.getterFor(PROMISE); +var NativePromisePrototype = NativePromise && NativePromise.prototype; +var PromiseConstructor = NativePromise; +var PromiseConstructorPrototype = NativePromisePrototype; +var TypeError = global.TypeError; +var document = global.document; +var process = global.process; +var newPromiseCapability = newPromiseCapabilityModule.f; +var newGenericPromiseCapability = newPromiseCapability; +var DISPATCH_EVENT = !!(document && document.createEvent && global.dispatchEvent); +var NATIVE_REJECTION_EVENT = isCallable(global.PromiseRejectionEvent); +var UNHANDLED_REJECTION = 'unhandledrejection'; +var REJECTION_HANDLED = 'rejectionhandled'; +var PENDING = 0; +var FULFILLED = 1; +var REJECTED = 2; +var HANDLED = 1; +var UNHANDLED = 2; +var SUBCLASSING = false; +var Internal, OwnPromiseCapability, PromiseWrapper, nativeThen; + +var FORCED = isForced(PROMISE, function () { + var PROMISE_CONSTRUCTOR_SOURCE = inspectSource(PromiseConstructor); + var GLOBAL_CORE_JS_PROMISE = PROMISE_CONSTRUCTOR_SOURCE !== String(PromiseConstructor); + // V8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables + // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 + // We can't detect it synchronously, so just check versions + if (!GLOBAL_CORE_JS_PROMISE && V8_VERSION === 66) return true; + // We need Promise#finally in the pure version for preventing prototype pollution + if (IS_PURE && !PromiseConstructorPrototype['finally']) return true; + // We can't use @@species feature detection in V8 since it causes + // deoptimization and performance degradation + // https://github.com/zloirock/core-js/issues/679 + if (V8_VERSION >= 51 && /native code/.test(PROMISE_CONSTRUCTOR_SOURCE)) return false; + // Detect correctness of subclassing with @@species support + var promise = new PromiseConstructor(function (resolve) { resolve(1); }); + var FakePromise = function (exec) { + exec(function () { /* empty */ }, function () { /* empty */ }); + }; + var constructor = promise.constructor = {}; + constructor[SPECIES] = FakePromise; + SUBCLASSING = promise.then(function () { /* empty */ }) instanceof FakePromise; + if (!SUBCLASSING) return true; + // Unhandled rejections tracking support, NodeJS Promise without it fails @@species test + return !GLOBAL_CORE_JS_PROMISE && IS_BROWSER && !NATIVE_REJECTION_EVENT; +}); + +var INCORRECT_ITERATION = FORCED || !checkCorrectnessOfIteration(function (iterable) { + PromiseConstructor.all(iterable)['catch'](function () { /* empty */ }); +}); + +// helpers +var isThenable = function (it) { + var then; + return isObject(it) && isCallable(then = it.then) ? then : false; +}; + +var notify = function (state, isReject) { + if (state.notified) return; + state.notified = true; + var chain = state.reactions; + microtask(function () { + var value = state.value; + var ok = state.state == FULFILLED; + var index = 0; + // variable length - can't use forEach + while (chain.length > index) { + var reaction = chain[index++]; + var handler = ok ? reaction.ok : reaction.fail; + var resolve = reaction.resolve; + var reject = reaction.reject; + var domain = reaction.domain; + var result, then, exited; + try { + if (handler) { + if (!ok) { + if (state.rejection === UNHANDLED) onHandleUnhandled(state); + state.rejection = HANDLED; + } + if (handler === true) result = value; + else { + if (domain) domain.enter(); + result = handler(value); // can throw + if (domain) { + domain.exit(); + exited = true; + } + } + if (result === reaction.promise) { + reject(TypeError('Promise-chain cycle')); + } else if (then = isThenable(result)) { + then.call(result, resolve, reject); + } else resolve(result); + } else reject(value); + } catch (error) { + if (domain && !exited) domain.exit(); + reject(error); + } + } + state.reactions = []; + state.notified = false; + if (isReject && !state.rejection) onUnhandled(state); + }); +}; + +var dispatchEvent = function (name, promise, reason) { + var event, handler; + if (DISPATCH_EVENT) { + event = document.createEvent('Event'); + event.promise = promise; + event.reason = reason; + event.initEvent(name, false, true); + global.dispatchEvent(event); + } else event = { promise: promise, reason: reason }; + if (!NATIVE_REJECTION_EVENT && (handler = global['on' + name])) handler(event); + else if (name === UNHANDLED_REJECTION) hostReportErrors('Unhandled promise rejection', reason); +}; + +var onUnhandled = function (state) { + task.call(global, function () { + var promise = state.facade; + var value = state.value; + var IS_UNHANDLED = isUnhandled(state); + var result; + if (IS_UNHANDLED) { + result = perform(function () { + if (IS_NODE) { + process.emit('unhandledRejection', value, promise); + } else dispatchEvent(UNHANDLED_REJECTION, promise, value); + }); + // Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should + state.rejection = IS_NODE || isUnhandled(state) ? UNHANDLED : HANDLED; + if (result.error) throw result.value; + } + }); +}; + +var isUnhandled = function (state) { + return state.rejection !== HANDLED && !state.parent; +}; + +var onHandleUnhandled = function (state) { + task.call(global, function () { + var promise = state.facade; + if (IS_NODE) { + process.emit('rejectionHandled', promise); + } else dispatchEvent(REJECTION_HANDLED, promise, state.value); + }); +}; + +var bind = function (fn, state, unwrap) { + return function (value) { + fn(state, value, unwrap); + }; +}; + +var internalReject = function (state, value, unwrap) { + if (state.done) return; + state.done = true; + if (unwrap) state = unwrap; + state.value = value; + state.state = REJECTED; + notify(state, true); +}; + +var internalResolve = function (state, value, unwrap) { + if (state.done) return; + state.done = true; + if (unwrap) state = unwrap; + try { + if (state.facade === value) throw TypeError("Promise can't be resolved itself"); + var then = isThenable(value); + if (then) { + microtask(function () { + var wrapper = { done: false }; + try { + then.call(value, + bind(internalResolve, wrapper, state), + bind(internalReject, wrapper, state) + ); + } catch (error) { + internalReject(wrapper, error, state); + } + }); + } else { + state.value = value; + state.state = FULFILLED; + notify(state, false); + } + } catch (error) { + internalReject({ done: false }, error, state); + } +}; + +// constructor polyfill +if (FORCED) { + // 25.4.3.1 Promise(executor) + PromiseConstructor = function Promise(executor) { + anInstance(this, PromiseConstructor, PROMISE); + aCallable(executor); + Internal.call(this); + var state = getInternalState(this); + try { + executor(bind(internalResolve, state), bind(internalReject, state)); + } catch (error) { + internalReject(state, error); + } + }; + PromiseConstructorPrototype = PromiseConstructor.prototype; + // eslint-disable-next-line no-unused-vars -- required for `.length` + Internal = function Promise(executor) { + setInternalState(this, { + type: PROMISE, + done: false, + notified: false, + parent: false, + reactions: [], + rejection: false, + state: PENDING, + value: undefined + }); + }; + Internal.prototype = redefineAll(PromiseConstructorPrototype, { + // `Promise.prototype.then` method + // https://tc39.es/ecma262/#sec-promise.prototype.then + then: function then(onFulfilled, onRejected) { + var state = getInternalPromiseState(this); + var reaction = newPromiseCapability(speciesConstructor(this, PromiseConstructor)); + reaction.ok = isCallable(onFulfilled) ? onFulfilled : true; + reaction.fail = isCallable(onRejected) && onRejected; + reaction.domain = IS_NODE ? process.domain : undefined; + state.parent = true; + state.reactions.push(reaction); + if (state.state != PENDING) notify(state, false); + return reaction.promise; + }, + // `Promise.prototype.catch` method + // https://tc39.es/ecma262/#sec-promise.prototype.catch + 'catch': function (onRejected) { + return this.then(undefined, onRejected); + } + }); + OwnPromiseCapability = function () { + var promise = new Internal(); + var state = getInternalState(promise); + this.promise = promise; + this.resolve = bind(internalResolve, state); + this.reject = bind(internalReject, state); + }; + newPromiseCapabilityModule.f = newPromiseCapability = function (C) { + return C === PromiseConstructor || C === PromiseWrapper + ? new OwnPromiseCapability(C) + : newGenericPromiseCapability(C); + }; + + if (!IS_PURE && isCallable(NativePromise) && NativePromisePrototype !== Object.prototype) { + nativeThen = NativePromisePrototype.then; + + if (!SUBCLASSING) { + // make `Promise#then` return a polyfilled `Promise` for native promise-based APIs + redefine(NativePromisePrototype, 'then', function then(onFulfilled, onRejected) { + var that = this; + return new PromiseConstructor(function (resolve, reject) { + nativeThen.call(that, resolve, reject); + }).then(onFulfilled, onRejected); + // https://github.com/zloirock/core-js/issues/640 + }, { unsafe: true }); + + // makes sure that native promise-based APIs `Promise#catch` properly works with patched `Promise#then` + redefine(NativePromisePrototype, 'catch', PromiseConstructorPrototype['catch'], { unsafe: true }); + } + + // make `.constructor === Promise` work for native promise-based APIs + try { + delete NativePromisePrototype.constructor; + } catch (error) { /* empty */ } + + // make `instanceof Promise` work for native promise-based APIs + if (setPrototypeOf) { + setPrototypeOf(NativePromisePrototype, PromiseConstructorPrototype); + } + } +} + +$({ global: true, wrap: true, forced: FORCED }, { + Promise: PromiseConstructor +}); + +setToStringTag(PromiseConstructor, PROMISE, false, true); +setSpecies(PROMISE); + +PromiseWrapper = getBuiltIn(PROMISE); + +// statics +$({ target: PROMISE, stat: true, forced: FORCED }, { + // `Promise.reject` method + // https://tc39.es/ecma262/#sec-promise.reject + reject: function reject(r) { + var capability = newPromiseCapability(this); + capability.reject.call(undefined, r); + return capability.promise; + } +}); + +$({ target: PROMISE, stat: true, forced: IS_PURE || FORCED }, { + // `Promise.resolve` method + // https://tc39.es/ecma262/#sec-promise.resolve + resolve: function resolve(x) { + return promiseResolve(IS_PURE && this === PromiseWrapper ? PromiseConstructor : this, x); + } +}); + +$({ target: PROMISE, stat: true, forced: INCORRECT_ITERATION }, { + // `Promise.all` method + // https://tc39.es/ecma262/#sec-promise.all + all: function all(iterable) { + var C = this; + var capability = newPromiseCapability(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var $promiseResolve = aCallable(C.resolve); + var values = []; + var counter = 0; + var remaining = 1; + iterate(iterable, function (promise) { + var index = counter++; + var alreadyCalled = false; + values.push(undefined); + remaining++; + $promiseResolve.call(C, promise).then(function (value) { + if (alreadyCalled) return; + alreadyCalled = true; + values[index] = value; + --remaining || resolve(values); + }, reject); + }); + --remaining || resolve(values); + }); + if (result.error) reject(result.value); + return capability.promise; + }, + // `Promise.race` method + // https://tc39.es/ecma262/#sec-promise.race + race: function race(iterable) { + var C = this; + var capability = newPromiseCapability(C); + var reject = capability.reject; + var result = perform(function () { + var $promiseResolve = aCallable(C.resolve); + iterate(iterable, function (promise) { + $promiseResolve.call(C, promise).then(capability.resolve, reject); + }); + }); + if (result.error) reject(result.value); + return capability.promise; + } +}); + + +/***/ }), + +/***/ 5397: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var getBuiltIn = __webpack_require__(150); +var aConstructor = __webpack_require__(1404); +var anObject = __webpack_require__(1138); +var isObject = __webpack_require__(5744); +var create = __webpack_require__(2853); +var bind = __webpack_require__(6782); +var fails = __webpack_require__(6192); + +var nativeConstruct = getBuiltIn('Reflect', 'construct'); + +// `Reflect.construct` method +// https://tc39.es/ecma262/#sec-reflect.construct +// MS Edge supports only 2 arguments and argumentsList argument is optional +// FF Nightly sets third argument as `new.target`, but does not create `this` from it +var NEW_TARGET_BUG = fails(function () { + function F() { /* empty */ } + return !(nativeConstruct(function () { /* empty */ }, [], F) instanceof F); +}); +var ARGS_BUG = !fails(function () { + nativeConstruct(function () { /* empty */ }); +}); +var FORCED = NEW_TARGET_BUG || ARGS_BUG; + +$({ target: 'Reflect', stat: true, forced: FORCED, sham: FORCED }, { + construct: function construct(Target, args /* , newTarget */) { + aConstructor(Target); + anObject(args); + var newTarget = arguments.length < 3 ? Target : aConstructor(arguments[2]); + if (ARGS_BUG && !NEW_TARGET_BUG) return nativeConstruct(Target, args, newTarget); + if (Target == newTarget) { + // w/o altered newTarget, optimization for 0-4 arguments + switch (args.length) { + case 0: return new Target(); + case 1: return new Target(args[0]); + case 2: return new Target(args[0], args[1]); + case 3: return new Target(args[0], args[1], args[2]); + case 4: return new Target(args[0], args[1], args[2], args[3]); + } + // w/o altered newTarget, lot of arguments case + var $args = [null]; + $args.push.apply($args, args); + return new (bind.apply(Target, $args))(); + } + // with altered newTarget, not support built-in constructors + var proto = newTarget.prototype; + var instance = create(isObject(proto) ? proto : Object.prototype); + var result = Function.apply.call(Target, instance, args); + return isObject(result) ? result : instance; + } +}); + + +/***/ }), + +/***/ 1367: +/***/ (function() { + +// empty + + +/***/ }), + +/***/ 5454: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var charAt = __webpack_require__(863).charAt; +var toString = __webpack_require__(4845); +var InternalStateModule = __webpack_require__(3326); +var defineIterator = __webpack_require__(7218); + +var STRING_ITERATOR = 'String Iterator'; +var setInternalState = InternalStateModule.set; +var getInternalState = InternalStateModule.getterFor(STRING_ITERATOR); + +// `String.prototype[@@iterator]` method +// https://tc39.es/ecma262/#sec-string.prototype-@@iterator +defineIterator(String, 'String', function (iterated) { + setInternalState(this, { + type: STRING_ITERATOR, + string: toString(iterated), + index: 0 + }); +// `%StringIteratorPrototype%.next` method +// https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next +}, function next() { + var state = getInternalState(this); + var string = state.string; + var index = state.index; + var point; + if (index >= string.length) return { value: undefined, done: true }; + point = charAt(string, index); + state.index += point.length; + return { value: point, done: false }; +}); + + +/***/ }), + +/***/ 957: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var $trim = __webpack_require__(4277).trim; +var forcedStringTrimMethod = __webpack_require__(6815); + +// `String.prototype.trim` method +// https://tc39.es/ecma262/#sec-string.prototype.trim +$({ target: 'String', proto: true, forced: forcedStringTrimMethod('trim') }, { + trim: function trim() { + return $trim(this); + } +}); + + +/***/ }), + +/***/ 9781: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.asyncIterator` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.asynciterator +defineWellKnownSymbol('asyncIterator'); + + +/***/ }), + +/***/ 492: +/***/ (function() { + +// empty + + +/***/ }), + +/***/ 6681: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.hasInstance` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.hasinstance +defineWellKnownSymbol('hasInstance'); + + +/***/ }), + +/***/ 9594: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.isConcatSpreadable` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.isconcatspreadable +defineWellKnownSymbol('isConcatSpreadable'); + + +/***/ }), + +/***/ 3665: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.iterator` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.iterator +defineWellKnownSymbol('iterator'); + + +/***/ }), + +/***/ 6187: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +var $ = __webpack_require__(3085); +var global = __webpack_require__(8576); +var getBuiltIn = __webpack_require__(150); +var IS_PURE = __webpack_require__(5546); +var DESCRIPTORS = __webpack_require__(69); +var NATIVE_SYMBOL = __webpack_require__(3045); +var fails = __webpack_require__(6192); +var hasOwn = __webpack_require__(4500); +var isArray = __webpack_require__(4770); +var isCallable = __webpack_require__(6447); +var isObject = __webpack_require__(5744); +var isSymbol = __webpack_require__(3236); +var anObject = __webpack_require__(1138); +var toObject = __webpack_require__(1795); +var toIndexedObject = __webpack_require__(101); +var toPropertyKey = __webpack_require__(77); +var $toString = __webpack_require__(4845); +var createPropertyDescriptor = __webpack_require__(774); +var nativeObjectCreate = __webpack_require__(2853); +var objectKeys = __webpack_require__(7653); +var getOwnPropertyNamesModule = __webpack_require__(2092); +var getOwnPropertyNamesExternal = __webpack_require__(4052); +var getOwnPropertySymbolsModule = __webpack_require__(4750); +var getOwnPropertyDescriptorModule = __webpack_require__(5141); +var definePropertyModule = __webpack_require__(2760); +var propertyIsEnumerableModule = __webpack_require__(6007); +var redefine = __webpack_require__(9482); +var shared = __webpack_require__(8717); +var sharedKey = __webpack_require__(9766); +var hiddenKeys = __webpack_require__(4535); +var uid = __webpack_require__(2759); +var wellKnownSymbol = __webpack_require__(8182); +var wrappedWellKnownSymbolModule = __webpack_require__(9207); +var defineWellKnownSymbol = __webpack_require__(1488); +var setToStringTag = __webpack_require__(1284); +var InternalStateModule = __webpack_require__(3326); +var $forEach = __webpack_require__(454).forEach; + +var HIDDEN = sharedKey('hidden'); +var SYMBOL = 'Symbol'; +var PROTOTYPE = 'prototype'; +var TO_PRIMITIVE = wellKnownSymbol('toPrimitive'); +var setInternalState = InternalStateModule.set; +var getInternalState = InternalStateModule.getterFor(SYMBOL); +var ObjectPrototype = Object[PROTOTYPE]; +var $Symbol = global.Symbol; +var $stringify = getBuiltIn('JSON', 'stringify'); +var nativeGetOwnPropertyDescriptor = getOwnPropertyDescriptorModule.f; +var nativeDefineProperty = definePropertyModule.f; +var nativeGetOwnPropertyNames = getOwnPropertyNamesExternal.f; +var nativePropertyIsEnumerable = propertyIsEnumerableModule.f; +var AllSymbols = shared('symbols'); +var ObjectPrototypeSymbols = shared('op-symbols'); +var StringToSymbolRegistry = shared('string-to-symbol-registry'); +var SymbolToStringRegistry = shared('symbol-to-string-registry'); +var WellKnownSymbolsStore = shared('wks'); +var QObject = global.QObject; +// Don't use setters in Qt Script, https://github.com/zloirock/core-js/issues/173 +var USE_SETTER = !QObject || !QObject[PROTOTYPE] || !QObject[PROTOTYPE].findChild; + +// fallback for old Android, https://code.google.com/p/v8/issues/detail?id=687 +var setSymbolDescriptor = DESCRIPTORS && fails(function () { + return nativeObjectCreate(nativeDefineProperty({}, 'a', { + get: function () { return nativeDefineProperty(this, 'a', { value: 7 }).a; } + })).a != 7; +}) ? function (O, P, Attributes) { + var ObjectPrototypeDescriptor = nativeGetOwnPropertyDescriptor(ObjectPrototype, P); + if (ObjectPrototypeDescriptor) delete ObjectPrototype[P]; + nativeDefineProperty(O, P, Attributes); + if (ObjectPrototypeDescriptor && O !== ObjectPrototype) { + nativeDefineProperty(ObjectPrototype, P, ObjectPrototypeDescriptor); + } +} : nativeDefineProperty; + +var wrap = function (tag, description) { + var symbol = AllSymbols[tag] = nativeObjectCreate($Symbol[PROTOTYPE]); + setInternalState(symbol, { + type: SYMBOL, + tag: tag, + description: description + }); + if (!DESCRIPTORS) symbol.description = description; + return symbol; +}; + +var $defineProperty = function defineProperty(O, P, Attributes) { + if (O === ObjectPrototype) $defineProperty(ObjectPrototypeSymbols, P, Attributes); + anObject(O); + var key = toPropertyKey(P); + anObject(Attributes); + if (hasOwn(AllSymbols, key)) { + if (!Attributes.enumerable) { + if (!hasOwn(O, HIDDEN)) nativeDefineProperty(O, HIDDEN, createPropertyDescriptor(1, {})); + O[HIDDEN][key] = true; + } else { + if (hasOwn(O, HIDDEN) && O[HIDDEN][key]) O[HIDDEN][key] = false; + Attributes = nativeObjectCreate(Attributes, { enumerable: createPropertyDescriptor(0, false) }); + } return setSymbolDescriptor(O, key, Attributes); + } return nativeDefineProperty(O, key, Attributes); +}; + +var $defineProperties = function defineProperties(O, Properties) { + anObject(O); + var properties = toIndexedObject(Properties); + var keys = objectKeys(properties).concat($getOwnPropertySymbols(properties)); + $forEach(keys, function (key) { + if (!DESCRIPTORS || $propertyIsEnumerable.call(properties, key)) $defineProperty(O, key, properties[key]); + }); + return O; +}; + +var $create = function create(O, Properties) { + return Properties === undefined ? nativeObjectCreate(O) : $defineProperties(nativeObjectCreate(O), Properties); +}; + +var $propertyIsEnumerable = function propertyIsEnumerable(V) { + var P = toPropertyKey(V); + var enumerable = nativePropertyIsEnumerable.call(this, P); + if (this === ObjectPrototype && hasOwn(AllSymbols, P) && !hasOwn(ObjectPrototypeSymbols, P)) return false; + return enumerable || !hasOwn(this, P) || !hasOwn(AllSymbols, P) || hasOwn(this, HIDDEN) && this[HIDDEN][P] + ? enumerable : true; +}; + +var $getOwnPropertyDescriptor = function getOwnPropertyDescriptor(O, P) { + var it = toIndexedObject(O); + var key = toPropertyKey(P); + if (it === ObjectPrototype && hasOwn(AllSymbols, key) && !hasOwn(ObjectPrototypeSymbols, key)) return; + var descriptor = nativeGetOwnPropertyDescriptor(it, key); + if (descriptor && hasOwn(AllSymbols, key) && !(hasOwn(it, HIDDEN) && it[HIDDEN][key])) { + descriptor.enumerable = true; + } + return descriptor; +}; + +var $getOwnPropertyNames = function getOwnPropertyNames(O) { + var names = nativeGetOwnPropertyNames(toIndexedObject(O)); + var result = []; + $forEach(names, function (key) { + if (!hasOwn(AllSymbols, key) && !hasOwn(hiddenKeys, key)) result.push(key); + }); + return result; +}; + +var $getOwnPropertySymbols = function getOwnPropertySymbols(O) { + var IS_OBJECT_PROTOTYPE = O === ObjectPrototype; + var names = nativeGetOwnPropertyNames(IS_OBJECT_PROTOTYPE ? ObjectPrototypeSymbols : toIndexedObject(O)); + var result = []; + $forEach(names, function (key) { + if (hasOwn(AllSymbols, key) && (!IS_OBJECT_PROTOTYPE || hasOwn(ObjectPrototype, key))) { + result.push(AllSymbols[key]); + } + }); + return result; +}; + +// `Symbol` constructor +// https://tc39.es/ecma262/#sec-symbol-constructor +if (!NATIVE_SYMBOL) { + $Symbol = function Symbol() { + if (this instanceof $Symbol) throw TypeError('Symbol is not a constructor'); + var description = !arguments.length || arguments[0] === undefined ? undefined : $toString(arguments[0]); + var tag = uid(description); + var setter = function (value) { + if (this === ObjectPrototype) setter.call(ObjectPrototypeSymbols, value); + if (hasOwn(this, HIDDEN) && hasOwn(this[HIDDEN], tag)) this[HIDDEN][tag] = false; + setSymbolDescriptor(this, tag, createPropertyDescriptor(1, value)); + }; + if (DESCRIPTORS && USE_SETTER) setSymbolDescriptor(ObjectPrototype, tag, { configurable: true, set: setter }); + return wrap(tag, description); + }; + + redefine($Symbol[PROTOTYPE], 'toString', function toString() { + return getInternalState(this).tag; + }); + + redefine($Symbol, 'withoutSetter', function (description) { + return wrap(uid(description), description); + }); + + propertyIsEnumerableModule.f = $propertyIsEnumerable; + definePropertyModule.f = $defineProperty; + getOwnPropertyDescriptorModule.f = $getOwnPropertyDescriptor; + getOwnPropertyNamesModule.f = getOwnPropertyNamesExternal.f = $getOwnPropertyNames; + getOwnPropertySymbolsModule.f = $getOwnPropertySymbols; + + wrappedWellKnownSymbolModule.f = function (name) { + return wrap(wellKnownSymbol(name), name); + }; + + if (DESCRIPTORS) { + // https://github.com/tc39/proposal-Symbol-description + nativeDefineProperty($Symbol[PROTOTYPE], 'description', { + configurable: true, + get: function description() { + return getInternalState(this).description; + } + }); + if (!IS_PURE) { + redefine(ObjectPrototype, 'propertyIsEnumerable', $propertyIsEnumerable, { unsafe: true }); + } + } +} + +$({ global: true, wrap: true, forced: !NATIVE_SYMBOL, sham: !NATIVE_SYMBOL }, { + Symbol: $Symbol +}); + +$forEach(objectKeys(WellKnownSymbolsStore), function (name) { + defineWellKnownSymbol(name); +}); + +$({ target: SYMBOL, stat: true, forced: !NATIVE_SYMBOL }, { + // `Symbol.for` method + // https://tc39.es/ecma262/#sec-symbol.for + 'for': function (key) { + var string = $toString(key); + if (hasOwn(StringToSymbolRegistry, string)) return StringToSymbolRegistry[string]; + var symbol = $Symbol(string); + StringToSymbolRegistry[string] = symbol; + SymbolToStringRegistry[symbol] = string; + return symbol; + }, + // `Symbol.keyFor` method + // https://tc39.es/ecma262/#sec-symbol.keyfor + keyFor: function keyFor(sym) { + if (!isSymbol(sym)) throw TypeError(sym + ' is not a symbol'); + if (hasOwn(SymbolToStringRegistry, sym)) return SymbolToStringRegistry[sym]; + }, + useSetter: function () { USE_SETTER = true; }, + useSimple: function () { USE_SETTER = false; } +}); + +$({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL, sham: !DESCRIPTORS }, { + // `Object.create` method + // https://tc39.es/ecma262/#sec-object.create + create: $create, + // `Object.defineProperty` method + // https://tc39.es/ecma262/#sec-object.defineproperty + defineProperty: $defineProperty, + // `Object.defineProperties` method + // https://tc39.es/ecma262/#sec-object.defineproperties + defineProperties: $defineProperties, + // `Object.getOwnPropertyDescriptor` method + // https://tc39.es/ecma262/#sec-object.getownpropertydescriptors + getOwnPropertyDescriptor: $getOwnPropertyDescriptor +}); + +$({ target: 'Object', stat: true, forced: !NATIVE_SYMBOL }, { + // `Object.getOwnPropertyNames` method + // https://tc39.es/ecma262/#sec-object.getownpropertynames + getOwnPropertyNames: $getOwnPropertyNames, + // `Object.getOwnPropertySymbols` method + // https://tc39.es/ecma262/#sec-object.getownpropertysymbols + getOwnPropertySymbols: $getOwnPropertySymbols +}); + +// Chrome 38 and 39 `Object.getOwnPropertySymbols` fails on primitives +// https://bugs.chromium.org/p/v8/issues/detail?id=3443 +$({ target: 'Object', stat: true, forced: fails(function () { getOwnPropertySymbolsModule.f(1); }) }, { + getOwnPropertySymbols: function getOwnPropertySymbols(it) { + return getOwnPropertySymbolsModule.f(toObject(it)); + } +}); + +// `JSON.stringify` method behavior with symbols +// https://tc39.es/ecma262/#sec-json.stringify +if ($stringify) { + var FORCED_JSON_STRINGIFY = !NATIVE_SYMBOL || fails(function () { + var symbol = $Symbol(); + // MS Edge converts symbol values to JSON as {} + return $stringify([symbol]) != '[null]' + // WebKit converts symbol values to JSON as null + || $stringify({ a: symbol }) != '{}' + // V8 throws on boxed symbols + || $stringify(Object(symbol)) != '{}'; + }); + + $({ target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, { + // eslint-disable-next-line no-unused-vars -- required for `.length` + stringify: function stringify(it, replacer, space) { + var args = [it]; + var index = 1; + var $replacer; + while (arguments.length > index) args.push(arguments[index++]); + $replacer = replacer; + if (!isObject(replacer) && it === undefined || isSymbol(it)) return; // IE8 returns string on undefined + if (!isArray(replacer)) replacer = function (key, value) { + if (isCallable($replacer)) value = $replacer.call(this, key, value); + if (!isSymbol(value)) return value; + }; + args[1] = replacer; + return $stringify.apply(null, args); + } + }); +} + +// `Symbol.prototype[@@toPrimitive]` method +// https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive +if (!$Symbol[PROTOTYPE][TO_PRIMITIVE]) { + var valueOf = $Symbol[PROTOTYPE].valueOf; + redefine($Symbol[PROTOTYPE], TO_PRIMITIVE, function () { + return valueOf.apply(this, arguments); + }); +} +// `Symbol.prototype[@@toStringTag]` property +// https://tc39.es/ecma262/#sec-symbol.prototype-@@tostringtag +setToStringTag($Symbol, SYMBOL); + +hiddenKeys[HIDDEN] = true; + + +/***/ }), + +/***/ 1250: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.matchAll` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.matchall +defineWellKnownSymbol('matchAll'); + + +/***/ }), + +/***/ 9017: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.match` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.match +defineWellKnownSymbol('match'); + + +/***/ }), + +/***/ 9786: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.replace` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.replace +defineWellKnownSymbol('replace'); + + +/***/ }), + +/***/ 503: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.search` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.search +defineWellKnownSymbol('search'); + + +/***/ }), + +/***/ 6565: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.species` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.species +defineWellKnownSymbol('species'); + + +/***/ }), + +/***/ 9322: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.split` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.split +defineWellKnownSymbol('split'); + + +/***/ }), + +/***/ 3610: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.toPrimitive` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.toprimitive +defineWellKnownSymbol('toPrimitive'); + + +/***/ }), + +/***/ 6886: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.toStringTag` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.tostringtag +defineWellKnownSymbol('toStringTag'); + + +/***/ }), + +/***/ 3514: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.unscopables` well-known symbol +// https://tc39.es/ecma262/#sec-symbol.unscopables +defineWellKnownSymbol('unscopables'); + + +/***/ }), + +/***/ 177: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.asyncDispose` well-known symbol +// https://github.com/tc39/proposal-using-statement +defineWellKnownSymbol('asyncDispose'); + + +/***/ }), + +/***/ 9031: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.dispose` well-known symbol +// https://github.com/tc39/proposal-using-statement +defineWellKnownSymbol('dispose'); + + +/***/ }), + +/***/ 6658: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.matcher` well-known symbol +// https://github.com/tc39/proposal-pattern-matching +defineWellKnownSymbol('matcher'); + + +/***/ }), + +/***/ 1875: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.metadata` well-known symbol +// https://github.com/tc39/proposal-decorators +defineWellKnownSymbol('metadata'); + + +/***/ }), + +/***/ 8658: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.observable` well-known symbol +// https://github.com/tc39/proposal-observable +defineWellKnownSymbol('observable'); + + +/***/ }), + +/***/ 4592: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +// TODO: remove from `core-js@4` +var defineWellKnownSymbol = __webpack_require__(1488); + +// `Symbol.patternMatch` well-known symbol +// https://github.com/tc39/proposal-pattern-matching +defineWellKnownSymbol('patternMatch'); + + +/***/ }), + +/***/ 6680: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +// TODO: remove from `core-js@4` +var defineWellKnownSymbol = __webpack_require__(1488); + +defineWellKnownSymbol('replaceAll'); + + +/***/ }), + +/***/ 162: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(8939); +var DOMIterables = __webpack_require__(7365); +var global = __webpack_require__(8576); +var classof = __webpack_require__(4696); +var createNonEnumerableProperty = __webpack_require__(8711); +var Iterators = __webpack_require__(7771); +var wellKnownSymbol = __webpack_require__(8182); + +var TO_STRING_TAG = wellKnownSymbol('toStringTag'); + +for (var COLLECTION_NAME in DOMIterables) { + var Collection = global[COLLECTION_NAME]; + var CollectionPrototype = Collection && Collection.prototype; + if (CollectionPrototype && classof(CollectionPrototype) !== TO_STRING_TAG) { + createNonEnumerableProperty(CollectionPrototype, TO_STRING_TAG, COLLECTION_NAME); + } + Iterators[COLLECTION_NAME] = Iterators.Array; +} + + +/***/ }), + +/***/ 2906: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +var $ = __webpack_require__(3085); +var global = __webpack_require__(8576); +var isCallable = __webpack_require__(6447); +var userAgent = __webpack_require__(8989); + +var slice = [].slice; +var MSIE = /MSIE .\./.test(userAgent); // <- dirty ie9- check + +var wrap = function (scheduler) { + return function (handler, timeout /* , ...arguments */) { + var boundArgs = arguments.length > 2; + var args = boundArgs ? slice.call(arguments, 2) : undefined; + return scheduler(boundArgs ? function () { + // eslint-disable-next-line no-new-func -- spec requirement + (isCallable(handler) ? handler : Function(handler)).apply(this, args); + } : handler, timeout); + }; +}; + +// ie9- setTimeout & setInterval additional parameters fix +// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers +$({ global: true, bind: true, forced: MSIE }, { + // `setTimeout` method + // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-settimeout + setTimeout: wrap(global.setTimeout), + // `setInterval` method + // https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-setinterval + setInterval: wrap(global.setInterval) +}); + + +/***/ }), + +/***/ 9336: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +// TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env` +__webpack_require__(8939); +var $ = __webpack_require__(3085); +var getBuiltIn = __webpack_require__(150); +var USE_NATIVE_URL = __webpack_require__(4551); +var redefine = __webpack_require__(9482); +var redefineAll = __webpack_require__(533); +var setToStringTag = __webpack_require__(1284); +var createIteratorConstructor = __webpack_require__(5148); +var InternalStateModule = __webpack_require__(3326); +var anInstance = __webpack_require__(6961); +var isCallable = __webpack_require__(6447); +var hasOwn = __webpack_require__(4500); +var bind = __webpack_require__(8043); +var classof = __webpack_require__(4696); +var anObject = __webpack_require__(1138); +var isObject = __webpack_require__(5744); +var $toString = __webpack_require__(4845); +var create = __webpack_require__(2853); +var createPropertyDescriptor = __webpack_require__(774); +var getIterator = __webpack_require__(1669); +var getIteratorMethod = __webpack_require__(8703); +var wellKnownSymbol = __webpack_require__(8182); + +var nativeFetch = getBuiltIn('fetch'); +var NativeRequest = getBuiltIn('Request'); +var RequestPrototype = NativeRequest && NativeRequest.prototype; +var Headers = getBuiltIn('Headers'); +var ITERATOR = wellKnownSymbol('iterator'); +var URL_SEARCH_PARAMS = 'URLSearchParams'; +var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator'; +var setInternalState = InternalStateModule.set; +var getInternalParamsState = InternalStateModule.getterFor(URL_SEARCH_PARAMS); +var getInternalIteratorState = InternalStateModule.getterFor(URL_SEARCH_PARAMS_ITERATOR); + +var plus = /\+/g; +var sequences = Array(4); + +var percentSequence = function (bytes) { + return sequences[bytes - 1] || (sequences[bytes - 1] = RegExp('((?:%[\\da-f]{2}){' + bytes + '})', 'gi')); +}; + +var percentDecode = function (sequence) { + try { + return decodeURIComponent(sequence); + } catch (error) { + return sequence; + } +}; + +var deserialize = function (it) { + var result = it.replace(plus, ' '); + var bytes = 4; + try { + return decodeURIComponent(result); + } catch (error) { + while (bytes) { + result = result.replace(percentSequence(bytes--), percentDecode); + } + return result; + } +}; + +var find = /[!'()~]|%20/g; + +var replace = { + '!': '%21', + "'": '%27', + '(': '%28', + ')': '%29', + '~': '%7E', + '%20': '+' +}; + +var replacer = function (match) { + return replace[match]; +}; + +var serialize = function (it) { + return encodeURIComponent(it).replace(find, replacer); +}; + +var parseSearchParams = function (result, query) { + if (query) { + var attributes = query.split('&'); + var index = 0; + var attribute, entry; + while (index < attributes.length) { + attribute = attributes[index++]; + if (attribute.length) { + entry = attribute.split('='); + result.push({ + key: deserialize(entry.shift()), + value: deserialize(entry.join('=')) + }); + } + } + } +}; + +var updateSearchParams = function (query) { + this.entries.length = 0; + parseSearchParams(this.entries, query); +}; + +var validateArgumentsLength = function (passed, required) { + if (passed < required) throw TypeError('Not enough arguments'); +}; + +var URLSearchParamsIterator = createIteratorConstructor(function Iterator(params, kind) { + setInternalState(this, { + type: URL_SEARCH_PARAMS_ITERATOR, + iterator: getIterator(getInternalParamsState(params).entries), + kind: kind + }); +}, 'Iterator', function next() { + var state = getInternalIteratorState(this); + var kind = state.kind; + var step = state.iterator.next(); + var entry = step.value; + if (!step.done) { + step.value = kind === 'keys' ? entry.key : kind === 'values' ? entry.value : [entry.key, entry.value]; + } return step; +}); + +// `URLSearchParams` constructor +// https://url.spec.whatwg.org/#interface-urlsearchparams +var URLSearchParamsConstructor = function URLSearchParams(/* init */) { + anInstance(this, URLSearchParamsConstructor, URL_SEARCH_PARAMS); + var init = arguments.length > 0 ? arguments[0] : undefined; + var that = this; + var entries = []; + var iteratorMethod, iterator, next, step, entryIterator, entryNext, first, second, key; + + setInternalState(that, { + type: URL_SEARCH_PARAMS, + entries: entries, + updateURL: function () { /* empty */ }, + updateSearchParams: updateSearchParams + }); + + if (init !== undefined) { + if (isObject(init)) { + iteratorMethod = getIteratorMethod(init); + if (iteratorMethod) { + iterator = getIterator(init, iteratorMethod); + next = iterator.next; + while (!(step = next.call(iterator)).done) { + entryIterator = getIterator(anObject(step.value)); + entryNext = entryIterator.next; + if ( + (first = entryNext.call(entryIterator)).done || + (second = entryNext.call(entryIterator)).done || + !entryNext.call(entryIterator).done + ) throw TypeError('Expected sequence with length 2'); + entries.push({ key: $toString(first.value), value: $toString(second.value) }); + } + } else for (key in init) if (hasOwn(init, key)) entries.push({ key: key, value: $toString(init[key]) }); + } else { + parseSearchParams( + entries, + typeof init === 'string' ? init.charAt(0) === '?' ? init.slice(1) : init : $toString(init) + ); + } + } +}; + +var URLSearchParamsPrototype = URLSearchParamsConstructor.prototype; + +redefineAll(URLSearchParamsPrototype, { + // `URLSearchParams.prototype.append` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-append + append: function append(name, value) { + validateArgumentsLength(arguments.length, 2); + var state = getInternalParamsState(this); + state.entries.push({ key: $toString(name), value: $toString(value) }); + state.updateURL(); + }, + // `URLSearchParams.prototype.delete` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-delete + 'delete': function (name) { + validateArgumentsLength(arguments.length, 1); + var state = getInternalParamsState(this); + var entries = state.entries; + var key = $toString(name); + var index = 0; + while (index < entries.length) { + if (entries[index].key === key) entries.splice(index, 1); + else index++; + } + state.updateURL(); + }, + // `URLSearchParams.prototype.get` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-get + get: function get(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = $toString(name); + var index = 0; + for (; index < entries.length; index++) { + if (entries[index].key === key) return entries[index].value; + } + return null; + }, + // `URLSearchParams.prototype.getAll` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-getall + getAll: function getAll(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = $toString(name); + var result = []; + var index = 0; + for (; index < entries.length; index++) { + if (entries[index].key === key) result.push(entries[index].value); + } + return result; + }, + // `URLSearchParams.prototype.has` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-has + has: function has(name) { + validateArgumentsLength(arguments.length, 1); + var entries = getInternalParamsState(this).entries; + var key = $toString(name); + var index = 0; + while (index < entries.length) { + if (entries[index++].key === key) return true; + } + return false; + }, + // `URLSearchParams.prototype.set` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-set + set: function set(name, value) { + validateArgumentsLength(arguments.length, 1); + var state = getInternalParamsState(this); + var entries = state.entries; + var found = false; + var key = $toString(name); + var val = $toString(value); + var index = 0; + var entry; + for (; index < entries.length; index++) { + entry = entries[index]; + if (entry.key === key) { + if (found) entries.splice(index--, 1); + else { + found = true; + entry.value = val; + } + } + } + if (!found) entries.push({ key: key, value: val }); + state.updateURL(); + }, + // `URLSearchParams.prototype.sort` method + // https://url.spec.whatwg.org/#dom-urlsearchparams-sort + sort: function sort() { + var state = getInternalParamsState(this); + var entries = state.entries; + // Array#sort is not stable in some engines + var slice = entries.slice(); + var entry, entriesIndex, sliceIndex; + entries.length = 0; + for (sliceIndex = 0; sliceIndex < slice.length; sliceIndex++) { + entry = slice[sliceIndex]; + for (entriesIndex = 0; entriesIndex < sliceIndex; entriesIndex++) { + if (entries[entriesIndex].key > entry.key) { + entries.splice(entriesIndex, 0, entry); + break; + } + } + if (entriesIndex === sliceIndex) entries.push(entry); + } + state.updateURL(); + }, + // `URLSearchParams.prototype.forEach` method + forEach: function forEach(callback /* , thisArg */) { + var entries = getInternalParamsState(this).entries; + var boundFunction = bind(callback, arguments.length > 1 ? arguments[1] : undefined, 3); + var index = 0; + var entry; + while (index < entries.length) { + entry = entries[index++]; + boundFunction(entry.value, entry.key, this); + } + }, + // `URLSearchParams.prototype.keys` method + keys: function keys() { + return new URLSearchParamsIterator(this, 'keys'); + }, + // `URLSearchParams.prototype.values` method + values: function values() { + return new URLSearchParamsIterator(this, 'values'); + }, + // `URLSearchParams.prototype.entries` method + entries: function entries() { + return new URLSearchParamsIterator(this, 'entries'); + } +}, { enumerable: true }); + +// `URLSearchParams.prototype[@@iterator]` method +redefine(URLSearchParamsPrototype, ITERATOR, URLSearchParamsPrototype.entries, { name: 'entries' }); + +// `URLSearchParams.prototype.toString` method +// https://url.spec.whatwg.org/#urlsearchparams-stringification-behavior +redefine(URLSearchParamsPrototype, 'toString', function toString() { + var entries = getInternalParamsState(this).entries; + var result = []; + var index = 0; + var entry; + while (index < entries.length) { + entry = entries[index++]; + result.push(serialize(entry.key) + '=' + serialize(entry.value)); + } return result.join('&'); +}, { enumerable: true }); + +setToStringTag(URLSearchParamsConstructor, URL_SEARCH_PARAMS); + +$({ global: true, forced: !USE_NATIVE_URL }, { + URLSearchParams: URLSearchParamsConstructor +}); + +// Wrap `fetch` and `Request` for correct work with polyfilled `URLSearchParams` +if (!USE_NATIVE_URL && isCallable(Headers)) { + var wrapRequestOptions = function (init) { + if (isObject(init)) { + var body = init.body; + var headers; + if (classof(body) === URL_SEARCH_PARAMS) { + headers = init.headers ? new Headers(init.headers) : new Headers(); + if (!headers.has('content-type')) { + headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); + } + return create(init, { + body: createPropertyDescriptor(0, String(body)), + headers: createPropertyDescriptor(0, headers) + }); + } + } return init; + }; + + if (isCallable(nativeFetch)) { + $({ global: true, enumerable: true, forced: true }, { + fetch: function fetch(input /* , init */) { + return nativeFetch(input, arguments.length > 1 ? wrapRequestOptions(arguments[1]) : {}); + } + }); + } + + if (isCallable(NativeRequest)) { + var RequestConstructor = function Request(input /* , init */) { + anInstance(this, RequestConstructor, 'Request'); + return new NativeRequest(input, arguments.length > 1 ? wrapRequestOptions(arguments[1]) : {}); + }; + + RequestPrototype.constructor = RequestConstructor; + RequestConstructor.prototype = RequestPrototype; + + $({ global: true, forced: true }, { + Request: RequestConstructor + }); + } +} + +module.exports = { + URLSearchParams: URLSearchParamsConstructor, + getState: getInternalParamsState +}; + + +/***/ }), + +/***/ 4948: +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + +"use strict"; + +// TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env` +__webpack_require__(5454); +var $ = __webpack_require__(3085); +var DESCRIPTORS = __webpack_require__(69); +var USE_NATIVE_URL = __webpack_require__(4551); +var global = __webpack_require__(8576); +var defineProperties = __webpack_require__(1187); +var redefine = __webpack_require__(9482); +var anInstance = __webpack_require__(6961); +var hasOwn = __webpack_require__(4500); +var assign = __webpack_require__(2503); +var arrayFrom = __webpack_require__(841); +var codeAt = __webpack_require__(863).codeAt; +var toASCII = __webpack_require__(7977); +var $toString = __webpack_require__(4845); +var setToStringTag = __webpack_require__(1284); +var URLSearchParamsModule = __webpack_require__(9336); +var InternalStateModule = __webpack_require__(3326); + +var NativeURL = global.URL; +var URLSearchParams = URLSearchParamsModule.URLSearchParams; +var getInternalSearchParamsState = URLSearchParamsModule.getState; +var setInternalState = InternalStateModule.set; +var getInternalURLState = InternalStateModule.getterFor('URL'); +var floor = Math.floor; +var pow = Math.pow; + +var INVALID_AUTHORITY = 'Invalid authority'; +var INVALID_SCHEME = 'Invalid scheme'; +var INVALID_HOST = 'Invalid host'; +var INVALID_PORT = 'Invalid port'; + +var ALPHA = /[A-Za-z]/; +// eslint-disable-next-line regexp/no-obscure-range -- safe +var ALPHANUMERIC = /[\d+-.A-Za-z]/; +var DIGIT = /\d/; +var HEX_START = /^0x/i; +var OCT = /^[0-7]+$/; +var DEC = /^\d+$/; +var HEX = /^[\dA-Fa-f]+$/; +/* eslint-disable regexp/no-control-character -- safe */ +var FORBIDDEN_HOST_CODE_POINT = /[\0\t\n\r #%/:<>?@[\\\]^|]/; +var FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT = /[\0\t\n\r #/:<>?@[\\\]^|]/; +var LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g; +var TAB_AND_NEW_LINE = /[\t\n\r]/g; +/* eslint-enable regexp/no-control-character -- safe */ +var EOF; + +var parseHost = function (url, input) { + var result, codePoints, index; + if (input.charAt(0) == '[') { + if (input.charAt(input.length - 1) != ']') return INVALID_HOST; + result = parseIPv6(input.slice(1, -1)); + if (!result) return INVALID_HOST; + url.host = result; + // opaque host + } else if (!isSpecial(url)) { + if (FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT.test(input)) return INVALID_HOST; + result = ''; + codePoints = arrayFrom(input); + for (index = 0; index < codePoints.length; index++) { + result += percentEncode(codePoints[index], C0ControlPercentEncodeSet); + } + url.host = result; + } else { + input = toASCII(input); + if (FORBIDDEN_HOST_CODE_POINT.test(input)) return INVALID_HOST; + result = parseIPv4(input); + if (result === null) return INVALID_HOST; + url.host = result; + } +}; + +var parseIPv4 = function (input) { + var parts = input.split('.'); + var partsLength, numbers, index, part, radix, number, ipv4; + if (parts.length && parts[parts.length - 1] == '') { + parts.pop(); + } + partsLength = parts.length; + if (partsLength > 4) return input; + numbers = []; + for (index = 0; index < partsLength; index++) { + part = parts[index]; + if (part == '') return input; + radix = 10; + if (part.length > 1 && part.charAt(0) == '0') { + radix = HEX_START.test(part) ? 16 : 8; + part = part.slice(radix == 8 ? 1 : 2); + } + if (part === '') { + number = 0; + } else { + if (!(radix == 10 ? DEC : radix == 8 ? OCT : HEX).test(part)) return input; + number = parseInt(part, radix); + } + numbers.push(number); + } + for (index = 0; index < partsLength; index++) { + number = numbers[index]; + if (index == partsLength - 1) { + if (number >= pow(256, 5 - partsLength)) return null; + } else if (number > 255) return null; + } + ipv4 = numbers.pop(); + for (index = 0; index < numbers.length; index++) { + ipv4 += numbers[index] * pow(256, 3 - index); + } + return ipv4; +}; + +// eslint-disable-next-line max-statements -- TODO +var parseIPv6 = function (input) { + var address = [0, 0, 0, 0, 0, 0, 0, 0]; + var pieceIndex = 0; + var compress = null; + var pointer = 0; + var value, length, numbersSeen, ipv4Piece, number, swaps, swap; + + var chr = function () { + return input.charAt(pointer); + }; + + if (chr() == ':') { + if (input.charAt(1) != ':') return; + pointer += 2; + pieceIndex++; + compress = pieceIndex; + } + while (chr()) { + if (pieceIndex == 8) return; + if (chr() == ':') { + if (compress !== null) return; + pointer++; + pieceIndex++; + compress = pieceIndex; + continue; + } + value = length = 0; + while (length < 4 && HEX.test(chr())) { + value = value * 16 + parseInt(chr(), 16); + pointer++; + length++; + } + if (chr() == '.') { + if (length == 0) return; + pointer -= length; + if (pieceIndex > 6) return; + numbersSeen = 0; + while (chr()) { + ipv4Piece = null; + if (numbersSeen > 0) { + if (chr() == '.' && numbersSeen < 4) pointer++; + else return; + } + if (!DIGIT.test(chr())) return; + while (DIGIT.test(chr())) { + number = parseInt(chr(), 10); + if (ipv4Piece === null) ipv4Piece = number; + else if (ipv4Piece == 0) return; + else ipv4Piece = ipv4Piece * 10 + number; + if (ipv4Piece > 255) return; + pointer++; + } + address[pieceIndex] = address[pieceIndex] * 256 + ipv4Piece; + numbersSeen++; + if (numbersSeen == 2 || numbersSeen == 4) pieceIndex++; + } + if (numbersSeen != 4) return; + break; + } else if (chr() == ':') { + pointer++; + if (!chr()) return; + } else if (chr()) return; + address[pieceIndex++] = value; + } + if (compress !== null) { + swaps = pieceIndex - compress; + pieceIndex = 7; + while (pieceIndex != 0 && swaps > 0) { + swap = address[pieceIndex]; + address[pieceIndex--] = address[compress + swaps - 1]; + address[compress + --swaps] = swap; + } + } else if (pieceIndex != 8) return; + return address; +}; + +var findLongestZeroSequence = function (ipv6) { + var maxIndex = null; + var maxLength = 1; + var currStart = null; + var currLength = 0; + var index = 0; + for (; index < 8; index++) { + if (ipv6[index] !== 0) { + if (currLength > maxLength) { + maxIndex = currStart; + maxLength = currLength; + } + currStart = null; + currLength = 0; + } else { + if (currStart === null) currStart = index; + ++currLength; + } + } + if (currLength > maxLength) { + maxIndex = currStart; + maxLength = currLength; + } + return maxIndex; +}; + +var serializeHost = function (host) { + var result, index, compress, ignore0; + // ipv4 + if (typeof host == 'number') { + result = []; + for (index = 0; index < 4; index++) { + result.unshift(host % 256); + host = floor(host / 256); + } return result.join('.'); + // ipv6 + } else if (typeof host == 'object') { + result = ''; + compress = findLongestZeroSequence(host); + for (index = 0; index < 8; index++) { + if (ignore0 && host[index] === 0) continue; + if (ignore0) ignore0 = false; + if (compress === index) { + result += index ? ':' : '::'; + ignore0 = true; + } else { + result += host[index].toString(16); + if (index < 7) result += ':'; + } + } + return '[' + result + ']'; + } return host; +}; + +var C0ControlPercentEncodeSet = {}; +var fragmentPercentEncodeSet = assign({}, C0ControlPercentEncodeSet, { + ' ': 1, '"': 1, '<': 1, '>': 1, '`': 1 +}); +var pathPercentEncodeSet = assign({}, fragmentPercentEncodeSet, { + '#': 1, '?': 1, '{': 1, '}': 1 +}); +var userinfoPercentEncodeSet = assign({}, pathPercentEncodeSet, { + '/': 1, ':': 1, ';': 1, '=': 1, '@': 1, '[': 1, '\\': 1, ']': 1, '^': 1, '|': 1 +}); + +var percentEncode = function (chr, set) { + var code = codeAt(chr, 0); + return code > 0x20 && code < 0x7F && !hasOwn(set, chr) ? chr : encodeURIComponent(chr); +}; + +var specialSchemes = { + ftp: 21, + file: null, + http: 80, + https: 443, + ws: 80, + wss: 443 +}; + +var isSpecial = function (url) { + return hasOwn(specialSchemes, url.scheme); +}; + +var includesCredentials = function (url) { + return url.username != '' || url.password != ''; +}; + +var cannotHaveUsernamePasswordPort = function (url) { + return !url.host || url.cannotBeABaseURL || url.scheme == 'file'; +}; + +var isWindowsDriveLetter = function (string, normalized) { + var second; + return string.length == 2 && ALPHA.test(string.charAt(0)) + && ((second = string.charAt(1)) == ':' || (!normalized && second == '|')); +}; + +var startsWithWindowsDriveLetter = function (string) { + var third; + return string.length > 1 && isWindowsDriveLetter(string.slice(0, 2)) && ( + string.length == 2 || + ((third = string.charAt(2)) === '/' || third === '\\' || third === '?' || third === '#') + ); +}; + +var shortenURLsPath = function (url) { + var path = url.path; + var pathSize = path.length; + if (pathSize && (url.scheme != 'file' || pathSize != 1 || !isWindowsDriveLetter(path[0], true))) { + path.pop(); + } +}; + +var isSingleDot = function (segment) { + return segment === '.' || segment.toLowerCase() === '%2e'; +}; + +var isDoubleDot = function (segment) { + segment = segment.toLowerCase(); + return segment === '..' || segment === '%2e.' || segment === '.%2e' || segment === '%2e%2e'; +}; + +// States: +var SCHEME_START = {}; +var SCHEME = {}; +var NO_SCHEME = {}; +var SPECIAL_RELATIVE_OR_AUTHORITY = {}; +var PATH_OR_AUTHORITY = {}; +var RELATIVE = {}; +var RELATIVE_SLASH = {}; +var SPECIAL_AUTHORITY_SLASHES = {}; +var SPECIAL_AUTHORITY_IGNORE_SLASHES = {}; +var AUTHORITY = {}; +var HOST = {}; +var HOSTNAME = {}; +var PORT = {}; +var FILE = {}; +var FILE_SLASH = {}; +var FILE_HOST = {}; +var PATH_START = {}; +var PATH = {}; +var CANNOT_BE_A_BASE_URL_PATH = {}; +var QUERY = {}; +var FRAGMENT = {}; + +// eslint-disable-next-line max-statements -- TODO +var parseURL = function (url, input, stateOverride, base) { + var state = stateOverride || SCHEME_START; + var pointer = 0; + var buffer = ''; + var seenAt = false; + var seenBracket = false; + var seenPasswordToken = false; + var codePoints, chr, bufferCodePoints, failure; + + if (!stateOverride) { + url.scheme = ''; + url.username = ''; + url.password = ''; + url.host = null; + url.port = null; + url.path = []; + url.query = null; + url.fragment = null; + url.cannotBeABaseURL = false; + input = input.replace(LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE, ''); + } + + input = input.replace(TAB_AND_NEW_LINE, ''); + + codePoints = arrayFrom(input); + + while (pointer <= codePoints.length) { + chr = codePoints[pointer]; + switch (state) { + case SCHEME_START: + if (chr && ALPHA.test(chr)) { + buffer += chr.toLowerCase(); + state = SCHEME; + } else if (!stateOverride) { + state = NO_SCHEME; + continue; + } else return INVALID_SCHEME; + break; + + case SCHEME: + if (chr && (ALPHANUMERIC.test(chr) || chr == '+' || chr == '-' || chr == '.')) { + buffer += chr.toLowerCase(); + } else if (chr == ':') { + if (stateOverride && ( + (isSpecial(url) != hasOwn(specialSchemes, buffer)) || + (buffer == 'file' && (includesCredentials(url) || url.port !== null)) || + (url.scheme == 'file' && !url.host) + )) return; + url.scheme = buffer; + if (stateOverride) { + if (isSpecial(url) && specialSchemes[url.scheme] == url.port) url.port = null; + return; + } + buffer = ''; + if (url.scheme == 'file') { + state = FILE; + } else if (isSpecial(url) && base && base.scheme == url.scheme) { + state = SPECIAL_RELATIVE_OR_AUTHORITY; + } else if (isSpecial(url)) { + state = SPECIAL_AUTHORITY_SLASHES; + } else if (codePoints[pointer + 1] == '/') { + state = PATH_OR_AUTHORITY; + pointer++; + } else { + url.cannotBeABaseURL = true; + url.path.push(''); + state = CANNOT_BE_A_BASE_URL_PATH; + } + } else if (!stateOverride) { + buffer = ''; + state = NO_SCHEME; + pointer = 0; + continue; + } else return INVALID_SCHEME; + break; + + case NO_SCHEME: + if (!base || (base.cannotBeABaseURL && chr != '#')) return INVALID_SCHEME; + if (base.cannotBeABaseURL && chr == '#') { + url.scheme = base.scheme; + url.path = base.path.slice(); + url.query = base.query; + url.fragment = ''; + url.cannotBeABaseURL = true; + state = FRAGMENT; + break; + } + state = base.scheme == 'file' ? FILE : RELATIVE; + continue; + + case SPECIAL_RELATIVE_OR_AUTHORITY: + if (chr == '/' && codePoints[pointer + 1] == '/') { + state = SPECIAL_AUTHORITY_IGNORE_SLASHES; + pointer++; + } else { + state = RELATIVE; + continue; + } break; + + case PATH_OR_AUTHORITY: + if (chr == '/') { + state = AUTHORITY; + break; + } else { + state = PATH; + continue; + } + + case RELATIVE: + url.scheme = base.scheme; + if (chr == EOF) { + url.username = base.username; + url.password = base.password; + url.host = base.host; + url.port = base.port; + url.path = base.path.slice(); + url.query = base.query; + } else if (chr == '/' || (chr == '\\' && isSpecial(url))) { + state = RELATIVE_SLASH; + } else if (chr == '?') { + url.username = base.username; + url.password = base.password; + url.host = base.host; + url.port = base.port; + url.path = base.path.slice(); + url.query = ''; + state = QUERY; + } else if (chr == '#') { + url.username = base.username; + url.password = base.password; + url.host = base.host; + url.port = base.port; + url.path = base.path.slice(); + url.query = base.query; + url.fragment = ''; + state = FRAGMENT; + } else { + url.username = base.username; + url.password = base.password; + url.host = base.host; + url.port = base.port; + url.path = base.path.slice(); + url.path.pop(); + state = PATH; + continue; + } break; + + case RELATIVE_SLASH: + if (isSpecial(url) && (chr == '/' || chr == '\\')) { + state = SPECIAL_AUTHORITY_IGNORE_SLASHES; + } else if (chr == '/') { + state = AUTHORITY; + } else { + url.username = base.username; + url.password = base.password; + url.host = base.host; + url.port = base.port; + state = PATH; + continue; + } break; + + case SPECIAL_AUTHORITY_SLASHES: + state = SPECIAL_AUTHORITY_IGNORE_SLASHES; + if (chr != '/' || buffer.charAt(pointer + 1) != '/') continue; + pointer++; + break; + + case SPECIAL_AUTHORITY_IGNORE_SLASHES: + if (chr != '/' && chr != '\\') { + state = AUTHORITY; + continue; + } break; + + case AUTHORITY: + if (chr == '@') { + if (seenAt) buffer = '%40' + buffer; + seenAt = true; + bufferCodePoints = arrayFrom(buffer); + for (var i = 0; i < bufferCodePoints.length; i++) { + var codePoint = bufferCodePoints[i]; + if (codePoint == ':' && !seenPasswordToken) { + seenPasswordToken = true; + continue; + } + var encodedCodePoints = percentEncode(codePoint, userinfoPercentEncodeSet); + if (seenPasswordToken) url.password += encodedCodePoints; + else url.username += encodedCodePoints; + } + buffer = ''; + } else if ( + chr == EOF || chr == '/' || chr == '?' || chr == '#' || + (chr == '\\' && isSpecial(url)) + ) { + if (seenAt && buffer == '') return INVALID_AUTHORITY; + pointer -= arrayFrom(buffer).length + 1; + buffer = ''; + state = HOST; + } else buffer += chr; + break; + + case HOST: + case HOSTNAME: + if (stateOverride && url.scheme == 'file') { + state = FILE_HOST; + continue; + } else if (chr == ':' && !seenBracket) { + if (buffer == '') return INVALID_HOST; + failure = parseHost(url, buffer); + if (failure) return failure; + buffer = ''; + state = PORT; + if (stateOverride == HOSTNAME) return; + } else if ( + chr == EOF || chr == '/' || chr == '?' || chr == '#' || + (chr == '\\' && isSpecial(url)) + ) { + if (isSpecial(url) && buffer == '') return INVALID_HOST; + if (stateOverride && buffer == '' && (includesCredentials(url) || url.port !== null)) return; + failure = parseHost(url, buffer); + if (failure) return failure; + buffer = ''; + state = PATH_START; + if (stateOverride) return; + continue; + } else { + if (chr == '[') seenBracket = true; + else if (chr == ']') seenBracket = false; + buffer += chr; + } break; + + case PORT: + if (DIGIT.test(chr)) { + buffer += chr; + } else if ( + chr == EOF || chr == '/' || chr == '?' || chr == '#' || + (chr == '\\' && isSpecial(url)) || + stateOverride + ) { + if (buffer != '') { + var port = parseInt(buffer, 10); + if (port > 0xFFFF) return INVALID_PORT; + url.port = (isSpecial(url) && port === specialSchemes[url.scheme]) ? null : port; + buffer = ''; + } + if (stateOverride) return; + state = PATH_START; + continue; + } else return INVALID_PORT; + break; + + case FILE: + url.scheme = 'file'; + if (chr == '/' || chr == '\\') state = FILE_SLASH; + else if (base && base.scheme == 'file') { + if (chr == EOF) { + url.host = base.host; + url.path = base.path.slice(); + url.query = base.query; + } else if (chr == '?') { + url.host = base.host; + url.path = base.path.slice(); + url.query = ''; + state = QUERY; + } else if (chr == '#') { + url.host = base.host; + url.path = base.path.slice(); + url.query = base.query; + url.fragment = ''; + state = FRAGMENT; + } else { + if (!startsWithWindowsDriveLetter(codePoints.slice(pointer).join(''))) { + url.host = base.host; + url.path = base.path.slice(); + shortenURLsPath(url); + } + state = PATH; + continue; + } + } else { + state = PATH; + continue; + } break; + + case FILE_SLASH: + if (chr == '/' || chr == '\\') { + state = FILE_HOST; + break; + } + if (base && base.scheme == 'file' && !startsWithWindowsDriveLetter(codePoints.slice(pointer).join(''))) { + if (isWindowsDriveLetter(base.path[0], true)) url.path.push(base.path[0]); + else url.host = base.host; + } + state = PATH; + continue; + + case FILE_HOST: + if (chr == EOF || chr == '/' || chr == '\\' || chr == '?' || chr == '#') { + if (!stateOverride && isWindowsDriveLetter(buffer)) { + state = PATH; + } else if (buffer == '') { + url.host = ''; + if (stateOverride) return; + state = PATH_START; + } else { + failure = parseHost(url, buffer); + if (failure) return failure; + if (url.host == 'localhost') url.host = ''; + if (stateOverride) return; + buffer = ''; + state = PATH_START; + } continue; + } else buffer += chr; + break; + + case PATH_START: + if (isSpecial(url)) { + state = PATH; + if (chr != '/' && chr != '\\') continue; + } else if (!stateOverride && chr == '?') { + url.query = ''; + state = QUERY; + } else if (!stateOverride && chr == '#') { + url.fragment = ''; + state = FRAGMENT; + } else if (chr != EOF) { + state = PATH; + if (chr != '/') continue; + } break; + + case PATH: + if ( + chr == EOF || chr == '/' || + (chr == '\\' && isSpecial(url)) || + (!stateOverride && (chr == '?' || chr == '#')) + ) { + if (isDoubleDot(buffer)) { + shortenURLsPath(url); + if (chr != '/' && !(chr == '\\' && isSpecial(url))) { + url.path.push(''); + } + } else if (isSingleDot(buffer)) { + if (chr != '/' && !(chr == '\\' && isSpecial(url))) { + url.path.push(''); + } + } else { + if (url.scheme == 'file' && !url.path.length && isWindowsDriveLetter(buffer)) { + if (url.host) url.host = ''; + buffer = buffer.charAt(0) + ':'; // normalize windows drive letter + } + url.path.push(buffer); + } + buffer = ''; + if (url.scheme == 'file' && (chr == EOF || chr == '?' || chr == '#')) { + while (url.path.length > 1 && url.path[0] === '') { + url.path.shift(); + } + } + if (chr == '?') { + url.query = ''; + state = QUERY; + } else if (chr == '#') { + url.fragment = ''; + state = FRAGMENT; + } + } else { + buffer += percentEncode(chr, pathPercentEncodeSet); + } break; + + case CANNOT_BE_A_BASE_URL_PATH: + if (chr == '?') { + url.query = ''; + state = QUERY; + } else if (chr == '#') { + url.fragment = ''; + state = FRAGMENT; + } else if (chr != EOF) { + url.path[0] += percentEncode(chr, C0ControlPercentEncodeSet); + } break; + + case QUERY: + if (!stateOverride && chr == '#') { + url.fragment = ''; + state = FRAGMENT; + } else if (chr != EOF) { + if (chr == "'" && isSpecial(url)) url.query += '%27'; + else if (chr == '#') url.query += '%23'; + else url.query += percentEncode(chr, C0ControlPercentEncodeSet); + } break; + + case FRAGMENT: + if (chr != EOF) url.fragment += percentEncode(chr, fragmentPercentEncodeSet); + break; + } + + pointer++; + } +}; + +// `URL` constructor +// https://url.spec.whatwg.org/#url-class +var URLConstructor = function URL(url /* , base */) { + var that = anInstance(this, URLConstructor, 'URL'); + var base = arguments.length > 1 ? arguments[1] : undefined; + var urlString = $toString(url); + var state = setInternalState(that, { type: 'URL' }); + var baseState, failure; + if (base !== undefined) { + if (base instanceof URLConstructor) baseState = getInternalURLState(base); + else { + failure = parseURL(baseState = {}, $toString(base)); + if (failure) throw TypeError(failure); + } + } + failure = parseURL(state, urlString, null, baseState); + if (failure) throw TypeError(failure); + var searchParams = state.searchParams = new URLSearchParams(); + var searchParamsState = getInternalSearchParamsState(searchParams); + searchParamsState.updateSearchParams(state.query); + searchParamsState.updateURL = function () { + state.query = String(searchParams) || null; + }; + if (!DESCRIPTORS) { + that.href = serializeURL.call(that); + that.origin = getOrigin.call(that); + that.protocol = getProtocol.call(that); + that.username = getUsername.call(that); + that.password = getPassword.call(that); + that.host = getHost.call(that); + that.hostname = getHostname.call(that); + that.port = getPort.call(that); + that.pathname = getPathname.call(that); + that.search = getSearch.call(that); + that.searchParams = getSearchParams.call(that); + that.hash = getHash.call(that); + } +}; + +var URLPrototype = URLConstructor.prototype; + +var serializeURL = function () { + var url = getInternalURLState(this); + var scheme = url.scheme; + var username = url.username; + var password = url.password; + var host = url.host; + var port = url.port; + var path = url.path; + var query = url.query; + var fragment = url.fragment; + var output = scheme + ':'; + if (host !== null) { + output += '//'; + if (includesCredentials(url)) { + output += username + (password ? ':' + password : '') + '@'; + } + output += serializeHost(host); + if (port !== null) output += ':' + port; + } else if (scheme == 'file') output += '//'; + output += url.cannotBeABaseURL ? path[0] : path.length ? '/' + path.join('/') : ''; + if (query !== null) output += '?' + query; + if (fragment !== null) output += '#' + fragment; + return output; +}; + +var getOrigin = function () { + var url = getInternalURLState(this); + var scheme = url.scheme; + var port = url.port; + if (scheme == 'blob') try { + return new URLConstructor(scheme.path[0]).origin; + } catch (error) { + return 'null'; + } + if (scheme == 'file' || !isSpecial(url)) return 'null'; + return scheme + '://' + serializeHost(url.host) + (port !== null ? ':' + port : ''); +}; + +var getProtocol = function () { + return getInternalURLState(this).scheme + ':'; +}; + +var getUsername = function () { + return getInternalURLState(this).username; +}; + +var getPassword = function () { + return getInternalURLState(this).password; +}; + +var getHost = function () { + var url = getInternalURLState(this); + var host = url.host; + var port = url.port; + return host === null ? '' + : port === null ? serializeHost(host) + : serializeHost(host) + ':' + port; +}; + +var getHostname = function () { + var host = getInternalURLState(this).host; + return host === null ? '' : serializeHost(host); +}; + +var getPort = function () { + var port = getInternalURLState(this).port; + return port === null ? '' : String(port); +}; + +var getPathname = function () { + var url = getInternalURLState(this); + var path = url.path; + return url.cannotBeABaseURL ? path[0] : path.length ? '/' + path.join('/') : ''; +}; + +var getSearch = function () { + var query = getInternalURLState(this).query; + return query ? '?' + query : ''; +}; + +var getSearchParams = function () { + return getInternalURLState(this).searchParams; +}; + +var getHash = function () { + var fragment = getInternalURLState(this).fragment; + return fragment ? '#' + fragment : ''; +}; + +var accessorDescriptor = function (getter, setter) { + return { get: getter, set: setter, configurable: true, enumerable: true }; +}; + +if (DESCRIPTORS) { + defineProperties(URLPrototype, { + // `URL.prototype.href` accessors pair + // https://url.spec.whatwg.org/#dom-url-href + href: accessorDescriptor(serializeURL, function (href) { + var url = getInternalURLState(this); + var urlString = $toString(href); + var failure = parseURL(url, urlString); + if (failure) throw TypeError(failure); + getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query); + }), + // `URL.prototype.origin` getter + // https://url.spec.whatwg.org/#dom-url-origin + origin: accessorDescriptor(getOrigin), + // `URL.prototype.protocol` accessors pair + // https://url.spec.whatwg.org/#dom-url-protocol + protocol: accessorDescriptor(getProtocol, function (protocol) { + var url = getInternalURLState(this); + parseURL(url, $toString(protocol) + ':', SCHEME_START); + }), + // `URL.prototype.username` accessors pair + // https://url.spec.whatwg.org/#dom-url-username + username: accessorDescriptor(getUsername, function (username) { + var url = getInternalURLState(this); + var codePoints = arrayFrom($toString(username)); + if (cannotHaveUsernamePasswordPort(url)) return; + url.username = ''; + for (var i = 0; i < codePoints.length; i++) { + url.username += percentEncode(codePoints[i], userinfoPercentEncodeSet); + } + }), + // `URL.prototype.password` accessors pair + // https://url.spec.whatwg.org/#dom-url-password + password: accessorDescriptor(getPassword, function (password) { + var url = getInternalURLState(this); + var codePoints = arrayFrom($toString(password)); + if (cannotHaveUsernamePasswordPort(url)) return; + url.password = ''; + for (var i = 0; i < codePoints.length; i++) { + url.password += percentEncode(codePoints[i], userinfoPercentEncodeSet); + } + }), + // `URL.prototype.host` accessors pair + // https://url.spec.whatwg.org/#dom-url-host + host: accessorDescriptor(getHost, function (host) { + var url = getInternalURLState(this); + if (url.cannotBeABaseURL) return; + parseURL(url, $toString(host), HOST); + }), + // `URL.prototype.hostname` accessors pair + // https://url.spec.whatwg.org/#dom-url-hostname + hostname: accessorDescriptor(getHostname, function (hostname) { + var url = getInternalURLState(this); + if (url.cannotBeABaseURL) return; + parseURL(url, $toString(hostname), HOSTNAME); + }), + // `URL.prototype.port` accessors pair + // https://url.spec.whatwg.org/#dom-url-port + port: accessorDescriptor(getPort, function (port) { + var url = getInternalURLState(this); + if (cannotHaveUsernamePasswordPort(url)) return; + port = $toString(port); + if (port == '') url.port = null; + else parseURL(url, port, PORT); + }), + // `URL.prototype.pathname` accessors pair + // https://url.spec.whatwg.org/#dom-url-pathname + pathname: accessorDescriptor(getPathname, function (pathname) { + var url = getInternalURLState(this); + if (url.cannotBeABaseURL) return; + url.path = []; + parseURL(url, $toString(pathname), PATH_START); + }), + // `URL.prototype.search` accessors pair + // https://url.spec.whatwg.org/#dom-url-search + search: accessorDescriptor(getSearch, function (search) { + var url = getInternalURLState(this); + search = $toString(search); + if (search == '') { + url.query = null; + } else { + if ('?' == search.charAt(0)) search = search.slice(1); + url.query = ''; + parseURL(url, search, QUERY); + } + getInternalSearchParamsState(url.searchParams).updateSearchParams(url.query); + }), + // `URL.prototype.searchParams` getter + // https://url.spec.whatwg.org/#dom-url-searchparams + searchParams: accessorDescriptor(getSearchParams), + // `URL.prototype.hash` accessors pair + // https://url.spec.whatwg.org/#dom-url-hash + hash: accessorDescriptor(getHash, function (hash) { + var url = getInternalURLState(this); + hash = $toString(hash); + if (hash == '') { + url.fragment = null; + return; + } + if ('#' == hash.charAt(0)) hash = hash.slice(1); + url.fragment = ''; + parseURL(url, hash, FRAGMENT); + }) + }); +} + +// `URL.prototype.toJSON` method +// https://url.spec.whatwg.org/#dom-url-tojson +redefine(URLPrototype, 'toJSON', function toJSON() { + return serializeURL.call(this); +}, { enumerable: true }); + +// `URL.prototype.toString` method +// https://url.spec.whatwg.org/#URL-stringification-behavior +redefine(URLPrototype, 'toString', function toString() { + return serializeURL.call(this); +}, { enumerable: true }); + +if (NativeURL) { + var nativeCreateObjectURL = NativeURL.createObjectURL; + var nativeRevokeObjectURL = NativeURL.revokeObjectURL; + // `URL.createObjectURL` method + // https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL + // eslint-disable-next-line no-unused-vars -- required for `.length` + if (nativeCreateObjectURL) redefine(URLConstructor, 'createObjectURL', function createObjectURL(blob) { + return nativeCreateObjectURL.apply(NativeURL, arguments); + }); + // `URL.revokeObjectURL` method + // https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL + // eslint-disable-next-line no-unused-vars -- required for `.length` + if (nativeRevokeObjectURL) redefine(URLConstructor, 'revokeObjectURL', function revokeObjectURL(url) { + return nativeRevokeObjectURL.apply(NativeURL, arguments); + }); +} + +setToStringTag(URLConstructor, 'URL'); + +$({ global: true, forced: !USE_NATIVE_URL, sham: !DESCRIPTORS }, { + URL: URLConstructor +}); + + +/***/ }), + +/***/ 9801: +/***/ (function() { + +// empty + + +/***/ }), + +/***/ 3822: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(2221); + +module.exports = parent; + + +/***/ }), + +/***/ 1434: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(5078); + +module.exports = parent; + + +/***/ }), + +/***/ 6899: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(98); + +module.exports = parent; + + +/***/ }), + +/***/ 7710: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(5739); +__webpack_require__(162); + +module.exports = parent; + + +/***/ }), + +/***/ 4486: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(278); + +module.exports = parent; + + +/***/ }), + +/***/ 4877: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(1484); + +module.exports = parent; + + +/***/ }), + +/***/ 7178: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(7731); + +module.exports = parent; + + +/***/ }), + +/***/ 5603: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3669); + +module.exports = parent; + + +/***/ }), + +/***/ 1206: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(162); +var forEach = __webpack_require__(6899); +var classof = __webpack_require__(4696); +var ArrayPrototype = Array.prototype; + +var DOMIterables = { + DOMTokenList: true, + NodeList: true +}; + +module.exports = function (it) { + var own = it.forEach; + return it === ArrayPrototype || (it instanceof Array && own === ArrayPrototype.forEach) + // eslint-disable-next-line no-prototype-builtins -- safe + || DOMIterables.hasOwnProperty(classof(it)) ? forEach : own; +}; + + +/***/ }), + +/***/ 6174: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(2604); + +module.exports = parent; + + +/***/ }), + +/***/ 57: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(263); + +module.exports = parent; + + +/***/ }), + +/***/ 4741: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(7663); + +module.exports = parent; + + +/***/ }), + +/***/ 8368: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(5063); + +module.exports = parent; + + +/***/ }), + +/***/ 3739: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(6813); + +module.exports = parent; + + +/***/ }), + +/***/ 172: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(6285); + +module.exports = parent; + + +/***/ }), + +/***/ 4963: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3213); + +module.exports = parent; + + +/***/ }), + +/***/ 7820: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3512); + +module.exports = parent; + + +/***/ }), + +/***/ 8980: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(8168); + +module.exports = parent; + + +/***/ }), + +/***/ 5636: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(8651); + +module.exports = parent; + + +/***/ }), + +/***/ 6672: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3083); + +module.exports = parent; + + +/***/ }), + +/***/ 5059: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(2987); + +module.exports = parent; + + +/***/ }), + +/***/ 3969: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(2239); + +module.exports = parent; + + +/***/ }), + +/***/ 6618: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(3154); +__webpack_require__(162); + +module.exports = parent; + + +/***/ }), + +/***/ 5279: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(6577); + +module.exports = parent; + + +/***/ }), + +/***/ 9562: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(2906); +var path = __webpack_require__(7545); + +module.exports = path.setTimeout; + + +/***/ }), + +/***/ 2285: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(5008); +__webpack_require__(162); + +module.exports = parent; + + +/***/ }), + +/***/ 8535: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(994); +__webpack_require__(162); + +module.exports = parent; + + +/***/ }), + +/***/ 652: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +var parent = __webpack_require__(5668); + +module.exports = parent; + + +/***/ }), + +/***/ 5668: +/***/ (function(module, __unused_webpack_exports, __webpack_require__) { + +__webpack_require__(4948); +__webpack_require__(9801); +__webpack_require__(9336); +var path = __webpack_require__(7545); + +module.exports = path.URL; + + +/***/ }), + +/***/ 2534: +/***/ (function(module) { + +"use strict"; +module.exports = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyBkaXNwbGF5PSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGRlZnMgaWQ9InR1aS1pbWFnZS1lZGl0b3Itc3ZnLWRlZmF1bHQtaWNvbnMiPgo8c3ltYm9sIGlkPSJpYy1hcHBseSIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBkPSJNNCAxMi4wMTFsNSA1TDIwLjAxMSA2Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1jYW5jZWwiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgZD0iTTYgNmwxMiAxMk0xOCA2TDYgMTgiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWNyb3AiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIC8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik00IDBoMXYyMGExIDEgMCAwIDEtMS0xVjB6TTIwIDE3aC0xVjVoMXYxMnptMCAydjVoLTF2LTVoMXoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTUgMTloMTl2MUg1ek00Ljc2MiA0djFIMFY0aDQuNzYyek03IDRoMTJhMSAxIDAgMCAxIDEgMUg3VjR6Ii8+Cjwvc3ltYm9sPgo8IS0tIFRoaXMgaWNvbiBtYWRlIGJ5IFBpeGVsIHBlcmZlY3QgZnJvbSB3d3cuZmxhdGljb24uY29tIC0tPgo8c3ltYm9sIGlkPSJpYy1yZXNpemUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgc3Ryb2tlPSJub25lIiBmaWxsPSJub25lIi8+CiAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNIDE4Ljk4ODI4MSAzLjAxMTcxOSBDIDE4LjgwMDc4MSAyLjgyNDIxOSAxOC41IDIuODI0MjE5IDE4LjMxMjUgMy4wMTE3MTkgTCAxMS42MjEwOTQgOS43MDcwMzEgQyAxMS40Mjk2ODggOS44OTQ1MzEgMTEuNDI5Njg4IDEwLjE5NTMxMiAxMS42MjEwOTQgMTAuMzc4OTA2IEMgMTEuNzEwOTM4IDEwLjQ3MjY1NiAxMS44MzU5MzggMTAuNTE5NTMxIDExLjk1NzAzMSAxMC41MTk1MzEgQyAxMi4wNzgxMjUgMTAuNTE5NTMxIDEyLjIwMzEyNSAxMC40NzI2NTYgMTIuMjkyOTY5IDEwLjM3ODkwNiBMIDE4Ljk4ODI4MSAzLjY4NzUgQyAxOS4xNzU3ODEgMy41IDE5LjE3NTc4MSAzLjE5OTIxOSAxOC45ODgyODEgMy4wMTE3MTkgWiBNIDE4Ljk4ODI4MSAzLjAxMTcxOSAiLz4KICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0gMTguNjUyMzQ0IDIuODY3MTg4IEMgMTguMzg2NzE5IDIuODY3MTg4IDE4LjE3MTg3NSAzLjA4MjAzMSAxOC4xNzE4NzUgMy4zNDc2NTYgTCAxOC4xNzE4NzUgOS4wODU5MzggQyAxOC4xNzE4NzUgOS4zNDc2NTYgMTguMzg2NzE5IDkuNTYyNSAxOC42NTIzNDQgOS41NjI1IEMgMTguOTE3OTY5IDkuNTYyNSAxOS4xMzI4MTIgOS4zNDc2NTYgMTkuMTMyODEyIDkuMDg1OTM4IEwgMTkuMTMyODEyIDMuMzQ3NjU2IEMgMTkuMTMyODEyIDMuMDgyMDMxIDE4LjkxNzk2OSAyLjg2NzE4OCAxOC42NTIzNDQgMi44NjcxODggWiBNIDE4LjY1MjM0NCAyLjg2NzE4OCAiLz4KICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0gMTguNjUyMzQ0IDIuODY3MTg4IEwgMTIuOTE0MDYyIDIuODY3MTg4IEMgMTIuNjUyMzQ0IDIuODY3MTg4IDEyLjQzNzUgMy4wODIwMzEgMTIuNDM3NSAzLjM0NzY1NiBDIDEyLjQzNzUgMy42MTMyODEgMTIuNjUyMzQ0IDMuODI4MTI1IDEyLjkxNDA2MiAzLjgyODEyNSBMIDE4LjY1MjM0NCAzLjgyODEyNSBDIDE4LjkxNzk2OSAzLjgyODEyNSAxOS4xMzI4MTIgMy42MTMyODEgMTkuMTMyODEyIDMuMzQ3NjU2IEMgMTkuMTMyODEyIDMuMDgyMDMxIDE4LjkxNzk2OSAyLjg2NzE4OCAxOC42NTIzNDQgMi44NjcxODggWiBNIDE4LjY1MjM0NCAyLjg2NzE4OCAiLz4KICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0gMTAuMzc4OTA2IDExLjYyMTA5NCBDIDEwLjE5NTMxMiAxMS40MzM1OTQgOS44OTA2MjUgMTEuNDMzNTk0IDkuNzAzMTI1IDExLjYyMTA5NCBMIDMuMDA3ODEyIDE4LjMxNjQwNiBDIDIuODIwMzEyIDE4LjUgMi44MjAzMTIgMTguODA0Njg4IDMuMDA3ODEyIDE4Ljk5MjE4OCBDIDMuMTA1NDY5IDE5LjA4NTkzOCAzLjIyNjU2MiAxOS4xMzI4MTIgMy4zNDc2NTYgMTkuMTMyODEyIEMgMy40Njg3NSAxOS4xMzI4MTIgMy41ODk4NDQgMTkuMDg1OTM4IDMuNjgzNTk0IDE4Ljk5MjE4OCBMIDEwLjM3ODkwNiAxMi4yOTY4NzUgQyAxMC41NjY0MDYgMTIuMTA5Mzc1IDEwLjU2NjQwNiAxMS44MDQ2ODggMTAuMzc4OTA2IDExLjYyMTA5NCBaIE0gMTAuMzc4OTA2IDExLjYyMTA5NCAiLz4KICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0gMy4zNDc2NTYgMTIuNDM3NSBDIDMuMDgyMDMxIDEyLjQzNzUgMi44NjcxODggMTIuNjUyMzQ0IDIuODY3MTg4IDEyLjkxNDA2MiBMIDIuODY3MTg4IDE4LjY1MjM0NCBDIDIuODY3MTg4IDE4LjkxNzk2OSAzLjA4MjAzMSAxOS4xMzI4MTIgMy4zNDc2NTYgMTkuMTMyODEyIEMgMy42MTMyODEgMTkuMTMyODEyIDMuODI4MTI1IDE4LjkxNzk2OSAzLjgyODEyNSAxOC42NTIzNDQgTCAzLjgyODEyNSAxMi45MTQwNjIgQyAzLjgyODEyNSAxMi42NTIzNDQgMy42MTMyODEgMTIuNDM3NSAzLjM0NzY1NiAxMi40Mzc1IFogTSAzLjM0NzY1NiAxMi40Mzc1ICIvPgogIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTSA5LjA4NTkzOCAxOC4xNzE4NzUgTCAzLjM0NzY1NiAxOC4xNzE4NzUgQyAzLjA4MjAzMSAxOC4xNzE4NzUgMi44NjcxODggMTguMzg2NzE5IDIuODY3MTg4IDE4LjY1MjM0NCBDIDIuODY3MTg4IDE4LjkxNzk2OSAzLjA4MjAzMSAxOS4xMzI4MTIgMy4zNDc2NTYgMTkuMTMyODEyIEwgOS4wODU5MzggMTkuMTMyODEyIEMgOS4zNDc2NTYgMTkuMTMyODEyIDkuNTYyNSAxOC45MTc5NjkgOS41NjI1IDE4LjY1MjM0NCBDIDkuNTYyNSAxOC4zODY3MTkgOS4zNDc2NTYgMTguMTcxODc1IDkuMDg1OTM4IDE4LjE3MTg3NSBaIE0gOS4wODU5MzggMTguMTcxODc1ICIvPgogIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTSAyMC41NjI1IDAgTCAxLjQzNzUgMCBDIDAuNjQ0NTMxIDAgMCAwLjY0NDUzMSAwIDEuNDM3NSBMIDAgMjAuNTYyNSBDIDAgMjEuMzU1NDY5IDAuNjQ0NTMxIDIyIDEuNDM3NSAyMiBMIDIwLjU2MjUgMjIgQyAyMS4zNTU0NjkgMjIgMjIgMjEuMzU1NDY5IDIyIDIwLjU2MjUgTCAyMiAxLjQzNzUgQyAyMiAwLjY0NDUzMSAyMS4zNTU0NjkgMCAyMC41NjI1IDAgWiBNIDIxLjA0Mjk2OSAyMC41NjI1IEMgMjEuMDQyOTY5IDIwLjgyODEyNSAyMC44MjgxMjUgMjEuMDQyOTY5IDIwLjU2MjUgMjEuMDQyOTY5IEwgMS40Mzc1IDIxLjA0Mjk2OSBDIDEuMTcxODc1IDIxLjA0Mjk2OSAwLjk1NzAzMSAyMC44MjgxMjUgMC45NTcwMzEgMjAuNTYyNSBMIDAuOTU3MDMxIDEuNDM3NSBDIDAuOTU3MDMxIDEuMTcxODc1IDEuMTcxODc1IDAuOTU3MDMxIDEuNDM3NSAwLjk1NzAzMSBMIDIwLjU2MjUgMC45NTcwMzEgQyAyMC44MjgxMjUgMC45NTcwMzEgMjEuMDQyOTY5IDEuMTcxODc1IDIxLjA0Mjk2OSAxLjQzNzUgWiBNIDIxLjA0Mjk2OSAyMC41NjI1ICIvPgo8L3N5bWJvbD4KPCEtLSAgLS0+CjxzeW1ib2wgaWQ9ImljLWRlbGV0ZS1hbGwiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik01IDIzSDNhMSAxIDAgMCAxLTEtMVY2aDF2MTZoMnYxem0xNi0xMGgtMVY2aDF2N3pNOSAxM0g4di0zaDF2M3ptMyAwaC0xdi0zaDF2M3ptMyAwaC0xdi0zaDF2M3pNMTQuNzk0IDMuNzk0TDEzIDJoLTNMOC4yMDYgMy43OTRBLjk2My45NjMgMCAwIDEgOCAyLjVsLjcwMy0xLjA1NUExIDEgMCAwIDEgOS41MzUgMWgzLjkzYTEgMSAwIDAgMSAuODMyLjQ0NUwxNSAyLjVhLjk2NS45NjUgMCAwIDEtLjIwNiAxLjI5NHpNMTQuMTk3IDRIOC44MDNoNS4zOTR6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0wIDNoMjN2MUgwek0xMS4yODYgMjFIOC43MTRMOCAyM0g3bDEtMi44VjIwaC4wNzFMOS41IDE2aDFsMS40MjkgNEgxMnYuMmwxIDIuOGgtMWwtLjcxNC0yem0tLjM1Ny0xTDEwIDE3LjQgOS4wNzEgMjBoMS44NTh6TTIwIDIyaDN2MWgtNHYtN2gxdjZ6bS01IDBoM3YxaC00di03aDF2NnoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWRlbGV0ZSIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTMgNnYxNmgxN1Y2aDF2MTZhMSAxIDAgMCAxLTEgMUgzYTEgMSAwIDAgMS0xLTFWNmgxek0xNC43OTQgMy43OTRMMTMgMmgtM0w4LjIwNiAzLjc5NEEuOTYzLjk2MyAwIDAgMSA4IDIuNWwuNzAzLTEuMDU1QTEgMSAwIDAgMSA5LjUzNSAxaDMuOTNhMSAxIDAgMCAxIC44MzIuNDQ1TDE1IDIuNWEuOTY1Ljk2NSAwIDAgMS0uMjA2IDEuMjk0ek0xNC4xOTcgNEg4LjgwM2g1LjM5NHoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTAgM2gyM3YxSDB6TTggMTBoMXY2SDh2LTZ6bTMgMGgxdjZoLTF2LTZ6bTMgMGgxdjZoLTF2LTZ6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1kcmF3LWZyZWUiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIGQ9Ik0yLjUgMjAuOTI5QzIuNTk0IDEwLjk3NiA0LjMyMyA2IDcuNjg2IDZjNS44NzIgMCAyLjUyNCAxOSA3LjY5NyAxOXMxLjg5LTE0LjkyOSA2LjQxNC0xNC45MjkgMS4zNTcgMTAuODU4IDUuMTMgMTAuODU4YzEuODAyIDAgMi42NTctMi4yNjIgMi41NjYtNi43ODYiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWRyYXctbGluZSIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgZD0iTTIgMTUuNWgyOCIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtZHJhdyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgZD0iTTIuNSAyMS41SDVjLjI0NSAwIC40OC0uMDU4LjY5MS0uMTY4bC4xMjQtLjA2NS4xNC4wMWMuNDI5LjAyOC44NS0uMTI3IDEuMTYtLjQzN0wyMi41NSA1LjQwNWEuNS41IDAgMCAwIDAtLjcwN2wtMy4yNDYtMy4yNDVhLjUuNSAwIDAgMC0uNzA3IDBMMy4xNjIgMTYuODg4YTEuNDk1IDEuNDk1IDAgMCAwLS40MzcgMS4xNTVsLjAxLjE0LS4wNjUuMTIzYy0uMTExLjIxMi0uMTcuNDQ4LS4xNy42OTR2Mi41eiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMTYuNDE0IDMuNzA3bDMuODkgMy44OS0uNzA4LjcwNi0zLjg4OS0zLjg4OXoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWZpbHRlciIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTEyIDd2MUgyVjdoMTB6bTYgMGg0djFoLTRWN3pNMTIgMTZ2MWgxMHYtMUgxMnptLTYgMEgydjFoNHYtMXoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTguNSAyMGEzLjUgMy41IDAgMSAxIDAtNyAzLjUgMy41IDAgMCAxIDAgN3ptMC0xYTIuNSAyLjUgMCAxIDAgMC01IDIuNSAyLjUgMCAwIDAgMCA1ek0xNS41IDExYTMuNSAzLjUgMCAxIDEgMC03IDMuNSAzLjUgMCAwIDEgMCA3em0wLTFhMi41IDIuNSAwIDEgMCAwLTUgMi41IDIuNSAwIDAgMCAwIDV6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1mbGlwLXJlc2V0IiB2aWV3Qm94PSIwIDAgMzEgMzIiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBkPSJNMzEgMEgwdjMyaDMxeiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMjggMTZhOCA4IDAgMCAxLTggOEgzdi0xaDF2LTdIM2E4IDggMCAwIDEgOC04aDE3djFoLTF2N2gxek0xMSA5YTcgNyAwIDAgMC03IDd2N2gxNmE3IDcgMCAwIDAgNy03VjlIMTF6Ii8+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIGQ9Ik0yNCA1bDMuNSAzLjVMMjQgMTJNNyAyMGwtMy41IDMuNUw3IDI3Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1mbGlwLXgiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGQ9Ik0zMiAzMkgwVjBoMzJ6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0xNyAzMmgtMVYwaDF6TTI3LjE2NyAxMWwuNSAzaC0xLjAzbC0uNTQ2LTNoMS4wNzZ6bS0uNS0zaC0xLjEyMkwyNSA1aC01VjRoNS4xNTNhMSAxIDAgMCAxIC45ODYuODM2TDI2LjY2NyA4em0xLjUgOWwuNSAzaC0uOTRsLS41NDUtM2guOTg1em0xIDZsLjYzOSAzLjgzNkExIDEgMCAwIDEgMjguODE5IDI4SDI2di0xaDNsLS43MjYtNGguODk0ek0yMyAyOGgtM3YtMWgzdjF6TTEzIDR2MUg3TDMgMjdoMTB2MUgzLjE4YTEgMSAwIDAgMS0uOTg2LTEuMTY0bDMuNjY2LTIyQTEgMSAwIDAgMSA2Ljg0NyA0SDEzeiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtZmxpcC15IiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBkPSJNMCAwdjMyaDMyVjB6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0wIDE2djFoMzJ2LTF6TTExIDI3LjE2N2wzIC41di0xLjAzbC0zLS41NDZ2MS4wNzZ6bS0zLS41di0xLjEyMkw1IDI1di01SDR2NS4xNTNhMSAxIDAgMCAwIC44MzYuOTg2TDggMjYuNjY3em05IDEuNWwzIC41di0uOTRsLTMtLjU0NXYuOTg1em02IDFsMy44MzYuNjM5QTEgMSAwIDAgMCAyOCAyOC44MlYyNmgtMXYzbC00LS43Mjd2Ljg5NHpNMjggMjN2LTNoLTF2M2gxek00IDEzaDFWN2wyMi00djEwaDFWMy4xOGExIDEgMCAwIDAtMS4xNjQtLjk4NmwtMjIgMy42NjdBMSAxIDAgMCAwIDQgNi44NDdWMTN6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1mbGlwIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiAvPgogICAgPHBhdGggZmlsbD0iaW5oZXJpdCIgc3Ryb2tlPSJub25lIiBkPSJNMTEgMGgxdjI0aC0xek0xOSAyMXYtMWgydi0yaDF2MmExIDEgMCAwIDEtMSAxaC0yem0tMiAwaC0zdi0xaDN2MXptNS01aC0xdi0zaDF2M3ptMC01aC0xVjhoMXYzem0wLTVoLTFWNGgtMlYzaDJhMSAxIDAgMCAxIDEgMXYyem0tNS0zdjFoLTNWM2gzek05IDN2MUgydjE2aDd2MUgyYTEgMSAwIDAgMS0xLTFWNGExIDEgMCAwIDEgMS0xaDd6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5IiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBkPSJNMCAwSDI0VjI0SDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNzQwIC0xNikgdHJhbnNsYXRlKDU0NyA4KSB0cmFuc2xhdGUoMTkzIDgpIi8+CiAgICA8cGF0aCBmaWxsPSJpbmhlcml0IiBzdHJva2U9Im5vbmUiIGQ9Ik0xMi41IDFDMTguMjk5IDEgMjMgNS43MDEgMjMgMTEuNVMxOC4yOTkgMjIgMTIuNSAyMmMtNS4yOSAwLTkuNjY1LTMuOTExLTEwLjM5NC04Ljk5OWgxLjAxMkMzLjgzOCAxNy41MzQgNy43NjQgMjEgMTIuNSAyMWM1LjI0NyAwIDkuNS00LjI1MyA5LjUtOS41UzE3Ljc0NyAyIDEyLjUgMkM4LjQ5IDIgNS4wNiA0LjQ4NSAzLjY2NiA4SDNoNHYxSDJWNGgxdjMuMDIyQzQuNjggMy40NjIgOC4zMDMgMSAxMi41IDF6bS41IDVsLS4wMDEgNS4yOTEgMi41MzcgMi41MzctLjcwOC43MDhMMTIuMjkyIDEySDEyVjZoMXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC03NDAgLTE2KSB0cmFuc2xhdGUoNTQ3IDgpIHRyYW5zbGF0ZSgxOTMgOCkiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWhpc3RvcnktY2hlY2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiID4KICAgICAgICA8cGF0aCBzdHJva2U9IiM1NTU1NTUiIGQ9Ik00LjUgLTFMMS41IDIgNi41IDciIHRyYW5zZm9ybT0idHJhbnNsYXRlKC02MCAtODA0KSB0cmFuc2xhdGUoNjAgODA0KSB0cmFuc2xhdGUoMiAzKSByb3RhdGUoLTkwIDQgMykiIC8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LWNyb3AiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxwYXRoIGQ9Ik0wIDBIMTJWMTJIMHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04NCAtODA0KSB0cmFuc2xhdGUoODQgODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0yIDBoMXYxMGMtLjU1MiAwLTEtLjQ0OC0xLTFWMHpNMTAgOXYzSDlWOWgxek05IDJoMXY2SDlWMnoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC04NCAtODA0KSB0cmFuc2xhdGUoODQgODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0yIDlIMTJWMTBIMnpNOSAyYy41MTMgMCAuOTM2LjM4Ni45OTMuODgzTDEwIDNIM1YyaDZ6TTIgM0gwVjJoMnYxeiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTg0IC04MDQpIHRyYW5zbGF0ZSg4NCA4MDQpIi8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8IS0tIFRoaXMgaWNvbiBtYWRlIGJ5IFBpeGVsIHBlcmZlY3QgZnJvbSB3d3cuZmxhdGljb24uY29tIC0tPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LXJlc2l6ZSIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTSA5LjQ5MjE4OCAxLjUwNzgxMiBDIDkuMzk4NDM4IDEuNDE0MDYyIDkuMjUgMS40MTQwNjIgOS4xNTYyNSAxLjUwNzgxMiBMIDUuODEyNSA0Ljg1MTU2MiBDIDUuNzE0ODQ0IDQuOTQ1MzEyIDUuNzE0ODQ0IDUuMDk3NjU2IDUuODEyNSA1LjE4NzUgQyA1Ljg1NTQ2OSA1LjIzNDM3NSA1LjkxNzk2OSA1LjI1NzgxMiA1Ljk3NjU2MiA1LjI1NzgxMiBDIDYuMDM5MDYyIDUuMjU3ODEyIDYuMTAxNTYyIDUuMjM0Mzc1IDYuMTQ4NDM4IDUuMTg3NSBMIDkuNDkyMTg4IDEuODQzNzUgQyA5LjU4NTkzOCAxLjc1IDkuNTg1OTM4IDEuNjAxNTYyIDkuNDkyMTg4IDEuNTA3ODEyIFogTSA5LjQ5MjE4OCAxLjUwNzgxMiAiLz4KICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0gOS4zMjgxMjUgMS40MzM1OTQgQyA5LjE5NTMxMiAxLjQzMzU5NCA5LjA4NTkzOCAxLjUzOTA2MiA5LjA4NTkzOCAxLjY3MTg3NSBMIDkuMDg1OTM4IDQuNTQyOTY5IEMgOS4wODU5MzggNC42NzE4NzUgOS4xOTUzMTIgNC43ODEyNSA5LjMyODEyNSA0Ljc4MTI1IEMgOS40NjA5MzggNC43ODEyNSA5LjU2NjQwNiA0LjY3MTg3NSA5LjU2NjQwNiA0LjU0Mjk2OSBMIDkuNTY2NDA2IDEuNjcxODc1IEMgOS41NjY0MDYgMS41MzkwNjIgOS40NjA5MzggMS40MzM1OTQgOS4zMjgxMjUgMS40MzM1OTQgWiBNIDkuMzI4MTI1IDEuNDMzNTk0ICIvPgogICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTSA5LjMyODEyNSAxLjQzMzU5NCBMIDYuNDU3MDMxIDEuNDMzNTk0IEMgNi4zMjgxMjUgMS40MzM1OTQgNi4yMTg3NSAxLjUzOTA2MiA2LjIxODc1IDEuNjcxODc1IEMgNi4yMTg3NSAxLjgwNDY4OCA2LjMyODEyNSAxLjkxNDA2MiA2LjQ1NzAzMSAxLjkxNDA2MiBMIDkuMzI4MTI1IDEuOTE0MDYyIEMgOS40NjA5MzggMS45MTQwNjIgOS41NjY0MDYgMS44MDQ2ODggOS41NjY0MDYgMS42NzE4NzUgQyA5LjU2NjQwNiAxLjUzOTA2MiA5LjQ2MDkzOCAxLjQzMzU5NCA5LjMyODEyNSAxLjQzMzU5NCBaIE0gOS4zMjgxMjUgMS40MzM1OTQgIi8+CiAgICA8cGF0aCBmaWxsPSIjNDM0MzQzIiBkPSJNIDUuMTg3NSA1LjgxMjUgQyA1LjA5NzY1NiA1LjcxODc1IDQuOTQ1MzEyIDUuNzE4NzUgNC44NTE1NjIgNS44MTI1IEwgMS41MDM5MDYgOS4xNTYyNSBDIDEuNDEwMTU2IDkuMjUgMS40MTAxNTYgOS40MDIzNDQgMS41MDM5MDYgOS40OTYwOTQgQyAxLjU1NDY4OCA5LjU0Mjk2OSAxLjYxMzI4MSA5LjU2NjQwNiAxLjY3MTg3NSA5LjU2NjQwNiBDIDEuNzM0Mzc1IDkuNTY2NDA2IDEuNzk2ODc1IDkuNTQyOTY5IDEuODQzNzUgOS40OTYwOTQgTCA1LjE4NzUgNi4xNDg0MzggQyA1LjI4MTI1IDYuMDU0Njg4IDUuMjgxMjUgNS45MDIzNDQgNS4xODc1IDUuODEyNSBaIE0gNS4xODc1IDUuODEyNSAiLz4KICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0gMS42NzE4NzUgNi4yMTg3NSBDIDEuNTM5MDYyIDYuMjE4NzUgMS40MzM1OTQgNi4zMjgxMjUgMS40MzM1OTQgNi40NTcwMzEgTCAxLjQzMzU5NCA5LjMyODEyNSBDIDEuNDMzNTk0IDkuNDYwOTM4IDEuNTM5MDYyIDkuNTY2NDA2IDEuNjcxODc1IDkuNTY2NDA2IEMgMS44MDQ2ODggOS41NjY0MDYgMS45MTQwNjIgOS40NjA5MzggMS45MTQwNjIgOS4zMjgxMjUgTCAxLjkxNDA2MiA2LjQ1NzAzMSBDIDEuOTE0MDYyIDYuMzI4MTI1IDEuODA0Njg4IDYuMjE4NzUgMS42NzE4NzUgNi4yMTg3NSBaIE0gMS42NzE4NzUgNi4yMTg3NSAiLz4KICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0gNC41NDI5NjkgOS4wODU5MzggTCAxLjY3MTg3NSA5LjA4NTkzOCBDIDEuNTM5MDYyIDkuMDg1OTM4IDEuNDMzNTk0IDkuMTk1MzEyIDEuNDMzNTk0IDkuMzI4MTI1IEMgMS40MzM1OTQgOS40NjA5MzggMS41MzkwNjIgOS41NjY0MDYgMS42NzE4NzUgOS41NjY0MDYgTCA0LjU0Mjk2OSA5LjU2NjQwNiBDIDQuNjcxODc1IDkuNTY2NDA2IDQuNzgxMjUgOS40NjA5MzggNC43ODEyNSA5LjMyODEyNSBDIDQuNzgxMjUgOS4xOTUzMTIgNC42NzE4NzUgOS4wODU5MzggNC41NDI5NjkgOS4wODU5MzggWiBNIDQuNTQyOTY5IDkuMDg1OTM4ICIvPgogICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTSAxMC4yODEyNSAwIEwgMC43MTg3NSAwIEMgMC4zMjAzMTIgMCAwIDAuMzIwMzEyIDAgMC43MTg3NSBMIDAgMTAuMjgxMjUgQyAwIDEwLjY3OTY4OCAwLjMyMDMxMiAxMSAwLjcxODc1IDExIEwgMTAuMjgxMjUgMTEgQyAxMC42Nzk2ODggMTEgMTEgMTAuNjc5Njg4IDExIDEwLjI4MTI1IEwgMTEgMC43MTg3NSBDIDExIDAuMzIwMzEyIDEwLjY3OTY4OCAwIDEwLjI4MTI1IDAgWiBNIDEwLjUyMzQzOCAxMC4yODEyNSBDIDEwLjUyMzQzOCAxMC40MTQwNjIgMTAuNDE0MDYyIDEwLjUyMzQzOCAxMC4yODEyNSAxMC41MjM0MzggTCAwLjcxODc1IDEwLjUyMzQzOCBDIDAuNTg1OTM4IDEwLjUyMzQzOCAwLjQ3NjU2MiAxMC40MTQwNjIgMC40NzY1NjIgMTAuMjgxMjUgTCAwLjQ3NjU2MiAwLjcxODc1IEMgMC40NzY1NjIgMC41ODU5MzggMC41ODU5MzggMC40NzY1NjIgMC43MTg3NSAwLjQ3NjU2MiBMIDEwLjI4MTI1IDAuNDc2NTYyIEMgMTAuNDE0MDYyIDAuNDc2NTYyIDEwLjUyMzQzOCAwLjU4NTkzOCAxMC41MjM0MzggMC43MTg3NSBaIE0gMTAuNTIzNDM4IDEwLjI4MTI1ICIvPgogIDwvZz4KPC9zeW1ib2w+CjwhLS0gIC0tPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LWRyYXciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxwYXRoIGQ9Ik0wIDFIMTJWMTNIMHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNTYgLTgwNCkgdHJhbnNsYXRlKDE1NiA4MDMpIi8+CiAgICAgICAgPHBhdGggc3Ryb2tlPSIjNDM0MzQzIiBkPSJNOS42MjIgMS41ODRsMS44MzUgMS42NTgtOC4zMSA4LjQwN0wuNSAxMi41VjExbDkuMTIyLTkuNDE2eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE1NiAtODA0KSB0cmFuc2xhdGUoMTU2IDgwMykiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjNDM0MzQzIiBkPSJNNy42MjggMy43NTNMMTAuMzc4IDMuNzUzIDEwLjM3OCA0LjI1MyA3LjYyOCA0LjI1M3oiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNTYgLTgwNCkgdHJhbnNsYXRlKDE1NiA4MDMpIHJvdGF0ZSg0NSA5LjAwMyA0LjAwMykiLz4KICAgIDwvZz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWhpc3RvcnktZmlsdGVyIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiID4KICAgICAgICA8cGF0aCBkPSJNMCAwSDEyVjEySDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjc2IC04MDQpIHRyYW5zbGF0ZSgyNzYgODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0xMiAzdjFIOVYzaDN6TTcgNEgwVjNoN3YxeiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI3NiAtODA0KSB0cmFuc2xhdGUoMjc2IDgwNCkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjNDM0MzQzIiBkPSJNMTIgOHYxSDlWOGgzek03IDlIMFY4aDd2MXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNzYgLTgwNCkgdHJhbnNsYXRlKDI3NiA4MDQpIG1hdHJpeCgtMSAwIDAgMSAxMiAwKSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik04IDFjMS4xMDUgMCAyIC44OTUgMiAycy0uODk1IDItMiAyLTItLjg5NS0yLTIgLjg5NS0yIDItMnptMCAxYy0uNTUyIDAtMSAuNDQ4LTEgMXMuNDQ4IDEgMSAxIDEtLjQ0OCAxLTEtLjQ0OC0xLTEtMXpNNCA3YzEuMTA1IDAgMiAuODk1IDIgMnMtLjg5NSAyLTIgMi0yLS44OTUtMi0yIC44OTUtMiAyLTJ6bTAgMWMtLjU1MiAwLTEgLjQ0OC0xIDFzLjQ0OCAxIDEgMSAxLS40NDggMS0xLS40NDgtMS0xLTF6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjc2IC04MDQpIHRyYW5zbGF0ZSgyNzYgODA0KSIvPgogICAgPC9nPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaGlzdG9yeS1mbGlwIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiID4KICAgICAgICA8cGF0aCBkPSJNMCAwSDEyVjEySDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTA4IC04MDQpIHRyYW5zbGF0ZSgxMDggODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik02IDBMNyAwIDcgMTIgNiAxMnpNMTEgMTBWOWgxdjEuNWMwIC4yNzYtLjIyNC41LS41LjVIMTB2LTFoMXpNNSAxdjFIMXY4aDR2MUguNWMtLjI3NiAwLS41LS4yMjQtLjUtLjV2LTljMC0uMjc2LjIyNC0uNS41LS41SDV6bTcgNXYyaC0xVjZoMXptMC0zdjJoLTFWM2gxek05IDF2MUg3VjFoMnptMi41IDBjLjI3NiAwIC41LjIyNC41LjVWMmgtMlYxaDEuNXpNOSAxMUg3di0xaDJ2MXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMDggLTgwNCkgdHJhbnNsYXRlKDEwOCA4MDQpIi8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LWljb24iIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxwYXRoIGQ9Ik0wIDBIMTJWMTJIMHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMDQgLTgwNCkgdHJhbnNsYXRlKDIwNCA4MDQpIi8+CiAgICAgICAgPHBhdGggc3Ryb2tlPSIjNDM0MzQzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMS4xIiBkPSJNNiA5LjU2OEwyLjYwMSAxMSAyLjk3NSA3LjQ2NyAwLjUgNC44MiA0LjEzIDQuMDY4IDYgMSA3Ljg3IDQuMDY4IDExLjUgNC44MiA5LjAyNSA3LjQ2NyA5LjM5OSAxMXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMDQgLTgwNCkgdHJhbnNsYXRlKDIwNCA4MDQpIi8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LW1hc2siIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yNTIgLTgwNCkgdHJhbnNsYXRlKDI1MiA4MDQpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTAgMEgxMlYxMkgweiIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI2IiBjeT0iNiIgcj0iMi41IiBzdHJva2U9IiM0NDQiLz4KICAgICAgICAgICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTTExLjUgMGMuMjc2IDAgLjUuMjI0LjUuNXYxMWMwIC4yNzYtLjIyNC41LS41LjVILjVjLS4yNzYgMC0uNS0uMjI0LS41LS41Vi41QzAgLjIyNC4yMjQgMCAuNSAwaDExek0xMSAxSDF2MTBoMTBWMXoiLz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oaXN0b3J5LXJvdGF0ZSIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxkZWZzPgogICAgICAgIDxwYXRoIGlkPSJyZm40cnlsZmZhIiBkPSJNNyAxMmMtLjMzNSAwLS42NjMtLjAyNS0uOTgzLS4wNzRDMy4xNzEgMTEuNDkyIDEgOS4yMDUgMSA2LjQ0NGMwLTEuMzYzLjUzNC0yLjYxMyAxLjQxNS0zLjU4Ii8+CiAgICAgICAgPG1hc2sgaWQ9IjZmOWduMmR5c2IiIHdpZHRoPSI2IiBoZWlnaHQ9IjkuMTM2IiB4PSIwIiB5PSIwIiBtYXNrVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94Ij4KICAgICAgICAgICAgPHVzZSB4bGluazpocmVmPSIjcmZuNHJ5bGZmYSIgc3Ryb2tlPSI0MzQzNDMiLz4KICAgICAgICA8L21hc2s+CiAgICA8L2RlZnM+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xMzIgLTgwNCkgdHJhbnNsYXRlKDEzMiA4MDQpIj4KICAgICAgICAgICAgPHBhdGggZD0iTTAgMC41SDEyVjEyLjVIMHoiLz4KICAgICAgICAgICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTTYuNSAxQzkuNTM4IDEgMTIgMy40NjIgMTIgNi41YzAgMi4zNy0xLjUgNC4zOS0zLjYgNS4xNjNsLS40MDctLjkxNkM5Ljc0NCAxMC4xMyAxMSA4LjQ2MiAxMSA2LjUgMTEgNC4wMTUgOC45ODUgMiA2LjUgMmMtLjc3NyAwLTEuNTA5LjE5Ny0yLjE0Ny41NDRMNCAxLjc1bC0uMjA1LS4wNEM0LjU5NCAxLjI1OCA1LjUxNyAxIDYuNSAxeiIvPgogICAgICAgICAgICA8dXNlIHN0cm9rZT0iIzQzNDM0MyIgc3Ryb2tlLWRhc2hhcnJheT0iMiAxLjI1IiBzdHJva2Utd2lkdGg9IjEiIG1hc2s9InVybCgjNmY5Z24yZHlzYikiIHhsaW5rOmhyZWY9IiNyZm40cnlsZmZhIi8+CiAgICAgICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik00LjI3OSAwTDYgMS43NSA0LjI1IDMuNTcxIDMuNTQzIDIuODY0IDQuNTg2IDEuNzUgMy41NzIgMC43MDd6IiB0cmFuc2Zvcm09Im1hdHJpeCgtMSAwIDAgMSA5LjU0MyAwKSIvPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWhpc3Rvcnktc2hhcGUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgPgogICAgICAgIDxwYXRoIGQ9Ik0wIDBIMTJWMTJIMHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xODAgLTgwNCkgdHJhbnNsYXRlKDE4MCA4MDQpIi8+CiAgICAgICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTTExLjUgNGMuMjc2IDAgLjUuMjI0LjUuNXY3YzAgLjI3Ni0uMjI0LjUtLjUuNWgtN2MtLjI3NiAwLS41LS4yMjQtLjUtLjVWOC44aDFWMTFoNlY1SDguMzQxbC0uNTY4LTFIMTEuNXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xODAgLTgwNCkgdHJhbnNsYXRlKDE4MCA4MDQpIi8+CiAgICAgICAgPHBhdGggc3Ryb2tlPSIjNDM0MzQzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik00LjUgMC41TDguNSA3LjYxMSAwLjUgNy42MTF6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTgwIC04MDQpIHRyYW5zbGF0ZSgxODAgODA0KSIvPgogICAgPC9nPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaGlzdG9yeS10ZXh0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiID4KICAgICAgICA8cGF0aCBkPSJNMCAwSDEyVjEySDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI4IC04MDQpIHRyYW5zbGF0ZSgyMjggODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0yIDFoOGMuNTUyIDAgMSAuNDQ4IDEgMUgxYzAtLjU1Mi40NDgtMSAxLTF6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI4IC04MDQpIHRyYW5zbGF0ZSgyMjggODA0KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik0xIDFIMlYzSDF6TTEwIDFIMTFWM0gxMHpNNS41IDFMNi41IDEgNi41IDExIDUuNSAxMXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMjggLTgwNCkgdHJhbnNsYXRlKDIyOCA4MDQpIi8+CiAgICAgICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTTQgMTBIOFYxMUg0eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTIyOCAtODA0KSB0cmFuc2xhdGUoMjI4IDgwNCkiLz4KICAgIDwvZz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWhpc3RvcnktbG9hZCIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxnIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8cGF0aCBkPSJNMCAwSDEyVjEySDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzI0IC04MDUpIHRyYW5zbGF0ZSgzMjQgODA1KSIvPgogICAgICAgIDxwYXRoIGZpbGw9IiM0MzQzNDMiIGQ9Ik01IDBjLjU1MiAwIDEgLjQ0OCAxIDF2MWg1LjVjLjI3NiAwIC41LjIyNC41LjV2OGMwIC4yNzYtLjIyNC41LS41LjVILjVjLS4yNzYgMC0uNS0uMjI0LS41LS41VjFjMC0uNTUyLjQ0OC0xIDEtMWg0em0wIDFIMXY5aDEwVjNINVYxeiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyNCAtODA1KSB0cmFuc2xhdGUoMzI0IDgwNSkiLz4KICAgICAgICA8cGF0aCBmaWxsPSIjNDM0MzQzIiBkPSJNMSAyTDUgMiA1IDMgMSAzeiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMyNCAtODA1KSB0cmFuc2xhdGUoMzI0IDgwNSkiLz4KICAgIDwvZz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWhpc3RvcnktZGVsZXRlIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGZpbGw9IiM0MzQzNDMiPgogICAgICAgICAgICA8cGF0aCBkPSJNMiA5aDhWMWgxdjguNWMwIC4yNzYtLjIyNC41LS41LjVoLTljLS4yNzYgMC0uNS0uMjI0LS41LS41VjFoMXY4ek0wIDBIMTJWMUgweiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwMCAtODA0KSB0cmFuc2xhdGUoMzAwIDgwNCkgdHJhbnNsYXRlKDAgMikiLz4KICAgICAgICAgICAgPHBhdGggZD0iTTQgM0g1VjdINHpNNyAzSDhWN0g3eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMwMCAtODA0KSB0cmFuc2xhdGUoMzAwIDgwNCkgdHJhbnNsYXRlKDAgMikiLz4KICAgICAgICAgICAgPHBhdGggZD0iTTQgMWg0VjBoMXYxLjVjMCAuMjc2LS4yMjQuNS0uNS41aC01Yy0uMjc2IDAtLjUtLjIyNC0uNS0uNVYwaDF2MXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0zMDAgLTgwNCkgdHJhbnNsYXRlKDMwMCA4MDQpIG1hdHJpeCgxIDAgMCAtMSAwIDIpIi8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaGlzdG9yeS1ncm91cCIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxnIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMzQ4IC04MDQpIHRyYW5zbGF0ZSgzNDggODA0KSI+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0wIDBIMTJWMTJIMHoiLz4KICAgICAgICAgICAgPHBhdGggZmlsbD0iIzQzNDM0MyIgZD0iTTEgOXYyaDF2MUguNWMtLjI3NiAwLS41LS4yMjQtLjUtLjVWOWgxem0xMSAxdjEuNWMwIC4yNzYtLjIyNC41LS41LjVIOXYtMWgydi0xaDF6bS00IDF2MUg2di0xaDJ6bS0zIDB2MUgzdi0xaDJ6bTctNHYyaC0xVjdoMXpNMSA2djJIMFY2aDF6bTExLTJ2MmgtMVY0aDF6TTEgM3YySDBWM2gxem0xMC41LTNjLjI3NiAwIC41LjIyNC41LjVWM2gtMVYxaC0xVjBoMS41ek02IDB2MUg0VjBoMnptMyAwdjFIN1YwaDJ6TTAgLjVDMCAuMjI0LjIyNCAwIC41IDBIM3YxSDF2MUgwVi41ek05LjUgNGMuMjc2IDAgLjUuMjI0LjUuNXY1YzAgLjI3Ni0uMjI0LjUtLjUuNWgtNWMtLjI3NiAwLS41LS4yMjQtLjUtLjVWOC4zNTVjLjMxNy4wOTQuNjUyLjE0NSAxIC4xNDVWOWg0VjVoLS41YzAtLjM0OC0uMDUtLjY4My0uMTQ1LTFIOS41eiIvPgogICAgICAgICAgICA8Y2lyY2xlIGN4PSI1IiBjeT0iNSIgcj0iMi41IiBzdHJva2U9IiM0MzQzNDMiLz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1pY29uLWFycm93LTIiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTIxLjc5MyAxOC41SDIuNXYtNWgxOC45MzVsLTcuNi04aDUuODcybDEwLjUgMTAuNS0xMC41IDEwLjVoLTUuOTE0bDgtOHoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWljb24tYXJyb3ctMyIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMjUuMjg4IDE2LjQyTDE0LjIwOCAyNy41SDYuNzkybDExLjI5MS0xMS4yOTFMNi44MjYgNC41aDcuMzgxbDExLjY2MSAxMS42NjEtLjU4LjI1OHoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWljb24tYXJyb3ciIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIGQ9Ik0yLjUgMTEuNXY5aDE4djUuMjkzTDMwLjI5MyAxNiAyMC41IDYuMjA3VjExLjVoLTE4eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbi1idWJibGUiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTIyLjIwNyAyNC41TDE2LjUgMzAuMjA3VjI0LjVIOEE2LjUgNi41IDAgMCAxIDEuNSAxOFY5QTYuNSA2LjUgMCAwIDEgOCAyLjVoMTZBNi41IDYuNSAwIDAgMSAzMC41IDl2OWE2LjUgNi41IDAgMCAxLTYuNSA2LjVoLTEuNzkzeiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbi1oZWFydCIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGwtcnVsZT0ibm9uemVybyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBkPSJNMTUuOTk2IDMwLjY3NWwxLjk4MS0xLjc5YzcuODk4LTcuMTc3IDEwLjM2NS05LjcxOCAxMi4xMzUtMTMuMDEyLjkyMi0xLjcxNiAxLjM3Ny0zLjM3IDEuMzc3LTUuMDc2IDAtNC42NS0zLjY0Ny04LjI5Ny04LjI5Ny04LjI5Ny0yLjMzIDAtNC44NiAxLjUyNy02LjgxNyAzLjgyNGwtLjM4LjQ0Ny0uMzgxLS40NDdDMTMuNjU4IDQuMDI3IDExLjEyNiAyLjUgOC43OTcgMi41IDQuMTQ3IDIuNS41IDYuMTQ3LjUgMTAuNzk3YzAgMS43MTQuNDYgMy4zNzUgMS4zODkgNS4wOTggMS43NzUgMy4yODggNC4yNiA1Ljg0MyAxMi4xMjMgMTIuOTc0bDEuOTg0IDEuODA2eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbi1sb2FkIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xNy4zMTQgMTguODY3bDEuOTUxLTIuNTMgNCA1LjE4NGgtMTdsNi41LTguODQgNC41NDkgNi4xODZ6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0xOC4wMSA0YTExLjc5OCAxMS43OTggMCAwIDAgMCAxSDN2MjRoMjRWMTQuOTg2YTguNzM4IDguNzM4IDAgMCAwIDEgMFYyOWExIDEgMCAwIDEtMSAxSDNhMSAxIDAgMCAxLTEtMVY1YTEgMSAwIDAgMSAxLTFoMTUuMDF6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0yNSAzaDF2OWgtMXoiLz4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgZD0iTTIyIDZsMy41LTMuNUwyOSA2Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1pY29uLWxvY2F0aW9uIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBkPSJNMTYgMzEuMjhDMjMuNjc1IDIzLjMwMiAyNy41IDE3LjE4MSAyNy41IDEzYzAtNi4zNTEtNS4xNDktMTEuNS0xMS41LTExLjVTNC41IDYuNjQ5IDQuNSAxM2MwIDQuMTgxIDMuODI1IDEwLjMwMiAxMS41IDE4LjI4eiIvPgogICAgPGNpcmNsZSBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIGN4PSIxNiIgY3k9IjEzIiByPSI0LjUiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLWljb24tcG9seWdvbiIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgZD0iTS41NzYgMTZMOC4yOSAyOS41aDE1LjQyTDMxLjQyNCAxNiAyMy43MSAyLjVIOC4yOUwuNTc2IDE2eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbi1zdGFyLTIiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIGQ9Ik0xOS40NDYgMzEuNTkybDIuMjY1LTMuMjcyIDMuOTQ2LjI1LjYzNi0zLjk0IDMuNjY1LTEuNTA1LTEuMTItMy44MzIgMi42NTUtMi45NjItMi42NTYtMi45NjIgMS4xMi0zLjgzMi0zLjY2NC0xLjUwNS0uNjM2LTMuOTQxLTMuOTQ2LjI1LTIuMjY1LTMuMjcxTDE2IDMuMDI0IDEyLjU1NCAxLjA3IDEwLjI4OSA0LjM0bC0zLjk0Ni0uMjUtLjYzNiAzLjk0MS0zLjY2NSAxLjUwNSAxLjEyIDMuODMyTC41MDggMTYuMzNsMi42NTYgMi45NjItMS4xMiAzLjgzMiAzLjY2NCAxLjUwNC42MzYgMy45NDIgMy45NDYtLjI1IDIuMjY1IDMuMjdMMTYgMjkuNjM4bDMuNDQ2IDEuOTU1eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbi1zdGFyIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBkPSJNMjUuMjkyIDI5Ljg3OGwtMS43NzUtMTAuMzQ2IDcuNTE3LTcuMzI3LTEwLjM4OC0xLjUxTDE2IDEuMjgybC00LjY0NiA5LjQxMy0xMC4zODggMS41MSA3LjUxNyA3LjMyNy0xLjc3NSAxMC4zNDZMMTYgMjQuOTkzbDkuMjkyIDQuODg1eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtaWNvbiIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMTEuOTIzIDE5LjEzNkw1LjQyNCAyMmwuNzE1LTcuMDY1LTQuNzMxLTUuMjk2IDYuOTQtMS41MDNMMTEuOTIzIDJsMy41NzQgNi4xMzYgNi45NCAxLjUwMy00LjczMSA1LjI5NkwxOC40MiAyMnoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLW1hc2stbG9hZCIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIgZD0iTTAgMGgzMnYzMkgweiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMTguMDEgNGExMS43OTggMTEuNzk4IDAgMCAwIDAgMUgzdjI0aDI0VjE0Ljk4NmE4LjczOCA4LjczOCAwIDAgMCAxIDBWMjlhMSAxIDAgMCAxLTEgMUgzYTEgMSAwIDAgMS0xLTFWNWExIDEgMCAwIDEgMS0xaDE1LjAxek0xNSAyM2E2IDYgMCAxIDEgMC0xMiA2IDYgMCAwIDEgMCAxMnptMC0xYTUgNSAwIDEgMCAwLTEwIDUgNSAwIDAgMCAwIDEweiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMjUgM2gxdjloLTF6Ii8+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIGQ9Ik0yMiA2bDMuNS0zLjVMMjkgNiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtbWFzayIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjQuNSIgc3Ryb2tlPSJpbmhlcml0IiBmaWxsPSJub25lIi8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0yIDFoMjBhMSAxIDAgMCAxIDEgMXYyMGExIDEgMCAwIDEtMSAxSDJhMSAxIDAgMCAxLTEtMVYyYTEgMSAwIDAgMSAxLTF6bTAgMXYyMGgyMFYySDJ6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1yZWRvIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgb3BhY2l0eT0iLjUiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTIxIDZIOWE2IDYgMCAxIDAgMCAxMmgxMnYxSDlBNyA3IDAgMCAxIDkgNWgxMnYxeiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBzdHJva2UtbGluZWNhcD0ic3F1YXJlIiBkPSJNMTkgM2wyLjUgMi41TDE5IDgiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXJlc2V0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgb3BhY2l0eT0iLjUiIHN0cm9rZT0ibm9uZSIgZmlsbD0ibm9uZSIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMiAxM3YtMWE3IDcgMCAwIDEgNy03aDEzdjFoLTF2NWgxdjFhNyA3IDAgMCAxLTcgN0gydi0xaDF2LTVIMnptNy03YTYgNiAwIDAgMC02IDZ2NmgxMmE2IDYgMCAwIDAgNi02VjZIOXoiLz4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iaW5oZXJpdCIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgZD0iTTE5IDNsMi41IDIuNUwxOSA4TTUgMTZsLTIuNSAyLjVMNSAyMSIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtcm90YXRlLWNsb2Nrd2lzZSIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTI5IDE3aC0uOTI0YzAgNi42MjctNS4zNzMgMTItMTIgMTItNi42MjggMC0xMi01LjM3My0xMi0xMkM0LjA3NiAxMC4zOTggOS40MDcgNS4wNDEgMTYgNVY0QzguODIgNCAzIDkuODIgMyAxN3M1LjgyIDEzIDEzIDEzIDEzLTUuODIgMTMtMTN6Ii8+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIGQ9Ik0xNiAxLjVsNCAzLTQgMyIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik0xNiA0aDR2MWgtNHoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXJvdGF0ZS1jb3VudGVyY2xvY2t3aXNlIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBkPSJNMyAxN2guOTI0YzAgNi42MjcgNS4zNzMgMTIgMTIgMTIgNi42MjggMCAxMi01LjM3MyAxMi0xMiAwLTYuNjAyLTUuMzMxLTExLjk2LTExLjkyNC0xMlY0YzcuMTggMCAxMyA1LjgyIDEzIDEzcy01LjgyIDEzLTEzIDEzUzMgMjQuMTggMyAxN3oiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZmlsbC1ydWxlPSJub256ZXJvIiBkPSJNMTIgNGg0djFoLTR6Ii8+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIGQ9Ik0xNiAxLjVsLTQgMyA0IDMiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXJvdGF0ZSIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgLz4KICAgIDxwYXRoIGZpbGw9ImluaGVyaXQiIHN0cm9rZT0ibm9uZSIgZD0iTTguMzQ5IDIyLjI1NGExMC4wMDIgMTAuMDAyIDAgMCAxLTIuNzc4LTEuNzE5bC42NS0uNzZhOS4wMDIgOS4wMDIgMCAwIDAgMi40OTUgMS41NDhsLS4zNjcuOTMxem0yLjg3My43MDRsLjA3OC0uOTk3YTkgOSAwIDEgMC0uNTU3LTE3Ljg1MmwtLjE0LS45OUExMC4wNzYgMTAuMDc2IDAgMCAxIDEyLjE0NSAzYzUuNTIzIDAgMTAgNC40NzcgMTAgMTBzLTQuNDc3IDEwLTEwIDEwYy0uMzEyIDAtLjYyLS4wMTQtLjkyNC0uMDQyem0tNy41NTYtNC42NTVhOS45NDIgOS45NDIgMCAwIDEtMS4yNTMtMi45OTZsLjk3My0uMjM0YTguOTQ4IDguOTQ4IDAgMCAwIDEuMTI0IDIuNjkzbC0uODQ0LjUzN3ptLTEuNTAyLTUuOTFBOS45NDkgOS45NDkgMCAwIDEgMi44OCA5LjIzbC45MjUuMzgyYTguOTU0IDguOTU0IDAgMCAwLS42NDQgMi44NDRsLS45OTgtLjA2MnptMi4yMS01LjY4NmMuNjg3LS44NDggMS41MS0xLjU4IDIuNDM2LTIuMTY2bC41MjMuODUyYTkuMDQ4IDkuMDQ4IDAgMCAwLTIuMTg4IDEuOTVsLS43NzEtLjYzNnoiLz4KICAgIDxwYXRoIHN0cm9rZT0iaW5oZXJpdCIgZmlsbD0ibm9uZSIgc3Ryb2tlLWxpbmVjYXA9InNxdWFyZSIgZD0iTTEzIDFsLTIuNSAyLjVMMTMgNiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtc2hhcGUtY2lyY2xlIiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTQuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1zaGFwZS1yZWN0YW5nbGUiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cmVjdCB3aWR0aD0iMjciIGhlaWdodD0iMjciIHg9IjIuNSIgeT0iMi41IiBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHJ4PSIxIi8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1zaGFwZS10cmlhbmdsZSIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTE2IDIuNWwxNS41IDI3SC41eiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtc2hhcGUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0xNC43MDYgOEgyMWExIDEgMCAwIDEgMSAxdjEyYTEgMSAwIDAgMS0xIDFIOWExIDEgMCAwIDEtMS0xdi00aDF2NGgxMlY5aC01LjcwNmwtLjU4OC0xeiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik04LjUgMS41bDcuNSAxM0gxeiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtdGV4dC1hbGlnbi1jZW50ZXIiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTIgNWgyOHYxSDJ6TTggMTJoMTZ2MUg4ek0yIDE5aDI4djFIMnpNOCAyNmgxNnYxSDh6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy10ZXh0LWFsaWduLWxlZnQiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTIgNWgyOHYxSDJ6TTIgMTJoMTZ2MUgyek0yIDE5aDI4djFIMnpNMiAyNmgxNnYxSDJ6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy10ZXh0LWFsaWduLXJpZ2h0IiB2aWV3Qm94PSIwIDAgMzIgMzIiPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJub25lIiBkPSJNMCAwaDMydjMySDB6Ii8+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9ImluaGVyaXQiIGQ9Ik0yIDVoMjh2MUgyek0xNCAxMmgxNnYxSDE0ek0yIDE5aDI4djFIMnpNMTQgMjZoMTZ2MUgxNHoiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXRleHQtYm9sZCIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgZD0iTTAgMGgzMnYzMkgweiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNNyAyaDJ2Mkg3ek03IDI4aDJ2Mkg3eiIvPgogICAgPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJpbmhlcml0IiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik05IDN2MTJoOWE2IDYgMCAxIDAgMC0xMkg5ek05IDE1djE0aDEwYTcgNyAwIDAgMCAwLTE0SDl6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy10ZXh0LWl0YWxpYyIgdmlld0JveD0iMCAwIDMyIDMyIj4KICAgIDxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgZD0iTTAgMGgzMnYzMkgweiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMTUgMmg1djFoLTV6TTExIDI5aDV2MWgtNXpNMTcgM2gxbC00IDI2aC0xeiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtdGV4dC11bmRlcmxpbmUiIHZpZXdCb3g9IjAgMCAzMiAzMiI+CiAgICA8cGF0aCBzdHJva2U9Im5vbmUiIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMzJ2MzJIMHoiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTggMnYxNGE4IDggMCAxIDAgMTYgMFYyaDF2MTRhOSA5IDAgMCAxLTE4IDBWMmgxek0zIDI5aDI2djFIM3oiLz4KICAgIDxwYXRoIHN0cm9rZT0ibm9uZSIgZmlsbD0iaW5oZXJpdCIgZD0iTTUgMmg1djFINXpNMjIgMmg1djFoLTV6Ii8+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy10ZXh0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNNCAzaDE1YTEgMSAwIDAgMSAxIDFIM2ExIDEgMCAwIDEgMS0xek0zIDRoMXYxSDN6TTE5IDRoMXYxaC0xeiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMTEgM2gxdjE4aC0xeiIvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMTAgMjBoM3YxaC0zeiIvPgo8L3N5bWJvbD4KPHN5bWJvbCBpZD0iaWMtdW5kbyIgdmlld0JveD0iMCAwIDI0IDI0Ij4KICAgIDxwYXRoIGQ9Ik0yNCAwSDB2MjRoMjR6IiBvcGFjaXR5PSIuNSIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJub25lIiAvPgogICAgPHBhdGggc3Ryb2tlPSJub25lIiBmaWxsPSJpbmhlcml0IiBkPSJNMyA2aDEyYTYgNiAwIDEgMSAwIDEySDN2MWgxMmE3IDcgMCAwIDAgMC0xNEgzdjF6Ii8+CiAgICA8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImluaGVyaXQiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiIGQ9Ik01IDNMMi41IDUuNSA1IDgiLz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXpvb20taW4iIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI5IC0yOTApIHRyYW5zbGF0ZSgyMjkgMjkwKSI+CiAgICAgICAgPGNpcmNsZSBjeD0iMTAuNSIgY3k9IjEwLjUiIHI9IjkiIHN0cm9rZT0iaW5oZXJpdCIgZmlsbD0ibm9uZSIvPgogICAgICAgIDxwYXRoIGZpbGw9ImluaGVyaXQiIGQ9Ik0xOC44MjggMTUuODI4SDE5LjgyOFYyMi44MjhIMTguODI4eiIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE5LjMyOCAxOS4zMjgpIi8+CiAgICAgICAgPHBhdGggZmlsbD0iaW5oZXJpdCIgZD0iTTcgMTBIMTRWMTFIN3oiLz4KICAgICAgICA8cGF0aCBmaWxsPSJpbmhlcml0IiBkPSJNMTAgN0gxMVYxNEgxMHoiLz4KICAgIDwvZz4KPC9zeW1ib2w+CjxzeW1ib2wgaWQ9ImljLXpvb20tb3V0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI2MyAtMjkwKSB0cmFuc2xhdGUoMjYzIDI5MCkiPgogICAgICAgIDxjaXJjbGUgY3g9IjEwLjUiIGN5PSIxMC41IiByPSI5IiBzdHJva2U9ImluaGVyaXQiIGZpbGw9Im5vbmUiLz4KICAgICAgICA8cGF0aCBmaWxsPSJpbmhlcml0IiBkPSJNMTguODI4IDE1LjgyOEgxOS44MjhWMjIuODI4SDE4LjgyOHoiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxOS4zMjggMTkuMzI4KSIvPgogICAgICAgIDxwYXRoIGZpbGw9ImluaGVyaXQiIGQ9Ik03IDEwSDE0VjExSDd6Ii8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8c3ltYm9sIGlkPSJpYy1oYW5kIiB2aWV3Qm94PSIwIDAgMjQgMjQiPgogICAgPGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj4KICAgICAgICA8cGF0aCBmaWxsPSJpbmhlcml0IiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik04LjY3MiAzLjM2YzEuMzI4IDAgMi4xMTQuNzggMi4yOSAxLjg2OWwuMDE0LjEwMS4wMjMuMDA2djEuMDQybC0uNjM4LS4xODVjLS4xODctLjA1NS0uMzIzLS4yMTEtLjM1NC0uMzk5TDEwIDUuNzEzYzAtLjgyNS0uNDItMS4zNTMtMS4zMjgtMS4zNTNDNy42OTUgNC4zNiA3IDUuMDQxIDcgNS43MTN2Ny45NDFjMCAuNDM5LS41MjQuNjY1LS44NDMuMzY0bC0xLjg2OC0xLjc2MWMtLjU5NS0uNTI4LTEuMzE2LS42MTctMS45MTgtLjIxNi0uNTIyLjM0OC0uNTYyIDEuMjAzLS4xOCAxLjhMNy43MzggMjJoMTEuMDEzbC4yODUtLjUxOGMxLjI0Ny0yLjMyNiAxLjg5Ny00LjI1OSAxLjk2LTUuNzg1bC4wMDQtLjIzOVY4LjAzNWMwLS42NTYtLjUtMS4xNy0xLTEuMTctLjUwMyAwLTEgLjQ1Ni0xIDEuMTcgMCAuMzMzLS4zMi41NzMtLjY0LjQ4TDE4IDguNDFWNy4zNjhsLjA4Ni4wMjYuMDQyLS4xMzZjLjI3OS0uODA1Ljk3OC0xLjMzMiAxLjczOC0xLjM4OEwyMCA1Ljg2NWMxLjA1NyAwIDIgLjk2NyAyIDIuMTd2Ny40MjNjMCAxLjkyOS0uODQ1IDQuMzUyLTIuNTIxIDcuMjktLjA5LjE1Ni0uMjU1LjI1Mi0uNDM1LjI1Mkg3LjQ3NGMtLjE2NiAwLS4zMjEtLjA4Mi0uNDE0LS4yMTlsLTUuNzA0LTguMzljLS42NTMtMS4wMTktLjU4NC0yLjQ4Ni40Ni0zLjE4MiAxLS42NjYgMi4yMTYtLjUxNiAzLjE0OC4zMUw2IDEyLjQ5NVY1LjcxM2MwLTEuMTggMS4wNTgtMi4yNjMgMi40OS0yLjM0OHoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yOTcgLTI5MCkgdHJhbnNsYXRlKDI5NyAyOTApIi8+CiAgICAgICAgPHBhdGggZmlsbD0iaW5oZXJpdCIgZmlsbC1ydWxlPSJub256ZXJvIiBkPSJNMTIuNSAxLjVjMS4zMjUgMCAyLjQxIDEuMDMyIDIuNDk1IDIuMzM2TDE1IDR2Ny4yMmgtMVY0YzAtLjgyOC0uNjcyLTEuNS0xLjUtMS41LS43OCAwLTEuNDIuNTk1LTEuNDkzIDEuMzU2TDExIDR2Ny4yMmgtMVY0YzAtMS4zOCAxLjEyLTIuNSAyLjUtMi41eiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI5NyAtMjkwKSB0cmFuc2xhdGUoMjk3IDI5MCkiLz4KICAgICAgICA8cGF0aCBmaWxsPSJpbmhlcml0IiBmaWxsLXJ1bGU9Im5vbnplcm8iIGQ9Ik0xNi41IDMuNWMxLjMyNSAwIDIuNDEgMS4wMzIgMi40OTUgMi4zMzZMMTkgNnY2LjNoLTFWNmMwLS44MjgtLjY3Mi0xLjUtMS41LTEuNS0uNzggMC0xLjQyLjU5NS0xLjQ5MyAxLjM1NkwxNSA2djIuNDRoLTFWNmMwLTEuMzggMS4xMi0yLjUgMi41LTIuNXoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yOTcgLTI5MCkgdHJhbnNsYXRlKDI5NyAyOTApIi8+CiAgICA8L2c+Cjwvc3ltYm9sPgo8L2RlZnM+Cjwvc3ZnPgo="; + +/***/ }), + +/***/ 4858: +/***/ (function(module) { + +"use strict"; +module.exports = __WEBPACK_EXTERNAL_MODULE__4858__; + +/***/ }), + +/***/ 4960: +/***/ (function() { + +/* (ignored) */ + +/***/ }), + +/***/ 6759: +/***/ (function() { + +/* (ignored) */ + +/***/ }), + +/***/ 6272: +/***/ (function() { + +/* (ignored) */ + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ !function() { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function() { return module['default']; } : +/******/ function() { return module; }; +/******/ __webpack_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ !function() { +/******/ // define getter functions for harmony exports +/******/ __webpack_require__.d = function(exports, definition) { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ }(); +/******/ +/******/ /* webpack/runtime/global */ +/******/ !function() { +/******/ __webpack_require__.g = (function() { +/******/ if (typeof globalThis === 'object') return globalThis; +/******/ try { +/******/ return this || new Function('return this')(); +/******/ } catch (e) { +/******/ if (typeof window === 'object') return window; +/******/ } +/******/ })(); +/******/ }(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ !function() { +/******/ __webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } +/******/ }(); +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +!function() { +"use strict"; + +// EXPORTS +__webpack_require__.d(__webpack_exports__, { + "default": function() { return /* binding */ src; } +}); + +// UNUSED EXPORTS: ImageEditor + +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/trim.js +var trim = __webpack_require__(9131); +var trim_default = /*#__PURE__*/__webpack_require__.n(trim); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/index-of.js +var index_of = __webpack_require__(1899); +var index_of_default = /*#__PURE__*/__webpack_require__.n(index_of); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/splice.js +var splice = __webpack_require__(6562); +var splice_default = /*#__PURE__*/__webpack_require__.n(splice); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js +var define_property = __webpack_require__(1734); +var define_property_default = /*#__PURE__*/__webpack_require__.n(define_property); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/slice.js +var slice = __webpack_require__(8005); +var slice_default = /*#__PURE__*/__webpack_require__.n(slice); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/object/create.js +var create = __webpack_require__(6065); +var create_default = /*#__PURE__*/__webpack_require__.n(create); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/set-timeout.js +var set_timeout = __webpack_require__(4496); +var set_timeout_default = /*#__PURE__*/__webpack_require__.n(set_timeout); +;// CONCATENATED MODULE: ./src/js/polyfill.js + + + + + + + + +/* eslint-disable */ +// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest +if (!Element.prototype.matches) { + Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; +} + +if (!Element.prototype.closest) { + Element.prototype.closest = function (s) { + var el = this; + + do { + if (Element.prototype.matches.call(el, s)) return el; + el = el.parentElement || el.parentNode; + } while (el !== null && el.nodeType === 1); + + return null; + }; +} +/* + * classList.js: Cross-browser full element.classList implementation. + * 1.2.20171210 + * + * By Eli Grey, http://eligrey.com + * License: Dedicated to the public domain. + * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md + */ + +/*global self, document, DOMException */ + +/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ + + +if ('document' in self) { + // Full polyfill for browsers with no classList support + // Including IE < Edge missing SVGElement.classList + if (!('classList' in document.createElement('_')) || document.createElementNS && !('classList' in document.createElementNS('http://www.w3.org/2000/svg', 'g'))) { + (function (view) { + 'use strict'; + + if (!('Element' in view)) return; + + var classListProp = 'classList', + protoProp = 'prototype', + elemCtrProto = view.Element[protoProp], + objCtr = Object, + strTrim = trim_default()(String[protoProp]) || function () { + return this.replace(/^\s+|\s+$/g, ''); + }, + arrIndexOf = index_of_default()(Array[protoProp]) || function (item) { + var i = 0, + len = this.length; + + for (; i < len; i++) { + if (i in this && this[i] === item) { + return i; + } + } + + return -1; + }, + // Vendors: please allow content code to instantiate DOMExceptions + DOMEx = function DOMEx(type, message) { + this.name = type; + this.code = DOMException[type]; + this.message = message; + }, + checkTokenAndGetIndex = function checkTokenAndGetIndex(classList, token) { + if (token === '') { + throw new DOMEx('SYNTAX_ERR', 'The token must not be empty.'); + } + + if (/\s/.test(token)) { + throw new DOMEx('INVALID_CHARACTER_ERR', 'The token must not contain space characters.'); + } + + return arrIndexOf.call(classList, token); + }, + ClassList = function ClassList(elem) { + var trimmedClasses = strTrim.call(elem.getAttribute('class') || ''), + classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [], + i = 0, + len = classes.length; + + for (; i < len; i++) { + this.push(classes[i]); + } + + this._updateClassName = function () { + elem.setAttribute('class', this.toString()); + }; + }, + classListProto = ClassList[protoProp] = [], + classListGetter = function classListGetter() { + return new ClassList(this); + }; // Most DOMException implementations don't allow calling DOMException's toString() + // on non-DOMExceptions. Error's toString() is sufficient here. + + + DOMEx[protoProp] = Error[protoProp]; + + classListProto.item = function (i) { + return this[i] || null; + }; + + classListProto.contains = function (token) { + return ~checkTokenAndGetIndex(this, token + ''); + }; + + classListProto.add = function () { + var tokens = arguments, + i = 0, + l = tokens.length, + token, + updated = false; + + do { + token = tokens[i] + ''; + + if (!~checkTokenAndGetIndex(this, token)) { + this.push(token); + updated = true; + } + } while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + + classListProto.remove = function () { + var tokens = arguments, + i = 0, + l = tokens.length, + token, + updated = false, + index; + + do { + token = tokens[i] + ''; + index = checkTokenAndGetIndex(this, token); + + while (~index) { + var _context; + + splice_default()(_context = this).call(_context, index, 1); + + updated = true; + index = checkTokenAndGetIndex(this, token); + } + } while (++i < l); + + if (updated) { + this._updateClassName(); + } + }; + + classListProto.toggle = function (token, force) { + var result = this.contains(token), + method = result ? force !== true && 'remove' : force !== false && 'add'; + + if (method) { + this[method](token); + } + + if (force === true || force === false) { + return force; + } else { + return !result; + } + }; + + classListProto.replace = function (token, replacement_token) { + var index = checkTokenAndGetIndex(token + ''); + + if (~index) { + var _context2; + + splice_default()(_context2 = this).call(_context2, index, 1, replacement_token); + + this._updateClassName(); + } + }; + + classListProto.toString = function () { + return this.join(' '); + }; + + if ((define_property_default())) { + var classListPropDesc = { + get: classListGetter, + enumerable: true, + configurable: true + }; + + try { + define_property_default()(elemCtrProto, classListProp, classListPropDesc); + } catch (ex) { + // IE 8 doesn't support enumerable:true + // adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36 + // modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected + if (ex.number === undefined || ex.number === -0x7ff5ec54) { + classListPropDesc.enumerable = false; + + define_property_default()(elemCtrProto, classListProp, classListPropDesc); + } + } + } else if (objCtr[protoProp].__defineGetter__) { + elemCtrProto.__defineGetter__(classListProp, classListGetter); + } + })(self); + } // There is full or partial native classList support, so just check if we need + // to normalize the add/remove and toggle APIs. + + + (function () { + 'use strict'; + + var testElement = document.createElement('_'); + testElement.classList.add('c1', 'c2'); // Polyfill for IE 10/11 and Firefox <26, where classList.add and + // classList.remove exist but support only one argument at a time. + + if (!testElement.classList.contains('c2')) { + var createMethod = function createMethod(method) { + var original = DOMTokenList.prototype[method]; + + DOMTokenList.prototype[method] = function (token) { + var i, + len = arguments.length; + + for (i = 0; i < len; i++) { + token = arguments[i]; + original.call(this, token); + } + }; + }; + + createMethod('add'); + createMethod('remove'); + } + + testElement.classList.toggle('c3', false); // Polyfill for IE 10 and Firefox <24, where classList.toggle does not + // support the second argument. + + if (testElement.classList.contains('c3')) { + var _toggle = DOMTokenList.prototype.toggle; + + DOMTokenList.prototype.toggle = function (token, force) { + if (1 in arguments && !this.contains(token) === !force) { + return force; + } else { + return _toggle.call(this, token); + } + }; + } // replace() polyfill + + + if (!('replace' in document.createElement('_').classList)) { + DOMTokenList.prototype.replace = function (token, replacement_token) { + var tokens = this.toString().split(' '), + index = index_of_default()(tokens).call(tokens, token + ''); + + if (~index) { + tokens = slice_default()(tokens).call(tokens, index); + this.remove.apply(this, tokens); + this.add(replacement_token); + this.add.apply(this, slice_default()(tokens).call(tokens, 1)); + } + }; + } + + testElement = null; + })(); +} +/*! + * @copyright Copyright (c) 2017 IcoMoon.io + * @license Licensed under MIT license + * See https://github.com/Keyamoon/svgxuse + * @version 1.2.6 + */ + +/*jslint browser: true */ + +/*global XDomainRequest, MutationObserver, window */ + + +(function () { + 'use strict'; + + if (typeof window !== 'undefined' && window.addEventListener) { + var cache = create_default()(null); // holds xhr objects to prevent multiple requests + + + var checkUseElems; + var tid; // timeout id + + var debouncedCheck = function debouncedCheck() { + clearTimeout(tid); + tid = set_timeout_default()(checkUseElems, 100); + }; + + var unobserveChanges = function unobserveChanges() { + return; + }; + + var observeChanges = function observeChanges() { + var observer; + window.addEventListener('resize', debouncedCheck, false); + window.addEventListener('orientationchange', debouncedCheck, false); + + if (window.MutationObserver) { + observer = new MutationObserver(debouncedCheck); + observer.observe(document.documentElement, { + childList: true, + subtree: true, + attributes: true + }); + + unobserveChanges = function unobserveChanges() { + try { + observer.disconnect(); + window.removeEventListener('resize', debouncedCheck, false); + window.removeEventListener('orientationchange', debouncedCheck, false); + } catch (ignore) {} + }; + } else { + document.documentElement.addEventListener('DOMSubtreeModified', debouncedCheck, false); + + unobserveChanges = function unobserveChanges() { + document.documentElement.removeEventListener('DOMSubtreeModified', debouncedCheck, false); + window.removeEventListener('resize', debouncedCheck, false); + window.removeEventListener('orientationchange', debouncedCheck, false); + }; + } + }; + + var createRequest = function createRequest(url) { + // In IE 9, cross origin requests can only be sent using XDomainRequest. + // XDomainRequest would fail if CORS headers are not set. + // Therefore, XDomainRequest should only be used with cross origin requests. + function getOrigin(loc) { + var a; + + if (loc.protocol !== undefined) { + a = loc; + } else { + a = document.createElement('a'); + a.href = loc; + } + + return a.protocol.replace(/:/g, '') + a.host; + } + + var Request; + var origin; + var origin2; + + if (window.XMLHttpRequest) { + Request = new XMLHttpRequest(); + origin = getOrigin(location); + origin2 = getOrigin(url); + + if (Request.withCredentials === undefined && origin2 !== '' && origin2 !== origin) { + Request = XDomainRequest || undefined; + } else { + Request = XMLHttpRequest; + } + } + + return Request; + }; + + var xlinkNS = 'http://www.w3.org/1999/xlink'; + + checkUseElems = function checkUseElems() { + var base; + var bcr; + var fallback = ''; // optional fallback URL in case no base path to SVG file was given and no symbol definition was found. + + var hash; + var href; + var i; + var inProgressCount = 0; + var isHidden; + var Request; + var url; + var uses; + var xhr; + + function observeIfDone() { + // If done with making changes, start watching for chagnes in DOM again + inProgressCount -= 1; + + if (inProgressCount === 0) { + // if all xhrs were resolved + unobserveChanges(); // make sure to remove old handlers + + observeChanges(); // watch for changes to DOM + } + } + + function attrUpdateFunc(spec) { + return function () { + if (cache[spec.base] !== true) { + spec.useEl.setAttributeNS(xlinkNS, 'xlink:href', '#' + spec.hash); + + if (spec.useEl.hasAttribute('href')) { + spec.useEl.setAttribute('href', '#' + spec.hash); + } + } + }; + } + + function onloadFunc(xhr) { + return function () { + var body = document.body; + var x = document.createElement('x'); + var svg; + xhr.onload = null; + x.innerHTML = xhr.responseText; + svg = x.getElementsByTagName('svg')[0]; + + if (svg) { + svg.setAttribute('aria-hidden', 'true'); + svg.style.position = 'absolute'; + svg.style.width = 0; + svg.style.height = 0; + svg.style.overflow = 'hidden'; + body.insertBefore(svg, body.firstChild); + } + + observeIfDone(); + }; + } + + function onErrorTimeout(xhr) { + return function () { + xhr.onerror = null; + xhr.ontimeout = null; + observeIfDone(); + }; + } + + unobserveChanges(); // stop watching for changes to DOM + // find all use elements + + uses = document.getElementsByTagName('use'); + + for (i = 0; i < uses.length; i += 1) { + try { + bcr = uses[i].getBoundingClientRect(); + } catch (ignore) { + // failed to get bounding rectangle of the use element + bcr = false; + } + + href = uses[i].getAttribute('href') || uses[i].getAttributeNS(xlinkNS, 'href') || uses[i].getAttribute('xlink:href'); + + if (href && href.split) { + url = href.split('#'); + } else { + url = ['', '']; + } + + base = url[0]; + hash = url[1]; + isHidden = bcr && bcr.left === 0 && bcr.right === 0 && bcr.top === 0 && bcr.bottom === 0; + + if (bcr && bcr.width === 0 && bcr.height === 0 && !isHidden) { + // the use element is empty + // if there is a reference to an external SVG, try to fetch it + // use the optional fallback URL if there is no reference to an external SVG + if (fallback && !base.length && hash && !document.getElementById(hash)) { + base = fallback; + } + + if (uses[i].hasAttribute('href')) { + uses[i].setAttributeNS(xlinkNS, 'xlink:href', href); + } + + if (base.length) { + // schedule updating xlink:href + xhr = cache[base]; + + if (xhr !== true) { + // true signifies that prepending the SVG was not required + set_timeout_default()(attrUpdateFunc({ + useEl: uses[i], + base: base, + hash: hash + }), 0); + } + + if (xhr === undefined) { + Request = createRequest(base); + + if (Request !== undefined) { + xhr = new Request(); + cache[base] = xhr; + xhr.onload = onloadFunc(xhr); + xhr.onerror = onErrorTimeout(xhr); + xhr.ontimeout = onErrorTimeout(xhr); + xhr.open('GET', base); + xhr.send(); + inProgressCount += 1; + } + } + } + } else { + if (!isHidden) { + if (cache[base] === undefined) { + // remember this URL if the use element was not empty and no request was sent + cache[base] = true; + } else if (cache[base].onload) { + // if it turns out that prepending the SVG is not necessary, + // abort the in-progress xhr. + cache[base].abort(); + delete cache[base].onload; + cache[base] = true; + } + } else if (base.length && cache[base]) { + set_timeout_default()(attrUpdateFunc({ + useEl: uses[i], + base: base, + hash: hash + }), 0); + } + } + } + + uses = ''; + inProgressCount += 1; + observeIfDone(); + }; + + var _winLoad; + + _winLoad = function winLoad() { + window.removeEventListener('load', _winLoad, false); // to prevent memory leaks + + tid = set_timeout_default()(checkUseElems, 0); + }; + + if (document.readyState !== 'complete') { + // The load event fires when all resources have finished loading, which allows detecting whether SVG use elements are empty. + window.addEventListener('load', _winLoad, false); + } else { + // No need to add a listener if the document is already loaded, initialize immediately. + _winLoad(); + } + } +})(); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/array/is-array.js +var is_array = __webpack_require__(1845); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/arrayLikeToArray.js +function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + + return arr2; +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/arrayWithoutHoles.js + + +function _arrayWithoutHoles(arr) { + if (is_array(arr)) return _arrayLikeToArray(arr); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/symbol.js +var symbol = __webpack_require__(184); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/get-iterator-method.js +var get_iterator_method = __webpack_require__(662); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/array/from.js +var from = __webpack_require__(7172); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/iterableToArray.js + + + +function _iterableToArray(iter) { + if (typeof symbol !== "undefined" && get_iterator_method(iter) != null || iter["@@iterator"] != null) return from(iter); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/instance/slice.js +var instance_slice = __webpack_require__(711); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/unsupportedIterableToArray.js + + + +function _unsupportedIterableToArray(o, minLen) { + var _context; + + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + + var n = instance_slice(_context = Object.prototype.toString.call(o)).call(_context, 8, -1); + + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/nonIterableSpread.js +function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/toConsumableArray.js + + + + +function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/object/define-property.js +var object_define_property = __webpack_require__(7077); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/defineProperty.js + +function _defineProperty(obj, key, value) { + if (key in obj) { + object_define_property(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/classCallCheck.js +function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/createClass.js + + +function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + + object_define_property(target, descriptor.key, descriptor); + } +} + +function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/bind.js +var bind = __webpack_require__(4426); +var bind_default = /*#__PURE__*/__webpack_require__.n(bind); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/concat.js +var concat = __webpack_require__(9406); +var concat_default = /*#__PURE__*/__webpack_require__.n(concat); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/promise.js +var core_js_stable_promise = __webpack_require__(8189); +var promise_default = /*#__PURE__*/__webpack_require__.n(core_js_stable_promise); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/url.js +var url = __webpack_require__(3972); +var url_default = /*#__PURE__*/__webpack_require__.n(url); +// EXTERNAL MODULE: ./node_modules/fabric/dist/fabric.js +var fabric = __webpack_require__(2777); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/object/extend.js +var extend = __webpack_require__(961); +var extend_default = /*#__PURE__*/__webpack_require__.n(extend); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/type/isUndefined.js +var type_isUndefined = __webpack_require__(5695); +var isUndefined_default = /*#__PURE__*/__webpack_require__.n(type_isUndefined); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/collection/forEach.js +var collection_forEach = __webpack_require__(8592); +var forEach_default = /*#__PURE__*/__webpack_require__.n(collection_forEach); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/customEvents/customEvents.js +var customEvents = __webpack_require__(9052); +var customEvents_default = /*#__PURE__*/__webpack_require__.n(customEvents); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/type/isString.js +var isString = __webpack_require__(2560); +var isString_default = /*#__PURE__*/__webpack_require__.n(isString); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/object/keys.js +var keys = __webpack_require__(2461); +var keys_default = /*#__PURE__*/__webpack_require__.n(keys); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/arrayWithHoles.js + +function _arrayWithHoles(arr) { + if (is_array(arr)) return arr; +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/iterableToArrayLimit.js + + +function _iterableToArrayLimit(arr, i) { + var _i = arr == null ? null : typeof symbol !== "undefined" && get_iterator_method(arr) || arr["@@iterator"]; + + if (_i == null) return; + var _arr = []; + var _n = true; + var _d = false; + + var _s, _e; + + try { + for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"] != null) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/nonIterableRest.js +function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/slicedToArray.js + + + + +function _slicedToArray(arr, i) { + return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/parse-int.js +var parse_int = __webpack_require__(6397); +var parse_int_default = /*#__PURE__*/__webpack_require__.n(parse_int); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/for-each.js +var for_each = __webpack_require__(7636); +var for_each_default = /*#__PURE__*/__webpack_require__.n(for_each); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/fill.js +var instance_fill = __webpack_require__(789); +var fill_default = /*#__PURE__*/__webpack_require__.n(instance_fill); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/request/sendHostname.js +var sendHostname = __webpack_require__(4729); +var sendHostname_default = /*#__PURE__*/__webpack_require__.n(sendHostname); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/object/pick.js +var pick = __webpack_require__(1610); +var pick_default = /*#__PURE__*/__webpack_require__.n(pick); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/array/inArray.js +var inArray = __webpack_require__(3053); +var inArray_default = /*#__PURE__*/__webpack_require__.n(inArray); +;// CONCATENATED MODULE: ./src/js/consts.js +var _context; + + + +/** + * Help features for zoom + * @type {Array.} + */ + +var ZOOM_HELP_MENUS = ['zoomIn', 'zoomOut', 'hand']; +/** + * Help features for command + * @type {Array.} + */ + +var COMMAND_HELP_MENUS = ['history', 'undo', 'redo', 'reset']; +/** + * Help features for delete + * @type {Array.} + */ + +var DELETE_HELP_MENUS = ['delete', 'deleteAll']; +/** + * Editor help features + * @type {Array.} + */ + +var HELP_MENUS = concat_default()(_context = []).call(_context, ZOOM_HELP_MENUS, COMMAND_HELP_MENUS, DELETE_HELP_MENUS); +/** + * Fill type for shape + * @type {Object.} + */ + +var SHAPE_FILL_TYPE = { + FILTER: 'filter', + COLOR: 'color' +}; +/** + * Shape type list + * @type {Array.} + */ + +var SHAPE_TYPE = ['rect', 'circle', 'triangle']; +/** + * Object type + * @type {Object.} + */ + +var OBJ_TYPE = { + CROPZONE: 'cropzone' +}; +/** + * Filter type map + * @type {Object.} + */ + +var filterType = { + VINTAGE: 'vintage', + SEPIA2: 'sepia2', + REMOVE_COLOR: 'removeColor', + COLOR_FILTER: 'colorFilter', + REMOVE_WHITE: 'removeWhite', + BLEND_COLOR: 'blendColor', + BLEND: 'blend' +}; +/** + * Component names + * @type {Object.} + */ + +var componentNames = keyMirror('IMAGE_LOADER', 'CROPPER', 'FLIP', 'ROTATION', 'FREE_DRAWING', 'LINE', 'TEXT', 'ICON', 'FILTER', 'SHAPE', 'ZOOM', 'RESIZE'); +/** + * Shape default option + * @type {Object} + */ + +var SHAPE_DEFAULT_OPTIONS = { + lockSkewingX: true, + lockSkewingY: true, + bringForward: true, + isRegular: false +}; +/** + * Cropzone default option + * @type {Object} + */ + +var CROPZONE_DEFAULT_OPTIONS = { + hasRotatingPoint: false, + hasBorders: false, + lockScalingFlip: true, + lockRotation: true, + lockSkewingX: true, + lockSkewingY: true +}; +/** + * Command names + * @type {Object.} + */ + +var commandNames = { + CLEAR_OBJECTS: 'clearObjects', + LOAD_IMAGE: 'loadImage', + FLIP_IMAGE: 'flip', + ROTATE_IMAGE: 'rotate', + ADD_OBJECT: 'addObject', + REMOVE_OBJECT: 'removeObject', + APPLY_FILTER: 'applyFilter', + REMOVE_FILTER: 'removeFilter', + ADD_ICON: 'addIcon', + CHANGE_ICON_COLOR: 'changeIconColor', + ADD_SHAPE: 'addShape', + CHANGE_SHAPE: 'changeShape', + ADD_TEXT: 'addText', + CHANGE_TEXT: 'changeText', + CHANGE_TEXT_STYLE: 'changeTextStyle', + ADD_IMAGE_OBJECT: 'addImageObject', + RESIZE_CANVAS_DIMENSION: 'resizeCanvasDimension', + SET_OBJECT_PROPERTIES: 'setObjectProperties', + SET_OBJECT_POSITION: 'setObjectPosition', + CHANGE_SELECTION: 'changeSelection', + RESIZE_IMAGE: 'resize' +}; +/** + * Event names + * @type {Object.} + */ + +var eventNames = { + OBJECT_ACTIVATED: 'objectActivated', + OBJECT_MOVED: 'objectMoved', + OBJECT_SCALED: 'objectScaled', + OBJECT_CREATED: 'objectCreated', + OBJECT_ROTATED: 'objectRotated', + OBJECT_ADDED: 'objectAdded', + OBJECT_MODIFIED: 'objectModified', + TEXT_EDITING: 'textEditing', + TEXT_CHANGED: 'textChanged', + ICON_CREATE_RESIZE: 'iconCreateResize', + ICON_CREATE_END: 'iconCreateEnd', + ADD_TEXT: 'addText', + ADD_OBJECT: 'addObject', + ADD_OBJECT_AFTER: 'addObjectAfter', + MOUSE_DOWN: 'mousedown', + MOUSE_UP: 'mouseup', + MOUSE_MOVE: 'mousemove', + // UNDO/REDO Events + REDO_STACK_CHANGED: 'redoStackChanged', + UNDO_STACK_CHANGED: 'undoStackChanged', + SELECTION_CLEARED: 'selectionCleared', + SELECTION_CREATED: 'selectionCreated', + EXECUTE_COMMAND: 'executeCommand', + AFTER_UNDO: 'afterUndo', + AFTER_REDO: 'afterRedo', + ZOOM_CHANGED: 'zoomChanged', + HAND_STARTED: 'handStarted', + HAND_STOPPED: 'handStopped', + KEY_DOWN: 'keydown', + KEY_UP: 'keyup', + INPUT_BOX_EDITING_STARTED: 'inputBoxEditingStarted', + INPUT_BOX_EDITING_STOPPED: 'inputBoxEditingStopped', + FOCUS: 'focus', + BLUR: 'blur', + IMAGE_RESIZED: 'imageResized' +}; +/** + * Selector names + * @type {Object.} + */ + +var selectorNames = { + COLOR_PICKER_INPUT_BOX: '.tui-colorpicker-palette-hex' +}; +/** + * History names + * @type {Object.} + */ + +var historyNames = { + LOAD_IMAGE: 'Load', + LOAD_MASK_IMAGE: 'Mask', + ADD_MASK_IMAGE: 'Mask', + ADD_IMAGE_OBJECT: 'Mask', + CROP: 'Crop', + RESIZE: 'Resize', + APPLY_FILTER: 'Filter', + REMOVE_FILTER: 'Filter', + CHANGE_SHAPE: 'Shape', + CHANGE_ICON_COLOR: 'Icon', + ADD_TEXT: 'Text', + CHANGE_TEXT_STYLE: 'Text', + REMOVE_OBJECT: 'Delete', + CLEAR_OBJECTS: 'Delete' +}; +/** + * Editor states + * @type {Object.} + */ + +var drawingModes = keyMirror('NORMAL', 'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE', 'ICON', 'ZOOM', 'RESIZE'); +/** + * Menu names with drawing mode + * @type {Object.} + */ + +var drawingMenuNames = { + TEXT: 'text', + CROP: 'crop', + RESIZE: 'resize', + SHAPE: 'shape', + ZOOM: 'zoom' +}; +/** + * Zoom modes + * @type {Object.} + */ + +var zoomModes = { + DEFAULT: 'normal', + ZOOM: 'zoom', + HAND: 'hand' +}; +/** + * Shortcut key values + * @type {Object.} + */ + +var keyCodes = { + Z: 90, + Y: 89, + C: 67, + V: 86, + SHIFT: 16, + BACKSPACE: 8, + DEL: 46, + ARROW_DOWN: 40, + ARROW_UP: 38, + SPACE: 32, + DIGIT_0: 48, + DIGIT_9: 57 +}; +/** + * Fabric object options + * @type {Object.} + */ + +var fObjectOptions = { + SELECTION_STYLE: { + borderColor: 'red', + cornerColor: 'green', + cornerSize: 10, + originX: 'center', + originY: 'center', + transparentCorners: false + } +}; +/** + * Promise reject messages + * @type {Object.} + */ + +var rejectMessages = { + addedObject: 'The object is already added.', + flip: 'The flipX and flipY setting values are not changed.', + invalidDrawingMode: 'This operation is not supported in the drawing mode.', + invalidParameters: 'Invalid parameters.', + isLock: 'The executing command state is locked.', + loadImage: 'The background image is empty.', + loadingImageFailed: 'Invalid image loaded.', + noActiveObject: 'There is no active object.', + noObject: 'The object is not in canvas.', + redo: 'The promise of redo command is reject.', + rotation: 'The current angle is same the old angle.', + undo: 'The promise of undo command is reject.', + unsupportedOperation: 'Unsupported operation.', + unsupportedType: 'Unsupported object type.' +}; +/** + * Default icon menu svg path + * @type {Object.} + */ + +var defaultIconPath = { + 'icon-arrow': 'M40 12V0l24 24-24 24V36H0V12h40z', + 'icon-arrow-2': 'M49,32 H3 V22 h46 l-18,-18 h12 l23,23 L43,50 h-12 l18,-18 z ', + 'icon-arrow-3': 'M43.349998,27 L17.354,53 H1.949999 l25.996,-26 L1.949999,1 h15.404 L43.349998,27 z ', + 'icon-star': 'M35,54.557999 l-19.912001,10.468 l3.804,-22.172001 l-16.108,-15.7 l22.26,-3.236 L35,3.746 l9.956,20.172001 l22.26,3.236 l-16.108,15.7 l3.804,22.172001 z ', + 'icon-star-2': 'M17,31.212 l-7.194,4.08 l-4.728,-6.83 l-8.234,0.524 l-1.328,-8.226 l-7.644,-3.14 l2.338,-7.992 l-5.54,-6.18 l5.54,-6.176 l-2.338,-7.994 l7.644,-3.138 l1.328,-8.226 l8.234,0.522 l4.728,-6.83 L17,-24.312 l7.194,-4.08 l4.728,6.83 l8.234,-0.522 l1.328,8.226 l7.644,3.14 l-2.338,7.992 l5.54,6.178 l-5.54,6.178 l2.338,7.992 l-7.644,3.14 l-1.328,8.226 l-8.234,-0.524 l-4.728,6.83 z ', + 'icon-polygon': 'M3,31 L19,3 h32 l16,28 l-16,28 H19 z ', + 'icon-location': 'M24 62C8 45.503 0 32.837 0 24 0 10.745 10.745 0 24 0s24 10.745 24 24c0 8.837-8 21.503-24 38zm0-28c5.523 0 10-4.477 10-10s-4.477-10-10-10-10 4.477-10 10 4.477 10 10 10z', + 'icon-heart': 'M49.994999,91.349998 l-6.96,-6.333 C18.324001,62.606995 2.01,47.829002 2.01,29.690998 C2.01,14.912998 13.619999,3.299999 28.401001,3.299999 c8.349,0 16.362,5.859 21.594,12 c5.229,-6.141 13.242001,-12 21.591,-12 c14.778,0 26.390999,11.61 26.390999,26.390999 c0,18.138 -16.314001,32.916 -41.025002,55.374001 l-6.96,6.285 z ', + 'icon-bubble': 'M44 48L34 58V48H12C5.373 48 0 42.627 0 36V12C0 5.373 5.373 0 12 0h40c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12h-8z' +}; +var defaultRotateRangeValues = { + realTimeEvent: true, + min: -360, + max: 360, + value: 0 +}; +var defaultDrawRangeValues = { + min: 5, + max: 30, + value: 12 +}; +var defaultShapeStrokeValues = { + realTimeEvent: true, + min: 2, + max: 300, + value: 3 +}; +var defaultTextRangeValues = { + realTimeEvent: true, + min: 10, + max: 100, + value: 50 +}; +var defaultFilterRangeValues = { + tintOpacityRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.7, + useDecimal: true + }, + removewhiteDistanceRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.2, + useDecimal: true + }, + brightnessRange: { + realTimeEvent: true, + min: -1, + max: 1, + value: 0, + useDecimal: true + }, + noiseRange: { + realTimeEvent: true, + min: 0, + max: 1000, + value: 100 + }, + pixelateRange: { + realTimeEvent: true, + min: 2, + max: 20, + value: 4 + }, + colorfilterThresholdRange: { + realTimeEvent: true, + min: 0, + max: 1, + value: 0.2, + useDecimal: true + }, + blurFilterRange: { + value: 0.1 + } +}; +var emptyCropRectValues = { + LEFT: 0, + TOP: 0, + WIDTH: 0.5, + HEIGHT: 0.5 +}; +var defaultResizePixelValues = { + realTimeEvent: true, + min: 32, + max: 4088, + value: 800 +}; +;// CONCATENATED MODULE: ./src/js/util.js + + + + + + + + + + + + + + + + +var FLOATING_POINT_DIGIT = 2; +var CSS_PREFIX = 'tui-image-editor-'; +var min = Math.min, + max = Math.max; +var hostnameSent = false; +var lastId = 0; +function stamp(obj) { + if (!obj.__fe_id) { + lastId += 1; // eslint-disable-next-line camelcase + + obj.__fe_id = lastId; + } + + return obj.__fe_id; +} +function hasStamp(obj) { + return !isNil(obj === null || obj === void 0 ? void 0 : obj.__fe_id); +} +function isNil(value) { + return isUndefined(value) || value === null; +} +function isFunction(value) { + return typeof value === 'function'; +} +/** + * Clamp value + * @param {number} value - Value + * @param {number} minValue - Minimum value + * @param {number} maxValue - Maximum value + * @returns {number} clamped value + */ + +function clamp(value, minValue, maxValue) { + if (minValue > maxValue) { + var _ref = [maxValue, minValue]; + minValue = _ref[0]; + maxValue = _ref[1]; + } + + return max(minValue, min(value, maxValue)); +} +/** + * Make key-value object from arguments + * @returns {object.} + */ + +function keyMirror() { + var obj = {}; + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + forEach_default()(args, function (key) { + obj[key] = key; + }); + return obj; +} +/** + * Make CSSText + * @param {Object} styleObj - Style info object + * @returns {string} Connected string of style + */ + +function makeStyleText(styleObj) { + var styleStr = ''; + forEach(styleObj, function (value, prop) { + var _context; + + styleStr += _concatInstanceProperty(_context = "".concat(prop, ": ")).call(_context, value, ";"); + }); + return styleStr; +} +/** + * Get object's properties + * @param {Object} obj - object + * @param {Array} keys - keys + * @returns {Object} properties object + */ + +function getProperties(obj, keys) { + var props = {}; + var length = keys.length; + var i = 0; + var key; + + for (i = 0; i < length; i += 1) { + key = keys[i]; + props[key] = obj[key]; + } + + return props; +} +/** + * ParseInt simpliment + * @param {number} value - Value + * @returns {number} + */ + +function toInteger(value) { + return parse_int_default()(value, 10); +} +/** + * String to camelcase string + * @param {string} targetString - change target + * @returns {string} + * @private + */ + +function toCamelCase(targetString) { + return targetString.replace(/-([a-z])/g, function ($0, $1) { + return $1.toUpperCase(); + }); +} +/** + * Check browser file api support + * @returns {boolean} + * @private + */ + +function isSupportFileApi() { + return !!(window.File && window.FileList && window.FileReader); +} +/** + * hex to rgb + * @param {string} color - hex color + * @param {string} alpha - color alpha value + * @returns {string} rgb expression + */ + +function getRgb(color, alpha) { + var _context3, _context4, _context5; + + if (color.length === 4) { + var _context2; + + color = concat_default()(_context2 = "".concat(color)).call(_context2, slice_default()(color).call(color, 1, 4)); + } + + var r = parse_int_default()(slice_default()(color).call(color, 1, 3), 16); + + var g = parse_int_default()(slice_default()(color).call(color, 3, 5), 16); + + var b = parse_int_default()(slice_default()(color).call(color, 5, 7), 16); + + var a = alpha || 1; + return concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = "rgba(".concat(r, ", ")).call(_context5, g, ", ")).call(_context4, b, ", ")).call(_context3, a, ")"); +} +/** + * send hostname + */ + +function sendHostName() { + if (hostnameSent) { + return; + } + + hostnameSent = true; + sendHostname_default()('image-editor', 'UA-129999381-1'); +} +/** + * Apply css resource + * @param {string} styleBuffer - serialized css text + * @param {string} tagId - style tag id + */ + +function styleLoad(styleBuffer, tagId) { + var _document$getElements = document.getElementsByTagName('head'), + _document$getElements2 = _slicedToArray(_document$getElements, 1), + head = _document$getElements2[0]; + + var linkElement = document.createElement('link'); + var styleData = encodeURIComponent(styleBuffer); + + if (tagId) { + linkElement.id = tagId; // linkElement.id = 'tui-image-editor-theme-style'; + } + + linkElement.setAttribute('rel', 'stylesheet'); + linkElement.setAttribute('type', 'text/css'); + linkElement.setAttribute('href', "data:text/css;charset=UTF-8,".concat(styleData)); + head.appendChild(linkElement); +} +/** + * Get selector + * @param {HTMLElement} targetElement - target element + * @returns {Function} selector + */ + +function getSelector(targetElement) { + return function (str) { + return targetElement.querySelector(str); + }; +} +/** + * Change base64 to blob + * @param {String} data - base64 string data + * @returns {Blob} Blob Data + */ + +function base64ToBlob(data) { + var rImageType = /data:(image\/.+);base64,/; + var mimeString = ''; + var raw, uInt8Array, i; + raw = data.replace(rImageType, function (header, imageType) { + mimeString = imageType; + return ''; + }); + raw = atob(raw); + var rawLength = raw.length; + uInt8Array = new Uint8Array(rawLength); // eslint-disable-line + + for (i = 0; i < rawLength; i += 1) { + uInt8Array[i] = raw.charCodeAt(i); + } + + return new Blob([uInt8Array], { + type: mimeString + }); +} +/** + * Fix floating point diff. + * @param {number} value - original value + * @returns {number} fixed value + */ + +function fixFloatingPoint(value) { + return Number(value.toFixed(FLOATING_POINT_DIGIT)); +} +/** + * Assignment for destroying objects. + * @param {Object} targetObject - object to be removed. + */ + +function assignmentForDestroy(targetObject) { + forEach_default()(targetObject, function (value, key) { + targetObject[key] = null; + }); +} +/** + * Make class name for ui + * @param {String} str - main string of className + * @param {String} prefix - prefix string of className + * @returns {String} class name + */ + +function cls() { + var _context7; + + var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; + var prefix = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; + + if (str.charAt(0) === '.') { + var _context6; + + return concat_default()(_context6 = ".".concat(CSS_PREFIX).concat(prefix)).call(_context6, slice_default()(str).call(str, 1)); + } + + return concat_default()(_context7 = "".concat(CSS_PREFIX).concat(prefix)).call(_context7, str); +} +/** + * Change object origin + * @param {fabric.Object} fObject - fabric object + * @param {Object} origin - origin of fabric object + * @param {string} originX - horizontal basis. + * @param {string} originY - vertical basis. + */ + +function changeOrigin(fObject, origin) { + var originX = origin.originX, + originY = origin.originY; + + var _fObject$getPointByOr = fObject.getPointByOrigin(originX, originY), + left = _fObject$getPointByOr.x, + top = _fObject$getPointByOr.y; + + fObject.set({ + left: left, + top: top, + originX: originX, + originY: originY + }); + fObject.setCoords(); +} +/** + * Object key value flip + * @param {Object} targetObject - The data object of the key value. + * @returns {Object} + */ + +function flipObject(targetObject) { + var _context8; + + var result = {}; + + for_each_default()(_context8 = keys_default()(targetObject)).call(_context8, function (key) { + result[targetObject[key]] = key; + }); + + return result; +} +/** + * Set custom properties + * @param {Object} targetObject - target object + * @param {Object} props - custom props object + */ + +function setCustomProperty(targetObject, props) { + targetObject.customProps = targetObject.customProps || {}; + extend_default()(targetObject.customProps, props); +} +/** + * Get custom property + * @param {fabric.Object} fObject - fabric object + * @param {Array|string} propNames - prop name array + * @returns {object | number | string} + */ + +function getCustomProperty(fObject, propNames) { + var resultObject = {}; + + if (isString_default()(propNames)) { + propNames = [propNames]; + } + + forEach_default()(propNames, function (propName) { + resultObject[propName] = fObject.customProps[propName]; + }); + return resultObject; +} +/** + * Capitalize string + * @param {string} targetString - target string + * @returns {string} + */ + +function capitalizeString(targetString) { + return targetString.charAt(0).toUpperCase() + slice_default()(targetString).call(targetString, 1); +} +/** + * Array includes check + * @param {Array} targetArray - target array + * @param {string|number} compareValue - compare value + * @returns {boolean} + */ + +function includes(targetArray, compareValue) { + return index_of_default()(targetArray).call(targetArray, compareValue) >= 0; +} +/** + * Get fill type + * @param {Object | string} fillOption - shape fill option + * @returns {string} 'color' or 'filter' + */ + +function getFillTypeFromOption() { + var fillOption = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + return pick_default()(fillOption, 'type') || SHAPE_FILL_TYPE.COLOR; +} +/** + * Get fill type of shape type object + * @param {fabric.Object} shapeObj - fabric object + * @returns {string} 'transparent' or 'color' or 'filter' + */ + +function getFillTypeFromObject(shapeObj) { + var _shapeObj$fill = fill_default()(shapeObj), + fill = _shapeObj$fill === void 0 ? {} : _shapeObj$fill; + + if (fill.source) { + return SHAPE_FILL_TYPE.FILTER; + } + + return SHAPE_FILL_TYPE.COLOR; +} +/** + * Check if the object is a shape object. + * @param {fabric.Object} obj - fabric object + * @returns {boolean} + */ + +function isShape(obj) { + return inArray_default()(obj.get('type'), SHAPE_TYPE) >= 0; +} +/** + * Get object type + * @param {string} type - fabric object type + * @returns {string} type of object (ex: shape, icon, ...) + */ + +function getObjectType(type) { + if (includes(SHAPE_TYPE, type)) { + return 'Shape'; + } + + switch (type) { + case 'i-text': + return 'Text'; + + case 'path': + case 'line': + return 'Draw'; + + case 'activeSelection': + return 'Group'; + + default: + return toStartOfCapital(type); + } +} +/** + * Get filter type + * @param {string} type - fabric filter type + * @param {object} [options] - filter type options + * @param {boolean} [options.useAlpha=true] - usage of alpha(true is 'color filter', false is 'remove white') + * @param {string} [options.mode] - mode of blendColor + * @returns {string} type of filter (ex: sepia, blur, ...) + */ + +function getFilterType(type) { + var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref2$useAlpha = _ref2.useAlpha, + useAlpha = _ref2$useAlpha === void 0 ? true : _ref2$useAlpha, + mode = _ref2.mode; + + var VINTAGE = filterType.VINTAGE, + REMOVE_COLOR = filterType.REMOVE_COLOR, + BLEND_COLOR = filterType.BLEND_COLOR, + SEPIA2 = filterType.SEPIA2, + COLOR_FILTER = filterType.COLOR_FILTER, + REMOVE_WHITE = filterType.REMOVE_WHITE, + BLEND = filterType.BLEND; + var filterName; + + switch (type) { + case VINTAGE: + filterName = SEPIA2; + break; + + case REMOVE_COLOR: + filterName = useAlpha ? COLOR_FILTER : REMOVE_WHITE; + break; + + case BLEND_COLOR: + filterName = mode === 'add' ? BLEND : mode; + break; + + default: + filterName = type; + } + + return toStartOfCapital(filterName); +} +/** + * Check if command is silent command + * @param {Command|string} command - command or command name + * @returns {boolean} + */ + + +function isSilentCommand(command) { + var LOAD_IMAGE = commandNames.LOAD_IMAGE; + return typeof command === 'string' ? LOAD_IMAGE === command : LOAD_IMAGE === command.name; +} +/** + * Get command name + * @param {Command|string} command - command or command name + * @returns {{name: string, ?detail: string}} + */ +// eslint-disable-next-line complexity, require-jsdoc + +function getHistoryTitle(command) { + var _context9, _context10; + + var FLIP_IMAGE = commandNames.FLIP_IMAGE, + ROTATE_IMAGE = commandNames.ROTATE_IMAGE, + ADD_TEXT = commandNames.ADD_TEXT, + APPLY_FILTER = commandNames.APPLY_FILTER, + REMOVE_FILTER = commandNames.REMOVE_FILTER, + CHANGE_SHAPE = commandNames.CHANGE_SHAPE, + CHANGE_ICON_COLOR = commandNames.CHANGE_ICON_COLOR, + CHANGE_TEXT_STYLE = commandNames.CHANGE_TEXT_STYLE, + CLEAR_OBJECTS = commandNames.CLEAR_OBJECTS, + ADD_IMAGE_OBJECT = commandNames.ADD_IMAGE_OBJECT, + REMOVE_OBJECT = commandNames.REMOVE_OBJECT, + RESIZE_IMAGE = commandNames.RESIZE_IMAGE; + var name = command.name, + args = command.args; + var historyInfo; + + switch (name) { + case FLIP_IMAGE: + historyInfo = { + name: name, + detail: args[1] === 'reset' ? args[1] : slice_default()(_context9 = args[1]).call(_context9, 4) + }; + break; + + case ROTATE_IMAGE: + historyInfo = { + name: name, + detail: args[2] + }; + break; + + case APPLY_FILTER: + historyInfo = { + name: historyNames.APPLY_FILTER, + detail: getFilterType(args[1], args[2]) + }; + break; + + case REMOVE_FILTER: + historyInfo = { + name: historyNames.REMOVE_FILTER, + detail: 'Remove' + }; + break; + + case CHANGE_SHAPE: + historyInfo = { + name: historyNames.CHANGE_SHAPE, + detail: 'Change' + }; + break; + + case CHANGE_ICON_COLOR: + historyInfo = { + name: historyNames.CHANGE_ICON_COLOR, + detail: 'Change' + }; + break; + + case CHANGE_TEXT_STYLE: + historyInfo = { + name: historyNames.CHANGE_TEXT_STYLE, + detail: 'Change' + }; + break; + + case REMOVE_OBJECT: + historyInfo = { + name: historyNames.REMOVE_OBJECT, + detail: args[2] + }; + break; + + case CLEAR_OBJECTS: + historyInfo = { + name: historyNames.CLEAR_OBJECTS, + detail: 'All' + }; + break; + + case ADD_IMAGE_OBJECT: + historyInfo = { + name: historyNames.ADD_IMAGE_OBJECT, + detail: 'Add' + }; + break; + + case ADD_TEXT: + historyInfo = { + name: historyNames.ADD_TEXT + }; + break; + + case RESIZE_IMAGE: + historyInfo = { + name: historyNames.RESIZE, + detail: concat_default()(_context10 = "".concat(~~args[1].width, "x")).call(_context10, ~~args[1].height) + }; + break; + + default: + historyInfo = { + name: name + }; + break; + } + + if (args[1] === 'mask') { + historyInfo = { + name: historyNames.LOAD_MASK_IMAGE, + detail: 'Apply' + }; + } + + return historyInfo; +} +/** + * Get help menubar position(opposite of menubar) + * @param {string} position - position of menubar + * @returns {string} position of help menubar + */ + +function getHelpMenuBarPosition(position) { + if (position === 'top') { + return 'bottom'; + } + + if (position === 'left') { + return 'right'; + } + + if (position === 'right') { + return 'left'; + } + + return 'top'; +} +/** + * Change to capital start letter + * @param {string} str - string to change + * @returns {string} + */ + +function toStartOfCapital(str) { + return str.replace(/[a-z]/, function (first) { + return first.toUpperCase(); + }); +} +/** + * Check if cropRect is Empty. + * @param {Object} cropRect - cropRect object + * @param {Number} cropRect.left - cropRect left position value + * @param {Number} cropRect.top - cropRect top position value + * @param {Number} cropRect.width - cropRect width value + * @param {Number} cropRect.height - cropRect height value + * @returns {boolean} + */ + + +function isEmptyCropzone(cropRect) { + var left = cropRect.left, + top = cropRect.top, + width = cropRect.width, + height = cropRect.height; + var LEFT = emptyCropRectValues.LEFT, + TOP = emptyCropRectValues.TOP, + WIDTH = emptyCropRectValues.WIDTH, + HEIGHT = emptyCropRectValues.HEIGHT; + return left === LEFT && top === TOP && width === WIDTH && height === HEIGHT; +} +;// CONCATENATED MODULE: ./src/js/factory/errorMessage.js + + +var types = keyMirror('UN_IMPLEMENTATION', 'NO_COMPONENT_NAME'); +var messages = { + UN_IMPLEMENTATION: 'Should implement a method: ', + NO_COMPONENT_NAME: 'Should set a component name' +}; +var map = { + UN_IMPLEMENTATION: function UN_IMPLEMENTATION(methodName) { + return messages.UN_IMPLEMENTATION + methodName; + }, + NO_COMPONENT_NAME: function NO_COMPONENT_NAME() { + return messages.NO_COMPONENT_NAME; + } +}; +/* harmony default export */ var errorMessage = ({ + types: extend_default()({}, types), + create: function create(type) { + type = type.toLowerCase(); + var func = map[type]; + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + return func.apply(void 0, args); + } +}); +;// CONCATENATED MODULE: ./src/js/interface/command.js + + + + + +var createMessage = errorMessage.create; +var errorTypes = errorMessage.types; +/** + * Command class + * @class + * @param {{name:function, execute: function, undo: function, + * executeCallback: function, undoCallback: function}} actions - Command actions + * @param {Array} args - passing arguments on execute, undo + * @ignore + */ + +var Command = /*#__PURE__*/function () { + function Command(actions, args) { + _classCallCheck(this, Command); + + /** + * command name + * @type {string} + */ + this.name = actions.name; + /** + * arguments + * @type {Array} + */ + + this.args = args; + /** + * Execute function + * @type {function} + */ + + this.execute = actions.execute; + /** + * Undo function + * @type {function} + */ + + this.undo = actions.undo; + /** + * executeCallback + * @type {function} + */ + + this.executeCallback = actions.executeCallback || null; + /** + * undoCallback + * @type {function} + */ + + this.undoCallback = actions.undoCallback || null; + /** + * data for undo + * @type {Object} + */ + + this.undoData = {}; + } + /** + * Execute action + * @param {Object.} compMap - Components injection + * @abstract + */ + + + _createClass(Command, [{ + key: "execute", + value: function execute() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'execute')); + } + /** + * Undo action + * @param {Object.} compMap - Components injection + * @abstract + */ + + }, { + key: "undo", + value: function undo() { + throw new Error(createMessage(errorTypes.UN_IMPLEMENTATION, 'undo')); + } + /** + * command for redo if undoData exists + * @returns {boolean} isRedo + */ + + }, { + key: "isRedo", + get: function get() { + return keys_default()(this.undoData).length > 0; + } + /** + * Set undoData action + * @param {Object} undoData - maked undo data + * @param {Object} cachedUndoDataForSilent - cached undo data + * @param {boolean} isSilent - is silent execution or not + * @returns {Object} cachedUndoDataForSilent + */ + + }, { + key: "setUndoData", + value: function setUndoData(undoData, cachedUndoDataForSilent, isSilent) { + if (cachedUndoDataForSilent) { + undoData = cachedUndoDataForSilent; + } + + if (!isSilent) { + extend_default()(this.undoData, undoData); + cachedUndoDataForSilent = null; + } else if (!cachedUndoDataForSilent) { + cachedUndoDataForSilent = undoData; + } + + return cachedUndoDataForSilent; + } + /** + * Attach execute callabck + * @param {function} callback - Callback after execution + * @returns {Command} this + */ + + }, { + key: "setExecuteCallback", + value: function setExecuteCallback(callback) { + this.executeCallback = callback; + return this; + } + /** + * Attach undo callback + * @param {function} callback - Callback after undo + * @returns {Command} this + */ + + }, { + key: "setUndoCallback", + value: function setUndoCallback(callback) { + this.undoCallback = callback; + return this; + } + }]); + + return Command; +}(); + +/* harmony default export */ var command = (Command); +;// CONCATENATED MODULE: ./src/js/factory/command.js + +var commands = {}; +/** + * Create a command + * @param {string} name - Command name + * @param {...*} args - Arguments for creating command + * @returns {Command} + * @ignore + */ + +function command_create(name) { + var actions = commands[name]; + + if (actions) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + return new command(actions, args); + } + + return null; +} +/** + * Register a command with name as a key + * @param {Object} command - {name:{string}, execute: {function}, undo: {function}} + * @param {string} command.name - command name + * @param {function} command.execute - executable function + * @param {function} command.undo - undo function + * @ignore + */ + + +function register(command) { + commands[command.name] = command; +} + +/* harmony default export */ var factory_command = ({ + create: command_create, + register: register +}); +;// CONCATENATED MODULE: ./src/js/invoker.js + + + + + + + + + + +/** + * Invoker + * @class + * @ignore + */ + +var Invoker = /*#__PURE__*/function () { + function Invoker() { + _classCallCheck(this, Invoker); + + /** + * Undo stack + * @type {Array.} + * @private + */ + this._undoStack = []; + /** + * Redo stack + * @type {Array.} + * @private + */ + + this._redoStack = []; + /** + * Lock-flag for executing command + * @type {boolean} + * @private + */ + + this._isLocked = false; + this._isSilent = false; + } + /** + * Invoke command execution + * @param {Command} command - Command + * @param {boolean} [isRedo=false] - check if command is redo + * @returns {Promise} + * @private + */ + + + _createClass(Invoker, [{ + key: "_invokeExecution", + value: function _invokeExecution(command) { + var _this = this; + + var isRedo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + this.lock(); + var args = command.args; + + if (!args) { + args = []; + } + + return command.execute.apply(command, _toConsumableArray(args)).then(function (value) { + if (!_this._isSilent) { + _this.pushUndoStack(command); + + _this.fire(isRedo ? eventNames.AFTER_REDO : eventNames.EXECUTE_COMMAND, command); + } + + _this.unlock(); + + if (isFunction(command.executeCallback)) { + command.executeCallback(value); + } + + return value; + })['catch'](function (message) { + _this.unlock(); + + return promise_default().reject(message); + }); + } + /** + * Invoke command undo + * @param {Command} command - Command + * @returns {Promise} + * @private + */ + + }, { + key: "_invokeUndo", + value: function _invokeUndo(command) { + var _this2 = this; + + this.lock(); + var args = command.args; + + if (!args) { + args = []; + } + + return command.undo.apply(command, _toConsumableArray(args)).then(function (value) { + _this2.pushRedoStack(command); + + _this2.fire(eventNames.AFTER_UNDO, command); + + _this2.unlock(); + + if (isFunction(command.undoCallback)) { + command.undoCallback(value); + } + + return value; + })['catch'](function (message) { + _this2.unlock(); + + return promise_default().reject(message); + }); + } + /** + * fire REDO_STACK_CHANGED event + * @private + */ + + }, { + key: "_fireRedoStackChanged", + value: function _fireRedoStackChanged() { + this.fire(eventNames.REDO_STACK_CHANGED, this._redoStack.length); + } + /** + * fire UNDO_STACK_CHANGED event + * @private + */ + + }, { + key: "_fireUndoStackChanged", + value: function _fireUndoStackChanged() { + this.fire(eventNames.UNDO_STACK_CHANGED, this._undoStack.length); + } + /** + * Lock this invoker + */ + + }, { + key: "lock", + value: function lock() { + this._isLocked = true; + } + /** + * Unlock this invoker + */ + + }, { + key: "unlock", + value: function unlock() { + this._isLocked = false; + } + }, { + key: "executeSilent", + value: function executeSilent() { + var _this3 = this; + + this._isSilent = true; + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return this.execute.apply(this, concat_default()(args).call(args, [this._isSilent])).then(function () { + _this3._isSilent = false; + }); + } + /** + * Invoke command + * Store the command to the undoStack + * Clear the redoStack + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + */ + + }, { + key: "execute", + value: function execute() { + var _this4 = this; + + if (this._isLocked) { + return promise_default().reject(rejectMessages.isLock); + } + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + var command = args[0]; + + if (isString_default()(command)) { + command = factory_command.create.apply(factory_command, args); + } + + return this._invokeExecution(command).then(function (value) { + _this4.clearRedoStack(); + + return value; + }); + } + /** + * Undo command + * @returns {Promise} + */ + + }, { + key: "undo", + value: function undo() { + var command = this._undoStack.pop(); + + var promise; + var message = ''; + + if (command && this._isLocked) { + this.pushUndoStack(command, true); + command = null; + } + + if (command) { + if (this.isEmptyUndoStack()) { + this._fireUndoStackChanged(); + } + + promise = this._invokeUndo(command); + } else { + message = rejectMessages.undo; + + if (this._isLocked) { + var _context; + + message = concat_default()(_context = "".concat(message, " Because ")).call(_context, rejectMessages.isLock); + } + + promise = promise_default().reject(message); + } + + return promise; + } + /** + * Redo command + * @returns {Promise} + */ + + }, { + key: "redo", + value: function redo() { + var command = this._redoStack.pop(); + + var promise; + var message = ''; + + if (command && this._isLocked) { + this.pushRedoStack(command, true); + command = null; + } + + if (command) { + if (this.isEmptyRedoStack()) { + this._fireRedoStackChanged(); + } + + promise = this._invokeExecution(command, true); + } else { + message = rejectMessages.redo; + + if (this._isLocked) { + var _context2; + + message = concat_default()(_context2 = "".concat(message, " Because ")).call(_context2, rejectMessages.isLock); + } + + promise = promise_default().reject(message); + } + + return promise; + } + /** + * Push undo stack + * @param {Command} command - command + * @param {boolean} [isSilent] - Fire event or not + */ + + }, { + key: "pushUndoStack", + value: function pushUndoStack(command, isSilent) { + this._undoStack.push(command); + + if (!isSilent) { + this._fireUndoStackChanged(); + } + } + /** + * Push redo stack + * @param {Command} command - command + * @param {boolean} [isSilent] - Fire event or not + */ + + }, { + key: "pushRedoStack", + value: function pushRedoStack(command, isSilent) { + this._redoStack.push(command); + + if (!isSilent) { + this._fireRedoStackChanged(); + } + } + /** + * Return whether the redoStack is empty + * @returns {boolean} + */ + + }, { + key: "isEmptyRedoStack", + value: function isEmptyRedoStack() { + return this._redoStack.length === 0; + } + /** + * Return whether the undoStack is empty + * @returns {boolean} + */ + + }, { + key: "isEmptyUndoStack", + value: function isEmptyUndoStack() { + return this._undoStack.length === 0; + } + /** + * Clear undoStack + */ + + }, { + key: "clearUndoStack", + value: function clearUndoStack() { + if (!this.isEmptyUndoStack()) { + this._undoStack = []; + + this._fireUndoStackChanged(); + } + } + /** + * Clear redoStack + */ + + }, { + key: "clearRedoStack", + value: function clearRedoStack() { + if (!this.isEmptyRedoStack()) { + this._redoStack = []; + + this._fireRedoStackChanged(); + } + } + }]); + + return Invoker; +}(); + +customEvents_default().mixin(Invoker); +/* harmony default export */ var invoker = (Invoker); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/parse-float.js +var parse_float = __webpack_require__(5214); +var parse_float_default = /*#__PURE__*/__webpack_require__.n(parse_float); +;// CONCATENATED MODULE: ./src/js/ui/template/mainContainer.js + +/* harmony default export */ var mainContainer = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7; + + var locale = _ref.locale, + biImage = _ref.biImage, + commonStyle = _ref.commonStyle, + headerStyle = _ref.headerStyle, + loadButtonStyle = _ref.loadButtonStyle, + downloadButtonStyle = _ref.downloadButtonStyle, + submenuStyle = _ref.submenuStyle; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = "\n
    \n
    \n
    \n \n
    \n
    \n
    \n ")).call(_context4, locale.localize('Load'), "\n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/template/controls.js + + +/* harmony default export */ var controls = (function (_ref) { + var _context, _context2, _context3, _context4, _context5; + + var locale = _ref.locale, + biImage = _ref.biImage, + loadButtonStyle = _ref.loadButtonStyle, + downloadButtonStyle = _ref.downloadButtonStyle, + menuBarPosition = _ref.menuBarPosition; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = "\n
      \n
      \n
      \n \n
      \n
        \n\n
        \n
        \n ")).call(_context3, locale.localize('Load'), "\n \n
        \n \n
        \n
        \n"); +}); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/map.js +var instance_map = __webpack_require__(899); +var map_default = /*#__PURE__*/__webpack_require__.n(instance_map); +;// CONCATENATED MODULE: ./src/js/ui/template/style.js + +/* harmony default export */ var style = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14, _context15, _context16, _context17, _context18, _context19, _context20, _context21, _context22, _context23, _context24, _context25, _context26, _context27, _context28, _context29; + + var subMenuLabelActive = _ref.subMenuLabelActive, + subMenuLabelNormal = _ref.subMenuLabelNormal, + subMenuRangeTitle = _ref.subMenuRangeTitle, + submenuPartitionVertical = _ref.submenuPartitionVertical, + submenuPartitionHorizontal = _ref.submenuPartitionHorizontal, + submenuCheckbox = _ref.submenuCheckbox, + submenuRangePointer = _ref.submenuRangePointer, + submenuRangeValue = _ref.submenuRangeValue, + submenuColorpickerTitle = _ref.submenuColorpickerTitle, + submenuColorpickerButton = _ref.submenuColorpickerButton, + submenuRangeBar = _ref.submenuRangeBar, + submenuRangeSubbar = _ref.submenuRangeSubbar, + submenuDisabledRangePointer = _ref.submenuDisabledRangePointer, + submenuDisabledRangeBar = _ref.submenuDisabledRangeBar, + submenuDisabledRangeSubbar = _ref.submenuDisabledRangeSubbar, + submenuIconSize = _ref.submenuIconSize, + menuIconSize = _ref.menuIconSize, + biSize = _ref.biSize, + menuIconStyle = _ref.menuIconStyle, + submenuIconStyle = _ref.submenuIconStyle; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = concat_default()(_context9 = concat_default()(_context10 = concat_default()(_context11 = concat_default()(_context12 = concat_default()(_context13 = concat_default()(_context14 = concat_default()(_context15 = concat_default()(_context16 = concat_default()(_context17 = concat_default()(_context18 = concat_default()(_context19 = concat_default()(_context20 = concat_default()(_context21 = concat_default()(_context22 = concat_default()(_context23 = concat_default()(_context24 = concat_default()(_context25 = concat_default()(_context26 = concat_default()(_context27 = concat_default()(_context28 = concat_default()(_context29 = "\n .tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype=\"icon-bubble\"] label,\n .tie-icon-add-button.icon-heart .tui-image-editor-button[data-icontype=\"icon-heart\"] label,\n .tie-icon-add-button.icon-location .tui-image-editor-button[data-icontype=\"icon-location\"] label,\n .tie-icon-add-button.icon-polygon .tui-image-editor-button[data-icontype=\"icon-polygon\"] label,\n .tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype=\"icon-star\"] label,\n .tie-icon-add-button.icon-star-2 .tui-image-editor-button[data-icontype=\"icon-star-2\"] label,\n .tie-icon-add-button.icon-arrow-3 .tui-image-editor-button[data-icontype=\"icon-arrow-3\"] label,\n .tie-icon-add-button.icon-arrow-2 .tui-image-editor-button[data-icontype=\"icon-arrow-2\"] label,\n .tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype=\"icon-arrow\"] label,\n .tie-icon-add-button.icon-bubble .tui-image-editor-button[data-icontype=\"icon-bubble\"] label,\n .tie-draw-line-select-button.line .tui-image-editor-button.line label,\n .tie-draw-line-select-button.free .tui-image-editor-button.free label,\n .tie-flip-button.flipX .tui-image-editor-button.flipX label,\n .tie-flip-button.flipY .tui-image-editor-button.flipY label,\n .tie-flip-button.resetFlip .tui-image-editor-button.resetFlip label,\n .tie-crop-button .tui-image-editor-button.apply.active label,\n .tie-crop-preset-button .tui-image-editor-button.preset.active label,\n .tie-resize-button .tui-image-editor-button.apply.active label,\n .tie-resize-preset-button .tui-image-editor-button.preset.active label,\n .tie-shape-button.rect .tui-image-editor-button.rect label,\n .tie-shape-button.circle .tui-image-editor-button.circle label,\n .tie-shape-button.triangle .tui-image-editor-button.triangle label,\n .tie-text-effect-button .tui-image-editor-button.active label,\n .tie-text-align-button.tie-text-align-left .tui-image-editor-button.left label,\n .tie-text-align-button.tie-text-align-center .tui-image-editor-button.center label,\n .tie-text-align-button.tie-text-align-right .tui-image-editor-button.right label,\n .tie-mask-apply.apply.active .tui-image-editor-button.apply label,\n .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button:hover > label,\n .tui-image-editor-container .tui-image-editor-checkbox label > span {\n ".concat(subMenuLabelActive, "\n }\n .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button > label,\n .tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label,\n .tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label > span {\n ")).call(_context29, subMenuLabelNormal, "\n }\n .tui-image-editor-container .tui-image-editor-range-wrap label > span {\n ")).call(_context28, subMenuRangeTitle, "\n }\n .tui-image-editor-container .tui-image-editor-partition > div {\n ")).call(_context27, submenuPartitionVertical, "\n }\n .tui-image-editor-container.left .tui-image-editor-submenu .tui-image-editor-partition > div,\n .tui-image-editor-container.right .tui-image-editor-submenu .tui-image-editor-partition > div {\n ")).call(_context26, submenuPartitionHorizontal, "\n }\n .tui-image-editor-container .tui-image-editor-checkbox label > span:before {\n ")).call(_context25, submenuCheckbox, "\n }\n .tui-image-editor-container .tui-image-editor-checkbox label > input:checked + span:before {\n border: 0;\n }\n .tui-image-editor-container .tui-image-editor-virtual-range-pointer {\n ")).call(_context24, submenuRangePointer, "\n }\n .tui-image-editor-container .tui-image-editor-virtual-range-bar {\n ")).call(_context23, submenuRangeBar, "\n }\n .tui-image-editor-container .tui-image-editor-virtual-range-subbar {\n ")).call(_context22, submenuRangeSubbar, "\n }\n .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-pointer {\n ")).call(_context21, submenuDisabledRangePointer, "\n }\n .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-subbar {\n ")).call(_context20, submenuDisabledRangeSubbar, "\n }\n .tui-image-editor-container .tui-image-editor-disabled .tui-image-editor-virtual-range-bar {\n ")).call(_context19, submenuDisabledRangeBar, "\n }\n .tui-image-editor-container .tui-image-editor-range-value {\n ")).call(_context18, submenuRangeValue, "\n }\n .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value + label {\n ")).call(_context17, submenuColorpickerTitle, "\n }\n .tui-image-editor-container .tui-image-editor-submenu .tui-image-editor-button .color-picker-value {\n ")).call(_context16, submenuColorpickerButton, "\n }\n .tui-image-editor-container .svg_ic-menu {\n ")).call(_context15, menuIconSize, "\n }\n .tui-image-editor-container .svg_ic-submenu {\n ")).call(_context14, submenuIconSize, "\n }\n .tui-image-editor-container .tui-image-editor-controls-logo > img,\n .tui-image-editor-container .tui-image-editor-header-logo > img {\n ")).call(_context13, biSize, "\n }\n .tui-image-editor-menu use.normal.use-default,\n .tui-image-editor-help-menu use.normal.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context12, menuIconStyle.normal.color, ";\n stroke: ")).call(_context11, menuIconStyle.normal.color, ";\n }\n .tui-image-editor-menu use.active.use-default,\n .tui-image-editor-help-menu use.active.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context10, menuIconStyle.active.color, ";\n stroke: ")).call(_context9, menuIconStyle.active.color, ";\n }\n .tui-image-editor-menu use.hover.use-default,\n .tui-image-editor-help-menu use.hover.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context8, menuIconStyle.hover.color, ";\n stroke: ")).call(_context7, menuIconStyle.hover.color, ";\n }\n .tui-image-editor-menu use.disabled.use-default,\n .tui-image-editor-help-menu use.disabled.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context6, menuIconStyle.disabled.color, ";\n stroke: ")).call(_context5, menuIconStyle.disabled.color, ";\n }\n .tui-image-editor-submenu use.normal.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context4, submenuIconStyle.normal.color, ";\n stroke: ")).call(_context3, submenuIconStyle.normal.color, ";\n }\n .tui-image-editor-submenu use.active.use-default {\n fill-rule: evenodd;\n fill: ")).call(_context2, submenuIconStyle.active.color, ";\n stroke: ")).call(_context, submenuIconStyle.active.color, ";\n }\n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/theme/standard.js +/** + * Full configuration for theme.
        + * @typedef {object} themeConfig + * @property {string} common.bi.image - Brand icon image + * @property {string} common.bisize.width - Icon image width + * @property {string} common.bisize.height - Icon Image Height + * @property {string} common.backgroundImage - Background image + * @property {string} common.backgroundColor - Background color + * @property {string} common.border - Full area border style + * @property {string} header.backgroundImage - header area background + * @property {string} header.backgroundColor - header area background color + * @property {string} header.border - header area border style + * @property {string} loadButton.backgroundColor - load button background color + * @property {string} loadButton.border - load button border style + * @property {string} loadButton.color - load button foreground color + * @property {string} loadButton.fontFamily - load button font type + * @property {string} loadButton.fontSize - load button font size + * @property {string} downloadButton.backgroundColor - download button background color + * @property {string} downloadButton.border - download button border style + * @property {string} downloadButton.color - download button foreground color + * @property {string} downloadButton.fontFamily - download button font type + * @property {string} downloadButton.fontSize - download button font size + * @property {string} menu.normalIcon.color - Menu normal color for default icon + * @property {string} menu.normalIcon.path - Menu normal icon svg bundle file path + * @property {string} menu.normalIcon.name - Menu normal icon svg bundle name + * @property {string} menu.activeIcon.color - Menu active color for default icon + * @property {string} menu.activeIcon.path - Menu active icon svg bundle file path + * @property {string} menu.activeIcon.name - Menu active icon svg bundle name + * @property {string} menu.disabled.color - Menu disabled color for default icon + * @property {string} menu.disabled.path - Menu disabled icon svg bundle file path + * @property {string} menu.disabled.name - Menu disabled icon svg bundle name + * @property {string} menu.hover.color - Menu default icon hover color + * @property {string} menu.hover.path - Menu hover icon svg bundle file path + * @property {string} menu.hover.name - Menu hover icon svg bundle name + * @property {string} menu.iconSize.width - Menu icon Size Width + * @property {string} menu.iconSize.height - Menu Icon Size Height + * @property {string} submenu.backgroundColor - Sub-menu area background color + * @property {string} submenu.partition.color - Submenu partition line color + * @property {string} submenu.normalIcon.color - Submenu normal color for default icon + * @property {string} submenu.normalIcon.path - Submenu default icon svg bundle file path + * @property {string} submenu.normalIcon.name - Submenu default icon svg bundle name + * @property {string} submenu.activeIcon.color - Submenu active color for default icon + * @property {string} submenu.activeIcon.path - Submenu active icon svg bundle file path + * @property {string} submenu.activeIcon.name - Submenu active icon svg bundle name + * @property {string} submenu.iconSize.width - Submenu icon Size Width + * @property {string} submenu.iconSize.height - Submenu Icon Size Height + * @property {string} submenu.normalLabel.color - Submenu default label color + * @property {string} submenu.normalLabel.fontWeight - Sub Menu Default Label Font Thickness + * @property {string} submenu.activeLabel.color - Submenu active label color + * @property {string} submenu.activeLabel.fontWeight - Submenu active label Font thickness + * @property {string} checkbox.border - Checkbox border style + * @property {string} checkbox.backgroundColor - Checkbox background color + * @property {string} range.pointer.color - range control pointer color + * @property {string} range.bar.color - range control bar color + * @property {string} range.subbar.color - range control subbar color + * @property {string} range.value.color - range number box font color + * @property {string} range.value.fontWeight - range number box font thickness + * @property {string} range.value.fontSize - range number box font size + * @property {string} range.value.border - range number box border style + * @property {string} range.value.backgroundColor - range number box background color + * @property {string} range.title.color - range title font color + * @property {string} range.title.fontWeight - range title font weight + * @property {string} colorpicker.button.border - colorpicker button border style + * @property {string} colorpicker.title.color - colorpicker button title font color + * @example + // default keys and styles + var customTheme = { + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': 'none', + 'common.backgroundColor': '#1e1e1e', + 'common.border': '0px', + + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', + + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': 'NotoSans, sans-serif', + 'loadButton.fontSize': '12px', + + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': 'NotoSans, sans-serif', + 'downloadButton.fontSize': '12px', + + // icons default + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#e9e9e9', + + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', + + // submenu primary color + 'submenu.backgroundColor': '#1e1e1e', + 'submenu.partition.color': '#858585', + + // submenu labels + 'submenu.normalLabel.color': '#858585', + 'submenu.normalLabel.fontWeight': 'lighter', + 'submenu.activeLabel.color': '#fff', + 'submenu.activeLabel.fontWeight': 'lighter', + + // checkbox style + 'checkbox.border': '1px solid #ccc', + 'checkbox.backgroundColor': '#fff', + + // rango style + 'range.pointer.color': '#fff', + 'range.bar.color': '#666', + 'range.subbar.color': '#d1d1d1', + + 'range.disabledPointer.color': '#414141', + 'range.disabledBar.color': '#282828', + 'range.disabledSubbar.color': '#414141', + + 'range.value.color': '#fff', + 'range.value.fontWeight': 'lighter', + 'range.value.fontSize': '11px', + 'range.value.border': '1px solid #353535', + 'range.value.backgroundColor': '#151515', + 'range.title.color': '#fff', + 'range.title.fontWeight': 'lighter', + + // colorpicker style + 'colorpicker.button.border': '1px solid #1e1e1e', + 'colorpicker.title.color': '#fff' +}; + */ +/* harmony default export */ var standard = ({ + 'common.bi.image': 'https://uicdn.toast.com/toastui/img/tui-image-editor-bi.png', + 'common.bisize.width': '251px', + 'common.bisize.height': '21px', + 'common.backgroundImage': 'none', + 'common.backgroundColor': '#1e1e1e', + 'common.border': '0px', + // header + 'header.backgroundImage': 'none', + 'header.backgroundColor': 'transparent', + 'header.border': '0px', + // load button + 'loadButton.backgroundColor': '#fff', + 'loadButton.border': '1px solid #ddd', + 'loadButton.color': '#222', + 'loadButton.fontFamily': "'Noto Sans', sans-serif", + 'loadButton.fontSize': '12px', + // download button + 'downloadButton.backgroundColor': '#fdba3b', + 'downloadButton.border': '1px solid #fdba3b', + 'downloadButton.color': '#fff', + 'downloadButton.fontFamily': "'Noto Sans', sans-serif", + 'downloadButton.fontSize': '12px', + // main icons + 'menu.normalIcon.color': '#8a8a8a', + 'menu.activeIcon.color': '#555555', + 'menu.disabledIcon.color': '#434343', + 'menu.hoverIcon.color': '#e9e9e9', + // submenu icons + 'submenu.normalIcon.color': '#8a8a8a', + 'submenu.activeIcon.color': '#e9e9e9', + 'menu.iconSize.width': '24px', + 'menu.iconSize.height': '24px', + 'submenu.iconSize.width': '32px', + 'submenu.iconSize.height': '32px', + // submenu primary color + 'submenu.backgroundColor': '#1e1e1e', + 'submenu.partition.color': '#3c3c3c', + // submenu labels + 'submenu.normalLabel.color': '#8a8a8a', + 'submenu.normalLabel.fontWeight': 'lighter', + 'submenu.activeLabel.color': '#fff', + 'submenu.activeLabel.fontWeight': 'lighter', + // checkbox style + 'checkbox.border': '0px', + 'checkbox.backgroundColor': '#fff', + // range style + 'range.pointer.color': '#fff', + 'range.bar.color': '#666', + 'range.subbar.color': '#d1d1d1', + 'range.disabledPointer.color': '#414141', + 'range.disabledBar.color': '#282828', + 'range.disabledSubbar.color': '#414141', + 'range.value.color': '#fff', + 'range.value.fontWeight': 'lighter', + 'range.value.fontSize': '11px', + 'range.value.border': '1px solid #353535', + 'range.value.backgroundColor': '#151515', + 'range.title.color': '#fff', + 'range.title.fontWeight': 'lighter', + // colorpicker style + 'colorpicker.button.border': '1px solid #1e1e1e', + 'colorpicker.title.color': '#fff' +}); +// EXTERNAL MODULE: ./src/svg/default.svg +var svg_default = __webpack_require__(2534); +;// CONCATENATED MODULE: ./src/js/ui/theme/theme.js + + + + + + + + + + + + +/** + * Theme manager + * @class + * @param {Object} customTheme - custom theme + * @ignore + */ + +var Theme = /*#__PURE__*/function () { + function Theme(customTheme) { + _classCallCheck(this, Theme); + + this.styles = this._changeToObject(extend_default()({}, standard, customTheme)); + styleLoad(this._styleMaker()); + + this._loadDefaultSvgIcon(); + } + /** + * Get a Style cssText or StyleObject + * @param {string} type - style type + * @returns {string|object} - cssText or StyleObject + */ + // eslint-disable-next-line complexity + + + _createClass(Theme, [{ + key: "getStyle", + value: function getStyle(type) { + var result = null; + var firstProperty = type.replace(/\..+$/, ''); + var option = this.styles[type]; + + switch (type) { + case 'common.bi': + result = this.styles[type].image; + break; + + case 'menu.icon': + result = { + active: this.styles["".concat(firstProperty, ".activeIcon")], + normal: this.styles["".concat(firstProperty, ".normalIcon")], + hover: this.styles["".concat(firstProperty, ".hoverIcon")], + disabled: this.styles["".concat(firstProperty, ".disabledIcon")] + }; + break; + + case 'submenu.icon': + result = { + active: this.styles["".concat(firstProperty, ".activeIcon")], + normal: this.styles["".concat(firstProperty, ".normalIcon")] + }; + break; + + case 'submenu.label': + result = { + active: this._makeCssText(this.styles["".concat(firstProperty, ".activeLabel")]), + normal: this._makeCssText(this.styles["".concat(firstProperty, ".normalLabel")]) + }; + break; + + case 'submenu.partition': + result = { + vertical: this._makeCssText(extend_default()({}, option, { + borderLeft: "1px solid ".concat(option.color) + })), + horizontal: this._makeCssText(extend_default()({}, option, { + borderBottom: "1px solid ".concat(option.color) + })) + }; + break; + + case 'range.disabledPointer': + case 'range.disabledBar': + case 'range.disabledSubbar': + case 'range.pointer': + case 'range.bar': + case 'range.subbar': + option.backgroundColor = option.color; + result = this._makeCssText(option); + break; + + default: + result = this._makeCssText(option); + break; + } + + return result; + } + /** + * Make css resource + * @returns {string} - serialized css text + * @private + */ + + }, { + key: "_styleMaker", + value: function _styleMaker() { + var submenuLabelStyle = this.getStyle('submenu.label'); + var submenuPartitionStyle = this.getStyle('submenu.partition'); + return style({ + subMenuLabelActive: submenuLabelStyle.active, + subMenuLabelNormal: submenuLabelStyle.normal, + submenuPartitionVertical: submenuPartitionStyle.vertical, + submenuPartitionHorizontal: submenuPartitionStyle.horizontal, + biSize: this.getStyle('common.bisize'), + subMenuRangeTitle: this.getStyle('range.title'), + submenuRangePointer: this.getStyle('range.pointer'), + submenuRangeBar: this.getStyle('range.bar'), + submenuRangeSubbar: this.getStyle('range.subbar'), + submenuDisabledRangePointer: this.getStyle('range.disabledPointer'), + submenuDisabledRangeBar: this.getStyle('range.disabledBar'), + submenuDisabledRangeSubbar: this.getStyle('range.disabledSubbar'), + submenuRangeValue: this.getStyle('range.value'), + submenuColorpickerTitle: this.getStyle('colorpicker.title'), + submenuColorpickerButton: this.getStyle('colorpicker.button'), + submenuCheckbox: this.getStyle('checkbox'), + menuIconSize: this.getStyle('menu.iconSize'), + submenuIconSize: this.getStyle('submenu.iconSize'), + menuIconStyle: this.getStyle('menu.icon'), + submenuIconStyle: this.getStyle('submenu.icon') + }); + } + /** + * Change to low dimensional object. + * @param {object} styleOptions - style object of user interface + * @returns {object} low level object for style apply + * @private + */ + + }, { + key: "_changeToObject", + value: function _changeToObject(styleOptions) { + var styleObject = {}; + forEach_default()(styleOptions, function (value, key) { + var keyExplode = key.match(/^(.+)\.([a-z]+)$/i); + + var _keyExplode = _slicedToArray(keyExplode, 3), + property = _keyExplode[1], + subProperty = _keyExplode[2]; + + if (!styleObject[property]) { + styleObject[property] = {}; + } + + styleObject[property][subProperty] = value; + }); + return styleObject; + } + /** + * Style object to Csstext serialize + * @param {object} styleObject - style object + * @returns {string} - css text string + * @private + */ + + }, { + key: "_makeCssText", + value: function _makeCssText(styleObject) { + var _this = this; + + var converterStack = []; + forEach_default()(styleObject, function (value, key) { + var _context, _context2; + + if (index_of_default()(_context = ['backgroundImage']).call(_context, key) > -1 && value !== 'none') { + value = "url(".concat(value, ")"); + } + + converterStack.push(concat_default()(_context2 = "".concat(_this._toUnderScore(key), ": ")).call(_context2, value)); + }); + return converterStack.join(';'); + } + /** + * Camel key string to Underscore string + * @param {string} targetString - change target + * @returns {string} + * @private + */ + + }, { + key: "_toUnderScore", + value: function _toUnderScore(targetString) { + return targetString.replace(/([A-Z])/g, function ($0, $1) { + return "-".concat($1.toLowerCase()); + }); + } + /** + * Load default svg icon + * @private + */ + + }, { + key: "_loadDefaultSvgIcon", + value: function _loadDefaultSvgIcon() { + if (!document.getElementById('tui-image-editor-svg-default-icons')) { + var parser = new DOMParser(); + var encodedURI = svg_default.replace(/data:image\/svg\+xml;base64,/, ''); + var dom = parser.parseFromString(atob(encodedURI), 'text/xml'); + document.body.appendChild(dom.documentElement); + } + } + /** + * Make className for svg icon + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + + }, { + key: "_makeIconClassName", + value: function _makeIconClassName(iconType, isSubmenu) { + var iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); + var _iconStyleInfo$iconTy = iconStyleInfo[iconType], + path = _iconStyleInfo$iconTy.path, + name = _iconStyleInfo$iconTy.name; + return path && name ? iconType : "".concat(iconType, " use-default"); + } + /** + * Make svg use link path name + * @param {string} iconType - normal' or 'active' or 'hover' or 'disabled + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + + }, { + key: "_makeSvgIconPrefix", + value: function _makeSvgIconPrefix(iconType, isSubmenu) { + var _context3; + + var iconStyleInfo = isSubmenu ? this.getStyle('submenu.icon') : this.getStyle('menu.icon'); + var _iconStyleInfo$iconTy2 = iconStyleInfo[iconType], + path = _iconStyleInfo$iconTy2.path, + name = _iconStyleInfo$iconTy2.name; + return path && name ? concat_default()(_context3 = "".concat(path, "#")).call(_context3, name, "-") : '#'; + } + /** + * Make svg use link path name + * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled + * @param {string} menuName - menu name + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + * @private + */ + + }, { + key: "_makeSvgItem", + value: function _makeSvgItem(useIconTypes, menuName, isSubmenu) { + var _this2 = this; + + return map_default()(useIconTypes).call(useIconTypes, function (iconType) { + var _context4, _context5; + + var svgIconPrefix = _this2._makeSvgIconPrefix(iconType, isSubmenu); + + var iconName = _this2._toUnderScore(menuName); + + var svgIconClassName = _this2._makeIconClassName(iconType, isSubmenu); + + return concat_default()(_context4 = concat_default()(_context5 = ""); + }).join(''); + } + /** + * Make svg icon set + * @param {Array.} useIconTypes - normal' or 'active' or 'hover' or 'disabled + * @param {string} menuName - menu name + * @param {boolean} isSubmenu - submenu icon or not. + * @returns {string} + */ + + }, { + key: "makeMenSvgIconSet", + value: function makeMenSvgIconSet(useIconTypes, menuName) { + var _context6; + + var isSubmenu = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; + return concat_default()(_context6 = "")).call(_context6, this._makeSvgItem(useIconTypes, menuName, isSubmenu), ""); + } + }]); + + return Theme; +}(); + +/* harmony default export */ var theme = (Theme); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/reflect/construct.js +var construct = __webpack_require__(9146); +var construct_default = /*#__PURE__*/__webpack_require__.n(construct); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/object/create.js +var object_create = __webpack_require__(6623); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/object/set-prototype-of.js +var set_prototype_of = __webpack_require__(4230); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/setPrototypeOf.js + +function _setPrototypeOf(o, p) { + _setPrototypeOf = set_prototype_of || function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + + return _setPrototypeOf(o, p); +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/inherits.js + + +function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function"); + } + + subClass.prototype = object_create(superClass && superClass.prototype, { + constructor: { + value: subClass, + writable: true, + configurable: true + } + }); + if (superClass) _setPrototypeOf(subClass, superClass); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/symbol/iterator.js +var iterator = __webpack_require__(3742); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/typeof.js + + +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof symbol === "function" && typeof iterator === "symbol") { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && typeof symbol === "function" && obj.constructor === symbol && obj !== symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/assertThisInitialized.js +function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; +} +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/possibleConstructorReturn.js + + +function _possibleConstructorReturn(self, call) { + if (call && (_typeof(call) === "object" || typeof call === "function")) { + return call; + } else if (call !== void 0) { + throw new TypeError("Derived constructors may only return object or undefined"); + } + + return _assertThisInitialized(self); +} +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js/object/get-prototype-of.js +var get_prototype_of = __webpack_require__(9856); +;// CONCATENATED MODULE: ../../node_modules/@babel/runtime-corejs3/helpers/esm/getPrototypeOf.js + + +function _getPrototypeOf(o) { + _getPrototypeOf = set_prototype_of ? get_prototype_of : function _getPrototypeOf(o) { + return o.__proto__ || get_prototype_of(o); + }; + return _getPrototypeOf(o); +} +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/collection/forEachArray.js +var forEachArray = __webpack_require__(6092); +var forEachArray_default = /*#__PURE__*/__webpack_require__.n(forEachArray); +// EXTERNAL MODULE: external {"commonjs":"tui-color-picker","commonjs2":"tui-color-picker","amd":"tui-color-picker","root":["tui","colorPicker"]} +var external_commonjs_tui_color_picker_commonjs2_tui_color_picker_amd_tui_color_picker_root_tui_colorPicker_ = __webpack_require__(4858); +var external_commonjs_tui_color_picker_commonjs2_tui_color_picker_amd_tui_color_picker_root_tui_colorPicker_default = /*#__PURE__*/__webpack_require__.n(external_commonjs_tui_color_picker_commonjs2_tui_color_picker_amd_tui_color_picker_root_tui_colorPicker_); +;// CONCATENATED MODULE: ./src/js/ui/tools/colorpicker.js + + + + + + +var PICKER_COLOR = ['#000000', '#2a2a2a', '#545454', '#7e7e7e', '#a8a8a8', '#d2d2d2', '#ffffff', '', '#ff4040', '#ff6518', '#ffbb3b', '#03bd9e', '#00a9ff', '#515ce6', '#9e5fff', '#ff5583']; +/** + * Colorpicker control class + * @class + * @ignore + */ + +var Colorpicker = /*#__PURE__*/function () { + function Colorpicker(colorpickerElement, _ref) { + var _ref$defaultColor = _ref.defaultColor, + defaultColor = _ref$defaultColor === void 0 ? '#7e7e7e' : _ref$defaultColor, + _ref$toggleDirection = _ref.toggleDirection, + toggleDirection = _ref$toggleDirection === void 0 ? 'up' : _ref$toggleDirection, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Colorpicker); + + this.colorpickerElement = colorpickerElement; + this.usageStatistics = usageStatistics; + this._show = false; + this._colorpickerElement = colorpickerElement; + this._toggleDirection = toggleDirection; + + this._makePickerButtonElement(defaultColor); + + this._makePickerLayerElement(colorpickerElement, colorpickerElement.getAttribute('title')); + + this._color = defaultColor; + this.picker = external_commonjs_tui_color_picker_commonjs2_tui_color_picker_amd_tui_color_picker_root_tui_colorPicker_default().create({ + container: this.pickerElement, + preset: PICKER_COLOR, + color: defaultColor, + usageStatistics: this.usageStatistics + }); + + this._addEvent(); + } + /** + * Destroys the instance. + */ + + + _createClass(Colorpicker, [{ + key: "destroy", + value: function destroy() { + var _this = this; + + this._removeEvent(); + + this.picker.destroy(); + this.colorpickerElement.innerHTML = ''; + forEach_default()(this, function (value, key) { + _this[key] = null; + }); + } + /** + * Get color + * @returns {Number} color value + */ + + }, { + key: "color", + get: function get() { + return this._color; + } + /** + * Set color + * @param {string} color color value + */ + , + set: function set(color) { + this._color = color; + + this._changeColorElement(color); + } + /** + * Change color element + * @param {string} color color value + * #private + */ + + }, { + key: "_changeColorElement", + value: function _changeColorElement(color) { + if (color) { + this.colorElement.classList.remove('transparent'); + this.colorElement.style.backgroundColor = color; + } else { + this.colorElement.style.backgroundColor = '#fff'; + this.colorElement.classList.add('transparent'); + } + } + /** + * Make picker button element + * @param {string} defaultColor color value + * @private + */ + + }, { + key: "_makePickerButtonElement", + value: function _makePickerButtonElement(defaultColor) { + this.colorpickerElement.classList.add('tui-image-editor-button'); + this.colorElement = document.createElement('div'); + this.colorElement.className = 'color-picker-value'; + + if (defaultColor) { + this.colorElement.style.backgroundColor = defaultColor; + } else { + this.colorElement.classList.add('transparent'); + } + } + /** + * Make picker layer element + * @param {HTMLElement} colorpickerElement color picker element + * @param {string} title picker title + * @private + */ + + }, { + key: "_makePickerLayerElement", + value: function _makePickerLayerElement(colorpickerElement, title) { + var label = document.createElement('label'); + var triangle = document.createElement('div'); + this.pickerControl = document.createElement('div'); + this.pickerControl.className = 'color-picker-control'; + this.pickerElement = document.createElement('div'); + this.pickerElement.className = 'color-picker'; + label.innerHTML = title; + triangle.className = 'triangle'; + this.pickerControl.appendChild(this.pickerElement); + this.pickerControl.appendChild(triangle); + colorpickerElement.appendChild(this.pickerControl); + colorpickerElement.appendChild(this.colorElement); + colorpickerElement.appendChild(label); + } + /** + * Add event + * @private + */ + + }, { + key: "_addEvent", + value: function _addEvent() { + var _this2 = this, + _context; + + this.picker.on('selectColor', function (value) { + _this2._changeColorElement(value.color); + + _this2._color = value.color; + + _this2.fire('change', value.color); + }); + this.eventHandler = { + pickerToggle: bind_default()(_context = this._pickerToggleEventHandler).call(_context, this), + pickerHide: function pickerHide() { + return _this2.hide(); + } + }; + this.colorpickerElement.addEventListener('click', this.eventHandler.pickerToggle); + document.body.addEventListener('click', this.eventHandler.pickerHide); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this.colorpickerElement.removeEventListener('click', this.eventHandler.pickerToggle); + document.body.removeEventListener('click', this.eventHandler.pickerHide); + this.picker.off(); + } + /** + * Picker toggle event handler + * @param {object} event - change event + * @private + */ + + }, { + key: "_pickerToggleEventHandler", + value: function _pickerToggleEventHandler(event) { + var target = event.target; + + var isInPickerControl = target && this._isElementInColorPickerControl(target); + + if (!isInPickerControl || isInPickerControl && this._isPaletteButton(target)) { + this._show = !this._show; + this.pickerControl.style.display = this._show ? 'block' : 'none'; + + this._setPickerControlPosition(); + + this.fire('changeShow', this); + } + + event.stopPropagation(); + } + /** + * Check hex input or not + * @param {Element} target - Event target element + * @returns {boolean} + * @private + */ + + }, { + key: "_isPaletteButton", + value: function _isPaletteButton(target) { + return target.className === 'tui-colorpicker-palette-button'; + } + /** + * Check given element is in pickerControl element + * @param {Element} element - element to check + * @returns {boolean} + * @private + */ + + }, { + key: "_isElementInColorPickerControl", + value: function _isElementInColorPickerControl(element) { + var parentNode = element; + + while (parentNode !== document.body) { + if (!parentNode) { + break; + } + + if (parentNode === this.pickerControl) { + return true; + } + + parentNode = parentNode.parentNode; + } + + return false; + } + }, { + key: "hide", + value: function hide() { + this._show = false; + this.pickerControl.style.display = 'none'; + } + /** + * Set picker control position + * @private + */ + + }, { + key: "_setPickerControlPosition", + value: function _setPickerControlPosition() { + var controlStyle = this.pickerControl.style; + var halfPickerWidth = this._colorpickerElement.clientWidth / 2 + 2; + var left = this.pickerControl.offsetWidth / 2 - halfPickerWidth; + var top = (this.pickerControl.offsetHeight + 10) * -1; + + if (this._toggleDirection === 'down') { + top = 30; + } + + controlStyle.top = "".concat(top, "px"); + controlStyle.left = "-".concat(left, "px"); + } + }]); + + return Colorpicker; +}(); + +customEvents_default().mixin(Colorpicker); +/* harmony default export */ var colorpicker = (Colorpicker); +;// CONCATENATED MODULE: ./src/js/ui/tools/range.js + + + + + + + + + +var INPUT_FILTER_REGEXP = /(-?)([0-9]*)[^0-9]*([0-9]*)/g; +/** + * Range control class + * @class + * @ignore + */ + +var Range = /*#__PURE__*/function () { + /** + * @constructor + * @extends {View} + * @param {Object} rangeElements - Html resources for creating sliders + * @param {HTMLElement} rangeElements.slider - b + * @param {HTMLElement} [rangeElements.input] - c + * @param {Object} options - Slider make options + * @param {number} options.min - min value + * @param {number} options.max - max value + * @param {number} options.value - default value + * @param {number} [options.useDecimal] - Decimal point processing. + * @param {boolean} [options.realTimeEvent] - Reflect live events. + */ + function Range(rangeElements) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7; + + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Range); + + this._value = options.value || 0; + this.rangeElement = rangeElements.slider; + this.rangeInputElement = rangeElements.input; + + this._drawRangeElement(); + + this.rangeWidth = this._getRangeWidth(); + this._min = options.min || 0; + this._max = options.max || 100; + this._useDecimal = options.useDecimal; + this._absMax = this._min * -1 + this._max; + this.realTimeEvent = options.realTimeEvent || false; + this._userInputTimer = null; + this.eventHandler = { + startChangingSlide: bind_default()(_context = this._startChangingSlide).call(_context, this), + stopChangingSlide: bind_default()(_context2 = this._stopChangingSlide).call(_context2, this), + changeSlide: bind_default()(_context3 = this._changeSlide).call(_context3, this), + changeSlideFinally: bind_default()(_context4 = this._changeSlideFinally).call(_context4, this), + changeInput: bind_default()(_context5 = this._changeInput).call(_context5, this), + changeInputFinally: bind_default()(_context6 = this._changeValueWithInput).call(_context6, this, true), + changeInputWithArrow: bind_default()(_context7 = this._changeValueWithInputKeyEvent).call(_context7, this) + }; + + this._addClickEvent(); + + this._addDragEvent(); + + this._addInputEvent(); + + this.value = options.value; + this.trigger('change'); + } + /** + * Destroys the instance. + */ + + + _createClass(Range, [{ + key: "destroy", + value: function destroy() { + var _this = this; + + this._removeClickEvent(); + + this._removeDragEvent(); + + this._removeInputEvent(); + + this.rangeElement.innerHTML = ''; + forEach_default()(this, function (value, key) { + _this[key] = null; + }); + } + }, { + key: "max", + get: function get() { + return this._max; + } + /** + * Set range max value and re position cursor + * @param {number} maxValue - max value + */ + , + set: function set(maxValue) { + this._max = maxValue; + this._absMax = this._min * -1 + this._max; + this.value = this._value; + } + }, { + key: "min", + get: function get() { + return this._min; + } + /** + * Set range min value and re position cursor + * @param {number} minValue - min value + */ + , + set: function set(minValue) { + this._min = minValue; + this.max = this._max; + } + /** + * Get range value + * @returns {Number} range value + */ + + }, { + key: "value", + get: function get() { + return this._value; + } + /** + * Set range value + * @param {Number} value range value + */ + , + set: function set(value) { + value = this._useDecimal ? value : toInteger(value); + var absValue = value - this._min; + var leftPosition = absValue * this.rangeWidth / this._absMax; + + if (this.rangeWidth < leftPosition) { + leftPosition = this.rangeWidth; + } + + this.pointer.style.left = "".concat(leftPosition, "px"); + this.subbar.style.right = "".concat(this.rangeWidth - leftPosition, "px"); + this._value = value; + + if (this.rangeInputElement) { + this.rangeInputElement.value = value; + } + } + /** + * event trigger + * @param {string} type - type + */ + + }, { + key: "trigger", + value: function trigger(type) { + this.fire(type, this._value); + } + /** + * Calculate slider width + * @returns {number} - slider width + */ + + }, { + key: "_getRangeWidth", + value: function _getRangeWidth() { + var getElementWidth = function getElementWidth(element) { + return toInteger(window.getComputedStyle(element, null).width); + }; + + return getElementWidth(this.rangeElement) - getElementWidth(this.pointer); + } + /** + * Make range element + * @private + */ + + }, { + key: "_drawRangeElement", + value: function _drawRangeElement() { + this.rangeElement.classList.add('tui-image-editor-range'); + this.bar = document.createElement('div'); + this.bar.className = 'tui-image-editor-virtual-range-bar'; + this.subbar = document.createElement('div'); + this.subbar.className = 'tui-image-editor-virtual-range-subbar'; + this.pointer = document.createElement('div'); + this.pointer.className = 'tui-image-editor-virtual-range-pointer'; + this.bar.appendChild(this.subbar); + this.bar.appendChild(this.pointer); + this.rangeElement.appendChild(this.bar); + } + /** + * Add range input editing event + * @private + */ + + }, { + key: "_addInputEvent", + value: function _addInputEvent() { + if (this.rangeInputElement) { + this.rangeInputElement.addEventListener('keydown', this.eventHandler.changeInputWithArrow); + this.rangeInputElement.addEventListener('keydown', this.eventHandler.changeInput); + this.rangeInputElement.addEventListener('blur', this.eventHandler.changeInputFinally); + } + } + /** + * Remove range input editing event + * @private + */ + + }, { + key: "_removeInputEvent", + value: function _removeInputEvent() { + if (this.rangeInputElement) { + this.rangeInputElement.removeEventListener('keydown', this.eventHandler.changeInputWithArrow); + this.rangeInputElement.removeEventListener('keydown', this.eventHandler.changeInput); + this.rangeInputElement.removeEventListener('blur', this.eventHandler.changeInputFinally); + } + } + /** + * change angle event + * @param {object} event - key event + * @private + */ + + }, { + key: "_changeValueWithInputKeyEvent", + value: function _changeValueWithInputKeyEvent(event) { + var _context8; + + var keyCode = event.keyCode, + target = event.target; + + if (index_of_default()(_context8 = [keyCodes.ARROW_UP, keyCodes.ARROW_DOWN]).call(_context8, keyCode) < 0) { + return; + } + + var value = Number(target.value); + value = this._valueUpDownForKeyEvent(value, keyCode); + var unChanged = value < this._min || value > this._max; + + if (!unChanged) { + var clampValue = clamp(value, this._min, this.max); + this.value = clampValue; + this.fire('change', clampValue, false); + } + } + /** + * value up down for input + * @param {number} value - original value number + * @param {number} keyCode - input event key code + * @returns {number} value - changed value + * @private + */ + + }, { + key: "_valueUpDownForKeyEvent", + value: function _valueUpDownForKeyEvent(value, keyCode) { + var step = this._useDecimal ? 0.1 : 1; + + if (keyCode === keyCodes.ARROW_UP) { + value += step; + } else if (keyCode === keyCodes.ARROW_DOWN) { + value -= step; + } + + return value; + } + }, { + key: "_changeInput", + value: function _changeInput(event) { + var _this2 = this; + + clearTimeout(this._userInputTimer); + var keyCode = event.keyCode; + + if (keyCode < keyCodes.DIGIT_0 || keyCode > keyCodes.DIGIT_9) { + event.preventDefault(); + return; + } + + this._userInputTimer = set_timeout_default()(function () { + _this2._inputSetValue(event.target.value); + }, 350); + } + }, { + key: "_inputSetValue", + value: function _inputSetValue(stringValue) { + var value = this._useDecimal ? Number(stringValue) : toInteger(stringValue); + value = clamp(value, this._min, this.max); + this.value = value; + this.fire('change', value, true); + } + /** + * change angle event + * @param {boolean} isLast - Is last change + * @param {object} event - key event + * @private + */ + + }, { + key: "_changeValueWithInput", + value: function _changeValueWithInput(isLast, event) { + var _context9; + + var keyCode = event.keyCode, + target = event.target; + + if (index_of_default()(_context9 = [keyCodes.ARROW_UP, keyCodes.ARROW_DOWN]).call(_context9, keyCode) >= 0) { + return; + } + + var stringValue = this._filterForInputText(target.value); + + var waitForChange = !stringValue || isNaN(stringValue); + target.value = stringValue; + + if (!waitForChange) { + this._inputSetValue(stringValue); + } + } + /** + * Add Range click event + * @private + */ + + }, { + key: "_addClickEvent", + value: function _addClickEvent() { + this.rangeElement.addEventListener('click', this.eventHandler.changeSlideFinally); + } + /** + * Remove Range click event + * @private + */ + + }, { + key: "_removeClickEvent", + value: function _removeClickEvent() { + this.rangeElement.removeEventListener('click', this.eventHandler.changeSlideFinally); + } + /** + * Add Range drag event + * @private + */ + + }, { + key: "_addDragEvent", + value: function _addDragEvent() { + this.pointer.addEventListener('mousedown', this.eventHandler.startChangingSlide); + } + /** + * Remove Range drag event + * @private + */ + + }, { + key: "_removeDragEvent", + value: function _removeDragEvent() { + this.pointer.removeEventListener('mousedown', this.eventHandler.startChangingSlide); + } + /** + * change angle event + * @param {object} event - change event + * @private + */ + + }, { + key: "_changeSlide", + value: function _changeSlide(event) { + var changePosition = event.screenX; + var diffPosition = changePosition - this.firstPosition; + var touchPx = this.firstLeft + diffPosition; + touchPx = touchPx > this.rangeWidth ? this.rangeWidth : touchPx; + touchPx = touchPx < 0 ? 0 : touchPx; + this.pointer.style.left = "".concat(touchPx, "px"); + this.subbar.style.right = "".concat(this.rangeWidth - touchPx, "px"); + var ratio = touchPx / this.rangeWidth; + var resultValue = this._absMax * ratio + this._min; + var value = this._useDecimal ? resultValue : toInteger(resultValue); + var isValueChanged = this.value !== value; + + if (isValueChanged) { + this.value = value; + + if (this.realTimeEvent) { + this.fire('change', this._value, false); + } + } + } + }, { + key: "_changeSlideFinally", + value: function _changeSlideFinally(event) { + event.stopPropagation(); + + if (event.target.className !== 'tui-image-editor-range') { + return; + } + + var touchPx = event.offsetX; + var ratio = touchPx / this.rangeWidth; + var value = this._absMax * ratio + this._min; + this.pointer.style.left = "".concat(ratio * this.rangeWidth, "px"); + this.subbar.style.right = "".concat((1 - ratio) * this.rangeWidth, "px"); + this.value = value; + this.fire('change', value, true); + } + }, { + key: "_startChangingSlide", + value: function _startChangingSlide(event) { + this.firstPosition = event.screenX; + this.firstLeft = toInteger(this.pointer.style.left) || 0; + document.addEventListener('mousemove', this.eventHandler.changeSlide); + document.addEventListener('mouseup', this.eventHandler.stopChangingSlide); + } + /** + * stop change angle event + * @private + */ + + }, { + key: "_stopChangingSlide", + value: function _stopChangingSlide() { + this.fire('change', this._value, true); + document.removeEventListener('mousemove', this.eventHandler.changeSlide); + document.removeEventListener('mouseup', this.eventHandler.stopChangingSlide); + } + /** + * Unnecessary string filtering. + * @param {string} inputValue - origin string of input + * @returns {string} filtered string + * @private + */ + + }, { + key: "_filterForInputText", + value: function _filterForInputText(inputValue) { + return inputValue.replace(INPUT_FILTER_REGEXP, '$1$2$3'); + } + }]); + + return Range; +}(); + +customEvents_default().mixin(Range); +/* harmony default export */ var range = (Range); +;// CONCATENATED MODULE: ./src/js/ui/submenuBase.js + + + + + +/** + * Submenu Base Class + * @class + * @ignore + */ + +var Submenu = /*#__PURE__*/function () { + /** + * @param {HTMLElement} subMenuElement - submenu dom element + * @param {Locale} locale - translate text + * @param {string} name - name of sub menu + * @param {Object} iconStyle - style of icon + * @param {string} menuBarPosition - position of menu + * @param {*} templateHtml - template for SubMenuElement + * @param {boolean} [usageStatistics=false] - template for SubMenuElement + */ + function Submenu(subMenuElement, _ref) { + var locale = _ref.locale, + name = _ref.name, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + templateHtml = _ref.templateHtml, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Submenu); + + this.subMenuElement = subMenuElement; + this.menuBarPosition = menuBarPosition; + this.toggleDirection = menuBarPosition === 'top' ? 'down' : 'up'; + this.colorPickerControls = []; + this.usageStatistics = usageStatistics; + this.eventHandler = {}; + + this._makeSubMenuElement({ + locale: locale, + name: name, + makeSvgIcon: makeSvgIcon, + templateHtml: templateHtml + }); + } + /** + * editor dom ui query selector + * @param {string} selectName - query selector string name + * @returns {HTMLElement} + */ + + + _createClass(Submenu, [{ + key: "selector", + value: function selector(selectName) { + return this.subMenuElement.querySelector(selectName); + } + /** + * change show state change for colorpicker instance + * @param {Colorpicker} occurredControl - target Colorpicker Instance + */ + + }, { + key: "colorPickerChangeShow", + value: function colorPickerChangeShow(occurredControl) { + var _context; + + for_each_default()(_context = this.colorPickerControls).call(_context, function (pickerControl) { + if (occurredControl !== pickerControl) { + pickerControl.hide(); + } + }); + } + /** + * Get button type + * @param {HTMLElement} button - event target element + * @param {array} buttonNames - Array of button names + * @returns {string} - button type + */ + + }, { + key: "getButtonType", + value: function getButtonType(button, buttonNames) { + return button.className.match(RegExp("(".concat(buttonNames.join('|'), ")")))[0]; + } + /** + * Get button type + * @param {HTMLElement} target - event target element + * @param {string} removeClass - remove class name + * @param {string} addClass - add class name + */ + + }, { + key: "changeClass", + value: function changeClass(target, removeClass, addClass) { + target.classList.remove(removeClass); + target.classList.add(addClass); + } + /** + * Interface method whose implementation is optional. + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() {} + /** + * Interface method whose implementation is optional. + * Executed when the menu starts. + */ + + }, { + key: "changeStartMode", + value: function changeStartMode() {} + /** + * Make submenu dom element + * @param {Locale} locale - translate text + * @param {string} name - submenu name + * @param {Object} iconStyle - icon style + * @param {*} templateHtml - template for SubMenuElement + * @private + */ + + }, { + key: "_makeSubMenuElement", + value: function _makeSubMenuElement(_ref2) { + var locale = _ref2.locale, + name = _ref2.name, + iconStyle = _ref2.iconStyle, + makeSvgIcon = _ref2.makeSvgIcon, + templateHtml = _ref2.templateHtml; + var iconSubMenu = document.createElement('div'); + iconSubMenu.className = "tui-image-editor-menu-".concat(name); + iconSubMenu.innerHTML = templateHtml({ + locale: locale, + iconStyle: iconStyle, + makeSvgIcon: makeSvgIcon + }); + this.subMenuElement.appendChild(iconSubMenu); + } + }, { + key: "_onStartEditingInputBox", + value: function _onStartEditingInputBox() { + this.fire(eventNames.INPUT_BOX_EDITING_STARTED); + } + }, { + key: "_onStopEditingInputBox", + value: function _onStopEditingInputBox() { + this.fire(eventNames.INPUT_BOX_EDITING_STOPPED); + } + }]); + + return Submenu; +}(); + +customEvents_default().mixin(Submenu); +/* harmony default export */ var submenuBase = (Submenu); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/shape.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var shape = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'shape-rectangle', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context7, makeSvgIcon(['normal', 'active'], 'shape-circle', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context5, makeSvgIcon(['normal', 'active'], 'shape-triangle', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n \n
          \n \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/shape.js + + + + + + + + +function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + + +var SHAPE_DEFAULT_OPTION = { + stroke: '#ffbb3b', + fill: '', + strokeWidth: 3 +}; +/** + * Shape ui class + * @class + * @ignore + */ + +var Shape = /*#__PURE__*/function (_Submenu) { + _inherits(Shape, _Submenu); + + var _super = _createSuper(Shape); + + function Shape(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Shape); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'shape', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: shape, + usageStatistics: usageStatistics + }); + _this.type = null; + _this.options = SHAPE_DEFAULT_OPTION; + _this._els = { + shapeSelectButton: _this.selector('.tie-shape-button'), + shapeColorButton: _this.selector('.tie-shape-color-button'), + strokeRange: new range({ + slider: _this.selector('.tie-stroke-range'), + input: _this.selector('.tie-stroke-range-value') + }, defaultShapeStrokeValues), + fillColorpicker: new colorpicker(_this.selector('.tie-color-fill'), { + defaultColor: '', + toggleDirection: _this.toggleDirection, + usageStatistics: _this.usageStatistics + }), + strokeColorpicker: new colorpicker(_this.selector('.tie-color-stroke'), { + defaultColor: '#ffbb3b', + toggleDirection: _this.toggleDirection, + usageStatistics: _this.usageStatistics + }) + }; + + _this.colorPickerControls.push(_this._els.fillColorpicker); + + _this.colorPickerControls.push(_this._els.strokeColorpicker); + + _this.colorPickerInputBoxes = []; + + _this.colorPickerInputBoxes.push(_this._els.fillColorpicker.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX)); + + _this.colorPickerInputBoxes.push(_this._els.strokeColorpicker.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX)); + + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Shape, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._els.strokeRange.destroy(); + + this._els.fillColorpicker.destroy(); + + this._els.strokeColorpicker.destroy(); + + assignmentForDestroy(this); + } + /** + * Add event for shape + * @param {Object} actions - actions for shape + * @param {Function} actions.changeShape - change shape mode + * @param {Function} actions.setDrawingShape - set drawing shape + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, + _context2, + _context3, + _context4, + _context5, + _context6, + _this2 = this; + + this.eventHandler.shapeTypeSelected = bind_default()(_context = this._changeShapeHandler).call(_context, this); + this.actions = actions; + + this._els.shapeSelectButton.addEventListener('click', this.eventHandler.shapeTypeSelected); + + this._els.strokeRange.on('change', bind_default()(_context2 = this._changeStrokeRangeHandler).call(_context2, this)); + + this._els.fillColorpicker.on('change', bind_default()(_context3 = this._changeFillColorHandler).call(_context3, this)); + + this._els.strokeColorpicker.on('change', bind_default()(_context4 = this._changeStrokeColorHandler).call(_context4, this)); + + this._els.fillColorpicker.on('changeShow', bind_default()(_context5 = this.colorPickerChangeShow).call(_context5, this)); + + this._els.strokeColorpicker.on('changeShow', bind_default()(_context6 = this.colorPickerChangeShow).call(_context6, this)); + + forEachArray_default()(this.colorPickerInputBoxes, function (inputBox) { + var _context7, _context8; + + inputBox.addEventListener(eventNames.FOCUS, bind_default()(_context7 = _this2._onStartEditingInputBox).call(_context7, _this2)); + inputBox.addEventListener(eventNames.BLUR, bind_default()(_context8 = _this2._onStopEditingInputBox).call(_context8, _this2)); + }, this); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + var _this3 = this; + + this._els.shapeSelectButton.removeEventListener('click', this.eventHandler.shapeTypeSelected); + + this._els.strokeRange.off(); + + this._els.fillColorpicker.off(); + + this._els.strokeColorpicker.off(); + + forEachArray_default()(this.colorPickerInputBoxes, function (inputBox) { + var _context9, _context10; + + inputBox.removeEventListener(eventNames.FOCUS, bind_default()(_context9 = _this3._onStartEditingInputBox).call(_context9, _this3)); + inputBox.removeEventListener(eventNames.BLUR, bind_default()(_context10 = _this3._onStopEditingInputBox).call(_context10, _this3)); + }, this); + } + /** + * Set Shape status + * @param {Object} options - options of shape status + * @param {string} strokeWidth - stroke width + * @param {string} strokeColor - stroke color + * @param {string} fillColor - fill color + */ + + }, { + key: "setShapeStatus", + value: function setShapeStatus(_ref2) { + var strokeWidth = _ref2.strokeWidth, + strokeColor = _ref2.strokeColor, + fillColor = _ref2.fillColor; + this._els.strokeRange.value = strokeWidth; + this._els.strokeColorpicker.color = strokeColor; + this._els.fillColorpicker.color = fillColor; + this.options.stroke = strokeColor; + this.options.fill = fillColor; + this.options.strokeWidth = strokeWidth; + this.actions.setDrawingShape(this.type, { + strokeWidth: strokeWidth + }); + } + /** + * Executed when the menu starts. + */ + + }, { + key: "changeStartMode", + value: function changeStartMode() { + this.actions.stopDrawingMode(); + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.type = null; + this.actions.changeSelectableAll(true); + + this._els.shapeSelectButton.classList.remove('circle'); + + this._els.shapeSelectButton.classList.remove('triangle'); + + this._els.shapeSelectButton.classList.remove('rect'); + } + /** + * set range stroke max value + * @param {number} maxValue - expect max value for change + */ + + }, { + key: "setMaxStrokeValue", + value: function setMaxStrokeValue(maxValue) { + var strokeMaxValue = maxValue; + + if (strokeMaxValue <= 0) { + strokeMaxValue = defaultShapeStrokeValues.max; + } + + this._els.strokeRange.max = strokeMaxValue; + } + /** + * Set stroke value + * @param {number} value - expect value for strokeRange change + */ + + }, { + key: "setStrokeValue", + value: function setStrokeValue(value) { + this._els.strokeRange.value = value; + + this._els.strokeRange.trigger('change'); + } + /** + * Get stroke value + * @returns {number} - stroke range value + */ + + }, { + key: "getStrokeValue", + value: function getStrokeValue() { + return this._els.strokeRange.value; + } + /** + * Change icon color + * @param {object} event - add button event object + * @private + */ + + }, { + key: "_changeShapeHandler", + value: function _changeShapeHandler(event) { + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + this.actions.stopDrawingMode(); + this.actions.discardSelection(); + var shapeType = this.getButtonType(button, ['circle', 'triangle', 'rect']); + + if (this.type === shapeType) { + this.changeStandbyMode(); + return; + } + + this.changeStandbyMode(); + this.type = shapeType; + event.currentTarget.classList.add(shapeType); + this.actions.changeSelectableAll(false); + this.actions.modeChange('shape'); + } + } + /** + * Change stroke range + * @param {number} value - stroke range value + * @param {boolean} isLast - Is last change + * @private + */ + + }, { + key: "_changeStrokeRangeHandler", + value: function _changeStrokeRangeHandler(value, isLast) { + this.options.strokeWidth = toInteger(value); + this.actions.changeShape({ + strokeWidth: value + }, !isLast); + this.actions.setDrawingShape(this.type, this.options); + } + /** + * Change shape color + * @param {string} color - fill color + * @private + */ + + }, { + key: "_changeFillColorHandler", + value: function _changeFillColorHandler(color) { + color = color || 'transparent'; + this.options.fill = color; + this.actions.changeShape({ + fill: color + }); + } + /** + * Change shape stroke color + * @param {string} color - fill color + * @private + */ + + }, { + key: "_changeStrokeColorHandler", + value: function _changeStrokeColorHandler(color) { + color = color || 'transparent'; + this.options.stroke = color; + this.actions.changeShape({ + stroke: color + }); + } + }]); + + return Shape; +}(submenuBase); + +/* harmony default export */ var ui_shape = (Shape); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/crop.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var crop = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14, _context15, _context16, _context17; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = concat_default()(_context9 = concat_default()(_context10 = concat_default()(_context11 = concat_default()(_context12 = concat_default()(_context13 = concat_default()(_context14 = concat_default()(_context15 = concat_default()(_context16 = concat_default()(_context17 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'shape-rectangle', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context16, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context14, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context12, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context10, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context8, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context6, makeSvgIcon(['normal', 'active'], 'crop', true), "\n
          \n \n
          \n
        • \n
        • \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n ")).call(_context4, makeSvgIcon(['normal', 'active'], 'apply'), "\n \n
          \n
          \n ")).call(_context2, makeSvgIcon(['normal', 'active'], 'cancel'), "\n \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/crop.js + + + + + + + + + +function crop_createSuper(Derived) { var hasNativeReflectConstruct = crop_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function crop_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + +/** + * Crop ui class + * @class + * @ignore + */ + +var Crop = /*#__PURE__*/function (_Submenu) { + _inherits(Crop, _Submenu); + + var _super = crop_createSuper(Crop); + + function Crop(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Crop); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'crop', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: crop, + usageStatistics: usageStatistics + }); + _this.status = 'active'; + _this._els = { + apply: _this.selector('.tie-crop-button .apply'), + cancel: _this.selector('.tie-crop-button .cancel'), + preset: _this.selector('.tie-crop-preset-button') + }; + _this.defaultPresetButton = _this._els.preset.querySelector('.preset-none'); + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Crop, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + assignmentForDestroy(this); + } + /** + * Add event for crop + * @param {Object} actions - actions for crop + * @param {Function} actions.crop - crop action + * @param {Function} actions.cancel - cancel action + * @param {Function} actions.preset - draw rectzone at a predefined ratio + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2, _context3; + + var apply = bind_default()(_context = this._applyEventHandler).call(_context, this); + + var cancel = bind_default()(_context2 = this._cancelEventHandler).call(_context2, this); + + var cropzonePreset = bind_default()(_context3 = this._cropzonePresetEventHandler).call(_context3, this); + + this.eventHandler = { + apply: apply, + cancel: cancel, + cropzonePreset: cropzonePreset + }; + this.actions = actions; + + this._els.apply.addEventListener('click', apply); + + this._els.cancel.addEventListener('click', cancel); + + this._els.preset.addEventListener('click', cropzonePreset); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this._els.apply.removeEventListener('click', this.eventHandler.apply); + + this._els.cancel.removeEventListener('click', this.eventHandler.cancel); + + this._els.preset.removeEventListener('click', this.eventHandler.cropzonePreset); + } + }, { + key: "_applyEventHandler", + value: function _applyEventHandler() { + this.actions.crop(); + + this._els.apply.classList.remove('active'); + } + }, { + key: "_cancelEventHandler", + value: function _cancelEventHandler() { + this.actions.cancel(); + + this._els.apply.classList.remove('active'); + } + }, { + key: "_cropzonePresetEventHandler", + value: function _cropzonePresetEventHandler(event) { + var button = event.target.closest('.tui-image-editor-button.preset'); + + if (button) { + var _button$className$mat = button.className.match(/preset-[^\s]+/), + _button$className$mat2 = _slicedToArray(_button$className$mat, 1), + presetType = _button$className$mat2[0]; + + this._setPresetButtonActive(button); + + this.actions.preset(presetType); + } + } + /** + * Executed when the menu starts. + */ + + }, { + key: "changeStartMode", + value: function changeStartMode() { + this.actions.modeChange('crop'); + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.actions.stopDrawingMode(); + + this._setPresetButtonActive(); + } + /** + * Change apply button status + * @param {Boolean} enableStatus - apply button status + */ + + }, { + key: "changeApplyButtonStatus", + value: function changeApplyButtonStatus(enableStatus) { + if (enableStatus) { + this._els.apply.classList.add('active'); + } else { + this._els.apply.classList.remove('active'); + } + } + /** + * Set preset button to active status + * @param {HTMLElement} button - event target element + * @private + */ + + }, { + key: "_setPresetButtonActive", + value: function _setPresetButtonActive() { + var button = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultPresetButton; + forEach_default()(this._els.preset.querySelectorAll('.preset'), function (presetButton) { + presetButton.classList.remove('active'); + }); + + if (button) { + button.classList.add('active'); + } + } + }]); + + return Crop; +}(submenuBase); + +/* harmony default export */ var ui_crop = (Crop); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/resize.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var resize = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = "\n
          \n
        • \n
          \n \n
          \n \n
          \n \n
          \n \n
          \n
        • \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n \n
          \n
          \n
        • \n
        • \n
        • \n
          \n
        • \n
        • \n
        • \n
          \n ")).call(_context4, makeSvgIcon(['normal', 'active'], 'apply'), "\n \n
          \n
          \n ")).call(_context2, makeSvgIcon(['normal', 'active'], 'cancel'), "\n \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/resize.js + + + + + + + + +function resize_createSuper(Derived) { var hasNativeReflectConstruct = resize_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function resize_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + +/** + * Resize ui class + * @class + * @ignore + */ + +var Resize = /*#__PURE__*/function (_Submenu) { + _inherits(Resize, _Submenu); + + var _super = resize_createSuper(Resize); + + function Resize(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Resize); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'resize', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: resize, + usageStatistics: usageStatistics + }); + _this.status = 'active'; + _this._lockState = false; + /** + * Original dimensions + * @type {Object} + * @private + */ + + _this._originalDimensions = null; + _this._els = { + widthRange: new range({ + slider: _this.selector('.tie-width-range'), + input: _this.selector('.tie-width-range-value') + }, defaultResizePixelValues), + heightRange: new range({ + slider: _this.selector('.tie-height-range'), + input: _this.selector('.tie-height-range-value') + }, defaultResizePixelValues), + lockAspectRatio: _this.selector('.tie-lock-aspect-ratio'), + apply: _this.selector('.tie-resize-button .apply'), + cancel: _this.selector('.tie-resize-button .cancel') + }; + return _this; + } + /** + * Executed when the menu starts. + */ + + + _createClass(Resize, [{ + key: "changeStartMode", + value: function changeStartMode() { + this.actions.modeChange('resize'); + var dimensions = this.actions.getCurrentDimensions(); + this._originalDimensions = dimensions; + this.setWidthValue(dimensions.width); + this.setHeightValue(dimensions.height); + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.actions.stopDrawingMode(); + this.actions.reset(true); + } + /** + * Set dimension limits + * @param {object} limits - expect dimension limits for change + */ + + }, { + key: "setLimit", + value: function setLimit(limits) { + this._els.widthRange.min = this.calcMinValue(limits.minWidth); + this._els.heightRange.min = this.calcMinValue(limits.minHeight); + this._els.widthRange.max = this.calcMaxValue(limits.maxWidth); + this._els.heightRange.max = this.calcMaxValue(limits.maxHeight); + } + /** + * Calculate max value + * @param {number} maxValue - max value + * @returns {number} + */ + + }, { + key: "calcMaxValue", + value: function calcMaxValue(maxValue) { + if (maxValue <= 0) { + maxValue = defaultResizePixelValues.max; + } + + return maxValue; + } + /** + * Calculate min value + * @param {number} minValue - min value + * @returns {number} + */ + + }, { + key: "calcMinValue", + value: function calcMinValue(minValue) { + if (minValue <= 0) { + minValue = defaultResizePixelValues.min; + } + + return minValue; + } + /** + * Set width value + * @param {number} value - expect value for widthRange change + * @param {boolean} trigger - fire change event control + */ + + }, { + key: "setWidthValue", + value: function setWidthValue(value) { + var trigger = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + this._els.widthRange.value = value; + + if (trigger) { + this._els.widthRange.trigger('change'); + } + } + /** + * Set height value + * @param {number} value - expect value for heightRange change + * @param {boolean} trigger - fire change event control + */ + + }, { + key: "setHeightValue", + value: function setHeightValue(value) { + var trigger = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + this._els.heightRange.value = value; + + if (trigger) { + this._els.heightRange.trigger('change'); + } + } + /** + * Destroys the instance. + */ + + }, { + key: "destroy", + value: function destroy() { + this._removeEvent(); + + assignmentForDestroy(this); + } + /** + * Add event for resize + * @param {Object} actions - actions for resize + * @param {Function} actions.resize - resize action + * @param {Function} actions.preview - preview action + * @param {Function} actions.getCurrentDimensions - Get current dimensions action + * @param {Function} actions.modeChange - change mode + * @param {Function} actions.stopDrawingMode - stop drawing mode + * @param {Function} actions.lockAspectRatio - lock aspect ratio + * @param {Function} actions.reset - reset action + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2, _context3, _context4, _context5; + + this._els.widthRange.on('change', bind_default()(_context = this._changeWidthRangeHandler).call(_context, this)); + + this._els.heightRange.on('change', bind_default()(_context2 = this._changeHeightRangeHandler).call(_context2, this)); + + this._els.lockAspectRatio.addEventListener('change', bind_default()(_context3 = this._changeLockAspectRatio).call(_context3, this)); + + var apply = bind_default()(_context4 = this._applyEventHandler).call(_context4, this); + + var cancel = bind_default()(_context5 = this._cancelEventHandler).call(_context5, this); + + this.eventHandler = { + apply: apply, + cancel: cancel + }; + this.actions = actions; + + this._els.apply.addEventListener('click', apply); + + this._els.cancel.addEventListener('click', cancel); + } + /** + * Change width + * @param {number} value - width range value + * @private + */ + + }, { + key: "_changeWidthRangeHandler", + value: function _changeWidthRangeHandler(value) { + this.actions.preview('width', toInteger(value), this._lockState); + } + /** + * Change height + * @param {number} value - height range value + * @private + */ + + }, { + key: "_changeHeightRangeHandler", + value: function _changeHeightRangeHandler(value) { + this.actions.preview('height', toInteger(value), this._lockState); + } + /** + * Change lock aspect ratio state + * @param {Event} event - aspect ratio check event + * @private + */ + + }, { + key: "_changeLockAspectRatio", + value: function _changeLockAspectRatio(event) { + this._lockState = event.target.checked; + this.actions.lockAspectRatio(this._lockState, defaultResizePixelValues.min, defaultResizePixelValues.max); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this._els.apply.removeEventListener('click', this.eventHandler.apply); + + this._els.cancel.removeEventListener('click', this.eventHandler.cancel); + } + }, { + key: "_applyEventHandler", + value: function _applyEventHandler() { + this.actions.resize(); + + this._els.apply.classList.remove('active'); + } + }, { + key: "_cancelEventHandler", + value: function _cancelEventHandler() { + this.actions.reset(); + + this._els.cancel.classList.remove('active'); + } + /** + * Change apply button status + * @param {Boolean} enableStatus - apply button status + */ + + }, { + key: "changeApplyButtonStatus", + value: function changeApplyButtonStatus(enableStatus) { + if (enableStatus) { + this._els.apply.classList.add('active'); + } else { + this._els.apply.classList.remove('active'); + } + } + }]); + + return Resize; +}(submenuBase); + +/* harmony default export */ var ui_resize = (Resize); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/flip.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var flip = (function (_ref) { + var _context, _context2, _context3, _context4, _context5; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'flip-x', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context4, makeSvgIcon(['normal', 'active'], 'flip-y', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n ")).call(_context2, makeSvgIcon(['normal', 'active'], 'flip-reset', true), "\n
          \n \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/flip.js + + + + + + + + +function flip_createSuper(Derived) { var hasNativeReflectConstruct = flip_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function flip_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + +/** + * Flip ui class + * @class + * @ignore + */ + +var Flip = /*#__PURE__*/function (_Submenu) { + _inherits(Flip, _Submenu); + + var _super = flip_createSuper(Flip); + + function Flip(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Flip); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'flip', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: flip, + usageStatistics: usageStatistics + }); + _this.flipStatus = false; + _this._els = { + flipButton: _this.selector('.tie-flip-button') + }; + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Flip, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + assignmentForDestroy(this); + } + /** + * Add event for flip + * @param {Object} actions - actions for flip + * @param {Function} actions.flip - flip action + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context; + + this.eventHandler.changeFlip = bind_default()(_context = this._changeFlip).call(_context, this); + this._actions = actions; + + this._els.flipButton.addEventListener('click', this.eventHandler.changeFlip); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this._els.flipButton.removeEventListener('click', this.eventHandler.changeFlip); + } + /** + * change Flip status + * @param {object} event - change event + * @private + */ + + }, { + key: "_changeFlip", + value: function _changeFlip(event) { + var _this2 = this; + + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + var flipType = this.getButtonType(button, ['flipX', 'flipY', 'resetFlip']); + + if (!this.flipStatus && flipType === 'resetFlip') { + return; + } + + this._actions.flip(flipType).then(function (flipStatus) { + var flipClassList = _this2._els.flipButton.classList; + _this2.flipStatus = false; + flipClassList.remove('resetFlip'); + forEach_default()(['flipX', 'flipY'], function (type) { + flipClassList.remove(type); + + if (flipStatus[type]) { + flipClassList.add(type); + flipClassList.add('resetFlip'); + _this2.flipStatus = true; + } + }); + }); + } + } + }]); + + return Flip; +}(submenuBase); + +/* harmony default export */ var ui_flip = (Flip); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/rotate.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var rotate = (function (_ref) { + var _context, _context2; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'rotate-clockwise', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context2, makeSvgIcon(['normal', 'active'], 'rotate-counterclockwise', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n \n
          \n \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/rotate.js + + + + + + + + + +function rotate_createSuper(Derived) { var hasNativeReflectConstruct = rotate_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function rotate_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + +var CLOCKWISE = 30; +var COUNTERCLOCKWISE = -30; +/** + * Rotate ui class + * @class + * @ignore + */ + +var Rotate = /*#__PURE__*/function (_Submenu) { + _inherits(Rotate, _Submenu); + + var _super = rotate_createSuper(Rotate); + + function Rotate(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Rotate); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'rotate', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: rotate, + usageStatistics: usageStatistics + }); + _this._value = 0; + _this._els = { + rotateButton: _this.selector('.tie-rotate-button'), + rotateRange: new range({ + slider: _this.selector('.tie-rotate-range'), + input: _this.selector('.tie-rotate-range-value') + }, defaultRotateRangeValues) + }; + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Rotate, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._els.rotateRange.destroy(); + + assignmentForDestroy(this); + } + }, { + key: "setRangeBarAngle", + value: function setRangeBarAngle(type, angle) { + var resultAngle = angle; + + if (type === 'rotate') { + resultAngle = parse_int_default()(this._els.rotateRange.value, 10) + angle; + } + + this._setRangeBarRatio(resultAngle); + } + }, { + key: "_setRangeBarRatio", + value: function _setRangeBarRatio(angle) { + this._els.rotateRange.value = angle; + } + /** + * Add event for rotate + * @param {Object} actions - actions for crop + * @param {Function} actions.rotate - rotate action + * @param {Function} actions.setAngle - set angle action + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2; + + this.eventHandler.rotationAngleChanged = bind_default()(_context = this._changeRotateForButton).call(_context, this); // {rotate, setAngle} + + this.actions = actions; + + this._els.rotateButton.addEventListener('click', this.eventHandler.rotationAngleChanged); + + this._els.rotateRange.on('change', bind_default()(_context2 = this._changeRotateForRange).call(_context2, this)); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this._els.rotateButton.removeEventListener('click', this.eventHandler.rotationAngleChanged); + + this._els.rotateRange.off(); + } + /** + * Change rotate for range + * @param {number} value - angle value + * @param {boolean} isLast - Is last change + * @private + */ + + }, { + key: "_changeRotateForRange", + value: function _changeRotateForRange(value, isLast) { + var angle = toInteger(value); + this.actions.setAngle(angle, !isLast); + this._value = angle; + } + /** + * Change rotate for button + * @param {object} event - add button event object + * @private + */ + + }, { + key: "_changeRotateForButton", + value: function _changeRotateForButton(event) { + var button = event.target.closest('.tui-image-editor-button'); + var angle = this._els.rotateRange.value; + + if (button) { + var rotateType = this.getButtonType(button, ['counterclockwise', 'clockwise']); + var rotateAngle = { + clockwise: CLOCKWISE, + counterclockwise: COUNTERCLOCKWISE + }[rotateType]; + var newAngle = parse_int_default()(angle, 10) + rotateAngle; + var isRotatable = newAngle >= -360 && newAngle <= 360; + + if (isRotatable) { + this.actions.rotate(rotateAngle); + } + } + } + }]); + + return Rotate; +}(submenuBase); + +/* harmony default export */ var ui_rotate = (Rotate); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/text.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var submenu_text = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = concat_default()(_context9 = concat_default()(_context10 = concat_default()(_context11 = concat_default()(_context12 = concat_default()(_context13 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'text-bold', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context12, makeSvgIcon(['normal', 'active'], 'text-italic', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context10, makeSvgIcon(['normal', 'active'], 'text-underline', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n ")).call(_context8, makeSvgIcon(['normal', 'active'], 'text-align-left', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context6, makeSvgIcon(['normal', 'active'], 'text-align-center', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context4, makeSvgIcon(['normal', 'active'], 'text-align-right', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n \n
          \n \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/text.js + + + + + + + + + + +function text_createSuper(Derived) { var hasNativeReflectConstruct = text_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function text_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + +/** + * Crop ui class + * @class + * @ignore + */ + +var Text = /*#__PURE__*/function (_Submenu) { + _inherits(Text, _Submenu); + + var _super = text_createSuper(Text); + + function Text(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Text); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'text', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: submenu_text, + usageStatistics: usageStatistics + }); + _this.effect = { + bold: false, + italic: false, + underline: false + }; + _this.align = 'tie-text-align-left'; + _this._els = { + textEffectButton: _this.selector('.tie-text-effect-button'), + textAlignButton: _this.selector('.tie-text-align-button'), + textColorpicker: new colorpicker(_this.selector('.tie-text-color'), { + defaultColor: '#ffbb3b', + toggleDirection: _this.toggleDirection, + usageStatistics: _this.usageStatistics + }), + textRange: new range({ + slider: _this.selector('.tie-text-range'), + input: _this.selector('.tie-text-range-value') + }, defaultTextRangeValues) + }; + _this.colorPickerInputBox = _this._els.textColorpicker.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX); + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Text, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._els.textColorpicker.destroy(); + + this._els.textRange.destroy(); + + assignmentForDestroy(this); + } + /** + * Add event for text + * @param {Object} actions - actions for text + * @param {Function} actions.changeTextStyle - change text style + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2, _context3, _context4, _context5, _context6; + + var setTextEffect = bind_default()(_context = this._setTextEffectHandler).call(_context, this); + + var setTextAlign = bind_default()(_context2 = this._setTextAlignHandler).call(_context2, this); + + this.eventHandler = { + setTextEffect: setTextEffect, + setTextAlign: setTextAlign + }; + this.actions = actions; + + this._els.textEffectButton.addEventListener('click', setTextEffect); + + this._els.textAlignButton.addEventListener('click', setTextAlign); + + this._els.textRange.on('change', bind_default()(_context3 = this._changeTextRnageHandler).call(_context3, this)); + + this._els.textColorpicker.on('change', bind_default()(_context4 = this._changeColorHandler).call(_context4, this)); + + this.colorPickerInputBox.addEventListener(eventNames.FOCUS, bind_default()(_context5 = this._onStartEditingInputBox).call(_context5, this)); + this.colorPickerInputBox.addEventListener(eventNames.BLUR, bind_default()(_context6 = this._onStopEditingInputBox).call(_context6, this)); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + var _context7, _context8; + + var _this$eventHandler = this.eventHandler, + setTextEffect = _this$eventHandler.setTextEffect, + setTextAlign = _this$eventHandler.setTextAlign; + + this._els.textEffectButton.removeEventListener('click', setTextEffect); + + this._els.textAlignButton.removeEventListener('click', setTextAlign); + + this._els.textRange.off(); + + this._els.textColorpicker.off(); + + this.colorPickerInputBox.removeEventListener(eventNames.FOCUS, bind_default()(_context7 = this._onStartEditingInputBox).call(_context7, this)); + this.colorPickerInputBox.removeEventListener(eventNames.BLUR, bind_default()(_context8 = this._onStopEditingInputBox).call(_context8, this)); + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.actions.stopDrawingMode(); + } + /** + * Executed when the menu starts. + */ + + }, { + key: "changeStartMode", + value: function changeStartMode() { + this.actions.modeChange('text'); + } + /** + * Get text color + * @returns {string} - text color + */ + + }, { + key: "textColor", + get: function get() { + return this._els.textColorpicker.color; + }, + set: function set(color) { + this._els.textColorpicker.color = color; + } + /** + * Get text size + * @returns {string} - text size + */ + + }, { + key: "fontSize", + get: function get() { + return this._els.textRange.value; + } + /** + * Set text size + * @param {Number} value - text size + */ + , + set: function set(value) { + this._els.textRange.value = value; + } + /** + * get font style + * @returns {string} - font style + */ + + }, { + key: "fontStyle", + get: function get() { + return this.effect.italic ? 'italic' : 'normal'; + } + /** + * get font weight + * @returns {string} - font weight + */ + + }, { + key: "fontWeight", + get: function get() { + return this.effect.bold ? 'bold' : 'normal'; + } + /** + * get text underline text underline + * @returns {boolean} - true or false + */ + + }, { + key: "underline", + get: function get() { + return this.effect.underline; + } + }, { + key: "setTextStyleStateOnAction", + value: function setTextStyleStateOnAction() { + var textStyle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + var fill = fill_default()(textStyle), + fontSize = textStyle.fontSize, + fontStyle = textStyle.fontStyle, + fontWeight = textStyle.fontWeight, + textDecoration = textStyle.textDecoration, + textAlign = textStyle.textAlign; + + this.textColor = fill; + this.fontSize = fontSize; + this.setEffectState('italic', fontStyle); + this.setEffectState('bold', fontWeight); + this.setEffectState('underline', textDecoration); + this.setAlignState("tie-text-align-".concat(textAlign)); + } + }, { + key: "setEffectState", + value: function setEffectState(effectName, value) { + var effectValue = value === 'italic' || value === 'bold' || value === 'underline'; + + var button = this._els.textEffectButton.querySelector(".tui-image-editor-button.".concat(effectName)); + + this.effect[effectName] = effectValue; + button.classList[effectValue ? 'add' : 'remove']('active'); + } + }, { + key: "setAlignState", + value: function setAlignState(value) { + var button = this._els.textAlignButton; + button.classList.remove(this.align); + button.classList.add(value); + this.align = value; + } + /** + * text effect set handler + * @param {object} event - add button event object + * @private + */ + + }, { + key: "_setTextEffectHandler", + value: function _setTextEffectHandler(event) { + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + var _button$className$mat = button.className.match(/(bold|italic|underline)/), + _button$className$mat2 = _slicedToArray(_button$className$mat, 1), + styleType = _button$className$mat2[0]; + + var styleObj = { + bold: { + fontWeight: 'bold' + }, + italic: { + fontStyle: 'italic' + }, + underline: { + textDecoration: 'underline' + } + }[styleType]; + this.effect[styleType] = !this.effect[styleType]; + button.classList.toggle('active'); + this.actions.changeTextStyle(styleObj); + } + } + /** + * text effect set handler + * @param {object} event - add button event object + * @private + */ + + }, { + key: "_setTextAlignHandler", + value: function _setTextAlignHandler(event) { + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + var styleType = this.getButtonType(button, ['left', 'center', 'right']); + var styleTypeAlias = "tie-text-align-".concat(styleType); + event.currentTarget.classList.remove(this.align); + + if (this.align !== styleTypeAlias) { + event.currentTarget.classList.add(styleTypeAlias); + } + + this.actions.changeTextStyle({ + textAlign: styleType + }); + this.align = styleTypeAlias; + } + } + /** + * text align set handler + * @param {number} value - range value + * @param {boolean} isLast - Is last change + * @private + */ + + }, { + key: "_changeTextRnageHandler", + value: function _changeTextRnageHandler(value, isLast) { + this.actions.changeTextStyle({ + fontSize: value + }, !isLast); + } + /** + * change color handler + * @param {string} color - change color string + * @private + */ + + }, { + key: "_changeColorHandler", + value: function _changeColorHandler(color) { + color = color || 'transparent'; + this.actions.changeTextStyle({ + fill: color + }); + } + }]); + + return Text; +}(submenuBase); + +/* harmony default export */ var ui_text = (Text); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/mask.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var mask = (function (_ref) { + var _context, _context2, _context3; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = "\n
          \n
        • \n
          \n
          \n \n ".concat(makeSvgIcon(['normal', 'active'], 'mask-load', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n ")).call(_context2, makeSvgIcon(['normal', 'active'], 'apply'), "\n \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/mask.js + + + + + + + + + + +function mask_createSuper(Derived) { var hasNativeReflectConstruct = mask_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function mask_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + +/** + * Mask ui class + * @class + * @ignore + */ + +var Mask = /*#__PURE__*/function (_Submenu) { + _inherits(Mask, _Submenu); + + var _super = mask_createSuper(Mask); + + function Mask(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Mask); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'mask', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: mask, + usageStatistics: usageStatistics + }); + _this._els = { + applyButton: _this.selector('.tie-mask-apply'), + maskImageButton: _this.selector('.tie-mask-image-file') + }; + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Mask, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + assignmentForDestroy(this); + } + /** + * Add event for mask + * @param {Object} actions - actions for crop + * @param {Function} actions.loadImageFromURL - load image action + * @param {Function} actions.applyFilter - apply filter action + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2; + + var loadMaskFile = bind_default()(_context = this._loadMaskFile).call(_context, this); + + var applyMask = bind_default()(_context2 = this._applyMask).call(_context2, this); + + this.eventHandler = { + loadMaskFile: loadMaskFile, + applyMask: applyMask + }; + this.actions = actions; + + this._els.maskImageButton.addEventListener('change', loadMaskFile); + + this._els.applyButton.addEventListener('click', applyMask); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + this._els.maskImageButton.removeEventListener('change', this.eventHandler.loadMaskFile); + + this._els.applyButton.removeEventListener('click', this.eventHandler.applyMask); + } + /** + * Apply mask + * @private + */ + + }, { + key: "_applyMask", + value: function _applyMask() { + this.actions.applyFilter(); + + this._els.applyButton.classList.remove('active'); + } + /** + * Load mask file + * @param {object} event - File change event object + * @private + */ + + }, { + key: "_loadMaskFile", + value: function _loadMaskFile(event) { + var imgUrl; + + if (!isSupportFileApi()) { + alert('This browser does not support file-api'); + } + + var _event$target$files = _slicedToArray(event.target.files, 1), + file = _event$target$files[0]; + + if (file) { + imgUrl = url_default().createObjectURL(file); + this.actions.loadImageFromURL(imgUrl, file); + + this._els.applyButton.classList.add('active'); + } + } + }]); + + return Mask; +}(submenuBase); + +/* harmony default export */ var ui_mask = (Mask); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/icon.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var icon = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14, _context15, _context16, _context17, _context18, _context19, _context20; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = concat_default()(_context9 = concat_default()(_context10 = concat_default()(_context11 = concat_default()(_context12 = concat_default()(_context13 = concat_default()(_context14 = concat_default()(_context15 = concat_default()(_context16 = concat_default()(_context17 = concat_default()(_context18 = concat_default()(_context19 = concat_default()(_context20 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'icon-arrow', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context19, makeSvgIcon(['normal', 'active'], 'icon-arrow-2', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context17, makeSvgIcon(['normal', 'active'], 'icon-arrow-3', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context15, makeSvgIcon(['normal', 'active'], 'icon-star', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context13, makeSvgIcon(['normal', 'active'], 'icon-star-2', true), "\n
          \n \n
          \n\n
          \n
          \n ")).call(_context11, makeSvgIcon(['normal', 'active'], 'icon-polygon', true), "\n
          \n \n
          \n\n
          \n
          \n ")).call(_context9, makeSvgIcon(['normal', 'active'], 'icon-location', true), "\n
          \n \n
          \n\n
          \n
          \n ")).call(_context7, makeSvgIcon(['normal', 'active'], 'icon-heart', true), "\n
          \n \n
          \n\n
          \n
          \n ")).call(_context5, makeSvgIcon(['normal', 'active'], 'icon-bubble', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n \n ")).call(_context3, makeSvgIcon(['normal', 'active'], 'icon-load', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/icon.js + + + + + + + + + + +function icon_createSuper(Derived) { var hasNativeReflectConstruct = icon_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function icon_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + +/** + * Icon ui class + * @class + * @ignore + */ + +var Icon = /*#__PURE__*/function (_Submenu) { + _inherits(Icon, _Submenu); + + var _super = icon_createSuper(Icon); + + function Icon(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Icon); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'icon', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: icon, + usageStatistics: usageStatistics + }); + _this.iconType = null; + _this._iconMap = {}; + _this._els = { + registerIconButton: _this.selector('.tie-icon-image-file'), + addIconButton: _this.selector('.tie-icon-add-button'), + iconColorpicker: new colorpicker(_this.selector('.tie-icon-color'), { + defaultColor: '#ffbb3b', + toggleDirection: _this.toggleDirection, + usageStatistics: _this.usageStatistics + }) + }; + _this.colorPickerInputBox = _this._els.iconColorpicker.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX); + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Icon, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._els.iconColorpicker.destroy(); + + assignmentForDestroy(this); + } + /** + * Add event for icon + * @param {Object} actions - actions for icon + * @param {Function} actions.registerCustomIcon - register icon + * @param {Function} actions.addIcon - add icon + * @param {Function} actions.changeColor - change icon color + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2, _context3, _context4, _context5; + + var registerIcon = bind_default()(_context = this._registerIconHandler).call(_context, this); + + var addIcon = bind_default()(_context2 = this._addIconHandler).call(_context2, this); + + this.eventHandler = { + registerIcon: registerIcon, + addIcon: addIcon + }; + this.actions = actions; + + this._els.iconColorpicker.on('change', bind_default()(_context3 = this._changeColorHandler).call(_context3, this)); + + this._els.registerIconButton.addEventListener('change', registerIcon); + + this._els.addIconButton.addEventListener('click', addIcon); + + this.colorPickerInputBox.addEventListener(eventNames.FOCUS, bind_default()(_context4 = this._onStartEditingInputBox).call(_context4, this)); + this.colorPickerInputBox.addEventListener(eventNames.BLUR, bind_default()(_context5 = this._onStopEditingInputBox).call(_context5, this)); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + var _context6, _context7; + + this._els.iconColorpicker.off(); + + this._els.registerIconButton.removeEventListener('change', this.eventHandler.registerIcon); + + this._els.addIconButton.removeEventListener('click', this.eventHandler.addIcon); + + this.colorPickerInputBox.removeEventListener(eventNames.FOCUS, bind_default()(_context6 = this._onStartEditingInputBox).call(_context6, this)); + this.colorPickerInputBox.removeEventListener(eventNames.BLUR, bind_default()(_context7 = this._onStopEditingInputBox).call(_context7, this)); + } + /** + * Clear icon type + */ + + }, { + key: "clearIconType", + value: function clearIconType() { + this._els.addIconButton.classList.remove(this.iconType); + + this.iconType = null; + } + /** + * Register default icon + */ + + }, { + key: "registerDefaultIcon", + value: function registerDefaultIcon() { + var _this2 = this; + + forEach_default()(defaultIconPath, function (path, type) { + _this2.actions.registerDefaultIcons(type, path); + }); + } + /** + * Set icon picker color + * @param {string} iconColor - rgb color string + */ + + }, { + key: "setIconPickerColor", + value: function setIconPickerColor(iconColor) { + this._els.iconColorpicker.color = iconColor; + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.clearIconType(); + this.actions.cancelAddIcon(); + } + /** + * Change icon color + * @param {string} color - color for change + * @private + */ + + }, { + key: "_changeColorHandler", + value: function _changeColorHandler(color) { + color = color || 'transparent'; + this.actions.changeColor(color); + } + /** + * Change icon color + * @param {object} event - add button event object + * @private + */ + + }, { + key: "_addIconHandler", + value: function _addIconHandler(event) { + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + var iconType = button.getAttribute('data-icontype'); + var iconColor = this._els.iconColorpicker.color; + this.actions.discardSelection(); + this.actions.changeSelectableAll(false); + + this._els.addIconButton.classList.remove(this.iconType); + + this._els.addIconButton.classList.add(iconType); + + if (this.iconType === iconType) { + this.changeStandbyMode(); + } else { + this.actions.addIcon(iconType, iconColor); + this.iconType = iconType; + } + } + } + /** + * register icon + * @param {object} event - file change event object + * @private + */ + + }, { + key: "_registerIconHandler", + value: function _registerIconHandler(event) { + var imgUrl; + + if (!isSupportFileApi) { + alert('This browser does not support file-api'); + } + + var _event$target$files = _slicedToArray(event.target.files, 1), + file = _event$target$files[0]; + + if (file) { + imgUrl = url_default().createObjectURL(file); + this.actions.registerCustomIcon(imgUrl, file); + } + } + }]); + + return Icon; +}(submenuBase); + +/* harmony default export */ var ui_icon = (Icon); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/draw.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @returns {string} + */ +/* harmony default export */ var draw = (function (_ref) { + var _context, _context2, _context3, _context4, _context5; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = "\n
          \n
        • \n
          \n
          \n ".concat(makeSvgIcon(['normal', 'active'], 'draw-free', true), "\n
          \n \n
          \n
          \n
          \n ")).call(_context4, makeSvgIcon(['normal', 'active'], 'draw-line', true), "\n
          \n \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n \n
          \n \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/draw.js + + + + + + + + +function draw_createSuper(Derived) { var hasNativeReflectConstruct = draw_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function draw_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + +var DRAW_OPACITY = 0.7; +/** + * Draw ui class + * @class + * @ignore + */ + +var Draw = /*#__PURE__*/function (_Submenu) { + _inherits(Draw, _Submenu); + + var _super = draw_createSuper(Draw); + + function Draw(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Draw); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'draw', + makeSvgIcon: makeSvgIcon, + menuBarPosition: menuBarPosition, + templateHtml: draw, + usageStatistics: usageStatistics + }); + _this._els = { + lineSelectButton: _this.selector('.tie-draw-line-select-button'), + drawColorPicker: new colorpicker(_this.selector('.tie-draw-color'), { + defaultColor: '#00a9ff', + toggleDirection: _this.toggleDirection, + usageStatistics: _this.usageStatistics + }), + drawRange: new range({ + slider: _this.selector('.tie-draw-range'), + input: _this.selector('.tie-draw-range-value') + }, defaultDrawRangeValues) + }; + _this.type = null; + _this.color = _this._els.drawColorPicker.color; + _this.width = _this._els.drawRange.value; + _this.colorPickerInputBox = _this._els.drawColorPicker.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX); + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Draw, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._els.drawColorPicker.destroy(); + + this._els.drawRange.destroy(); + + assignmentForDestroy(this); + } + /** + * Add event for draw + * @param {Object} actions - actions for crop + * @param {Function} actions.setDrawMode - set draw mode + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + var _context, _context2, _context3, _context4, _context5; + + this.eventHandler.changeDrawType = bind_default()(_context = this._changeDrawType).call(_context, this); + this.actions = actions; + + this._els.lineSelectButton.addEventListener('click', this.eventHandler.changeDrawType); + + this._els.drawColorPicker.on('change', bind_default()(_context2 = this._changeDrawColor).call(_context2, this)); + + this._els.drawRange.on('change', bind_default()(_context3 = this._changeDrawRange).call(_context3, this)); + + this.colorPickerInputBox.addEventListener(eventNames.FOCUS, bind_default()(_context4 = this._onStartEditingInputBox).call(_context4, this)); + this.colorPickerInputBox.addEventListener(eventNames.BLUR, bind_default()(_context5 = this._onStopEditingInputBox).call(_context5, this)); + } + /** + * Remove event + * @private + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + var _context6, _context7; + + this._els.lineSelectButton.removeEventListener('click', this.eventHandler.changeDrawType); + + this._els.drawColorPicker.off(); + + this._els.drawRange.off(); + + this.colorPickerInputBox.removeEventListener(eventNames.FOCUS, bind_default()(_context6 = this._onStartEditingInputBox).call(_context6, this)); + this.colorPickerInputBox.removeEventListener(eventNames.BLUR, bind_default()(_context7 = this._onStopEditingInputBox).call(_context7, this)); + } + /** + * set draw mode - action runner + */ + + }, { + key: "setDrawMode", + value: function setDrawMode() { + this.actions.setDrawMode(this.type, { + width: this.width, + color: getRgb(this.color, DRAW_OPACITY) + }); + } + /** + * Returns the menu to its default state. + */ + + }, { + key: "changeStandbyMode", + value: function changeStandbyMode() { + this.type = null; + this.actions.stopDrawingMode(); + this.actions.changeSelectableAll(true); + + this._els.lineSelectButton.classList.remove('free'); + + this._els.lineSelectButton.classList.remove('line'); + } + /** + * Executed when the menu starts. + */ + + }, { + key: "changeStartMode", + value: function changeStartMode() { + this.type = 'free'; + + this._els.lineSelectButton.classList.add('free'); + + this.setDrawMode(); + } + /** + * Change draw type event + * @param {object} event - line select event + * @private + */ + + }, { + key: "_changeDrawType", + value: function _changeDrawType(event) { + var button = event.target.closest('.tui-image-editor-button'); + + if (button) { + var lineType = this.getButtonType(button, ['free', 'line']); + this.actions.discardSelection(); + + if (this.type === lineType) { + this.changeStandbyMode(); + return; + } + + this.changeStandbyMode(); + this.type = lineType; + + this._els.lineSelectButton.classList.add(lineType); + + this.setDrawMode(); + } + } + /** + * Change drawing color + * @param {string} color - select drawing color + * @private + */ + + }, { + key: "_changeDrawColor", + value: function _changeDrawColor(color) { + this.color = color || 'transparent'; + + if (!this.type) { + this.changeStartMode(); + } else { + this.setDrawMode(); + } + } + /** + * Change drawing Range + * @param {number} value - select drawing range + * @private + */ + + }, { + key: "_changeDrawRange", + value: function _changeDrawRange(value) { + this.width = value; + + if (!this.type) { + this.changeStartMode(); + } else { + this.setDrawMode(); + } + } + }]); + + return Draw; +}(submenuBase); + +/* harmony default export */ var ui_draw = (Draw); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/type/isExisty.js +var isExisty = __webpack_require__(9886); +var isExisty_default = /*#__PURE__*/__webpack_require__.n(isExisty); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/filter.js + + +/** + * @param {Locale} locale - Translate text + * @returns {string} + */ +/* harmony default export */ var filter = (function (_ref) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14, _context15, _context16; + + var locale = _ref.locale; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = concat_default()(_context4 = concat_default()(_context5 = concat_default()(_context6 = concat_default()(_context7 = concat_default()(_context8 = concat_default()(_context9 = concat_default()(_context10 = concat_default()(_context11 = concat_default()(_context12 = concat_default()(_context13 = concat_default()(_context14 = concat_default()(_context15 = concat_default()(_context16 = "\n
          \n
        • \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n \n
          \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n
          \n \n
          \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n
          \n
          \n
          \n \n
          \n
          \n
          \n \n
          \n
          \n
          \n
        • \n
        • \n
          \n
        • \n
        • \n
          \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n \n
          \n
          \n
          \n
          \n
          \n \n
          \n
          \n
        • \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/filter.js + + + + + + + + + + +function filter_createSuper(Derived) { var hasNativeReflectConstruct = filter_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function filter_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + + + + +var PICKER_CONTROL_HEIGHT = '130px'; +var BLEND_OPTIONS = ['add', 'diff', 'subtract', 'multiply', 'screen', 'lighten', 'darken']; +var FILTER_OPTIONS = ['grayscale', 'invert', 'sepia', 'vintage', 'blur', 'sharpen', 'emboss', 'remove-white', 'brightness', 'noise', 'pixelate', 'color-filter', 'tint', 'multiply', 'blend']; +var filterNameMap = { + grayscale: 'grayscale', + invert: 'invert', + sepia: 'sepia', + blur: 'blur', + sharpen: 'sharpen', + emboss: 'emboss', + removeWhite: 'removeColor', + brightness: 'brightness', + contrast: 'contrast', + saturation: 'saturation', + vintage: 'vintage', + polaroid: 'polaroid', + noise: 'noise', + pixelate: 'pixelate', + colorFilter: 'removeColor', + tint: 'blendColor', + multiply: 'blendColor', + blend: 'blendColor', + hue: 'hue', + gamma: 'gamma' +}; +var RANGE_INSTANCE_NAMES = ['removewhiteDistanceRange', 'colorfilterThresholdRange', 'pixelateRange', 'noiseRange', 'brightnessRange', 'tintOpacity']; +var COLORPICKER_INSTANCE_NAMES = ['filterBlendColor', 'filterMultiplyColor', 'filterTintColor']; +/** + * Filter ui class + * @class + * @ignore + */ + +var Filter = /*#__PURE__*/function (_Submenu) { + _inherits(Filter, _Submenu); + + var _super = filter_createSuper(Filter); + + function Filter(subMenuElement, _ref) { + var _this; + + var locale = _ref.locale, + menuBarPosition = _ref.menuBarPosition, + usageStatistics = _ref.usageStatistics; + + _classCallCheck(this, Filter); + + _this = _super.call(this, subMenuElement, { + locale: locale, + name: 'filter', + menuBarPosition: menuBarPosition, + templateHtml: filter, + usageStatistics: usageStatistics + }); + _this.selectBoxShow = false; + _this.checkedMap = {}; + + _this._makeControlElement(); + + return _this; + } + /** + * Destroys the instance. + */ + + + _createClass(Filter, [{ + key: "destroy", + value: function destroy() { + this._removeEvent(); + + this._destroyToolInstance(); + + assignmentForDestroy(this); + } + /** + * Remove event for filter + */ + + }, { + key: "_removeEvent", + value: function _removeEvent() { + var _this2 = this, + _context; + + forEach_default()(FILTER_OPTIONS, function (filter) { + var filterCheckElement = _this2.selector(".tie-".concat(filter)); + + var filterNameCamelCase = toCamelCase(filter); + filterCheckElement.removeEventListener('change', _this2.eventHandler[filterNameCamelCase]); + }); + forEach_default()(concat_default()(_context = []).call(_context, RANGE_INSTANCE_NAMES, COLORPICKER_INSTANCE_NAMES), function (instanceName) { + _this2._els[instanceName].off(); + }); + + this._els.blendType.removeEventListener('change', this.eventHandler.changeBlendFilter); + + this._els.blendType.removeEventListener('click', this.eventHandler.changeBlendFilter); + + forEachArray_default()(this.colorPickerInputBoxes, function (inputBox) { + var _context2, _context3; + + inputBox.removeEventListener(eventNames.FOCUS, bind_default()(_context2 = _this2._onStartEditingInputBox).call(_context2, _this2)); + inputBox.removeEventListener(eventNames.BLUR, bind_default()(_context3 = _this2._onStopEditingInputBox).call(_context3, _this2)); + }, this); + } + }, { + key: "_destroyToolInstance", + value: function _destroyToolInstance() { + var _context4, + _this3 = this; + + forEach_default()(concat_default()(_context4 = []).call(_context4, RANGE_INSTANCE_NAMES, COLORPICKER_INSTANCE_NAMES), function (instanceName) { + _this3._els[instanceName].destroy(); + }); + } + /** + * Add event for filter + * @param {Object} actions - actions for crop + * @param {Function} actions.applyFilter - apply filter option + */ + + }, { + key: "addEvent", + value: function addEvent(_ref2) { + var _this4 = this, + _context6, + _context7, + _context8; + + var applyFilter = _ref2.applyFilter; + + var changeFilterState = function changeFilterState(filterName) { + var _context5; + + return bind_default()(_context5 = _this4._changeFilterState).call(_context5, _this4, applyFilter, filterName); + }; + + var changeFilterStateForRange = function changeFilterStateForRange(filterName) { + return function (value, isLast) { + return _this4._changeFilterState(applyFilter, filterName, isLast); + }; + }; + + this.eventHandler = { + changeBlendFilter: changeFilterState('blend'), + blandTypeClick: function blandTypeClick(event) { + return event.stopPropagation(); + } + }; + forEach_default()(FILTER_OPTIONS, function (filter) { + var filterCheckElement = _this4.selector(".tie-".concat(filter)); + + var filterNameCamelCase = toCamelCase(filter); + _this4.checkedMap[filterNameCamelCase] = filterCheckElement; + _this4.eventHandler[filterNameCamelCase] = changeFilterState(filterNameCamelCase); + filterCheckElement.addEventListener('change', _this4.eventHandler[filterNameCamelCase]); + }); + + this._els.removewhiteDistanceRange.on('change', changeFilterStateForRange('removeWhite')); + + this._els.colorfilterThresholdRange.on('change', changeFilterStateForRange('colorFilter')); + + this._els.pixelateRange.on('change', changeFilterStateForRange('pixelate')); + + this._els.noiseRange.on('change', changeFilterStateForRange('noise')); + + this._els.brightnessRange.on('change', changeFilterStateForRange('brightness')); + + this._els.filterBlendColor.on('change', this.eventHandler.changeBlendFilter); + + this._els.filterMultiplyColor.on('change', changeFilterState('multiply')); + + this._els.filterTintColor.on('change', changeFilterState('tint')); + + this._els.tintOpacity.on('change', changeFilterStateForRange('tint')); + + this._els.filterMultiplyColor.on('changeShow', bind_default()(_context6 = this.colorPickerChangeShow).call(_context6, this)); + + this._els.filterTintColor.on('changeShow', bind_default()(_context7 = this.colorPickerChangeShow).call(_context7, this)); + + this._els.filterBlendColor.on('changeShow', bind_default()(_context8 = this.colorPickerChangeShow).call(_context8, this)); + + this._els.blendType.addEventListener('change', this.eventHandler.changeBlendFilter); + + this._els.blendType.addEventListener('click', this.eventHandler.blandTypeClick); + + forEachArray_default()(this.colorPickerInputBoxes, function (inputBox) { + var _context9, _context10; + + inputBox.addEventListener(eventNames.FOCUS, bind_default()(_context9 = _this4._onStartEditingInputBox).call(_context9, _this4)); + inputBox.addEventListener(eventNames.BLUR, bind_default()(_context10 = _this4._onStopEditingInputBox).call(_context10, _this4)); + }, this); + } + /** + * Set filter for undo changed + * @param {Object} changedFilterInfos - changed command infos + * @param {string} type - filter type + * @param {string} action - add or remove + * @param {Object} options - filter options + */ + + }, { + key: "setFilterState", + value: function setFilterState(changedFilterInfos) { + var type = changedFilterInfos.type, + options = changedFilterInfos.options, + action = changedFilterInfos.action; + + var filterName = this._getFilterNameFromOptions(type, options); + + var isRemove = action === 'remove'; + + if (!isRemove) { + this._setFilterState(filterName, options); + } + + this.checkedMap[filterName].checked = !isRemove; + } + /** + * Init all filter's checkbox to unchecked state + */ + + }, { + key: "initFilterCheckBoxState", + value: function initFilterCheckBoxState() { + forEach_default()(this.checkedMap, function (filter) { + filter.checked = false; + }, this); + } + /** + * Set filter for undo changed + * @param {string} filterName - filter name + * @param {Object} options - filter options + * @private + */ + // eslint-disable-next-line complexity + + }, { + key: "_setFilterState", + value: function _setFilterState(filterName, options) { + if (filterName === 'colorFilter') { + this._els.colorfilterThresholdRange.value = options.distance; + } else if (filterName === 'removeWhite') { + this._els.removewhiteDistanceRange.value = options.distance; + } else if (filterName === 'pixelate') { + this._els.pixelateRange.value = options.blocksize; + } else if (filterName === 'brightness') { + this._els.brightnessRange.value = options.brightness; + } else if (filterName === 'noise') { + this._els.noiseRange.value = options.noise; + } else if (filterName === 'tint') { + this._els.tintOpacity.value = options.alpha; + this._els.filterTintColor.color = options.color; + } else if (filterName === 'blend') { + this._els.filterBlendColor.color = options.color; + } else if (filterName === 'multiply') { + this._els.filterMultiplyColor.color = options.color; + } + } + /** + * Get filter name + * @param {string} type - filter type + * @param {Object} options - filter options + * @returns {string} filter name + * @private + */ + + }, { + key: "_getFilterNameFromOptions", + value: function _getFilterNameFromOptions(type, options) { + var filterName = type; + + if (type === 'removeColor') { + filterName = isExisty_default()(options.useAlpha) ? 'removeWhite' : 'colorFilter'; + } else if (type === 'blendColor') { + filterName = { + add: 'blend', + multiply: 'multiply', + tint: 'tint' + }[options.mode]; + } + + return filterName; + } + /** + * Add event for filter + * @param {Function} applyFilter - actions for firter + * @param {string} filterName - filter name + * @param {boolean} [isLast] - Is last change + */ + + }, { + key: "_changeFilterState", + value: function _changeFilterState(applyFilter, filterName) { + var isLast = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + var apply = this.checkedMap[filterName].checked; + var type = filterNameMap[filterName]; + var checkboxGroup = this.checkedMap[filterName].closest('.tui-image-editor-checkbox-group'); + + if (checkboxGroup) { + if (apply) { + checkboxGroup.classList.remove('tui-image-editor-disabled'); + } else { + checkboxGroup.classList.add('tui-image-editor-disabled'); + } + } + + applyFilter(apply, type, this._getFilterOption(filterName), !isLast); + } + /** + * Get filter option + * @param {String} type - filter type + * @returns {Object} filter option object + * @private + */ + // eslint-disable-next-line complexity + + }, { + key: "_getFilterOption", + value: function _getFilterOption(type) { + var option = {}; + + switch (type) { + case 'removeWhite': + option.color = '#FFFFFF'; + option.useAlpha = false; + option.distance = parse_float_default()(this._els.removewhiteDistanceRange.value); + break; + + case 'colorFilter': + option.color = '#FFFFFF'; + option.distance = parse_float_default()(this._els.colorfilterThresholdRange.value); + break; + + case 'pixelate': + option.blocksize = toInteger(this._els.pixelateRange.value); + break; + + case 'noise': + option.noise = toInteger(this._els.noiseRange.value); + break; + + case 'brightness': + option.brightness = parse_float_default()(this._els.brightnessRange.value); + break; + + case 'blend': + option.mode = 'add'; + option.color = this._els.filterBlendColor.color; + option.mode = this._els.blendType.value; + break; + + case 'multiply': + option.mode = 'multiply'; + option.color = this._els.filterMultiplyColor.color; + break; + + case 'tint': + option.mode = 'tint'; + option.color = this._els.filterTintColor.color; + option.alpha = this._els.tintOpacity.value; + break; + + case 'blur': + option.blur = this._els.blurRange.value; + break; + + default: + break; + } + + return option; + } + /** + * Make submenu range and colorpicker control + * @private + */ + + }, { + key: "_makeControlElement", + value: function _makeControlElement() { + this._els = { + removewhiteDistanceRange: new range({ + slider: this.selector('.tie-removewhite-distance-range') + }, defaultFilterRangeValues.removewhiteDistanceRange), + brightnessRange: new range({ + slider: this.selector('.tie-brightness-range') + }, defaultFilterRangeValues.brightnessRange), + noiseRange: new range({ + slider: this.selector('.tie-noise-range') + }, defaultFilterRangeValues.noiseRange), + pixelateRange: new range({ + slider: this.selector('.tie-pixelate-range') + }, defaultFilterRangeValues.pixelateRange), + colorfilterThresholdRange: new range({ + slider: this.selector('.tie-colorfilter-threshold-range') + }, defaultFilterRangeValues.colorfilterThresholdRange), + filterTintColor: new colorpicker(this.selector('.tie-filter-tint-color'), { + defaultColor: '#03bd9e', + toggleDirection: this.toggleDirection, + usageStatistics: this.usageStatistics + }), + filterMultiplyColor: new colorpicker(this.selector('.tie-filter-multiply-color'), { + defaultColor: '#515ce6', + toggleDirection: this.toggleDirection, + usageStatistics: this.usageStatistics + }), + filterBlendColor: new colorpicker(this.selector('.tie-filter-blend-color'), { + defaultColor: '#ffbb3b', + toggleDirection: this.toggleDirection, + usageStatistics: this.usageStatistics + }), + blurRange: defaultFilterRangeValues.blurFilterRange + }; + this._els.tintOpacity = this._pickerWithRange(this._els.filterTintColor.pickerControl); + this._els.blendType = this._pickerWithSelectbox(this._els.filterBlendColor.pickerControl); + this.colorPickerControls.push(this._els.filterTintColor); + this.colorPickerControls.push(this._els.filterMultiplyColor); + this.colorPickerControls.push(this._els.filterBlendColor); + this.colorPickerInputBoxes = []; + this.colorPickerInputBoxes.push(this._els.filterTintColor.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX)); + this.colorPickerInputBoxes.push(this._els.filterMultiplyColor.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX)); + this.colorPickerInputBoxes.push(this._els.filterBlendColor.colorpickerElement.querySelector(selectorNames.COLOR_PICKER_INPUT_BOX)); + } + /** + * Make submenu control for picker & range mixin + * @param {HTMLElement} pickerControl - pickerControl dom element + * @returns {Range} + * @private + */ + + }, { + key: "_pickerWithRange", + value: function _pickerWithRange(pickerControl) { + var rangeWrap = document.createElement('div'); + var rangeLabel = document.createElement('label'); + var slider = document.createElement('div'); + slider.id = 'tie-filter-tint-opacity'; + rangeLabel.innerHTML = 'Opacity'; + rangeWrap.appendChild(rangeLabel); + rangeWrap.appendChild(slider); + pickerControl.appendChild(rangeWrap); + pickerControl.style.height = PICKER_CONTROL_HEIGHT; + return new range({ + slider: slider + }, defaultFilterRangeValues.tintOpacityRange); + } + /** + * Make submenu control for picker & selectbox + * @param {HTMLElement} pickerControl - pickerControl dom element + * @returns {HTMLElement} + * @private + */ + + }, { + key: "_pickerWithSelectbox", + value: function _pickerWithSelectbox(pickerControl) { + var selectlistWrap = document.createElement('div'); + var selectlist = document.createElement('select'); + var optionlist = document.createElement('ul'); + selectlistWrap.className = 'tui-image-editor-selectlist-wrap'; + optionlist.className = 'tui-image-editor-selectlist'; + selectlistWrap.appendChild(selectlist); + selectlistWrap.appendChild(optionlist); + + this._makeSelectOptionList(selectlist); + + pickerControl.appendChild(selectlistWrap); + pickerControl.style.height = PICKER_CONTROL_HEIGHT; + + this._drawSelectOptionList(selectlist, optionlist); + + this._pickerWithSelectboxForAddEvent(selectlist, optionlist); + + return selectlist; + } + /** + * Make selectbox option list custom style + * @param {HTMLElement} selectlist - selectbox element + * @param {HTMLElement} optionlist - custom option list item element + * @private + */ + + }, { + key: "_drawSelectOptionList", + value: function _drawSelectOptionList(selectlist, optionlist) { + var options = selectlist.querySelectorAll('option'); + forEach_default()(options, function (option) { + var optionElement = document.createElement('li'); + optionElement.innerHTML = option.innerHTML; + optionElement.setAttribute('data-item', option.value); + optionlist.appendChild(optionElement); + }); + } + /** + * custom selectbox custom event + * @param {HTMLElement} selectlist - selectbox element + * @param {HTMLElement} optionlist - custom option list item element + * @private + */ + + }, { + key: "_pickerWithSelectboxForAddEvent", + value: function _pickerWithSelectboxForAddEvent(selectlist, optionlist) { + var _this5 = this; + + optionlist.addEventListener('click', function (event) { + var optionValue = event.target.getAttribute('data-item'); + var fireEvent = document.createEvent('HTMLEvents'); + selectlist.querySelector("[value=\"".concat(optionValue, "\"]")).selected = true; + fireEvent.initEvent('change', true, true); + selectlist.dispatchEvent(fireEvent); + _this5.selectBoxShow = false; + optionlist.style.display = 'none'; + }); + selectlist.addEventListener('mousedown', function (event) { + event.preventDefault(); + _this5.selectBoxShow = !_this5.selectBoxShow; + optionlist.style.display = _this5.selectBoxShow ? 'block' : 'none'; + optionlist.setAttribute('data-selectitem', selectlist.value); + optionlist.querySelector("[data-item='".concat(selectlist.value, "']")).classList.add('active'); + }); + } + /** + * Make option list for select control + * @param {HTMLElement} selectlist - blend option select list element + * @private + */ + + }, { + key: "_makeSelectOptionList", + value: function _makeSelectOptionList(selectlist) { + forEach_default()(BLEND_OPTIONS, function (option) { + var selectOption = document.createElement('option'); + selectOption.setAttribute('value', option); + selectOption.innerHTML = option.replace(/^[a-z]/, function ($0) { + return $0.toUpperCase(); + }); + selectlist.appendChild(selectOption); + }); + } + }]); + + return Filter; +}(submenuBase); + +/* harmony default export */ var ui_filter = (Filter); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/number/parse-int.js +var number_parse_int = __webpack_require__(4383); +var number_parse_int_default = /*#__PURE__*/__webpack_require__.n(number_parse_int); +;// CONCATENATED MODULE: ./src/js/ui/panelMenu.js + + + + +/** + * Menu Panel Class + * @class + * @ignore + */ +var Panel = /*#__PURE__*/function () { + /** + * @param {HTMLElement} menuElement - menu dom element + * @param {Object} options - menu options + * @param {string} options.name - name of panel menu + */ + function Panel(menuElement, _ref) { + var name = _ref.name; + + _classCallCheck(this, Panel); + + this.name = name; + this.items = []; + this.panelElement = this._makePanelElement(); + this.listElement = this._makeListElement(); + this.panelElement.appendChild(this.listElement); + menuElement.appendChild(this.panelElement); + } + /** + * Make Panel element + * @returns {HTMLElement} + */ + + + _createClass(Panel, [{ + key: "_makePanelElement", + value: function _makePanelElement() { + var panel = document.createElement('div'); + panel.className = "tie-panel-".concat(this.name); + return panel; + } + /** + * Make list element + * @returns {HTMLElement} list element + * @private + */ + + }, { + key: "_makeListElement", + value: function _makeListElement() { + var list = document.createElement('ol'); + list.className = "".concat(this.name, "-list"); + return list; + } + /** + * Make list item element + * @param {string} html - history list item html + * @returns {HTMLElement} list item element + */ + + }, { + key: "makeListItemElement", + value: function makeListItemElement(html) { + var listItem = document.createElement('li'); + listItem.innerHTML = html; + listItem.className = "".concat(this.name, "-item"); + listItem.setAttribute('data-index', this.items.length); + return listItem; + } + /** + * Push list item element + * @param {HTMLElement} item - list item element to add to the list + */ + + }, { + key: "pushListItemElement", + value: function pushListItemElement(item) { + this.listElement.appendChild(item); + this.listElement.scrollTop += item.offsetHeight; + this.items.push(item); + } + /** + * Delete list item element + * @param {number} start - start index to delete + * @param {number} end - end index to delete + */ + + }, { + key: "deleteListItemElement", + value: function deleteListItemElement(start, end) { + var items = this.items; + + for (var i = start; i < end; i += 1) { + this.listElement.removeChild(items[i]); + } + + splice_default()(items).call(items, start, end - start + 1); + } + /** + * Get list's length + * @returns {number} + */ + + }, { + key: "getListLength", + value: function getListLength() { + return this.items.length; + } + /** + * Add class name of item + * @param {number} index - index of item + * @param {string} className - class name to add + */ + + }, { + key: "addClass", + value: function addClass(index, className) { + if (this.items[index]) { + this.items[index].classList.add(className); + } + } + /** + * Remove class name of item + * @param {number} index - index of item + * @param {string} className - class name to remove + */ + + }, { + key: "removeClass", + value: function removeClass(index, className) { + if (this.items[index]) { + this.items[index].classList.remove(className); + } + } + /** + * Toggle class name of item + * @param {number} index - index of item + * @param {string} className - class name to remove + */ + + }, { + key: "toggleClass", + value: function toggleClass(index, className) { + if (this.items[index]) { + this.items[index].classList.toggle(className); + } + } + }]); + + return Panel; +}(); + +/* harmony default export */ var panelMenu = (Panel); +;// CONCATENATED MODULE: ./src/js/ui/template/submenu/history.js + + +/** + * @param {Object} submenuInfo - submenu info for make template + * @param {Locale} locale - Translate text + * @param {Function} makeSvgIcon - svg icon generator + * @param {string} name - history name + * @param {string} detail - history detail information + * @returns {string} + */ +/* harmony default export */ var submenu_history = (function (_ref) { + var _context, _context2, _context3; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon, + name = _ref.name, + detail = _ref.detail; + return concat_default()(_context = concat_default()(_context2 = concat_default()(_context3 = "\n
        \n
        \n ".concat(makeSvgIcon(['normal', 'active'], "history-".concat(name.toLowerCase()), true), "\n
        \n \n ")).call(_context3, locale.localize(name), "\n ")).call(_context2, detail ? "(".concat(locale.localize(detail), ")") : '', "\n \n
        \n ")).call(_context, makeSvgIcon(['normal'], 'history-check', true), "\n
        \n
        \n"); +}); +;// CONCATENATED MODULE: ./src/js/ui/history.js + + + + + + + + +function history_createSuper(Derived) { var hasNativeReflectConstruct = history_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function history_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + +var historyClassName = 'history-item'; +var selectedClassName = 'selected-item'; +var disabledClassName = 'disabled-item'; +/** + * History ui class + * @class + * @ignore + */ + +var History = /*#__PURE__*/function (_Panel) { + _inherits(History, _Panel); + + var _super = history_createSuper(History); + + function History(menuElement, _ref) { + var _this; + + var locale = _ref.locale, + makeSvgIcon = _ref.makeSvgIcon; + + _classCallCheck(this, History); + + _this = _super.call(this, menuElement, { + name: 'history' + }); + menuElement.classList.add('enabled'); + _this.locale = locale; + _this.makeSvgIcon = makeSvgIcon; + _this._eventHandler = {}; + _this._historyIndex = _this.getListLength(); + return _this; + } + /** + * Add history + * @param {string} name - name of history + * @param {?string} detail - detail information of history + */ + + + _createClass(History, [{ + key: "add", + value: function add(_ref2) { + var name = _ref2.name, + detail = _ref2.detail; + + if (this._hasDisabledItem()) { + this.deleteListItemElement(this._historyIndex + 1, this.getListLength()); + } + + var html = submenu_history({ + locale: this.locale, + makeSvgIcon: this.makeSvgIcon, + name: name, + detail: detail + }); + var item = this.makeListItemElement(html); + this.pushListItemElement(item); + this._historyIndex = this.getListLength() - 1; + + this._selectItem(this._historyIndex); + } + /** + * Init history + */ + + }, { + key: "init", + value: function init() { + this.deleteListItemElement(1, this.getListLength()); + this._historyIndex = 0; + + this._selectItem(this._historyIndex); + } + /** + * Clear history + */ + + }, { + key: "clear", + value: function clear() { + this.deleteListItemElement(0, this.getListLength()); + this._historyIndex = -1; + } + /** + * Select previous history of current selected history + */ + + }, { + key: "prev", + value: function prev() { + this._historyIndex -= 1; + + this._selectItem(this._historyIndex); + } + /** + * Select next history of current selected history + */ + + }, { + key: "next", + value: function next() { + this._historyIndex += 1; + + this._selectItem(this._historyIndex); + } + /** + * Whether history menu has disabled item + * @returns {boolean} + */ + + }, { + key: "_hasDisabledItem", + value: function _hasDisabledItem() { + return this.getListLength() - 1 > this._historyIndex; + } + /** + * Add history menu event + * @private + */ + + }, { + key: "_addHistoryEventListener", + value: function _addHistoryEventListener() { + var _this2 = this; + + this._eventHandler.history = function (event) { + return _this2._clickHistoryItem(event); + }; + + this.listElement.addEventListener('click', this._eventHandler.history); + } + /** + * Remove history menu event + * @private + */ + + }, { + key: "_removeHistoryEventListener", + value: function _removeHistoryEventListener() { + this.listElement.removeEventListener('click', this._eventHandler.history); + } + /** + * onClick history menu event listener + * @param {object} event - event object + * @private + */ + + }, { + key: "_clickHistoryItem", + value: function _clickHistoryItem(event) { + var target = event.target; + var item = target.closest(".".concat(historyClassName)); + + if (!item) { + return; + } + + var index = number_parse_int_default()(item.getAttribute('data-index'), 10); + + if (index !== this._historyIndex) { + var count = Math.abs(index - this._historyIndex); + + if (index < this._historyIndex) { + this._actions.undo(count); + } else { + this._actions.redo(count); + } + } + } + /** + * Change item's state to selected state + * @param {number} index - index of selected item + */ + + }, { + key: "_selectItem", + value: function _selectItem(index) { + for (var i = 0; i < this.getListLength(); i += 1) { + this.removeClass(i, selectedClassName); + this.removeClass(i, disabledClassName); + + if (i > index) { + this.addClass(i, disabledClassName); + } + } + + this.addClass(index, selectedClassName); + } + /** + * Destroys the instance. + */ + + }, { + key: "destroy", + value: function destroy() { + this.removeEvent(); + assignmentForDestroy(this); + } + /** + * Add event for history + * @param {Object} actions - actions for crop + * @param {Function} actions.undo - undo action + * @param {Function} actions.redo - redo action + */ + + }, { + key: "addEvent", + value: function addEvent(actions) { + this._actions = actions; + + this._addHistoryEventListener(); + } + /** + * Remove event + * @private + */ + + }, { + key: "removeEvent", + value: function removeEvent() { + this._removeHistoryEventListener(); + } + }]); + + return History; +}(panelMenu); + +/* harmony default export */ var ui_history = (History); +;// CONCATENATED MODULE: ./src/js/ui/locale/locale.js + + + +/** + * Translate messages + */ +var Locale = /*#__PURE__*/function () { + function Locale(locale) { + _classCallCheck(this, Locale); + + this._locale = locale; + } + /** + * localize message + * @param {string} message - message who will be localized + * @returns {string} + */ + + + _createClass(Locale, [{ + key: "localize", + value: function localize(message) { + return this._locale[message] || message; + } + }]); + + return Locale; +}(); + +/* harmony default export */ var locale = (Locale); +;// CONCATENATED MODULE: ./src/js/ui.js + + + + + + + + + + + + + + + + + + + + + + + + + + +var SUB_UI_COMPONENT = { + Shape: ui_shape, + Crop: ui_crop, + Resize: ui_resize, + Flip: ui_flip, + Rotate: ui_rotate, + Text: ui_text, + Mask: ui_mask, + Icon: ui_icon, + Draw: ui_draw, + Filter: ui_filter +}; +var BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION = '1300'; +var HISTORY_MENU = 'history'; +var HISTORY_PANEL_CLASS_NAME = 'tie-panel-history'; +var CLASS_NAME_ON = 'on'; +var ZOOM_BUTTON_TYPE = { + ZOOM_IN: 'zoomIn', + HAND: 'hand' +}; +/** + * Ui class + * @class + * @param {string|HTMLElement} element - Wrapper's element or selector + * @param {Object} [options] - Ui setting options + * @param {number} options.loadImage - Init default load image + * @param {number} options.initMenu - Init start menu + * @param {Boolean} [options.menuBarPosition=bottom] - Let + * @param {Boolean} [options.applyCropSelectionStyle=false] - Let + * @param {Boolean} [options.usageStatistics=false] - Use statistics or not + * @param {Object} [options.uiSize] - ui size of editor + * @param {string} options.uiSize.width - width of ui + * @param {string} options.uiSize.height - height of ui + * @param {Object} actions - ui action instance + */ + +var Ui = /*#__PURE__*/function () { + function Ui(element, options, actions) { + _classCallCheck(this, Ui); + + this.options = this._initializeOption(options); + this._actions = actions; + this.submenu = false; + this.imageSize = {}; + this.uiSize = {}; + this._locale = new locale(this.options.locale); + this.theme = new theme(this.options.theme); + this.eventHandler = {}; + this._submenuChangeTransection = false; + this._selectedElement = null; + this._mainElement = null; + this._editorElementWrap = null; + this._editorElement = null; + this._menuBarElement = null; + this._subMenuElement = null; + + this._makeUiElement(element); + + this._setUiSize(); + + this._initMenuEvent = false; + + this._makeSubMenu(); + + this._attachHistoryEvent(); + + this._attachZoomEvent(); + } + /** + * Destroys the instance. + */ + + + _createClass(Ui, [{ + key: "destroy", + value: function destroy() { + this._removeUiEvent(); + + this._destroyAllMenu(); + + this._selectedElement.innerHTML = ''; + assignmentForDestroy(this); + } + /** + * Set Default Selection for includeUI + * @param {Object} option - imageEditor options + * @returns {Object} - extends selectionStyle option + * @ignore + */ + + }, { + key: "setUiDefaultSelectionStyle", + value: function setUiDefaultSelectionStyle(option) { + return extend_default()({ + applyCropSelectionStyle: true, + applyGroupSelectionStyle: true, + selectionStyle: { + cornerStyle: 'circle', + cornerSize: 16, + cornerColor: '#fff', + cornerStrokeColor: '#fff', + transparentCorners: false, + lineWidth: 2, + borderColor: '#fff' + } + }, option); + } + /** + * Change editor size + * @param {Object} resizeInfo - ui & image size info + * @param {Object} [resizeInfo.uiSize] - image size dimension + * @param {string} resizeInfo.uiSize.width - ui width + * @param {string} resizeInfo.uiSize.height - ui height + * @param {Object} [resizeInfo.imageSize] - image size dimension + * @param {Number} resizeInfo.imageSize.oldWidth - old width + * @param {Number} resizeInfo.imageSize.oldHeight - old height + * @param {Number} resizeInfo.imageSize.newWidth - new width + * @param {Number} resizeInfo.imageSize.newHeight - new height + * @example + * // Change the image size and ui size, and change the affected ui state together. + * imageEditor.ui.resizeEditor({ + * imageSize: {oldWidth: 100, oldHeight: 100, newWidth: 700, newHeight: 700}, + * uiSize: {width: 1000, height: 1000} + * }); + * @example + * // Apply the ui state while preserving the previous attribute (for example, if responsive Ui) + * imageEditor.ui.resizeEditor(); + */ + + }, { + key: "resizeEditor", + value: function resizeEditor() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + uiSize = _ref.uiSize, + _ref$imageSize = _ref.imageSize, + imageSize = _ref$imageSize === void 0 ? this.imageSize : _ref$imageSize; + + if (imageSize !== this.imageSize) { + this.imageSize = imageSize; + } + + if (uiSize) { + this._setUiSize(uiSize); + } + + var _this$_getCanvasMaxDi = this._getCanvasMaxDimension(), + width = _this$_getCanvasMaxDi.width, + height = _this$_getCanvasMaxDi.height; + + var editorElementStyle = this._editorElement.style; + var menuBarPosition = this.options.menuBarPosition; + editorElementStyle.height = "".concat(height, "px"); + editorElementStyle.width = "".concat(width, "px"); + + this._setEditorPosition(menuBarPosition); + + this._editorElementWrap.style.bottom = "0px"; + this._editorElementWrap.style.top = "0px"; + this._editorElementWrap.style.left = "0px"; + this._editorElementWrap.style.width = "100%"; + var selectElementClassList = this._selectedElement.classList; + + if (menuBarPosition === 'top' && this._selectedElement.offsetWidth < BI_EXPRESSION_MINSIZE_WHEN_TOP_POSITION) { + selectElementClassList.add('tui-image-editor-top-optimization'); + } else { + selectElementClassList.remove('tui-image-editor-top-optimization'); + } + } + /** + * Toggle zoom button status + * @param {string} type - type of zoom button + */ + + }, { + key: "toggleZoomButtonStatus", + value: function toggleZoomButtonStatus(type) { + var targetClassList = this._buttonElements[type].classList; + targetClassList.toggle(CLASS_NAME_ON); + + if (type === ZOOM_BUTTON_TYPE.ZOOM_IN) { + this._buttonElements[ZOOM_BUTTON_TYPE.HAND].classList.remove(CLASS_NAME_ON); + } else { + this._buttonElements[ZOOM_BUTTON_TYPE.ZOOM_IN].classList.remove(CLASS_NAME_ON); + } + } + /** + * Turn off zoom-in button status + */ + + }, { + key: "offZoomInButtonStatus", + value: function offZoomInButtonStatus() { + var zoomInClassList = this._buttonElements[ZOOM_BUTTON_TYPE.ZOOM_IN].classList; + zoomInClassList.remove(CLASS_NAME_ON); + } + /** + * Change hand button status + * @param {boolean} enabled - status to change + */ + + }, { + key: "changeHandButtonStatus", + value: function changeHandButtonStatus(enabled) { + var handClassList = this._buttonElements[ZOOM_BUTTON_TYPE.HAND].classList; + handClassList[enabled ? 'add' : 'remove'](CLASS_NAME_ON); + } + /** + * Change help button status + * @param {string} buttonType - target button type + * @param {Boolean} enableStatus - enabled status + * @ignore + */ + + }, { + key: "changeHelpButtonEnabled", + value: function changeHelpButtonEnabled(buttonType, enableStatus) { + var buttonClassList = this._buttonElements[buttonType].classList; + buttonClassList[enableStatus ? 'add' : 'remove']('enabled'); + } + /** + * Change delete button status + * @param {Object} [options] - Ui setting options + * @param {object} [options.loadImage] - Init default load image + * @param {string} [options.initMenu] - Init start menu + * @param {string} [options.menuBarPosition=bottom] - Let + * @param {boolean} [options.applyCropSelectionStyle=false] - Let + * @param {boolean} [options.usageStatistics=false] - Send statistics ping or not + * @returns {Object} initialize option + * @private + */ + + }, { + key: "_initializeOption", + value: function _initializeOption(options) { + return extend_default()({ + loadImage: { + path: '', + name: '' + }, + locale: {}, + menuIconPath: '', + menu: ['resize', 'crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'], + initMenu: '', + uiSize: { + width: '100%', + height: '100%' + }, + menuBarPosition: 'bottom' + }, options); + } + /** + * Set ui container size + * @param {Object} uiSize - ui dimension + * @param {string} uiSize.width - css width property + * @param {string} uiSize.height - css height property + * @private + */ + + }, { + key: "_setUiSize", + value: function _setUiSize() { + var uiSize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.options.uiSize; + var elementDimension = this._selectedElement.style; + elementDimension.width = uiSize.width; + elementDimension.height = uiSize.height; + } + /** + * Make submenu dom element + * @private + */ + + }, { + key: "_makeSubMenu", + value: function _makeSubMenu() { + var _this = this; + + forEach_default()(this.options.menu, function (menuName) { + var _context; + + var SubComponentClass = SUB_UI_COMPONENT[menuName.replace(/^[a-z]/, function ($0) { + return $0.toUpperCase(); + })]; // make menu element + + _this._makeMenuElement(menuName); // menu btn element + + + _this._buttonElements[menuName] = _this._menuBarElement.querySelector(".tie-btn-".concat(menuName)); // submenu ui instance + + _this[menuName] = new SubComponentClass(_this._subMenuElement, { + locale: _this._locale, + makeSvgIcon: bind_default()(_context = _this.theme.makeMenSvgIconSet).call(_context, _this.theme), + menuBarPosition: _this.options.menuBarPosition, + usageStatistics: _this.options.usageStatistics + }); + }); + } + /** + * Attach history event + * @private + */ + + }, { + key: "_attachHistoryEvent", + value: function _attachHistoryEvent() { + var _context2, _context3, _context4; + + this.on(eventNames.EXECUTE_COMMAND, bind_default()(_context2 = this._addHistory).call(_context2, this)); + this.on(eventNames.AFTER_UNDO, bind_default()(_context3 = this._selectPrevHistory).call(_context3, this)); + this.on(eventNames.AFTER_REDO, bind_default()(_context4 = this._selectNextHistory).call(_context4, this)); + } + /** + * Attach zoom event + * @private + */ + + }, { + key: "_attachZoomEvent", + value: function _attachZoomEvent() { + var _this2 = this; + + this.on(eventNames.HAND_STARTED, function () { + _this2.offZoomInButtonStatus(); + + _this2.changeHandButtonStatus(true); + }); + this.on(eventNames.HAND_STOPPED, function () { + return _this2.changeHandButtonStatus(false); + }); + } + /** + * Make primary ui dom element + * @param {string|HTMLElement} element - Wrapper's element or selector + * @private + */ + + }, { + key: "_makeUiElement", + value: function _makeUiElement(element) { + var _context5; + + var selectedElement; + + if (element.nodeType) { + selectedElement = element; + } else { + selectedElement = document.querySelector(element); + } + + var selector = getSelector(selectedElement); + selectedElement.classList.add('tui-image-editor-container'); + selectedElement.innerHTML = controls({ + locale: this._locale, + biImage: this.theme.getStyle('common.bi'), + loadButtonStyle: this.theme.getStyle('loadButton'), + downloadButtonStyle: this.theme.getStyle('downloadButton'), + menuBarPosition: this.options.menuBarPosition + }) + mainContainer({ + locale: this._locale, + biImage: this.theme.getStyle('common.bi'), + commonStyle: this.theme.getStyle('common'), + headerStyle: this.theme.getStyle('header'), + loadButtonStyle: this.theme.getStyle('loadButton'), + downloadButtonStyle: this.theme.getStyle('downloadButton'), + submenuStyle: this.theme.getStyle('submenu') + }); + this._selectedElement = selectedElement; + + this._selectedElement.classList.add(this.options.menuBarPosition); + + this._mainElement = selector('.tui-image-editor-main'); + this._editorElementWrap = selector('.tui-image-editor-wrap'); + this._editorElement = selector('.tui-image-editor'); + this._helpMenuBarElement = selector('.tui-image-editor-help-menu'); + this._menuBarElement = selector('.tui-image-editor-menu'); + this._subMenuElement = selector('.tui-image-editor-submenu'); + this._buttonElements = { + download: this._selectedElement.querySelectorAll('.tui-image-editor-download-btn'), + load: this._selectedElement.querySelectorAll('.tui-image-editor-load-btn') + }; + + this._addHelpMenus(); + + this._historyMenu = new ui_history(this._buttonElements[HISTORY_MENU], { + locale: this._locale, + makeSvgIcon: bind_default()(_context5 = this.theme.makeMenSvgIconSet).call(_context5, this.theme) + }); + + this._activateZoomMenus(); + } + /** + * Activate help menus for zoom. + * @private + */ + + }, { + key: "_activateZoomMenus", + value: function _activateZoomMenus() { + var _this3 = this; + + forEach_default()(ZOOM_HELP_MENUS, function (menu) { + _this3.changeHelpButtonEnabled(menu, true); + }); + } + /** + * make array for help menu output, including partitions. + * @returns {Array} + * @private + */ + + }, { + key: "_makeHelpMenuWithPartition", + value: function _makeHelpMenuWithPartition() { + var _context6; + + return concat_default()(_context6 = []).call(_context6, _toConsumableArray(ZOOM_HELP_MENUS), [''], _toConsumableArray(COMMAND_HELP_MENUS), [''], _toConsumableArray(DELETE_HELP_MENUS)); + } + /** + * Add help menu + * @private + */ + + }, { + key: "_addHelpMenus", + value: function _addHelpMenus() { + var _this4 = this; + + var helpMenuWithPartition = this._makeHelpMenuWithPartition(); + + forEach_default()(helpMenuWithPartition, function (menuName) { + if (!menuName) { + _this4._makeMenuPartitionElement(); + } else { + _this4._makeMenuElement(menuName, ['normal', 'disabled', 'hover'], 'help'); + + _this4._buttonElements[menuName] = _this4._helpMenuBarElement.querySelector(".tie-btn-".concat(menuName)); + } + }); + } + /** + * Make menu partition element + * @private + */ + + }, { + key: "_makeMenuPartitionElement", + value: function _makeMenuPartitionElement() { + var partitionElement = document.createElement('li'); + var partitionInnerElement = document.createElement('div'); + partitionElement.className = cls('item'); + partitionInnerElement.className = cls('icpartition'); + partitionElement.appendChild(partitionInnerElement); + + this._helpMenuBarElement.appendChild(partitionElement); + } + /** + * Make menu button element + * @param {string} menuName - menu name + * @param {Array} useIconTypes - Possible values are \['normal', 'active', 'hover', 'disabled'\] + * @param {string} menuType - 'normal' or 'help' + * @private + */ + + }, { + key: "_makeMenuElement", + value: function _makeMenuElement(menuName) { + var _context7, _context8; + + var useIconTypes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ['normal', 'active', 'hover']; + var menuType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'normal'; + var btnElement = document.createElement('li'); + var menuItemHtml = this.theme.makeMenSvgIconSet(useIconTypes, menuName); + + this._addTooltipAttribute(btnElement, menuName); + + btnElement.className = concat_default()(_context7 = concat_default()(_context8 = "tie-btn-".concat(menuName, " ")).call(_context8, cls('item'), " ")).call(_context7, menuType); + btnElement.innerHTML = menuItemHtml; + + if (menuType === 'normal') { + this._menuBarElement.appendChild(btnElement); + } else { + this._helpMenuBarElement.appendChild(btnElement); + } + } + /** + * Add help action event + * @private + */ + + }, { + key: "_addHelpActionEvent", + value: function _addHelpActionEvent() { + var _this5 = this; + + forEach_default()(HELP_MENUS, function (helpName) { + _this5.eventHandler[helpName] = function (event) { + return _this5._actions.main[helpName](event); + }; + + _this5._buttonElements[helpName].addEventListener('click', _this5.eventHandler[helpName]); + }); + } + /** + * Remove help action event + * @private + */ + + }, { + key: "_removeHelpActionEvent", + value: function _removeHelpActionEvent() { + var _this6 = this; + + forEach_default()(HELP_MENUS, function (helpName) { + _this6._buttonElements[helpName].removeEventListener('click', _this6.eventHandler[helpName]); + }); + } + /** + * Add history + * @param {Command|string} command - command or command name + */ + + }, { + key: "_addHistory", + value: function _addHistory(command) { + if (!isSilentCommand(command)) { + var historyTitle = typeof command === 'string' ? { + name: command + } : getHistoryTitle(command); + + this._historyMenu.add(historyTitle); + } + } + /** + * Init history + */ + + }, { + key: "initHistory", + value: function initHistory() { + this._historyMenu.init(); + } + /** + * Clear history + */ + + }, { + key: "clearHistory", + value: function clearHistory() { + this._historyMenu.clear(); + } + /** + * Select prev history + */ + + }, { + key: "_selectPrevHistory", + value: function _selectPrevHistory() { + this._historyMenu.prev(); + } + /** + * Select next history + */ + + }, { + key: "_selectNextHistory", + value: function _selectNextHistory() { + this._historyMenu.next(); + } + /** + * Toggle history menu + * @param {object} event - event object + */ + + }, { + key: "toggleHistoryMenu", + value: function toggleHistoryMenu(event) { + var target = event.target; + var item = target.closest(".".concat(HISTORY_PANEL_CLASS_NAME)); + + if (item) { + return; + } + + var historyButtonClassList = this._buttonElements[HISTORY_MENU].classList; + historyButtonClassList.toggle('opened'); + } + /** + * Add attribute for menu tooltip + * @param {HTMLElement} element - menu element + * @param {string} tooltipName - tooltipName + * @private + */ + + }, { + key: "_addTooltipAttribute", + value: function _addTooltipAttribute(element, tooltipName) { + element.setAttribute('tooltip-content', this._locale.localize(tooltipName.replace(/^[a-z]/g, function ($0) { + return $0.toUpperCase(); + }))); + } + /** + * Add download event + * @private + */ + + }, { + key: "_addDownloadEvent", + value: function _addDownloadEvent() { + var _this7 = this; + + this.eventHandler.download = function () { + return _this7._actions.main.download(); + }; + + forEach_default()(this._buttonElements.download, function (element) { + element.addEventListener('click', _this7.eventHandler.download); + }); + } + }, { + key: "_removeDownloadEvent", + value: function _removeDownloadEvent() { + var _this8 = this; + + forEach_default()(this._buttonElements.download, function (element) { + element.removeEventListener('click', _this8.eventHandler.download); + }); + } + /** + * Add load event + * @private + */ + + }, { + key: "_addLoadEvent", + value: function _addLoadEvent() { + var _this9 = this; + + this.eventHandler.loadImage = function (event) { + return _this9._actions.main.load(event.target.files[0]); + }; + + forEach_default()(this._buttonElements.load, function (element) { + element.addEventListener('change', _this9.eventHandler.loadImage); + }); + } + /** + * Remove load event + * @private + */ + + }, { + key: "_removeLoadEvent", + value: function _removeLoadEvent() { + var _this10 = this; + + forEach_default()(this._buttonElements.load, function (element) { + element.removeEventListener('change', _this10.eventHandler.loadImage); + }); + } + /** + * Add menu event + * @param {string} menuName - menu name + * @private + */ + + }, { + key: "_addMainMenuEvent", + value: function _addMainMenuEvent(menuName) { + var _this11 = this; + + this.eventHandler[menuName] = function () { + return _this11.changeMenu(menuName); + }; + + this._buttonElements[menuName].addEventListener('click', this.eventHandler[menuName]); + } + /** + * Add menu event + * @param {string} menuName - menu name + * @private + */ + + }, { + key: "_addSubMenuEvent", + value: function _addSubMenuEvent(menuName) { + var _this12 = this; + + this[menuName].addEvent(this._actions[menuName]); + this[menuName].on(eventNames.INPUT_BOX_EDITING_STARTED, function () { + return _this12.fire(eventNames.INPUT_BOX_EDITING_STARTED); + }); + this[menuName].on(eventNames.INPUT_BOX_EDITING_STOPPED, function () { + return _this12.fire(eventNames.INPUT_BOX_EDITING_STOPPED); + }); + } + /** + * Add menu event + * @private + */ + + }, { + key: "_addMenuEvent", + value: function _addMenuEvent() { + var _this13 = this; + + forEach_default()(this.options.menu, function (menuName) { + _this13._addMainMenuEvent(menuName); + + _this13._addSubMenuEvent(menuName); + }); + } + /** + * Remove menu event + * @private + */ + + }, { + key: "_removeMainMenuEvent", + value: function _removeMainMenuEvent() { + var _this14 = this; + + forEach_default()(this.options.menu, function (menuName) { + _this14._buttonElements[menuName].removeEventListener('click', _this14.eventHandler[menuName]); + + _this14[menuName].off(eventNames.INPUT_BOX_EDITING_STARTED); + + _this14[menuName].off(eventNames.INPUT_BOX_EDITING_STOPPED); + }); + } + /** + * Get editor area element + * @returns {HTMLElement} editor area html element + * @ignore + */ + + }, { + key: "getEditorArea", + value: function getEditorArea() { + return this._editorElement; + } + /** + * Add event for menu items + * @ignore + */ + + }, { + key: "activeMenuEvent", + value: function activeMenuEvent() { + if (this._initMenuEvent) { + return; + } + + this._addHelpActionEvent(); + + this._addDownloadEvent(); + + this._addMenuEvent(); + + this._initMenu(); + + this._historyMenu.addEvent(this._actions.history); + + this._initMenuEvent = true; + } + /** + * Remove ui event + * @private + */ + + }, { + key: "_removeUiEvent", + value: function _removeUiEvent() { + this._removeHelpActionEvent(); + + this._removeDownloadEvent(); + + this._removeLoadEvent(); + + this._removeMainMenuEvent(); + + this._historyMenu.removeEvent(); + } + /** + * Destroy all menu instance + * @private + */ + + }, { + key: "_destroyAllMenu", + value: function _destroyAllMenu() { + var _this15 = this; + + forEach_default()(this.options.menu, function (menuName) { + _this15[menuName].destroy(); + }); + + this._historyMenu.destroy(); + } + /** + * Init canvas + * @ignore + */ + + }, { + key: "initCanvas", + value: function initCanvas() { + var _this16 = this; + + var loadImageInfo = this._getLoadImage(); + + if (loadImageInfo.path) { + this._actions.main.initLoadImage(loadImageInfo.path, loadImageInfo.name).then(function () { + _this16.activeMenuEvent(); + }); + } + + this._addLoadEvent(); + + var gridVisual = document.createElement('div'); + gridVisual.className = cls('grid-visual'); + var grid = "\n \n \n \n
        "; + gridVisual.innerHTML = grid; + this._editorContainerElement = this._editorElement.querySelector('.tui-image-editor-canvas-container'); + + this._editorContainerElement.appendChild(gridVisual); + } + /** + * get editor area element + * @returns {Object} load image option + * @private + */ + + }, { + key: "_getLoadImage", + value: function _getLoadImage() { + return this.options.loadImage; + } + /** + * change menu + * @param {string} menuName - menu name + * @param {boolean} toggle - whether toogle or not + * @param {boolean} discardSelection - discard selection + * @ignore + */ + + }, { + key: "changeMenu", + value: function changeMenu(menuName) { + var toggle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; + var discardSelection = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; + + if (!this._submenuChangeTransection) { + this._submenuChangeTransection = true; + + this._changeMenu(menuName, toggle, discardSelection); + + this._submenuChangeTransection = false; + } + } + /** + * change menu + * @param {string} menuName - menu name + * @param {boolean} toggle - whether toggle or not + * @param {boolean} discardSelection - discard selection + * @private + */ + + }, { + key: "_changeMenu", + value: function _changeMenu(menuName, toggle, discardSelection) { + if (this.submenu) { + this._buttonElements[this.submenu].classList.remove('active'); + + this._mainElement.classList.remove("tui-image-editor-menu-".concat(this.submenu)); + + if (discardSelection) { + this._actions.main.discardSelection(); + } + + this._actions.main.changeSelectableAll(true); + + this[this.submenu].changeStandbyMode(); + } + + if (this.submenu === menuName && toggle) { + this.submenu = null; + } else { + this._buttonElements[menuName].classList.add('active'); + + this._mainElement.classList.add("tui-image-editor-menu-".concat(menuName)); + + this.submenu = menuName; + this[this.submenu].changeStartMode(); + } + + this.resizeEditor(); + } + /** + * Init menu + * @private + */ + + }, { + key: "_initMenu", + value: function _initMenu() { + if (this.options.initMenu) { + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, false); + + this._buttonElements[this.options.initMenu].dispatchEvent(evt); + } + + if (this.icon) { + this.icon.registerDefaultIcon(); + } + } + /** + * Get canvas max Dimension + * @returns {Object} - width & height of editor + * @private + */ + + }, { + key: "_getCanvasMaxDimension", + value: function _getCanvasMaxDimension() { + var _this$_editorContaine = this._editorContainerElement.style, + maxWidth = _this$_editorContaine.maxWidth, + maxHeight = _this$_editorContaine.maxHeight; + + var width = parse_float_default()(maxWidth); + + var height = parse_float_default()(maxHeight); + + return { + width: width, + height: height + }; + } + /** + * Set editor position + * @param {string} menuBarPosition - top or right or bottom or left + * @private + */ + // eslint-disable-next-line complexity + + }, { + key: "_setEditorPosition", + value: function _setEditorPosition(menuBarPosition) { + var _this$_getCanvasMaxDi2 = this._getCanvasMaxDimension(), + width = _this$_getCanvasMaxDi2.width, + height = _this$_getCanvasMaxDi2.height; + + var editorElementStyle = this._editorElement.style; + var top = 0; + var left = 0; + + if (this.submenu) { + if (menuBarPosition === 'bottom') { + if (height > this._editorElementWrap.scrollHeight - 150) { + top = (height - this._editorElementWrap.scrollHeight) / 2; + } else { + top = 150 / 2 * -1; + } + } else if (menuBarPosition === 'top') { + if (height > this._editorElementWrap.offsetHeight - 150) { + top = 150 / 2 - (height - (this._editorElementWrap.offsetHeight - 150)) / 2; + } else { + top = 150 / 2; + } + } else if (menuBarPosition === 'left') { + if (width > this._editorElementWrap.offsetWidth - 248) { + left = 248 / 2 - (width - (this._editorElementWrap.offsetWidth - 248)) / 2; + } else { + left = 248 / 2; + } + } else if (menuBarPosition === 'right') { + if (width > this._editorElementWrap.scrollWidth - 248) { + left = (width - this._editorElementWrap.scrollWidth) / 2; + } else { + left = 248 / 2 * -1; + } + } + } + + editorElementStyle.top = "".concat(top, "px"); + editorElementStyle.left = "".concat(left, "px"); + } + }]); + + return Ui; +}(); + +customEvents_default().mixin(Ui); +/* harmony default export */ var ui = (Ui); +// EXTERNAL MODULE: ../../node_modules/@babel/runtime-corejs3/core-js-stable/instance/filter.js +var instance_filter = __webpack_require__(381); +var filter_default = /*#__PURE__*/__webpack_require__.n(instance_filter); +;// CONCATENATED MODULE: ./src/js/helper/imagetracer.js + + + + + +/* + imagetracer.js version 1.2.4 + Simple raster image tracer and vectorizer written in JavaScript. + andras@jankovics.net +*/ + +/* + The Unlicense / PUBLIC DOMAIN + This is free and unencumbered software released into the public domain. + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + For more information, please refer to http://unlicense.org/ +*/ +var ImageTracer = /*#__PURE__*/function () { + /* eslint-disable */ + function ImageTracer() { + _classCallCheck(this, ImageTracer); + + this.versionnumber = '1.2.4'; + this.optionpresets = { + default: { + corsenabled: false, + ltres: 1, + qtres: 1, + pathomit: 8, + rightangleenhance: true, + colorsampling: 2, + numberofcolors: 16, + mincolorratio: 0, + colorquantcycles: 3, + layering: 0, + strokewidth: 1, + linefilter: false, + scale: 1, + roundcoords: 1, + viewbox: false, + desc: false, + lcpr: 0, + qcpr: 0, + blurradius: 0, + blurdelta: 20 + }, + posterized1: { + colorsampling: 0, + numberofcolors: 2 + }, + posterized2: { + numberofcolors: 4, + blurradius: 5 + }, + curvy: { + ltres: 0.01, + linefilter: true, + rightangleenhance: false + }, + sharp: { + qtres: 0.01, + linefilter: false + }, + detailed: { + pathomit: 0, + roundcoords: 2, + ltres: 0.5, + qtres: 0.5, + numberofcolors: 64 + }, + smoothed: { + blurradius: 5, + blurdelta: 64 + }, + grayscale: { + colorsampling: 0, + colorquantcycles: 1, + numberofcolors: 7 + }, + fixedpalette: { + colorsampling: 0, + colorquantcycles: 1, + numberofcolors: 27 + }, + randomsampling1: { + colorsampling: 1, + numberofcolors: 8 + }, + randomsampling2: { + colorsampling: 1, + numberofcolors: 64 + }, + artistic1: { + colorsampling: 0, + colorquantcycles: 1, + pathomit: 0, + blurradius: 5, + blurdelta: 64, + ltres: 0.01, + linefilter: true, + numberofcolors: 16, + strokewidth: 2 + }, + artistic2: { + qtres: 0.01, + colorsampling: 0, + colorquantcycles: 1, + numberofcolors: 4, + strokewidth: 0 + }, + artistic3: { + qtres: 10, + ltres: 10, + numberofcolors: 8 + }, + artistic4: { + qtres: 10, + ltres: 10, + numberofcolors: 64, + blurradius: 5, + blurdelta: 256, + strokewidth: 2 + }, + posterized3: { + ltres: 1, + qtres: 1, + pathomit: 20, + rightangleenhance: true, + colorsampling: 0, + numberofcolors: 3, + mincolorratio: 0, + colorquantcycles: 3, + blurradius: 3, + blurdelta: 20, + strokewidth: 0, + linefilter: false, + roundcoords: 1, + pal: [{ + r: 0, + g: 0, + b: 100, + a: 255 + }, { + r: 255, + g: 255, + b: 255, + a: 255 + }] + } + }; + this.pathscan_combined_lookup = [[[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[0, 1, 0, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [0, 2, -1, 0]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [0, 1, 0, -1], [0, 0, 1, 0]], [[0, 0, 1, 0], [-1, -1, -1, -1], [0, 2, -1, 0], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [0, 0, 1, 0], [0, 3, 0, 1], [-1, -1, -1, -1]], [[13, 3, 0, 1], [13, 2, -1, 0], [7, 1, 0, -1], [7, 0, 1, 0]], [[-1, -1, -1, -1], [0, 1, 0, -1], [-1, -1, -1, -1], [0, 3, 0, 1]], [[0, 3, 0, 1], [0, 2, -1, 0], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[0, 3, 0, 1], [0, 2, -1, 0], [-1, -1, -1, -1], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [0, 1, 0, -1], [-1, -1, -1, -1], [0, 3, 0, 1]], [[11, 1, 0, -1], [14, 0, 1, 0], [14, 3, 0, 1], [11, 2, -1, 0]], [[-1, -1, -1, -1], [0, 0, 1, 0], [0, 3, 0, 1], [-1, -1, -1, -1]], [[0, 0, 1, 0], [-1, -1, -1, -1], [0, 2, -1, 0], [-1, -1, -1, -1]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [0, 1, 0, -1], [0, 0, 1, 0]], [[0, 1, 0, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [0, 2, -1, 0]], [[-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1], [-1, -1, -1, -1]]]; + this.gks = [[0.27901, 0.44198, 0.27901], [0.135336, 0.228569, 0.272192, 0.228569, 0.135336], [0.086776, 0.136394, 0.178908, 0.195843, 0.178908, 0.136394, 0.086776], [0.063327, 0.093095, 0.122589, 0.144599, 0.152781, 0.144599, 0.122589, 0.093095, 0.063327], [0.049692, 0.069304, 0.089767, 0.107988, 0.120651, 0.125194, 0.120651, 0.107988, 0.089767, 0.069304, 0.049692]]; + this.specpalette = [{ + r: 0, + g: 0, + b: 0, + a: 255 + }, { + r: 128, + g: 128, + b: 128, + a: 255 + }, { + r: 0, + g: 0, + b: 128, + a: 255 + }, { + r: 64, + g: 64, + b: 128, + a: 255 + }, { + r: 192, + g: 192, + b: 192, + a: 255 + }, { + r: 255, + g: 255, + b: 255, + a: 255 + }, { + r: 128, + g: 128, + b: 192, + a: 255 + }, { + r: 0, + g: 0, + b: 192, + a: 255 + }, { + r: 128, + g: 0, + b: 0, + a: 255 + }, { + r: 128, + g: 64, + b: 64, + a: 255 + }, { + r: 128, + g: 0, + b: 128, + a: 255 + }, { + r: 168, + g: 168, + b: 168, + a: 255 + }, { + r: 192, + g: 128, + b: 128, + a: 255 + }, { + r: 192, + g: 0, + b: 0, + a: 255 + }, { + r: 255, + g: 255, + b: 255, + a: 255 + }, { + r: 0, + g: 128, + b: 0, + a: 255 + }]; + } + + _createClass(ImageTracer, [{ + key: "imageToSVG", + value: function imageToSVG(url, callback, options) { + var _this = this; + + options = this.checkoptions(options); + this.loadImage(url, function (canvas) { + callback(_this.imagedataToSVG(_this.getImgdata(canvas), options)); + }, options); + } + }, { + key: "imagedataToSVG", + value: function imagedataToSVG(imgd, options) { + options = this.checkoptions(options); + var td = this.imagedataToTracedata(imgd, options); + return this.getsvgstring(td, options); + } + }, { + key: "imageToTracedata", + value: function imageToTracedata(url, callback, options) { + var _this2 = this; + + options = this.checkoptions(options); + this.loadImage(url, function (canvas) { + callback(_this2.imagedataToTracedata(_this2.getImgdata(canvas), options)); + }, options); + } + }, { + key: "imagedataToTracedata", + value: function imagedataToTracedata(imgd, options) { + options = this.checkoptions(options); + var ii = this.colorquantization(imgd, options); + var tracedata; + + if (options.layering === 0) { + tracedata = { + layers: [], + palette: ii.palette, + width: ii.array[0].length - 2, + height: ii.array.length - 2 + }; + + for (var colornum = 0; colornum < ii.palette.length; colornum += 1) { + var tracedlayer = this.batchtracepaths(this.internodes(this.pathscan(this.layeringstep(ii, colornum), options.pathomit), options), options.ltres, options.qtres); + tracedata.layers.push(tracedlayer); + } + } else { + var ls = this.layering(ii); + + if (options.layercontainerid) { + this.drawLayers(ls, this.specpalette, options.scale, options.layercontainerid); + } + + var bps = this.batchpathscan(ls, options.pathomit); + var bis = this.batchinternodes(bps, options); + tracedata = { + layers: this.batchtracelayers(bis, options.ltres, options.qtres), + palette: ii.palette, + width: imgd.width, + height: imgd.height + }; + } + + return tracedata; + } + }, { + key: "checkoptions", + value: function checkoptions(options) { + options = options || {}; + + if (typeof options === 'string') { + options = options.toLowerCase(); + + if (this.optionpresets[options]) { + options = this.optionpresets[options]; + } else { + options = {}; + } + } + + var ok = keys_default()(this.optionpresets['default']); + + for (var k = 0; k < ok.length; k += 1) { + if (!options.hasOwnProperty(ok[k])) { + options[ok[k]] = this.optionpresets['default'][ok[k]]; + } + } + + return options; + } + }, { + key: "colorquantization", + value: function colorquantization(imgd, options) { + var arr = []; + var idx = 0; + var cd; + var cdl; + var ci; + var paletteacc = []; + var pixelnum = imgd.width * imgd.height; + var i; + var j; + var k; + var cnt; + var palette; + + for (j = 0; j < imgd.height + 2; j += 1) { + arr[j] = []; + + for (i = 0; i < imgd.width + 2; i += 1) { + arr[j][i] = -1; + } + } + + if (options.pal) { + palette = options.pal; + } else if (options.colorsampling === 0) { + palette = this.generatepalette(options.numberofcolors); + } else if (options.colorsampling === 1) { + palette = this.samplepalette(options.numberofcolors, imgd); + } else { + palette = this.samplepalette2(options.numberofcolors, imgd); + } + + if (options.blurradius > 0) { + imgd = this.blur(imgd, options.blurradius, options.blurdelta); + } + + for (cnt = 0; cnt < options.colorquantcycles; cnt += 1) { + if (cnt > 0) { + for (k = 0; k < palette.length; k += 1) { + if (paletteacc[k].n > 0) { + palette[k] = { + r: Math.floor(paletteacc[k].r / paletteacc[k].n), + g: Math.floor(paletteacc[k].g / paletteacc[k].n), + b: Math.floor(paletteacc[k].b / paletteacc[k].n), + a: Math.floor(paletteacc[k].a / paletteacc[k].n) + }; + } + + if (paletteacc[k].n / pixelnum < options.mincolorratio && cnt < options.colorquantcycles - 1) { + palette[k] = { + r: Math.floor(Math.random() * 255), + g: Math.floor(Math.random() * 255), + b: Math.floor(Math.random() * 255), + a: Math.floor(Math.random() * 255) + }; + } + } + } + + for (i = 0; i < palette.length; i += 1) { + paletteacc[i] = { + r: 0, + g: 0, + b: 0, + a: 0, + n: 0 + }; + } + + for (j = 0; j < imgd.height; j += 1) { + for (i = 0; i < imgd.width; i += 1) { + idx = (j * imgd.width + i) * 4; + ci = 0; + cdl = 1024; + + for (k = 0; k < palette.length; k += 1) { + cd = Math.abs(palette[k].r - imgd.data[idx]) + Math.abs(palette[k].g - imgd.data[idx + 1]) + Math.abs(palette[k].b - imgd.data[idx + 2]) + Math.abs(palette[k].a - imgd.data[idx + 3]); + + if (cd < cdl) { + cdl = cd; + ci = k; + } + } + + paletteacc[ci].r += imgd.data[idx]; + paletteacc[ci].g += imgd.data[idx + 1]; + paletteacc[ci].b += imgd.data[idx + 2]; + paletteacc[ci].a += imgd.data[idx + 3]; + paletteacc[ci].n += 1; + arr[j + 1][i + 1] = ci; + } + } + } + + return { + array: arr, + palette: palette + }; + } + }, { + key: "samplepalette", + value: function samplepalette(numberofcolors, imgd) { + var idx; + var palette = []; + + for (var i = 0; i < numberofcolors; i += 1) { + idx = Math.floor(Math.random() * imgd.data.length / 4) * 4; + palette.push({ + r: imgd.data[idx], + g: imgd.data[idx + 1], + b: imgd.data[idx + 2], + a: imgd.data[idx + 3] + }); + } + + return palette; + } + }, { + key: "samplepalette2", + value: function samplepalette2(numberofcolors, imgd) { + var idx; + var palette = []; + var ni = Math.ceil(Math.sqrt(numberofcolors)); + var nj = Math.ceil(numberofcolors / ni); + var vx = imgd.width / (ni + 1); + var vy = imgd.height / (nj + 1); + + for (var j = 0; j < nj; j += 1) { + for (var i = 0; i < ni; i += 1) { + if (palette.length === numberofcolors) { + break; + } else { + idx = Math.floor((j + 1) * vy * imgd.width + (i + 1) * vx) * 4; + palette.push({ + r: imgd.data[idx], + g: imgd.data[idx + 1], + b: imgd.data[idx + 2], + a: imgd.data[idx + 3] + }); + } + } + } + + return palette; + } + }, { + key: "generatepalette", + value: function generatepalette(numberofcolors) { + var palette = []; + var rcnt; + var gcnt; + var bcnt; + + if (numberofcolors < 8) { + var graystep = Math.floor(255 / (numberofcolors - 1)); + + for (var i = 0; i < numberofcolors; i += 1) { + palette.push({ + r: i * graystep, + g: i * graystep, + b: i * graystep, + a: 255 + }); + } + } else { + var colorqnum = Math.floor(Math.pow(numberofcolors, 1 / 3)); + var colorstep = Math.floor(255 / (colorqnum - 1)); + var rndnum = numberofcolors - colorqnum * colorqnum * colorqnum; + + for (rcnt = 0; rcnt < colorqnum; rcnt += 1) { + for (gcnt = 0; gcnt < colorqnum; gcnt += 1) { + for (bcnt = 0; bcnt < colorqnum; bcnt += 1) { + palette.push({ + r: rcnt * colorstep, + g: gcnt * colorstep, + b: bcnt * colorstep, + a: 255 + }); + } + } + } + + for (rcnt = 0; rcnt < rndnum; rcnt += 1) { + palette.push({ + r: Math.floor(Math.random() * 255), + g: Math.floor(Math.random() * 255), + b: Math.floor(Math.random() * 255), + a: Math.floor(Math.random() * 255) + }); + } + } + + return palette; + } + }, { + key: "layering", + value: function layering(ii) { + var layers = []; + var val = 0; + var ah = ii.array.length; + var aw = ii.array[0].length; + var n1; + var n2; + var n3; + var n4; + var n5; + var n6; + var n7; + var n8; + var i; + var j; + var k; + + for (k = 0; k < ii.palette.length; k += 1) { + layers[k] = []; + + for (j = 0; j < ah; j += 1) { + layers[k][j] = []; + + for (i = 0; i < aw; i += 1) { + layers[k][j][i] = 0; + } + } + } + + for (j = 1; j < ah - 1; j += 1) { + for (i = 1; i < aw - 1; i += 1) { + val = ii.array[j][i]; + n1 = ii.array[j - 1][i - 1] === val ? 1 : 0; + n2 = ii.array[j - 1][i] === val ? 1 : 0; + n3 = ii.array[j - 1][i + 1] === val ? 1 : 0; + n4 = ii.array[j][i - 1] === val ? 1 : 0; + n5 = ii.array[j][i + 1] === val ? 1 : 0; + n6 = ii.array[j + 1][i - 1] === val ? 1 : 0; + n7 = ii.array[j + 1][i] === val ? 1 : 0; + n8 = ii.array[j + 1][i + 1] === val ? 1 : 0; + layers[val][j + 1][i + 1] = 1 + n5 * 2 + n8 * 4 + n7 * 8; + + if (!n4) { + layers[val][j + 1][i] = 0 + 2 + n7 * 4 + n6 * 8; + } + + if (!n2) { + layers[val][j][i + 1] = 0 + n3 * 2 + n5 * 4 + 8; + } + + if (!n1) { + layers[val][j][i] = 0 + n2 * 2 + 4 + n4 * 8; + } + } + } + + return layers; + } + }, { + key: "layeringstep", + value: function layeringstep(ii, cnum) { + var layer = []; + var ah = ii.array.length; + var aw = ii.array[0].length; + var i; + var j; + + for (j = 0; j < ah; j += 1) { + layer[j] = []; + + for (i = 0; i < aw; i += 1) { + layer[j][i] = 0; + } + } + + for (j = 1; j < ah; j += 1) { + for (i = 1; i < aw; i += 1) { + layer[j][i] = (ii.array[j - 1][i - 1] === cnum ? 1 : 0) + (ii.array[j - 1][i] === cnum ? 2 : 0) + (ii.array[j][i - 1] === cnum ? 8 : 0) + (ii.array[j][i] === cnum ? 4 : 0); + } + } + + return layer; + } + }, { + key: "pathscan", + value: function pathscan(arr, pathomit) { + var paths = []; + var pacnt = 0; + var pcnt = 0; + var px = 0; + var py = 0; + var w = arr[0].length; + var h = arr.length; + var dir = 0; + var pathfinished = true; + var holepath = false; + var lookuprow; + + for (var j = 0; j < h; j += 1) { + for (var i = 0; i < w; i += 1) { + if (arr[j][i] === 4 || arr[j][i] === 11) { + px = i; + py = j; + paths[pacnt] = {}; + paths[pacnt].points = []; + paths[pacnt].boundingbox = [px, py, px, py]; + paths[pacnt].holechildren = []; + pathfinished = false; + pcnt = 0; + holepath = arr[j][i] === 11; + dir = 1; + + while (!pathfinished) { + paths[pacnt].points[pcnt] = {}; + paths[pacnt].points[pcnt].x = px - 1; + paths[pacnt].points[pcnt].y = py - 1; + paths[pacnt].points[pcnt].t = arr[py][px]; + + if (px - 1 < paths[pacnt].boundingbox[0]) { + paths[pacnt].boundingbox[0] = px - 1; + } + + if (px - 1 > paths[pacnt].boundingbox[2]) { + paths[pacnt].boundingbox[2] = px - 1; + } + + if (py - 1 < paths[pacnt].boundingbox[1]) { + paths[pacnt].boundingbox[1] = py - 1; + } + + if (py - 1 > paths[pacnt].boundingbox[3]) { + paths[pacnt].boundingbox[3] = py - 1; + } + + lookuprow = this.pathscan_combined_lookup[arr[py][px]][dir]; + arr[py][px] = lookuprow[0]; + dir = lookuprow[1]; + px += lookuprow[2]; + py += lookuprow[3]; + + if (px - 1 === paths[pacnt].points[0].x && py - 1 === paths[pacnt].points[0].y) { + pathfinished = true; + + if (paths[pacnt].points.length < pathomit) { + paths.pop(); + } else { + paths[pacnt].isholepath = !!holepath; + + if (holepath) { + var parentidx = 0, + parentbbox = [-1, -1, w + 1, h + 1]; + + for (var parentcnt = 0; parentcnt < pacnt; parentcnt++) { + if (!paths[parentcnt].isholepath && this.boundingboxincludes(paths[parentcnt].boundingbox, paths[pacnt].boundingbox) && this.boundingboxincludes(parentbbox, paths[parentcnt].boundingbox)) { + parentidx = parentcnt; + parentbbox = paths[parentcnt].boundingbox; + } + } + + paths[parentidx].holechildren.push(pacnt); + } + + pacnt += 1; + } + } + + pcnt += 1; + } + } + } + } + + return paths; + } + }, { + key: "boundingboxincludes", + value: function boundingboxincludes(parentbbox, childbbox) { + return parentbbox[0] < childbbox[0] && parentbbox[1] < childbbox[1] && parentbbox[2] > childbbox[2] && parentbbox[3] > childbbox[3]; + } + }, { + key: "batchpathscan", + value: function batchpathscan(layers, pathomit) { + var bpaths = []; + + for (var k in layers) { + if (!layers.hasOwnProperty(k)) { + continue; + } + + bpaths[k] = this.pathscan(layers[k], pathomit); + } + + return bpaths; + } + }, { + key: "internodes", + value: function internodes(paths, options) { + var ins = []; + var palen = 0; + var nextidx = 0; + var nextidx2 = 0; + var previdx = 0; + var previdx2 = 0; + var pacnt; + var pcnt; + + for (pacnt = 0; pacnt < paths.length; pacnt += 1) { + ins[pacnt] = {}; + ins[pacnt].points = []; + ins[pacnt].boundingbox = paths[pacnt].boundingbox; + ins[pacnt].holechildren = paths[pacnt].holechildren; + ins[pacnt].isholepath = paths[pacnt].isholepath; + palen = paths[pacnt].points.length; + + for (pcnt = 0; pcnt < palen; pcnt += 1) { + nextidx = (pcnt + 1) % palen; + nextidx2 = (pcnt + 2) % palen; + previdx = (pcnt - 1 + palen) % palen; + previdx2 = (pcnt - 2 + palen) % palen; + + if (options.rightangleenhance && this.testrightangle(paths[pacnt], previdx2, previdx, pcnt, nextidx, nextidx2)) { + if (ins[pacnt].points.length > 0) { + ins[pacnt].points[ins[pacnt].points.length - 1].linesegment = this.getdirection(ins[pacnt].points[ins[pacnt].points.length - 1].x, ins[pacnt].points[ins[pacnt].points.length - 1].y, paths[pacnt].points[pcnt].x, paths[pacnt].points[pcnt].y); + } + + ins[pacnt].points.push({ + x: paths[pacnt].points[pcnt].x, + y: paths[pacnt].points[pcnt].y, + linesegment: this.getdirection(paths[pacnt].points[pcnt].x, paths[pacnt].points[pcnt].y, (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2) + }); + } + + ins[pacnt].points.push({ + x: (paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, + y: (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2, + linesegment: this.getdirection((paths[pacnt].points[pcnt].x + paths[pacnt].points[nextidx].x) / 2, (paths[pacnt].points[pcnt].y + paths[pacnt].points[nextidx].y) / 2, (paths[pacnt].points[nextidx].x + paths[pacnt].points[nextidx2].x) / 2, (paths[pacnt].points[nextidx].y + paths[pacnt].points[nextidx2].y) / 2) + }); + } + } + + return ins; + } + }, { + key: "testrightangle", + value: function testrightangle(path, idx1, idx2, idx3, idx4, idx5) { + return path.points[idx3].x === path.points[idx1].x && path.points[idx3].x === path.points[idx2].x && path.points[idx3].y === path.points[idx4].y && path.points[idx3].y === path.points[idx5].y || path.points[idx3].y === path.points[idx1].y && path.points[idx3].y === path.points[idx2].y && path.points[idx3].x === path.points[idx4].x && path.points[idx3].x === path.points[idx5].x; + } + }, { + key: "getdirection", + value: function getdirection(x1, y1, x2, y2) { + var val = 8; + + if (x1 < x2) { + if (y1 < y2) { + val = 1; + } else if (y1 > y2) { + val = 7; + } else { + val = 0; + } + } else if (x1 > x2) { + if (y1 < y2) { + val = 3; + } else if (y1 > y2) { + val = 5; + } else { + val = 4; + } + } else if (y1 < y2) { + val = 2; + } else if (y1 > y2) { + val = 6; + } else { + val = 8; + } + + return val; + } + }, { + key: "batchinternodes", + value: function batchinternodes(bpaths, options) { + var binternodes = []; + + for (var k in bpaths) { + if (!bpaths.hasOwnProperty(k)) { + continue; + } + + binternodes[k] = this.internodes(bpaths[k], options); + } + + return binternodes; + } + }, { + key: "tracepath", + value: function tracepath(path, ltres, qtres) { + var pcnt = 0; + var segtype1; + var segtype2; + var seqend; + var smp = {}; + smp.segments = []; + smp.boundingbox = path.boundingbox; + smp.holechildren = path.holechildren; + smp.isholepath = path.isholepath; + + while (pcnt < path.points.length) { + var _context; + + segtype1 = path.points[pcnt].linesegment; + segtype2 = -1; + seqend = pcnt + 1; + + while ((path.points[seqend].linesegment === segtype1 || path.points[seqend].linesegment === segtype2 || segtype2 === -1) && seqend < path.points.length - 1) { + if (path.points[seqend].linesegment !== segtype1 && segtype2 === -1) { + segtype2 = path.points[seqend].linesegment; + } + + seqend += 1; + } + + if (seqend === path.points.length - 1) { + seqend = 0; + } + + smp.segments = concat_default()(_context = smp.segments).call(_context, this.fitseq(path, ltres, qtres, pcnt, seqend)); + + if (seqend > 0) { + pcnt = seqend; + } else { + pcnt = path.points.length; + } + } + + return smp; + } + }, { + key: "fitseq", + value: function fitseq(path, ltres, qtres, seqstart, seqend) { + var _context2; + + if (seqend > path.points.length || seqend < 0) { + return []; + } + + var errorpoint = seqstart, + errorval = 0, + curvepass = true, + px, + py, + dist2; + var tl = seqend - seqstart; + + if (tl < 0) { + tl += path.points.length; + } + + var vx = (path.points[seqend].x - path.points[seqstart].x) / tl, + vy = (path.points[seqend].y - path.points[seqstart].y) / tl; + var pcnt = (seqstart + 1) % path.points.length, + pl; + + while (pcnt != seqend) { + pl = pcnt - seqstart; + + if (pl < 0) { + pl += path.points.length; + } + + px = path.points[seqstart].x + vx * pl; + py = path.points[seqstart].y + vy * pl; + dist2 = (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + (path.points[pcnt].y - py) * (path.points[pcnt].y - py); + + if (dist2 > ltres) { + curvepass = false; + } + + if (dist2 > errorval) { + errorpoint = pcnt; + errorval = dist2; + } + + pcnt = (pcnt + 1) % path.points.length; + } + + if (curvepass) { + return [{ + type: 'L', + x1: path.points[seqstart].x, + y1: path.points[seqstart].y, + x2: path.points[seqend].x, + y2: path.points[seqend].y + }]; + } + + var fitpoint = errorpoint; + curvepass = true; + errorval = 0; + var t = (fitpoint - seqstart) / tl, + t1 = (1 - t) * (1 - t), + t2 = 2 * (1 - t) * t, + t3 = t * t; + var cpx = (t1 * path.points[seqstart].x + t3 * path.points[seqend].x - path.points[fitpoint].x) / -t2, + cpy = (t1 * path.points[seqstart].y + t3 * path.points[seqend].y - path.points[fitpoint].y) / -t2; + pcnt = seqstart + 1; + + while (pcnt != seqend) { + t = (pcnt - seqstart) / tl; + t1 = (1 - t) * (1 - t); + t2 = 2 * (1 - t) * t; + t3 = t * t; + px = t1 * path.points[seqstart].x + t2 * cpx + t3 * path.points[seqend].x; + py = t1 * path.points[seqstart].y + t2 * cpy + t3 * path.points[seqend].y; + dist2 = (path.points[pcnt].x - px) * (path.points[pcnt].x - px) + (path.points[pcnt].y - py) * (path.points[pcnt].y - py); + + if (dist2 > qtres) { + curvepass = false; + } + + if (dist2 > errorval) { + errorpoint = pcnt; + errorval = dist2; + } + + pcnt = (pcnt + 1) % path.points.length; + } + + if (curvepass) { + return [{ + type: 'Q', + x1: path.points[seqstart].x, + y1: path.points[seqstart].y, + x2: cpx, + y2: cpy, + x3: path.points[seqend].x, + y3: path.points[seqend].y + }]; + } + + var splitpoint = fitpoint; + return concat_default()(_context2 = this.fitseq(path, ltres, qtres, seqstart, splitpoint)).call(_context2, this.fitseq(path, ltres, qtres, splitpoint, seqend)); + } + }, { + key: "batchtracepaths", + value: function batchtracepaths(internodepaths, ltres, qtres) { + var btracedpaths = []; + + for (var k in internodepaths) { + if (!internodepaths.hasOwnProperty(k)) { + continue; + } + + btracedpaths.push(this.tracepath(internodepaths[k], ltres, qtres)); + } + + return btracedpaths; + } + }, { + key: "batchtracelayers", + value: function batchtracelayers(binternodes, ltres, qtres) { + var btbis = []; + + for (var k in binternodes) { + if (!binternodes.hasOwnProperty(k)) { + continue; + } + + btbis[k] = this.batchtracepaths(binternodes[k], ltres, qtres); + } + + return btbis; + } + }, { + key: "roundtodec", + value: function roundtodec(val, places) { + return Number(val.toFixed(places)); + } + }, { + key: "svgpathstring", + value: function svgpathstring(tracedata, lnum, pathnum, options) { + var _context3, _context4; + + var layer = tracedata.layers[lnum], + smp = layer[pathnum], + str = '', + pcnt; + + if (options.linefilter && smp.segments.length < 3) { + return str; + } + + str = concat_default()(_context3 = "= 0; pcnt--) { + var _context16; + + str += "".concat(hsmp.segments[pcnt].type, " "); + + if (hsmp.segments[pcnt].hasOwnProperty('x3')) { + var _context15; + + str += concat_default()(_context15 = "".concat(hsmp.segments[pcnt].x2 * options.scale, " ")).call(_context15, hsmp.segments[pcnt].y2 * options.scale, " "); + } + + str += concat_default()(_context16 = "".concat(hsmp.segments[pcnt].x1 * options.scale, " ")).call(_context16, hsmp.segments[pcnt].y1 * options.scale, " "); + } + } else { + if (hsmp.segments[hsmp.segments.length - 1].hasOwnProperty('x3')) { + var _context17; + + str += concat_default()(_context17 = "M ".concat(this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x3 * options.scale), " ")).call(_context17, this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y3 * options.scale), " "); + } else { + var _context18; + + str += concat_default()(_context18 = "M ".concat(this.roundtodec(hsmp.segments[hsmp.segments.length - 1].x2 * options.scale), " ")).call(_context18, this.roundtodec(hsmp.segments[hsmp.segments.length - 1].y2 * options.scale), " "); + } + + for (pcnt = hsmp.segments.length - 1; pcnt >= 0; pcnt--) { + var _context20; + + str += "".concat(hsmp.segments[pcnt].type, " "); + + if (hsmp.segments[pcnt].hasOwnProperty('x3')) { + var _context19; + + str += concat_default()(_context19 = "".concat(this.roundtodec(hsmp.segments[pcnt].x2 * options.scale), " ")).call(_context19, this.roundtodec(hsmp.segments[pcnt].y2 * options.scale), " "); + } + + str += concat_default()(_context20 = "".concat(this.roundtodec(hsmp.segments[pcnt].x1 * options.scale), " ")).call(_context20, this.roundtodec(hsmp.segments[pcnt].y1 * options.scale), " "); + } + } + + str += 'Z '; + } + + str += '" />'; + + if (options.lcpr || options.qcpr) { + for (pcnt = 0; pcnt < smp.segments.length; pcnt++) { + if (smp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { + var _context21, _context22, _context23, _context24, _context25, _context26, _context27, _context28, _context29, _context30, _context31, _context32, _context33, _context34; + + str += concat_default()(_context21 = concat_default()(_context22 = concat_default()(_context23 = ""); + str += concat_default()(_context24 = concat_default()(_context25 = concat_default()(_context26 = ""); + str += concat_default()(_context27 = concat_default()(_context28 = concat_default()(_context29 = concat_default()(_context30 = ""); + str += concat_default()(_context31 = concat_default()(_context32 = concat_default()(_context33 = concat_default()(_context34 = ""); + } + + if (!smp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) { + var _context35, _context36, _context37; + + str += concat_default()(_context35 = concat_default()(_context36 = concat_default()(_context37 = ""); + } + } + + for (var hcnt = 0; hcnt < smp.holechildren.length; hcnt++) { + var hsmp = layer[smp.holechildren[hcnt]]; + + for (pcnt = 0; pcnt < hsmp.segments.length; pcnt++) { + if (hsmp.segments[pcnt].hasOwnProperty('x3') && options.qcpr) { + var _context38, _context39, _context40, _context41, _context42, _context43, _context44, _context45, _context46, _context47, _context48, _context49, _context50, _context51; + + str += concat_default()(_context38 = concat_default()(_context39 = concat_default()(_context40 = ""); + str += concat_default()(_context41 = concat_default()(_context42 = concat_default()(_context43 = ""); + str += concat_default()(_context44 = concat_default()(_context45 = concat_default()(_context46 = concat_default()(_context47 = ""); + str += concat_default()(_context48 = concat_default()(_context49 = concat_default()(_context50 = concat_default()(_context51 = ""); + } + + if (!hsmp.segments[pcnt].hasOwnProperty('x3') && options.lcpr) { + var _context52, _context53, _context54; + + str += concat_default()(_context52 = concat_default()(_context53 = concat_default()(_context54 = ""); + } + } + } + } + + return str; + } + }, { + key: "getsvgstring", + value: function getsvgstring(tracedata, options) { + var _context55, _context56, _context57; + + options = this.checkoptions(options); + var w = tracedata.width * options.scale; + var h = tracedata.height * options.scale; + + var svgstr = concat_default()(_context55 = ""); + + for (var lcnt = 0; lcnt < tracedata.layers.length; lcnt += 1) { + for (var pcnt = 0; pcnt < tracedata.layers[lcnt].length; pcnt += 1) { + if (!tracedata.layers[lcnt][pcnt].isholepath) { + svgstr += this.svgpathstring(tracedata, lcnt, pcnt, options); + } + } + } + + svgstr += ''; + return svgstr; + } + }, { + key: "compareNumbers", + value: function compareNumbers(a, b) { + return a - b; + } + }, { + key: "torgbastr", + value: function torgbastr(c) { + var _context58, _context59, _context60; + + return concat_default()(_context58 = concat_default()(_context59 = concat_default()(_context60 = "rgba(".concat(c.r, ",")).call(_context60, c.g, ",")).call(_context59, c.b, ",")).call(_context58, c.a, ")"); + } + }, { + key: "tosvgcolorstr", + value: function tosvgcolorstr(c, options) { + var _context61, _context62, _context63, _context64, _context65, _context66, _context67; + + return concat_default()(_context61 = concat_default()(_context62 = concat_default()(_context63 = concat_default()(_context64 = concat_default()(_context65 = concat_default()(_context66 = concat_default()(_context67 = "fill=\"rgb(".concat(c.r, ",")).call(_context67, c.g, ",")).call(_context66, c.b, ")\" stroke=\"rgb(")).call(_context65, c.r, ",")).call(_context64, c.g, ",")).call(_context63, c.b, ")\" stroke-width=\"")).call(_context62, options.strokewidth, "\" opacity=\"")).call(_context61, c.a / 255.0, "\" "); + } + }, { + key: "appendSVGString", + value: function appendSVGString(svgstr, parentid) { + var div; + + if (parentid) { + div = document.getElementById(parentid); + + if (!div) { + div = document.createElement('div'); + div.id = parentid; + document.body.appendChild(div); + } + } else { + div = document.createElement('div'); + document.body.appendChild(div); + } + + div.innerHTML += svgstr; + } + }, { + key: "blur", + value: function blur(imgd, radius, delta) { + var i, j, k, d, idx, racc, gacc, bacc, aacc, wacc; + var imgd2 = { + width: imgd.width, + height: imgd.height, + data: [] + }; + radius = Math.floor(radius); + + if (radius < 1) { + return imgd; + } + + if (radius > 5) { + radius = 5; + } + + delta = Math.abs(delta); + + if (delta > 1024) { + delta = 1024; + } + + var thisgk = this.gks[radius - 1]; + + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + racc = 0; + gacc = 0; + bacc = 0; + aacc = 0; + wacc = 0; + + for (k = -radius; k < radius + 1; k++) { + if (i + k > 0 && i + k < imgd.width) { + idx = (j * imgd.width + i + k) * 4; + racc += imgd.data[idx] * thisgk[k + radius]; + gacc += imgd.data[idx + 1] * thisgk[k + radius]; + bacc += imgd.data[idx + 2] * thisgk[k + radius]; + aacc += imgd.data[idx + 3] * thisgk[k + radius]; + wacc += thisgk[k + radius]; + } + } + + idx = (j * imgd.width + i) * 4; + imgd2.data[idx] = Math.floor(racc / wacc); + imgd2.data[idx + 1] = Math.floor(gacc / wacc); + imgd2.data[idx + 2] = Math.floor(bacc / wacc); + imgd2.data[idx + 3] = Math.floor(aacc / wacc); + } + } + + var himgd = new Uint8ClampedArray(imgd2.data); + + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + racc = 0; + gacc = 0; + bacc = 0; + aacc = 0; + wacc = 0; + + for (k = -radius; k < radius + 1; k++) { + if (j + k > 0 && j + k < imgd.height) { + idx = ((j + k) * imgd.width + i) * 4; + racc += himgd[idx] * thisgk[k + radius]; + gacc += himgd[idx + 1] * thisgk[k + radius]; + bacc += himgd[idx + 2] * thisgk[k + radius]; + aacc += himgd[idx + 3] * thisgk[k + radius]; + wacc += thisgk[k + radius]; + } + } + + idx = (j * imgd.width + i) * 4; + imgd2.data[idx] = Math.floor(racc / wacc); + imgd2.data[idx + 1] = Math.floor(gacc / wacc); + imgd2.data[idx + 2] = Math.floor(bacc / wacc); + imgd2.data[idx + 3] = Math.floor(aacc / wacc); + } + } + + for (j = 0; j < imgd.height; j++) { + for (i = 0; i < imgd.width; i++) { + idx = (j * imgd.width + i) * 4; + d = Math.abs(imgd2.data[idx] - imgd.data[idx]) + Math.abs(imgd2.data[idx + 1] - imgd.data[idx + 1]) + Math.abs(imgd2.data[idx + 2] - imgd.data[idx + 2]) + Math.abs(imgd2.data[idx + 3] - imgd.data[idx + 3]); + + if (d > delta) { + imgd2.data[idx] = imgd.data[idx]; + imgd2.data[idx + 1] = imgd.data[idx + 1]; + imgd2.data[idx + 2] = imgd.data[idx + 2]; + imgd2.data[idx + 3] = imgd.data[idx + 3]; + } + } + } + + return imgd2; + } + }, { + key: "loadImage", + value: function loadImage(url, callback, options) { + var img = new Image(); + + if (options && options.corsenabled) { + img.crossOrigin = 'Anonymous'; + } + + img.src = url; + + img.onload = function () { + var canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + var context = canvas.getContext('2d'); + context.drawImage(img, 0, 0); + callback(canvas); + }; + } + }, { + key: "getImgdata", + value: function getImgdata(canvas) { + var context = canvas.getContext('2d'); + return context.getImageData(0, 0, canvas.width, canvas.height); + } + }, { + key: "drawLayers", + value: function drawLayers(layers, palette, scale, parentid) { + scale = scale || 1; + var w, h, i, j, k; + var div; + + if (parentid) { + div = document.getElementById(parentid); + + if (!div) { + div = document.createElement('div'); + div.id = parentid; + document.body.appendChild(div); + } + } else { + div = document.createElement('div'); + document.body.appendChild(div); + } + + for (k in layers) { + if (!layers.hasOwnProperty(k)) { + continue; + } + + w = layers[k][0].length; + h = layers[k].length; + var canvas = document.createElement('canvas'); + canvas.width = w * scale; + canvas.height = h * scale; + var context = canvas.getContext('2d'); + + for (j = 0; j < h; j += 1) { + for (i = 0; i < w; i += 1) { + context.fillStyle = this.torgbastr(palette[layers[k][j][i] % palette.length]); + context.fillRect(i * scale, j * scale, scale, scale); + } + } + + div.appendChild(canvas); + } + } + }], [{ + key: "tracerDefaultOption", + value: function tracerDefaultOption() { + return { + pathomit: 100, + ltres: 0.1, + qtres: 1, + scale: 1, + strokewidth: 5, + viewbox: false, + linefilter: true, + desc: false, + rightangleenhance: false, + pal: [{ + r: 0, + g: 0, + b: 0, + a: 255 + }, { + r: 255, + g: 255, + b: 255, + a: 255 + }] + }; + } + }]); + + return ImageTracer; +}(); + + +;// CONCATENATED MODULE: ./src/js/action.js + + + + + + + + + + + +/* harmony default export */ var action = ({ + /** + * Get ui actions + * @returns {Object} actions for ui + * @private + */ + getActions: function getActions() { + return { + main: this._mainAction(), + shape: this._shapeAction(), + crop: this._cropAction(), + resize: this._resizeAction(), + flip: this._flipAction(), + rotate: this._rotateAction(), + text: this._textAction(), + mask: this._maskAction(), + draw: this._drawAction(), + icon: this._iconAction(), + filter: this._filterAction(), + history: this._historyAction() + }; + }, + + /** + * Main Action + * @returns {Object} actions for ui main + * @private + */ + _mainAction: function _mainAction() { + var _this = this; + + var exitCropOnAction = function exitCropOnAction() { + if (_this.ui.submenu === 'crop') { + _this.stopDrawingMode(); + + _this.ui.changeMenu('crop'); + } + }; + + var setAngleRangeBarOnAction = function setAngleRangeBarOnAction(angle) { + if (_this.ui.submenu === 'rotate') { + _this.ui.rotate.setRangeBarAngle('setAngle', angle); + } + }; + + var setFilterStateRangeBarOnAction = function setFilterStateRangeBarOnAction(filterOptions) { + if (_this.ui.submenu === 'filter') { + filter_default()(_this.ui).setFilterState(filterOptions); + } + }; + + var onEndUndoRedo = function onEndUndoRedo(result) { + setAngleRangeBarOnAction(result); + setFilterStateRangeBarOnAction(result); + return result; + }; + + var toggleZoomMode = function toggleZoomMode() { + var zoomMode = _this._graphics.getZoomMode(); + + _this.stopDrawingMode(); + + if (zoomMode !== zoomModes.ZOOM) { + _this.startDrawingMode(drawingModes.ZOOM); + + _this._graphics.startZoomInMode(); + } else { + _this._graphics.endZoomInMode(); + } + }; + + var toggleHandMode = function toggleHandMode() { + var zoomMode = _this._graphics.getZoomMode(); + + _this.stopDrawingMode(); + + if (zoomMode !== zoomModes.HAND) { + _this.startDrawingMode(drawingModes.ZOOM); + + _this._graphics.startHandMode(); + } else { + _this._graphics.endHandMode(); + } + }; + + var initFilterState = function initFilterState() { + if (filter_default()(_this.ui)) { + filter_default()(_this.ui).initFilterCheckBoxState(); + } + }; + + return extend_default()({ + initLoadImage: function initLoadImage(imagePath, imageName) { + return _this.loadImageFromURL(imagePath, imageName).then(function (sizeValue) { + exitCropOnAction(); + _this.ui.initializeImgUrl = imagePath; + + _this.ui.resizeEditor({ + imageSize: sizeValue + }); + + _this.clearUndoStack(); + + _this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_IMAGE); + }); + }, + undo: function undo() { + if (!_this.isEmptyUndoStack()) { + exitCropOnAction(); + + _this.deactivateAll(); + + _this.undo().then(onEndUndoRedo); + } + }, + redo: function redo() { + if (!_this.isEmptyRedoStack()) { + exitCropOnAction(); + + _this.deactivateAll(); + + _this.redo().then(onEndUndoRedo); + } + }, + reset: function reset() { + exitCropOnAction(); + + _this.loadImageFromURL(_this.ui.initializeImgUrl, 'resetImage').then(function (sizeValue) { + exitCropOnAction(); + initFilterState(); + + _this.ui.resizeEditor({ + imageSize: sizeValue + }); + + _this.clearUndoStack(); + + _this._initHistory(); + }); + }, + delete: function _delete() { + _this.ui.changeHelpButtonEnabled('delete', false); + + exitCropOnAction(); + + _this.removeActiveObject(); + + _this.activeObjectId = null; + }, + deleteAll: function deleteAll() { + exitCropOnAction(); + + _this.clearObjects(); + + _this.ui.changeHelpButtonEnabled('delete', false); + + _this.ui.changeHelpButtonEnabled('deleteAll', false); + }, + load: function load(file) { + if (!isSupportFileApi()) { + alert('This browser does not support file-api'); + } + + _this.ui.initializeImgUrl = url_default().createObjectURL(file); + + _this.loadImageFromFile(file).then(function (sizeValue) { + exitCropOnAction(); + initFilterState(); + + _this.clearUndoStack(); + + _this.ui.activeMenuEvent(); + + _this.ui.resizeEditor({ + imageSize: sizeValue + }); + + _this._clearHistory(); + + _this._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_IMAGE); + })['catch'](function (message) { + return promise_default().reject(message); + }); + }, + download: function download() { + var dataURL = _this.toDataURL(); + + var imageName = _this.getImageName(); + + var blob, type, w; + + if (isSupportFileApi() && window.saveAs) { + blob = base64ToBlob(dataURL); + type = blob.type.split('/')[1]; + + if (imageName.split('.').pop() !== type) { + imageName += ".".concat(type); + } + + saveAs(blob, imageName); // eslint-disable-line + } else { + w = window.open(); + w.document.body.innerHTML = ""); + } + }, + history: function history(event) { + _this.ui.toggleHistoryMenu(event); + }, + zoomIn: function zoomIn() { + _this.ui.toggleZoomButtonStatus('zoomIn'); + + _this.deactivateAll(); + + toggleZoomMode(); + }, + zoomOut: function zoomOut() { + _this._graphics.zoomOut(); + }, + hand: function hand() { + _this.ui.offZoomInButtonStatus(); + + _this.ui.toggleZoomButtonStatus('hand'); + + _this.deactivateAll(); + + toggleHandMode(); + } + }, this._commonAction()); + }, + + /** + * Icon Action + * @returns {Object} actions for ui icon + * @private + */ + _iconAction: function _iconAction() { + var _this2 = this; + + return extend_default()({ + changeColor: function changeColor(color) { + if (_this2.activeObjectId) { + _this2.changeIconColor(_this2.activeObjectId, color); + } + }, + addIcon: function addIcon(iconType, iconColor) { + _this2.startDrawingMode('ICON'); + + _this2.setDrawingIcon(iconType, iconColor); + }, + cancelAddIcon: function cancelAddIcon() { + _this2.ui.icon.clearIconType(); + + _this2.changeSelectableAll(true); + + _this2.changeCursor('default'); + + _this2.stopDrawingMode(); + }, + registerDefaultIcons: function registerDefaultIcons(type, path) { + var iconObj = {}; + iconObj[type] = path; + + _this2.registerIcons(iconObj); + }, + registerCustomIcon: function registerCustomIcon(imgUrl, file) { + var imagetracer = new ImageTracer(); + imagetracer.imageToSVG(imgUrl, function (svgstr) { + var _svgstr$match = svgstr.match(/path[^>]*d="([^"]*)"/), + _svgstr$match2 = _slicedToArray(_svgstr$match, 2), + svgPath = _svgstr$match2[1]; + + var iconObj = {}; + iconObj[file.name] = svgPath; + + _this2.registerIcons(iconObj); + + _this2.addIcon(file.name, { + left: 100, + top: 100 + }); + }, ImageTracer.tracerDefaultOption()); + } + }, this._commonAction()); + }, + + /** + * Draw Action + * @returns {Object} actions for ui draw + * @private + */ + _drawAction: function _drawAction() { + var _this3 = this; + + return extend_default()({ + setDrawMode: function setDrawMode(type, settings) { + _this3.stopDrawingMode(); + + if (type === 'free') { + _this3.startDrawingMode('FREE_DRAWING', settings); + } else { + _this3.startDrawingMode('LINE_DRAWING', settings); + } + }, + setColor: function setColor(color) { + _this3.setBrush({ + color: color + }); + } + }, this._commonAction()); + }, + + /** + * Mask Action + * @returns {Object} actions for ui mask + * @private + */ + _maskAction: function _maskAction() { + var _this4 = this; + + return extend_default()({ + loadImageFromURL: function loadImageFromURL(imgUrl, file) { + return _this4.loadImageFromURL(_this4.toDataURL(), 'FilterImage').then(function () { + _this4.addImageObject(imgUrl).then(function () { + url_default().revokeObjectURL(file); + }); + + _this4._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.LOAD_MASK_IMAGE); + }); + }, + applyFilter: function applyFilter() { + _this4.applyFilter('mask', { + maskObjId: _this4.activeObjectId + }); + } + }, this._commonAction()); + }, + + /** + * Text Action + * @returns {Object} actions for ui text + * @private + */ + _textAction: function _textAction() { + var _this5 = this; + + return extend_default()({ + changeTextStyle: function changeTextStyle(styleObj, isSilent) { + if (_this5.activeObjectId) { + _this5.changeTextStyle(_this5.activeObjectId, styleObj, isSilent); + } + } + }, this._commonAction()); + }, + + /** + * Rotate Action + * @returns {Object} actions for ui rotate + * @private + */ + _rotateAction: function _rotateAction() { + var _this6 = this; + + return extend_default()({ + rotate: function rotate(angle, isSilent) { + _this6.rotate(angle, isSilent); + + _this6.ui.resizeEditor(); + + _this6.ui.rotate.setRangeBarAngle('rotate', angle); + }, + setAngle: function setAngle(angle, isSilent) { + _this6.setAngle(angle, isSilent); + + _this6.ui.resizeEditor(); + + _this6.ui.rotate.setRangeBarAngle('setAngle', angle); + } + }, this._commonAction()); + }, + + /** + * Shape Action + * @returns {Object} actions for ui shape + * @private + */ + _shapeAction: function _shapeAction() { + var _this7 = this; + + return extend_default()({ + changeShape: function changeShape(changeShapeObject, isSilent) { + if (_this7.activeObjectId) { + _this7.changeShape(_this7.activeObjectId, changeShapeObject, isSilent); + } + }, + setDrawingShape: function setDrawingShape(shapeType) { + _this7.setDrawingShape(shapeType); + } + }, this._commonAction()); + }, + + /** + * Crop Action + * @returns {Object} actions for ui crop + * @private + */ + _cropAction: function _cropAction() { + var _this8 = this; + + return extend_default()({ + crop: function crop() { + var cropRect = _this8.getCropzoneRect(); + + if (cropRect && !isEmptyCropzone(cropRect)) { + _this8.crop(cropRect).then(function () { + _this8.stopDrawingMode(); + + _this8.ui.resizeEditor(); + + _this8.ui.changeMenu('crop'); + + _this8._invoker.fire(eventNames.EXECUTE_COMMAND, historyNames.CROP); + })['catch'](function (message) { + return promise_default().reject(message); + }); + } + }, + cancel: function cancel() { + _this8.stopDrawingMode(); + + _this8.ui.changeMenu('crop'); + }, + + /* eslint-disable */ + preset: function preset(presetType) { + switch (presetType) { + case 'preset-square': + _this8.setCropzoneRect(1 / 1); + + break; + + case 'preset-3-2': + _this8.setCropzoneRect(3 / 2); + + break; + + case 'preset-4-3': + _this8.setCropzoneRect(4 / 3); + + break; + + case 'preset-5-4': + _this8.setCropzoneRect(5 / 4); + + break; + + case 'preset-7-5': + _this8.setCropzoneRect(7 / 5); + + break; + + case 'preset-16-9': + _this8.setCropzoneRect(16 / 9); + + break; + + default: + _this8.setCropzoneRect(); + + _this8.ui.crop.changeApplyButtonStatus(false); + + break; + } + } + }, this._commonAction()); + }, + + /** + * Resize Action + * @returns {Object} actions for ui resize + * @private + */ + _resizeAction: function _resizeAction() { + var _this9 = this; + + return extend_default()({ + getCurrentDimensions: function getCurrentDimensions() { + return _this9._graphics.getCurrentDimensions(); + }, + preview: function preview(actor, value, lockState) { + var currentDimensions = _this9._graphics.getCurrentDimensions(); + + var calcAspectRatio = function calcAspectRatio() { + return currentDimensions.width / currentDimensions.height; + }; + + var dimensions = {}; + + switch (actor) { + case 'width': + dimensions.width = value; + + if (lockState) { + dimensions.height = value / calcAspectRatio(); + } else { + dimensions.height = currentDimensions.height; + } + + break; + + case 'height': + dimensions.height = value; + + if (lockState) { + dimensions.width = value * calcAspectRatio(); + } else { + dimensions.width = currentDimensions.width; + } + + break; + + default: + dimensions = currentDimensions; + } + + _this9._graphics.resize(dimensions).then(function () { + _this9.ui.resizeEditor(); + }); + + if (lockState) { + _this9.ui.resize.setWidthValue(dimensions.width); + + _this9.ui.resize.setHeightValue(dimensions.height); + } + }, + lockAspectRatio: function lockAspectRatio(lockState, min, max) { + var _this9$_graphics$getC = _this9._graphics.getCurrentDimensions(), + width = _this9$_graphics$getC.width, + height = _this9$_graphics$getC.height; + + var aspectRatio = width / height; + + if (lockState) { + if (width > height) { + var pMax = max / aspectRatio; + var pMin = min * aspectRatio; + + _this9.ui.resize.setLimit({ + minWidth: pMin > min ? pMin : min, + minHeight: min, + maxWidth: max, + maxHeight: pMax < max ? pMax : max + }); + } else { + var _pMax = max * aspectRatio; + + var _pMin = min / aspectRatio; + + _this9.ui.resize.setLimit({ + minWidth: min, + minHeight: _pMin > min ? _pMin : min, + maxWidth: _pMax < max ? _pMax : max, + maxHeight: max + }); + } + } else { + _this9.ui.resize.setLimit({ + minWidth: min, + minHeight: min, + maxWidth: max, + maxHeight: max + }); + } + }, + resize: function resize() { + var dimensions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + + if (!dimensions) { + dimensions = _this9._graphics.getCurrentDimensions(); + } + + _this9.resize(dimensions).then(function () { + _this9._graphics.setOriginalDimensions(dimensions); + + _this9.stopDrawingMode(); + + _this9.ui.resizeEditor(); + + _this9.ui.changeMenu('resize'); + })['catch'](function (message) { + return promise_default().reject(message); + }); + }, + reset: function reset() { + var standByMode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + + var dimensions = _this9._graphics.getOriginalDimensions(); + + _this9.ui.resize.setWidthValue(dimensions.width, true); + + _this9.ui.resize.setHeightValue(dimensions.height, true); + + _this9._graphics.resize(dimensions).then(function () { + if (!standByMode) { + _this9.stopDrawingMode(); + + _this9.ui.resizeEditor(); + + _this9.ui.changeMenu('resize'); + } + }); + } + }, this._commonAction()); + }, + + /** + * Flip Action + * @returns {Object} actions for ui flip + * @private + */ + _flipAction: function _flipAction() { + var _this10 = this; + + return extend_default()({ + flip: function flip(flipType) { + return _this10[flipType](); + } + }, this._commonAction()); + }, + + /** + * Filter Action + * @returns {Object} actions for ui filter + * @private + */ + _filterAction: function _filterAction() { + var _this11 = this; + + return extend_default()({ + applyFilter: function applyFilter(applying, type, options, isSilent) { + if (applying) { + _this11.applyFilter(type, options, isSilent); + } else if (_this11.hasFilter(type)) { + _this11.removeFilter(type); + } + } + }, this._commonAction()); + }, + + /** + * Image Editor Event Observer + */ + setReAction: function setReAction() { + var _this12 = this; + + this.on({ + undoStackChanged: function undoStackChanged(length) { + if (length) { + _this12.ui.changeHelpButtonEnabled('undo', true); + + _this12.ui.changeHelpButtonEnabled('reset', true); + } else { + _this12.ui.changeHelpButtonEnabled('undo', false); + + _this12.ui.changeHelpButtonEnabled('reset', false); + } + + _this12.ui.resizeEditor(); + }, + redoStackChanged: function redoStackChanged(length) { + if (length) { + _this12.ui.changeHelpButtonEnabled('redo', true); + } else { + _this12.ui.changeHelpButtonEnabled('redo', false); + } + + _this12.ui.resizeEditor(); + }, + + /* eslint-disable complexity */ + objectActivated: function objectActivated(obj) { + var _context, _context2; + + _this12.activeObjectId = obj.id; + + _this12.ui.changeHelpButtonEnabled('delete', true); + + _this12.ui.changeHelpButtonEnabled('deleteAll', true); + + if (obj.type === 'cropzone') { + _this12.ui.crop.changeApplyButtonStatus(true); + } else if (index_of_default()(_context = ['rect', 'circle', 'triangle']).call(_context, obj.type) > -1) { + _this12.stopDrawingMode(); + + if (_this12.ui.submenu !== 'shape') { + _this12.ui.changeMenu('shape', false, false); + } + + _this12.ui.shape.setShapeStatus({ + strokeColor: obj.stroke, + strokeWidth: obj.strokeWidth, + fillColor: fill_default()(obj) + }); + + _this12.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); + } else if (obj.type === 'path' || obj.type === 'line') { + if (_this12.ui.submenu !== 'draw') { + _this12.ui.changeMenu('draw', false, false); + + _this12.ui.draw.changeStandbyMode(); + } + } else if (index_of_default()(_context2 = ['i-text', 'text']).call(_context2, obj.type) > -1) { + if (_this12.ui.submenu !== 'text') { + _this12.ui.changeMenu('text', false, false); + } + + _this12.ui.text.setTextStyleStateOnAction(obj); + } else if (obj.type === 'icon') { + _this12.stopDrawingMode(); + + if (_this12.ui.submenu !== 'icon') { + _this12.ui.changeMenu('icon', false, false); + } + + _this12.ui.icon.setIconPickerColor(fill_default()(obj)); + } + }, + + /* eslint-enable complexity */ + addText: function addText(pos) { + var _this12$ui$text = _this12.ui.text, + fill = _this12$ui$text.textColor, + fontSize = _this12$ui$text.fontSize, + fontStyle = _this12$ui$text.fontStyle, + fontWeight = _this12$ui$text.fontWeight, + underline = _this12$ui$text.underline; + var fontFamily = 'Noto Sans'; + + _this12.addText('Double Click', { + position: pos.originPosition, + styles: { + fill: fill, + fontSize: fontSize, + fontFamily: fontFamily, + fontStyle: fontStyle, + fontWeight: fontWeight, + underline: underline + } + }).then(function () { + _this12.changeCursor('default'); + }); + }, + addObjectAfter: function addObjectAfter(obj) { + var _context3; + + if (obj.type === 'icon') { + _this12.ui.icon.changeStandbyMode(); + } else if (index_of_default()(_context3 = ['rect', 'circle', 'triangle']).call(_context3, obj.type) > -1) { + _this12.ui.shape.setMaxStrokeValue(Math.min(obj.width, obj.height)); + + _this12.ui.shape.changeStandbyMode(); + } + }, + objectScaled: function objectScaled(obj) { + var _context4, _context5; + + if (index_of_default()(_context4 = ['i-text', 'text']).call(_context4, obj.type) > -1) { + _this12.ui.text.fontSize = toInteger(obj.fontSize); + } else if (index_of_default()(_context5 = ['rect', 'circle', 'triangle']).call(_context5, obj.type) >= 0) { + var width = obj.width, + height = obj.height; + + var strokeValue = _this12.ui.shape.getStrokeValue(); + + if (width < strokeValue) { + _this12.ui.shape.setStrokeValue(width); + } + + if (height < strokeValue) { + _this12.ui.shape.setStrokeValue(height); + } + } + }, + selectionCleared: function selectionCleared() { + _this12.activeObjectId = null; + + if (_this12.ui.submenu === 'text') { + _this12.changeCursor('text'); + } else if (!includes(['draw', 'crop', 'resize'], _this12.ui.submenu)) { + _this12.stopDrawingMode(); + } + } + }); + }, + + /** + * History Action + * @returns {Object} history actions for ui + * @private + */ + _historyAction: function _historyAction() { + var _this13 = this; + + return { + undo: function undo(count) { + return _this13.undo(count); + }, + redo: function redo(count) { + return _this13.redo(count); + } + }; + }, + + /** + * Common Action + * @returns {Object} common actions for ui + * @private + */ + _commonAction: function _commonAction() { + var _this14 = this, + _context6, + _context7, + _context8, + _context9; + + var TEXT = drawingModes.TEXT, + CROPPER = drawingModes.CROPPER, + SHAPE = drawingModes.SHAPE, + ZOOM = drawingModes.ZOOM, + RESIZE = drawingModes.RESIZE; + return { + modeChange: function modeChange(menu) { + switch (menu) { + case drawingMenuNames.TEXT: + _this14._changeActivateMode(TEXT); + + break; + + case drawingMenuNames.CROP: + _this14.startDrawingMode(CROPPER); + + break; + + case drawingMenuNames.SHAPE: + _this14._changeActivateMode(SHAPE); + + _this14.setDrawingShape(_this14.ui.shape.type, _this14.ui.shape.options); + + break; + + case drawingMenuNames.ZOOM: + _this14.startDrawingMode(ZOOM); + + break; + + case drawingMenuNames.RESIZE: + _this14.startDrawingMode(RESIZE); + + break; + + default: + break; + } + }, + deactivateAll: bind_default()(_context6 = this.deactivateAll).call(_context6, this), + changeSelectableAll: bind_default()(_context7 = this.changeSelectableAll).call(_context7, this), + discardSelection: bind_default()(_context8 = this.discardSelection).call(_context8, this), + stopDrawingMode: bind_default()(_context9 = this.stopDrawingMode).call(_context9, this) + }; + }, + + /** + * Mixin + * @param {ImageEditor} ImageEditor instance + */ + mixin: function mixin(ImageEditor) { + extend_default()(ImageEditor.prototype, this); + } +}); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/type/isArray.js +var isArray = __webpack_require__(602); +var isArray_default = /*#__PURE__*/__webpack_require__.n(isArray); +// EXTERNAL MODULE: ./node_modules/tui-code-snippet/collection/forEachOwnProperties.js +var forEachOwnProperties = __webpack_require__(5573); +var forEachOwnProperties_default = /*#__PURE__*/__webpack_require__.n(forEachOwnProperties); +;// CONCATENATED MODULE: ./src/js/interface/component.js + + + +/** + * Component interface + * @class + * @param {string} name - component name + * @param {Graphics} graphics - Graphics instance + * @ignore + */ +var Component = /*#__PURE__*/function () { + function Component(name, graphics) { + _classCallCheck(this, Component); + + /** + * Component name + * @type {string} + */ + this.name = name; + /** + * Graphics instance + * @type {Graphics} + */ + + this.graphics = graphics; + } + /** + * Fire Graphics event + * @returns {Object} return value + */ + + + _createClass(Component, [{ + key: "fire", + value: function fire() { + var context = this.graphics; + + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return this.graphics.fire.apply(context, args); + } + /** + * Save image(background) of canvas + * @param {string} name - Name of image + * @param {fabric.Image} oImage - Fabric image instance + */ + + }, { + key: "setCanvasImage", + value: function setCanvasImage(name, oImage) { + this.graphics.setCanvasImage(name, oImage); + } + /** + * Returns canvas element of fabric.Canvas[[lower-canvas]] + * @returns {HTMLCanvasElement} + */ + + }, { + key: "getCanvasElement", + value: function getCanvasElement() { + return this.graphics.getCanvasElement(); + } + /** + * Get fabric.Canvas instance + * @returns {fabric.Canvas} + */ + + }, { + key: "getCanvas", + value: function getCanvas() { + return this.graphics.getCanvas(); + } + /** + * Get canvasImage (fabric.Image instance) + * @returns {fabric.Image} + */ + + }, { + key: "getCanvasImage", + value: function getCanvasImage() { + return this.graphics.getCanvasImage(); + } + /** + * Get image name + * @returns {string} + */ + + }, { + key: "getImageName", + value: function getImageName() { + return this.graphics.getImageName(); + } + /** + * Get image editor + * @returns {ImageEditor} + */ + + }, { + key: "getEditor", + value: function getEditor() { + return this.graphics.getEditor(); + } + /** + * Return component name + * @returns {string} + */ + + }, { + key: "getName", + value: function getName() { + return this.name; + } + /** + * Set image properties + * @param {Object} setting - Image properties + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas + */ + + }, { + key: "setImageProperties", + value: function setImageProperties(setting, withRendering) { + this.graphics.setImageProperties(setting, withRendering); + } + /** + * Set canvas dimension - css only + * @param {Object} dimension - Canvas css dimension + */ + + }, { + key: "setCanvasCssDimension", + value: function setCanvasCssDimension(dimension) { + this.graphics.setCanvasCssDimension(dimension); + } + /** + * Set canvas dimension - css only + * @param {Object} dimension - Canvas backstore dimension + */ + + }, { + key: "setCanvasBackstoreDimension", + value: function setCanvasBackstoreDimension(dimension) { + this.graphics.setCanvasBackstoreDimension(dimension); + } + /** + * Adjust canvas dimension with scaling image + */ + + }, { + key: "adjustCanvasDimension", + value: function adjustCanvasDimension() { + this.graphics.adjustCanvasDimension(); + } + }, { + key: "adjustCanvasDimensionBase", + value: function adjustCanvasDimensionBase() { + this.graphics.adjustCanvasDimensionBase(); + } + }]); + + return Component; +}(); + +/* harmony default export */ var component = (Component); +;// CONCATENATED MODULE: ./src/js/component/imageLoader.js + + + + + + + + +function imageLoader_createSuper(Derived) { var hasNativeReflectConstruct = imageLoader_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function imageLoader_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +var imageOption = { + padding: 0, + crossOrigin: 'Anonymous' +}; +/** + * ImageLoader components + * @extends {Component} + * @class ImageLoader + * @param {Graphics} graphics - Graphics instance + * @ignore + */ + +var ImageLoader = /*#__PURE__*/function (_Component) { + _inherits(ImageLoader, _Component); + + var _super = imageLoader_createSuper(ImageLoader); + + function ImageLoader(graphics) { + _classCallCheck(this, ImageLoader); + + return _super.call(this, componentNames.IMAGE_LOADER, graphics); + } + /** + * Load image from url + * @param {?string} imageName - File name + * @param {?(fabric.Image|string)} img - fabric.Image instance or URL of an image + * @returns {Promise} + */ + + + _createClass(ImageLoader, [{ + key: "load", + value: function load(imageName, img) { + var _this = this; + + var promise; + + if (!imageName && !img) { + // Back to the initial state, not error. + var canvas = this.getCanvas(); + canvas.backgroundImage = null; + canvas.renderAll(); + promise = new (promise_default())(function (resolve) { + _this.setCanvasImage('', null); + + resolve(); + }); + } else { + promise = this._setBackgroundImage(img).then(function (oImage) { + _this.setCanvasImage(imageName, oImage); + + _this.adjustCanvasDimension(); + + return oImage; + }); + } + + return promise; + } + /** + * Set background image + * @param {?(fabric.Image|String)} img fabric.Image instance or URL of an image to set background to + * @returns {Promise} + * @private + */ + + }, { + key: "_setBackgroundImage", + value: function _setBackgroundImage(img) { + var _this2 = this; + + if (!img) { + return promise_default().reject(rejectMessages.loadImage); + } + + return new (promise_default())(function (resolve, reject) { + var canvas = _this2.getCanvas(); + + canvas.setBackgroundImage(img, function () { + var oImage = canvas.backgroundImage; + + if (oImage && oImage.getElement()) { + resolve(oImage); + } else { + reject(rejectMessages.loadingImageFailed); + } + }, imageOption); + }); + } + }]); + + return ImageLoader; +}(component); + +/* harmony default export */ var imageLoader = (ImageLoader); +;// CONCATENATED MODULE: ./src/js/extension/cropzone.js + + + + + + + + + + +var CORNER_TYPE_TOP_LEFT = 'tl'; +var CORNER_TYPE_TOP_RIGHT = 'tr'; +var CORNER_TYPE_MIDDLE_TOP = 'mt'; +var CORNER_TYPE_MIDDLE_LEFT = 'ml'; +var CORNER_TYPE_MIDDLE_RIGHT = 'mr'; +var CORNER_TYPE_MIDDLE_BOTTOM = 'mb'; +var CORNER_TYPE_BOTTOM_LEFT = 'bl'; +var CORNER_TYPE_BOTTOM_RIGHT = 'br'; +var CORNER_TYPE_LIST = [CORNER_TYPE_TOP_LEFT, CORNER_TYPE_TOP_RIGHT, CORNER_TYPE_MIDDLE_TOP, CORNER_TYPE_MIDDLE_LEFT, CORNER_TYPE_MIDDLE_RIGHT, CORNER_TYPE_MIDDLE_BOTTOM, CORNER_TYPE_BOTTOM_LEFT, CORNER_TYPE_BOTTOM_RIGHT]; + +var NOOP_FUNCTION = function NOOP_FUNCTION() {}; +/** + * Align with cropzone ratio + * @param {string} selectedCorner - selected corner type + * @returns {{width: number, height: number}} + * @private + */ + + +function cornerTypeValid(selectedCorner) { + return index_of_default()(CORNER_TYPE_LIST).call(CORNER_TYPE_LIST, selectedCorner) >= 0; +} +/** + * return scale basis type + * @param {number} diffX - X distance of the cursor and corner. + * @param {number} diffY - Y distance of the cursor and corner. + * @returns {string} + * @private + */ + + +function getScaleBasis(diffX, diffY) { + return diffX > diffY ? 'width' : 'height'; +} +/** + * Cropzone object + * Issue: IE7, 8(with excanvas) + * - Cropzone is a black zone without transparency. + * @class Cropzone + * @extends {fabric.Rect} + * @ignore + */ + + +var Cropzone = fabric.fabric.util.createClass(fabric.fabric.Rect, +/** @lends Cropzone.prototype */ +{ + /** + * Constructor + * @param {Object} canvas canvas + * @param {Object} options Options object + * @param {Object} extendsOptions object for extends "options" + * @override + */ + initialize: function initialize(canvas, options, extendsOptions) { + options = extend_default()(options, extendsOptions); + options.type = 'cropzone'; + this.callSuper('initialize', options); + + this._addEventHandler(); + + this.canvas = canvas; + this.options = options; + }, + canvasEventDelegation: function canvasEventDelegation(eventName) { + var _context; + + var delegationState = 'unregistered'; + var isRegistered = this.canvasEventTrigger[eventName] !== NOOP_FUNCTION; + + if (isRegistered) { + delegationState = 'registered'; + } else if (index_of_default()(_context = [eventNames.OBJECT_MOVED, eventNames.OBJECT_SCALED]).call(_context, eventName) < 0) { + delegationState = 'none'; + } + + return delegationState; + }, + canvasEventRegister: function canvasEventRegister(eventName, eventTrigger) { + this.canvasEventTrigger[eventName] = eventTrigger; + }, + _addEventHandler: function _addEventHandler() { + var _this$canvasEventTrig, _context2, _context3, _context4, _context5; + + this.canvasEventTrigger = (_this$canvasEventTrig = {}, _defineProperty(_this$canvasEventTrig, eventNames.OBJECT_MOVED, NOOP_FUNCTION), _defineProperty(_this$canvasEventTrig, eventNames.OBJECT_SCALED, NOOP_FUNCTION), _this$canvasEventTrig); + this.on({ + moving: bind_default()(_context2 = this._onMoving).call(_context2, this), + scaling: bind_default()(_context3 = this._onScaling).call(_context3, this) + }); + fabric.fabric.util.addListener(document, 'keydown', bind_default()(_context4 = this._onKeyDown).call(_context4, this)); + fabric.fabric.util.addListener(document, 'keyup', bind_default()(_context5 = this._onKeyUp).call(_context5, this)); + }, + _renderCropzone: function _renderCropzone(ctx) { + var cropzoneDashLineWidth = 7; + var cropzoneDashLineOffset = 7; // Calc original scale + + var originalFlipX = this.flipX ? -1 : 1; + var originalFlipY = this.flipY ? -1 : 1; + var originalScaleX = originalFlipX / this.scaleX; + var originalScaleY = originalFlipY / this.scaleY; // Set original scale + + ctx.scale(originalScaleX, originalScaleY); // Render outer rect + + this._fillOuterRect(ctx, 'rgba(0, 0, 0, 0.5)'); + + if (this.options.lineWidth) { + this._fillInnerRect(ctx); + + this._strokeBorder(ctx, 'rgb(255, 255, 255)', { + lineWidth: this.options.lineWidth + }); + } else { + // Black dash line + this._strokeBorder(ctx, 'rgb(0, 0, 0)', { + lineDashWidth: cropzoneDashLineWidth + }); // White dash line + + + this._strokeBorder(ctx, 'rgb(255, 255, 255)', { + lineDashWidth: cropzoneDashLineWidth, + lineDashOffset: cropzoneDashLineOffset + }); + } // Reset scale + + + ctx.scale(1 / originalScaleX, 1 / originalScaleY); + }, + + /** + * Render Crop-zone + * @private + * @override + */ + _render: function _render(ctx) { + this.callSuper('_render', ctx); + + this._renderCropzone(ctx); + }, + + /** + * Cropzone-coordinates with outer rectangle + * + * x0 x1 x2 x3 + * y0 +--------------------------+ + * |///////|//////////|///////| // <--- "Outer-rectangle" + * |///////|//////////|///////| + * y1 +-------+----------+-------+ + * |///////| Cropzone |///////| Cropzone is the "Inner-rectangle" + * |///////| (0, 0) |///////| Center point (0, 0) + * y2 +-------+----------+-------+ + * |///////|//////////|///////| + * |///////|//////////|///////| + * y3 +--------------------------+ + * + * @typedef {{x: Array, y: Array}} cropzoneCoordinates + * @ignore + */ + + /** + * Fill outer rectangle + * @param {CanvasRenderingContext2D} ctx - Context + * @param {string|CanvasGradient|CanvasPattern} fillStyle - Fill-style + * @private + */ + _fillOuterRect: function _fillOuterRect(ctx, fillStyle) { + var _this$_getCoordinates = this._getCoordinates(), + x = _this$_getCoordinates.x, + y = _this$_getCoordinates.y; + + ctx.save(); + ctx.fillStyle = fillStyle; + ctx.beginPath(); // Outer rectangle + // Numbers are +/-1 so that overlay edges don't get blurry. + + ctx.moveTo(x[0] - 1, y[0] - 1); + ctx.lineTo(x[3] + 1, y[0] - 1); + ctx.lineTo(x[3] + 1, y[3] + 1); + ctx.lineTo(x[0] - 1, y[3] + 1); + ctx.lineTo(x[0] - 1, y[0] - 1); + ctx.closePath(); // Inner rectangle + + ctx.moveTo(x[1], y[1]); + ctx.lineTo(x[1], y[2]); + ctx.lineTo(x[2], y[2]); + ctx.lineTo(x[2], y[1]); + ctx.lineTo(x[1], y[1]); + ctx.closePath(); + + fill_default()(ctx).call(ctx); + + ctx.restore(); + }, + + /** + * Draw Inner grid line + * @param {CanvasRenderingContext2D} ctx - Context + * @private + */ + _fillInnerRect: function _fillInnerRect(ctx) { + var _this$_getCoordinates2 = this._getCoordinates(), + outerX = _this$_getCoordinates2.x, + outerY = _this$_getCoordinates2.y; + + var x = this._caculateInnerPosition(outerX, (outerX[2] - outerX[1]) / 3); + + var y = this._caculateInnerPosition(outerY, (outerY[2] - outerY[1]) / 3); + + ctx.save(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; + ctx.lineWidth = this.options.lineWidth; + ctx.beginPath(); + ctx.moveTo(x[0], y[1]); + ctx.lineTo(x[3], y[1]); + ctx.moveTo(x[0], y[2]); + ctx.lineTo(x[3], y[2]); + ctx.moveTo(x[1], y[0]); + ctx.lineTo(x[1], y[3]); + ctx.moveTo(x[2], y[0]); + ctx.lineTo(x[2], y[3]); + ctx.stroke(); + ctx.closePath(); + ctx.restore(); + }, + + /** + * Calculate Inner Position + * @param {Array} outer - outer position + * @param {number} size - interval for calculate + * @returns {Array} - inner position + * @private + */ + _caculateInnerPosition: function _caculateInnerPosition(outer, size) { + var position = []; + position[0] = outer[1]; + position[1] = outer[1] + size; + position[2] = outer[1] + size * 2; + position[3] = outer[2]; + return position; + }, + + /** + * Get coordinates + * @returns {cropzoneCoordinates} - {@link cropzoneCoordinates} + * @private + */ + _getCoordinates: function _getCoordinates() { + var _context6, _context7; + + var canvas = this.canvas, + width = this.width, + height = this.height, + left = this.left, + top = this.top; + var halfWidth = width / 2; + var halfHeight = height / 2; + var canvasHeight = canvas.getHeight(); // fabric object + + var canvasWidth = canvas.getWidth(); // fabric object + + return { + x: map_default()(_context6 = [-(halfWidth + left), // x0 + -halfWidth, // x1 + halfWidth, // x2 + halfWidth + (canvasWidth - left - width) // x3 + ]).call(_context6, Math.ceil), + y: map_default()(_context7 = [-(halfHeight + top), // y0 + -halfHeight, // y1 + halfHeight, // y2 + halfHeight + (canvasHeight - top - height) // y3 + ]).call(_context7, Math.ceil) + }; + }, + + /** + * Stroke border + * @param {CanvasRenderingContext2D} ctx - Context + * @param {string|CanvasGradient|CanvasPattern} strokeStyle - Stroke-style + * @param {number} lineDashWidth - Dash width + * @param {number} [lineDashOffset] - Dash offset + * @param {number} [lineWidth] - line width + * @private + */ + _strokeBorder: function _strokeBorder(ctx, strokeStyle, _ref) { + var lineDashWidth = _ref.lineDashWidth, + lineDashOffset = _ref.lineDashOffset, + lineWidth = _ref.lineWidth; + var halfWidth = this.width / 2; + var halfHeight = this.height / 2; + ctx.save(); + ctx.strokeStyle = strokeStyle; + + if (ctx.setLineDash) { + ctx.setLineDash([lineDashWidth, lineDashWidth]); + } + + if (lineDashOffset) { + ctx.lineDashOffset = lineDashOffset; + } + + if (lineWidth) { + ctx.lineWidth = lineWidth; + } + + ctx.beginPath(); + ctx.moveTo(-halfWidth, -halfHeight); + ctx.lineTo(halfWidth, -halfHeight); + ctx.lineTo(halfWidth, halfHeight); + ctx.lineTo(-halfWidth, halfHeight); + ctx.lineTo(-halfWidth, -halfHeight); + ctx.stroke(); + ctx.restore(); + }, + + /** + * onMoving event listener + * @private + */ + _onMoving: function _onMoving() { + var height = this.height, + width = this.width, + left = this.left, + top = this.top; + var maxLeft = this.canvas.getWidth() - width; + var maxTop = this.canvas.getHeight() - height; + this.left = clamp(left, 0, maxLeft); + this.top = clamp(top, 0, maxTop); + this.canvasEventTrigger[eventNames.OBJECT_MOVED](this); + }, + + /** + * onScaling event listener + * @param {{e: MouseEvent}} fEvent - Fabric event + * @private + */ + _onScaling: function _onScaling(fEvent) { + var selectedCorner = fEvent.transform.corner; + var pointer = this.canvas.getPointer(fEvent.e); + + var settings = this._calcScalingSizeFromPointer(pointer, selectedCorner); // On scaling cropzone, + // change real width and height and fix scaleFactor to 1 + + + this.scale(1).set(settings); + this.canvasEventTrigger[eventNames.OBJECT_SCALED](this); + }, + + /** + * Calc scaled size from mouse pointer with selected corner + * @param {{x: number, y: number}} pointer - Mouse position + * @param {string} selectedCorner - selected corner type + * @returns {Object} Having left or(and) top or(and) width or(and) height. + * @private + */ + _calcScalingSizeFromPointer: function _calcScalingSizeFromPointer(pointer, selectedCorner) { + var isCornerTypeValid = cornerTypeValid(selectedCorner); + return isCornerTypeValid && this._resizeCropZone(pointer, selectedCorner); + }, + + /** + * Align with cropzone ratio + * @param {number} width - cropzone width + * @param {number} height - cropzone height + * @param {number} maxWidth - limit max width + * @param {number} maxHeight - limit max height + * @param {number} scaleTo - cropzone ratio + * @returns {{width: number, height: number}} + * @private + */ + adjustRatioCropzoneSize: function adjustRatioCropzoneSize(_ref2) { + var width = _ref2.width, + height = _ref2.height, + leftMaker = _ref2.leftMaker, + topMaker = _ref2.topMaker, + maxWidth = _ref2.maxWidth, + maxHeight = _ref2.maxHeight, + scaleTo = _ref2.scaleTo; + width = maxWidth ? clamp(width, 1, maxWidth) : width; + height = maxHeight ? clamp(height, 1, maxHeight) : height; + + if (!this.presetRatio) { + if (this._withShiftKey) { + // make fixed ratio cropzone + if (width > height) { + height = width; + } else if (height > width) { + width = height; + } + } + + return { + width: width, + height: height, + left: leftMaker(width), + top: topMaker(height) + }; + } + + if (scaleTo === 'width') { + height = width / this.presetRatio; + } else { + width = height * this.presetRatio; + } + + var maxScaleFactor = Math.min(maxWidth / width, maxHeight / height); + + if (maxScaleFactor <= 1) { + var _context8; + + var _map = map_default()(_context8 = [width, height]).call(_context8, function (v) { + return v * maxScaleFactor; + }); + + var _map2 = _slicedToArray(_map, 2); + + width = _map2[0]; + height = _map2[1]; + } + + return { + width: width, + height: height, + left: leftMaker(width), + top: topMaker(height) + }; + }, + + /** + * Get dimension last state cropzone + * @returns {{rectTop: number, rectLeft: number, rectWidth: number, rectHeight: number}} + * @private + */ + _getCropzoneRectInfo: function _getCropzoneRectInfo() { + var _this$canvas = this.canvas, + canvasWidth = _this$canvas.width, + canvasHeight = _this$canvas.height; + + var _this$getBoundingRect = this.getBoundingRect(false, true), + rectTop = _this$getBoundingRect.top, + rectLeft = _this$getBoundingRect.left, + rectWidth = _this$getBoundingRect.width, + rectHeight = _this$getBoundingRect.height; + + return { + rectTop: rectTop, + rectLeft: rectLeft, + rectWidth: rectWidth, + rectHeight: rectHeight, + rectRight: rectLeft + rectWidth, + rectBottom: rectTop + rectHeight, + canvasWidth: canvasWidth, + canvasHeight: canvasHeight + }; + }, + + /** + * Calc scaling dimension + * @param {Object} position - Mouse position + * @param {string} corner - corner type + * @returns {{left: number, top: number, width: number, height: number}} + * @private + */ + _resizeCropZone: function _resizeCropZone(_ref3, corner) { + var x = _ref3.x, + y = _ref3.y; + + var _this$_getCropzoneRec = this._getCropzoneRectInfo(), + rectWidth = _this$_getCropzoneRec.rectWidth, + rectHeight = _this$_getCropzoneRec.rectHeight, + rectTop = _this$_getCropzoneRec.rectTop, + rectLeft = _this$_getCropzoneRec.rectLeft, + rectBottom = _this$_getCropzoneRec.rectBottom, + rectRight = _this$_getCropzoneRec.rectRight, + canvasWidth = _this$_getCropzoneRec.canvasWidth, + canvasHeight = _this$_getCropzoneRec.canvasHeight; + + var resizeInfoMap = { + tl: { + width: rectRight - x, + height: rectBottom - y, + leftMaker: function leftMaker(newWidth) { + return rectRight - newWidth; + }, + topMaker: function topMaker(newHeight) { + return rectBottom - newHeight; + }, + maxWidth: rectRight, + maxHeight: rectBottom, + scaleTo: getScaleBasis(rectLeft - x, rectTop - y) + }, + tr: { + width: x - rectLeft, + height: rectBottom - y, + leftMaker: function leftMaker() { + return rectLeft; + }, + topMaker: function topMaker(newHeight) { + return rectBottom - newHeight; + }, + maxWidth: canvasWidth - rectLeft, + maxHeight: rectBottom, + scaleTo: getScaleBasis(x - rectRight, rectTop - y) + }, + mt: { + width: rectWidth, + height: rectBottom - y, + leftMaker: function leftMaker() { + return rectLeft; + }, + topMaker: function topMaker(newHeight) { + return rectBottom - newHeight; + }, + maxWidth: canvasWidth - rectLeft, + maxHeight: rectBottom, + scaleTo: 'height' + }, + ml: { + width: rectRight - x, + height: rectHeight, + leftMaker: function leftMaker(newWidth) { + return rectRight - newWidth; + }, + topMaker: function topMaker() { + return rectTop; + }, + maxWidth: rectRight, + maxHeight: canvasHeight - rectTop, + scaleTo: 'width' + }, + mr: { + width: x - rectLeft, + height: rectHeight, + leftMaker: function leftMaker() { + return rectLeft; + }, + topMaker: function topMaker() { + return rectTop; + }, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: 'width' + }, + mb: { + width: rectWidth, + height: y - rectTop, + leftMaker: function leftMaker() { + return rectLeft; + }, + topMaker: function topMaker() { + return rectTop; + }, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: 'height' + }, + bl: { + width: rectRight - x, + height: y - rectTop, + leftMaker: function leftMaker(newWidth) { + return rectRight - newWidth; + }, + topMaker: function topMaker() { + return rectTop; + }, + maxWidth: rectRight, + maxHeight: canvasHeight - rectTop, + scaleTo: getScaleBasis(rectLeft - x, y - rectBottom) + }, + br: { + width: x - rectLeft, + height: y - rectTop, + leftMaker: function leftMaker() { + return rectLeft; + }, + topMaker: function topMaker() { + return rectTop; + }, + maxWidth: canvasWidth - rectLeft, + maxHeight: canvasHeight - rectTop, + scaleTo: getScaleBasis(x - rectRight, y - rectBottom) + } + }; + return this.adjustRatioCropzoneSize(resizeInfoMap[corner]); + }, + + /** + * Return the whether this cropzone is valid + * @returns {boolean} + */ + isValid: function isValid() { + return this.left >= 0 && this.top >= 0 && this.width > 0 && this.height > 0; + }, + + /** + * Keydown event handler + * @param {{number}} keyCode - Event keyCode + * @private + */ + _onKeyDown: function _onKeyDown(_ref4) { + var keyCode = _ref4.keyCode; + + if (keyCode === keyCodes.SHIFT) { + this._withShiftKey = true; + } + }, + + /** + * Keyup event handler + * @param {{number}} keyCode - Event keyCode + * @private + */ + _onKeyUp: function _onKeyUp(_ref5) { + var keyCode = _ref5.keyCode; + + if (keyCode === keyCodes.SHIFT) { + this._withShiftKey = false; + } + } +}); +/* harmony default export */ var cropzone = (Cropzone); +;// CONCATENATED MODULE: ./src/js/component/cropper.js + + + + + + + + + + + +function cropper_createSuper(Derived) { var hasNativeReflectConstruct = cropper_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function cropper_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + +var MOUSE_MOVE_THRESHOLD = 10; +var DEFAULT_OPTION = { + presetRatio: null, + top: -10, + left: -10, + height: 1, + width: 1 +}; +/** + * Cropper components + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @class Cropper + * @ignore + */ + +var Cropper = /*#__PURE__*/function (_Component) { + _inherits(Cropper, _Component); + + var _super = cropper_createSuper(Cropper); + + function Cropper(graphics) { + var _context, _context2, _context3, _context4, _context5; + + var _this; + + _classCallCheck(this, Cropper); + + _this = _super.call(this, componentNames.CROPPER, graphics); + /** + * Cropzone + * @type {Cropzone} + * @private + */ + + _this._cropzone = null; + /** + * StartX of Cropzone + * @type {number} + * @private + */ + + _this._startX = null; + /** + * StartY of Cropzone + * @type {number} + * @private + */ + + _this._startY = null; + /** + * State whether shortcut key is pressed or not + * @type {boolean} + * @private + */ + + _this._withShiftKey = false; + /** + * Listeners + * @type {object.} + * @private + */ + + _this._listeners = { + keydown: bind_default()(_context = _this._onKeyDown).call(_context, _assertThisInitialized(_this)), + keyup: bind_default()(_context2 = _this._onKeyUp).call(_context2, _assertThisInitialized(_this)), + mousedown: bind_default()(_context3 = _this._onFabricMouseDown).call(_context3, _assertThisInitialized(_this)), + mousemove: bind_default()(_context4 = _this._onFabricMouseMove).call(_context4, _assertThisInitialized(_this)), + mouseup: bind_default()(_context5 = _this._onFabricMouseUp).call(_context5, _assertThisInitialized(_this)) + }; + return _this; + } + /** + * Start cropping + */ + + + _createClass(Cropper, [{ + key: "start", + value: function start() { + if (this._cropzone) { + return; + } + + var canvas = this.getCanvas(); + canvas.forEachObject(function (obj) { + // {@link http://fabricjs.com/docs/fabric.Object.html#evented} + obj.evented = false; + }); + this._cropzone = new cropzone(canvas, extend_default()({ + left: 0, + top: 0, + width: 0.5, + height: 0.5, + strokeWidth: 0, + // {@link https://github.com/kangax/fabric.js/issues/2860} + cornerSize: 10, + cornerColor: 'black', + fill: 'transparent' + }, CROPZONE_DEFAULT_OPTIONS, this.graphics.cropSelectionStyle)); + canvas.discardActiveObject(); + canvas.add(this._cropzone); + canvas.on('mouse:down', this._listeners.mousedown); + canvas.selection = false; + canvas.defaultCursor = 'crosshair'; + fabric.fabric.util.addListener(document, 'keydown', this._listeners.keydown); + fabric.fabric.util.addListener(document, 'keyup', this._listeners.keyup); + } + /** + * End cropping + */ + + }, { + key: "end", + value: function end() { + var canvas = this.getCanvas(); + var cropzone = this._cropzone; + + if (!cropzone) { + return; + } + + canvas.remove(cropzone); + canvas.selection = true; + canvas.defaultCursor = 'default'; + canvas.off('mouse:down', this._listeners.mousedown); + canvas.forEachObject(function (obj) { + obj.evented = true; + }); + this._cropzone = null; + fabric.fabric.util.removeListener(document, 'keydown', this._listeners.keydown); + fabric.fabric.util.removeListener(document, 'keyup', this._listeners.keyup); + } + /** + * Change cropzone visible + * @param {boolean} visible - cropzone visible state + */ + + }, { + key: "changeVisibility", + value: function changeVisibility(visible) { + if (this._cropzone) { + this._cropzone.set({ + visible: visible + }); + } + } + /** + * onMousedown handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onFabricMouseDown", + value: function _onFabricMouseDown(fEvent) { + var canvas = this.getCanvas(); + + if (fEvent.target) { + return; + } + + canvas.selection = false; + var coord = canvas.getPointer(fEvent.e); + this._startX = coord.x; + this._startY = coord.y; + canvas.on({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup + }); + } + /** + * onMousemove handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onFabricMouseMove", + value: function _onFabricMouseMove(fEvent) { + var canvas = this.getCanvas(); + var pointer = canvas.getPointer(fEvent.e); + var x = pointer.x, + y = pointer.y; + var cropzone = this._cropzone; + + if (Math.abs(x - this._startX) + Math.abs(y - this._startY) > MOUSE_MOVE_THRESHOLD) { + canvas.remove(cropzone); + cropzone.set(this._calcRectDimensionFromPoint(x, y, cropzone.presetRatio)); + canvas.add(cropzone); + canvas.setActiveObject(cropzone); + } + } + /** + * Get rect dimension setting from Canvas-Mouse-Position(x, y) + * @param {number} x - Canvas-Mouse-Position x + * @param {number} y - Canvas-Mouse-Position Y + * @param {number|null} presetRatio - fixed aspect ratio (width/height) of the cropzone (null if not set) + * @returns {{left: number, top: number, width: number, height: number}} + * @private + */ + + }, { + key: "_calcRectDimensionFromPoint", + value: function _calcRectDimensionFromPoint(x, y) { + var presetRatio = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + var canvas = this.getCanvas(); + var canvasWidth = canvas.getWidth(); + var canvasHeight = canvas.getHeight(); + var startX = this._startX; + var startY = this._startY; + var left = clamp(x, 0, startX); + var top = clamp(y, 0, startY); + var width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left + + var height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top + + if (this._withShiftKey && !presetRatio) { + // make fixed ratio cropzone + if (width > height) { + height = width; + } else if (height > width) { + width = height; + } + + if (startX >= x) { + left = startX - width; + } + + if (startY >= y) { + top = startY - height; + } + } else if (presetRatio) { + // Restrict cropzone to given presetRatio + height = width / presetRatio; // If moving in a direction where the top left corner moves (ie. top-left, bottom-left, top-right) + // the left and/or top values has to be changed based on the new height/width + + if (startX >= x) { + left = clamp(startX - width, 0, canvasWidth); + } + + if (startY >= y) { + top = clamp(startY - height, 0, canvasHeight); + } // Check if the new height is too large + + + if (top + height > canvasHeight) { + height = canvasHeight - top; // Set height to max available height + + width = height * presetRatio; // Restrict cropzone to given presetRatio based on the new height + // If moving in a direction where the top left corner moves (ie. top-left, bottom-left, top-right) + // the left and/or top values has to be changed based on the new height/width + + if (startX >= x) { + left = clamp(startX - width, 0, canvasWidth); + } + + if (startY >= y) { + top = clamp(startY - height, 0, canvasHeight); + } + } + } + + return { + left: left, + top: top, + width: width, + height: height + }; + } + /** + * onMouseup handler in fabric canvas + * @private + */ + + }, { + key: "_onFabricMouseUp", + value: function _onFabricMouseUp() { + var cropzone = this._cropzone; + var listeners = this._listeners; + var canvas = this.getCanvas(); + canvas.setActiveObject(cropzone); + canvas.off({ + 'mouse:move': listeners.mousemove, + 'mouse:up': listeners.mouseup + }); + } + /** + * Get cropped image data + * @param {Object} cropRect cropzone rect + * @param {Number} cropRect.left left position + * @param {Number} cropRect.top top position + * @param {Number} cropRect.width width + * @param {Number} cropRect.height height + * @returns {?{imageName: string, url: string}} cropped Image data + */ + + }, { + key: "getCroppedImageData", + value: function getCroppedImageData(cropRect) { + var canvas = this.getCanvas(); + var containsCropzone = canvas.contains(this._cropzone); + + if (!cropRect) { + return null; + } + + if (containsCropzone) { + canvas.remove(this._cropzone); + } + + var imageData = { + imageName: this.getImageName(), + url: canvas.toDataURL(cropRect) + }; + + if (containsCropzone) { + canvas.add(this._cropzone); + } + + return imageData; + } + /** + * Get cropped rect + * @returns {Object} rect + */ + + }, { + key: "getCropzoneRect", + value: function getCropzoneRect() { + var cropzone = this._cropzone; + + if (!cropzone.isValid()) { + return null; + } + + return { + left: cropzone.left, + top: cropzone.top, + width: cropzone.width, + height: cropzone.height + }; + } + /** + * Set a cropzone square + * @param {number} [presetRatio] - preset ratio + */ + + }, { + key: "setCropzoneRect", + value: function setCropzoneRect(presetRatio) { + var canvas = this.getCanvas(); + var cropzone = this._cropzone; + canvas.discardActiveObject(); + canvas.selection = false; + canvas.remove(cropzone); + cropzone.set(presetRatio ? this._getPresetPropertiesForCropSize(presetRatio) : DEFAULT_OPTION); + canvas.add(cropzone); + canvas.selection = true; + + if (presetRatio) { + canvas.setActiveObject(cropzone); + } + } + /** + * get a cropzone square info + * @param {number} presetRatio - preset ratio + * @returns {{presetRatio: number, left: number, top: number, width: number, height: number}} + * @private + */ + + }, { + key: "_getPresetPropertiesForCropSize", + value: function _getPresetPropertiesForCropSize(presetRatio) { + var _context6, _context7; + + var canvas = this.getCanvas(); + var originalWidth = canvas.getWidth(); + var originalHeight = canvas.getHeight(); + var standardSize = originalWidth >= originalHeight ? originalWidth : originalHeight; + + var getScale = function getScale(value, orignalValue) { + return value > orignalValue ? orignalValue / value : 1; + }; + + var width = standardSize * presetRatio; + var height = standardSize; + var scaleWidth = getScale(width, originalWidth); + + var _map = map_default()(_context6 = [width, height]).call(_context6, function (sizeValue) { + return sizeValue * scaleWidth; + }); + + var _map2 = _slicedToArray(_map, 2); + + width = _map2[0]; + height = _map2[1]; + var scaleHeight = getScale(height, originalHeight); + + var _map3 = map_default()(_context7 = [width, height]).call(_context7, function (sizeValue) { + return fixFloatingPoint(sizeValue * scaleHeight); + }); + + var _map4 = _slicedToArray(_map3, 2); + + width = _map4[0]; + height = _map4[1]; + return { + presetRatio: presetRatio, + top: (originalHeight - height) / 2, + left: (originalWidth - width) / 2, + width: width, + height: height + }; + } + /** + * Keydown event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_onKeyDown", + value: function _onKeyDown(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = true; + } + } + /** + * Keyup event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_onKeyUp", + value: function _onKeyUp(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = false; + } + } + }]); + + return Cropper; +}(component); + +/* harmony default export */ var cropper = (Cropper); +;// CONCATENATED MODULE: ./src/js/component/flip.js + + + + + + + + + +function component_flip_createSuper(Derived) { var hasNativeReflectConstruct = component_flip_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function component_flip_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + +/** + * Flip + * @class Flip + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var flip_Flip = /*#__PURE__*/function (_Component) { + _inherits(Flip, _Component); + + var _super = component_flip_createSuper(Flip); + + function Flip(graphics) { + _classCallCheck(this, Flip); + + return _super.call(this, componentNames.FLIP, graphics); + } + /** + * Get current flip settings + * @returns {{flipX: Boolean, flipY: Boolean}} + */ + + + _createClass(Flip, [{ + key: "getCurrentSetting", + value: function getCurrentSetting() { + var canvasImage = this.getCanvasImage(); + return { + flipX: canvasImage.flipX, + flipY: canvasImage.flipY + }; + } + /** + * Set flipX, flipY + * @param {{flipX: Boolean, flipY: Boolean}} newSetting - Flip setting + * @returns {Promise} + */ + + }, { + key: "set", + value: function set(newSetting) { + var setting = this.getCurrentSetting(); + var isChangingFlipX = setting.flipX !== newSetting.flipX; + var isChangingFlipY = setting.flipY !== newSetting.flipY; + + if (!isChangingFlipX && !isChangingFlipY) { + return promise_default().reject(rejectMessages.flip); + } + + extend_default()(setting, newSetting); + this.setImageProperties(setting, true); + + this._invertAngle(isChangingFlipX, isChangingFlipY); + + this._flipObjects(isChangingFlipX, isChangingFlipY); + + return promise_default().resolve({ + flipX: setting.flipX, + flipY: setting.flipY, + angle: this.getCanvasImage().angle + }); + } + /** + * Invert image angle for flip + * @param {boolean} isChangingFlipX - Change flipX + * @param {boolean} isChangingFlipY - Change flipY + */ + + }, { + key: "_invertAngle", + value: function _invertAngle(isChangingFlipX, isChangingFlipY) { + var canvasImage = this.getCanvasImage(); + var angle = canvasImage.angle; + + if (isChangingFlipX) { + angle *= -1; + } + + if (isChangingFlipY) { + angle *= -1; + } + + canvasImage.rotate(parse_float_default()(angle)).setCoords(); // parseFloat for -0 to 0 + } + /** + * Flip objects + * @param {boolean} isChangingFlipX - Change flipX + * @param {boolean} isChangingFlipY - Change flipY + * @private + */ + + }, { + key: "_flipObjects", + value: function _flipObjects(isChangingFlipX, isChangingFlipY) { + var canvas = this.getCanvas(); + + if (isChangingFlipX) { + canvas.forEachObject(function (obj) { + obj.set({ + angle: parse_float_default()(obj.angle * -1), + // parseFloat for -0 to 0 + flipX: !obj.flipX, + left: canvas.width - obj.left + }).setCoords(); + }); + } + + if (isChangingFlipY) { + canvas.forEachObject(function (obj) { + obj.set({ + angle: parse_float_default()(obj.angle * -1), + // parseFloat for -0 to 0 + flipY: !obj.flipY, + top: canvas.height - obj.top + }).setCoords(); + }); + } + + canvas.renderAll(); + } + /** + * Reset flip settings + * @returns {Promise} + */ + + }, { + key: "reset", + value: function reset() { + return this.set({ + flipX: false, + flipY: false + }); + } + /** + * Flip x + * @returns {Promise} + */ + + }, { + key: "flipX", + value: function flipX() { + var current = this.getCurrentSetting(); + return this.set({ + flipX: !current.flipX, + flipY: current.flipY + }); + } + /** + * Flip y + * @returns {Promise} + */ + + }, { + key: "flipY", + value: function flipY() { + var current = this.getCurrentSetting(); + return this.set({ + flipX: current.flipX, + flipY: !current.flipY + }); + } + }]); + + return Flip; +}(component); + +/* harmony default export */ var component_flip = (flip_Flip); +;// CONCATENATED MODULE: ./src/js/component/rotation.js + + + + + + + + +function rotation_createSuper(Derived) { var hasNativeReflectConstruct = rotation_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function rotation_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + +/** + * Image Rotation component + * @class Rotation + * @extends {Component} + * @param {Graphics} graphics - Graphics instance + * @ignore + */ + +var Rotation = /*#__PURE__*/function (_Component) { + _inherits(Rotation, _Component); + + var _super = rotation_createSuper(Rotation); + + function Rotation(graphics) { + _classCallCheck(this, Rotation); + + return _super.call(this, componentNames.ROTATION, graphics); + } + /** + * Get current angle + * @returns {Number} + */ + + + _createClass(Rotation, [{ + key: "getCurrentAngle", + value: function getCurrentAngle() { + return this.getCanvasImage().angle; + } + /** + * Set angle of the image + * + * Do not call "this.setImageProperties" for setting angle directly. + * Before setting angle, The originX,Y of image should be set to center. + * See "http://fabricjs.com/docs/fabric.Object.html#setAngle" + * + * @param {number} angle - Angle value + * @returns {Promise} + */ + + }, { + key: "setAngle", + value: function setAngle(angle) { + var oldAngle = this.getCurrentAngle() % 360; // The angle is lower than 2*PI(===360 degrees) + + angle %= 360; + var canvasImage = this.getCanvasImage(); + var oldImageCenter = canvasImage.getCenterPoint(); + canvasImage.set({ + angle: angle + }).setCoords(); + this.adjustCanvasDimension(); + var newImageCenter = canvasImage.getCenterPoint(); + + this._rotateForEachObject(oldImageCenter, newImageCenter, angle - oldAngle); + + return promise_default().resolve(angle); + } + /** + * Rotate for each object + * @param {fabric.Point} oldImageCenter - Image center point before rotation + * @param {fabric.Point} newImageCenter - Image center point after rotation + * @param {number} angleDiff - Image angle difference after rotation + * @private + */ + + }, { + key: "_rotateForEachObject", + value: function _rotateForEachObject(oldImageCenter, newImageCenter, angleDiff) { + var canvas = this.getCanvas(); + var centerDiff = { + x: oldImageCenter.x - newImageCenter.x, + y: oldImageCenter.y - newImageCenter.y + }; + canvas.forEachObject(function (obj) { + var objCenter = obj.getCenterPoint(); + var radian = fabric.fabric.util.degreesToRadians(angleDiff); + var newObjCenter = fabric.fabric.util.rotatePoint(objCenter, oldImageCenter, radian); + obj.set({ + left: newObjCenter.x - centerDiff.x, + top: newObjCenter.y - centerDiff.y, + angle: (obj.angle + angleDiff) % 360 + }); + obj.setCoords(); + }); + canvas.renderAll(); + } + /** + * Rotate the image + * @param {number} additionalAngle - Additional angle + * @returns {Promise} + */ + + }, { + key: "rotate", + value: function rotate(additionalAngle) { + var current = this.getCurrentAngle(); + return this.setAngle(current + additionalAngle); + } + }]); + + return Rotation; +}(component); + +/* harmony default export */ var rotation = (Rotation); +;// CONCATENATED MODULE: ./src/js/component/freeDrawing.js + + + + + + + +function freeDrawing_createSuper(Derived) { var hasNativeReflectConstruct = freeDrawing_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function freeDrawing_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + +/** + * FreeDrawing + * @class FreeDrawing + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var FreeDrawing = /*#__PURE__*/function (_Component) { + _inherits(FreeDrawing, _Component); + + var _super = freeDrawing_createSuper(FreeDrawing); + + function FreeDrawing(graphics) { + var _this; + + _classCallCheck(this, FreeDrawing); + + _this = _super.call(this, componentNames.FREE_DRAWING, graphics); + /** + * Brush width + * @type {number} + */ + + _this.width = 12; + /** + * fabric.Color instance for brush color + * @type {fabric.Color} + */ + + _this.oColor = new fabric.fabric.Color('rgba(0, 0, 0, 0.5)'); + return _this; + } + /** + * Start free drawing mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + + + _createClass(FreeDrawing, [{ + key: "start", + value: function start(setting) { + var canvas = this.getCanvas(); + canvas.isDrawingMode = true; + this.setBrush(setting); + } + /** + * Set brush + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + + }, { + key: "setBrush", + value: function setBrush(setting) { + var brush = this.getCanvas().freeDrawingBrush; + setting = setting || {}; + this.width = setting.width || this.width; + + if (setting.color) { + this.oColor = new fabric.fabric.Color(setting.color); + } + + brush.width = this.width; + brush.color = this.oColor.toRgba(); + } + /** + * End free drawing mode + */ + + }, { + key: "end", + value: function end() { + var canvas = this.getCanvas(); + canvas.isDrawingMode = false; + } + }]); + + return FreeDrawing; +}(component); + +/* harmony default export */ var freeDrawing = (FreeDrawing); +;// CONCATENATED MODULE: ./src/js/extension/arrowLine.js + + + +var ARROW_ANGLE = 30; +var CHEVRON_SIZE_RATIO = 2.7; +var TRIANGLE_SIZE_RATIO = 1.7; +var RADIAN_CONVERSION_VALUE = 180; +var ArrowLine = fabric.fabric.util.createClass(fabric.fabric.Line, +/** @lends Convolute.prototype */ +{ + /** + * Line type + * @param {String} type + * @default + */ + type: 'line', + + /** + * Constructor + * @param {Array} [points] Array of points + * @param {Object} [options] Options object + * @override + */ + initialize: function initialize(points) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + this.callSuper('initialize', points, options); + this.arrowType = options.arrowType; + }, + + /** + * Render ArrowLine + * @private + * @override + */ + _render: function _render(ctx) { + var _this$calcLinePoints = this.calcLinePoints(), + fromX = _this$calcLinePoints.x1, + fromY = _this$calcLinePoints.y1, + toX = _this$calcLinePoints.x2, + toY = _this$calcLinePoints.y2; + + var linePosition = { + fromX: fromX, + fromY: fromY, + toX: toX, + toY: toY + }; + this.ctx = ctx; + ctx.lineWidth = this.strokeWidth; + + this._renderBasicLinePath(linePosition); + + this._drawDecoratorPath(linePosition); + + this._renderStroke(ctx); + }, + + /** + * Render Basic line path + * @param {Object} linePosition - line position + * @param {number} option.fromX - line start position x + * @param {number} option.fromY - line start position y + * @param {number} option.toX - line end position x + * @param {number} option.toY - line end position y + * @private + */ + _renderBasicLinePath: function _renderBasicLinePath(_ref) { + var fromX = _ref.fromX, + fromY = _ref.fromY, + toX = _ref.toX, + toY = _ref.toY; + this.ctx.beginPath(); + this.ctx.moveTo(fromX, fromY); + this.ctx.lineTo(toX, toY); + }, + + /** + * Render Arrow Head + * @param {Object} linePosition - line position + * @param {number} option.fromX - line start position x + * @param {number} option.fromY - line start position y + * @param {number} option.toX - line end position x + * @param {number} option.toY - line end position y + * @private + */ + _drawDecoratorPath: function _drawDecoratorPath(linePosition) { + this._drawDecoratorPathType('head', linePosition); + + this._drawDecoratorPathType('tail', linePosition); + }, + + /** + * Render Arrow Head + * @param {string} type - 'head' or 'tail' + * @param {Object} linePosition - line position + * @param {number} option.fromX - line start position x + * @param {number} option.fromY - line start position y + * @param {number} option.toX - line end position x + * @param {number} option.toY - line end position y + * @private + */ + _drawDecoratorPathType: function _drawDecoratorPathType(type, linePosition) { + switch (this.arrowType[type]) { + case 'triangle': + this._drawTrianglePath(type, linePosition); + + break; + + case 'chevron': + this._drawChevronPath(type, linePosition); + + break; + + default: + break; + } + }, + + /** + * Render Triangle Head + * @param {string} type - 'head' or 'tail' + * @param {Object} linePosition - line position + * @param {number} option.fromX - line start position x + * @param {number} option.fromY - line start position y + * @param {number} option.toX - line end position x + * @param {number} option.toY - line end position y + * @private + */ + _drawTrianglePath: function _drawTrianglePath(type, linePosition) { + var decorateSize = this.ctx.lineWidth * TRIANGLE_SIZE_RATIO; + + this._drawChevronPath(type, linePosition, decorateSize); + + this.ctx.closePath(); + }, + + /** + * Render Chevron Head + * @param {string} type - 'head' or 'tail' + * @param {Object} linePosition - line position + * @param {number} option.fromX - line start position x + * @param {number} option.fromY - line start position y + * @param {number} option.toX - line end position x + * @param {number} option.toY - line end position y + * @param {number} decorateSize - decorate size + * @private + */ + _drawChevronPath: function _drawChevronPath(type, _ref2, decorateSize) { + var _this = this; + + var fromX = _ref2.fromX, + fromY = _ref2.fromY, + toX = _ref2.toX, + toY = _ref2.toY; + var ctx = this.ctx; + + if (!decorateSize) { + decorateSize = this.ctx.lineWidth * CHEVRON_SIZE_RATIO; + } + + var _ref3 = type === 'head' ? [fromX, fromY] : [toX, toY], + _ref4 = _slicedToArray(_ref3, 2), + standardX = _ref4[0], + standardY = _ref4[1]; + + var _ref5 = type === 'head' ? [toX, toY] : [fromX, fromY], + _ref6 = _slicedToArray(_ref5, 2), + compareX = _ref6[0], + compareY = _ref6[1]; + + var angle = Math.atan2(compareY - standardY, compareX - standardX) * RADIAN_CONVERSION_VALUE / Math.PI; + + var rotatedPosition = function rotatedPosition(changeAngle) { + return _this.getRotatePosition(decorateSize, changeAngle, { + x: standardX, + y: standardY + }); + }; + + ctx.moveTo.apply(ctx, _toConsumableArray(rotatedPosition(angle + ARROW_ANGLE))); + ctx.lineTo(standardX, standardY); + ctx.lineTo.apply(ctx, _toConsumableArray(rotatedPosition(angle - ARROW_ANGLE))); + }, + + /** + * return position from change angle. + * @param {number} distance - change distance + * @param {number} angle - change angle + * @param {Object} referencePosition - reference position + * @returns {Array} + * @private + */ + getRotatePosition: function getRotatePosition(distance, angle, referencePosition) { + var radian = angle * Math.PI / RADIAN_CONVERSION_VALUE; + var x = referencePosition.x, + y = referencePosition.y; + return [distance * Math.cos(radian) + x, distance * Math.sin(radian) + y]; + } +}); +/* harmony default export */ var arrowLine = (ArrowLine); +;// CONCATENATED MODULE: ./src/js/component/line.js + + + + + + + + + +function line_createSuper(Derived) { var hasNativeReflectConstruct = line_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function line_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + +/** + * Line + * @class Line + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var Line = /*#__PURE__*/function (_Component) { + _inherits(Line, _Component); + + var _super = line_createSuper(Line); + + function Line(graphics) { + var _context, _context2, _context3; + + var _this; + + _classCallCheck(this, Line); + + _this = _super.call(this, componentNames.LINE, graphics); + /** + * Brush width + * @type {number} + * @private + */ + + _this._width = 12; + /** + * fabric.Color instance for brush color + * @type {fabric.Color} + * @private + */ + + _this._oColor = new fabric.fabric.Color('rgba(0, 0, 0, 0.5)'); + /** + * Listeners + * @type {object.} + * @private + */ + + _this._listeners = { + mousedown: bind_default()(_context = _this._onFabricMouseDown).call(_context, _assertThisInitialized(_this)), + mousemove: bind_default()(_context2 = _this._onFabricMouseMove).call(_context2, _assertThisInitialized(_this)), + mouseup: bind_default()(_context3 = _this._onFabricMouseUp).call(_context3, _assertThisInitialized(_this)) + }; + return _this; + } + /** + * Start drawing line mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + + + _createClass(Line, [{ + key: "setHeadOption", + value: function setHeadOption(setting) { + var _setting$arrowType = setting.arrowType, + arrowType = _setting$arrowType === void 0 ? { + head: null, + tail: null + } : _setting$arrowType; + this._arrowType = arrowType; + } + /** + * Start drawing line mode + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + + }, { + key: "start", + value: function start() { + var setting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var canvas = this.getCanvas(); + canvas.defaultCursor = 'crosshair'; + canvas.selection = false; + this.setHeadOption(setting); + this.setBrush(setting); + canvas.forEachObject(function (obj) { + obj.set({ + evented: false + }); + }); + canvas.on({ + 'mouse:down': this._listeners.mousedown + }); + } + /** + * Set brush + * @param {{width: ?number, color: ?string}} [setting] - Brush width & color + */ + + }, { + key: "setBrush", + value: function setBrush(setting) { + var brush = this.getCanvas().freeDrawingBrush; + setting = setting || {}; + this._width = setting.width || this._width; + + if (setting.color) { + this._oColor = new fabric.fabric.Color(setting.color); + } + + brush.width = this._width; + brush.color = this._oColor.toRgba(); + } + /** + * End drawing line mode + */ + + }, { + key: "end", + value: function end() { + var canvas = this.getCanvas(); + canvas.defaultCursor = 'default'; + canvas.selection = true; + canvas.forEachObject(function (obj) { + obj.set({ + evented: true + }); + }); + canvas.off('mouse:down', this._listeners.mousedown); + } + /** + * Mousedown event handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseDown", + value: function _onFabricMouseDown(fEvent) { + var canvas = this.getCanvas(); + + var _canvas$getPointer = canvas.getPointer(fEvent.e), + x = _canvas$getPointer.x, + y = _canvas$getPointer.y; + + var points = [x, y, x, y]; + this._line = new arrowLine(points, { + stroke: this._oColor.toRgba(), + strokeWidth: this._width, + arrowType: this._arrowType, + evented: false + }); + + this._line.set(fObjectOptions.SELECTION_STYLE); + + canvas.add(this._line); + canvas.on({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup + }); + this.fire(eventNames.ADD_OBJECT, this._createLineEventObjectProperties()); + } + /** + * Mousemove event handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseMove", + value: function _onFabricMouseMove(fEvent) { + var canvas = this.getCanvas(); + var pointer = canvas.getPointer(fEvent.e); + + this._line.set({ + x2: pointer.x, + y2: pointer.y + }); + + this._line.setCoords(); + + canvas.renderAll(); + } + /** + * Mouseup event handler in fabric canvas + * @private + */ + + }, { + key: "_onFabricMouseUp", + value: function _onFabricMouseUp() { + var canvas = this.getCanvas(); + this.fire(eventNames.OBJECT_ADDED, this._createLineEventObjectProperties()); + this._line = null; + canvas.off({ + 'mouse:move': this._listeners.mousemove, + 'mouse:up': this._listeners.mouseup + }); + } + /** + * create line event object properties + * @returns {Object} properties line object + * @private + */ + + }, { + key: "_createLineEventObjectProperties", + value: function _createLineEventObjectProperties() { + var params = this.graphics.createObjectProperties(this._line); + var _this$_line = this._line, + x1 = _this$_line.x1, + x2 = _this$_line.x2, + y1 = _this$_line.y1, + y2 = _this$_line.y2; + return extend_default()({}, params, { + startPosition: { + x: x1, + y: y1 + }, + endPosition: { + x: x2, + y: y2 + } + }); + } + }]); + + return Line; +}(component); + +/* harmony default export */ var line = (Line); +;// CONCATENATED MODULE: ./src/js/component/text.js + + + + + + + + + + + + +function component_text_createSuper(Derived) { var hasNativeReflectConstruct = component_text_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function component_text_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + + +var defaultStyles = { + fill: '#000000', + left: 0, + top: 0 +}; +var resetStyles = { + fill: '#000000', + fontStyle: 'normal', + fontWeight: 'normal', + textAlign: 'tie-text-align-left', + underline: false +}; +var DBCLICK_TIME = 500; +/** + * Text + * @class Text + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var text_Text = /*#__PURE__*/function (_Component) { + _inherits(Text, _Component); + + var _super = component_text_createSuper(Text); + + function Text(graphics) { + var _context, _context2, _context3, _context4, _context5; + + var _this; + + _classCallCheck(this, Text); + + _this = _super.call(this, componentNames.TEXT, graphics); + /** + * Default text style + * @type {Object} + */ + + _this._defaultStyles = defaultStyles; + /** + * Selected state + * @type {boolean} + */ + + _this._isSelected = false; + /** + * Selected text object + * @type {Object} + */ + + _this._selectedObj = {}; + /** + * Editing text object + * @type {Object} + */ + + _this._editingObj = {}; + /** + * Listeners for fabric event + * @type {Object} + */ + + _this._listeners = { + mousedown: bind_default()(_context = _this._onFabricMouseDown).call(_context, _assertThisInitialized(_this)), + select: bind_default()(_context2 = _this._onFabricSelect).call(_context2, _assertThisInitialized(_this)), + selectClear: bind_default()(_context3 = _this._onFabricSelectClear).call(_context3, _assertThisInitialized(_this)), + scaling: bind_default()(_context4 = _this._onFabricScaling).call(_context4, _assertThisInitialized(_this)), + textChanged: bind_default()(_context5 = _this._onFabricTextChanged).call(_context5, _assertThisInitialized(_this)) + }; + /** + * Textarea element for editing + * @type {HTMLElement} + */ + + _this._textarea = null; + /** + * Ratio of current canvas + * @type {number} + */ + + _this._ratio = 1; + /** + * Last click time + * @type {Date} + */ + + _this._lastClickTime = new Date().getTime(); + /** + * Text object infos before editing + * @type {Object} + */ + + _this._editingObjInfos = {}; + /** + * Previous state of editing + * @type {boolean} + */ + + _this.isPrevEditing = false; + return _this; + } + /** + * Start input text mode + */ + + + _createClass(Text, [{ + key: "start", + value: function start() { + var _this2 = this; + + var canvas = this.getCanvas(); + canvas.selection = false; + canvas.defaultCursor = 'text'; + canvas.on({ + 'mouse:down': this._listeners.mousedown, + 'selection:created': this._listeners.select, + 'selection:updated': this._listeners.select, + 'before:selection:cleared': this._listeners.selectClear, + 'object:scaling': this._listeners.scaling, + 'text:changed': this._listeners.textChanged + }); + canvas.forEachObject(function (obj) { + if (obj.type === 'i-text') { + _this2.adjustOriginPosition(obj, 'start'); + } + }); + this.setCanvasRatio(); + } + /** + * End input text mode + */ + + }, { + key: "end", + value: function end() { + var _this3 = this; + + var canvas = this.getCanvas(); + canvas.selection = true; + canvas.defaultCursor = 'default'; + canvas.forEachObject(function (obj) { + if (obj.type === 'i-text') { + if (obj.text === '') { + canvas.remove(obj); + } else { + _this3.adjustOriginPosition(obj, 'end'); + } + } + }); + canvas.off({ + 'mouse:down': this._listeners.mousedown, + 'selection:created': this._listeners.select, + 'selection:updated': this._listeners.select, + 'before:selection:cleared': this._listeners.selectClear, + 'object:selected': this._listeners.select, + 'object:scaling': this._listeners.scaling, + 'text:changed': this._listeners.textChanged + }); + } + /** + * Adjust the origin position + * @param {fabric.Object} text - text object + * @param {string} editStatus - 'start' or 'end' + */ + + }, { + key: "adjustOriginPosition", + value: function adjustOriginPosition(text, editStatus) { + var originX = 'center', + originY = 'center'; + + if (editStatus === 'start') { + originX = 'left'; + originY = 'top'; + } + + var _text$getPointByOrigi = text.getPointByOrigin(originX, originY), + left = _text$getPointByOrigi.x, + top = _text$getPointByOrigi.y; + + text.set({ + left: left, + top: top, + originX: originX, + originY: originY + }); + text.setCoords(); + } + /** + * Add new text on canvas image + * @param {string} text - Initial input text + * @param {Object} options - Options for generating text + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @returns {Promise} + */ + + }, { + key: "add", + value: function add(text, options) { + var _this4 = this; + + return new (promise_default())(function (resolve) { + var _context6; + + var canvas = _this4.getCanvas(); + + var newText = null; + var selectionStyle = fObjectOptions.SELECTION_STYLE; + var styles = _this4._defaultStyles; + + _this4._setInitPos(options.position); + + if (options.styles) { + styles = extend_default()(styles, options.styles); + } + + if (!isExisty_default()(options.autofocus)) { + options.autofocus = true; + } + + newText = new fabric.fabric.IText(text, styles); + selectionStyle = extend_default()({}, selectionStyle, { + originX: 'left', + originY: 'top' + }); + newText.set(selectionStyle); + newText.on({ + mouseup: bind_default()(_context6 = _this4._onFabricMouseUp).call(_context6, _this4) + }); + canvas.add(newText); + + if (options.autofocus) { + newText.enterEditing(); + newText.selectAll(); + } + + if (!canvas.getActiveObject()) { + canvas.setActiveObject(newText); + } + + _this4.isPrevEditing = true; + resolve(_this4.graphics.createObjectProperties(newText)); + }); + } + /** + * Change text of activate object on canvas image + * @param {Object} activeObj - Current selected text object + * @param {string} text - Changed text + * @returns {Promise} + */ + + }, { + key: "change", + value: function change(activeObj, text) { + var _this5 = this; + + return new (promise_default())(function (resolve) { + activeObj.set('text', text); + + _this5.getCanvas().renderAll(); + + resolve(); + }); + } + /** + * Set style + * @param {Object} activeObj - Current selected text object + * @param {Object} styleObj - Initial styles + * @param {string} [styleObj.fill] Color + * @param {string} [styleObj.fontFamily] Font type for text + * @param {number} [styleObj.fontSize] Size + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styleObj.textAlign] Type of text align (left / center / right) + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) + * @returns {Promise} + */ + + }, { + key: "setStyle", + value: function setStyle(activeObj, styleObj) { + var _this6 = this; + + return new (promise_default())(function (resolve) { + forEach_default()(styleObj, function (val, key) { + if (activeObj[key] === val && key !== 'fontSize') { + styleObj[key] = resetStyles[key] || ''; + } + }, _this6); + + if ('textDecoration' in styleObj) { + extend_default()(styleObj, _this6._getTextDecorationAdaptObject(styleObj.textDecoration)); + } + + activeObj.set(styleObj); + + _this6.getCanvas().renderAll(); + + resolve(); + }); + } + /** + * Get the text + * @param {Object} activeObj - Current selected text object + * @returns {String} text + */ + + }, { + key: "getText", + value: function getText(activeObj) { + return activeObj.text; + } + /** + * Set infos of the current selected object + * @param {fabric.Text} obj - Current selected text object + * @param {boolean} state - State of selecting + */ + + }, { + key: "setSelectedInfo", + value: function setSelectedInfo(obj, state) { + this._selectedObj = obj; + this._isSelected = state; + } + /** + * Whether object is selected or not + * @returns {boolean} State of selecting + */ + + }, { + key: "isSelected", + value: function isSelected() { + return this._isSelected; + } + /** + * Get current selected text object + * @returns {fabric.Text} Current selected text object + */ + + }, { + key: "getSelectedObj", + value: function getSelectedObj() { + return this._selectedObj; + } + /** + * Set ratio value of canvas + */ + + }, { + key: "setCanvasRatio", + value: function setCanvasRatio() { + var canvasElement = this.getCanvasElement(); + + var cssWidth = parse_int_default()(canvasElement.style.maxWidth, 10); + + var originWidth = canvasElement.width; + this._ratio = originWidth / cssWidth; + } + /** + * Get ratio value of canvas + * @returns {number} Ratio value + */ + + }, { + key: "getCanvasRatio", + value: function getCanvasRatio() { + return this._ratio; + } + /** + * Get text decoration adapt object + * @param {string} textDecoration - text decoration option string + * @returns {object} adapt object for override + */ + + }, { + key: "_getTextDecorationAdaptObject", + value: function _getTextDecorationAdaptObject(textDecoration) { + return { + underline: textDecoration === 'underline', + linethrough: textDecoration === 'line-through', + overline: textDecoration === 'overline' + }; + } + /** + * Set initial position on canvas image + * @param {{x: number, y: number}} [position] - Selected position + * @private + */ + + }, { + key: "_setInitPos", + value: function _setInitPos(position) { + position = position || this.getCanvasImage().getCenterPoint(); + this._defaultStyles.left = position.x; + this._defaultStyles.top = position.y; + } + /** + * Input event handler + * @private + */ + + }, { + key: "_onInput", + value: function _onInput() { + var ratio = this.getCanvasRatio(); + var obj = this._editingObj; + var textareaStyle = this._textarea.style; + textareaStyle.width = "".concat(Math.ceil(obj.width / ratio), "px"); + textareaStyle.height = "".concat(Math.ceil(obj.height / ratio), "px"); + } + /** + * Keydown event handler + * @private + */ + + }, { + key: "_onKeyDown", + value: function _onKeyDown() { + var _this7 = this; + + var ratio = this.getCanvasRatio(); + var obj = this._editingObj; + var textareaStyle = this._textarea.style; + + set_timeout_default()(function () { + obj.text(_this7._textarea.value); + textareaStyle.width = "".concat(Math.ceil(obj.width / ratio), "px"); + textareaStyle.height = "".concat(Math.ceil(obj.height / ratio), "px"); + }, 0); + } + /** + * Blur event handler + * @private + */ + + }, { + key: "_onBlur", + value: function _onBlur() { + var ratio = this.getCanvasRatio(); + var editingObj = this._editingObj; + var editingObjInfos = this._editingObjInfos; + var textContent = this._textarea.value; + var transWidth = editingObj.width / ratio - editingObjInfos.width / ratio; + var transHeight = editingObj.height / ratio - editingObjInfos.height / ratio; + + if (ratio === 1) { + transWidth /= 2; + transHeight /= 2; + } + + this._textarea.style.display = 'none'; + editingObj.set({ + left: editingObjInfos.left + transWidth, + top: editingObjInfos.top + transHeight + }); + + if (textContent.length) { + this.getCanvas().add(editingObj); + var params = { + id: stamp(editingObj), + type: editingObj.type, + text: textContent + }; + this.fire(eventNames.TEXT_CHANGED, params); + } + } + /** + * Scroll event handler + * @private + */ + + }, { + key: "_onScroll", + value: function _onScroll() { + this._textarea.scrollLeft = 0; + this._textarea.scrollTop = 0; + } + /** + * Fabric scaling event handler + * @param {fabric.Event} fEvent - Current scaling event on selected object + * @private + */ + + }, { + key: "_onFabricScaling", + value: function _onFabricScaling(fEvent) { + var obj = fEvent.target; + obj.fontSize = obj.fontSize * obj.scaleY; + obj.scaleX = 1; + obj.scaleY = 1; + } + /** + * textChanged event handler + * @param {{target: fabric.Object}} props - changed text object + * @private + */ + + }, { + key: "_onFabricTextChanged", + value: function _onFabricTextChanged(props) { + this.fire(eventNames.TEXT_CHANGED, props.target); + } + /** + * onSelectClear handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onFabricSelectClear", + value: function _onFabricSelectClear(fEvent) { + var obj = this.getSelectedObj(); + this.isPrevEditing = true; + this.setSelectedInfo(fEvent.target, false); + + if (obj) { + // obj is empty object at initial time, will be set fabric object + if (obj.text === '') { + this.getCanvas().remove(obj); + } + } + } + /** + * onSelect handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onFabricSelect", + value: function _onFabricSelect(fEvent) { + this.isPrevEditing = true; + this.setSelectedInfo(fEvent.target, true); + } + /** + * Fabric 'mousedown' event handler + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + + }, { + key: "_onFabricMouseDown", + value: function _onFabricMouseDown(fEvent) { + var obj = fEvent.target; + + if (obj && !obj.isType('text')) { + return; + } + + if (this.isPrevEditing) { + this.isPrevEditing = false; + return; + } + + this._fireAddText(fEvent); + } + /** + * Fire 'addText' event if object is not selected. + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + + }, { + key: "_fireAddText", + value: function _fireAddText(fEvent) { + var obj = fEvent.target; + var e = fEvent.e || {}; + var originPointer = this.getCanvas().getPointer(e); + + if (!obj) { + this.fire(eventNames.ADD_TEXT, { + originPosition: { + x: originPointer.x, + y: originPointer.y + }, + clientPosition: { + x: e.clientX || 0, + y: e.clientY || 0 + } + }); + } + } + /** + * Fabric mouseup event handler + * @param {fabric.Event} fEvent - Current mousedown event on selected object + * @private + */ + + }, { + key: "_onFabricMouseUp", + value: function _onFabricMouseUp(fEvent) { + var target = fEvent.target; + var newClickTime = new Date().getTime(); + + if (this._isDoubleClick(newClickTime) && !target.isEditing) { + target.enterEditing(); + } + + if (target.isEditing) { + this.fire(eventNames.TEXT_EDITING); // fire editing text event + } + + this._lastClickTime = newClickTime; + } + /** + * Get state of firing double click event + * @param {Date} newClickTime - Current clicked time + * @returns {boolean} Whether double clicked or not + * @private + */ + + }, { + key: "_isDoubleClick", + value: function _isDoubleClick(newClickTime) { + return newClickTime - this._lastClickTime < DBCLICK_TIME; + } + }]); + + return Text; +}(component); + +/* harmony default export */ var component_text = (text_Text); +;// CONCATENATED MODULE: ./src/js/component/icon.js + + + + + + + + + + + +function component_icon_createSuper(Derived) { var hasNativeReflectConstruct = component_icon_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function component_icon_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + +var pathMap = { + arrow: 'M 0 90 H 105 V 120 L 160 60 L 105 0 V 30 H 0 Z', + cancel: 'M 0 30 L 30 60 L 0 90 L 30 120 L 60 90 L 90 120 L 120 90 ' + 'L 90 60 L 120 30 L 90 0 L 60 30 L 30 0 Z' +}; +/** + * Icon + * @class Icon + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var icon_Icon = /*#__PURE__*/function (_Component) { + _inherits(Icon, _Component); + + var _super = component_icon_createSuper(Icon); + + function Icon(graphics) { + var _context, _context2, _context3; + + var _this; + + _classCallCheck(this, Icon); + + _this = _super.call(this, componentNames.ICON, graphics); + /** + * Default icon color + * @type {string} + */ + + _this._oColor = '#000000'; + /** + * Path value of each icon type + * @type {Object} + */ + + _this._pathMap = pathMap; + /** + * Type of the drawing icon + * @type {string} + * @private + */ + + _this._type = null; + /** + * Color of the drawing icon + * @type {string} + * @private + */ + + _this._iconColor = null; + /** + * Event handler list + * @type {Object} + * @private + */ + + _this._handlers = { + mousedown: bind_default()(_context = _this._onFabricMouseDown).call(_context, _assertThisInitialized(_this)), + mousemove: bind_default()(_context2 = _this._onFabricMouseMove).call(_context2, _assertThisInitialized(_this)), + mouseup: bind_default()(_context3 = _this._onFabricMouseUp).call(_context3, _assertThisInitialized(_this)) + }; + return _this; + } + /** + * Set states of the current drawing shape + * @ignore + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {string} iconColor - Icon foreground color + */ + + + _createClass(Icon, [{ + key: "setStates", + value: function setStates(type, iconColor) { + this._type = type; + this._iconColor = iconColor; + } + /** + * Start to draw the icon on canvas + * @ignore + */ + + }, { + key: "start", + value: function start() { + var canvas = this.getCanvas(); + canvas.selection = false; + canvas.on('mouse:down', this._handlers.mousedown); + } + /** + * End to draw the icon on canvas + * @ignore + */ + + }, { + key: "end", + value: function end() { + var canvas = this.getCanvas(); + canvas.selection = true; + canvas.off({ + 'mouse:down': this._handlers.mousedown + }); + } + /** + * Add icon + * @param {string} type - Icon type + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {string} [options.left] - Icon x position + * @param {string} [options.top] - Icon y position + * @returns {Promise} + */ + + }, { + key: "add", + value: function add(type, options) { + var _this2 = this; + + return new (promise_default())(function (resolve, reject) { + var canvas = _this2.getCanvas(); + + var path = _this2._pathMap[type]; + var selectionStyle = fObjectOptions.SELECTION_STYLE; + var icon = path ? _this2._createIcon(path) : null; + _this2._icon = icon; + + if (!icon) { + reject(rejectMessages.invalidParameters); + } + + icon.set(extend_default()({ + type: 'icon', + fill: _this2._oColor + }, selectionStyle, options, _this2.graphics.controlStyle)); + canvas.add(icon).setActiveObject(icon); + resolve(_this2.graphics.createObjectProperties(icon)); + }); + } + /** + * Register icon paths + * @param {{key: string, value: string}} pathInfos - Path infos + */ + + }, { + key: "registerPaths", + value: function registerPaths(pathInfos) { + var _this3 = this; + + forEach_default()(pathInfos, function (path, type) { + _this3._pathMap[type] = path; + }, this); + } + /** + * Set icon object color + * @param {string} color - Color to set + * @param {fabric.Path}[obj] - Current activated path object + */ + + }, { + key: "setColor", + value: function setColor(color, obj) { + this._oColor = color; + + if (obj && obj.get('type') === 'icon') { + obj.set({ + fill: this._oColor + }); + this.getCanvas().renderAll(); + } + } + /** + * Get icon color + * @param {fabric.Path}[obj] - Current activated path object + * @returns {string} color + */ + + }, { + key: "getColor", + value: function getColor(obj) { + return fill_default()(obj); + } + /** + * Create icon object + * @param {string} path - Path value to create icon + * @returns {fabric.Path} Path object + */ + + }, { + key: "_createIcon", + value: function _createIcon(path) { + return new fabric.fabric.Path(path); + } + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseDown", + value: function _onFabricMouseDown(fEvent) { + var _this4 = this; + + var canvas = this.getCanvas(); + this._startPoint = canvas.getPointer(fEvent.e); + var _this$_startPoint = this._startPoint, + left = _this$_startPoint.x, + top = _this$_startPoint.y; + this.add(this._type, { + left: left, + top: top, + fill: this._iconColor + }).then(function () { + _this4.fire(eventNames.ADD_OBJECT, _this4.graphics.createObjectProperties(_this4._icon)); + + canvas.on('mouse:move', _this4._handlers.mousemove); + canvas.on('mouse:up', _this4._handlers.mouseup); + }); + } + /** + * MouseMove event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseMove", + value: function _onFabricMouseMove(fEvent) { + var canvas = this.getCanvas(); + + if (!this._icon) { + return; + } + + var moveOriginPointer = canvas.getPointer(fEvent.e); + var scaleX = (moveOriginPointer.x - this._startPoint.x) / this._icon.width; + var scaleY = (moveOriginPointer.y - this._startPoint.y) / this._icon.height; + + this._icon.set({ + scaleX: Math.abs(scaleX * 2), + scaleY: Math.abs(scaleY * 2) + }); + + this._icon.setCoords(); + + canvas.renderAll(); + } + /** + * MouseUp event handler on canvas + * @private + */ + + }, { + key: "_onFabricMouseUp", + value: function _onFabricMouseUp() { + var canvas = this.getCanvas(); + this.fire(eventNames.OBJECT_ADDED, this.graphics.createObjectProperties(this._icon)); + this._icon = null; + canvas.off('mouse:down', this._handlers.mousedown); + canvas.off('mouse:move', this._handlers.mousemove); + canvas.off('mouse:up', this._handlers.mouseup); + } + }]); + + return Icon; +}(component); + +/* harmony default export */ var component_icon = (icon_Icon); +;// CONCATENATED MODULE: ./src/js/extension/mask.js + +/** + * Mask object + * @class Mask + * @extends {fabric.Image.filters.BlendImage} + * @ignore + */ + +var mask_Mask = fabric.fabric.util.createClass(fabric.fabric.Image.filters.BlendImage, +/** @lends Mask.prototype */ +{ + /** + * Apply filter to canvas element + * @param {Object} pipelineState - Canvas element to apply filter + * @override + */ + applyTo: function applyTo(pipelineState) { + if (!this.mask) { + return; + } + + var canvas = pipelineState.canvasEl; + var width = canvas.width, + height = canvas.height; + + var maskCanvasEl = this._createCanvasOfMask(width, height); + + var ctx = canvas.getContext('2d'); + var maskCtx = maskCanvasEl.getContext('2d'); + var imageData = ctx.getImageData(0, 0, width, height); + + this._drawMask(maskCtx, canvas, ctx); + + this._mapData(maskCtx, imageData, width, height); + + pipelineState.imageData = imageData; + }, + + /** + * Create canvas of mask image + * @param {number} width - Width of main canvas + * @param {number} height - Height of main canvas + * @returns {HTMLElement} Canvas element + * @private + */ + _createCanvasOfMask: function _createCanvasOfMask(width, height) { + var maskCanvasEl = fabric.fabric.util.createCanvasElement(); + maskCanvasEl.width = width; + maskCanvasEl.height = height; + return maskCanvasEl; + }, + + /** + * Draw mask image on canvas element + * @param {Object} maskCtx - Context of mask canvas + * @private + */ + _drawMask: function _drawMask(maskCtx) { + var mask = this.mask; + var maskImg = mask.getElement(); + var angle = mask.angle, + left = mask.left, + scaleX = mask.scaleX, + scaleY = mask.scaleY, + top = mask.top; + maskCtx.save(); + maskCtx.translate(left, top); + maskCtx.rotate(angle * Math.PI / 180); + maskCtx.scale(scaleX, scaleY); + maskCtx.drawImage(maskImg, -maskImg.width / 2, -maskImg.height / 2); + maskCtx.restore(); + }, + + /** + * Map mask image data to source image data + * @param {Object} maskCtx - Context of mask canvas + * @param {Object} imageData - Data of source image + * @param {number} width - Width of main canvas + * @param {number} height - Height of main canvas + * @private + */ + _mapData: function _mapData(maskCtx, imageData, width, height) { + var data = imageData.data, + imgHeight = imageData.height, + imgWidth = imageData.width; + var sourceData = data; + var len = imgWidth * imgHeight * 4; + var maskData = maskCtx.getImageData(0, 0, width, height).data; + + for (var i = 0; i < len; i += 4) { + sourceData[i + 3] = maskData[i]; // adjust value of alpha data + } + } +}); +/* harmony default export */ var extension_mask = (mask_Mask); +;// CONCATENATED MODULE: ./src/js/extension/sharpen.js + +/** + * Sharpen object + * @class Sharpen + * @extends {fabric.Image.filters.Convolute} + * @ignore + */ + +var Sharpen = fabric.fabric.util.createClass(fabric.fabric.Image.filters.Convolute, +/** @lends Convolute.prototype */ +{ + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Sharpen', + + /** + * constructor + * @override + */ + initialize: function initialize() { + this.matrix = [0, -1, 0, -1, 5, -1, 0, -1, 0]; + } +}); +/* harmony default export */ var sharpen = (Sharpen); +;// CONCATENATED MODULE: ./src/js/extension/emboss.js + +/** + * Emboss object + * @class Emboss + * @extends {fabric.Image.filters.Convolute} + * @ignore + */ + +var Emboss = fabric.fabric.util.createClass(fabric.fabric.Image.filters.Convolute, +/** @lends Convolute.prototype */ +{ + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Emboss', + + /** + * constructor + * @override + */ + initialize: function initialize() { + this.matrix = [1, 1, 1, 1, 0.7, -1, -1, -1, -1]; + } +}); +/* harmony default export */ var emboss = (Emboss); +;// CONCATENATED MODULE: ./src/js/extension/colorFilter.js + +/** + * ColorFilter object + * @class ColorFilter + * @extends {fabric.Image.filters.BaseFilter} + * @ignore + */ + +var ColorFilter = fabric.fabric.util.createClass(fabric.fabric.Image.filters.BaseFilter, +/** @lends BaseFilter.prototype */ +{ + /** + * Filter type + * @param {String} type + * @default + */ + type: 'ColorFilter', + + /** + * Constructor + * @member fabric.Image.filters.ColorFilter.prototype + * @param {Object} [options] Options object + * @param {Number} [options.color='#FFFFFF'] Value of color (0...255) + * @param {Number} [options.threshold=45] Value of threshold (0...255) + * @override + */ + initialize: function initialize(options) { + if (!options) { + options = {}; + } + + this.color = options.color || '#FFFFFF'; + this.threshold = options.threshold || 45; + this.x = options.x || null; + this.y = options.y || null; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvas Canvas object passed by fabric + */ + // eslint-disable-next-line complexity + applyTo: function applyTo(canvas) { + var canvasEl = canvas.canvasEl; + var context = canvasEl.getContext('2d'); + var imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height); + var data = imageData.data; + var threshold = this.threshold; + var filterColor = fabric.fabric.Color.sourceFromHex(this.color); + var i, len; + + if (this.x && this.y) { + filterColor = this._getColor(imageData, this.x, this.y); + } + + for (i = 0, len = data.length; i < len; i += 4) { + if (this._isOutsideThreshold(data[i], filterColor[0], threshold) || this._isOutsideThreshold(data[i + 1], filterColor[1], threshold) || this._isOutsideThreshold(data[i + 2], filterColor[2], threshold)) { + continue; + } + + data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Check color if it is within threshold + * @param {Number} color1 source color + * @param {Number} color2 filtering color + * @param {Number} threshold threshold + * @returns {boolean} true if within threshold or false + */ + _isOutsideThreshold: function _isOutsideThreshold(color1, color2, threshold) { + var diff = color1 - color2; + return Math.abs(diff) > threshold; + }, + + /** + * Get color at (x, y) + * @param {Object} imageData of canvas + * @param {Number} x left position + * @param {Number} y top position + * @returns {Array} color array + */ + _getColor: function _getColor(imageData, x, y) { + var color = [0, 0, 0, 0]; + var data = imageData.data, + width = imageData.width; + var bytes = 4; + var position = (width * y + x) * bytes; + color[0] = data[position]; + color[1] = data[position + 1]; + color[2] = data[position + 2]; + color[3] = data[position + 3]; + return color; + } +}); +/* harmony default export */ var colorFilter = (ColorFilter); +;// CONCATENATED MODULE: ./src/js/component/filter.js + + + + + + + + + + +function component_filter_createSuper(Derived) { var hasNativeReflectConstruct = component_filter_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function component_filter_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + + + + + +var filters = fabric.fabric.Image.filters; +filters.Mask = extension_mask; +filters.Sharpen = sharpen; +filters.Emboss = emboss; +filters.ColorFilter = colorFilter; +/** + * Filter + * @class Filter + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + +var filter_Filter = /*#__PURE__*/function (_Component) { + _inherits(Filter, _Component); + + var _super = component_filter_createSuper(Filter); + + function Filter(graphics) { + _classCallCheck(this, Filter); + + return _super.call(this, componentNames.FILTER, graphics); + } + /** + * Add filter to source image (a specific filter is added on fabric.js) + * @param {string} type - Filter type + * @param {Object} [options] - Options of filter + * @returns {Promise} + */ + + + _createClass(Filter, [{ + key: "add", + value: function add(type, options) { + var _this = this; + + return new (promise_default())(function (resolve, reject) { + var sourceImg = _this._getSourceImage(); + + var canvas = _this.getCanvas(); + + var imgFilter = _this._getFilter(sourceImg, type); + + if (!imgFilter) { + imgFilter = _this._createFilter(sourceImg, type, options); + } + + if (!imgFilter) { + reject(rejectMessages.invalidParameters); + } + + _this._changeFilterValues(imgFilter, options); + + _this._apply(sourceImg, function () { + canvas.renderAll(); + resolve({ + type: type, + action: 'add', + options: options + }); + }); + }); + } + /** + * Remove filter to source image + * @param {string} type - Filter type + * @returns {Promise} + */ + + }, { + key: "remove", + value: function remove(type) { + var _this2 = this; + + return new (promise_default())(function (resolve, reject) { + var sourceImg = _this2._getSourceImage(); + + var canvas = _this2.getCanvas(); + + var options = _this2.getOptions(type); + + if (!sourceImg.filters.length) { + reject(rejectMessages.unsupportedOperation); + } + + _this2._removeFilter(sourceImg, type); + + _this2._apply(sourceImg, function () { + canvas.renderAll(); + resolve({ + type: type, + action: 'remove', + options: options + }); + }); + }); + } + /** + * Whether this has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + + }, { + key: "hasFilter", + value: function hasFilter(type) { + return !!this._getFilter(this._getSourceImage(), type); + } + /** + * Get a filter options + * @param {string} type - Filter type + * @returns {Object} filter options or null if there is no that filter + */ + + }, { + key: "getOptions", + value: function getOptions(type) { + var sourceImg = this._getSourceImage(); + + var imgFilter = this._getFilter(sourceImg, type); + + if (!imgFilter) { + return null; + } + + return extend_default()({}, imgFilter.options); + } + /** + * Change filter values + * @param {Object} imgFilter object of filter + * @param {Object} options object + * @private + */ + + }, { + key: "_changeFilterValues", + value: function _changeFilterValues(imgFilter, options) { + forEach_default()(options, function (value, key) { + if (!isUndefined_default()(imgFilter[key])) { + imgFilter[key] = value; + } + }); + forEach_default()(imgFilter.options, function (value, key) { + if (!isUndefined_default()(options[key])) { + imgFilter.options[key] = options[key]; + } + }); + } + /** + * Apply filter + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {function} callback - Executed function after applying filter + * @private + */ + + }, { + key: "_apply", + value: function _apply(sourceImg, callback) { + sourceImg.filters.push(); + var result = sourceImg.applyFilters(); + + if (result) { + callback(); + } + } + /** + * Get source image on canvas + * @returns {fabric.Image} Current source image on canvas + * @private + */ + + }, { + key: "_getSourceImage", + value: function _getSourceImage() { + return this.getCanvasImage(); + } + /** + * Create filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @param {Object} [options] - Options of filter + * @returns {Object} Fabric object of filter + * @private + */ + + }, { + key: "_createFilter", + value: function _createFilter(sourceImg, type, options) { + var filterObj; // capitalize first letter for matching with fabric image filter name + + var fabricType = this._getFabricFilterType(type); + + var ImageFilter = fabric.fabric.Image.filters[fabricType]; + + if (ImageFilter) { + filterObj = new ImageFilter(options); + filterObj.options = options; + sourceImg.filters.push(filterObj); + } + + return filterObj; + } + /** + * Get applied filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @returns {Object} Fabric object of filter + * @private + */ + + }, { + key: "_getFilter", + value: function _getFilter(sourceImg, type) { + var imgFilter = null; + + if (sourceImg) { + var fabricType = this._getFabricFilterType(type); + + var length = sourceImg.filters.length; + var item, i; + + for (i = 0; i < length; i += 1) { + item = sourceImg.filters[i]; + + if (item.type === fabricType) { + imgFilter = item; + break; + } + } + } + + return imgFilter; + } + /** + * Remove applied filter instance + * @param {fabric.Image} sourceImg - Source image to apply filter + * @param {string} type - Filter type + * @private + */ + + }, { + key: "_removeFilter", + value: function _removeFilter(sourceImg, type) { + var _context; + + var fabricType = this._getFabricFilterType(type); + + sourceImg.filters = filter_default()(_context = sourceImg.filters).call(_context, function (value) { + return value.type !== fabricType; + }); + } + /** + * Change filter class name to fabric's, especially capitalizing first letter + * @param {string} type - Filter type + * @example + * 'grayscale' -> 'Grayscale' + * @returns {string} Fabric filter class name + */ + + }, { + key: "_getFabricFilterType", + value: function _getFabricFilterType(type) { + return type.charAt(0).toUpperCase() + slice_default()(type).call(type, 1); + } + }]); + + return Filter; +}(component); + +/* harmony default export */ var component_filter = (filter_Filter); +// EXTERNAL MODULE: ./src/js/helper/shapeResizeHelper.js +var shapeResizeHelper = __webpack_require__(1801); +var shapeResizeHelper_default = /*#__PURE__*/__webpack_require__.n(shapeResizeHelper); +;// CONCATENATED MODULE: ./src/js/helper/shapeFilterFillHelper.js + + + + + + + + + + + +var FILTER_OPTION_MAP = { + pixelate: 'blocksize', + blur: 'blur' +}; +var POSITION_DIMENSION_MAP = { + x: 'width', + y: 'height' +}; +var FILTER_NAME_VALUE_MAP = flipObject(FILTER_OPTION_MAP); +/** + * Cached canvas image element for fill image + * @type {boolean} + * @private + */ + +var cachedCanvasImageElement = null; +/** + * Get background image of fill + * @param {fabric.Object} shapeObj - Shape object + * @returns {fabric.Image} + * @private + */ + +function getFillImageFromShape(shapeObj) { + var _getCustomProperty = getCustomProperty(shapeObj, 'patternSourceCanvas'), + patternSourceCanvas = _getCustomProperty.patternSourceCanvas; + + var _patternSourceCanvas$ = patternSourceCanvas.getObjects(), + _patternSourceCanvas$2 = _slicedToArray(_patternSourceCanvas$, 1), + fillImage = _patternSourceCanvas$2[0]; + + return fillImage; +} +/** + * Reset the image position in the filter type fill area. + * @param {fabric.Object} shapeObj - Shape object + * @private + */ + +function rePositionFilterTypeFillImage(shapeObj) { + var angle = shapeObj.angle, + flipX = shapeObj.flipX, + flipY = shapeObj.flipY; + var fillImage = getFillImageFromShape(shapeObj); + var rotatedShapeCornerDimension = getRotatedDimension(shapeObj); + var right = rotatedShapeCornerDimension.right, + bottom = rotatedShapeCornerDimension.bottom; + var width = rotatedShapeCornerDimension.width, + height = rotatedShapeCornerDimension.height; + var diffLeft = (width - shapeObj.width) / 2; + var diffTop = (height - shapeObj.height) / 2; + var cropX = shapeObj.left - shapeObj.width / 2 - diffLeft; + var cropY = shapeObj.top - shapeObj.height / 2 - diffTop; + var left = width / 2 - diffLeft; + var top = height / 2 - diffTop; + var fillImageMaxSize = Math.max(width, height) + Math.max(diffLeft, diffTop); + + var _calculateFillImageDi = calculateFillImageDimensionOutsideCanvas({ + shapeObj: shapeObj, + left: left, + top: top, + width: width, + height: height, + cropX: cropX, + cropY: cropY, + flipX: flipX, + flipY: flipY, + right: right, + bottom: bottom + }); + + var _calculateFillImageDi2 = _slicedToArray(_calculateFillImageDi, 4); + + left = _calculateFillImageDi2[0]; + top = _calculateFillImageDi2[1]; + width = _calculateFillImageDi2[2]; + height = _calculateFillImageDi2[3]; + fillImage.set({ + angle: flipX === flipY ? -angle : angle, + left: left, + top: top, + width: width, + height: height, + cropX: cropX, + cropY: cropY, + flipX: flipX, + flipY: flipY + }); + setCustomProperty(fillImage, { + fillImageMaxSize: fillImageMaxSize + }); +} +/** + * Make filter option from fabric image + * @param {fabric.Image} imageObject - fabric image object + * @returns {object} + */ + +function makeFilterOptionFromFabricImage(imageObject) { + var _context; + + return map_default()(_context = imageObject.filters).call(_context, function (filter) { + var _Object$keys = keys_default()(filter), + _Object$keys2 = _slicedToArray(_Object$keys, 1), + key = _Object$keys2[0]; + + return _defineProperty({}, FILTER_NAME_VALUE_MAP[key], filter[key]); + }); +} +/** + * Calculate fill image position and size for out of Canvas + * @param {Object} options - options for position dimension calculate + * @param {fabric.Object} shapeObj - shape object + * @param {number} left - original left position + * @param {number} top - original top position + * @param {number} width - image width + * @param {number} height - image height + * @param {number} cropX - image cropX + * @param {number} cropY - image cropY + * @param {boolean} flipX - shape flipX + * @param {boolean} flipY - shape flipY + * @returns {Object} + */ + +function calculateFillImageDimensionOutsideCanvas(_ref2) { + var shapeObj = _ref2.shapeObj, + left = _ref2.left, + top = _ref2.top, + width = _ref2.width, + height = _ref2.height, + cropX = _ref2.cropX, + cropY = _ref2.cropY, + flipX = _ref2.flipX, + flipY = _ref2.flipY, + right = _ref2.right, + bottom = _ref2.bottom; + + var overflowAreaPositionFixer = function overflowAreaPositionFixer(type, outDistance, imageLeft, imageTop) { + return calculateDistanceOverflowPart({ + type: type, + outDistance: outDistance, + shapeObj: shapeObj, + flipX: flipX, + flipY: flipY, + left: imageLeft, + top: imageTop + }); + }; + + var originalWidth = width, + originalHeight = height; + + var _calculateDimensionLe = calculateDimensionLeftTopEdge(overflowAreaPositionFixer, { + left: left, + top: top, + width: width, + height: height, + cropX: cropX, + cropY: cropY + }); + + var _calculateDimensionLe2 = _slicedToArray(_calculateDimensionLe, 4); + + left = _calculateDimensionLe2[0]; + top = _calculateDimensionLe2[1]; + width = _calculateDimensionLe2[2]; + height = _calculateDimensionLe2[3]; + + var _calculateDimensionRi = calculateDimensionRightBottomEdge(overflowAreaPositionFixer, { + left: left, + top: top, + insideCanvasRealImageWidth: width, + insideCanvasRealImageHeight: height, + right: right, + bottom: bottom, + cropX: cropX, + cropY: cropY, + originalWidth: originalWidth, + originalHeight: originalHeight + }); + + var _calculateDimensionRi2 = _slicedToArray(_calculateDimensionRi, 4); + + left = _calculateDimensionRi2[0]; + top = _calculateDimensionRi2[1]; + width = _calculateDimensionRi2[2]; + height = _calculateDimensionRi2[3]; + return [left, top, width, height]; +} +/** + * Calculate fill image position and size for for right bottom edge + * @param {Function} overflowAreaPositionFixer - position fixer + * @param {Object} options - options for position dimension calculate + * @param {fabric.Object} shapeObj - shape object + * @param {number} left - original left position + * @param {number} top - original top position + * @param {number} width - image width + * @param {number} height - image height + * @param {number} right - image right + * @param {number} bottom - image bottom + * @param {number} cropX - image cropX + * @param {number} cropY - image cropY + * @param {boolean} originalWidth - image original width + * @param {boolean} originalHeight - image original height + * @returns {Object} + */ + + +function calculateDimensionRightBottomEdge(overflowAreaPositionFixer, _ref3) { + var left = _ref3.left, + top = _ref3.top, + insideCanvasRealImageWidth = _ref3.insideCanvasRealImageWidth, + insideCanvasRealImageHeight = _ref3.insideCanvasRealImageHeight, + right = _ref3.right, + bottom = _ref3.bottom, + cropX = _ref3.cropX, + cropY = _ref3.cropY, + originalWidth = _ref3.originalWidth, + originalHeight = _ref3.originalHeight; + var width = insideCanvasRealImageWidth, + height = insideCanvasRealImageHeight; + var _cachedCanvasImageEle = cachedCanvasImageElement, + canvasWidth = _cachedCanvasImageEle.width, + canvasHeight = _cachedCanvasImageEle.height; + + if (right > canvasWidth && cropX > 0) { + width = originalWidth - Math.abs(right - canvasWidth); + } + + if (bottom > canvasHeight && cropY > 0) { + height = originalHeight - Math.abs(bottom - canvasHeight); + } + + var diff = { + x: (insideCanvasRealImageWidth - width) / 2, + y: (insideCanvasRealImageHeight - height) / 2 + }; + forEach_default()(['x', 'y'], function (type) { + var cropDistance2 = diff[type]; + + if (cropDistance2 > 0) { + var _overflowAreaPosition = overflowAreaPositionFixer(type, cropDistance2, left, top); + + var _overflowAreaPosition2 = _slicedToArray(_overflowAreaPosition, 2); + + left = _overflowAreaPosition2[0]; + top = _overflowAreaPosition2[1]; + } + }); + return [left, top, width, height]; +} +/** + * Calculate fill image position and size for for left top + * @param {Function} overflowAreaPositionFixer - position fixer + * @param {Object} options - options for position dimension calculate + * @param {fabric.Object} shapeObj - shape object + * @param {number} left - original left position + * @param {number} top - original top position + * @param {number} width - image width + * @param {number} height - image height + * @param {number} cropX - image cropX + * @param {number} cropY - image cropY + * @returns {Object} + */ + + +function calculateDimensionLeftTopEdge(overflowAreaPositionFixer, _ref4) { + var left = _ref4.left, + top = _ref4.top, + width = _ref4.width, + height = _ref4.height, + cropX = _ref4.cropX, + cropY = _ref4.cropY; + var dimension = { + width: width, + height: height + }; + forEach_default()(['x', 'y'], function (type) { + var cropDistance = type === 'x' ? cropX : cropY; + var compareSize = dimension[POSITION_DIMENSION_MAP[type]]; + var standardSize = cachedCanvasImageElement[POSITION_DIMENSION_MAP[type]]; + + if (compareSize > standardSize) { + var outDistance = (compareSize - standardSize) / 2; + dimension[POSITION_DIMENSION_MAP[type]] = standardSize; + + var _overflowAreaPosition3 = overflowAreaPositionFixer(type, outDistance, left, top); + + var _overflowAreaPosition4 = _slicedToArray(_overflowAreaPosition3, 2); + + left = _overflowAreaPosition4[0]; + top = _overflowAreaPosition4[1]; + } + + if (cropDistance < 0) { + var _overflowAreaPosition5 = overflowAreaPositionFixer(type, cropDistance, left, top); + + var _overflowAreaPosition6 = _slicedToArray(_overflowAreaPosition5, 2); + + left = _overflowAreaPosition6[0]; + top = _overflowAreaPosition6[1]; + } + }); + return [left, top, dimension.width, dimension.height]; +} +/** + * Make fill property of dynamic pattern type + * @param {fabric.Image} canvasImage - canvas background image + * @param {Array} filterOption - filter option + * @param {fabric.StaticCanvas} patternSourceCanvas - fabric static canvas + * @returns {Object} + */ + + +function makeFillPatternForFilter(canvasImage, filterOption, patternSourceCanvas) { + var copiedCanvasElement = getCachedCanvasImageElement(canvasImage); + var fillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); + patternSourceCanvas.add(fillImage); + var fabricProperty = { + fill: new fabric.fabric.Pattern({ + source: patternSourceCanvas.getElement(), + repeat: 'no-repeat' + }) + }; + setCustomProperty(fabricProperty, { + patternSourceCanvas: patternSourceCanvas + }); + return fabricProperty; +} +/** + * Reset fill pattern canvas + * @param {fabric.StaticCanvas} patternSourceCanvas - fabric static canvas + */ + +function resetFillPatternCanvas(patternSourceCanvas) { + var _patternSourceCanvas$3 = patternSourceCanvas.getObjects(), + _patternSourceCanvas$4 = _slicedToArray(_patternSourceCanvas$3, 1), + innerImage = _patternSourceCanvas$4[0]; + + var _getCustomProperty2 = getCustomProperty(innerImage, 'fillImageMaxSize'), + fillImageMaxSize = _getCustomProperty2.fillImageMaxSize; + + fillImageMaxSize = Math.max(1, fillImageMaxSize); + patternSourceCanvas.setDimensions({ + width: fillImageMaxSize, + height: fillImageMaxSize + }); + patternSourceCanvas.renderAll(); +} +/** + * Remake filter pattern image source + * @param {fabric.Object} shapeObj - Shape object + * @param {fabric.Image} canvasImage - canvas background image + */ + +function reMakePatternImageSource(shapeObj, canvasImage) { + var _getCustomProperty3 = getCustomProperty(shapeObj, 'patternSourceCanvas'), + patternSourceCanvas = _getCustomProperty3.patternSourceCanvas; + + var _patternSourceCanvas$5 = patternSourceCanvas.getObjects(), + _patternSourceCanvas$6 = _slicedToArray(_patternSourceCanvas$5, 1), + fillImage = _patternSourceCanvas$6[0]; + + var filterOption = makeFilterOptionFromFabricImage(fillImage); + patternSourceCanvas.remove(fillImage); + var copiedCanvasElement = getCachedCanvasImageElement(canvasImage, true); + var newFillImage = makeFillImage(copiedCanvasElement, canvasImage.angle, filterOption); + patternSourceCanvas.add(newFillImage); +} +/** + * Calculate a point line outside the canvas. + * @param {fabric.Image} canvasImage - canvas background image + * @param {boolean} reset - default is false + * @returns {HTMLImageElement} + */ + +function getCachedCanvasImageElement(canvasImage) { + var reset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!cachedCanvasImageElement || reset) { + cachedCanvasImageElement = canvasImage.toCanvasElement(); + } + + return cachedCanvasImageElement; +} +/** + * Calculate fill image position for out of Canvas + * @param {string} type - 'x' or 'y' + * @param {fabric.Object} shapeObj - shape object + * @param {number} outDistance - distance away + * @param {number} left - original left position + * @param {number} top - original top position + * @returns {Array} + */ + +function calculateDistanceOverflowPart(_ref5) { + var type = _ref5.type, + shapeObj = _ref5.shapeObj, + outDistance = _ref5.outDistance, + left = _ref5.left, + top = _ref5.top, + flipX = _ref5.flipX, + flipY = _ref5.flipY; + var shapePointNavigation = getShapeEdgePoint(shapeObj); + var shapeNeighborPointNavigation = [[1, 2], [0, 3], [0, 3], [1, 2]]; + var linePointsOutsideCanvas = calculateLinePointsOutsideCanvas(type, shapePointNavigation, shapeNeighborPointNavigation); + var reatAngles = calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePointsOutsideCanvas); + var startPointIndex = linePointsOutsideCanvas.startPointIndex; + var diffPosition = getReversePositionForFlip({ + outDistance: outDistance, + startPointIndex: startPointIndex, + flipX: flipX, + flipY: flipY, + reatAngles: reatAngles + }); + return [left + diffPosition.left, top + diffPosition.top]; +} +/** + * Calculate fill image position for out of Canvas + * @param {number} outDistance - distance away + * @param {boolean} flipX - flip x statux + * @param {boolean} flipY - flip y statux + * @param {Array} reatAngles - Line angle of the rectangle vertex. + * @returns {Object} diffPosition + */ + + +function getReversePositionForFlip(_ref6) { + var outDistance = _ref6.outDistance, + startPointIndex = _ref6.startPointIndex, + flipX = _ref6.flipX, + flipY = _ref6.flipY, + reatAngles = _ref6.reatAngles; + var rotationChangePoint1 = outDistance * Math.cos(reatAngles[0] * Math.PI / 180); + var rotationChangePoint2 = outDistance * Math.cos(reatAngles[1] * Math.PI / 180); + var isForward = startPointIndex === 2 || startPointIndex === 3; + var diffPosition = { + top: isForward ? rotationChangePoint1 : rotationChangePoint2, + left: isForward ? rotationChangePoint2 : rotationChangePoint1 + }; + + if (isReverseLeftPositionForFlip(startPointIndex, flipX, flipY)) { + diffPosition.left = diffPosition.left * -1; + } + + if (isReverseTopPositionForFlip(startPointIndex, flipX, flipY)) { + diffPosition.top = diffPosition.top * -1; + } + + return diffPosition; +} +/** + * Calculate a point line outside the canvas. + * @param {string} type - 'x' or 'y' + * @param {Array} shapePointNavigation - shape edge positions + * @param {Object} shapePointNavigation.lefttop - left top position + * @param {Object} shapePointNavigation.righttop - right top position + * @param {Object} shapePointNavigation.leftbottom - lefttop position + * @param {Object} shapePointNavigation.rightbottom - rightbottom position + * @param {Array} shapeNeighborPointNavigation - Array to find adjacent edges. + * @returns {Object} + */ + + +function calculateLinePointsOutsideCanvas(type, shapePointNavigation, shapeNeighborPointNavigation) { + var minimumPoint = 0; + var minimumPointIndex = 0; + forEach_default()(shapePointNavigation, function (point, index) { + if (point[type] < minimumPoint) { + minimumPoint = point[type]; + minimumPointIndex = index; + } + }); + + var _shapeNeighborPointNa = _slicedToArray(shapeNeighborPointNavigation[minimumPointIndex], 2), + endPointIndex1 = _shapeNeighborPointNa[0], + endPointIndex2 = _shapeNeighborPointNa[1]; + + return { + startPointIndex: minimumPointIndex, + endPointIndex1: endPointIndex1, + endPointIndex2: endPointIndex2 + }; +} +/** + * Calculate a point line outside the canvas. + * @param {string} type - 'x' or 'y' + * @param {Array} shapePointNavigation - shape edge positions + * @param {object} shapePointNavigation.lefttop - left top position + * @param {object} shapePointNavigation.righttop - right top position + * @param {object} shapePointNavigation.leftbottom - lefttop position + * @param {object} shapePointNavigation.rightbottom - rightbottom position + * @param {Object} linePointsOfOneVertexIndex - Line point of one vertex + * @param {Object} linePointsOfOneVertexIndex.startPoint - start point index + * @param {Object} linePointsOfOneVertexIndex.endPointIndex1 - end point index + * @param {Object} linePointsOfOneVertexIndex.endPointIndex2 - end point index + * @returns {Object} + */ + + +function calculateLineAngleOfOutsideCanvas(type, shapePointNavigation, linePointsOfOneVertexIndex) { + var _context2; + + var startPointIndex = linePointsOfOneVertexIndex.startPointIndex, + endPointIndex1 = linePointsOfOneVertexIndex.endPointIndex1, + endPointIndex2 = linePointsOfOneVertexIndex.endPointIndex2; + var horizontalVerticalAngle = type === 'x' ? 180 : 270; + return map_default()(_context2 = [endPointIndex1, endPointIndex2]).call(_context2, function (pointIndex) { + var startPoint = shapePointNavigation[startPointIndex]; + var endPoint = shapePointNavigation[pointIndex]; + var diffY = startPoint.y - endPoint.y; + var diffX = startPoint.x - endPoint.x; + return Math.atan2(diffY, diffX) * 180 / Math.PI - horizontalVerticalAngle; + }); +} +/* eslint-disable complexity */ + +/** + * Calculate a point line outside the canvas for horizontal. + * @param {number} startPointIndex - start point index + * @param {boolean} flipX - flip x statux + * @param {boolean} flipY - flip y statux + * @returns {boolean} flipY - flip y statux + */ + + +function isReverseLeftPositionForFlip(startPointIndex, flipX, flipY) { + return (!flipX && flipY || !flipX && !flipY) && startPointIndex === 0 || (flipX && flipY || flipX && !flipY) && startPointIndex === 1 || (!flipX && !flipY || !flipX && flipY) && startPointIndex === 2 || (flipX && !flipY || flipX && flipY) && startPointIndex === 3; +} +/* eslint-enable complexity */ + +/* eslint-disable complexity */ + +/** + * Calculate a point line outside the canvas for vertical. + * @param {number} startPointIndex - start point index + * @param {boolean} flipX - flip x statux + * @param {boolean} flipY - flip y statux + * @returns {boolean} flipY - flip y statux + */ + + +function isReverseTopPositionForFlip(startPointIndex, flipX, flipY) { + return (flipX && !flipY || !flipX && !flipY) && startPointIndex === 0 || (!flipX && !flipY || flipX && !flipY) && startPointIndex === 1 || (flipX && flipY || !flipX && flipY) && startPointIndex === 2 || (!flipX && flipY || flipX && flipY) && startPointIndex === 3; +} +/* eslint-enable complexity */ + +/** + * Shape edge points + * @param {fabric.Object} shapeObj - Selected shape object on canvas + * @returns {Array} shapeEdgePoint - shape edge positions + */ + + +function getShapeEdgePoint(shapeObj) { + return [shapeObj.getPointByOrigin('left', 'top'), shapeObj.getPointByOrigin('right', 'top'), shapeObj.getPointByOrigin('left', 'bottom'), shapeObj.getPointByOrigin('right', 'bottom')]; +} +/** + * Rotated shape dimension + * @param {fabric.Object} shapeObj - Shape object + * @returns {Object} Rotated shape dimension + */ + + +function getRotatedDimension(shapeObj) { + var _getShapeEdgePoint = getShapeEdgePoint(shapeObj), + _getShapeEdgePoint2 = _slicedToArray(_getShapeEdgePoint, 4), + _getShapeEdgePoint2$ = _getShapeEdgePoint2[0], + ax = _getShapeEdgePoint2$.x, + ay = _getShapeEdgePoint2$.y, + _getShapeEdgePoint2$2 = _getShapeEdgePoint2[1], + bx = _getShapeEdgePoint2$2.x, + by = _getShapeEdgePoint2$2.y, + _getShapeEdgePoint2$3 = _getShapeEdgePoint2[2], + cx = _getShapeEdgePoint2$3.x, + cy = _getShapeEdgePoint2$3.y, + _getShapeEdgePoint2$4 = _getShapeEdgePoint2[3], + dx = _getShapeEdgePoint2$4.x, + dy = _getShapeEdgePoint2$4.y; + + var left = Math.min(ax, bx, cx, dx); + var top = Math.min(ay, by, cy, dy); + var right = Math.max(ax, bx, cx, dx); + var bottom = Math.max(ay, by, cy, dy); + return { + left: left, + top: top, + right: right, + bottom: bottom, + width: right - left, + height: bottom - top + }; +} +/** + * Make fill image + * @param {HTMLImageElement} copiedCanvasElement - html image element + * @param {number} currentCanvasImageAngle - current canvas angle + * @param {Array} filterOption - filter option + * @returns {fabric.Image} + * @private + */ + + +function makeFillImage(copiedCanvasElement, currentCanvasImageAngle, filterOption) { + var _context3; + + var fillImage = new fabric.fabric.Image(copiedCanvasElement); + forEach_default()(extend_default().apply(void 0, concat_default()(_context3 = [{}]).call(_context3, _toConsumableArray(filterOption))), function (value, key) { + var fabricFilterClassName = capitalizeString(key); + var filter = new fabric.fabric.Image.filters[fabricFilterClassName](_defineProperty({}, FILTER_OPTION_MAP[key], value)); + fillImage.filters.push(filter); + }); + fillImage.applyFilters(); + setCustomProperty(fillImage, { + originalAngle: currentCanvasImageAngle, + fillImageMaxSize: Math.max(fillImage.width, fillImage.height) + }); + shapeResizeHelper_default().adjustOriginToCenter(fillImage); + return fillImage; +} +;// CONCATENATED MODULE: ./src/js/component/shape.js + + + + + + + + +function shape_createSuper(Derived) { var hasNativeReflectConstruct = shape_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function shape_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + + + + + + + + +var SHAPE_INIT_OPTIONS = extend_default()({ + strokeWidth: 1, + stroke: '#000000', + fill: '#ffffff', + width: 1, + height: 1, + rx: 0, + ry: 0 +}, SHAPE_DEFAULT_OPTIONS); +var DEFAULT_TYPE = 'rect'; +var DEFAULT_WIDTH = 20; +var DEFAULT_HEIGHT = 20; +/** + * Make fill option + * @param {Object} options - Options to create the shape + * @param {Object.Image} canvasImage - canvas background image + * @param {Function} createStaticCanvas - static canvas creater + * @returns {Object} - shape option + * @private + */ + +function makeFabricFillOption(options, canvasImage, createStaticCanvas) { + var fillOption = fill_default()(options); + + var fillType = getFillTypeFromOption(fill_default()(options)); + var fill = fillOption; + + if (fillOption.color) { + fill = fillOption.color; + } + + var extOption = null; + + if (fillType === 'filter') { + var newStaticCanvas = createStaticCanvas(); + extOption = makeFillPatternForFilter(canvasImage, filter_default()(fillOption), newStaticCanvas); + } else { + extOption = { + fill: fill + }; + } + + return extend_default()({}, options, extOption); +} +/** + * Shape + * @class Shape + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @ignore + */ + + +var shape_Shape = /*#__PURE__*/function (_Component) { + _inherits(Shape, _Component); + + var _super = shape_createSuper(Shape); + + function Shape(graphics) { + var _context, _context2, _context3, _context4, _context5; + + var _this; + + _classCallCheck(this, Shape); + + _this = _super.call(this, componentNames.SHAPE, graphics); + /** + * Object of The drawing shape + * @type {fabric.Object} + * @private + */ + + _this._shapeObj = null; + /** + * Type of the drawing shape + * @type {string} + * @private + */ + + _this._type = DEFAULT_TYPE; + /** + * Options to draw the shape + * @type {Object} + * @private + */ + + _this._options = extend_default()({}, SHAPE_INIT_OPTIONS); + /** + * Whether the shape object is selected or not + * @type {boolean} + * @private + */ + + _this._isSelected = false; + /** + * Pointer for drawing shape (x, y) + * @type {Object} + * @private + */ + + _this._startPoint = {}; + /** + * Using shortcut on drawing shape + * @type {boolean} + * @private + */ + + _this._withShiftKey = false; + /** + * Event handler list + * @type {Object} + * @private + */ + + _this._handlers = { + mousedown: bind_default()(_context = _this._onFabricMouseDown).call(_context, _assertThisInitialized(_this)), + mousemove: bind_default()(_context2 = _this._onFabricMouseMove).call(_context2, _assertThisInitialized(_this)), + mouseup: bind_default()(_context3 = _this._onFabricMouseUp).call(_context3, _assertThisInitialized(_this)), + keydown: bind_default()(_context4 = _this._onKeyDown).call(_context4, _assertThisInitialized(_this)), + keyup: bind_default()(_context5 = _this._onKeyUp).call(_context5, _assertThisInitialized(_this)) + }; + return _this; + } + /** + * Start to draw the shape on canvas + * @ignore + */ + + + _createClass(Shape, [{ + key: "start", + value: function start() { + var canvas = this.getCanvas(); + this._isSelected = false; + canvas.defaultCursor = 'crosshair'; + canvas.selection = false; + canvas.uniformScaling = true; + canvas.on({ + 'mouse:down': this._handlers.mousedown + }); + fabric.fabric.util.addListener(document, 'keydown', this._handlers.keydown); + fabric.fabric.util.addListener(document, 'keyup', this._handlers.keyup); + } + /** + * End to draw the shape on canvas + * @ignore + */ + + }, { + key: "end", + value: function end() { + var canvas = this.getCanvas(); + this._isSelected = false; + canvas.defaultCursor = 'default'; + canvas.selection = true; + canvas.uniformScaling = false; + canvas.off({ + 'mouse:down': this._handlers.mousedown + }); + fabric.fabric.util.removeListener(document, 'keydown', this._handlers.keydown); + fabric.fabric.util.removeListener(document, 'keyup', this._handlers.keyup); + } + /** + * Set states of the current drawing shape + * @ignore + * @param {string} type - Shape type (ex: 'rect', 'circle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + */ + + }, { + key: "setStates", + value: function setStates(type, options) { + this._type = type; + + if (options) { + this._options = extend_default()(this._options, options); + } + } + /** + * Add the shape + * @ignore + * @param {string} type - Shape type (ex: 'rect', 'circle') + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - ShapeFillOption or Shape foreground color (ex: '#fff', 'transparent') or ShapeFillOption object + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not + * @returns {Promise} + */ + + }, { + key: "add", + value: function add(type, options) { + var _this2 = this; + + return new (promise_default())(function (resolve) { + var canvas = _this2.getCanvas(); + + var extendOption = _this2._extendOptions(options); + + var shapeObj = _this2._createInstance(type, extendOption); + + var objectProperties = _this2.graphics.createObjectProperties(shapeObj); + + _this2._bindEventOnShape(shapeObj); + + canvas.add(shapeObj).setActiveObject(shapeObj); + + _this2._resetPositionFillFilter(shapeObj); + + resolve(objectProperties); + }); + } + /** + * Change the shape + * @ignore + * @param {fabric.Object} shapeObj - Selected shape object on canvas + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether scaling shape has 1:1 ratio or not + * @returns {Promise} + */ + + }, { + key: "change", + value: function change(shapeObj, options) { + var _this3 = this; + + return new (promise_default())(function (resolve, reject) { + if (!isShape(shapeObj)) { + reject(rejectMessages.unsupportedType); + } + + var hasFillOption = getFillTypeFromOption(fill_default()(options)) === 'filter'; + var _this3$graphics = _this3.graphics, + canvasImage = _this3$graphics.canvasImage, + createStaticCanvas = _this3$graphics.createStaticCanvas; + shapeObj.set(hasFillOption ? makeFabricFillOption(options, canvasImage, createStaticCanvas) : options); + + if (hasFillOption) { + _this3._resetPositionFillFilter(shapeObj); + } + + _this3.getCanvas().renderAll(); + + resolve(); + }); + } + /** + * make fill property for user event + * @param {fabric.Object} shapeObj - fabric object + * @returns {Object} + */ + + }, { + key: "makeFillPropertyForUserEvent", + value: function makeFillPropertyForUserEvent(shapeObj) { + var fillType = getFillTypeFromObject(shapeObj); + var fillProp = {}; + + if (fillType === SHAPE_FILL_TYPE.FILTER) { + var fillImage = getFillImageFromShape(shapeObj); + var filterOption = makeFilterOptionFromFabricImage(fillImage); + fillProp.type = fillType; + fillProp.filter = filterOption; + } else { + fillProp.type = SHAPE_FILL_TYPE.COLOR; + fillProp.color = fill_default()(shapeObj) || 'transparent'; + } + + return fillProp; + } + /** + * Copy object handling. + * @param {fabric.Object} shapeObj - Shape object + * @param {fabric.Object} originalShapeObj - Shape object + */ + + }, { + key: "processForCopiedObject", + value: function processForCopiedObject(shapeObj, originalShapeObj) { + this._bindEventOnShape(shapeObj); + + if (getFillTypeFromObject(shapeObj) === 'filter') { + var fillImage = getFillImageFromShape(originalShapeObj); + var filterOption = makeFilterOptionFromFabricImage(fillImage); + var newStaticCanvas = this.graphics.createStaticCanvas(); + shapeObj.set(makeFillPatternForFilter(this.graphics.canvasImage, filterOption, newStaticCanvas)); + + this._resetPositionFillFilter(shapeObj); + } + } + /** + * Create the instance of shape + * @param {string} type - Shape type + * @param {Object} options - Options to creat the shape + * @returns {fabric.Object} Shape instance + * @private + */ + + }, { + key: "_createInstance", + value: function _createInstance(type, options) { + var instance; + + switch (type) { + case 'rect': + instance = new fabric.fabric.Rect(options); + break; + + case 'circle': + instance = new fabric.fabric.Ellipse(extend_default()({ + type: 'circle' + }, options)); + break; + + case 'triangle': + instance = new fabric.fabric.Triangle(options); + break; + + default: + instance = {}; + } + + return instance; + } + /** + * Get the options to create the shape + * @param {Object} options - Options to creat the shape + * @returns {Object} Shape options + * @private + */ + + }, { + key: "_extendOptions", + value: function _extendOptions(options) { + var selectionStyles = fObjectOptions.SELECTION_STYLE; + var _this$graphics = this.graphics, + canvasImage = _this$graphics.canvasImage, + createStaticCanvas = _this$graphics.createStaticCanvas; + options = extend_default()({}, SHAPE_INIT_OPTIONS, this._options, selectionStyles, options); + return makeFabricFillOption(options, canvasImage, createStaticCanvas); + } + /** + * Bind fabric events on the creating shape object + * @param {fabric.Object} shapeObj - Shape object + * @private + */ + + }, { + key: "_bindEventOnShape", + value: function _bindEventOnShape(shapeObj) { + var self = this; + var canvas = this.getCanvas(); + shapeObj.on({ + added: function added() { + self._shapeObj = this; + shapeResizeHelper_default().setOrigins(self._shapeObj); + }, + selected: function selected() { + self._isSelected = true; + self._shapeObj = this; + canvas.uniformScaling = true; + canvas.defaultCursor = 'default'; + shapeResizeHelper_default().setOrigins(self._shapeObj); + }, + deselected: function deselected() { + self._isSelected = false; + self._shapeObj = null; + canvas.defaultCursor = 'crosshair'; + canvas.uniformScaling = false; + }, + modified: function modified() { + var currentObj = self._shapeObj; + shapeResizeHelper_default().adjustOriginToCenter(currentObj); + shapeResizeHelper_default().setOrigins(currentObj); + }, + modifiedInGroup: function modifiedInGroup(activeSelection) { + self._fillFilterRePositionInGroupSelection(shapeObj, activeSelection); + }, + moving: function moving() { + self._resetPositionFillFilter(this); + }, + rotating: function rotating() { + self._resetPositionFillFilter(this); + }, + scaling: function scaling(fEvent) { + var pointer = canvas.getPointer(fEvent.e); + var currentObj = self._shapeObj; + canvas.setCursor('crosshair'); + shapeResizeHelper_default().resize(currentObj, pointer, true); + + self._resetPositionFillFilter(this); + } + }); + } + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseDown", + value: function _onFabricMouseDown(fEvent) { + if (!fEvent.target) { + this._isSelected = false; + this._shapeObj = false; + } + + if (!this._isSelected && !this._shapeObj) { + var canvas = this.getCanvas(); + this._startPoint = canvas.getPointer(fEvent.e); + canvas.on({ + 'mouse:move': this._handlers.mousemove, + 'mouse:up': this._handlers.mouseup + }); + } + } + /** + * MouseDown event handler on canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event object + * @private + */ + + }, { + key: "_onFabricMouseMove", + value: function _onFabricMouseMove(fEvent) { + var _this4 = this; + + var canvas = this.getCanvas(); + var pointer = canvas.getPointer(fEvent.e); + var startPointX = this._startPoint.x; + var startPointY = this._startPoint.y; + var width = startPointX - pointer.x; + var height = startPointY - pointer.y; + var shape = this._shapeObj; + + if (!shape) { + this.add(this._type, { + left: startPointX, + top: startPointY, + width: width, + height: height + }).then(function (objectProps) { + _this4.fire(eventNames.ADD_OBJECT, objectProps); + }); + } else { + this._shapeObj.set({ + isRegular: this._withShiftKey + }); + + shapeResizeHelper_default().resize(shape, pointer); + canvas.renderAll(); + + this._resetPositionFillFilter(shape); + } + } + /** + * MouseUp event handler on canvas + * @private + */ + + }, { + key: "_onFabricMouseUp", + value: function _onFabricMouseUp() { + var _this5 = this; + + var canvas = this.getCanvas(); + var startPointX = this._startPoint.x; + var startPointY = this._startPoint.y; + var shape = this._shapeObj; + + if (!shape) { + this.add(this._type, { + left: startPointX, + top: startPointY, + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT + }).then(function (objectProps) { + _this5.fire(eventNames.ADD_OBJECT, objectProps); + }); + } else if (shape) { + shapeResizeHelper_default().adjustOriginToCenter(shape); + this.fire(eventNames.OBJECT_ADDED, this.graphics.createObjectProperties(shape)); + } + + canvas.off({ + 'mouse:move': this._handlers.mousemove, + 'mouse:up': this._handlers.mouseup + }); + } + /** + * Keydown event handler on document + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_onKeyDown", + value: function _onKeyDown(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = true; + + if (this._shapeObj) { + this._shapeObj.isRegular = true; + } + } + } + /** + * Keyup event handler on document + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_onKeyUp", + value: function _onKeyUp(e) { + if (e.keyCode === keyCodes.SHIFT) { + this._withShiftKey = false; + + if (this._shapeObj) { + this._shapeObj.isRegular = false; + } + } + } + /** + * Reset shape position and internal proportions in the filter type fill area. + * @param {fabric.Object} shapeObj - Shape object + * @private + */ + + }, { + key: "_resetPositionFillFilter", + value: function _resetPositionFillFilter(shapeObj) { + if (getFillTypeFromObject(shapeObj) !== 'filter') { + return; + } + + var _getCustomProperty = getCustomProperty(shapeObj, 'patternSourceCanvas'), + patternSourceCanvas = _getCustomProperty.patternSourceCanvas; + + var fillImage = getFillImageFromShape(shapeObj); + + var _getCustomProperty2 = getCustomProperty(fillImage, 'originalAngle'), + originalAngle = _getCustomProperty2.originalAngle; + + if (this.graphics.canvasImage.angle !== originalAngle) { + reMakePatternImageSource(shapeObj, this.graphics.canvasImage); + } + + var originX = shapeObj.originX, + originY = shapeObj.originY; + shapeResizeHelper_default().adjustOriginToCenter(shapeObj); + shapeObj.width *= shapeObj.scaleX; + shapeObj.height *= shapeObj.scaleY; + shapeObj.rx *= shapeObj.scaleX; + shapeObj.ry *= shapeObj.scaleY; + shapeObj.scaleX = 1; + shapeObj.scaleY = 1; + rePositionFilterTypeFillImage(shapeObj); + changeOrigin(shapeObj, { + originX: originX, + originY: originY + }); + resetFillPatternCanvas(patternSourceCanvas); + } + /** + * Reset filter area position within group selection. + * @param {fabric.Object} shapeObj - Shape object + * @param {fabric.ActiveSelection} activeSelection - Shape object + * @private + */ + + }, { + key: "_fillFilterRePositionInGroupSelection", + value: function _fillFilterRePositionInGroupSelection(shapeObj, activeSelection) { + if (activeSelection.scaleX !== 1 || activeSelection.scaleY !== 1) { + // This is necessary because the group's scale transition state affects the relative size of the fill area. + // The only way to reset the object transformation scale state to neutral. + // {@link https://github.com/fabricjs/fabric.js/issues/5372} + activeSelection.addWithUpdate(); + } + + var angle = shapeObj.angle, + left = shapeObj.left, + top = shapeObj.top; + fabric.fabric.util.addTransformToObject(shapeObj, activeSelection.calcTransformMatrix()); + + this._resetPositionFillFilter(shapeObj); + + shapeObj.set({ + angle: angle, + left: left, + top: top + }); + } + }]); + + return Shape; +}(component); + + +;// CONCATENATED MODULE: ./src/js/component/zoom.js + + + + + + + + + + +function zoom_createSuper(Derived) { var hasNativeReflectConstruct = zoom_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function zoom_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + + + +var zoom_MOUSE_MOVE_THRESHOLD = 10; +var DEFAULT_SCROLL_OPTION = { + left: 0, + top: 0, + width: 0, + height: 0, + stroke: '#000000', + strokeWidth: 0, + fill: '#000000', + opacity: 0.4, + evented: false, + selectable: false, + hoverCursor: 'auto' +}; +var DEFAULT_VERTICAL_SCROLL_RATIO = { + SIZE: 0.0045, + MARGIN: 0.003, + BORDER_RADIUS: 0.003 +}; +var DEFAULT_HORIZONTAL_SCROLL_RATIO = { + SIZE: 0.0066, + MARGIN: 0.0044, + BORDER_RADIUS: 0.003 +}; +var DEFAULT_ZOOM_LEVEL = 1.0; +var ZOOM_CHANGED = eventNames.ZOOM_CHANGED, + ADD_TEXT = eventNames.ADD_TEXT, + TEXT_EDITING = eventNames.TEXT_EDITING, + OBJECT_MODIFIED = eventNames.OBJECT_MODIFIED, + KEY_DOWN = eventNames.KEY_DOWN, + KEY_UP = eventNames.KEY_UP, + HAND_STARTED = eventNames.HAND_STARTED, + HAND_STOPPED = eventNames.HAND_STOPPED; +/** + * Zoom components + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @class Zoom + * @ignore + */ + +var Zoom = /*#__PURE__*/function (_Component) { + _inherits(Zoom, _Component); + + var _super = zoom_createSuper(Zoom); + + function Zoom(graphics) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12; + + var _this; + + _classCallCheck(this, Zoom); + + _this = _super.call(this, componentNames.ZOOM, graphics); + /** + * zoomArea + * @type {?fabric.Rect} + * @private + */ + + _this.zoomArea = null; + /** + * Start point of zoom area + * @type {?{x: number, y: number}} + */ + + _this._startPoint = null; + /** + * Center point of every zoom + * @type {Array.<{prevZoomLevel: number, zoomLevel: number, x: number, y: number}>} + */ + + _this._centerPoints = []; + /** + * Zoom level (default: 100%(1.0), max: 400%(4.0)) + * @type {number} + */ + + _this.zoomLevel = DEFAULT_ZOOM_LEVEL; + /** + * Zoom mode ('normal', 'zoom', 'hand') + * @type {string} + */ + + _this.zoomMode = zoomModes.DEFAULT; + /** + * Listeners + * @type {Object.} + * @private + */ + + _this._listeners = { + startZoom: bind_default()(_context = _this._onMouseDownWithZoomMode).call(_context, _assertThisInitialized(_this)), + moveZoom: bind_default()(_context2 = _this._onMouseMoveWithZoomMode).call(_context2, _assertThisInitialized(_this)), + stopZoom: bind_default()(_context3 = _this._onMouseUpWithZoomMode).call(_context3, _assertThisInitialized(_this)), + startHand: bind_default()(_context4 = _this._onMouseDownWithHandMode).call(_context4, _assertThisInitialized(_this)), + moveHand: bind_default()(_context5 = _this._onMouseMoveWithHandMode).call(_context5, _assertThisInitialized(_this)), + stopHand: bind_default()(_context6 = _this._onMouseUpWithHandMode).call(_context6, _assertThisInitialized(_this)), + zoomChanged: bind_default()(_context7 = _this._changeScrollState).call(_context7, _assertThisInitialized(_this)), + keydown: bind_default()(_context8 = _this._startHandModeWithSpaceBar).call(_context8, _assertThisInitialized(_this)), + keyup: bind_default()(_context9 = _this._endHandModeWithSpaceBar).call(_context9, _assertThisInitialized(_this)) + }; + + var canvas = _this.getCanvas(); + /** + * Width:Height ratio (ex. width=1.5, height=1 -> aspectRatio=1.5) + * @private + */ + + + _this.aspectRatio = canvas.width / canvas.height; + /** + * vertical scroll bar + * @type {fabric.Rect} + * @private + */ + + _this._verticalScroll = new fabric.fabric.Rect(DEFAULT_SCROLL_OPTION); + /** + * horizontal scroll bar + * @type {fabric.Rect} + * @private + */ + + _this._horizontalScroll = new fabric.fabric.Rect(DEFAULT_SCROLL_OPTION); + canvas.on(ZOOM_CHANGED, _this._listeners.zoomChanged); + + _this.graphics.on(ADD_TEXT, bind_default()(_context10 = _this._startTextEditingHandler).call(_context10, _assertThisInitialized(_this))); + + _this.graphics.on(TEXT_EDITING, bind_default()(_context11 = _this._startTextEditingHandler).call(_context11, _assertThisInitialized(_this))); + + _this.graphics.on(OBJECT_MODIFIED, bind_default()(_context12 = _this._stopTextEditingHandler).call(_context12, _assertThisInitialized(_this))); + + return _this; + } + /** + * Attach zoom keyboard events + */ + + + _createClass(Zoom, [{ + key: "attachKeyboardZoomEvents", + value: function attachKeyboardZoomEvents() { + fabric.fabric.util.addListener(document, KEY_DOWN, this._listeners.keydown); + fabric.fabric.util.addListener(document, KEY_UP, this._listeners.keyup); + } + /** + * Detach zoom keyboard events + */ + + }, { + key: "detachKeyboardZoomEvents", + value: function detachKeyboardZoomEvents() { + fabric.fabric.util.removeListener(document, KEY_DOWN, this._listeners.keydown); + fabric.fabric.util.removeListener(document, KEY_UP, this._listeners.keyup); + } + /** + * Handler when you started editing text + * @private + */ + + }, { + key: "_startTextEditingHandler", + value: function _startTextEditingHandler() { + this.isTextEditing = true; + } + /** + * Handler when you stopped editing text + * @private + */ + + }, { + key: "_stopTextEditingHandler", + value: function _stopTextEditingHandler() { + this.isTextEditing = false; + } + /** + * Handler who turns on hand mode when the space bar is down + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_startHandModeWithSpaceBar", + value: function _startHandModeWithSpaceBar(e) { + if (this.withSpace || this.isTextEditing) { + return; + } + + if (e.keyCode === keyCodes.SPACE) { + this.withSpace = true; + this.startHandMode(); + } + } + /** + * Handler who turns off hand mode when space bar is up + * @param {KeyboardEvent} e - Event object + * @private + */ + + }, { + key: "_endHandModeWithSpaceBar", + value: function _endHandModeWithSpaceBar(e) { + if (e.keyCode === keyCodes.SPACE) { + this.withSpace = false; + this.endHandMode(); + } + } + /** + * Start zoom-in mode + */ + + }, { + key: "startZoomInMode", + value: function startZoomInMode() { + if (this.zoomArea) { + return; + } + + this.endHandMode(); + this.zoomMode = zoomModes.ZOOM; + var canvas = this.getCanvas(); + + this._changeObjectsEventedState(false); + + this.zoomArea = new fabric.fabric.Rect({ + left: 0, + top: 0, + width: 0.5, + height: 0.5, + stroke: 'black', + strokeWidth: 1, + fill: 'transparent', + hoverCursor: 'zoom-in' + }); + canvas.discardActiveObject(); + canvas.add(this.zoomArea); + canvas.on('mouse:down', this._listeners.startZoom); + canvas.selection = false; + canvas.defaultCursor = 'zoom-in'; + } + /** + * End zoom-in mode + */ + + }, { + key: "endZoomInMode", + value: function endZoomInMode() { + this.zoomMode = zoomModes.DEFAULT; + var canvas = this.getCanvas(); + var _this$_listeners = this._listeners, + startZoom = _this$_listeners.startZoom, + moveZoom = _this$_listeners.moveZoom, + stopZoom = _this$_listeners.stopZoom; + canvas.selection = true; + canvas.defaultCursor = 'auto'; + canvas.off({ + 'mouse:down': startZoom, + 'mouse:move': moveZoom, + 'mouse:up': stopZoom + }); + + this._changeObjectsEventedState(true); + + canvas.remove(this.zoomArea); + this.zoomArea = null; + } + /** + * Start zoom drawing mode + */ + + }, { + key: "start", + value: function start() { + this.zoomArea = null; + this._startPoint = null; + this._startHandPoint = null; + } + /** + * Stop zoom drawing mode + */ + + }, { + key: "end", + value: function end() { + this.endZoomInMode(); + this.endHandMode(); + } + /** + * Start hand mode + */ + + }, { + key: "startHandMode", + value: function startHandMode() { + this.endZoomInMode(); + this.zoomMode = zoomModes.HAND; + var canvas = this.getCanvas(); + + this._changeObjectsEventedState(false); + + canvas.discardActiveObject(); + canvas.off('mouse:down', this._listeners.startHand); + canvas.on('mouse:down', this._listeners.startHand); + canvas.selection = false; + canvas.defaultCursor = 'grab'; + canvas.fire(HAND_STARTED); + } + /** + * Stop hand mode + */ + + }, { + key: "endHandMode", + value: function endHandMode() { + this.zoomMode = zoomModes.DEFAULT; + var canvas = this.getCanvas(); + + this._changeObjectsEventedState(true); + + canvas.off('mouse:down', this._listeners.startHand); + canvas.selection = true; + canvas.defaultCursor = 'auto'; + this._startHandPoint = null; + canvas.fire(HAND_STOPPED); + } + /** + * onMousedown handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onMouseDownWithZoomMode", + value: function _onMouseDownWithZoomMode(_ref) { + var target = _ref.target, + e = _ref.e; + + if (target) { + return; + } + + var canvas = this.getCanvas(); + canvas.selection = false; + this._startPoint = canvas.getPointer(e); + this.zoomArea.set({ + width: 0, + height: 0 + }); + var _this$_listeners2 = this._listeners, + moveZoom = _this$_listeners2.moveZoom, + stopZoom = _this$_listeners2.stopZoom; + canvas.on({ + 'mouse:move': moveZoom, + 'mouse:up': stopZoom + }); + } + /** + * onMousemove handler in fabric canvas + * @param {{e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onMouseMoveWithZoomMode", + value: function _onMouseMoveWithZoomMode(_ref2) { + var e = _ref2.e; + var canvas = this.getCanvas(); + var pointer = canvas.getPointer(e); + var x = pointer.x, + y = pointer.y; + var zoomArea = this.zoomArea, + _startPoint = this._startPoint; + var deltaX = Math.abs(x - _startPoint.x); + var deltaY = Math.abs(y - _startPoint.y); + + if (deltaX + deltaY > zoom_MOUSE_MOVE_THRESHOLD) { + canvas.remove(zoomArea); + zoomArea.set(this._calcRectDimensionFromPoint(x, y)); + canvas.add(zoomArea); + } + } + /** + * Get rect dimension setting from Canvas-Mouse-Position(x, y) + * @param {number} x - Canvas-Mouse-Position x + * @param {number} y - Canvas-Mouse-Position Y + * @returns {{left: number, top: number, width: number, height: number}} + * @private + */ + + }, { + key: "_calcRectDimensionFromPoint", + value: function _calcRectDimensionFromPoint(x, y) { + var canvas = this.getCanvas(); + var canvasWidth = canvas.getWidth(); + var canvasHeight = canvas.getHeight(); + var _this$_startPoint = this._startPoint, + startX = _this$_startPoint.x, + startY = _this$_startPoint.y; + var min = Math.min; + var left = min(startX, x); + var top = min(startY, y); + var width = clamp(x, startX, canvasWidth) - left; // (startX <= x(mouse) <= canvasWidth) - left + + var height = clamp(y, startY, canvasHeight) - top; // (startY <= y(mouse) <= canvasHeight) - top + + return { + left: left, + top: top, + width: width, + height: height + }; + } + /** + * onMouseup handler in fabric canvas + * @private + */ + + }, { + key: "_onMouseUpWithZoomMode", + value: function _onMouseUpWithZoomMode() { + var zoomLevel = this.zoomLevel; + var zoomArea = this.zoomArea; + var _this$_listeners3 = this._listeners, + moveZoom = _this$_listeners3.moveZoom, + stopZoom = _this$_listeners3.stopZoom; + var canvas = this.getCanvas(); + + var center = this._getCenterPoint(); + + var x = center.x, + y = center.y; + + if (!this._isMaxZoomLevel()) { + this._centerPoints.push({ + x: x, + y: y, + prevZoomLevel: zoomLevel, + zoomLevel: zoomLevel + 1 + }); + + zoomLevel += 1; + canvas.zoomToPoint({ + x: x, + y: y + }, zoomLevel); + + this._fireZoomChanged(canvas, zoomLevel); + + this.zoomLevel = zoomLevel; + } + + canvas.off({ + 'mouse:move': moveZoom, + 'mouse:up': stopZoom + }); + canvas.remove(zoomArea); + this._startPoint = null; + } + /** + * Get center point + * @returns {{x: number, y: number}} + * @private + */ + + }, { + key: "_getCenterPoint", + value: function _getCenterPoint() { + var _this$zoomArea = this.zoomArea, + left = _this$zoomArea.left, + top = _this$zoomArea.top, + width = _this$zoomArea.width, + height = _this$zoomArea.height; + var _this$_startPoint2 = this._startPoint, + x = _this$_startPoint2.x, + y = _this$_startPoint2.y; + var aspectRatio = this.aspectRatio; + + if (width < zoom_MOUSE_MOVE_THRESHOLD && height < zoom_MOUSE_MOVE_THRESHOLD) { + return { + x: x, + y: y + }; + } + + return width > height ? { + x: left + aspectRatio * height / 2, + y: top + height / 2 + } : { + x: left + width / 2, + y: top + width / aspectRatio / 2 + }; + } + /** + * Zoom the canvas + * @param {{x: number, y: number}} center - center of zoom + * @param {?number} zoomLevel - zoom level + */ + + }, { + key: "zoom", + value: function zoom(_ref3) { + var x = _ref3.x, + y = _ref3.y; + var zoomLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.zoomLevel; + var canvas = this.getCanvas(); + var centerPoints = this._centerPoints; + + for (var i = centerPoints.length - 1; i >= 0; i -= 1) { + if (centerPoints[i].zoomLevel < zoomLevel) { + break; + } + + var _centerPoints$pop = centerPoints.pop(), + prevX = _centerPoints$pop.x, + prevY = _centerPoints$pop.y, + prevZoomLevel = _centerPoints$pop.prevZoomLevel; + + canvas.zoomToPoint({ + x: prevX, + y: prevY + }, prevZoomLevel); + this.zoomLevel = prevZoomLevel; + } + + canvas.zoomToPoint({ + x: x, + y: y + }, zoomLevel); + + if (!this._isDefaultZoomLevel(zoomLevel)) { + this._centerPoints.push({ + x: x, + y: y, + zoomLevel: zoomLevel, + prevZoomLevel: this.zoomLevel + }); + } + + this.zoomLevel = zoomLevel; + + this._fireZoomChanged(canvas, zoomLevel); + } + /** + * Zoom out one step + */ + + }, { + key: "zoomOut", + value: function zoomOut() { + var centerPoints = this._centerPoints; + + if (!centerPoints.length) { + return; + } + + var canvas = this.getCanvas(); + var point = centerPoints.pop(); + var x = point.x, + y = point.y, + prevZoomLevel = point.prevZoomLevel; + + if (this._isDefaultZoomLevel(prevZoomLevel)) { + canvas.setViewportTransform([1, 0, 0, 1, 0, 0]); + } else { + canvas.zoomToPoint({ + x: x, + y: y + }, prevZoomLevel); + } + + this.zoomLevel = prevZoomLevel; + + this._fireZoomChanged(canvas, this.zoomLevel); + } + /** + * Zoom reset + */ + + }, { + key: "resetZoom", + value: function resetZoom() { + var canvas = this.getCanvas(); + canvas.setViewportTransform([1, 0, 0, 1, 0, 0]); + this.zoomLevel = DEFAULT_ZOOM_LEVEL; + this._centerPoints = []; + + this._fireZoomChanged(canvas, this.zoomLevel); + } + /** + * Whether zoom level is max (5.0) + * @returns {boolean} + * @private + */ + + }, { + key: "_isMaxZoomLevel", + value: function _isMaxZoomLevel() { + return this.zoomLevel >= 5.0; + } + /** + * Move point of zoom + * @param {{x: number, y: number}} delta - move amount + * @private + */ + + }, { + key: "_movePointOfZoom", + value: function _movePointOfZoom(_ref4) { + var deltaX = _ref4.x, + deltaY = _ref4.y; + var centerPoints = this._centerPoints; + + if (!centerPoints.length) { + return; + } + + var canvas = this.getCanvas(); + var zoomLevel = this.zoomLevel; + var point = centerPoints.pop(); + var originX = point.x, + originY = point.y, + prevZoomLevel = point.prevZoomLevel; + var x = originX - deltaX; + var y = originY - deltaY; + canvas.zoomToPoint({ + x: originX, + y: originY + }, prevZoomLevel); + canvas.zoomToPoint({ + x: x, + y: y + }, zoomLevel); + centerPoints.push({ + x: x, + y: y, + prevZoomLevel: prevZoomLevel, + zoomLevel: zoomLevel + }); + + this._fireZoomChanged(canvas, zoomLevel); + } + /** + * onMouseDown handler in fabric canvas + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onMouseDownWithHandMode", + value: function _onMouseDownWithHandMode(_ref5) { + var target = _ref5.target, + e = _ref5.e; + + if (target) { + return; + } + + var canvas = this.getCanvas(); + + if (this.zoomLevel <= DEFAULT_ZOOM_LEVEL) { + return; + } + + canvas.selection = false; + this._startHandPoint = canvas.getPointer(e); + var _this$_listeners4 = this._listeners, + moveHand = _this$_listeners4.moveHand, + stopHand = _this$_listeners4.stopHand; + canvas.on({ + 'mouse:move': moveHand, + 'mouse:up': stopHand + }); + } + /** + * onMouseMove handler in fabric canvas + * @param {{e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onMouseMoveWithHandMode", + value: function _onMouseMoveWithHandMode(_ref6) { + var e = _ref6.e; + var canvas = this.getCanvas(); + + var _canvas$getPointer = canvas.getPointer(e), + x = _canvas$getPointer.x, + y = _canvas$getPointer.y; + + var deltaX = x - this._startHandPoint.x; + var deltaY = y - this._startHandPoint.y; + + this._movePointOfZoom({ + x: deltaX, + y: deltaY + }); + } + /** + * onMouseUp handler in fabric canvas + * @private + */ + + }, { + key: "_onMouseUpWithHandMode", + value: function _onMouseUpWithHandMode() { + var canvas = this.getCanvas(); + var _this$_listeners5 = this._listeners, + moveHand = _this$_listeners5.moveHand, + stopHand = _this$_listeners5.stopHand; + canvas.off({ + 'mouse:move': moveHand, + 'mouse:up': stopHand + }); + this._startHandPoint = null; + } + /** + * onChangeZoom handler in fabric canvas + * @private + */ + + }, { + key: "_changeScrollState", + value: function _changeScrollState(_ref7) { + var viewport = _ref7.viewport, + zoomLevel = _ref7.zoomLevel; + var canvas = this.getCanvas(); + canvas.remove(this._verticalScroll); + canvas.remove(this._horizontalScroll); + + if (this._isDefaultZoomLevel(zoomLevel)) { + return; + } + + var canvasWidth = canvas.width; + var canvasHeight = canvas.height; + var tl = viewport.tl, + tr = viewport.tr, + bl = viewport.bl; + var viewportWidth = tr.x - tl.x; + var viewportHeight = bl.y - tl.y; + var horizontalScrollWidth = viewportWidth * viewportWidth / canvasWidth; + var horizontalScrollHeight = viewportHeight * DEFAULT_HORIZONTAL_SCROLL_RATIO.SIZE; + var horizontalScrollLeft = clamp(tl.x + tl.x / canvasWidth * viewportWidth, tl.x, tr.x - horizontalScrollWidth); + var horizontalScrollMargin = viewportHeight * DEFAULT_HORIZONTAL_SCROLL_RATIO.MARGIN; + var horizontalScrollBorderRadius = viewportHeight * DEFAULT_HORIZONTAL_SCROLL_RATIO.BORDER_RADIUS; + + this._horizontalScroll.set({ + left: horizontalScrollLeft, + top: bl.y - horizontalScrollHeight - horizontalScrollMargin, + width: horizontalScrollWidth, + height: horizontalScrollHeight, + rx: horizontalScrollBorderRadius, + ry: horizontalScrollBorderRadius + }); + + var verticalScrollWidth = viewportWidth * DEFAULT_VERTICAL_SCROLL_RATIO.SIZE; + var verticalScrollHeight = viewportHeight * viewportHeight / canvasHeight; + var verticalScrollTop = clamp(tl.y + tl.y / canvasHeight * viewportHeight, tr.y, bl.y - verticalScrollHeight); + var verticalScrollMargin = viewportWidth * DEFAULT_VERTICAL_SCROLL_RATIO.MARGIN; + var verticalScrollBorderRadius = viewportWidth * DEFAULT_VERTICAL_SCROLL_RATIO.BORDER_RADIUS; + + this._verticalScroll.set({ + left: tr.x - verticalScrollWidth - verticalScrollMargin, + top: verticalScrollTop, + width: verticalScrollWidth, + height: verticalScrollHeight, + rx: verticalScrollBorderRadius, + ry: verticalScrollBorderRadius + }); + + this._addScrollBar(); + } + /** + * Change objects 'evented' state + * @param {boolean} [evented=true] - objects 'evented' state + */ + + }, { + key: "_changeObjectsEventedState", + value: function _changeObjectsEventedState() { + var evented = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + var canvas = this.getCanvas(); + canvas.forEachObject(function (obj) { + // {@link http://fabricjs.com/docs/fabric.Object.html#evented} + obj.evented = evented; + }); + } + /** + * Add scroll bar and set remove timer + */ + + }, { + key: "_addScrollBar", + value: function _addScrollBar() { + var _this2 = this; + + var canvas = this.getCanvas(); + canvas.add(this._horizontalScroll); + canvas.add(this._verticalScroll); + + if (this.scrollBarTid) { + clearTimeout(this.scrollBarTid); + } + + this.scrollBarTid = set_timeout_default()(function () { + canvas.remove(_this2._horizontalScroll); + canvas.remove(_this2._verticalScroll); + }, 3000); + } + /** + * Check zoom level is default zoom level (1.0) + * @param {number} zoomLevel - zoom level + * @returns {boolean} - whether zoom level is 1.0 + */ + + }, { + key: "_isDefaultZoomLevel", + value: function _isDefaultZoomLevel(zoomLevel) { + return zoomLevel === DEFAULT_ZOOM_LEVEL; + } + /** + * Fire 'zoomChanged' event + * @param {fabric.Canvas} canvas - fabric canvas + * @param {number} zoomLevel - 'zoomChanged' event params + */ + + }, { + key: "_fireZoomChanged", + value: function _fireZoomChanged(canvas, zoomLevel) { + canvas.fire(ZOOM_CHANGED, { + viewport: canvas.calcViewportBoundaries(), + zoomLevel: zoomLevel + }); + } + /** + * Get zoom mode + */ + + }, { + key: "mode", + get: function get() { + return this.zoomMode; + } + }]); + + return Zoom; +}(component); + +/* harmony default export */ var zoom = (Zoom); +;// CONCATENATED MODULE: ./src/js/interface/drawingMode.js + + + +var drawingMode_createMessage = errorMessage.create; +var drawingMode_errorTypes = errorMessage.types; +/** + * DrawingMode interface + * @class + * @param {string} name - drawing mode name + * @ignore + */ + +var DrawingMode = /*#__PURE__*/function () { + function DrawingMode(name) { + _classCallCheck(this, DrawingMode); + + /** + * the name of drawing mode + * @type {string} + */ + this.name = name; + } + /** + * Get this drawing mode name; + * @returns {string} drawing mode name + */ + + + _createClass(DrawingMode, [{ + key: "getName", + value: function getName() { + return this.name; + } + /** + * start this drawing mode + * @param {Object} options - drawing mode options + * @abstract + */ + + }, { + key: "start", + value: function start() { + throw new Error(drawingMode_createMessage(drawingMode_errorTypes.UN_IMPLEMENTATION, 'start')); + } + /** + * stop this drawing mode + * @abstract + */ + + }, { + key: "end", + value: function end() { + throw new Error(drawingMode_createMessage(drawingMode_errorTypes.UN_IMPLEMENTATION, 'stop')); + } + }]); + + return DrawingMode; +}(); + +/* harmony default export */ var drawingMode = (DrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/cropper.js + + + + + + + +function drawingMode_cropper_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_cropper_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_cropper_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * CropperDrawingMode class + * @class + * @ignore + */ + +var CropperDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(CropperDrawingMode, _DrawingMode); + + var _super = drawingMode_cropper_createSuper(CropperDrawingMode); + + function CropperDrawingMode() { + _classCallCheck(this, CropperDrawingMode); + + return _super.call(this, drawingModes.CROPPER); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(CropperDrawingMode, [{ + key: "start", + value: function start(graphics) { + var cropper = graphics.getComponent(componentNames.CROPPER); + cropper.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var cropper = graphics.getComponent(componentNames.CROPPER); + cropper.end(); + } + }]); + + return CropperDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_cropper = (CropperDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/freeDrawing.js + + + + + + + +function drawingMode_freeDrawing_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_freeDrawing_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_freeDrawing_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * FreeDrawingMode class + * @class + * @ignore + */ + +var FreeDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(FreeDrawingMode, _DrawingMode); + + var _super = drawingMode_freeDrawing_createSuper(FreeDrawingMode); + + function FreeDrawingMode() { + _classCallCheck(this, FreeDrawingMode); + + return _super.call(this, drawingModes.FREE_DRAWING); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @param {{width: ?number, color: ?string}} [options] - Brush width & color + * @override + */ + + + _createClass(FreeDrawingMode, [{ + key: "start", + value: function start(graphics, options) { + var freeDrawing = graphics.getComponent(componentNames.FREE_DRAWING); + freeDrawing.start(options); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var freeDrawing = graphics.getComponent(componentNames.FREE_DRAWING); + freeDrawing.end(); + } + }]); + + return FreeDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_freeDrawing = (FreeDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/lineDrawing.js + + + + + + + +function lineDrawing_createSuper(Derived) { var hasNativeReflectConstruct = lineDrawing_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function lineDrawing_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * LineDrawingMode class + * @class + * @ignore + */ + +var LineDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(LineDrawingMode, _DrawingMode); + + var _super = lineDrawing_createSuper(LineDrawingMode); + + function LineDrawingMode() { + _classCallCheck(this, LineDrawingMode); + + return _super.call(this, drawingModes.LINE_DRAWING); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @param {{width: ?number, color: ?string}} [options] - Brush width & color + * @override + */ + + + _createClass(LineDrawingMode, [{ + key: "start", + value: function start(graphics, options) { + var lineDrawing = graphics.getComponent(componentNames.LINE); + lineDrawing.start(options); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var lineDrawing = graphics.getComponent(componentNames.LINE); + lineDrawing.end(); + } + }]); + + return LineDrawingMode; +}(drawingMode); + +/* harmony default export */ var lineDrawing = (LineDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/shape.js + + + + + + + +function drawingMode_shape_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_shape_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_shape_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * ShapeDrawingMode class + * @class + * @ignore + */ + +var ShapeDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(ShapeDrawingMode, _DrawingMode); + + var _super = drawingMode_shape_createSuper(ShapeDrawingMode); + + function ShapeDrawingMode() { + _classCallCheck(this, ShapeDrawingMode); + + return _super.call(this, drawingModes.SHAPE); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(ShapeDrawingMode, [{ + key: "start", + value: function start(graphics) { + var shape = graphics.getComponent(componentNames.SHAPE); + shape.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var shape = graphics.getComponent(componentNames.SHAPE); + shape.end(); + } + }]); + + return ShapeDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_shape = (ShapeDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/text.js + + + + + + + +function drawingMode_text_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_text_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_text_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * TextDrawingMode class + * @class + * @ignore + */ + +var TextDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(TextDrawingMode, _DrawingMode); + + var _super = drawingMode_text_createSuper(TextDrawingMode); + + function TextDrawingMode() { + _classCallCheck(this, TextDrawingMode); + + return _super.call(this, drawingModes.TEXT); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(TextDrawingMode, [{ + key: "start", + value: function start(graphics) { + var text = graphics.getComponent(componentNames.TEXT); + text.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var text = graphics.getComponent(componentNames.TEXT); + text.end(); + } + }]); + + return TextDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_text = (TextDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/icon.js + + + + + + + +function drawingMode_icon_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_icon_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_icon_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * IconDrawingMode class + * @class + * @ignore + */ + +var IconDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(IconDrawingMode, _DrawingMode); + + var _super = drawingMode_icon_createSuper(IconDrawingMode); + + function IconDrawingMode() { + _classCallCheck(this, IconDrawingMode); + + return _super.call(this, drawingModes.ICON); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(IconDrawingMode, [{ + key: "start", + value: function start(graphics) { + var icon = graphics.getComponent(componentNames.ICON); + icon.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var icon = graphics.getComponent(componentNames.ICON); + icon.end(); + } + }]); + + return IconDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_icon = (IconDrawingMode); +;// CONCATENATED MODULE: ./src/js/drawingMode/zoom.js + + + + + + + +function drawingMode_zoom_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_zoom_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_zoom_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * ZoomDrawingMode class + * @class + * @ignore + */ + +var ZoomDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(ZoomDrawingMode, _DrawingMode); + + var _super = drawingMode_zoom_createSuper(ZoomDrawingMode); + + function ZoomDrawingMode() { + _classCallCheck(this, ZoomDrawingMode); + + return _super.call(this, drawingModes.ZOOM); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(ZoomDrawingMode, [{ + key: "start", + value: function start(graphics) { + var zoom = graphics.getComponent(componentNames.ZOOM); + zoom.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var zoom = graphics.getComponent(componentNames.ZOOM); + zoom.end(); + } + }]); + + return ZoomDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_zoom = (ZoomDrawingMode); +;// CONCATENATED MODULE: ./src/js/helper/selectionModifyHelper.js + + + +/** + * Cached selection's info + * @type {Array} + * @private + */ + +var cachedUndoDataForChangeDimension = null; +/** + * Set cached undo data + * @param {Array} undoData - selection object + * @private + */ + +function setCachedUndoDataForDimension(undoData) { + cachedUndoDataForChangeDimension = undoData; +} +/** + * Get cached undo data + * @returns {Object} cached undo data + * @private + */ + +function getCachedUndoDataForDimension() { + return cachedUndoDataForChangeDimension; +} +/** + * Make undo data + * @param {fabric.Object} obj - selection object + * @param {Function} undoDatumMaker - make undo datum + * @returns {Array} undoData + * @private + */ + +function makeSelectionUndoData(obj, undoDatumMaker) { + var undoData; + + if (obj.type === 'activeSelection') { + var _context; + + undoData = map_default()(_context = obj.getObjects()).call(_context, function (item) { + var angle = item.angle, + left = item.left, + top = item.top, + scaleX = item.scaleX, + scaleY = item.scaleY, + width = item.width, + height = item.height; + fabric.fabric.util.addTransformToObject(item, obj.calcTransformMatrix()); + var result = undoDatumMaker(item); + item.set({ + angle: angle, + left: left, + top: top, + width: width, + height: height, + scaleX: scaleX, + scaleY: scaleY + }); + return result; + }); + } else { + undoData = [undoDatumMaker(obj)]; + } + + return undoData; +} +/** + * Make undo datum + * @param {number} id - object id + * @param {fabric.Object} obj - selection object + * @param {boolean} isSelection - whether or not object is selection + * @returns {Object} undo datum + * @private + */ + +function makeSelectionUndoDatum(id, obj, isSelection) { + return isSelection ? { + id: id, + width: obj.width, + height: obj.height, + top: obj.top, + left: obj.left, + angle: obj.angle, + scaleX: obj.scaleX, + scaleY: obj.scaleY + } : extend_default()({ + id: id + }, obj); +} +;// CONCATENATED MODULE: ./src/js/component/resize.js + + + + + + + + +function component_resize_createSuper(Derived) { var hasNativeReflectConstruct = component_resize_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function component_resize_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * Resize components + * @param {Graphics} graphics - Graphics instance + * @extends {Component} + * @class Resize + * @ignore + */ + +var resize_Resize = /*#__PURE__*/function (_Component) { + _inherits(Resize, _Component); + + var _super = component_resize_createSuper(Resize); + + function Resize(graphics) { + var _this; + + _classCallCheck(this, Resize); + + _this = _super.call(this, componentNames.RESIZE, graphics); + /** + * Current dimensions + * @type {Object} + * @private + */ + + _this._dimensions = null; + /** + * Original dimensions + * @type {Object} + * @private + */ + + _this._originalDimensions = null; + return _this; + } + /** + * Get current dimensions + * @returns {object} + */ + + + _createClass(Resize, [{ + key: "getCurrentDimensions", + value: function getCurrentDimensions() { + var canvasImage = this.getCanvasImage(); + + if (!this._dimensions && canvasImage) { + var width = canvasImage.width, + height = canvasImage.height; + this._dimensions = { + width: width, + height: height + }; + } + + return this._dimensions; + } + /** + * Get original dimensions + * @returns {object} + */ + + }, { + key: "getOriginalDimensions", + value: function getOriginalDimensions() { + return this._originalDimensions; + } + /** + * Set original dimensions + * @param {object} dimensions - Dimensions + */ + + }, { + key: "setOriginalDimensions", + value: function setOriginalDimensions(dimensions) { + this._originalDimensions = dimensions; + } + /** + * Resize Image + * @param {Object} dimensions - Resize dimensions + * @returns {Promise} + */ + + }, { + key: "resize", + value: function resize(dimensions) { + var canvasImage = this.getCanvasImage(); + var width = canvasImage.width, + height = canvasImage.height, + scaleX = canvasImage.scaleX, + scaleY = canvasImage.scaleY; + var dimensionsWidth = dimensions.width, + dimensionsHeight = dimensions.height; + var scaleValues = { + scaleX: dimensionsWidth ? dimensionsWidth / width : scaleX, + scaleY: dimensionsHeight ? dimensionsHeight / height : scaleY + }; + + if (scaleX !== scaleValues.scaleX || scaleY !== scaleValues.scaleY) { + canvasImage.set(scaleValues).setCoords(); + this._dimensions = { + width: canvasImage.width * canvasImage.scaleX, + height: canvasImage.height * canvasImage.scaleY + }; + } + + this.adjustCanvasDimensionBase(); + return promise_default().resolve(); + } + /** + * Start resizing + */ + + }, { + key: "start", + value: function start() { + var dimensions = this.getCurrentDimensions(); + this.setOriginalDimensions(dimensions); + } + /** + * End resizing + */ + + }, { + key: "end", + value: function end() {} + }]); + + return Resize; +}(component); + +/* harmony default export */ var component_resize = (resize_Resize); +;// CONCATENATED MODULE: ./src/js/drawingMode/resize.js + + + + + + + +function drawingMode_resize_createSuper(Derived) { var hasNativeReflectConstruct = drawingMode_resize_isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = construct_default()(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } + +function drawingMode_resize_isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !(construct_default())) return false; if ((construct_default()).sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(construct_default()(Boolean, [], function () {})); return true; } catch (e) { return false; } } + + + +/** + * ResizeDrawingMode class + * @class + * @ignore + */ + +var ResizeDrawingMode = /*#__PURE__*/function (_DrawingMode) { + _inherits(ResizeDrawingMode, _DrawingMode); + + var _super = drawingMode_resize_createSuper(ResizeDrawingMode); + + function ResizeDrawingMode() { + _classCallCheck(this, ResizeDrawingMode); + + return _super.call(this, drawingModes.RESIZE); + } + /** + * start this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + + _createClass(ResizeDrawingMode, [{ + key: "start", + value: function start(graphics) { + var resize = graphics.getComponent(componentNames.RESIZE); + resize.start(); + } + /** + * stop this drawing mode + * @param {Graphics} graphics - Graphics instance + * @override + */ + + }, { + key: "end", + value: function end(graphics) { + var resize = graphics.getComponent(componentNames.RESIZE); + resize.end(); + } + }]); + + return ResizeDrawingMode; +}(drawingMode); + +/* harmony default export */ var drawingMode_resize = (ResizeDrawingMode); +;// CONCATENATED MODULE: ./src/js/graphics.js + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +var DEFAULT_CSS_MAX_WIDTH = 1000; +var DEFAULT_CSS_MAX_HEIGHT = 800; +var EXTRA_PX_FOR_PASTE = 10; +var cssOnly = { + cssOnly: true +}; +var backstoreOnly = { + backstoreOnly: true +}; +/** + * Graphics class + * @class + * @param {string|HTMLElement} wrapper - Wrapper's element or selector + * @param {Object} [option] - Canvas max width & height of css + * @param {number} option.cssMaxWidth - Canvas css-max-width + * @param {number} option.cssMaxHeight - Canvas css-max-height + * @ignore + */ + +var Graphics = /*#__PURE__*/function () { + function Graphics(element) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + cssMaxWidth = _ref.cssMaxWidth, + cssMaxHeight = _ref.cssMaxHeight; + + _classCallCheck(this, Graphics); + + /** + * Fabric image instance + * @type {fabric.Image} + */ + this.canvasImage = null; + /** + * Max width of canvas elements + * @type {number} + */ + + this.cssMaxWidth = cssMaxWidth || DEFAULT_CSS_MAX_WIDTH; + /** + * Max height of canvas elements + * @type {number} + */ + + this.cssMaxHeight = cssMaxHeight || DEFAULT_CSS_MAX_HEIGHT; + /** + * cropper Selection Style + * @type {Object} + */ + + this.cropSelectionStyle = {}; + /** + * target fabric object for copy paste feature + * @type {fabric.Object} + * @private + */ + + this.targetObjectForCopyPaste = null; + /** + * Image name + * @type {string} + */ + + this.imageName = ''; + /** + * Object Map + * @type {Object} + * @private + */ + + this._objects = {}; + /** + * Fabric-Canvas instance + * @type {fabric.Canvas} + * @private + */ + + this._canvas = null; + /** + * Drawing mode + * @type {string} + * @private + */ + + this._drawingMode = drawingModes.NORMAL; + /** + * DrawingMode map + * @type {Object.} + * @private + */ + + this._drawingModeMap = {}; + /** + * Component map + * @type {Object.} + * @private + */ + + this._componentMap = {}; + /** + * fabric event handlers + * @type {Object.} + * @private + */ + + this._handler = { + onMouseDown: bind_default()(_context = this._onMouseDown).call(_context, this), + onObjectAdded: bind_default()(_context2 = this._onObjectAdded).call(_context2, this), + onObjectRemoved: bind_default()(_context3 = this._onObjectRemoved).call(_context3, this), + onObjectMoved: bind_default()(_context4 = this._onObjectMoved).call(_context4, this), + onObjectScaled: bind_default()(_context5 = this._onObjectScaled).call(_context5, this), + onObjectModified: bind_default()(_context6 = this._onObjectModified).call(_context6, this), + onObjectRotated: bind_default()(_context7 = this._onObjectRotated).call(_context7, this), + onObjectSelected: bind_default()(_context8 = this._onObjectSelected).call(_context8, this), + onPathCreated: bind_default()(_context9 = this._onPathCreated).call(_context9, this), + onSelectionCleared: bind_default()(_context10 = this._onSelectionCleared).call(_context10, this), + onSelectionCreated: bind_default()(_context11 = this._onSelectionCreated).call(_context11, this) + }; + + this._setObjectCachingToFalse(); + + this._setCanvasElement(element); + + this._createDrawingModeInstances(); + + this._createComponents(); + + this._attachCanvasEvents(); + + this._attachZoomEvents(); + } + /** + * Destroy canvas element + */ + + + _createClass(Graphics, [{ + key: "destroy", + value: function destroy() { + var wrapperEl = this._canvas.wrapperEl; + + this._canvas.clear(); + + wrapperEl.parentNode.removeChild(wrapperEl); + + this._detachZoomEvents(); + } + /** + * Attach zoom events + */ + + }, { + key: "_attachZoomEvents", + value: function _attachZoomEvents() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.attachKeyboardZoomEvents(); + } + /** + * Detach zoom events + */ + + }, { + key: "_detachZoomEvents", + value: function _detachZoomEvents() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.detachKeyboardZoomEvents(); + } + /** + * Deactivates all objects on canvas + * @returns {Graphics} this + */ + + }, { + key: "deactivateAll", + value: function deactivateAll() { + this._canvas.discardActiveObject(); + + return this; + } + /** + * Renders all objects on canvas + * @returns {Graphics} this + */ + + }, { + key: "renderAll", + value: function renderAll() { + this._canvas.renderAll(); + + return this; + } + /** + * Adds objects on canvas + * @param {Object|Array} objects - objects + */ + + }, { + key: "add", + value: function add(objects) { + var _this$_canvas; + + var theArgs = []; + + if (isArray_default()(objects)) { + theArgs = objects; + } else { + theArgs.push(objects); + } + + (_this$_canvas = this._canvas).add.apply(_this$_canvas, _toConsumableArray(theArgs)); + } + /** + * Removes the object or group + * @param {Object} target - graphics object or group + * @returns {boolean} true if contains or false + */ + + }, { + key: "contains", + value: function contains(target) { + return this._canvas.contains(target); + } + /** + * Gets all objects or group + * @returns {Array} all objects, shallow copy + */ + + }, { + key: "getObjects", + value: function getObjects() { + var _context12; + + return slice_default()(_context12 = this._canvas.getObjects()).call(_context12); + } + /** + * Get an object by id + * @param {number} id - object id + * @returns {fabric.Object} object corresponding id + */ + + }, { + key: "getObject", + value: function getObject(id) { + return this._objects[id]; + } + /** + * Removes the object or group + * @param {Object} target - graphics object or group + */ + + }, { + key: "remove", + value: function remove(target) { + this._canvas.remove(target); + } + /** + * Removes all object or group + * @param {boolean} includesBackground - remove the background image or not + * @returns {Array} all objects array which is removed + */ + + }, { + key: "removeAll", + value: function removeAll(includesBackground) { + var _context13; + + var canvas = this._canvas; + + var objects = slice_default()(_context13 = canvas.getObjects()).call(_context13); + + canvas.remove.apply(canvas, _toConsumableArray(this._canvas.getObjects())); + + if (includesBackground) { + canvas.clear(); + } + + return objects; + } + /** + * Removes an object or group by id + * @param {number} id - object id + * @returns {Array} removed objects + */ + + }, { + key: "removeObjectById", + value: function removeObjectById(id) { + var objects = []; + var canvas = this._canvas; + var target = this.getObject(id); + var isValidGroup = target && target.isType('group') && !target.isEmpty(); + + if (isValidGroup) { + canvas.discardActiveObject(); // restore states for each objects + + target.forEachObject(function (obj) { + objects.push(obj); + canvas.remove(obj); + }); + } else if (canvas.contains(target)) { + objects.push(target); + canvas.remove(target); + } + + return objects; + } + /** + * Get an id by object instance + * @param {fabric.Object} object object + * @returns {number} object id if it exists or null + */ + + }, { + key: "getObjectId", + value: function getObjectId(object) { + var key = null; + + for (key in this._objects) { + if (this._objects.hasOwnProperty(key)) { + if (object === this._objects[key]) { + return key; + } + } + } + + return null; + } + /** + * Gets an active object or group + * @returns {Object} active object or group instance + */ + + }, { + key: "getActiveObject", + value: function getActiveObject() { + return this._canvas._activeObject; + } + /** + * Returns the object ID to delete the object. + * @returns {number} object id for remove + */ + + }, { + key: "getActiveObjectIdForRemove", + value: function getActiveObjectIdForRemove() { + var activeObject = this.getActiveObject(); + var type = activeObject.type, + left = activeObject.left, + top = activeObject.top; + var isSelection = type === 'activeSelection'; + + if (isSelection) { + var group = new fabric.fabric.Group(_toConsumableArray(activeObject.getObjects()), { + left: left, + top: top + }); + return this._addFabricObject(group); + } + + return this.getObjectId(activeObject); + } + /** + * Verify that you are ready to erase the object. + * @returns {boolean} ready for object remove + */ + + }, { + key: "isReadyRemoveObject", + value: function isReadyRemoveObject() { + var activeObject = this.getActiveObject(); + return activeObject && !activeObject.isEditing; + } + /** + * Gets an active group object + * @returns {Object} active group object instance + */ + + }, { + key: "getActiveObjects", + value: function getActiveObjects() { + var activeObject = this._canvas._activeObject; + return activeObject && activeObject.type === 'activeSelection' ? activeObject : null; + } + /** + * Get Active object Selection from object ids + * @param {Array.} objects - fabric objects + * @returns {Object} target - target object group + */ + + }, { + key: "getActiveSelectionFromObjects", + value: function getActiveSelectionFromObjects(objects) { + var canvas = this.getCanvas(); + return new fabric.fabric.ActiveSelection(objects, { + canvas: canvas + }); + } + /** + * Activates an object or group + * @param {Object} target - target object or group + */ + + }, { + key: "setActiveObject", + value: function setActiveObject(target) { + this._canvas.setActiveObject(target); + } + /** + * Set Crop selection style + * @param {Object} style - Selection styles + */ + + }, { + key: "setCropSelectionStyle", + value: function setCropSelectionStyle(style) { + this.cropSelectionStyle = style; + } + /** + * Get component + * @param {string} name - Component name + * @returns {Component} + */ + + }, { + key: "getComponent", + value: function getComponent(name) { + return this._componentMap[name]; + } + /** + * Get current drawing mode + * @returns {string} + */ + + }, { + key: "getDrawingMode", + value: function getDrawingMode() { + return this._drawingMode; + } + /** + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE', 'TEXT', 'SHAPE' + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' + * @param {Number} [option.width] brush width + * @param {String} [option.color] brush color + * @returns {boolean} true if success or false + */ + + }, { + key: "startDrawingMode", + value: function startDrawingMode(mode, option) { + if (this._isSameDrawingMode(mode)) { + return true; + } // If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + + + this.stopDrawingMode(); + + var drawingModeInstance = this._getDrawingModeInstance(mode); + + if (drawingModeInstance && drawingModeInstance.start) { + drawingModeInstance.start(this, option); + this._drawingMode = mode; + } + + return !!drawingModeInstance; + } + /** + * Stop the current drawing mode and back to the 'NORMAL' mode + */ + + }, { + key: "stopDrawingMode", + value: function stopDrawingMode() { + if (this._isSameDrawingMode(drawingModes.NORMAL)) { + return; + } + + var drawingModeInstance = this._getDrawingModeInstance(this.getDrawingMode()); + + if (drawingModeInstance && drawingModeInstance.end) { + drawingModeInstance.end(this); + } + + this._drawingMode = drawingModes.NORMAL; + } + /** + * Change zoom of canvas + * @param {{x: number, y: number}} center - center of zoom + * @param {number} zoomLevel - zoom level + */ + + }, { + key: "zoom", + value: function zoom(_ref2, zoomLevel) { + var x = _ref2.x, + y = _ref2.y; + var zoom = this.getComponent(componentNames.ZOOM); + zoom.zoom({ + x: x, + y: y + }, zoomLevel); + } + /** + * Get zoom mode + * @returns {string} + */ + + }, { + key: "getZoomMode", + value: function getZoomMode() { + var zoom = this.getComponent(componentNames.ZOOM); + return zoom.mode; + } + /** + * Start zoom-in mode + */ + + }, { + key: "startZoomInMode", + value: function startZoomInMode() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.startZoomInMode(); + } + /** + * Stop zoom-in mode + */ + + }, { + key: "endZoomInMode", + value: function endZoomInMode() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.endZoomInMode(); + } + /** + * Zoom out one step + */ + + }, { + key: "zoomOut", + value: function zoomOut() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.zoomOut(); + } + /** + * Start hand mode + */ + + }, { + key: "startHandMode", + value: function startHandMode() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.startHandMode(); + } + /** + * Stop hand mode + */ + + }, { + key: "endHandMode", + value: function endHandMode() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.endHandMode(); + } + /** + * Zoom reset + */ + + }, { + key: "resetZoom", + value: function resetZoom() { + var zoom = this.getComponent(componentNames.ZOOM); + zoom.resetZoom(); + } + /** + * To data url from canvas + * @param {Object} options - options for toDataURL + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 + * @returns {string} A DOMString containing the requested data URI. + */ + + }, { + key: "toDataURL", + value: function toDataURL(options) { + var cropper = this.getComponent(componentNames.CROPPER); + cropper.changeVisibility(false); + + var dataUrl = this._canvas && this._canvas.toDataURL(options); + + cropper.changeVisibility(true); + return dataUrl; + } + /** + * Save image(background) of canvas + * @param {string} name - Name of image + * @param {?fabric.Image} canvasImage - Fabric image instance + */ + + }, { + key: "setCanvasImage", + value: function setCanvasImage(name, canvasImage) { + if (canvasImage) { + stamp(canvasImage); + } + + this.imageName = name; + this.canvasImage = canvasImage; + } + /** + * Set css max dimension + * @param {{width: number, height: number}} maxDimension - Max width & Max height + */ + + }, { + key: "setCssMaxDimension", + value: function setCssMaxDimension(maxDimension) { + this.cssMaxWidth = maxDimension.width || this.cssMaxWidth; + this.cssMaxHeight = maxDimension.height || this.cssMaxHeight; + } + /** + * Adjust canvas dimension with scaling image + */ + + }, { + key: "adjustCanvasDimension", + value: function adjustCanvasDimension() { + this.adjustCanvasDimensionBase(this.canvasImage.scale(1)); + } + }, { + key: "adjustCanvasDimensionBase", + value: function adjustCanvasDimensionBase() { + var canvasImage = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + + if (!canvasImage) { + canvasImage = this.canvasImage; + } + + var _canvasImage$getBound = canvasImage.getBoundingRect(), + width = _canvasImage$getBound.width, + height = _canvasImage$getBound.height; + + var maxDimension = this._calcMaxDimension(width, height); + + this.setCanvasCssDimension({ + width: '100%', + height: '100%', + // Set height '' for IE9 + 'max-width': "".concat(maxDimension.width, "px"), + 'max-height': "".concat(maxDimension.height, "px") + }); + this.setCanvasBackstoreDimension({ + width: width, + height: height + }); + + this._canvas.centerObject(canvasImage); + } + /** + * Set canvas dimension - css only + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} + * @param {Object} dimension - Canvas css dimension + */ + + }, { + key: "setCanvasCssDimension", + value: function setCanvasCssDimension(dimension) { + this._canvas.setDimensions(dimension, cssOnly); + } + /** + * Set canvas dimension - backstore only + * {@link http://fabricjs.com/docs/fabric.Canvas.html#setDimensions} + * @param {Object} dimension - Canvas backstore dimension + */ + + }, { + key: "setCanvasBackstoreDimension", + value: function setCanvasBackstoreDimension(dimension) { + this._canvas.setDimensions(dimension, backstoreOnly); + } + /** + * Set image properties + * {@link http://fabricjs.com/docs/fabric.Image.html#set} + * @param {Object} setting - Image properties + * @param {boolean} [withRendering] - If true, The changed image will be reflected in the canvas + */ + + }, { + key: "setImageProperties", + value: function setImageProperties(setting, withRendering) { + var canvasImage = this.canvasImage; + + if (!canvasImage) { + return; + } + + canvasImage.set(setting).setCoords(); + + if (withRendering) { + this._canvas.renderAll(); + } + } + /** + * Returns canvas element of fabric.Canvas[[lower-canvas]] + * @returns {HTMLCanvasElement} + */ + + }, { + key: "getCanvasElement", + value: function getCanvasElement() { + return this._canvas.getElement(); + } + /** + * Get fabric.Canvas instance + * @returns {fabric.Canvas} + */ + + }, { + key: "getCanvas", + value: function getCanvas() { + return this._canvas; + } + /** + * Get canvasImage (fabric.Image instance) + * @returns {fabric.Image} + */ + + }, { + key: "getCanvasImage", + value: function getCanvasImage() { + return this.canvasImage; + } + /** + * Get image name + * @returns {string} + */ + + }, { + key: "getImageName", + value: function getImageName() { + return this.imageName; + } + /** + * Add image object on canvas + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + */ + + }, { + key: "addImageObject", + value: function addImageObject(imgUrl) { + var _context14, + _this = this; + + var callback = bind_default()(_context14 = this._callbackAfterLoadingImageObject).call(_context14, this); + + return new (promise_default())(function (resolve) { + fabric.fabric.Image.fromURL(imgUrl, function (image) { + callback(image); + resolve(_this.createObjectProperties(image)); + }, { + crossOrigin: 'Anonymous' + }); + }); + } + /** + * Get center position of canvas + * @returns {Object} {left, top} + */ + + }, { + key: "getCenter", + value: function getCenter() { + return this._canvas.getCenter(); + } + /** + * Get cropped rect + * @returns {Object} rect + */ + + }, { + key: "getCropzoneRect", + value: function getCropzoneRect() { + return this.getComponent(componentNames.CROPPER).getCropzoneRect(); + } + /** + * Get cropped rect + * @param {number} [mode] cropzone rect mode + */ + + }, { + key: "setCropzoneRect", + value: function setCropzoneRect(mode) { + this.getComponent(componentNames.CROPPER).setCropzoneRect(mode); + } + /** + * Get cropped image data + * @param {Object} cropRect cropzone rect + * @param {Number} cropRect.left left position + * @param {Number} cropRect.top top position + * @param {Number} cropRect.width width + * @param {Number} cropRect.height height + * @returns {?{imageName: string, url: string}} cropped Image data + */ + + }, { + key: "getCroppedImageData", + value: function getCroppedImageData(cropRect) { + return this.getComponent(componentNames.CROPPER).getCroppedImageData(cropRect); + } + /** + * Set brush option + * @param {Object} option brush option + * @param {Number} option.width width + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' + */ + + }, { + key: "setBrush", + value: function setBrush(option) { + var drawingMode = this._drawingMode; + var compName = componentNames.FREE_DRAWING; + + if (drawingMode === drawingModes.LINE_DRAWING) { + compName = componentNames.LINE; + } + + this.getComponent(compName).setBrush(option); + } + /** + * Set states of current drawing shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + */ + + }, { + key: "setDrawingShape", + value: function setDrawingShape(type, options) { + this.getComponent(componentNames.SHAPE).setStates(type, options); + } + /** + * Set style of current drawing icon + * @param {string} type - icon type (ex: 'icon-arrow', 'icon-star') + * @param {Object} [iconColor] - Icon color + */ + + }, { + key: "setIconStyle", + value: function setIconStyle(type, iconColor) { + this.getComponent(componentNames.ICON).setStates(type, iconColor); + } + /** + * Register icon paths + * @param {Object} pathInfos - Path infos + * @param {string} pathInfos.key - key + * @param {string} pathInfos.value - value + */ + + }, { + key: "registerPaths", + value: function registerPaths(pathInfos) { + this.getComponent(componentNames.ICON).registerPaths(pathInfos); + } + /** + * Change cursor style + * @param {string} cursorType - cursor type + */ + + }, { + key: "changeCursor", + value: function changeCursor(cursorType) { + var canvas = this.getCanvas(); + canvas.defaultCursor = cursorType; + canvas.renderAll(); + } + /** + * Whether it has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + + }, { + key: "hasFilter", + value: function hasFilter(type) { + return this.getComponent(componentNames.FILTER).hasFilter(type); + } + /** + * Set selection style of fabric object by init option + * @param {Object} styles - Selection styles + */ + + }, { + key: "setSelectionStyle", + value: function setSelectionStyle(styles) { + extend_default()(fObjectOptions.SELECTION_STYLE, styles); + } + /** + * Set object properties + * @param {number} id - object id + * @param {Object} props - props + * @param {string} [props.fill] Color + * @param {string} [props.fontFamily] Font type for text + * @param {number} [props.fontSize] Size + * @param {string} [props.fontStyle] Type of inclination (normal / italic) + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [props.textAlign] Type of text align (left / center / right) + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) + * @returns {Object} applied properties + */ + + }, { + key: "setObjectProperties", + value: function setObjectProperties(id, props) { + var object = this.getObject(id); + var clone = extend_default()({}, props); + object.set(clone); + object.setCoords(); + this.getCanvas().renderAll(); + return clone; + } + /** + * Get object properties corresponding key + * @param {number} id - object id + * @param {Array|ObjectProps|string} keys - property's key + * @returns {Object} properties + */ + + }, { + key: "getObjectProperties", + value: function getObjectProperties(id, keys) { + var object = this.getObject(id); + var props = {}; + + if (isString_default()(keys)) { + props[keys] = object[keys]; + } else if (isArray_default()(keys)) { + forEachArray_default()(keys, function (value) { + props[value] = object[value]; + }); + } else { + forEachOwnProperties_default()(keys, function (value, key) { + props[key] = object[key]; + }); + } + + return props; + } + /** + * Get object position by originX, originY + * @param {number} id - object id + * @param {string} originX - can be 'left', 'center', 'right' + * @param {string} originY - can be 'top', 'center', 'bottom' + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null + */ + + }, { + key: "getObjectPosition", + value: function getObjectPosition(id, originX, originY) { + var targetObj = this.getObject(id); + + if (!targetObj) { + return null; + } + + return targetObj.getPointByOrigin(originX, originY); + } + /** + * Set object position by originX, originY + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {boolean} true if target id is valid or false + */ + + }, { + key: "setObjectPosition", + value: function setObjectPosition(id, posInfo) { + var targetObj = this.getObject(id); + var x = posInfo.x, + y = posInfo.y, + originX = posInfo.originX, + originY = posInfo.originY; + + if (!targetObj) { + return false; + } + + var targetOrigin = targetObj.getPointByOrigin(originX, originY); + var centerOrigin = targetObj.getPointByOrigin('center', 'center'); + var diffX = centerOrigin.x - targetOrigin.x; + var diffY = centerOrigin.y - targetOrigin.y; + targetObj.set({ + left: x + diffX, + top: y + diffY + }); + targetObj.setCoords(); + return true; + } + /** + * Get the canvas size + * @returns {Object} {{width: number, height: number}} image size + */ + + }, { + key: "getCanvasSize", + value: function getCanvasSize() { + var image = this.getCanvasImage(); + return { + width: image ? image.width : 0, + height: image ? image.height : 0 + }; + } + /** + * Create fabric static canvas + * @returns {Object} {{width: number, height: number}} image size + */ + + }, { + key: "createStaticCanvas", + value: function createStaticCanvas() { + var staticCanvas = new fabric.fabric.StaticCanvas(); + staticCanvas.set({ + enableRetinaScaling: false + }); + return staticCanvas; + } + /** + * Get a DrawingMode instance + * @param {string} modeName - DrawingMode Class Name + * @returns {DrawingMode} DrawingMode instance + * @private + */ + + }, { + key: "_getDrawingModeInstance", + value: function _getDrawingModeInstance(modeName) { + return this._drawingModeMap[modeName]; + } + /** + * Set object caching to false. This brought many bugs when draw Shape & cropzone + * @see http://fabricjs.com/fabric-object-caching + * @private + */ + + }, { + key: "_setObjectCachingToFalse", + value: function _setObjectCachingToFalse() { + fabric.fabric.Object.prototype.objectCaching = false; + } + /** + * Set canvas element to fabric.Canvas + * @param {Element|string} element - Wrapper or canvas element or selector + * @private + */ + + }, { + key: "_setCanvasElement", + value: function _setCanvasElement(element) { + var selectedElement; + var canvasElement; + + if (element.nodeType) { + selectedElement = element; + } else { + selectedElement = document.querySelector(element); + } + + if (selectedElement.nodeName.toUpperCase() !== 'CANVAS') { + canvasElement = document.createElement('canvas'); + selectedElement.appendChild(canvasElement); + } + + this._canvas = new fabric.fabric.Canvas(canvasElement, { + containerClass: 'tui-image-editor-canvas-container', + enableRetinaScaling: false + }); + } + /** + * Creates DrawingMode instances + * @private + */ + + }, { + key: "_createDrawingModeInstances", + value: function _createDrawingModeInstances() { + this._register(this._drawingModeMap, new drawingMode_cropper()); + + this._register(this._drawingModeMap, new drawingMode_freeDrawing()); + + this._register(this._drawingModeMap, new lineDrawing()); + + this._register(this._drawingModeMap, new drawingMode_shape()); + + this._register(this._drawingModeMap, new drawingMode_text()); + + this._register(this._drawingModeMap, new drawingMode_icon()); + + this._register(this._drawingModeMap, new drawingMode_zoom()); + + this._register(this._drawingModeMap, new drawingMode_resize()); + } + /** + * Create components + * @private + */ + + }, { + key: "_createComponents", + value: function _createComponents() { + this._register(this._componentMap, new imageLoader(this)); + + this._register(this._componentMap, new cropper(this)); + + this._register(this._componentMap, new component_flip(this)); + + this._register(this._componentMap, new rotation(this)); + + this._register(this._componentMap, new freeDrawing(this)); + + this._register(this._componentMap, new line(this)); + + this._register(this._componentMap, new component_text(this)); + + this._register(this._componentMap, new component_icon(this)); + + this._register(this._componentMap, new component_filter(this)); + + this._register(this._componentMap, new shape_Shape(this)); + + this._register(this._componentMap, new zoom(this)); + + this._register(this._componentMap, new component_resize(this)); + } + /** + * Register component + * @param {Object} map - map object + * @param {Object} module - module which has getName method + * @private + */ + + }, { + key: "_register", + value: function _register(map, module) { + map[module.getName()] = module; + } + /** + * Get the current drawing mode is same with given mode + * @param {string} mode drawing mode + * @returns {boolean} true if same or false + */ + + }, { + key: "_isSameDrawingMode", + value: function _isSameDrawingMode(mode) { + return this.getDrawingMode() === mode; + } + /** + * Calculate max dimension of canvas + * The css-max dimension is dynamically decided with maintaining image ratio + * The css-max dimension is lower than canvas dimension (attribute of canvas, not css) + * @param {number} width - Canvas width + * @param {number} height - Canvas height + * @returns {{width: number, height: number}} - Max width & Max height + * @private + */ + + }, { + key: "_calcMaxDimension", + value: function _calcMaxDimension(width, height) { + var wScaleFactor = this.cssMaxWidth / width; + var hScaleFactor = this.cssMaxHeight / height; + var cssMaxWidth = Math.min(width, this.cssMaxWidth); + var cssMaxHeight = Math.min(height, this.cssMaxHeight); + + if (wScaleFactor < 1 && wScaleFactor < hScaleFactor) { + cssMaxWidth = width * wScaleFactor; + cssMaxHeight = height * wScaleFactor; + } else if (hScaleFactor < 1 && hScaleFactor < wScaleFactor) { + cssMaxWidth = width * hScaleFactor; + cssMaxHeight = height * hScaleFactor; + } + + return { + width: Math.floor(cssMaxWidth), + height: Math.floor(cssMaxHeight) + }; + } + /** + * Callback function after loading image + * @param {fabric.Image} obj - Fabric image object + * @private + */ + + }, { + key: "_callbackAfterLoadingImageObject", + value: function _callbackAfterLoadingImageObject(obj) { + var centerPos = this.getCanvasImage().getCenterPoint(); + obj.set(fObjectOptions.SELECTION_STYLE); + obj.set({ + left: centerPos.x, + top: centerPos.y, + crossOrigin: 'Anonymous' + }); + this.getCanvas().add(obj).setActiveObject(obj); + } + /** + * Attach canvas's events + */ + + }, { + key: "_attachCanvasEvents", + value: function _attachCanvasEvents() { + var canvas = this._canvas; + var handler = this._handler; + canvas.on({ + 'mouse:down': handler.onMouseDown, + 'object:added': handler.onObjectAdded, + 'object:removed': handler.onObjectRemoved, + 'object:moving': handler.onObjectMoved, + 'object:scaling': handler.onObjectScaled, + 'object:modified': handler.onObjectModified, + 'object:rotating': handler.onObjectRotated, + 'path:created': handler.onPathCreated, + 'selection:cleared': handler.onSelectionCleared, + 'selection:created': handler.onSelectionCreated, + 'selection:updated': handler.onObjectSelected + }); + } + /** + * "mouse:down" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onMouseDown", + value: function _onMouseDown(fEvent) { + var _this2 = this; + + var event = fEvent.e, + target = fEvent.target; + + var originPointer = this._canvas.getPointer(event); + + if (target) { + var type = target.type; + var undoData = makeSelectionUndoData(target, function (item) { + return makeSelectionUndoDatum(_this2.getObjectId(item), item, type === 'activeSelection'); + }); + setCachedUndoDataForDimension(undoData); + } + + this.fire(eventNames.MOUSE_DOWN, event, originPointer); + } + /** + * "object:added" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectAdded", + value: function _onObjectAdded(fEvent) { + var obj = fEvent.target; + + if (obj.isType('cropzone')) { + return; + } + + this._addFabricObject(obj); + } + /** + * "object:removed" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectRemoved", + value: function _onObjectRemoved(fEvent) { + var obj = fEvent.target; + + this._removeFabricObject(stamp(obj)); + } + /** + * "object:moving" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectMoved", + value: function _onObjectMoved(fEvent) { + var _this3 = this; + + this._lazyFire(eventNames.OBJECT_MOVED, function (object) { + return _this3.createObjectProperties(object); + }, fEvent.target); + } + /** + * "object:scaling" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectScaled", + value: function _onObjectScaled(fEvent) { + var _this4 = this; + + this._lazyFire(eventNames.OBJECT_SCALED, function (object) { + return _this4.createObjectProperties(object); + }, fEvent.target); + } + /** + * "object:modified" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectModified", + value: function _onObjectModified(fEvent) { + var target = fEvent.target; + + if (target.type === 'activeSelection') { + var items = target.getObjects(); + + for_each_default()(items).call(items, function (item) { + return item.fire('modifiedInGroup', target); + }); + } + + this.fire(eventNames.OBJECT_MODIFIED, target, this.getObjectId(target)); + } + /** + * "object:rotating" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectRotated", + value: function _onObjectRotated(fEvent) { + var _this5 = this; + + this._lazyFire(eventNames.OBJECT_ROTATED, function (object) { + return _this5.createObjectProperties(object); + }, fEvent.target); + } + /** + * Lazy event emitter + * @param {string} eventName - event name + * @param {Function} paramsMaker - make param function + * @param {Object} [target] - Object of the event owner. + * @private + */ + + }, { + key: "_lazyFire", + value: function _lazyFire(eventName, paramsMaker, target) { + var _this6 = this; + + var existEventDelegation = target && target.canvasEventDelegation; + var delegationState = existEventDelegation ? target.canvasEventDelegation(eventName) : 'none'; + + if (delegationState === 'unregistered') { + target.canvasEventRegister(eventName, function (object) { + _this6.fire(eventName, paramsMaker(object)); + }); + } + + if (delegationState === 'none') { + this.fire(eventName, paramsMaker(target)); + } + } + /** + * "object:selected" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onObjectSelected", + value: function _onObjectSelected(fEvent) { + var target = fEvent.target; + var params = this.createObjectProperties(target); + this.fire(eventNames.OBJECT_ACTIVATED, params); + } + /** + * "path:created" canvas event handler + * @param {{path: fabric.Path}} obj - Path object + * @private + */ + + }, { + key: "_onPathCreated", + value: function _onPathCreated(obj) { + var _obj$path$getCenterPo = obj.path.getCenterPoint(), + left = _obj$path$getCenterPo.x, + top = _obj$path$getCenterPo.y; + + obj.path.set(extend_default()({ + left: left, + top: top + }, fObjectOptions.SELECTION_STYLE)); + var params = this.createObjectProperties(obj.path); + this.fire(eventNames.ADD_OBJECT, params); + } + /** + * "selction:cleared" canvas event handler + * @private + */ + + }, { + key: "_onSelectionCleared", + value: function _onSelectionCleared() { + this.fire(eventNames.SELECTION_CLEARED); + } + /** + * "selction:created" canvas event handler + * @param {{target: fabric.Object, e: MouseEvent}} fEvent - Fabric event + * @private + */ + + }, { + key: "_onSelectionCreated", + value: function _onSelectionCreated(fEvent) { + var target = fEvent.target; + var params = this.createObjectProperties(target); + this.fire(eventNames.OBJECT_ACTIVATED, params); + this.fire(eventNames.SELECTION_CREATED, fEvent.target); + } + /** + * Canvas discard selection all + */ + + }, { + key: "discardSelection", + value: function discardSelection() { + this._canvas.discardActiveObject(); + + this._canvas.renderAll(); + } + /** + * Canvas Selectable status change + * @param {boolean} selectable - expect status + */ + + }, { + key: "changeSelectableAll", + value: function changeSelectableAll(selectable) { + this._canvas.forEachObject(function (obj) { + obj.selectable = selectable; + obj.hoverCursor = selectable ? 'move' : 'crosshair'; + }); + } + /** + * Return object's properties + * @param {fabric.Object} obj - fabric object + * @returns {Object} properties object + */ + + }, { + key: "createObjectProperties", + value: function createObjectProperties(obj) { + var predefinedKeys = ['left', 'top', 'width', 'height', 'fill', 'stroke', 'strokeWidth', 'opacity', 'angle']; + var props = { + id: stamp(obj), + type: obj.type + }; + extend_default()(props, getProperties(obj, predefinedKeys)); + + if (includes(['i-text', 'text'], obj.type)) { + extend_default()(props, this._createTextProperties(obj, props)); + } else if (includes(['rect', 'triangle', 'circle'], obj.type)) { + var shapeComp = this.getComponent(componentNames.SHAPE); + extend_default()(props, { + fill: shapeComp.makeFillPropertyForUserEvent(obj) + }); + } + + return props; + } + /** + * Get text object's properties + * @param {fabric.Object} obj - fabric text object + * @param {Object} props - properties + * @returns {Object} properties object + */ + + }, { + key: "_createTextProperties", + value: function _createTextProperties(obj) { + var predefinedKeys = ['text', 'fontFamily', 'fontSize', 'fontStyle', 'textAlign', 'textDecoration', 'fontWeight']; + var props = {}; + extend_default()(props, getProperties(obj, predefinedKeys)); + return props; + } + /** + * Add object array by id + * @param {fabric.Object} obj - fabric object + * @returns {number} object id + */ + + }, { + key: "_addFabricObject", + value: function _addFabricObject(obj) { + var id = stamp(obj); + this._objects[id] = obj; + return id; + } + /** + * Remove an object in array yb id + * @param {number} id - object id + */ + + }, { + key: "_removeFabricObject", + value: function _removeFabricObject(id) { + delete this._objects[id]; + } + /** + * Reset targetObjectForCopyPaste value from activeObject + */ + + }, { + key: "resetTargetObjectForCopyPaste", + value: function resetTargetObjectForCopyPaste() { + var activeObject = this.getActiveObject(); + + if (activeObject) { + this.targetObjectForCopyPaste = activeObject; + } + } + /** + * Paste fabric object + * @returns {Promise} + */ + + }, { + key: "pasteObject", + value: function pasteObject() { + var _this7 = this; + + if (!this.targetObjectForCopyPaste) { + return promise_default().resolve([]); + } + + var targetObject = this.targetObjectForCopyPaste; + var isGroupSelect = targetObject.type === 'activeSelection'; + var targetObjects = isGroupSelect ? targetObject.getObjects() : [targetObject]; + var newTargetObject = null; + this.discardSelection(); + return this._cloneObject(targetObjects).then(function (addedObjects) { + if (addedObjects.length > 1) { + newTargetObject = _this7.getActiveSelectionFromObjects(addedObjects); + } else { + var _addedObjects = _slicedToArray(addedObjects, 1); + + newTargetObject = _addedObjects[0]; + } + + _this7.targetObjectForCopyPaste = newTargetObject; + + _this7.setActiveObject(newTargetObject); + }); + } + /** + * Clone object + * @param {fabric.Object} targetObjects - fabric object + * @returns {Promise} + * @private + */ + + }, { + key: "_cloneObject", + value: function _cloneObject(targetObjects) { + var _this8 = this; + + var addedObjects = map_default()(targetObjects).call(targetObjects, function (targetObject) { + return _this8._cloneObjectItem(targetObject); + }); + + return promise_default().all(addedObjects); + } + /** + * Clone object one item + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + + }, { + key: "_cloneObjectItem", + value: function _cloneObjectItem(targetObject) { + var _this9 = this; + + return this._copyFabricObjectForPaste(targetObject).then(function (clonedObject) { + var objectProperties = _this9.createObjectProperties(clonedObject); + + _this9.add(clonedObject); + + _this9.fire(eventNames.ADD_OBJECT, objectProperties); + + return clonedObject; + }); + } + /** + * Copy fabric object with Changed position for copy and paste + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + + }, { + key: "_copyFabricObjectForPaste", + value: function _copyFabricObjectForPaste(targetObject) { + var _this10 = this; + + var addExtraPx = function addExtraPx(value, isReverse) { + return isReverse ? value - EXTRA_PX_FOR_PASTE : value + EXTRA_PX_FOR_PASTE; + }; + + return this._copyFabricObject(targetObject).then(function (clonedObject) { + var left = clonedObject.left, + top = clonedObject.top, + width = clonedObject.width, + height = clonedObject.height; + + var _this10$getCanvasSize = _this10.getCanvasSize(), + canvasWidth = _this10$getCanvasSize.width, + canvasHeight = _this10$getCanvasSize.height; + + var rightEdge = left + width / 2; + var bottomEdge = top + height / 2; + clonedObject.set(extend_default()({ + left: addExtraPx(left, rightEdge + EXTRA_PX_FOR_PASTE > canvasWidth), + top: addExtraPx(top, bottomEdge + EXTRA_PX_FOR_PASTE > canvasHeight) + }, fObjectOptions.SELECTION_STYLE)); + return clonedObject; + }); + } + /** + * Copy fabric object + * @param {fabric.Object} targetObject - fabric object + * @returns {Promise} + * @private + */ + + }, { + key: "_copyFabricObject", + value: function _copyFabricObject(targetObject) { + var _this11 = this; + + return new (promise_default())(function (resolve) { + targetObject.clone(function (cloned) { + var shapeComp = _this11.getComponent(componentNames.SHAPE); + + if (isShape(cloned)) { + shapeComp.processForCopiedObject(cloned, targetObject); + } + + resolve(cloned); + }); + }); + } + /** + * Get current dimensions + * @returns {object} + */ + + }, { + key: "getCurrentDimensions", + value: function getCurrentDimensions() { + var resize = this.getComponent(componentNames.RESIZE); + return resize.getCurrentDimensions(); + } + /** + * Get original dimensions + * @returns {object} + */ + + }, { + key: "getOriginalDimensions", + value: function getOriginalDimensions() { + var resize = this.getComponent(componentNames.RESIZE); + return resize.getOriginalDimensions(); + } + /** + * Set original dimensions + * @param {object} dimensions - Dimensions + */ + + }, { + key: "setOriginalDimensions", + value: function setOriginalDimensions(dimensions) { + var resize = this.getComponent(componentNames.RESIZE); + resize.setOriginalDimensions(dimensions); + } + /** + * Resize Image + * @param {Object} dimensions - Resize dimensions + * @returns {Promise} + */ + + }, { + key: "resize", + value: function resize(dimensions) { + var resize = this.getComponent(componentNames.RESIZE); + return resize.resize(dimensions); + } + }]); + + return Graphics; +}(); + +customEvents_default().mixin(Graphics); +/* harmony default export */ var graphics = (Graphics); +;// CONCATENATED MODULE: ./src/js/imageEditor.js + + + + + + + + + + + + + + + + + + + + + +var MOUSE_DOWN = eventNames.MOUSE_DOWN, + OBJECT_MOVED = eventNames.OBJECT_MOVED, + OBJECT_SCALED = eventNames.OBJECT_SCALED, + OBJECT_ACTIVATED = eventNames.OBJECT_ACTIVATED, + OBJECT_ROTATED = eventNames.OBJECT_ROTATED, + OBJECT_ADDED = eventNames.OBJECT_ADDED, + imageEditor_OBJECT_MODIFIED = eventNames.OBJECT_MODIFIED, + imageEditor_ADD_TEXT = eventNames.ADD_TEXT, + ADD_OBJECT = eventNames.ADD_OBJECT, + imageEditor_TEXT_EDITING = eventNames.TEXT_EDITING, + TEXT_CHANGED = eventNames.TEXT_CHANGED, + ICON_CREATE_RESIZE = eventNames.ICON_CREATE_RESIZE, + ICON_CREATE_END = eventNames.ICON_CREATE_END, + SELECTION_CLEARED = eventNames.SELECTION_CLEARED, + SELECTION_CREATED = eventNames.SELECTION_CREATED, + ADD_OBJECT_AFTER = eventNames.ADD_OBJECT_AFTER; +/** + * Image filter result + * @typedef {object} FilterResult + * @property {string} type - filter type like 'mask', 'Grayscale' and so on + * @property {string} action - action type like 'add', 'remove' + */ + +/** + * Flip status + * @typedef {object} FlipStatus + * @property {boolean} flipX - x axis + * @property {boolean} flipY - y axis + * @property {Number} angle - angle + */ + +/** + * Rotation status + * @typedef {Number} RotateStatus + * @property {Number} angle - angle + */ + +/** + * Old and new Size + * @typedef {object} SizeChange + * @property {Number} oldWidth - old width + * @property {Number} oldHeight - old height + * @property {Number} newWidth - new width + * @property {Number} newHeight - new height + */ + +/** + * @typedef {string} ErrorMsg - {string} error message + */ + +/** + * @typedef {object} ObjectProps - graphics object properties + * @property {number} id - object id + * @property {string} type - object type + * @property {string} text - text content + * @property {(string | number)} left - Left + * @property {(string | number)} top - Top + * @property {(string | number)} width - Width + * @property {(string | number)} height - Height + * @property {string} fill - Color + * @property {string} stroke - Stroke + * @property {(string | number)} strokeWidth - StrokeWidth + * @property {string} fontFamily - Font type for text + * @property {number} fontSize - Font Size + * @property {string} fontStyle - Type of inclination (normal / italic) + * @property {string} fontWeight - Type of thicker or thinner looking (normal / bold) + * @property {string} textAlign - Type of text align (left / center / right) + * @property {string} textDecoration - Type of line (underline / line-through / overline) + */ + +/** + * Shape filter option + * @typedef {object.} ShapeFilterOption + */ + +/** + * Shape filter option + * @typedef {object} ShapeFillOption - fill option of shape + * @property {string} type - fill type ('color' or 'filter') + * @property {Array.} [filter] - {@link ShapeFilterOption} List. + * only applies to filter types + * (ex: \[\{pixelate: 20\}, \{blur: 0.3\}\]) + * @property {string} [color] - Shape foreground color (ex: '#fff', 'transparent') + */ + +/** + * Image editor + * @class + * @param {string|HTMLElement} wrapper - Wrapper's element or selector + * @param {Object} [options] - Canvas max width & height of css + * @param {number} [options.includeUI] - Use the provided UI + * @param {Object} [options.includeUI.loadImage] - Basic editing image + * @param {string} options.includeUI.loadImage.path - image path + * @param {string} options.includeUI.loadImage.name - image name + * @param {Object} [options.includeUI.theme] - Theme object + * @param {Array} [options.includeUI.menu] - It can be selected when only specific menu is used, Default values are \['crop', 'flip', 'rotate', 'draw', 'shape', 'icon', 'text', 'mask', 'filter'\]. + * @param {string} [options.includeUI.initMenu] - The first menu to be selected and started. + * @param {Object} [options.includeUI.uiSize] - ui size of editor + * @param {string} options.includeUI.uiSize.width - width of ui + * @param {string} options.includeUI.uiSize.height - height of ui + * @param {string} [options.includeUI.menuBarPosition=bottom] - Menu bar position('top', 'bottom', 'left', 'right') + * @param {number} options.cssMaxWidth - Canvas css-max-width + * @param {number} options.cssMaxHeight - Canvas css-max-height + * @param {Object} [options.selectionStyle] - selection style + * @param {string} [options.selectionStyle.cornerStyle] - selection corner style + * @param {number} [options.selectionStyle.cornerSize] - selection corner size + * @param {string} [options.selectionStyle.cornerColor] - selection corner color + * @param {string} [options.selectionStyle.cornerStrokeColor] = selection corner stroke color + * @param {boolean} [options.selectionStyle.transparentCorners] - selection corner transparent + * @param {number} [options.selectionStyle.lineWidth] - selection line width + * @param {string} [options.selectionStyle.borderColor] - selection border color + * @param {number} [options.selectionStyle.rotatingPointOffset] - selection rotating point length + * @param {Boolean} [options.usageStatistics=true] - Let us know the hostname. If you don't want to send the hostname, please set to false. + * @example + * var ImageEditor = require('tui-image-editor'); + * var blackTheme = require('./js/theme/black-theme.js'); + * var instance = new ImageEditor(document.querySelector('#tui-image-editor'), { + * includeUI: { + * loadImage: { + * path: 'img/sampleImage.jpg', + * name: 'SampleImage' + * }, + * theme: blackTheme, // or whiteTheme + * menu: ['shape', 'filter'], + * initMenu: 'filter', + * uiSize: { + * width: '1000px', + * height: '700px' + * }, + * menuBarPosition: 'bottom' + * }, + * cssMaxWidth: 700, + * cssMaxHeight: 500, + * selectionStyle: { + * cornerSize: 20, + * rotatingPointOffset: 70 + * } + * }); + */ + +var ImageEditor = /*#__PURE__*/function () { + function ImageEditor(wrapper, options) { + var _context, _context2, _context3, _context4, _context5, _context6, _context7, _context8, _context9, _context10, _context11, _context12, _context13, _context14, _context15, _context16; + + _classCallCheck(this, ImageEditor); + + options = extend_default()({ + includeUI: false, + usageStatistics: true + }, options); + this.mode = null; + this.activeObjectId = null; + /** + * UI instance + * @type {Ui} + */ + + if (options.includeUI) { + var UIOption = options.includeUI; + UIOption.usageStatistics = options.usageStatistics; + this.ui = new ui(wrapper, UIOption, this.getActions()); + options = this.ui.setUiDefaultSelectionStyle(options); + } + /** + * Invoker + * @type {Invoker} + * @private + */ + + + this._invoker = new invoker(); + /** + * Graphics instance + * @type {Graphics} + * @private + */ + + this._graphics = new graphics(this.ui ? this.ui.getEditorArea() : wrapper, { + cssMaxWidth: options.cssMaxWidth, + cssMaxHeight: options.cssMaxHeight + }); + /** + * Event handler list + * @type {Object} + * @private + */ + + this._handlers = { + keydown: bind_default()(_context = this._onKeyDown).call(_context, this), + mousedown: bind_default()(_context2 = this._onMouseDown).call(_context2, this), + objectActivated: bind_default()(_context3 = this._onObjectActivated).call(_context3, this), + objectMoved: bind_default()(_context4 = this._onObjectMoved).call(_context4, this), + objectScaled: bind_default()(_context5 = this._onObjectScaled).call(_context5, this), + objectRotated: bind_default()(_context6 = this._onObjectRotated).call(_context6, this), + objectAdded: bind_default()(_context7 = this._onObjectAdded).call(_context7, this), + objectModified: bind_default()(_context8 = this._onObjectModified).call(_context8, this), + createdPath: this._onCreatedPath, + addText: bind_default()(_context9 = this._onAddText).call(_context9, this), + addObject: bind_default()(_context10 = this._onAddObject).call(_context10, this), + textEditing: bind_default()(_context11 = this._onTextEditing).call(_context11, this), + textChanged: bind_default()(_context12 = this._onTextChanged).call(_context12, this), + iconCreateResize: bind_default()(_context13 = this._onIconCreateResize).call(_context13, this), + iconCreateEnd: bind_default()(_context14 = this._onIconCreateEnd).call(_context14, this), + selectionCleared: bind_default()(_context15 = this._selectionCleared).call(_context15, this), + selectionCreated: bind_default()(_context16 = this._selectionCreated).call(_context16, this) + }; + + this._attachInvokerEvents(); + + this._attachGraphicsEvents(); + + this._attachDomEvents(); + + this._setSelectionStyle(options.selectionStyle, { + applyCropSelectionStyle: options.applyCropSelectionStyle, + applyGroupSelectionStyle: options.applyGroupSelectionStyle + }); + + if (options.usageStatistics) { + sendHostName(); + } + + if (this.ui) { + this.ui.initCanvas(); + this.setReAction(); + + this._attachColorPickerInputBoxEvents(); + } + + fabric.fabric.enableGLFiltering = false; + } + + _createClass(ImageEditor, [{ + key: "_attachColorPickerInputBoxEvents", + value: function _attachColorPickerInputBoxEvents() { + var _this = this; + + this.ui.on(eventNames.INPUT_BOX_EDITING_STARTED, function () { + _this.isColorPickerInputBoxEditing = true; + }); + this.ui.on(eventNames.INPUT_BOX_EDITING_STOPPED, function () { + _this.isColorPickerInputBoxEditing = false; + }); + } + }, { + key: "_detachColorPickerInputBoxEvents", + value: function _detachColorPickerInputBoxEvents() { + this.ui.off(eventNames.INPUT_BOX_EDITING_STARTED); + this.ui.off(eventNames.INPUT_BOX_EDITING_STOPPED); + } + /** + * Set selection style by init option + * @param {Object} selectionStyle - Selection styles + * @param {Object} applyTargets - Selection apply targets + * @param {boolean} applyCropSelectionStyle - whether apply with crop selection style or not + * @param {boolean} applyGroupSelectionStyle - whether apply with group selection style or not + * @private + */ + + }, { + key: "_setSelectionStyle", + value: function _setSelectionStyle(selectionStyle, _ref) { + var applyCropSelectionStyle = _ref.applyCropSelectionStyle, + applyGroupSelectionStyle = _ref.applyGroupSelectionStyle; + + if (selectionStyle) { + this._graphics.setSelectionStyle(selectionStyle); + } + + if (applyCropSelectionStyle) { + this._graphics.setCropSelectionStyle(selectionStyle); + } + + if (applyGroupSelectionStyle) { + this.on('selectionCreated', function (eventTarget) { + if (eventTarget.type === 'activeSelection') { + eventTarget.set(selectionStyle); + } + }); + } + } + /** + * Attach invoker events + * @private + */ + + }, { + key: "_attachInvokerEvents", + value: function _attachInvokerEvents() { + var _context17, + _context18, + _this2 = this; + + var UNDO_STACK_CHANGED = eventNames.UNDO_STACK_CHANGED, + REDO_STACK_CHANGED = eventNames.REDO_STACK_CHANGED, + EXECUTE_COMMAND = eventNames.EXECUTE_COMMAND, + AFTER_UNDO = eventNames.AFTER_UNDO, + AFTER_REDO = eventNames.AFTER_REDO, + HAND_STARTED = eventNames.HAND_STARTED, + HAND_STOPPED = eventNames.HAND_STOPPED; + /** + * Undo stack changed event + * @event ImageEditor#undoStackChanged + * @param {Number} length - undo stack length + * @example + * imageEditor.on('undoStackChanged', function(length) { + * console.log(length); + * }); + */ + + this._invoker.on(UNDO_STACK_CHANGED, bind_default()(_context17 = this.fire).call(_context17, this, UNDO_STACK_CHANGED)); + /** + * Redo stack changed event + * @event ImageEditor#redoStackChanged + * @param {Number} length - redo stack length + * @example + * imageEditor.on('redoStackChanged', function(length) { + * console.log(length); + * }); + */ + + + this._invoker.on(REDO_STACK_CHANGED, bind_default()(_context18 = this.fire).call(_context18, this, REDO_STACK_CHANGED)); + + if (this.ui) { + var canvas = this._graphics.getCanvas(); + + this._invoker.on(EXECUTE_COMMAND, function (command) { + return _this2.ui.fire(EXECUTE_COMMAND, command); + }); + + this._invoker.on(AFTER_UNDO, function (command) { + return _this2.ui.fire(AFTER_UNDO, command); + }); + + this._invoker.on(AFTER_REDO, function (command) { + return _this2.ui.fire(AFTER_REDO, command); + }); + + canvas.on(HAND_STARTED, function () { + return _this2.ui.fire(HAND_STARTED); + }); + canvas.on(HAND_STOPPED, function () { + return _this2.ui.fire(HAND_STOPPED); + }); + } + } + /** + * Attach canvas events + * @private + */ + + }, { + key: "_attachGraphicsEvents", + value: function _attachGraphicsEvents() { + var _this$_graphics$on; + + this._graphics.on((_this$_graphics$on = {}, _defineProperty(_this$_graphics$on, MOUSE_DOWN, this._handlers.mousedown), _defineProperty(_this$_graphics$on, OBJECT_MOVED, this._handlers.objectMoved), _defineProperty(_this$_graphics$on, OBJECT_SCALED, this._handlers.objectScaled), _defineProperty(_this$_graphics$on, OBJECT_ROTATED, this._handlers.objectRotated), _defineProperty(_this$_graphics$on, OBJECT_ACTIVATED, this._handlers.objectActivated), _defineProperty(_this$_graphics$on, OBJECT_ADDED, this._handlers.objectAdded), _defineProperty(_this$_graphics$on, imageEditor_OBJECT_MODIFIED, this._handlers.objectModified), _defineProperty(_this$_graphics$on, imageEditor_ADD_TEXT, this._handlers.addText), _defineProperty(_this$_graphics$on, ADD_OBJECT, this._handlers.addObject), _defineProperty(_this$_graphics$on, imageEditor_TEXT_EDITING, this._handlers.textEditing), _defineProperty(_this$_graphics$on, TEXT_CHANGED, this._handlers.textChanged), _defineProperty(_this$_graphics$on, ICON_CREATE_RESIZE, this._handlers.iconCreateResize), _defineProperty(_this$_graphics$on, ICON_CREATE_END, this._handlers.iconCreateEnd), _defineProperty(_this$_graphics$on, SELECTION_CLEARED, this._handlers.selectionCleared), _defineProperty(_this$_graphics$on, SELECTION_CREATED, this._handlers.selectionCreated), _this$_graphics$on)); + } + /** + * Attach dom events + * @private + */ + + }, { + key: "_attachDomEvents", + value: function _attachDomEvents() { + // ImageEditor supports IE 9 higher + document.addEventListener('keydown', this._handlers.keydown); + } + /** + * Detach dom events + * @private + */ + + }, { + key: "_detachDomEvents", + value: function _detachDomEvents() { + // ImageEditor supports IE 9 higher + document.removeEventListener('keydown', this._handlers.keydown); + } + /** + * Keydown event handler + * @param {KeyboardEvent} e - Event object + * @private + */ + + /* eslint-disable complexity */ + + }, { + key: "_onKeyDown", + value: function _onKeyDown(e) { + var ctrlKey = e.ctrlKey, + keyCode = e.keyCode, + metaKey = e.metaKey; + var isModifierKey = ctrlKey || metaKey; + + if (isModifierKey) { + if (keyCode === keyCodes.C) { + this._graphics.resetTargetObjectForCopyPaste(); + } else if (keyCode === keyCodes.V) { + this._graphics.pasteObject(); + + this.clearRedoStack(); + } else if (keyCode === keyCodes.Z) { + // There is no error message on shortcut when it's empty + this.undo()['catch'](function () {}); + } else if (keyCode === keyCodes.Y) { + // There is no error message on shortcut when it's empty + this.redo()['catch'](function () {}); + } + } + + var isDeleteKey = keyCode === keyCodes.BACKSPACE || keyCode === keyCodes.DEL; + + var isRemoveReady = this._graphics.isReadyRemoveObject(); + + if (!this.isColorPickerInputBoxEditing && isRemoveReady && isDeleteKey) { + e.preventDefault(); + this.removeActiveObject(); + } + } + /** + * Remove Active Object + */ + + }, { + key: "removeActiveObject", + value: function removeActiveObject() { + var activeObjectId = this._graphics.getActiveObjectIdForRemove(); + + this.removeObject(activeObjectId); + } + /** + * mouse down event handler + * @param {Event} event - mouse down event + * @param {Object} originPointer - origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + + }, { + key: "_onMouseDown", + value: function _onMouseDown(event, originPointer) { + /** + * The mouse down event with position x, y on canvas + * @event ImageEditor#mousedown + * @param {Object} event - browser mouse event object + * @param {Object} originPointer origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @example + * imageEditor.on('mousedown', function(event, originPointer) { + * console.log(event); + * console.log(originPointer); + * if (imageEditor.hasFilter('colorFilter')) { + * imageEditor.applyFilter('colorFilter', { + * x: parseInt(originPointer.x, 10), + * y: parseInt(originPointer.y, 10) + * }); + * } + * }); + */ + this.fire(eventNames.MOUSE_DOWN, event, originPointer); + } + /** + * Add a 'addObject' command + * @param {Object} obj - Fabric object + * @private + */ + + }, { + key: "_pushAddObjectCommand", + value: function _pushAddObjectCommand(obj) { + var command = factory_command.create(commandNames.ADD_OBJECT, this._graphics, obj); + + this._invoker.pushUndoStack(command); + } + /** + * Add a 'changeSelection' command + * @param {fabric.Object} obj - selection object + * @private + */ + + }, { + key: "_pushModifyObjectCommand", + value: function _pushModifyObjectCommand(obj) { + var _this3 = this; + + var type = obj.type; + var props = makeSelectionUndoData(obj, function (item) { + return makeSelectionUndoDatum(_this3._graphics.getObjectId(item), item, type === 'activeSelection'); + }); + var command = factory_command.create(commandNames.CHANGE_SELECTION, this._graphics, props); + command.execute(this._graphics, props); + + this._invoker.pushUndoStack(command); + } + /** + * 'objectActivated' event handler + * @param {ObjectProps} props - object properties + * @private + */ + + }, { + key: "_onObjectActivated", + value: function _onObjectActivated(props) { + /** + * The event when object is selected(aka activated). + * @event ImageEditor#objectActivated + * @param {ObjectProps} objectProps - object properties + * @example + * imageEditor.on('objectActivated', function(props) { + * console.log(props); + * console.log(props.type); + * console.log(props.id); + * }); + */ + this.fire(eventNames.OBJECT_ACTIVATED, props); + } + /** + * 'objectMoved' event handler + * @param {ObjectProps} props - object properties + * @private + */ + + }, { + key: "_onObjectMoved", + value: function _onObjectMoved(props) { + /** + * The event when object is moved + * @event ImageEditor#objectMoved + * @param {ObjectProps} props - object properties + * @example + * imageEditor.on('objectMoved', function(props) { + * console.log(props); + * console.log(props.type); + * }); + */ + this.fire(eventNames.OBJECT_MOVED, props); + } + /** + * 'objectScaled' event handler + * @param {ObjectProps} props - object properties + * @private + */ + + }, { + key: "_onObjectScaled", + value: function _onObjectScaled(props) { + /** + * The event when scale factor is changed + * @event ImageEditor#objectScaled + * @param {ObjectProps} props - object properties + * @example + * imageEditor.on('objectScaled', function(props) { + * console.log(props); + * console.log(props.type); + * }); + */ + this.fire(eventNames.OBJECT_SCALED, props); + } + /** + * 'objectRotated' event handler + * @param {ObjectProps} props - object properties + * @private + */ + + }, { + key: "_onObjectRotated", + value: function _onObjectRotated(props) { + /** + * The event when object angle is changed + * @event ImageEditor#objectRotated + * @param {ObjectProps} props - object properties + * @example + * imageEditor.on('objectRotated', function(props) { + * console.log(props); + * console.log(props.type); + * }); + */ + this.fire(eventNames.OBJECT_ROTATED, props); + } + /** + * Get current drawing mode + * @returns {string} + * @example + * // Image editor drawing mode + * // + * // NORMAL: 'NORMAL' + * // CROPPER: 'CROPPER' + * // FREE_DRAWING: 'FREE_DRAWING' + * // LINE_DRAWING: 'LINE_DRAWING' + * // TEXT: 'TEXT' + * // + * if (imageEditor.getDrawingMode() === 'FREE_DRAWING') { + * imageEditor.stopDrawingMode(); + * } + */ + + }, { + key: "getDrawingMode", + value: function getDrawingMode() { + return this._graphics.getDrawingMode(); + } + /** + * Clear all objects + * @returns {Promise} + * @example + * imageEditor.clearObjects(); + */ + + }, { + key: "clearObjects", + value: function clearObjects() { + return this.execute(commandNames.CLEAR_OBJECTS); + } + /** + * Deactivate all objects + * @example + * imageEditor.deactivateAll(); + */ + + }, { + key: "deactivateAll", + value: function deactivateAll() { + this._graphics.deactivateAll(); + + this._graphics.renderAll(); + } + /** + * discard selction + * @example + * imageEditor.discardSelection(); + */ + + }, { + key: "discardSelection", + value: function discardSelection() { + this._graphics.discardSelection(); + } + /** + * selectable status change + * @param {boolean} selectable - selectable status + * @example + * imageEditor.changeSelectableAll(false); // or true + */ + + }, { + key: "changeSelectableAll", + value: function changeSelectableAll(selectable) { + this._graphics.changeSelectableAll(selectable); + } + /** + * Init history + */ + + }, { + key: "_initHistory", + value: function _initHistory() { + if (this.ui) { + this.ui.initHistory(); + } + } + /** + * Clear history + */ + + }, { + key: "_clearHistory", + value: function _clearHistory() { + if (this.ui) { + this.ui.clearHistory(); + } + } + /** + * Invoke command + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + * @private + */ + + }, { + key: "execute", + value: function execute(commandName) { + var _context19, _this$_invoker, _context20; + + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + // Inject an Graphics instance as first parameter + var theArgs = concat_default()(_context19 = [this._graphics]).call(_context19, args); + + return (_this$_invoker = this._invoker).execute.apply(_this$_invoker, concat_default()(_context20 = [commandName]).call(_context20, _toConsumableArray(theArgs))); + } + /** + * Invoke command + * @param {String} commandName - Command name + * @param {...*} args - Arguments for creating command + * @returns {Promise} + * @private + */ + + }, { + key: "executeSilent", + value: function executeSilent(commandName) { + var _context21, _this$_invoker2, _context22; + + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + args[_key2 - 1] = arguments[_key2]; + } + + // Inject an Graphics instance as first parameter + var theArgs = concat_default()(_context21 = [this._graphics]).call(_context21, args); + + return (_this$_invoker2 = this._invoker).executeSilent.apply(_this$_invoker2, concat_default()(_context22 = [commandName]).call(_context22, _toConsumableArray(theArgs))); + } + /** + * Undo + * @param {number} [iterationCount=1] - Iteration count of undo + * @returns {Promise} + * @example + * imageEditor.undo(); + */ + + }, { + key: "undo", + value: function undo() { + var _this4 = this; + + var iterationCount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; + + var promise = promise_default().resolve(); + + for (var i = 0; i < iterationCount; i += 1) { + promise = promise.then(function () { + return _this4._invoker.undo(); + }); + } + + return promise; + } + /** + * Redo + * @param {number} [iterationCount=1] - Iteration count of redo + * @returns {Promise} + * @example + * imageEditor.redo(); + */ + + }, { + key: "redo", + value: function redo() { + var _this5 = this; + + var iterationCount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; + + var promise = promise_default().resolve(); + + for (var i = 0; i < iterationCount; i += 1) { + promise = promise.then(function () { + return _this5._invoker.redo(); + }); + } + + return promise; + } + /** + * Zoom + * @param {number} x - x axis of center point for zoom + * @param {number} y - y axis of center point for zoom + * @param {number} zoomLevel - level of zoom(1.0 ~ 5.0) + */ + + }, { + key: "zoom", + value: function zoom(_ref2) { + var x = _ref2.x, + y = _ref2.y, + zoomLevel = _ref2.zoomLevel; + + this._graphics.zoom({ + x: x, + y: y + }, zoomLevel); + } + /** + * Reset zoom. Change zoom level to 1.0 + */ + + }, { + key: "resetZoom", + value: function resetZoom() { + this._graphics.resetZoom(); + } + /** + * Load image from file + * @param {File} imgFile - Image file + * @param {string} [imageName] - imageName + * @returns {Promise} + * @example + * imageEditor.loadImageFromFile(file).then(result => { + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); + * console.log('new : ' + result.newWidth + ', ' + result.newHeight); + * }); + */ + + }, { + key: "loadImageFromFile", + value: function loadImageFromFile(imgFile, imageName) { + if (!imgFile) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + var imgUrl = url_default().createObjectURL(imgFile); + + imageName = imageName || imgFile.name; + return this.loadImageFromURL(imgUrl, imageName).then(function (value) { + url_default().revokeObjectURL(imgFile); + + return value; + }); + } + /** + * Load image from url + * @param {string} url - File url + * @param {string} imageName - imageName + * @returns {Promise} + * @example + * imageEditor.loadImageFromURL('http://url/testImage.png', 'lena').then(result => { + * console.log('old : ' + result.oldWidth + ', ' + result.oldHeight); + * console.log('new : ' + result.newWidth + ', ' + result.newHeight); + * }); + */ + + }, { + key: "loadImageFromURL", + value: function loadImageFromURL(url, imageName) { + if (!imageName || !url) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + return this.execute(commandNames.LOAD_IMAGE, imageName, url); + } + /** + * Add image object on canvas + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + * @example + * imageEditor.addImageObject('path/fileName.jpg').then(objectProps => { + * console.log(ojectProps.id); + * }); + */ + + }, { + key: "addImageObject", + value: function addImageObject(imgUrl) { + if (!imgUrl) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + return this.execute(commandNames.ADD_IMAGE_OBJECT, imgUrl); + } + /** + * Start a drawing mode. If the current mode is not 'NORMAL', 'stopDrawingMode()' will be called first. + * @param {String} mode Can be one of 'CROPPER', 'FREE_DRAWING', 'LINE_DRAWING', 'TEXT', 'SHAPE' + * @param {Object} [option] parameters of drawing mode, it's available with 'FREE_DRAWING', 'LINE_DRAWING' + * @param {Number} [option.width] brush width + * @param {String} [option.color] brush color + * @param {Object} [option.arrowType] arrow decorate + * @param {string} [option.arrowType.tail] arrow decorate for tail. 'chevron' or 'triangle' + * @param {string} [option.arrowType.head] arrow decorate for head. 'chevron' or 'triangle' + * @returns {boolean} true if success or false + * @example + * imageEditor.startDrawingMode('FREE_DRAWING', { + * width: 10, + * color: 'rgba(255,0,0,0.5)' + * }); + * imageEditor.startDrawingMode('LINE_DRAWING', { + * width: 10, + * color: 'rgba(255,0,0,0.5)', + * arrowType: { + * tail: 'chevron' // triangle + * } + * }); + * + */ + + }, { + key: "startDrawingMode", + value: function startDrawingMode(mode, option) { + return this._graphics.startDrawingMode(mode, option); + } + /** + * Stop the current drawing mode and back to the 'NORMAL' mode + * @example + * imageEditor.stopDrawingMode(); + */ + + }, { + key: "stopDrawingMode", + value: function stopDrawingMode() { + this._graphics.stopDrawingMode(); + } + /** + * Crop this image with rect + * @param {Object} rect crop rect + * @param {Number} rect.left left position + * @param {Number} rect.top top position + * @param {Number} rect.width width + * @param {Number} rect.height height + * @returns {Promise} + * @example + * imageEditor.crop(imageEditor.getCropzoneRect()); + */ + + }, { + key: "crop", + value: function crop(rect) { + var data = this._graphics.getCroppedImageData(rect); + + if (!data) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + return this.loadImageFromURL(data.url, data.imageName); + } + /** + * Get the cropping rect + * @returns {Object} {{left: number, top: number, width: number, height: number}} rect + */ + + }, { + key: "getCropzoneRect", + value: function getCropzoneRect() { + return this._graphics.getCropzoneRect(); + } + /** + * Set the cropping rect + * @param {number} [mode] crop rect mode [1, 1.5, 1.3333333333333333, 1.25, 1.7777777777777777] + */ + + }, { + key: "setCropzoneRect", + value: function setCropzoneRect(mode) { + this._graphics.setCropzoneRect(mode); + } + /** + * Flip + * @returns {Promise} + * @param {string} type - 'flipX' or 'flipY' or 'reset' + * @returns {Promise} + * @private + */ + + }, { + key: "_flip", + value: function _flip(type) { + return this.execute(commandNames.FLIP_IMAGE, type); + } + /** + * Flip x + * @returns {Promise} + * @example + * imageEditor.flipX().then((status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + + }, { + key: "flipX", + value: function flipX() { + return this._flip('flipX'); + } + /** + * Flip y + * @returns {Promise} + * @example + * imageEditor.flipY().then(status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + + }, { + key: "flipY", + value: function flipY() { + return this._flip('flipY'); + } + /** + * Reset flip + * @returns {Promise} + * @example + * imageEditor.resetFlip().then(status => { + * console.log('flipX: ', status.flipX); + * console.log('flipY: ', status.flipY); + * console.log('angle: ', status.angle); + * }).catch(message => { + * console.log('error: ', message); + * });; + */ + + }, { + key: "resetFlip", + value: function resetFlip() { + return this._flip('reset'); + } + /** + * @param {string} type - 'rotate' or 'setAngle' + * @param {number} angle - angle value (degree) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @private + */ + + }, { + key: "_rotate", + value: function _rotate(type, angle, isSilent) { + var result = null; + + if (isSilent) { + result = this.executeSilent(commandNames.ROTATE_IMAGE, type, angle); + } else { + result = this.execute(commandNames.ROTATE_IMAGE, type, angle); + } + + return result; + } + /** + * Rotate image + * @returns {Promise} + * @param {number} angle - Additional angle to rotate image + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.rotate(10); // angle = 10 + * imageEditor.rotate(10); // angle = 20 + * imageEditor.rotate(5); // angle = 5 + * imageEditor.rotate(-95); // angle = -90 + * imageEditor.rotate(10).then(status => { + * console.log('angle: ', status.angle); + * })).catch(message => { + * console.log('error: ', message); + * }); + */ + + }, { + key: "rotate", + value: function rotate(angle, isSilent) { + return this._rotate('rotate', angle, isSilent); + } + /** + * Set angle + * @param {number} angle - Angle of image + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.setAngle(10); // angle = 10 + * imageEditor.rotate(10); // angle = 20 + * imageEditor.setAngle(5); // angle = 5 + * imageEditor.rotate(50); // angle = 55 + * imageEditor.setAngle(-40); // angle = -40 + * imageEditor.setAngle(10).then(status => { + * console.log('angle: ', status.angle); + * })).catch(message => { + * console.log('error: ', message); + * }); + */ + + }, { + key: "setAngle", + value: function setAngle(angle, isSilent) { + return this._rotate('setAngle', angle, isSilent); + } + /** + * Set drawing brush + * @param {Object} option brush option + * @param {Number} option.width width + * @param {String} option.color color like 'FFFFFF', 'rgba(0, 0, 0, 0.5)' + * @example + * imageEditor.startDrawingMode('FREE_DRAWING'); + * imageEditor.setBrush({ + * width: 12, + * color: 'rgba(0, 0, 0, 0.5)' + * }); + * imageEditor.setBrush({ + * width: 8, + * color: 'FFFFFF' + * }); + */ + + }, { + key: "setBrush", + value: function setBrush(option) { + this._graphics.setBrush(option); + } + /** + * Set states of current drawing shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} [options] - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stoke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @example + * imageEditor.setDrawingShape('rect', { + * fill: 'red', + * width: 100, + * height: 200 + * }); + * @example + * imageEditor.setDrawingShape('rect', { + * fill: { + * type: 'filter', + * filter: [{blur: 0.3}, {pixelate: 20}] + * }, + * width: 100, + * height: 200 + * }); + * @example + * imageEditor.setDrawingShape('circle', { + * fill: 'transparent', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100 + * }); + * @example + * imageEditor.setDrawingShape('triangle', { // When resizing, the shape keep the 1:1 ratio + * width: 1, + * height: 1, + * isRegular: true + * }); + * @example + * imageEditor.setDrawingShape('circle', { // When resizing, the shape keep the 1:1 ratio + * rx: 10, + * ry: 10, + * isRegular: true + * }); + */ + + }, { + key: "setDrawingShape", + value: function setDrawingShape(type, options) { + this._graphics.setDrawingShape(type, options); + } + }, { + key: "setDrawingIcon", + value: function setDrawingIcon(type, iconColor) { + this._graphics.setIconStyle(type, iconColor); + } + /** + * Add shape + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @returns {Promise} + * @example + * imageEditor.addShape('rect', { + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * width: 100, + * height: 200, + * left: 10, + * top: 10, + * isRegular: true + * }); + * @example + * imageEditor.addShape('circle', { + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100, + * isRegular: false + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + * @example + * imageEditor.addShape('rect', { + * fill: { + * type: 'filter', + * filter: [{blur: 0.3}, {pixelate: 20}] + * }, + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100, + * isRegular: false + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + + }, { + key: "addShape", + value: function addShape(type, options) { + options = options || {}; + + this._setPositions(options); + + return this.execute(commandNames.ADD_SHAPE, type, options); + } + /** + * Change shape + * @param {number} id - object id + * @param {Object} options - Shape options + * @param {(ShapeFillOption | string)} [options.fill] - {@link ShapeFillOption} or + * Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {boolean} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * // call after selecting shape object on canvas + * imageEditor.changeShape(id, { // change rectagle or triangle + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * width: 100, + * height: 200 + * }); + * @example + * // call after selecting shape object on canvas + * imageEditor.changeShape(id, { // change circle + * fill: 'red', + * stroke: 'blue', + * strokeWidth: 3, + * rx: 10, + * ry: 100 + * }); + */ + + }, { + key: "changeShape", + value: function changeShape(id, options, isSilent) { + var executeMethodName = isSilent ? 'executeSilent' : 'execute'; + return this[executeMethodName](commandNames.CHANGE_SHAPE, id, options); + } + /** + * Add text on image + * @param {string} text - Initial input text + * @param {Object} [options] Options for generating text + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @param {boolean} [options.autofocus] - text autofocus, default is true + * @returns {Promise} + * @example + * imageEditor.addText('init text'); + * @example + * imageEditor.addText('init text', { + * styles: { + * fill: '#000', + * fontSize: 20, + * fontWeight: 'bold' + * }, + * position: { + * x: 10, + * y: 10 + * } + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + + }, { + key: "addText", + value: function addText(text, options) { + text = text || ''; + options = options || {}; + return this.execute(commandNames.ADD_TEXT, text, options); + } + /** + * Change contents of selected text object on image + * @param {number} id - object id + * @param {string} text - Changing text + * @returns {Promise} + * @example + * imageEditor.changeText(id, 'change text'); + */ + + }, { + key: "changeText", + value: function changeText(id, text) { + text = text || ''; + return this.execute(commandNames.CHANGE_TEXT, id, text); + } + /** + * Set style + * @param {number} id - object id + * @param {Object} styleObj - text styles + * @param {string} [styleObj.fill] Color + * @param {string} [styleObj.fontFamily] Font type for text + * @param {number} [styleObj.fontSize] Size + * @param {string} [styleObj.fontStyle] Type of inclination (normal / italic) + * @param {string} [styleObj.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styleObj.textAlign] Type of text align (left / center / right) + * @param {string} [styleObj.textDecoration] Type of line (underline / line-through / overline) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.changeTextStyle(id, { + * fontStyle: 'italic' + * }); + */ + + }, { + key: "changeTextStyle", + value: function changeTextStyle(id, styleObj, isSilent) { + var executeMethodName = isSilent ? 'executeSilent' : 'execute'; + return this[executeMethodName](commandNames.CHANGE_TEXT_STYLE, id, styleObj); + } + /** + * change text mode + * @param {string} type - change type + * @private + */ + + }, { + key: "_changeActivateMode", + value: function _changeActivateMode(type) { + if (type !== 'ICON' && this.getDrawingMode() !== type) { + this.startDrawingMode(type); + } + } + /** + * 'textChanged' event handler + * @param {Object} target - changed text object + * @private + */ + + }, { + key: "_onTextChanged", + value: function _onTextChanged(target) { + this.fire(eventNames.TEXT_CHANGED, target); + } + /** + * 'iconCreateResize' event handler + * @param {Object} originPointer origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + + }, { + key: "_onIconCreateResize", + value: function _onIconCreateResize(originPointer) { + this.fire(eventNames.ICON_CREATE_RESIZE, originPointer); + } + /** + * 'iconCreateEnd' event handler + * @param {Object} originPointer origin pointer + * @param {Number} originPointer.x x position + * @param {Number} originPointer.y y position + * @private + */ + + }, { + key: "_onIconCreateEnd", + value: function _onIconCreateEnd(originPointer) { + this.fire(eventNames.ICON_CREATE_END, originPointer); + } + /** + * 'textEditing' event handler + * @private + */ + + }, { + key: "_onTextEditing", + value: function _onTextEditing() { + /** + * The event which starts to edit text object + * @event ImageEditor#textEditing + * @example + * imageEditor.on('textEditing', function() { + * console.log('text editing'); + * }); + */ + this.fire(eventNames.TEXT_EDITING); + } + /** + * Mousedown event handler in case of 'TEXT' drawing mode + * @param {fabric.Event} event - Current mousedown event object + * @private + */ + + }, { + key: "_onAddText", + value: function _onAddText(event) { + /** + * The event when 'TEXT' drawing mode is enabled and click non-object area. + * @event ImageEditor#addText + * @param {Object} pos + * @param {Object} pos.originPosition - Current position on origin canvas + * @param {Number} pos.originPosition.x - x + * @param {Number} pos.originPosition.y - y + * @param {Object} pos.clientPosition - Current position on client area + * @param {Number} pos.clientPosition.x - x + * @param {Number} pos.clientPosition.y - y + * @example + * imageEditor.on('addText', function(pos) { + * console.log('text position on canvas: ' + pos.originPosition); + * console.log('text position on brwoser: ' + pos.clientPosition); + * }); + */ + this.fire(eventNames.ADD_TEXT, { + originPosition: event.originPosition, + clientPosition: event.clientPosition + }); + } + /** + * 'addObject' event handler + * @param {Object} objectProps added object properties + * @private + */ + + }, { + key: "_onAddObject", + value: function _onAddObject(objectProps) { + var obj = this._graphics.getObject(objectProps.id); + + this._invoker.fire(eventNames.EXECUTE_COMMAND, getObjectType(obj.type)); + + this._pushAddObjectCommand(obj); + } + /** + * 'objectAdded' event handler + * @param {Object} objectProps added object properties + * @private + */ + + }, { + key: "_onObjectAdded", + value: function _onObjectAdded(objectProps) { + /** + * The event when object added + * @event ImageEditor#objectAdded + * @param {ObjectProps} props - object properties + * @example + * imageEditor.on('objectAdded', function(props) { + * console.log(props); + * }); + */ + this.fire(OBJECT_ADDED, objectProps); + /** + * The event when object added (deprecated) + * @event ImageEditor#addObjectAfter + * @param {ObjectProps} props - object properties + * @deprecated + */ + + this.fire(ADD_OBJECT_AFTER, objectProps); + } + /** + * 'objectModified' event handler + * @param {fabric.Object} obj - selection object + * @private + */ + + }, { + key: "_onObjectModified", + value: function _onObjectModified(obj) { + if (obj.type !== OBJ_TYPE.CROPZONE) { + this._invoker.fire(eventNames.EXECUTE_COMMAND, getObjectType(obj.type)); + + this._pushModifyObjectCommand(obj); + } + } + /** + * 'selectionCleared' event handler + * @private + */ + + }, { + key: "_selectionCleared", + value: function _selectionCleared() { + this.fire(SELECTION_CLEARED); + } + /** + * 'selectionCreated' event handler + * @param {Object} eventTarget - Fabric object + * @private + */ + + }, { + key: "_selectionCreated", + value: function _selectionCreated(eventTarget) { + this.fire(SELECTION_CREATED, eventTarget); + } + /** + * Register custom icons + * @param {{iconType: string, pathValue: string}} infos - Infos to register icons + * @example + * imageEditor.registerIcons({ + * customIcon: 'M 0 0 L 20 20 L 10 10 Z', + * customArrow: 'M 60 0 L 120 60 H 90 L 75 45 V 180 H 45 V 45 L 30 60 H 0 Z' + * }); + */ + + }, { + key: "registerIcons", + value: function registerIcons(infos) { + this._graphics.registerPaths(infos); + } + /** + * Change canvas cursor type + * @param {string} cursorType - cursor type + * @example + * imageEditor.changeCursor('crosshair'); + */ + + }, { + key: "changeCursor", + value: function changeCursor(cursorType) { + this._graphics.changeCursor(cursorType); + } + /** + * Add icon on canvas + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {number} [options.left] - Icon x position + * @param {number} [options.top] - Icon y position + * @returns {Promise} + * @example + * imageEditor.addIcon('arrow'); // The position is center on canvas + * @example + * imageEditor.addIcon('arrow', { + * left: 100, + * top: 100 + * }).then(objectProps => { + * console.log(objectProps.id); + * }); + */ + + }, { + key: "addIcon", + value: function addIcon(type, options) { + options = options || {}; + + this._setPositions(options); + + return this.execute(commandNames.ADD_ICON, type, options); + } + /** + * Change icon color + * @param {number} id - object id + * @param {string} color - Color for icon + * @returns {Promise} + * @example + * imageEditor.changeIconColor(id, '#000000'); + */ + + }, { + key: "changeIconColor", + value: function changeIconColor(id, color) { + return this.execute(commandNames.CHANGE_ICON_COLOR, id, color); + } + /** + * Remove an object or group by id + * @param {number} id - object id + * @returns {Promise} + * @example + * imageEditor.removeObject(id); + */ + + }, { + key: "removeObject", + value: function removeObject(id) { + var _this$_graphics$getOb = this._graphics.getObject(id), + type = _this$_graphics$getOb.type; + + return this.execute(commandNames.REMOVE_OBJECT, id, getObjectType(type)); + } + /** + * Whether it has the filter or not + * @param {string} type - Filter type + * @returns {boolean} true if it has the filter + */ + + }, { + key: "hasFilter", + value: function hasFilter(type) { + return this._graphics.hasFilter(type); + } + /** + * Remove filter on canvas image + * @param {string} type - Filter type + * @returns {Promise} + * @example + * imageEditor.removeFilter('Grayscale').then(obj => { + * console.log('filterType: ', obj.type); + * console.log('actType: ', obj.action); + * }).catch(message => { + * console.log('error: ', message); + * }); + */ + + }, { + key: "removeFilter", + value: function removeFilter(type) { + return this.execute(commandNames.REMOVE_FILTER, type); + } + /** + * Apply filter on canvas image + * @param {string} type - Filter type + * @param {object} options - Options to apply filter + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + * @example + * imageEditor.applyFilter('Grayscale'); + * @example + * imageEditor.applyFilter('mask', {maskObjId: id}).then(obj => { + * console.log('filterType: ', obj.type); + * console.log('actType: ', obj.action); + * }).catch(message => { + * console.log('error: ', message); + * });; + */ + + }, { + key: "applyFilter", + value: function applyFilter(type, options, isSilent) { + var executeMethodName = isSilent ? 'executeSilent' : 'execute'; + return this[executeMethodName](commandNames.APPLY_FILTER, type, options); + } + /** + * Get data url + * @param {Object} options - options for toDataURL + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in fabric v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in fabric v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in fabric v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in fabric v1.2.14 + * @returns {string} A DOMString containing the requested data URI + * @example + * imgEl.src = imageEditor.toDataURL(); + * + * imageEditor.loadImageFromURL(imageEditor.toDataURL(), 'FilterImage').then(() => { + * imageEditor.addImageObject(imgUrl); + * }); + */ + + }, { + key: "toDataURL", + value: function toDataURL(options) { + return this._graphics.toDataURL(options); + } + /** + * Get image name + * @returns {string} image name + * @example + * console.log(imageEditor.getImageName()); + */ + + }, { + key: "getImageName", + value: function getImageName() { + return this._graphics.getImageName(); + } + /** + * Clear undoStack + * @example + * imageEditor.clearUndoStack(); + */ + + }, { + key: "clearUndoStack", + value: function clearUndoStack() { + this._invoker.clearUndoStack(); + } + /** + * Clear redoStack + * @example + * imageEditor.clearRedoStack(); + */ + + }, { + key: "clearRedoStack", + value: function clearRedoStack() { + this._invoker.clearRedoStack(); + } + /** + * Whehter the undo stack is empty or not + * @returns {boolean} + * imageEditor.isEmptyUndoStack(); + */ + + }, { + key: "isEmptyUndoStack", + value: function isEmptyUndoStack() { + return this._invoker.isEmptyUndoStack(); + } + /** + * Whehter the redo stack is empty or not + * @returns {boolean} + * imageEditor.isEmptyRedoStack(); + */ + + }, { + key: "isEmptyRedoStack", + value: function isEmptyRedoStack() { + return this._invoker.isEmptyRedoStack(); + } + /** + * Resize canvas dimension + * @param {{width: number, height: number}} dimension - Max width & height + * @returns {Promise} + */ + + }, { + key: "resizeCanvasDimension", + value: function resizeCanvasDimension(dimension) { + if (!dimension) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + return this.execute(commandNames.RESIZE_CANVAS_DIMENSION, dimension); + } + /** + * Destroy + */ + + }, { + key: "destroy", + value: function destroy() { + var _this6 = this; + + this.stopDrawingMode(); + + this._detachDomEvents(); + + this._graphics.destroy(); + + this._graphics = null; + + if (this.ui) { + this._detachColorPickerInputBoxEvents(); + + this.ui.destroy(); + } + + forEach_default()(this, function (value, key) { + _this6[key] = null; + }, this); + } + /** + * Set position + * @param {Object} options - Position options (left or top) + * @private + */ + + }, { + key: "_setPositions", + value: function _setPositions(options) { + var centerPosition = this._graphics.getCenter(); + + if (isUndefined_default()(options.left)) { + options.left = centerPosition.left; + } + + if (isUndefined_default()(options.top)) { + options.top = centerPosition.top; + } + } + /** + * Set properties of active object + * @param {number} id - object id + * @param {Object} keyValue - key & value + * @returns {Promise} + * @example + * imageEditor.setObjectProperties(id, { + * left:100, + * top:100, + * width: 200, + * height: 200, + * opacity: 0.5 + * }); + */ + + }, { + key: "setObjectProperties", + value: function setObjectProperties(id, keyValue) { + return this.execute(commandNames.SET_OBJECT_PROPERTIES, id, keyValue); + } + /** + * Set properties of active object, Do not leave an invoke history. + * @param {number} id - object id + * @param {Object} keyValue - key & value + * @example + * imageEditor.setObjectPropertiesQuietly(id, { + * left:100, + * top:100, + * width: 200, + * height: 200, + * opacity: 0.5 + * }); + */ + + }, { + key: "setObjectPropertiesQuietly", + value: function setObjectPropertiesQuietly(id, keyValue) { + this._graphics.setObjectProperties(id, keyValue); + } + /** + * Get properties of active object corresponding key + * @param {number} id - object id + * @param {Array|ObjectProps|string} keys - property's key + * @returns {ObjectProps} properties if id is valid or null + * @example + * var props = imageEditor.getObjectProperties(id, 'left'); + * console.log(props); + * @example + * var props = imageEditor.getObjectProperties(id, ['left', 'top', 'width', 'height']); + * console.log(props); + * @example + * var props = imageEditor.getObjectProperties(id, { + * left: null, + * top: null, + * width: null, + * height: null, + * opacity: null + * }); + * console.log(props); + */ + + }, { + key: "getObjectProperties", + value: function getObjectProperties(id, keys) { + var object = this._graphics.getObject(id); + + if (!object) { + return null; + } + + return this._graphics.getObjectProperties(id, keys); + } + /** + * Get the canvas size + * @returns {Object} {{width: number, height: number}} canvas size + * @example + * var canvasSize = imageEditor.getCanvasSize(); + * console.log(canvasSize.width); + * console.height(canvasSize.height); + */ + + }, { + key: "getCanvasSize", + value: function getCanvasSize() { + return this._graphics.getCanvasSize(); + } + /** + * Get object position by originX, originY + * @param {number} id - object id + * @param {string} originX - can be 'left', 'center', 'right' + * @param {string} originY - can be 'top', 'center', 'bottom' + * @returns {Object} {{x:number, y: number}} position by origin if id is valid, or null + * @example + * var position = imageEditor.getObjectPosition(id, 'left', 'top'); + * console.log(position); + */ + + }, { + key: "getObjectPosition", + value: function getObjectPosition(id, originX, originY) { + return this._graphics.getObjectPosition(id, originX, originY); + } + /** + * Set object position by originX, originY + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {Promise} + * @example + * // align the object to 'left', 'top' + * imageEditor.setObjectPosition(id, { + * x: 0, + * y: 0, + * originX: 'left', + * originY: 'top' + * }); + * @example + * // align the object to 'right', 'top' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: canvasSize.width, + * y: 0, + * originX: 'right', + * originY: 'top' + * }); + * @example + * // align the object to 'left', 'bottom' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: 0, + * y: canvasSize.height, + * originX: 'left', + * originY: 'bottom' + * }); + * @example + * // align the object to 'right', 'bottom' + * var canvasSize = imageEditor.getCanvasSize(); + * imageEditor.setObjectPosition(id, { + * x: canvasSize.width, + * y: canvasSize.height, + * originX: 'right', + * originY: 'bottom' + * }); + */ + + }, { + key: "setObjectPosition", + value: function setObjectPosition(id, posInfo) { + return this.execute(commandNames.SET_OBJECT_POSITION, id, posInfo); + } + /** + * @param {object} dimensions - Image Dimensions + * @returns {Promise} + */ + + }, { + key: "resize", + value: function resize(dimensions) { + return this.execute(commandNames.RESIZE_IMAGE, dimensions); + } + }]); + + return ImageEditor; +}(); + +action.mixin(ImageEditor); +customEvents_default().mixin(ImageEditor); +/* harmony default export */ var imageEditor = (ImageEditor); +;// CONCATENATED MODULE: ./src/js/command/addIcon.js + + + +var ICON = componentNames.ICON; +var addIcon_command = { + name: commandNames.ADD_ICON, + + /** + * Add an icon + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Icon type ('arrow', 'cancel', custom icon name) + * @param {Object} options - Icon options + * @param {string} [options.fill] - Icon foreground color + * @param {string} [options.left] - Icon x position + * @param {string} [options.top] - Icon y position + * @returns {Promise} + */ + execute: function execute(graphics, type, options) { + var _this = this; + + var iconComp = graphics.getComponent(ICON); + return iconComp.add(type, options).then(function (objectProps) { + _this.undoData.object = graphics.getObject(objectProps.id); + return objectProps; + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.remove(this.undoData.object); + return promise_default().resolve(); + } +}; +factory_command.register(addIcon_command); +/* harmony default export */ var addIcon = ((/* unused pure expression or super */ null && (addIcon_command))); +;// CONCATENATED MODULE: ./src/js/command/addImageObject.js + + + +var addImageObject_command = { + name: commandNames.ADD_IMAGE_OBJECT, + + /** + * Add an image object + * @param {Graphics} graphics - Graphics instance + * @param {string} imgUrl - Image url to make object + * @returns {Promise} + */ + execute: function execute(graphics, imgUrl) { + var _this = this; + + return graphics.addImageObject(imgUrl).then(function (objectProps) { + _this.undoData.object = graphics.getObject(objectProps.id); + return objectProps; + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.remove(this.undoData.object); + return promise_default().resolve(); + } +}; +factory_command.register(addImageObject_command); +/* harmony default export */ var addImageObject = ((/* unused pure expression or super */ null && (addImageObject_command))); +;// CONCATENATED MODULE: ./src/js/command/addObject.js + + + +var addObject_command = { + name: commandNames.ADD_OBJECT, + + /** + * Add an object + * @param {Graphics} graphics - Graphics instance + * @param {Object} object - Fabric object + * @returns {Promise} + */ + execute: function execute(graphics, object) { + return new (promise_default())(function (resolve, reject) { + if (!graphics.contains(object)) { + graphics.add(object); + resolve(object); + } else { + reject(rejectMessages.addedObject); + } + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @param {Object} object - Fabric object + * @returns {Promise} + */ + undo: function undo(graphics, object) { + return new (promise_default())(function (resolve, reject) { + if (graphics.contains(object)) { + graphics.remove(object); + resolve(object); + } else { + reject(rejectMessages.noObject); + } + }); + } +}; +factory_command.register(addObject_command); +/* harmony default export */ var addObject = ((/* unused pure expression or super */ null && (addObject_command))); +;// CONCATENATED MODULE: ./src/js/command/addShape.js + + + +var SHAPE = componentNames.SHAPE; +var addShape_command = { + name: commandNames.ADD_SHAPE, + + /** + * Add a shape + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Shape type (ex: 'rect', 'circle', 'triangle') + * @param {Object} options - Shape options + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @returns {Promise} + */ + execute: function execute(graphics, type, options) { + var _this = this; + + var shapeComp = graphics.getComponent(SHAPE); + return shapeComp.add(type, options).then(function (objectProps) { + var id = objectProps.id; + _this.undoData.object = graphics.getObject(id); + return objectProps; + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.remove(this.undoData.object); + return promise_default().resolve(); + } +}; +factory_command.register(addShape_command); +/* harmony default export */ var addShape = ((/* unused pure expression or super */ null && (addShape_command))); +;// CONCATENATED MODULE: ./src/js/command/addText.js + + + + +var TEXT = componentNames.TEXT; +var addText_command = { + name: commandNames.ADD_TEXT, + + /** + * Add a text object + * @param {Graphics} graphics - Graphics instance + * @param {string} text - Initial input text + * @param {Object} [options] Options for text styles + * @param {Object} [options.styles] Initial styles + * @param {string} [options.styles.fill] Color + * @param {string} [options.styles.fontFamily] Font type for text + * @param {number} [options.styles.fontSize] Size + * @param {string} [options.styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [options.styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [options.styles.textAlign] Type of text align (left / center / right) + * @param {string} [options.styles.textDecoration] Type of line (underline / line-through / overline) + * @param {{x: number, y: number}} [options.position] - Initial position + * @returns {Promise} + */ + execute: function execute(graphics, text, options) { + var _this = this; + + var textComp = graphics.getComponent(TEXT); + + if (this.undoData.object) { + var undoObject = this.undoData.object; + return new (promise_default())(function (resolve, reject) { + if (!graphics.contains(undoObject)) { + graphics.add(undoObject); + resolve(undoObject); + } else { + reject(rejectMessages.redo); + } + }); + } + + return textComp.add(text, options).then(function (objectProps) { + var id = objectProps.id; + var textObject = graphics.getObject(id); + _this.undoData.object = textObject; + setCachedUndoDataForDimension(makeSelectionUndoData(textObject, function () { + return makeSelectionUndoDatum(id, textObject, false); + })); + return objectProps; + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.remove(this.undoData.object); + return promise_default().resolve(); + } +}; +factory_command.register(addText_command); +/* harmony default export */ var addText = ((/* unused pure expression or super */ null && (addText_command))); +;// CONCATENATED MODULE: ./src/js/command/applyFilter.js + + + + +var FILTER = componentNames.FILTER; +/** + * Cached data for undo + * @type {Object} + */ + +var cachedUndoDataForSilent = null; +/** + * Make undoData + * @param {string} type - Filter type + * @param {Object} prevfilterOption - prev Filter options + * @param {Object} options - Filter options + * @returns {object} - undo data + */ + +function makeUndoData(type, prevfilterOption, options) { + var undoData = {}; + + if (type === 'mask') { + undoData.object = options.mask; + } + + undoData.options = prevfilterOption; + return undoData; +} + +var applyFilter_command = { + name: commandNames.APPLY_FILTER, + + /** + * Apply a filter into an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @param {Object} options - Filter options + * @param {number} options.maskObjId - masking image object id + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute: function execute(graphics, type, options, isSilent) { + var filterComp = graphics.getComponent(FILTER); + + if (type === 'mask') { + var maskObj = graphics.getObject(options.maskObjId); + + if (!(maskObj && maskObj.isType('image'))) { + return promise_default().reject(rejectMessages.invalidParameters); + } + + extend_default()(options, { + mask: maskObj + }); + graphics.remove(options.mask); + } + + if (!this.isRedo) { + var prevfilterOption = filterComp.getOptions(type); + var undoData = makeUndoData(type, prevfilterOption, options); + cachedUndoDataForSilent = this.setUndoData(undoData, cachedUndoDataForSilent, isSilent); + } + + return filterComp.add(type, options); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + undo: function undo(graphics, type) { + var filterComp = graphics.getComponent(FILTER); + + if (type === 'mask') { + var mask = this.undoData.object; + graphics.add(mask); + graphics.setActiveObject(mask); + return filterComp.remove(type); + } // options changed case + + + if (this.undoData.options) { + return filterComp.add(type, this.undoData.options); + } // filter added case + + + return filterComp.remove(type); + } +}; +factory_command.register(applyFilter_command); +/* harmony default export */ var applyFilter = ((/* unused pure expression or super */ null && (applyFilter_command))); +;// CONCATENATED MODULE: ./src/js/command/changeIconColor.js + + + +var changeIconColor_ICON = componentNames.ICON; +var changeIconColor_command = { + name: commandNames.CHANGE_ICON_COLOR, + + /** + * Change icon color + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {string} color - Color for icon + * @returns {Promise} + */ + execute: function execute(graphics, id, color) { + var _this = this; + + return new (promise_default())(function (resolve, reject) { + var iconComp = graphics.getComponent(changeIconColor_ICON); + var targetObj = graphics.getObject(id); + + if (!targetObj) { + reject(rejectMessages.noObject); + } + + _this.undoData.object = targetObj; + _this.undoData.color = iconComp.getColor(targetObj); + iconComp.setColor(color, targetObj); + resolve(); + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var iconComp = graphics.getComponent(changeIconColor_ICON); + var _this$undoData = this.undoData, + icon = _this$undoData.object, + color = _this$undoData.color; + iconComp.setColor(color, icon); + return promise_default().resolve(); + } +}; +factory_command.register(changeIconColor_command); +/* harmony default export */ var changeIconColor = ((/* unused pure expression or super */ null && (changeIconColor_command))); +;// CONCATENATED MODULE: ./src/js/command/changeShape.js + + + + +var changeShape_SHAPE = componentNames.SHAPE; +/** + * Cached data for undo + * @type {Object} + */ + +var changeShape_cachedUndoDataForSilent = null; +/** + * Make undoData + * @param {object} options - shape options + * @param {Component} targetObj - shape component + * @returns {object} - undo data + */ + +function changeShape_makeUndoData(options, targetObj) { + var undoData = { + object: targetObj, + options: {} + }; + forEachOwnProperties_default()(options, function (value, key) { + undoData.options[key] = targetObj[key]; + }); + return undoData; +} + +var changeShape_command = { + name: commandNames.CHANGE_SHAPE, + + /** + * Change a shape + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} options - Shape options + * @param {string} [options.fill] - Shape foreground color (ex: '#fff', 'transparent') + * @param {string} [options.stroke] - Shape outline color + * @param {number} [options.strokeWidth] - Shape outline width + * @param {number} [options.width] - Width value (When type option is 'rect', this options can use) + * @param {number} [options.height] - Height value (When type option is 'rect', this options can use) + * @param {number} [options.rx] - Radius x value (When type option is 'circle', this options can use) + * @param {number} [options.ry] - Radius y value (When type option is 'circle', this options can use) + * @param {number} [options.left] - Shape x position + * @param {number} [options.top] - Shape y position + * @param {number} [options.isRegular] - Whether resizing shape has 1:1 ratio or not + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute: function execute(graphics, id, options, isSilent) { + var shapeComp = graphics.getComponent(changeShape_SHAPE); + var targetObj = graphics.getObject(id); + + if (!targetObj) { + return promise_default().reject(rejectMessages.noObject); + } + + if (!this.isRedo) { + var undoData = changeShape_makeUndoData(options, targetObj); + changeShape_cachedUndoDataForSilent = this.setUndoData(undoData, changeShape_cachedUndoDataForSilent, isSilent); + } + + return shapeComp.change(targetObj, options); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var shapeComp = graphics.getComponent(changeShape_SHAPE); + var _this$undoData = this.undoData, + shape = _this$undoData.object, + options = _this$undoData.options; + return shapeComp.change(shape, options); + } +}; +factory_command.register(changeShape_command); +/* harmony default export */ var changeShape = ((/* unused pure expression or super */ null && (changeShape_command))); +;// CONCATENATED MODULE: ./src/js/command/changeText.js + + + +var changeText_TEXT = componentNames.TEXT; +var changeText_command = { + name: commandNames.CHANGE_TEXT, + + /** + * Change a text + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {string} text - Changing text + * @returns {Promise} + */ + execute: function execute(graphics, id, text) { + var textComp = graphics.getComponent(changeText_TEXT); + var targetObj = graphics.getObject(id); + + if (!targetObj) { + return promise_default().reject(rejectMessages.noObject); + } + + this.undoData.object = targetObj; + this.undoData.text = textComp.getText(targetObj); + return textComp.change(targetObj, text); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var textComp = graphics.getComponent(changeText_TEXT); + var _this$undoData = this.undoData, + textObj = _this$undoData.object, + text = _this$undoData.text; + return textComp.change(textObj, text); + } +}; +factory_command.register(changeText_command); +/* harmony default export */ var changeText = ((/* unused pure expression or super */ null && (changeText_command))); +;// CONCATENATED MODULE: ./src/js/command/changeTextStyle.js + + + + +var changeTextStyle_TEXT = componentNames.TEXT; +/** + * Cached data for undo + * @type {Object} + */ + +var changeTextStyle_cachedUndoDataForSilent = null; +/** + * Make undoData + * @param {object} styles - text styles + * @param {Component} targetObj - text component + * @returns {object} - undo data + */ + +function changeTextStyle_makeUndoData(styles, targetObj) { + var undoData = { + object: targetObj, + styles: {} + }; + forEachOwnProperties_default()(styles, function (value, key) { + var undoValue = targetObj[key]; + undoData.styles[key] = undoValue; + }); + return undoData; +} + +var changeTextStyle_command = { + name: commandNames.CHANGE_TEXT_STYLE, + + /** + * Change text styles + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} styles - text styles + * @param {string} [styles.fill] Color + * @param {string} [styles.fontFamily] Font type for text + * @param {number} [styles.fontSize] Size + * @param {string} [styles.fontStyle] Type of inclination (normal / italic) + * @param {string} [styles.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [styles.textAlign] Type of text align (left / center / right) + * @param {string} [styles.textDecoration] Type of line (underline / line-through / overline) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute: function execute(graphics, id, styles, isSilent) { + var textComp = graphics.getComponent(changeTextStyle_TEXT); + var targetObj = graphics.getObject(id); + + if (!targetObj) { + return promise_default().reject(rejectMessages.noObject); + } + + if (!this.isRedo) { + var undoData = changeTextStyle_makeUndoData(styles, targetObj); + changeTextStyle_cachedUndoDataForSilent = this.setUndoData(undoData, changeTextStyle_cachedUndoDataForSilent, isSilent); + } + + return textComp.setStyle(targetObj, styles); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var textComp = graphics.getComponent(changeTextStyle_TEXT); + var _this$undoData = this.undoData, + textObj = _this$undoData.object, + styles = _this$undoData.styles; + return textComp.setStyle(textObj, styles); + } +}; +factory_command.register(changeTextStyle_command); +/* harmony default export */ var changeTextStyle = ((/* unused pure expression or super */ null && (changeTextStyle_command))); +;// CONCATENATED MODULE: ./src/js/command/clearObjects.js + + + +var clearObjects_command = { + name: commandNames.CLEAR_OBJECTS, + + /** + * Clear all objects without background (main) image + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + execute: function execute(graphics) { + var _this = this; + + return new (promise_default())(function (resolve) { + _this.undoData.objects = graphics.removeAll(); + resolve(); + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + * @ignore + */ + undo: function undo(graphics) { + graphics.add(this.undoData.objects); + return promise_default().resolve(); + } +}; +factory_command.register(clearObjects_command); +/* harmony default export */ var clearObjects = ((/* unused pure expression or super */ null && (clearObjects_command))); +;// CONCATENATED MODULE: ./src/js/command/flip.js + + +var FLIP = componentNames.FLIP; +var flip_command = { + name: commandNames.FLIP_IMAGE, + + /** + * flip an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - 'flipX' or 'flipY' or 'reset' + * @returns {Promise} + */ + execute: function execute(graphics, type) { + var flipComp = graphics.getComponent(FLIP); + this.undoData.setting = flipComp.getCurrentSetting(); + return flipComp[type](); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var flipComp = graphics.getComponent(FLIP); + return flipComp.set(this.undoData.setting); + } +}; +factory_command.register(flip_command); +/* harmony default export */ var command_flip = ((/* unused pure expression or super */ null && (flip_command))); +;// CONCATENATED MODULE: ./src/js/command/loadImage.js + + + + +var IMAGE_LOADER = componentNames.IMAGE_LOADER; +var loadImage_command = { + name: commandNames.LOAD_IMAGE, + + /** + * Load a background (main) image + * @param {Graphics} graphics - Graphics instance + * @param {string} imageName - Image name + * @param {string} imgUrl - Image Url + * @returns {Promise} + */ + execute: function execute(graphics, imageName, imgUrl) { + var _context; + + var loader = graphics.getComponent(IMAGE_LOADER); + var prevImage = loader.getCanvasImage(); + var prevImageWidth = prevImage ? prevImage.width : 0; + var prevImageHeight = prevImage ? prevImage.height : 0; + + var objects = filter_default()(_context = graphics.removeAll(true)).call(_context, function (objectItem) { + return objectItem.type !== 'cropzone'; + }); + + for_each_default()(objects).call(objects, function (objectItem) { + objectItem.evented = true; + }); + + this.undoData = { + name: loader.getImageName(), + image: prevImage, + objects: objects + }; + return loader.load(imageName, imgUrl).then(function (newImage) { + return { + oldWidth: prevImageWidth, + oldHeight: prevImageHeight, + newWidth: newImage.width, + newHeight: newImage.height + }; + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var loader = graphics.getComponent(IMAGE_LOADER); + var _this$undoData = this.undoData, + objects = _this$undoData.objects, + name = _this$undoData.name, + image = _this$undoData.image; + graphics.removeAll(true); + graphics.add(objects); + return loader.load(name, image); + } +}; +factory_command.register(loadImage_command); +/* harmony default export */ var loadImage = ((/* unused pure expression or super */ null && (loadImage_command))); +;// CONCATENATED MODULE: ./src/js/command/removeFilter.js + + +var removeFilter_FILTER = componentNames.FILTER; +var removeFilter_command = { + name: commandNames.REMOVE_FILTER, + + /** + * Remove a filter from an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + execute: function execute(graphics, type) { + var filterComp = graphics.getComponent(removeFilter_FILTER); + this.undoData.options = filterComp.getOptions(type); + return filterComp.remove(type); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @param {string} type - Filter type + * @returns {Promise} + */ + undo: function undo(graphics, type) { + var filterComp = graphics.getComponent(removeFilter_FILTER); + var options = this.undoData.options; + return filterComp.add(type, options); + } +}; +factory_command.register(removeFilter_command); +/* harmony default export */ var removeFilter = ((/* unused pure expression or super */ null && (removeFilter_command))); +;// CONCATENATED MODULE: ./src/js/command/removeObject.js + + + +var removeObject_command = { + name: commandNames.REMOVE_OBJECT, + + /** + * Remove an object + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @returns {Promise} + */ + execute: function execute(graphics, id) { + var _this = this; + + return new (promise_default())(function (resolve, reject) { + _this.undoData.objects = graphics.removeObjectById(id); + + if (_this.undoData.objects.length) { + resolve(); + } else { + reject(rejectMessages.noObject); + } + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.add(this.undoData.objects); + return promise_default().resolve(); + } +}; +factory_command.register(removeObject_command); +/* harmony default export */ var removeObject = ((/* unused pure expression or super */ null && (removeObject_command))); +;// CONCATENATED MODULE: ./src/js/command/resizeCanvasDimension.js + + + +var resizeCanvasDimension_command = { + name: commandNames.RESIZE_CANVAS_DIMENSION, + + /** + * resize the canvas with given dimension + * @param {Graphics} graphics - Graphics instance + * @param {{width: number, height: number}} dimension - Max width & height + * @returns {Promise} + */ + execute: function execute(graphics, dimension) { + var _this = this; + + return new (promise_default())(function (resolve) { + _this.undoData.size = { + width: graphics.cssMaxWidth, + height: graphics.cssMaxHeight + }; + graphics.setCssMaxDimension(dimension); + graphics.adjustCanvasDimension(); + resolve(); + }); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + graphics.setCssMaxDimension(this.undoData.size); + graphics.adjustCanvasDimension(); + return promise_default().resolve(); + } +}; +factory_command.register(resizeCanvasDimension_command); +/* harmony default export */ var resizeCanvasDimension = ((/* unused pure expression or super */ null && (resizeCanvasDimension_command))); +;// CONCATENATED MODULE: ./src/js/command/rotate.js + + + +var ROTATION = componentNames.ROTATION; +/** + * Cached data for undo + * @type {Object} + */ + +var rotate_cachedUndoDataForSilent = null; +/** + * Make undo data + * @param {Component} rotationComp - rotation component + * @returns {object} - undodata + */ + +function rotate_makeUndoData(rotationComp) { + return { + angle: rotationComp.getCurrentAngle() + }; +} + +var rotate_command = { + name: commandNames.ROTATE_IMAGE, + + /** + * Rotate an image + * @param {Graphics} graphics - Graphics instance + * @param {string} type - 'rotate' or 'setAngle' + * @param {number} angle - angle value (degree) + * @param {boolean} isSilent - is silent execution or not + * @returns {Promise} + */ + execute: function execute(graphics, type, angle, isSilent) { + var rotationComp = graphics.getComponent(ROTATION); + + if (!this.isRedo) { + var undoData = rotate_makeUndoData(rotationComp); + rotate_cachedUndoDataForSilent = this.setUndoData(undoData, rotate_cachedUndoDataForSilent, isSilent); + } + + return rotationComp[type](angle); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var rotationComp = graphics.getComponent(ROTATION); + + var _this$args = _slicedToArray(this.args, 3), + type = _this$args[1], + angle = _this$args[2]; + + if (type === 'setAngle') { + return rotationComp[type](this.undoData.angle); + } + + return rotationComp.rotate(-angle); + } +}; +factory_command.register(rotate_command); +/* harmony default export */ var command_rotate = ((/* unused pure expression or super */ null && (rotate_command))); +;// CONCATENATED MODULE: ./src/js/command/setObjectProperties.js + + + + +var setObjectProperties_command = { + name: commandNames.SET_OBJECT_PROPERTIES, + + /** + * Set object properties + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} props - properties + * @param {string} [props.fill] Color + * @param {string} [props.fontFamily] Font type for text + * @param {number} [props.fontSize] Size + * @param {string} [props.fontStyle] Type of inclination (normal / italic) + * @param {string} [props.fontWeight] Type of thicker or thinner looking (normal / bold) + * @param {string} [props.textAlign] Type of text align (left / center / right) + * @param {string} [props.textDecoration] Type of line (underline / line-through / overline) + * @returns {Promise} + */ + execute: function execute(graphics, id, props) { + var _this = this; + + var targetObj = graphics.getObject(id); + + if (!targetObj) { + return promise_default().reject(rejectMessages.noObject); + } + + this.undoData.props = {}; + forEachOwnProperties_default()(props, function (value, key) { + _this.undoData.props[key] = targetObj[key]; + }); + graphics.setObjectProperties(id, props); + return promise_default().resolve(); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @returns {Promise} + */ + undo: function undo(graphics, id) { + var props = this.undoData.props; + graphics.setObjectProperties(id, props); + return promise_default().resolve(); + } +}; +factory_command.register(setObjectProperties_command); +/* harmony default export */ var setObjectProperties = ((/* unused pure expression or super */ null && (setObjectProperties_command))); +;// CONCATENATED MODULE: ./src/js/command/setObjectPosition.js + + + +var setObjectPosition_command = { + name: commandNames.SET_OBJECT_POSITION, + + /** + * Set object properties + * @param {Graphics} graphics - Graphics instance + * @param {number} id - object id + * @param {Object} posInfo - position object + * @param {number} posInfo.x - x position + * @param {number} posInfo.y - y position + * @param {string} posInfo.originX - can be 'left', 'center', 'right' + * @param {string} posInfo.originY - can be 'top', 'center', 'bottom' + * @returns {Promise} + */ + execute: function execute(graphics, id, posInfo) { + var targetObj = graphics.getObject(id); + + if (!targetObj) { + return promise_default().reject(rejectMessages.noObject); + } + + this.undoData.objectId = id; + this.undoData.props = graphics.getObjectProperties(id, ['left', 'top']); + graphics.setObjectPosition(id, posInfo); + graphics.renderAll(); + return promise_default().resolve(); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var _this$undoData = this.undoData, + objectId = _this$undoData.objectId, + props = _this$undoData.props; + graphics.setObjectProperties(objectId, props); + graphics.renderAll(); + return promise_default().resolve(); + } +}; +factory_command.register(setObjectPosition_command); +/* harmony default export */ var setObjectPosition = ((/* unused pure expression or super */ null && (setObjectPosition_command))); +;// CONCATENATED MODULE: ./src/js/command/changeSelection.js + + + + + +var changeSelection_command = { + name: commandNames.CHANGE_SELECTION, + execute: function execute(graphics, props) { + if (this.isRedo) { + for_each_default()(props).call(props, function (prop) { + graphics.setObjectProperties(prop.id, prop); + }); + } else { + this.undoData = getCachedUndoDataForDimension(); + } + + return promise_default().resolve(); + }, + undo: function undo(graphics) { + var _context; + + for_each_default()(_context = this.undoData).call(_context, function (datum) { + graphics.setObjectProperties(datum.id, datum); + }); + + return promise_default().resolve(); + } +}; +factory_command.register(changeSelection_command); +/* harmony default export */ var changeSelection = ((/* unused pure expression or super */ null && (changeSelection_command))); +;// CONCATENATED MODULE: ./src/js/command/resize.js + + +var RESIZE = componentNames.RESIZE; +var resize_command = { + name: commandNames.RESIZE_IMAGE, + + /** + * Resize an image + * @param {Graphics} graphics - Graphics instance + * @param {object} dimensions - Image Dimensions + * @returns {Promise} + */ + execute: function execute(graphics, dimensions) { + var resizeComp = graphics.getComponent(RESIZE); + var originalDimensions = resizeComp.getOriginalDimensions(); + + if (!originalDimensions) { + originalDimensions = resizeComp.getCurrentDimensions(); + } + + this.undoData.dimensions = originalDimensions; + return resizeComp.resize(dimensions); + }, + + /** + * @param {Graphics} graphics - Graphics instance + * @returns {Promise} + */ + undo: function undo(graphics) { + var resizeComp = graphics.getComponent(RESIZE); + return resizeComp.resize(this.undoData.dimensions); + } +}; +factory_command.register(resize_command); +/* harmony default export */ var command_resize = ((/* unused pure expression or super */ null && (resize_command))); +;// CONCATENATED MODULE: ./src/index.js + + + // commands + + + + + + + + + + + + + + + + + + + + + + +/* harmony default export */ var src = (imageEditor); + +}(); +__webpack_exports__ = __webpack_exports__["default"]; +/******/ return __webpack_exports__; +/******/ })() +; +}); \ No newline at end of file diff --git a/core/vendor/lity/lity.min.css b/core/vendor/lity/lity.min.css index 3ff4fa55..ac6fa560 100755 --- a/core/vendor/lity/lity.min.css +++ b/core/vendor/lity/lity.min.css @@ -1 +1 @@ -.lity{z-index:9990;position:fixed;top:0;right:0;bottom:0;left:0;white-space:nowrap;background:#0b0b0b;background:rgba(0,0,0,.9);outline:none!important;opacity:0;-webkit-transition:opacity .3s ease;-o-transition:opacity .3s ease;transition:opacity .3s ease}.lity.lity-opened{opacity:1}.lity.lity-closed{opacity:0}.lity *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.lity-wrap{z-index:9990;position:fixed;top:0;right:0;bottom:0;left:0;text-align:center;outline:none!important}.lity-wrap:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.lity-loader{z-index:9991;color:#fff;position:absolute;top:50%;margin-top:-.8em;width:100%;text-align:center;font-size:14px;font-family:Arial,Helvetica,sans-serif;opacity:0;-webkit-transition:opacity .3s ease;-o-transition:opacity .3s ease;transition:opacity .3s ease}.lity-loading .lity-loader{opacity:1}.lity-container{z-index:9992;position:relative;text-align:left;vertical-align:middle;display:inline-block;white-space:normal;max-width:100%;max-height:100%;outline:none!important}.lity-content{z-index:9993;width:100%;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1);-webkit-transition:-webkit-transform .3s ease;transition:-webkit-transform .3s ease;-o-transition:-o-transform .3s ease;transition:transform .3s ease;transition:transform .3s ease,-webkit-transform .3s ease,-o-transform .3s ease}.lity-loading .lity-content,.lity-closed .lity-content{-webkit-transform:scale(.8);-ms-transform:scale(.8);-o-transform:scale(.8);transform:scale(.8)}.lity-content:after{content:'';position:absolute;left:0;top:0;bottom:0;display:block;right:0;width:auto;height:auto;z-index:-1;-webkit-box-shadow:0 0 8px rgba(0,0,0,.6);box-shadow:0 0 8px rgba(0,0,0,.6)}.lity-close{z-index:9994;width:35px;height:35px;position:fixed;right:0;top:0;-webkit-appearance:none;cursor:pointer;text-decoration:none;text-align:center;padding:0;color:#fff;font-style:normal;font-size:35px;font-family:Arial,Baskerville,monospace;line-height:35px;text-shadow:0 1px 2px rgba(0,0,0,.6);border:0;background:none;outline:none;-webkit-box-shadow:none;box-shadow:none}.lity-close::-moz-focus-inner{border:0;padding:0}.lity-close:hover,.lity-close:focus,.lity-close:active,.lity-close:visited{text-decoration:none;text-align:center;padding:0;color:#fff;font-style:normal;font-size:35px;font-family:Arial,Baskerville,monospace;line-height:35px;text-shadow:0 1px 2px rgba(0,0,0,.6);border:0;background:none;outline:none;-webkit-box-shadow:none;box-shadow:none}.lity-close:active{top:1px}.lity-image img{max-width:100%;display:block;line-height:0;border:0}.lity-iframe .lity-container,.lity-youtube .lity-container,.lity-vimeo .lity-container,.lity-facebookvideo .lity-container,.lity-googlemaps .lity-container{width:100%;max-width:1064px}.lity-iframe-container{width:100%;height:0;padding-top:56.25%;overflow:auto;pointer-events:auto;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-overflow-scrolling:touch}.lity-iframe-container iframe{position:absolute;display:block;top:0;left:0;width:100%;height:100%;-webkit-box-shadow:0 0 8px rgba(0,0,0,.6);box-shadow:0 0 8px rgba(0,0,0,.6);background:#000}.lity-hide{display:none} \ No newline at end of file +.lity{z-index:9990;position:fixed;top:0;right:0;bottom:0;left:0;white-space:nowrap;background:#0b0b0b;background:rgba(0,0,0,.9);outline:none!important;opacity:0;-webkit-transition:opacity .3s ease;-o-transition:opacity .3s ease;transition:opacity .3s ease}.lity.lity-opened{opacity:1}.lity.lity-closed{opacity:0}.lity *{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.lity-wrap{z-index:9990;position:fixed;top:0;right:0;bottom:0;left:0;text-align:center;outline:none!important}.lity-wrap:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.lity-loader{z-index:9991;color:#fff;position:absolute;top:50%;margin-top:-.8em;width:100%;text-align:center;font-size:14px;font-family:Arial,Helvetica,sans-serif;opacity:0;-webkit-transition:opacity .3s ease;-o-transition:opacity .3s ease;transition:opacity .3s ease}.lity-loading .lity-loader{opacity:1}.lity-container{z-index:9992;position:relative;text-align:left;vertical-align:middle;display:inline-block;white-space:normal;max-width:100%;max-height:100%;outline:none!important}.lity-content{z-index:9993;width:100%;-webkit-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1);-webkit-transition:-webkit-transform .3s ease;transition:-webkit-transform .3s ease;-o-transition:-o-transform .3s ease;transition:transform .3s ease;transition:transform .3s ease,-webkit-transform .3s ease,-o-transform .3s ease}.lity-loading .lity-content,.lity-closed .lity-content{-webkit-transform:scale(.8);-ms-transform:scale(.8);-o-transform:scale(.8);transform:scale(.8)}.lity-content:after{content:'';position:absolute;left:0;top:0;bottom:0;display:block;right:0;width:auto;height:auto;z-index:-1;-webkit-box-shadow:0 0 8px rgba(0,0,0,.6);box-shadow:0 0 8px rgba(0,0,0,.6)}.lity-close{z-index:9994;width:35px;height:35px;position:fixed;right:0;top:0;-webkit-appearance:none;cursor:pointer;text-decoration:none;text-align:center;padding:0;color:#fff;font-style:normal;font-size:35px;font-family:Arial,Baskerville,monospace;line-height:35px;text-shadow:0 1px 2px rgba(0,0,0,.6);border:0;background:none;outline:none;-webkit-box-shadow:none;box-shadow:none}.lity-close::-moz-focus-inner{border:0;padding:0}.lity-close:hover,.lity-close:focus,.lity-close:active,.lity-close:visited{text-decoration:none;text-align:center;padding:0;color:#fff;font-style:normal;font-size:35px;font-family:Arial,Baskerville,monospace;line-height:35px;text-shadow:0 1px 2px rgba(0,0,0,.6);border:0;background:none;outline:none;-webkit-box-shadow:none;box-shadow:none}.lity-close:active{top:1px}.lity-image img{max-width:100%;display:block;line-height:0;border:0}.lity-iframe .lity-container,.lity-youtube .lity-container,.lity-vimeo .lity-container,.lity-facebookvideo .lity-container,.lity-googlemaps .lity-container{width:100%;max-width:90%}.lity-iframe-container{width:100%;height:0;padding-top:56.25%;overflow:auto;pointer-events:auto;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-overflow-scrolling:touch}.lity-iframe-container iframe{position:absolute;display:block;top:0;left:0;width:100%;height:100%;-webkit-box-shadow:0 0 8px rgba(0,0,0,.6);box-shadow:0 0 8px rgba(0,0,0,.6);background:#000}.lity-hide{display:none} \ No newline at end of file