diff --git a/src/views/demo/tuiImageEditor/components/image-editor.vue b/src/views/demo/tuiImageEditor/components/image-editor.vue
new file mode 100644
index 0000000..4abce6b
--- /dev/null
+++ b/src/views/demo/tuiImageEditor/components/image-editor.vue
@@ -0,0 +1,7 @@
+
+ 123
+
+
+
diff --git a/src/views/demo/tuiImageEditor/css/tui-image-editor.css b/src/views/demo/tuiImageEditor/css/tui-image-editor.css
new file mode 100644
index 0000000..74e0a05
--- /dev/null
+++ b/src/views/demo/tuiImageEditor/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}
diff --git a/src/views/demo/tuiImageEditor/css/tui-image-editor.min.css b/src/views/demo/tuiImageEditor/css/tui-image-editor.min.css
new file mode 100644
index 0000000..ec076fc
--- /dev/null
+++ b/src/views/demo/tuiImageEditor/css/tui-image-editor.min.css
@@ -0,0 +1,5 @@
+/*!
+ * TOAST UI ImageEditor
+ * @version 3.15.3
+ * @license MIT
+ */body>textarea{position:fixed!important}.tui-image-editor-container{background-color:#282828;box-sizing:border-box;height:100%;letter-spacing:.3px;margin:0;min-height:300px;overflow:hidden;padding:0;position:relative}.tui-image-editor-container div,.tui-image-editor-container input,.tui-image-editor-container label,.tui-image-editor-container li,.tui-image-editor-container ul{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{background-color:#151515;min-width:533px;position:absolute;top:0;width:100%}.tui-image-editor-container .tui-image-editor-controls-buttons,.tui-image-editor-container .tui-image-editor-header-buttons{float:right;margin:8px}.tui-image-editor-container .tui-image-editor-controls-logo,.tui-image-editor-container .tui-image-editor-header-logo{float:left;padding:17px;width:30%}.tui-image-editor-container .tui-image-editor-controls-buttons,.tui-image-editor-container .tui-image-editor-controls-logo{display:none;height:100%;width:270px}.tui-image-editor-container .tui-image-editor-controls-buttons button,.tui-image-editor-container .tui-image-editor-controls-buttons div,.tui-image-editor-container .tui-image-editor-header-buttons button,.tui-image-editor-container .tui-image-editor-header-buttons div{border:1px solid #ddd;border-radius:20px;cursor:pointer;display:inline-block;font-family:Noto Sans,sans-serif;font-size:12px;font-weight:700;height:40px;letter-spacing:.3px;line-height:40px;outline:none;padding:0;position:relative;text-align:center;vertical-align:middle;width:120px}.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{bottom:0;cursor:pointer;display:inline-block;left:0;opacity:0;position:absolute;right:0;top:0;width:100%}.tui-image-editor-container .tui-image-editor-main-container{bottom:64px;position:absolute;top:0;width:100%}.tui-image-editor-container .tui-image-editor-main{bottom:0;left:0;position:absolute;right:0;text-align:center;top:64px}.tui-image-editor-container .tui-image-editor-wrap{bottom:0;overflow:auto;width:100%}.tui-image-editor-container .tui-image-editor-wrap .tui-image-editor-size-wrap{display:table;height:100%;width: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{display:inline-block;position:relative}.tui-image-editor-container .tui-image-editor-help-menu,.tui-image-editor-container .tui-image-editor-menu{display:table-cell;list-style:none;margin:0 auto;padding:0;text-align:center;vertical-align:middle;white-space:nowrap;width:auto}.tui-image-editor-container .tui-image-editor-help-menu>.tui-image-editor-item,.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item{border-radius:2px;cursor:pointer;display:inline-block;margin:0 4px;padding:7px 8px 3px;position:relative}.tui-image-editor-container .tui-image-editor-help-menu>.tui-image-editor-item[tooltip-content]:hover:before,.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item[tooltip-content]:hover:before{border-left:7px solid transparent;border-right:7px solid transparent;border-top:7px solid #2f2f2f;content:"";display:inline-block;height:0;left:13px;margin:0 auto;position:absolute;top:-2px;width:0}.tui-image-editor-container .tui-image-editor-help-menu>.tui-image-editor-item[tooltip-content]:hover:after,.tui-image-editor-container .tui-image-editor-menu>.tui-image-editor-item[tooltip-content]:hover:after{background-color:#2f2f2f;border-radius:3px;color:#fff;content:attr(tooltip-content);display:inline-block;font-size:11px;font-weight:lighter;left:0;max-height:23px;min-width:24px;padding:5px 8px;position:absolute;top:-25px}.tui-image-editor-container .tui-image-editor-help-menu>.tui-image-editor-item.active,.tui-image-editor-container .tui-image-editor-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{border:1px solid rgba(255,255,255,.7);display:none;height:100%;position:absolute;width:100%}.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-resize .tui-image-editor-grid-visual,.tui-image-editor-container .tui-image-editor-main.tui-image-editor-menu-rotate .tui-image-editor-grid-visual{display:block}.tui-image-editor-container .tui-image-editor-grid-visual table{border-collapse:collapse;height:100%;width:100%}.tui-image-editor-container .tui-image-editor-grid-visual table td{border:1px solid rgba(255,255,255,.3)}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot:before{background-color:#fff;border:0;border-radius:100%;box-shadow:0 0 1px 0 rgba(0,0,0,.3);box-sizing:border-box;content:"";height:10px;position:absolute;width:10px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.left-top:before{left:-5px;top:-5px}.tui-image-editor-container .tui-image-editor-grid-visual table td.dot.right-top:before{right:-5px;top:-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{bottom:0;display:none;height:150px;position:absolute;white-space:nowrap;width:100%;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{cursor:pointer;display:inline-block;font-size:11px;font-weight:400;margin:0 9px;position:relative}.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{cursor:pointer;display:inline-block;font-family:Noto Sans,sans-serif;font-size:11px;padding-top:5px}.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{bottom:0;display:block;left:0;opacity:.95;position:absolute;right:0;top:0;z-index:-1}.tui-image-editor-container .tui-image-editor-partition>div{border-left:1px solid #3c3c3c;height:52px;margin:0 8px;width:1px}.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{margin-right:30px;text-align:left}.tui-image-editor-container .tui-image-editor-submenu-align label>span{white-space:nowrap;width:55px}.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-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-flip .tui-image-editor-submenu>div.tui-image-editor-menu-flip,.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-mask .tui-image-editor-submenu>div.tui-image-editor-menu-mask,.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-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-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-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-flip .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-mask .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-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-zoom .tui-image-editor-submenu{display:table}.tui-image-editor-container .tui-image-editor-help-menu{background-color:rgba(255,255,255,.06);border-radius:20px;list-style:none;margin:0 auto;padding:0;position:absolute;text-align:center;vertical-align:middle;z-index:2}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history{background-color:#fff;box-shadow:0 2px 6px 0 rgba(0,0,0,.15);color:#444;cursor:auto;display:none;height:276px;padding:4px 2px;position:absolute;transform:translateX(calc(-50% + 12px));width:196px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list{height:268px;list-style:none;overflow:hidden scroll;padding:0}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item{font-size:11px;height:24px;line-height:24px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item{cursor:pointer;height:24px;position:relative}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item .tui-image-editor-history-item svg{height:24px;width: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;height:24px;text-align:left;width:128px}.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;height:24px;left:6px;position:absolute;top:6px;width:24px}.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;height:24px;position:absolute;right:-6px;top:5px;width:24px}.tui-image-editor-container .tui-image-editor-help-menu .tie-panel-history .history-list .history-item.selected-item{background-color:rgba(119,119,119,.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:"";display:inline-block;height:0;margin:0 auto;position:absolute;width: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{white-space:normal;width:187px}.tui-image-editor-container .tui-image-editor-checkbox{display:inline-block;margin:1px 0}.tui-image-editor-container .tui-image-editor-checkbox input{height:14px;opacity:0;width:14px}.tui-image-editor-container .tui-image-editor-checkbox>label>span{color:#fff;height:14px;position:relative}.tui-image-editor-container .tui-image-editor-checkbox>label>span:before,.tui-image-editor-container .tui-image-editor-checkbox input+label:before{background-color:#fff;border:0;border-radius:2px;box-sizing:border-box;content:"";display:inline-block;font-size:11px;height:14px;left:-19px;margin:0;padding-top:1px;position:absolute;text-align:center;top:6px;width:14px}.tui-image-editor-container .tui-image-editor-checkbox input[type=checkbox]:checked+span:before{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAMBJREFUKBWVkjEOwjAMRe2WgZW7IIHEDdhghhuwcQ42rlJugAQS54Cxa5cq1QM5TUpByZfS2j9+dlJVt/tX5ZxbS4ZU9VLkQvSHKTIGRaVJYFmKrBbTCJxE2UgCdDzMZDkHrOV6b95V0US6UmgKodujEZbJg0B0ZgEModO5lrY1TMQf1TpyJGBEjD+E2NPN7ukIUDiF/BfEXgRiGEw8NgkffYGYwCi808fpn/6OvfUfsDr/Vc1IfRf8sKnFVqeiVQfDu0tf/nWH9gAAAABJRU5ErkJggg==);background-size:cover}.tui-image-editor-container .tui-image-editor-selectlist-wrap{position:relative}.tui-image-editor-container .tui-image-editor-selectlist-wrap select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border:1px solid #cbdbdb;border-radius:0;height:28px;margin-top:4px;outline:0;padding:0 7px 0 10px;width:100%}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist{background-color:#fff;border:1px solid #ccc;border-top:0;display:none;padding:4px 0;position:relative;top:-1px}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li{display:block;font-family:Noto Sans,sans-serif;padding:7px 10px;text-align:left}.tui-image-editor-container .tui-image-editor-selectlist-wrap .tui-image-editor-selectlist li:hover{background-color:rgba(81,92,230,.05)}.tui-image-editor-container .tui-image-editor-selectlist-wrap:before{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAHlJREFUKBVjYBgFOEOAEVkmPDxc89+/f6eAYjzI4kD2FyYmJrOVK1deh4kzwRggGiQBVJCELAZig8SQNYHEmEEEMrh69eo1HR0dfqCYJUickZGxf9WqVf3IakBsFBthklpaWmVA9mEQhrJhUoTp0NBQCRAmrHL4qgAAuu4cWZOZIGsAAAAASUVORK5CYII=);background-size:cover;content:"";display:inline-block;height:14px;position:absolute;right:5px;top:10px;width:14px}.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-pointer .tui-image-editor-disabled,.tui-image-editor-container .tui-image-editor-virtual-range-subbar .tui-image-editor-disabled{backbround-color:red}.tui-image-editor-container .tui-image-editor-range{display:inline-block;height:17px;position:relative;top:5px;width:166px}.tui-image-editor-container .tui-image-editor-virtual-range-bar{background-color:#666;height:2px;position:absolute;top:7px;width:100%}.tui-image-editor-container .tui-image-editor-virtual-range-subbar{background-color:#d1d1d1;height:100%;left:0;position:absolute;right:0}.tui-image-editor-container .tui-image-editor-virtual-range-pointer{background-color:#fff;border-radius:100%;cursor:pointer;height:12px;left:0;position:absolute;top:-5px;width:12px}.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{margin-left:10px;width:108px}.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-left:19px;margin-top:-2px}.tui-image-editor-container .tui-image-editor-range-wrap.tui-image-editor-newline.short label{color:#8e8e8e;font-weight:400}.tui-image-editor-container .tui-image-editor-range-wrap label{color:#fff;font-size:11px;margin-right:7px;vertical-align:baseline}.tui-image-editor-container .tui-image-editor-range-value{background-color:#1c1c1c;border:1px solid #d5d5d5;border-radius:2px;box-shadow:none;color:#fff;cursor:default;font-family:Noto Sans,sans-serif;font-weight:lighter;height:24px;margin-left:4px;margin-top:15px;outline:none;text-align:center;vertical-align:baseline;width:40px}.tui-image-editor-container .tui-image-editor-controls{background-color:#151515;bottom:0;display:table;height:64px;position:absolute;width:100%;z-index:2}.tui-image-editor-container .tui-image-editor-icpartition{background-color:#444;display:inline-block;height:24px;width:1px}.tui-image-editor-container.left .tui-image-editor-menu>.tui-image-editor-item[tooltip-content]:before{border-bottom:7px solid transparent;border-right:7px solid #2f2f2f;border-top:7px solid transparent;left:28px;top:11px}.tui-image-editor-container.left .tui-image-editor-menu>.tui-image-editor-item[tooltip-content]:after{left:42px;top:7px;white-space:nowrap}.tui-image-editor-container.left .tui-image-editor-submenu{height:100%;left:0;width:248px}.tui-image-editor-container.left .tui-image-editor-main-container{height:100%;left:64px;width:calc(100% - 64px)}.tui-image-editor-container.left .tui-image-editor-controls{display:table;height:100%;width:64px}.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{height:1px;position:relative;top:-7px;width:24px}.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;margin:auto;width:75%}.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-bottom:1px solid #3c3c3c;border-left:0;height:10px;margin:0;width:100%}.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{white-space:normal;width:182px}.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;margin:auto;text-align:left;width:75%}.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{border-bottom:7px solid transparent;border-left:7px solid #2f2f2f;border-top:7px solid transparent;left:-3px;top:11px}.tui-image-editor-container.right .tui-image-editor-menu>.tui-image-editor-item[tooltip-content]:after{left:unset;right:43px;top:7px;white-space:nowrap}.tui-image-editor-container.right .tui-image-editor-submenu{height:100%;right:0;width:248px}.tui-image-editor-container.right .tui-image-editor-main-container{height:100%;right:64px;width:calc(100% - 64px)}.tui-image-editor-container.right .tui-image-editor-controls{display:table;height:100%;right:0;width:64px}.tui-image-editor-container.bottom .tui-image-editor-submenu .tui-image-editor-partition.only-left-right,.tui-image-editor-container.top .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{border-bottom:8px solid #fff;border-left:7px solid transparent;border-right:7px solid transparent;border-top:0;top:-8px}.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{border-bottom:7px solid #2f2f2f;border-top:0;left:13px;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{bottom:auto;top:0}.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-buttons,.tui-image-editor-container.top .tui-image-editor-controls-logo{display:table-cell}.tui-image-editor-container.top .tui-image-editor-main{height:calc(100% - 64px);top:64px}.tui-image-editor-container.top .tui-image-editor-controls{bottom:inherit;top:0}.tui-image-editor-container .tui-image-editor-help-menu.top{height:40px;left:50%;top:8px;transform:translateX(-50%);white-space:nowrap;width:506px}.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-bottom:8px solid #fff;border-left:8px solid transparent;border-right:8px solid transparent;left:90px;top:-8px}.tui-image-editor-container .tui-image-editor-help-menu.top>.tui-image-editor-item[tooltip-content]:before{border:7px solid transparent;border-bottom:7px solid #2f2f2f;border-top:none;left:13px;top:35px}.tui-image-editor-container .tui-image-editor-help-menu.top>.tui-image-editor-item[tooltip-content]:after{left:-4px;top:41px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.top>.tui-image-editor-item[tooltip-content].opened:after,.tui-image-editor-container .tui-image-editor-help-menu.top>.tui-image-editor-item[tooltip-content].opened:before{content:none}.tui-image-editor-container .tui-image-editor-help-menu.bottom{bottom:8px;height:40px;left:50%;transform:translateX(-50%);white-space:nowrap;width:506px}.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-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #fff;bottom:-8px;left:90px}.tui-image-editor-container .tui-image-editor-help-menu.bottom>.tui-image-editor-item[tooltip-content]:before{border:7px solid transparent;border-bottom:none;border-top-color:#2f2f2f;bottom:36px;left:13px;top:auto}.tui-image-editor-container .tui-image-editor-help-menu.bottom>.tui-image-editor-item[tooltip-content]:after{bottom:41px;left:-4px;top:auto;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.bottom>.tui-image-editor-item[tooltip-content].opened:after,.tui-image-editor-container .tui-image-editor-help-menu.bottom>.tui-image-editor-item[tooltip-content].opened:before{content:none}.tui-image-editor-container .tui-image-editor-help-menu.left{height:506px;left:8px;top:50%;transform:translateY(-50%);white-space:inherit;width:40px}.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-bottom:8px solid transparent;border-right:8px solid #fff;border-top:8px solid transparent;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{border:7px solid transparent;border-left:none;border-right-color:#2f2f2f;left:27px;top:11px}.tui-image-editor-container .tui-image-editor-help-menu.left>.tui-image-editor-item[tooltip-content]:after{left:40px;top:7px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.left>.tui-image-editor-item[tooltip-content].opened:after,.tui-image-editor-container .tui-image-editor-help-menu.left>.tui-image-editor-item[tooltip-content].opened:before{content:none}.tui-image-editor-container .tui-image-editor-help-menu.right{height:506px;right:8px;top:50%;transform:translateY(-50%);white-space:inherit;width:40px}.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-bottom:8px solid transparent;border-left:8px solid #fff;border-top:8px solid transparent;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{border:7px solid transparent;border-left:7px solid #2f2f2f;border-right:none;left:-6px;top:11px}.tui-image-editor-container .tui-image-editor-help-menu.right>.tui-image-editor-item[tooltip-content]:after{left:auto;right:39px;top:7px;white-space:nowrap}.tui-image-editor-container .tui-image-editor-help-menu.right>.tui-image-editor-item[tooltip-content].opened:after,.tui-image-editor-container .tui-image-editor-help-menu.right>.tui-image-editor-item[tooltip-content].opened:before{content:none}.tui-image-editor-container .tie-icon-add-button .tui-image-editor-button{min-width:42px}.tui-image-editor-container .svg_ic-helpmenu,.tui-image-editor-container .svg_ic-menu{height:24px;width:24px}.tui-image-editor-container .svg_ic-submenu{height:32px;width:32px}.tui-image-editor-container .svg_img-bi{height:26px;width:257px}.tui-image-editor-container .tui-image-editor-controls svg>use,.tui-image-editor-container .tui-image-editor-help-menu svg>use{display:none}.tui-image-editor-container .tui-image-editor-controls .enabled svg:hover>use.hover,.tui-image-editor-container .tui-image-editor-controls .normal svg:hover>use.hover,.tui-image-editor-container .tui-image-editor-help-menu .enabled svg:hover>use.hover,.tui-image-editor-container .tui-image-editor-help-menu .normal svg:hover>use.hover{display:block}.tui-image-editor-container .tui-image-editor-controls .active svg:hover>use.hover,.tui-image-editor-container .tui-image-editor-help-menu .active svg:hover>use.hover{display:none}.tui-image-editor-container .tui-image-editor-controls .active svg>use.active,.tui-image-editor-container .tui-image-editor-controls .enabled svg>use.enabled,.tui-image-editor-container .tui-image-editor-controls .on svg>use.hover,.tui-image-editor-container .tui-image-editor-controls .opened svg>use.hover,.tui-image-editor-container .tui-image-editor-controls svg>use.normal,.tui-image-editor-container .tui-image-editor-help-menu .active svg>use.active,.tui-image-editor-container .tui-image-editor-help-menu .enabled svg>use.enabled,.tui-image-editor-container .tui-image-editor-help-menu .on svg>use.hover,.tui-image-editor-container .tui-image-editor-help-menu .opened svg>use.hover,.tui-image-editor-container .tui-image-editor-help-menu svg>use.normal{display:block}.tui-image-editor-container .tui-image-editor-controls .active svg>use.normal,.tui-image-editor-container .tui-image-editor-controls .enabled svg>use.normal,.tui-image-editor-container .tui-image-editor-help-menu .active svg>use.normal,.tui-image-editor-container .tui-image-editor-help-menu .enabled svg>use.normal{display:none}.tui-image-editor-container .tui-image-editor-controls .help.enabled svg>use.normal,.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-help-menu .help svg>use.disabled{display:block}.tui-image-editor-container .tui-image-editor-controls .help.enabled svg>use.disabled,.tui-image-editor-container .tui-image-editor-help-menu .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{background-color:#f5f5f5;border:1px solid #d5d5d5;border-radius:2px;height:28px;margin-top:6px;padding:4px 7px;width:159px}.tui-image-editor-container .tui-colorpicker-palette-hex{background-color:#f5f5f5;border:0;font-family:Noto Sans,sans-serif;font-size:11px;margin-top:2px;width:114px}.tui-image-editor-container .tui-colorpicker-palette-hex[value=""]+.tui-colorpicker-palette-preview,.tui-image-editor-container .tui-colorpicker-palette-hex[value="#ffffff"]+.tui-colorpicker-palette-preview{border:1px solid #ccc}.tui-image-editor-container .tui-colorpicker-palette-hex[value=""]+.tui-colorpicker-palette-preview{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);background-size:cover}.tui-image-editor-container .tui-colorpicker-palette-preview{border:0;border-radius:100%;float:left;height:17px;width:17px}.tui-image-editor-container .color-picker-control{background-color:#fff;border-radius:2px;box-shadow:0 3px 22px 6px rgba(0,0,0,.15);display:none;padding:16px;position:absolute;width:192px;z-index:99}.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{background-size:cover;border:0;border-radius:100%;font-size:1px;margin:2px}.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title=""],.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-button[title="#ffffff"]{border:1px solid #ccc}.tui-image-editor-container .color-picker-control .triangle{border-left:7px solid transparent;border-right:7px solid transparent;border-top:8px solid #fff;bottom:-8px;height:0;left:84px;position:absolute;width:0}.tui-image-editor-container .color-picker-control .tui-colorpicker-container,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container,.tui-image-editor-container .color-picker-control .tui-colorpicker-palette-container ul{height:auto;width:100%}.tui-image-editor-container .filter-color-item .color-picker-control label{font-color:#333;font-weight:400;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>label:before,.tui-image-editor-container .filter-color-item .tui-image-editor-checkbox input+label:before{left:-16px}.tui-image-editor-container .color-picker{height:auto;width:100%}.tui-image-editor-container .color-picker-value{border:0;border-radius:100%;height:32px;margin:auto auto 1px;width:32px}.tui-image-editor-container .color-picker-value.transparent{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);background-size:cover;border:1px solid #cbcbcb}.tui-image-editor-container .color-picker-value+label{color:#fff}.tui-image-editor-container .tui-image-editor-submenu svg>use{display:none}.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-3 .tui-image-editor-button[data-icontype=icon-arrow-3] svg>use.active,.tie-icon-add-button.icon-arrow .tui-image-editor-button[data-icontype=icon-arrow] svg>use.active,.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-2 .tui-image-editor-button[data-icontype=icon-star-2] svg>use.active,.tie-icon-add-button.icon-star .tui-image-editor-button[data-icontype=icon-star] svg>use.active,.tui-image-editor-container .tui-image-editor-submenu svg>use.normal{display:block}.tie-draw-line-select-button.free .tui-image-editor-button.free svg>use.normal,.tie-draw-line-select-button.line .tui-image-editor-button.line svg>use.normal{display:none}.tie-draw-line-select-button.free .tui-image-editor-button.free svg>use.active,.tie-draw-line-select-button.line .tui-image-editor-button.line svg>use.active{display:block}.tie-flip-button.flipX .tui-image-editor-button.flipX svg>use.normal,.tie-flip-button.flipY .tui-image-editor-button.flipY svg>use.normal,.tie-flip-button.resetFlip .tui-image-editor-button.resetFlip svg>use.normal{display:none}.tie-flip-button.flipX .tui-image-editor-button.flipX svg>use.active,.tie-flip-button.flipY .tui-image-editor-button.flipY svg>use.active,.tie-flip-button.resetFlip .tui-image-editor-button.resetFlip 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.apply.active svg>use.active,.tie-crop-button .tui-image-editor-button.preset.active svg>use.active,.tie-crop-preset-button .tui-image-editor-button.apply.active svg>use.active,.tie-crop-preset-button .tui-image-editor-button.preset.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.apply.active svg>use.active,.tie-resize-button .tui-image-editor-button.preset.active svg>use.active,.tie-resize-preset-button .tui-image-editor-button.apply.active svg>use.active,.tie-resize-preset-button .tui-image-editor-button.preset.active svg>use.active{display:block}.tie-shape-button.circle .tui-image-editor-button.circle svg>use.normal,.tie-shape-button.rect .tui-image-editor-button.rect svg>use.normal,.tie-shape-button.triangle .tui-image-editor-button.triangle svg>use.normal{display:none}.tie-shape-button.circle .tui-image-editor-button.circle svg>use.active,.tie-shape-button.rect .tui-image-editor-button.rect svg>use.active,.tie-shape-button.triangle .tui-image-editor-button.triangle 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-left .tui-image-editor-button.left svg>use.active,.tie-text-align-button.tie-text-align-right .tui-image-editor-button.right svg>use.active,.tie-text-effect-button .tui-image-editor-button.active svg>use.active{display:block}.tie-icon-image-file,.tie-mask-image-file{border:1px solid green;cursor:inherit;height:100%;left:0;opacity:0;position:absolute;top:0;width:100%}.tie-zoom-button.flipX .tui-image-editor-button.flipX svg>use.normal,.tie-zoom-button.flipY .tui-image-editor-button.flipY svg>use.normal,.tie-zoom-button.resetFlip .tui-image-editor-button.resetFlip svg>use.normal{display:none}.tie-zoom-button.flipX .tui-image-editor-button.flipX svg>use.active,.tie-zoom-button.flipY .tui-image-editor-button.flipY svg>use.active,.tie-zoom-button.resetFlip .tui-image-editor-button.resetFlip 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/src/views/demo/tuiImageEditor/index.vue b/src/views/demo/tuiImageEditor/index.vue
new file mode 100644
index 0000000..79d9425
--- /dev/null
+++ b/src/views/demo/tuiImageEditor/index.vue
@@ -0,0 +1,32 @@
+
+
+ tui-image-editor使用教程
+ 点击打开图片标注
+
+
+
+
+
+
diff --git a/src/views/demo/tuiImageEditor/js/tui-image-editor.js b/src/views/demo/tuiImageEditor/js/tui-image-editor.js
new file mode 100644
index 0000000..03eef07
--- /dev/null
+++ b/src/views/demo/tuiImageEditor/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;
+ }
+ // /g,">")},graphemeSplit:function(e){var i,n=0,r=[];for(n=0;n-1?t.prototype[r]=function(t){return function(){var i=this.constructor.superclass;this.constructor.superclass=n;var r=e[t].apply(this,arguments);if(this.constructor.superclass=i,"initialize"!==t)return r}}(r):t.prototype[r]=e[r],i&&(e.toString!==Object.prototype.toString&&(t.prototype.toString=e.toString),e.valueOf!==Object.prototype.valueOf&&(t.prototype.valueOf=e.valueOf))};function r(){}function o(e){for(var i=null,n=this;n.constructor.superclass;){var r=n.constructor.superclass.prototype[e];if(n[e]!==r){i=r;break}n=n.constructor.superclass.prototype}return i?arguments.length>1?i.apply(this,t.call(arguments,1)):i.call(this):console.log("tried to callSuper "+e+", method not found in prototype chain",this)}S.util.createClass=function(){var i=null,a=t.call(arguments,0);function s(){this.initialize.apply(this,arguments)}"function"==typeof a[0]&&(i=a.shift()),s.superclass=i,s.subclasses=[],i&&(r.prototype=i.prototype,s.prototype=new r,i.subclasses.push(s));for(var l=0,c=a.length;l-1||"touch"===t.pointerType},h=S.document.createElement("div"),f="string"==typeof h.style.opacity,g="string"==typeof h.style.filter,d=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,v=function(t){return t},f?v=function(t,e){return t.style.opacity=e,t}:g&&(v=function(t,e){var i=t.style;return t.currentStyle&&!t.currentStyle.hasLayout&&(i.zoom=1),d.test(i.filter)?(e=e>=.9999?"":"alpha(opacity="+100*e+")",i.filter=i.filter.replace(d,e)):i.filter+=" alpha(opacity="+100*e+")",t}),S.util.setStyle=function(t,e){var i=t.style;if(!i)return t;if("string"==typeof e)return t.style.cssText+=";"+e,e.indexOf("opacity")>-1?v(t,e.match(/opacity:\s*(\d?\.?\d*)/)[1]):t;for(var n in e)"opacity"===n?v(t,e[n]):i["float"===n||"cssFloat"===n?void 0===i.styleFloat?"cssFloat":"styleFloat":n]=e[n];return t},function(){var t=Array.prototype.slice;var e,i,n,r,o=function(e){return t.call(e,0)};try{e=o(S.document.childNodes)instanceof Array}catch(t){}function a(t,e){var i=S.document.createElement(t);for(var n in e)"class"===n?i.className=e[n]:"for"===n?i.htmlFor=e[n]:i.setAttribute(n,e[n]);return i}function s(t){for(var e=0,i=0,n=S.document.documentElement,r=S.document.body||{scrollLeft:0,scrollTop:0};t&&(t.parentNode||t.host)&&((t=t.parentNode||t.host)===S.document?(e=r.scrollLeft||n.scrollLeft||0,i=r.scrollTop||n.scrollTop||0):(e+=t.scrollLeft||0,i+=t.scrollTop||0),1!==t.nodeType||"fixed"!==t.style.position););return{left:e,top:i}}e||(o=function(t){for(var e=new Array(t.length),i=t.length;i--;)e[i]=t[i];return e}),i=S.document.defaultView&&S.document.defaultView.getComputedStyle?function(t,e){var i=S.document.defaultView.getComputedStyle(t,null);return i?i[e]:void 0}:function(t,e){var i=t.style[e];return!i&&t.currentStyle&&(i=t.currentStyle[e]),i},n=S.document.documentElement.style,r="userSelect"in n?"userSelect":"MozUserSelect"in n?"MozUserSelect":"WebkitUserSelect"in n?"WebkitUserSelect":"KhtmlUserSelect"in n?"KhtmlUserSelect":"",S.util.makeElementUnselectable=function(t){return void 0!==t.onselectstart&&(t.onselectstart=S.util.falseFunction),r?t.style[r]="none":"string"==typeof t.unselectable&&(t.unselectable="on"),t},S.util.makeElementSelectable=function(t){return void 0!==t.onselectstart&&(t.onselectstart=null),r?t.style[r]="":"string"==typeof t.unselectable&&(t.unselectable=""),t},S.util.setImageSmoothing=function(t,e){t.imageSmoothingEnabled=t.imageSmoothingEnabled||t.webkitImageSmoothingEnabled||t.mozImageSmoothingEnabled||t.msImageSmoothingEnabled||t.oImageSmoothingEnabled,t.imageSmoothingEnabled=e},S.util.getById=function(t){return"string"==typeof t?S.document.getElementById(t):t},S.util.toArray=o,S.util.addClass=function(t,e){t&&-1===(" "+t.className+" ").indexOf(" "+e+" ")&&(t.className+=(t.className?" ":"")+e)},S.util.makeElement=a,S.util.wrapElement=function(t,e,i){return"string"==typeof e&&(e=a(e,i)),t.parentNode&&t.parentNode.replaceChild(e,t),e.appendChild(t),e},S.util.getScrollLeftTop=s,S.util.getElementOffset=function(t){var e,n,r=t&&t.ownerDocument,o={left:0,top:0},a={left:0,top:0},l={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!r)return a;for(var c in l)a[l[c]]+=parseInt(i(t,c),10)||0;return e=r.documentElement,void 0!==t.getBoundingClientRect&&(o=t.getBoundingClientRect()),n=s(t),{left:o.left+n.left-(e.clientLeft||0)+a.left,top:o.top+n.top-(e.clientTop||0)+a.top}},S.util.getNodeCanvas=function(t){var e=S.jsdomImplForWrapper(t);return e._canvas||e._image},S.util.cleanUpJsdomNode=function(t){if(S.isLikelyNode){var e=S.jsdomImplForWrapper(t);e&&(e._image=null,e._canvas=null,e._currentSrc=null,e._attributes=null,e._classList=null)}}}(),function(){function t(){}S.util.request=function(e,i){i||(i={});var n=i.method?i.method.toUpperCase():"GET",r=i.onComplete||function(){},o=new S.window.XMLHttpRequest,a=i.body||i.parameters;return o.onreadystatechange=function(){4===o.readyState&&(r(o),o.onreadystatechange=t)},"GET"===n&&(a=null,"string"==typeof i.parameters&&(e=function(t,e){return t+(/\?/.test(t)?"&":"?")+e}(e,i.parameters))),o.open(n,e,!0),"POST"!==n&&"PUT"!==n||o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.send(a),o}}(),S.log=console.log,S.warn=console.warn,function(){function t(){return!1}function e(t,e,i,n){return-i*Math.cos(t/n*(Math.PI/2))+i+e}var i=S.window.requestAnimationFrame||S.window.webkitRequestAnimationFrame||S.window.mozRequestAnimationFrame||S.window.oRequestAnimationFrame||S.window.msRequestAnimationFrame||function(t){return S.window.setTimeout(t,1e3/60)},n=S.window.cancelAnimationFrame||S.window.clearTimeout;function r(){return i.apply(S.window,arguments)}S.util.animate=function(i){var n=!1;return r((function(o){i||(i={});var a,s=o||+new Date,l=i.duration||500,c=s+l,u=i.onChange||t,h=i.abort||t,f=i.onComplete||t,g=i.easing||e,d="startValue"in i?i.startValue:0,v="endValue"in i?i.endValue:100,p=i.byValue||v-d;i.onStart&&i.onStart(),function t(e){var i=(a=e||+new Date)>c?l:a-s,o=i/l,m=g(i,d,p,l),y=Math.abs((m-d)/p);if(!n){if(!h(m,y,o))return a>c?(u(v,1,1),void f(v,1,1)):(u(m,y,o),void r(t));f(v,1,1)}}(s)})),function(){n=!0}},S.util.requestAnimFrame=r,S.util.cancelAnimFrame=function(){return n.apply(S.window,arguments)}}(),function(){function t(t,e,i){var n="rgba("+parseInt(t[0]+i*(e[0]-t[0]),10)+","+parseInt(t[1]+i*(e[1]-t[1]),10)+","+parseInt(t[2]+i*(e[2]-t[2]),10);return n+=","+(t&&e?parseFloat(t[3]+i*(e[3]-t[3])):1),n+=")"}S.util.animateColor=function(e,i,n,r){var o=new S.Color(e).getSource(),a=new S.Color(i).getSource(),s=r.onComplete,l=r.onChange;return r=r||{},S.util.animate(S.util.object.extend(r,{duration:n||500,startValue:o,endValue:a,byValue:a,easing:function(e,i,n,o){return t(i,n,r.colorEasing?r.colorEasing(e,o):1-Math.cos(e/o*(Math.PI/2)))},onComplete:function(e,i,n){if(s)return s(t(a,a,0),i,n)},onChange:function(e,i,n){if(l){if(Array.isArray(e))return l(t(e,e,0),i,n);l(e,i,n)}}}))}}(),function(){function t(t,e,i,n){return t-1&&u>-1&&u-1)&&(i="stroke")}else{if("href"===t||"xlink:href"===t||"font"===t)return i;if("imageSmoothing"===t)return"optimizeQuality"===i;s=l?i.map(o):o(i,r)}}else i="";return!l&&isNaN(s)?i:s}function g(t){return new RegExp("^("+t.join("|")+")\\b","i")}function d(t,e){var i,n,r,o,a=[];for(r=0,o=e.length;r1;)l.shift(),c=e.util.multiplyTransformMatrices(c,l[0]);return c}}();var y=new RegExp("^\\s*("+e.reNum+"+)\\s*,?\\s*("+e.reNum+"+)\\s*,?\\s*("+e.reNum+"+)\\s*,?\\s*("+e.reNum+"+)\\s*$");function b(t){if(!e.svgViewBoxElementsRegEx.test(t.nodeName))return{};var i,n,r,a,s,l,c=t.getAttribute("viewBox"),u=1,h=1,f=t.getAttribute("width"),g=t.getAttribute("height"),d=t.getAttribute("x")||0,v=t.getAttribute("y")||0,p=t.getAttribute("preserveAspectRatio")||"",m=!c||!(c=c.match(y)),b=!f||!g||"100%"===f||"100%"===g,x=m&&b,C={},I="",_=0,M=0;if(C.width=0,C.height=0,C.toBeParsed=x,m&&(d||v)&&t.parentNode&&"#document"!==t.parentNode.nodeName&&(I=" translate("+o(d)+" "+o(v)+") ",s=(t.getAttribute("transform")||"")+I,t.setAttribute("transform",s),t.removeAttribute("x"),t.removeAttribute("y")),x)return C;if(m)return C.width=o(f),C.height=o(g),C;if(i=-parseFloat(c[1]),n=-parseFloat(c[2]),r=parseFloat(c[3]),a=parseFloat(c[4]),C.minX=i,C.minY=n,C.viewBoxWidth=r,C.viewBoxHeight=a,b?(C.width=r,C.height=a):(C.width=o(f),C.height=o(g),u=C.width/r,h=C.height/a),"none"!==(p=e.util.parsePreserveAspectRatioAttribute(p)).alignX&&("meet"===p.meetOrSlice&&(h=u=u>h?h:u),"slice"===p.meetOrSlice&&(h=u=u>h?u:h),_=C.width-r*u,M=C.height-a*u,"Mid"===p.alignX&&(_/=2),"Mid"===p.alignY&&(M/=2),"Min"===p.alignX&&(_=0),"Min"===p.alignY&&(M=0)),1===u&&1===h&&0===i&&0===n&&0===d&&0===v)return C;if((d||v)&&"#document"!==t.parentNode.nodeName&&(I=" translate("+o(d)+" "+o(v)+") "),s=I+" matrix("+u+" 0 0 "+h+" "+(i*u+_)+" "+(n*h+M)+") ","svg"===t.nodeName){for(l=t.ownerDocument.createElementNS(e.svgNS,"g");t.firstChild;)l.appendChild(t.firstChild);t.appendChild(l)}else(l=t).removeAttribute("x"),l.removeAttribute("y"),s=l.getAttribute("transform")+s;return l.setAttribute("transform",s),C}function x(t,e){var i="xlink:href",n=m(t,e.getAttribute(i).substr(1));if(n&&n.getAttribute(i)&&x(t,n),["gradientTransform","x1","x2","y1","y2","gradientUnits","cx","cy","r","fx","fy"].forEach((function(t){n&&!e.hasAttribute(t)&&n.hasAttribute(t)&&e.setAttribute(t,n.getAttribute(t))})),!e.children.length)for(var r=n.cloneNode(!0);r.firstChild;)e.appendChild(r.firstChild);e.removeAttribute(i)}e.parseSVGDocument=function(t,i,r,o){if(t){!function(t){for(var i=d(t,["use","svg:use"]),n=0;i.length&&nt.x&&this.y>t.y},gte:function(t){return this.x>=t.x&&this.y>=t.y},lerp:function(t,e){return void 0===e&&(e=.5),e=Math.max(Math.min(1,e),0),new i(this.x+(t.x-this.x)*e,this.y+(t.y-this.y)*e)},distanceFrom:function(t){var e=this.x-t.x,i=this.y-t.y;return Math.sqrt(e*e+i*i)},midPointFrom:function(t){return this.lerp(t)},min:function(t){return new i(Math.min(this.x,t.x),Math.min(this.y,t.y))},max:function(t){return new i(Math.max(this.x,t.x),Math.max(this.y,t.y))},toString:function(){return this.x+","+this.y},setXY:function(t,e){return this.x=t,this.y=e,this},setX:function(t){return this.x=t,this},setY:function(t){return this.y=t,this},setFromPoint:function(t){return this.x=t.x,this.y=t.y,this},swap:function(t){var e=this.x,i=this.y;this.x=t.x,this.y=t.y,t.x=e,t.y=i},clone:function(){return new i(this.x,this.y)}})}(e),function(t){"use strict";var e=t.fabric||(t.fabric={});function i(t){this.status=t,this.points=[]}e.Intersection?e.warn("fabric.Intersection is already defined"):(e.Intersection=i,e.Intersection.prototype={constructor:i,appendPoint:function(t){return this.points.push(t),this},appendPoints:function(t){return this.points=this.points.concat(t),this}},e.Intersection.intersectLineLine=function(t,n,r,o){var a,s=(o.x-r.x)*(t.y-r.y)-(o.y-r.y)*(t.x-r.x),l=(n.x-t.x)*(t.y-r.y)-(n.y-t.y)*(t.x-r.x),c=(o.y-r.y)*(n.x-t.x)-(o.x-r.x)*(n.y-t.y);if(0!==c){var u=s/c,h=l/c;0<=u&&u<=1&&0<=h&&h<=1?(a=new i("Intersection")).appendPoint(new e.Point(t.x+u*(n.x-t.x),t.y+u*(n.y-t.y))):a=new i}else a=new i(0===s||0===l?"Coincident":"Parallel");return a},e.Intersection.intersectLinePolygon=function(t,e,n){var r,o,a,s,l=new i,c=n.length;for(s=0;s0&&(l.status="Intersection"),l},e.Intersection.intersectPolygonPolygon=function(t,e){var n,r=new i,o=t.length;for(n=0;n0&&(r.status="Intersection"),r},e.Intersection.intersectPolygonRectangle=function(t,n,r){var o=n.min(r),a=n.max(r),s=new e.Point(a.x,o.y),l=new e.Point(o.x,a.y),c=i.intersectLinePolygon(o,s,t),u=i.intersectLinePolygon(s,a,t),h=i.intersectLinePolygon(a,l,t),f=i.intersectLinePolygon(l,o,t),g=new i;return g.appendPoints(c.points),g.appendPoints(u.points),g.appendPoints(h.points),g.appendPoints(f.points),g.points.length>0&&(g.status="Intersection"),g})}(e),function(t){"use strict";var e=t.fabric||(t.fabric={});function i(t){t?this._tryParsingColor(t):this.setSource([0,0,0,1])}function n(t,e,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+(e-t)*(2/3-i)*6:t}e.Color?e.warn("fabric.Color is already defined."):(e.Color=i,e.Color.prototype={_tryParsingColor:function(t){var e;t in i.colorNameMap&&(t=i.colorNameMap[t]),"transparent"===t&&(e=[255,255,255,0]),e||(e=i.sourceFromHex(t)),e||(e=i.sourceFromRgb(t)),e||(e=i.sourceFromHsl(t)),e||(e=[0,0,0,1]),e&&this.setSource(e)},_rgbToHsl:function(t,i,n){t/=255,i/=255,n/=255;var r,o,a,s=e.util.array.max([t,i,n]),l=e.util.array.min([t,i,n]);if(a=(s+l)/2,s===l)r=o=0;else{var c=s-l;switch(o=a>.5?c/(2-s-l):c/(s+l),s){case t:r=(i-n)/c+(i0)-(t<0)||+t};function g(t,e){var i=t.angle+h(Math.atan2(e.y,e.x))+360;return Math.round(i%360/45)}function d(t,i){var n=i.transform.target,r=n.canvas,o=e.util.object.clone(i);o.target=n,r&&r.fire("object:"+t,o),n.fire(t,i)}function v(t,e){var i=e.canvas,n=t[i.uniScaleKey];return i.uniformScaling&&!n||!i.uniformScaling&&n}function p(t){return t.originX===c&&t.originY===c}function m(t,e,i){var n=t.lockScalingX,r=t.lockScalingY;return!(!n||!r)||(!(e||!n&&!r||!i)||(!(!n||"x"!==e)||!(!r||"y"!==e)))}function y(t,e,i,n){return{e:t,transform:e,pointer:{x:i,y:n}}}function b(t){return function(e,i,n,r){var o=i.target,a=o.getCenterPoint(),s=o.translateToOriginPoint(a,i.originX,i.originY),l=t(e,i,n,r);return o.setPositionByOrigin(s,i.originX,i.originY),l}}function x(t,e){return function(i,n,r,o){var a=e(i,n,r,o);return a&&d(t,y(i,n,r,o)),a}}function C(t,i,n,r,o){var a=t.target,s=a.controls[t.corner],l=a.canvas.getZoom(),c=a.padding/l,u=a.toLocalPoint(new e.Point(r,o),i,n);return u.x>=c&&(u.x-=c),u.x<=-c&&(u.x+=c),u.y>=c&&(u.y-=c),u.y<=c&&(u.y+=c),u.x-=s.offsetX,u.y-=s.offsetY,u}function I(t){return t.flipX!==t.flipY}function _(t,e,i,n,r){if(0!==t[e]){var o=r/t._getTransformedDimensions()[n]*t[i];t.set(i,o)}}function M(t,e,i,n){var r,c=e.target,u=c._getTransformedDimensions(0,c.skewY),f=C(e,e.originX,e.originY,i,n),g=Math.abs(2*f.x)-u.x,d=c.skewX;g<2?r=0:(r=h(Math.atan2(g/c.scaleX,u.y/c.scaleY)),e.originX===o&&e.originY===l&&(r=-r),e.originX===s&&e.originY===a&&(r=-r),I(c)&&(r=-r));var v=d!==r;if(v){var p=c._getTransformedDimensions().y;c.set("skewX",r),_(c,"skewY","scaleY","y",p)}return v}function S(t,e,i,n){var r,c=e.target,u=c._getTransformedDimensions(c.skewX,0),f=C(e,e.originX,e.originY,i,n),g=Math.abs(2*f.y)-u.y,d=c.skewY;g<2?r=0:(r=h(Math.atan2(g/c.scaleY,u.x/c.scaleX)),e.originX===o&&e.originY===l&&(r=-r),e.originX===s&&e.originY===a&&(r=-r),I(c)&&(r=-r));var v=d!==r;if(v){var p=c._getTransformedDimensions().x;c.set("skewY",r),_(c,"skewX","scaleX","x",p)}return v}function w(t,e,i,n,r){r=r||{};var o,a,s,l,c,h,g=e.target,d=g.lockScalingX,y=g.lockScalingY,b=r.by,x=v(t,g),I=m(g,b,x),_=e.gestureScale;if(I)return!1;if(_)a=e.scaleX*_,s=e.scaleY*_;else{if(o=C(e,e.originX,e.originY,i,n),c="y"!==b?f(o.x):1,h="x"!==b?f(o.y):1,e.signX||(e.signX=c),e.signY||(e.signY=h),g.lockScalingFlip&&(e.signX!==c||e.signY!==h))return!1;if(l=g._getTransformedDimensions(),x&&!b){var M=Math.abs(o.x)+Math.abs(o.y),S=e.original,w=M/(Math.abs(l.x*S.scaleX/g.scaleX)+Math.abs(l.y*S.scaleY/g.scaleY));a=S.scaleX*w,s=S.scaleY*w}else a=Math.abs(o.x*g.scaleX/l.x),s=Math.abs(o.y*g.scaleY/l.y);p(e)&&(a*=2,s*=2),e.signX!==c&&"y"!==b&&(e.originX=u[e.originX],a*=-1,e.signX=c),e.signY!==h&&"x"!==b&&(e.originY=u[e.originY],s*=-1,e.signY=h)}var k=g.scaleX,T=g.scaleY;return b?("x"===b&&g.set("scaleX",a),"y"===b&&g.set("scaleY",s)):(!d&&g.set("scaleX",a),!y&&g.set("scaleY",s)),k!==g.scaleX||T!==g.scaleY}r.scaleCursorStyleHandler=function(t,e,n){var r=v(t,n),o="";if(0!==e.x&&0===e.y?o="x":0===e.x&&0!==e.y&&(o="y"),m(n,o,r))return"not-allowed";var a=g(n,e);return i[a]+"-resize"},r.skewCursorStyleHandler=function(t,e,i){var r="not-allowed";if(0!==e.x&&i.lockSkewingY)return r;if(0!==e.y&&i.lockSkewingX)return r;var o=g(i,e)%4;return n[o]+"-resize"},r.scaleSkewCursorStyleHandler=function(t,e,i){return t[i.canvas.altActionKey]?r.skewCursorStyleHandler(t,e,i):r.scaleCursorStyleHandler(t,e,i)},r.rotationWithSnapping=x("rotating",b((function(t,e,i,n){var r=e,o=r.target,a=o.translateToOriginPoint(o.getCenterPoint(),r.originX,r.originY);if(o.lockRotation)return!1;var s,l=Math.atan2(r.ey-a.y,r.ex-a.x),c=Math.atan2(n-a.y,i-a.x),u=h(c-l+r.theta);if(o.snapAngle>0){var f=o.snapAngle,g=o.snapThreshold||f,d=Math.ceil(u/f)*f,v=Math.floor(u/f)*f;Math.abs(u-v)0?o:s:(u>0&&(r=h===a?o:s),u<0&&(r=h===a?s:o),I(l)&&(r=r===o?s:o)),e.originX=r,x("skewing",b(M))(t,e,i,n))},r.skewHandlerY=function(t,e,i,n){var r,s=e.target,u=s.skewY,h=e.originX;return!s.lockSkewingY&&(0===u?r=C(e,c,c,i,n).y>0?a:l:(u>0&&(r=h===o?a:l),u<0&&(r=h===o?l:a),I(s)&&(r=r===a?l:a)),e.originY=r,x("skewing",b(S))(t,e,i,n))},r.dragHandler=function(t,e,i,n){var r=e.target,o=i-e.offsetX,a=n-e.offsetY,s=!r.get("lockMovementX")&&r.left!==o,l=!r.get("lockMovementY")&&r.top!==a;return s&&r.set("left",o),l&&r.set("top",a),(s||l)&&d("moving",y(t,e,i,n)),s||l},r.scaleOrSkewActionName=function(t,e,i){var n=t[i.canvas.altActionKey];return 0===e.x?n?"skewX":"scaleY":0===e.y?n?"skewY":"scaleX":void 0},r.rotationStyleHandler=function(t,e,i){return i.lockRotation?"not-allowed":e.cursorStyle},r.fireEvent=d,r.wrapWithFixedAnchor=b,r.wrapWithFireEvent=x,r.getLocalPoint=C,e.controlsUtils=r}(e),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.degreesToRadians,n=e.controlsUtils;n.renderCircleControl=function(t,e,i,n,r){n=n||{};var o,a=this.sizeX||n.cornerSize||r.cornerSize,s=this.sizeY||n.cornerSize||r.cornerSize,l=void 0!==n.transparentCorners?n.transparentCorners:r.transparentCorners,c=l?"stroke":"fill",u=!l&&(n.cornerStrokeColor||r.cornerStrokeColor),h=e,f=i;t.save(),t.fillStyle=n.cornerColor||r.cornerColor,t.strokeStyle=n.cornerStrokeColor||r.cornerStrokeColor,a>s?(o=a,t.scale(1,s/a),f=i*a/s):s>a?(o=s,t.scale(a/s,1),h=e*s/a):o=a,t.lineWidth=1,t.beginPath(),t.arc(h,f,o/2,0,2*Math.PI,!1),t[c](),u&&t.stroke(),t.restore()},n.renderSquareControl=function(t,e,n,r,o){r=r||{};var a=this.sizeX||r.cornerSize||o.cornerSize,s=this.sizeY||r.cornerSize||o.cornerSize,l=void 0!==r.transparentCorners?r.transparentCorners:o.transparentCorners,c=l?"stroke":"fill",u=!l&&(r.cornerStrokeColor||o.cornerStrokeColor),h=a/2,f=s/2;t.save(),t.fillStyle=r.cornerColor||o.cornerColor,t.strokeStyle=r.cornerStrokeColor||o.cornerStrokeColor,t.lineWidth=1,t.translate(e,n),t.rotate(i(o.angle)),t[c+"Rect"](-h,-f,a,s),u&&t.strokeRect(-h,-f,a,s),t.restore()}}(e),function(t){"use strict";var e=t.fabric||(t.fabric={});e.Control=function(t){for(var e in t)this[e]=t[e]},e.Control.prototype={visible:!0,actionName:"scale",angle:0,x:0,y:0,offsetX:0,offsetY:0,sizeX:null,sizeY:null,touchSizeX:null,touchSizeY:null,cursorStyle:"crosshair",withConnection:!1,actionHandler:function(){},mouseDownHandler:function(){},mouseUpHandler:function(){},getActionHandler:function(){return this.actionHandler},getMouseDownHandler:function(){return this.mouseDownHandler},getMouseUpHandler:function(){return this.mouseUpHandler},cursorStyleHandler:function(t,e){return e.cursorStyle},getActionName:function(t,e){return e.actionName},getVisibility:function(t,e){var i=t._controlsVisibility;return i&&void 0!==i[e]?i[e]:this.visible},setVisibility:function(t){this.visible=t},positionHandler:function(t,i){return e.util.transformPoint({x:this.x*t.x+this.offsetX,y:this.y*t.y+this.offsetY},i)},calcCornerCoords:function(t,i,n,r,o){var a,s,l,c,u=o?this.touchSizeX:this.sizeX,h=o?this.touchSizeY:this.sizeY;if(u&&h&&u!==h){var f=Math.atan2(h,u),g=Math.sqrt(u*u+h*h)/2,d=f-e.util.degreesToRadians(t),v=Math.PI/2-f-e.util.degreesToRadians(t);a=g*e.util.cos(d),s=g*e.util.sin(d),l=g*e.util.cos(v),c=g*e.util.sin(v)}else{g=.7071067812*(u&&h?u:i);d=e.util.degreesToRadians(45-t);a=l=g*e.util.cos(d),s=c=g*e.util.sin(d)}return{tl:{x:n-c,y:r-l},tr:{x:n+a,y:r-s},bl:{x:n-a,y:r+s},br:{x:n+c,y:r+l}}},render:function(t,i,n,r,o){if("circle"===((r=r||{}).cornerStyle||o.cornerStyle))e.controlsUtils.renderCircleControl.call(this,t,i,n,r,o);else e.controlsUtils.renderSquareControl.call(this,t,i,n,r,o)}}}(e),function(){function t(t,e){var i,n,r,o,a=t.getAttribute("style"),s=t.getAttribute("offset")||0;if(s=(s=parseFloat(s)/(/%$/.test(s)?100:1))<0?0:s>1?1:s,a){var l=a.split(/\s*;\s*/);for(""===l[l.length-1]&&l.pop(),o=l.length;o--;){var c=l[o].split(/\s*:\s*/),u=c[0].trim(),h=c[1].trim();"stop-color"===u?i=h:"stop-opacity"===u&&(r=h)}}return i||(i=t.getAttribute("stop-color")||"rgb(0,0,0)"),r||(r=t.getAttribute("stop-opacity")),n=(i=new S.Color(i)).getAlpha(),r=isNaN(parseFloat(r))?1:parseFloat(r),r*=n*e,{offset:s,color:i.toRgb(),opacity:r}}var e=S.util.object.clone;S.Gradient=S.util.createClass({offsetX:0,offsetY:0,gradientTransform:null,gradientUnits:"pixels",type:"linear",initialize:function(t){t||(t={}),t.coords||(t.coords={});var e,i=this;Object.keys(t).forEach((function(e){i[e]=t[e]})),this.id?this.id+="_"+S.Object.__uid++:this.id=S.Object.__uid++,e={x1:t.coords.x1||0,y1:t.coords.y1||0,x2:t.coords.x2||0,y2:t.coords.y2||0},"radial"===this.type&&(e.r1=t.coords.r1||0,e.r2=t.coords.r2||0),this.coords=e,this.colorStops=t.colorStops.slice()},addColorStop:function(t){for(var e in t){var i=new S.Color(t[e]);this.colorStops.push({offset:parseFloat(e),color:i.toRgb(),opacity:i.getAlpha()})}return this},toObject:function(t){var e={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};return S.util.populateWithProperties(this,e,t),e},toSVG:function(t,i){var n,r,o,a,s=e(this.coords,!0),l=(i=i||{},e(this.colorStops,!0)),c=s.r1>s.r2,u=this.gradientTransform?this.gradientTransform.concat():S.iMatrix.concat(),h=-this.offsetX,f=-this.offsetY,g=!!i.additionalTransform,d="pixels"===this.gradientUnits?"userSpaceOnUse":"objectBoundingBox";if(l.sort((function(t,e){return t.offset-e.offset})),"objectBoundingBox"===d?(h/=t.width,f/=t.height):(h+=t.width/2,f+=t.height/2),"path"===t.type&&"percentage"!==this.gradientUnits&&(h-=t.pathOffset.x,f-=t.pathOffset.y),u[4]-=h,u[5]-=f,a='id="SVGID_'+this.id+'" gradientUnits="'+d+'"',a+=' gradientTransform="'+(g?i.additionalTransform+" ":"")+S.util.matrixToSVG(u)+'" ',"linear"===this.type?o=["\n']:"radial"===this.type&&(o=["\n']),"radial"===this.type){if(c)for((l=l.concat()).reverse(),n=0,r=l.length;n0){var p=v/Math.max(s.r1,s.r2);for(n=0,r=l.length;n\n')}return o.push("linear"===this.type?"\n":"\n"),o.join("")},toLive:function(t){var e,i,n,r=S.util.object.clone(this.coords);if(this.type){for("linear"===this.type?e=t.createLinearGradient(r.x1,r.y1,r.x2,r.y2):"radial"===this.type&&(e=t.createRadialGradient(r.x1,r.y1,r.r1,r.x2,r.y2,r.r2)),i=0,n=this.colorStops.length;i1?1:o,isNaN(o)&&(o=1);var a,s,l,c,u=e.getElementsByTagName("stop"),h="userSpaceOnUse"===e.getAttribute("gradientUnits")?"pixels":"percentage",f=e.getAttribute("gradientTransform")||"",g=[],d=0,v=0;for("linearGradient"===e.nodeName||"LINEARGRADIENT"===e.nodeName?(a="linear",s=function(t){return{x1:t.getAttribute("x1")||0,y1:t.getAttribute("y1")||0,x2:t.getAttribute("x2")||"100%",y2:t.getAttribute("y2")||0}}(e)):(a="radial",s=function(t){return{x1:t.getAttribute("fx")||t.getAttribute("cx")||"50%",y1:t.getAttribute("fy")||t.getAttribute("cy")||"50%",r1:0,x2:t.getAttribute("cx")||"50%",y2:t.getAttribute("cy")||"50%",r2:t.getAttribute("r")||"50%"}}(e)),l=u.length;l--;)g.push(t(u[l],o));return c=S.parseTransformAttribute(f),function(t,e,i,n){var r,o;Object.keys(e).forEach((function(t){"Infinity"===(r=e[t])?o=1:"-Infinity"===r?o=0:(o=parseFloat(e[t],10),"string"==typeof r&&/^(\d+\.\d+)%|(\d+)%$/.test(r)&&(o*=.01,"pixels"===n&&("x1"!==t&&"x2"!==t&&"r2"!==t||(o*=i.viewBoxWidth||i.width),"y1"!==t&&"y2"!==t||(o*=i.viewBoxHeight||i.height)))),e[t]=o}))}(0,s,r,h),"pixels"===h&&(d=-i.left,v=-i.top),new S.Gradient({id:e.getAttribute("id"),type:a,coords:s,colorStops:g,gradientUnits:h,gradientTransform:c,offsetX:d,offsetY:v})}})}(),function(){"use strict";var t=S.util.toFixed;S.Pattern=S.util.createClass({repeat:"repeat",offsetX:0,offsetY:0,crossOrigin:"",patternTransform:null,initialize:function(t,e){if(t||(t={}),this.id=S.Object.__uid++,this.setOptions(t),!t.source||t.source&&"string"!=typeof t.source)e&&e(this);else{var i=this;this.source=S.util.createImage(),S.util.loadImage(t.source,(function(t,n){i.source=t,e&&e(i,n)}),null,this.crossOrigin)}},toObject:function(e){var i,n,r=S.Object.NUM_FRACTION_DIGITS;return"string"==typeof this.source.src?i=this.source.src:"object"==typeof this.source&&this.source.toDataURL&&(i=this.source.toDataURL()),n={type:"pattern",source:i,repeat:this.repeat,crossOrigin:this.crossOrigin,offsetX:t(this.offsetX,r),offsetY:t(this.offsetY,r),patternTransform:this.patternTransform?this.patternTransform.concat():null},S.util.populateWithProperties(this,n,e),n},toSVG:function(t){var e="function"==typeof this.source?this.source():this.source,i=e.width/t.width,n=e.height/t.height,r=this.offsetX/t.width,o=this.offsetY/t.height,a="";return"repeat-x"!==this.repeat&&"no-repeat"!==this.repeat||(n=1,o&&(n+=Math.abs(o))),"repeat-y"!==this.repeat&&"no-repeat"!==this.repeat||(i=1,r&&(i+=Math.abs(r))),e.src?a=e.src:e.toDataURL&&(a=e.toDataURL()),'\n\n\n'},setOptions:function(t){for(var e in t)this[e]=t[e]},toLive:function(t){var e=this.source;if(!e)return"";if(void 0!==e.src){if(!e.complete)return"";if(0===e.naturalWidth||0===e.naturalHeight)return""}return t.createPattern(e,this.repeat)}})}(),function(t){"use strict";var e=t.fabric||(t.fabric={}),i=e.util.toFixed;e.Shadow?e.warn("fabric.Shadow is already defined."):(e.Shadow=e.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,nonScaling:!1,initialize:function(t){for(var i in"string"==typeof t&&(t=this._parseShadow(t)),t)this[i]=t[i];this.id=e.Object.__uid++},_parseShadow:function(t){var i=t.trim(),n=e.Shadow.reOffsetsAndBlur.exec(i)||[];return{color:(i.replace(e.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)").trim(),offsetX:parseFloat(n[1],10)||0,offsetY:parseFloat(n[2],10)||0,blur:parseFloat(n[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(t){var n=40,r=40,o=e.Object.NUM_FRACTION_DIGITS,a=e.util.rotateVector({x:this.offsetX,y:this.offsetY},e.util.degreesToRadians(-t.angle)),s=new e.Color(this.color);return t.width&&t.height&&(n=100*i((Math.abs(a.x)+this.blur)/t.width,o)+20,r=100*i((Math.abs(a.y)+this.blur)/t.height,o)+20),t.flipX&&(a.x*=-1),t.flipY&&(a.y*=-1),'\n\t\n\t\n\t\n\t\n\t\n\t\t\n\t\t\n\t\n\n'},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 t={},i=e.Shadow.prototype;return["color","blur","offsetX","offsetY","affectStroke","nonScaling"].forEach((function(e){this[e]!==i[e]&&(t[e]=this[e])}),this),t}}),e.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/)}(e),function(){"use strict";if(S.StaticCanvas)S.warn("fabric.StaticCanvas is already defined.");else{var t=S.util.object.extend,e=S.util.getElementOffset,i=S.util.removeFromArray,n=S.util.toFixed,r=S.util.transformPoint,o=S.util.invertTransform,a=S.util.getNodeCanvas,s=S.util.createCanvasElement,l=new Error("Could not initialize `canvas` element");S.StaticCanvas=S.util.createClass(S.CommonMethods,{initialize:function(t,e){e||(e={}),this.renderAndResetBound=this.renderAndReset.bind(this),this.requestRenderAllBound=this.requestRenderAll.bind(this),this._initStatic(t,e)},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!1,renderOnAddRemove:!0,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:S.iMatrix.concat(),backgroundVpt:!0,overlayVpt:!0,enableRetinaScaling:!0,vptCoords:{},skipOffscreen:!0,clipPath:void 0,_initStatic:function(t,e){var i=this.requestRenderAllBound;this._objects=[],this._createLowerCanvas(t),this._initOptions(e),this.interactive||this._initRetinaScaling(),e.overlayImage&&this.setOverlayImage(e.overlayImage,i),e.backgroundImage&&this.setBackgroundImage(e.backgroundImage,i),e.backgroundColor&&this.setBackgroundColor(e.backgroundColor,i),e.overlayColor&&this.setOverlayColor(e.overlayColor,i),this.calcOffset()},_isRetinaScaling:function(){return 1!==S.devicePixelRatio&&this.enableRetinaScaling},getRetinaScaling:function(){return this._isRetinaScaling()?S.devicePixelRatio:1},_initRetinaScaling:function(){if(this._isRetinaScaling()){var t=S.devicePixelRatio;this.__initRetinaScaling(t,this.lowerCanvasEl,this.contextContainer),this.upperCanvasEl&&this.__initRetinaScaling(t,this.upperCanvasEl,this.contextTop)}},__initRetinaScaling:function(t,e,i){e.setAttribute("width",this.width*t),e.setAttribute("height",this.height*t),i.scale(t,t)},calcOffset:function(){return this._offset=e(this.lowerCanvasEl),this},setOverlayImage:function(t,e,i){return this.__setBgOverlayImage("overlayImage",t,e,i)},setBackgroundImage:function(t,e,i){return this.__setBgOverlayImage("backgroundImage",t,e,i)},setOverlayColor:function(t,e){return this.__setBgOverlayColor("overlayColor",t,e)},setBackgroundColor:function(t,e){return this.__setBgOverlayColor("backgroundColor",t,e)},__setBgOverlayImage:function(t,e,i,n){return"string"==typeof e?S.util.loadImage(e,(function(e,r){if(e){var o=new S.Image(e,n);this[t]=o,o.canvas=this}i&&i(e,r)}),this,n&&n.crossOrigin):(n&&e.setOptions(n),this[t]=e,e&&(e.canvas=this),i&&i(e,!1)),this},__setBgOverlayColor:function(t,e,i){return this[t]=e,this._initGradient(e,t),this._initPattern(e,t,i),this},_createCanvasElement:function(){var t=s();if(!t)throw l;if(t.style||(t.style={}),void 0===t.getContext)throw l;return t},_initOptions:function(t){var e=this.lowerCanvasEl;this._setOptions(t),this.width=this.width||parseInt(e.width,10)||0,this.height=this.height||parseInt(e.height,10)||0,this.lowerCanvasEl.style&&(e.width=this.width,e.height=this.height,e.style.width=this.width+"px",e.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(t){t&&t.getContext?this.lowerCanvasEl=t:this.lowerCanvasEl=S.util.getById(t)||this._createCanvasElement(),S.util.addClass(this.lowerCanvasEl,"lower-canvas"),this._originalCanvasStyle=this.lowerCanvasEl.style,this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(t,e){return this.setDimensions({width:t},e)},setHeight:function(t,e){return this.setDimensions({height:t},e)},setDimensions:function(t,e){var i;for(var n in e=e||{},t)i=t[n],e.cssOnly||(this._setBackstoreDimension(n,t[n]),i+="px",this.hasLostContext=!0),e.backstoreOnly||this._setCssDimension(n,i);return this._isCurrentlyDrawing&&this.freeDrawingBrush&&this.freeDrawingBrush._setBrushStyles(),this._initRetinaScaling(),this.calcOffset(),e.cssOnly||this.requestRenderAll(),this},_setBackstoreDimension:function(t,e){return this.lowerCanvasEl[t]=e,this.upperCanvasEl&&(this.upperCanvasEl[t]=e),this.cacheCanvasEl&&(this.cacheCanvasEl[t]=e),this[t]=e,this},_setCssDimension:function(t,e){return this.lowerCanvasEl.style[t]=e,this.upperCanvasEl&&(this.upperCanvasEl.style[t]=e),this.wrapperEl&&(this.wrapperEl.style[t]=e),this},getZoom:function(){return this.viewportTransform[0]},setViewportTransform:function(t){var e,i,n,r=this._activeObject,o=this.backgroundImage,a=this.overlayImage;for(this.viewportTransform=t,i=0,n=this._objects.length;i\n'),this._setSVGBgOverlayColor(i,"background"),this._setSVGBgOverlayImage(i,"backgroundImage",e),this._setSVGObjects(i,e),this.clipPath&&i.push("\n"),this._setSVGBgOverlayColor(i,"overlay"),this._setSVGBgOverlayImage(i,"overlayImage",e),i.push(""),i.join("")},_setSVGPreamble:function(t,e){e.suppressPreamble||t.push('\n','\n')},_setSVGHeader:function(t,e){var i,r=e.width||this.width,o=e.height||this.height,a='viewBox="0 0 '+this.width+" "+this.height+'" ',s=S.Object.NUM_FRACTION_DIGITS;e.viewBox?a='viewBox="'+e.viewBox.x+" "+e.viewBox.y+" "+e.viewBox.width+" "+e.viewBox.height+'" ':this.svgViewportTransformation&&(i=this.viewportTransform,a='viewBox="'+n(-i[4]/i[0],s)+" "+n(-i[5]/i[3],s)+" "+n(this.width/i[0],s)+" "+n(this.height/i[3],s)+'" '),t.push("