aboutsummaryrefslogtreecommitdiff
path: root/old
diff options
context:
space:
mode:
authorBobby <[email protected]>2022-06-23 15:53:05 +0530
committerBobby <[email protected]>2022-06-23 15:53:05 +0530
commitffad9f37ad10860f4f170248d3037c4cadfc9317 (patch)
treeb42b03b3f1591996c71dc6c3268c539c22179ae8 /old
parent95c80c4081b692e1c600b424f8ceeab190226273 (diff)
downloadTShirtDesigner-ffad9f37ad10860f4f170248d3037c4cadfc9317.tar.xz
TShirtDesigner-ffad9f37ad10860f4f170248d3037c4cadfc9317.zip
update old link
Diffstat (limited to 'old')
-rw-r--r--old/css/bootstrap-responsive.css1058
-rw-r--r--old/css/bootstrap-responsive.min.css9
-rw-r--r--old/css/bootstrap.min.css854
-rw-r--r--old/css/jquery.miniColors.css125
-rw-r--r--old/css/jquery.simplecolorpicker.css95
-rw-r--r--old/demo.gifbin0 -> 2652487 bytes
-rw-r--r--old/img/Actions-format-text-bold-icon.pngbin0 -> 384 bytes
-rw-r--r--old/img/Actions-format-text-italic-icon.pngbin0 -> 351 bytes
-rw-r--r--old/img/Actions-format-text-strikethrough-icon.pngbin0 -> 393 bytes
-rw-r--r--old/img/Actions-format-text-underline-icon.pngbin0 -> 446 bytes
-rw-r--r--old/img/colors.pngbin0 -> 12973 bytes
-rw-r--r--old/img/crew_back.pngbin0 -> 245584 bytes
-rw-r--r--old/img/crew_front.pngbin0 -> 195349 bytes
-rw-r--r--old/img/delete.pngbin0 -> 1203 bytes
-rw-r--r--old/img/drag.pngbin0 -> 25830 bytes
-rw-r--r--old/img/font_bold.pngbin0 -> 235 bytes
-rw-r--r--old/img/font_italic.pngbin0 -> 276 bytes
-rw-r--r--old/img/font_strikethrough.pngbin0 -> 248 bytes
-rw-r--r--old/img/font_underline.pngbin0 -> 238 bytes
-rw-r--r--old/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--old/img/glyphicons-halflings.pngbin0 -> 12799 bytes
-rw-r--r--old/img/invisibleman.jpgbin0 -> 2827 bytes
-rw-r--r--old/img/mens_hoodie_back.pngbin0 -> 310377 bytes
-rw-r--r--old/img/mens_hoodie_front.pngbin0 -> 305684 bytes
-rw-r--r--old/img/mens_longsleeve_back.pngbin0 -> 347046 bytes
-rw-r--r--old/img/mens_longsleeve_front.pngbin0 -> 313138 bytes
-rw-r--r--old/img/mens_tank_back.pngbin0 -> 209060 bytes
-rw-r--r--old/img/mens_tank_front.pngbin0 -> 99658 bytes
-rw-r--r--old/img/phones/GalaxyS3.pngbin0 -> 75833 bytes
-rw-r--r--old/img/phones/GalaxyS3A.pngbin0 -> 90785 bytes
-rw-r--r--old/img/phones/GalaxyS3Base.pngbin0 -> 108172 bytes
-rw-r--r--old/img/phones/GalaxyS3Mask.pngbin0 -> 9474 bytes
-rw-r--r--old/img/phones/designs/1.jpgbin0 -> 110003 bytes
-rw-r--r--old/img/phones/designs/2.jpgbin0 -> 74369 bytes
-rw-r--r--old/img/phones/designs/3.jpgbin0 -> 153796 bytes
-rw-r--r--old/img/phones/designs/4.jpgbin0 -> 178689 bytes
-rw-r--r--old/img/phones/designs/5.jpgbin0 -> 139460 bytes
-rw-r--r--old/img/phones/designs/6.jpgbin0 -> 106741 bytes
-rw-r--r--old/img/phones/designs/7.jpgbin0 -> 157946 bytes
-rw-r--r--old/img/phones/designs/8.jpgbin0 -> 117747 bytes
-rw-r--r--old/img/phones/iPhone3A.pngbin0 -> 67714 bytes
-rw-r--r--old/img/phones/iPhone3GHCOverlay.pngbin0 -> 232052 bytes
-rw-r--r--old/img/phones/iPhone4.pngbin0 -> 25391 bytes
-rw-r--r--old/img/phones/iPhone4A.pngbin0 -> 67089 bytes
-rw-r--r--old/img/phones/iPhone4HCOverlay.pngbin0 -> 134629 bytes
-rw-r--r--old/img/phones/iPhone5A.pngbin0 -> 303978 bytes
-rw-r--r--old/img/phones/iphone4Base.pngbin0 -> 45919 bytes
-rw-r--r--old/img/phones/iphone4Mask.pngbin0 -> 5879 bytes
-rw-r--r--old/img/phones/iphone5.pngbin0 -> 12970 bytes
-rw-r--r--old/img/phones/iphone5Base.pngbin0 -> 104854 bytes
-rw-r--r--old/img/phones/iphone5Mask.pngbin0 -> 7183 bytes
-rw-r--r--old/img/rotate.pngbin0 -> 1157 bytes
-rw-r--r--old/img/sample.jpgbin0 -> 21010 bytes
-rw-r--r--old/img/scale.pngbin0 -> 1113 bytes
-rw-r--r--old/img/trigger.pngbin0 -> 706 bytes
-rw-r--r--old/img/womens_crew_back.pngbin0 -> 233558 bytes
-rw-r--r--old/img/womens_crew_front.pngbin0 -> 227604 bytes
-rw-r--r--old/index.html434
-rw-r--r--old/js/bootstrap.js2027
-rw-r--r--old/js/bootstrap.min.js6
-rw-r--r--old/js/caseEditor.js344
-rw-r--r--old/js/excanvas.js924
-rw-r--r--old/js/fabric.js13962
-rw-r--r--old/js/jquery.miniColors.min.js9
-rw-r--r--old/js/tshirtEditor.js355
65 files changed, 20202 insertions, 0 deletions
diff --git a/old/css/bootstrap-responsive.css b/old/css/bootstrap-responsive.css
new file mode 100644
index 0000000..9259d26
--- /dev/null
+++ b/old/css/bootstrap-responsive.css
@@ -0,0 +1,1058 @@
+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+.visible-phone {
+ display: none !important;
+}
+
+.visible-tablet {
+ display: none !important;
+}
+
+.hidden-desktop {
+ display: none !important;
+}
+
+.visible-desktop {
+ display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important ;
+ }
+ .visible-tablet {
+ display: inherit !important;
+ }
+ .hidden-tablet {
+ display: none !important;
+ }
+}
+
+@media (max-width: 767px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important;
+ }
+ .visible-phone {
+ display: inherit !important;
+ }
+ .hidden-phone {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row {
+ margin-left: -30px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 30px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 1170px;
+ }
+ .span12 {
+ width: 1170px;
+ }
+ .span11 {
+ width: 1070px;
+ }
+ .span10 {
+ width: 970px;
+ }
+ .span9 {
+ width: 870px;
+ }
+ .span8 {
+ width: 770px;
+ }
+ .span7 {
+ width: 670px;
+ }
+ .span6 {
+ width: 570px;
+ }
+ .span5 {
+ width: 470px;
+ }
+ .span4 {
+ width: 370px;
+ }
+ .span3 {
+ width: 270px;
+ }
+ .span2 {
+ width: 170px;
+ }
+ .span1 {
+ width: 70px;
+ }
+ .offset12 {
+ margin-left: 1230px;
+ }
+ .offset11 {
+ margin-left: 1130px;
+ }
+ .offset10 {
+ margin-left: 1030px;
+ }
+ .offset9 {
+ margin-left: 930px;
+ }
+ .offset8 {
+ margin-left: 830px;
+ }
+ .offset7 {
+ margin-left: 730px;
+ }
+ .offset6 {
+ margin-left: 630px;
+ }
+ .offset5 {
+ margin-left: 530px;
+ }
+ .offset4 {
+ margin-left: 430px;
+ }
+ .offset3 {
+ margin-left: 330px;
+ }
+ .offset2 {
+ margin-left: 230px;
+ }
+ .offset1 {
+ margin-left: 130px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.564102564102564%;
+ *margin-left: 2.5109110747408616%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.45299145299145%;
+ *width: 91.39979996362975%;
+ }
+ .row-fluid .span10 {
+ width: 82.90598290598291%;
+ *width: 82.8527914166212%;
+ }
+ .row-fluid .span9 {
+ width: 74.35897435897436%;
+ *width: 74.30578286961266%;
+ }
+ .row-fluid .span8 {
+ width: 65.81196581196582%;
+ *width: 65.75877432260411%;
+ }
+ .row-fluid .span7 {
+ width: 57.26495726495726%;
+ *width: 57.21176577559556%;
+ }
+ .row-fluid .span6 {
+ width: 48.717948717948715%;
+ *width: 48.664757228587014%;
+ }
+ .row-fluid .span5 {
+ width: 40.17094017094017%;
+ *width: 40.11774868157847%;
+ }
+ .row-fluid .span4 {
+ width: 31.623931623931625%;
+ *width: 31.570740134569924%;
+ }
+ .row-fluid .span3 {
+ width: 23.076923076923077%;
+ *width: 23.023731587561375%;
+ }
+ .row-fluid .span2 {
+ width: 14.52991452991453%;
+ *width: 14.476723040552828%;
+ }
+ .row-fluid .span1 {
+ width: 5.982905982905983%;
+ *width: 5.929714493544281%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.12820512820512%;
+ *margin-left: 105.02182214948171%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.56410256410257%;
+ *margin-left: 102.45771958537915%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.58119658119658%;
+ *margin-left: 96.47481360247316%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.01709401709402%;
+ *margin-left: 93.91071103837061%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.03418803418803%;
+ *margin-left: 87.92780505546462%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.47008547008548%;
+ *margin-left: 85.36370249136206%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.48717948717949%;
+ *margin-left: 79.38079650845607%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 76.92307692307693%;
+ *margin-left: 76.81669394435352%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 70.94017094017094%;
+ *margin-left: 70.83378796144753%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.37606837606839%;
+ *margin-left: 68.26968539734497%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.393162393162385%;
+ *margin-left: 62.28677941443899%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.82905982905982%;
+ *margin-left: 59.72267685033642%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 53.84615384615384%;
+ *margin-left: 53.739770867430444%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.28205128205128%;
+ *margin-left: 51.175668303327875%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.299145299145295%;
+ *margin-left: 45.1927623204219%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.73504273504273%;
+ *margin-left: 42.62865975631933%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 36.75213675213675%;
+ *margin-left: 36.645753773413354%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.18803418803419%;
+ *margin-left: 34.081651209310785%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.205128205128204%;
+ *margin-left: 28.0987452264048%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.641025641025642%;
+ *margin-left: 25.53464266230224%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.65811965811966%;
+ *margin-left: 19.551736679396257%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.094017094017094%;
+ *margin-left: 16.98763411529369%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.11111111111111%;
+ *margin-left: 11.004728132387708%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.547008547008547%;
+ *margin-left: 8.440625568285142%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 30px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 1156px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 1056px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 956px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 856px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 756px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 656px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 556px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 456px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 356px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 256px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 156px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 56px;
+ }
+ .thumbnails {
+ margin-left: -30px;
+ }
+ .thumbnails > li {
+ margin-left: 30px;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .row {
+ margin-left: -20px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 724px;
+ }
+ .span12 {
+ width: 724px;
+ }
+ .span11 {
+ width: 662px;
+ }
+ .span10 {
+ width: 600px;
+ }
+ .span9 {
+ width: 538px;
+ }
+ .span8 {
+ width: 476px;
+ }
+ .span7 {
+ width: 414px;
+ }
+ .span6 {
+ width: 352px;
+ }
+ .span5 {
+ width: 290px;
+ }
+ .span4 {
+ width: 228px;
+ }
+ .span3 {
+ width: 166px;
+ }
+ .span2 {
+ width: 104px;
+ }
+ .span1 {
+ width: 42px;
+ }
+ .offset12 {
+ margin-left: 764px;
+ }
+ .offset11 {
+ margin-left: 702px;
+ }
+ .offset10 {
+ margin-left: 640px;
+ }
+ .offset9 {
+ margin-left: 578px;
+ }
+ .offset8 {
+ margin-left: 516px;
+ }
+ .offset7 {
+ margin-left: 454px;
+ }
+ .offset6 {
+ margin-left: 392px;
+ }
+ .offset5 {
+ margin-left: 330px;
+ }
+ .offset4 {
+ margin-left: 268px;
+ }
+ .offset3 {
+ margin-left: 206px;
+ }
+ .offset2 {
+ margin-left: 144px;
+ }
+ .offset1 {
+ margin-left: 82px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.7624309392265194%;
+ *margin-left: 2.709239449864817%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.43646408839778%;
+ *width: 91.38327259903608%;
+ }
+ .row-fluid .span10 {
+ width: 82.87292817679558%;
+ *width: 82.81973668743387%;
+ }
+ .row-fluid .span9 {
+ width: 74.30939226519337%;
+ *width: 74.25620077583166%;
+ }
+ .row-fluid .span8 {
+ width: 65.74585635359117%;
+ *width: 65.69266486422946%;
+ }
+ .row-fluid .span7 {
+ width: 57.18232044198895%;
+ *width: 57.12912895262725%;
+ }
+ .row-fluid .span6 {
+ width: 48.61878453038674%;
+ *width: 48.56559304102504%;
+ }
+ .row-fluid .span5 {
+ width: 40.05524861878453%;
+ *width: 40.00205712942283%;
+ }
+ .row-fluid .span4 {
+ width: 31.491712707182323%;
+ *width: 31.43852121782062%;
+ }
+ .row-fluid .span3 {
+ width: 22.92817679558011%;
+ *width: 22.87498530621841%;
+ }
+ .row-fluid .span2 {
+ width: 14.3646408839779%;
+ *width: 14.311449394616199%;
+ }
+ .row-fluid .span1 {
+ width: 5.801104972375691%;
+ *width: 5.747913483013988%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.52486187845304%;
+ *margin-left: 105.41847889972962%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.76243093922652%;
+ *margin-left: 102.6560479605031%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.96132596685082%;
+ *margin-left: 96.8549429881274%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.1988950276243%;
+ *margin-left: 94.09251204890089%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.39779005524862%;
+ *margin-left: 88.2914070765252%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.6353591160221%;
+ *margin-left: 85.52897613729868%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.8342541436464%;
+ *margin-left: 79.72787116492299%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 77.07182320441989%;
+ *margin-left: 76.96544022569647%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 71.2707182320442%;
+ *margin-left: 71.16433525332079%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.50828729281768%;
+ *margin-left: 68.40190431409427%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.70718232044199%;
+ *margin-left: 62.600799341718584%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.94475138121547%;
+ *margin-left: 59.838368402492065%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 54.14364640883978%;
+ *margin-left: 54.037263430116376%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.38121546961326%;
+ *margin-left: 51.27483249088986%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.58011049723757%;
+ *margin-left: 45.47372751851417%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.81767955801105%;
+ *margin-left: 42.71129657928765%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 37.01657458563536%;
+ *margin-left: 36.91019160691196%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.25414364640884%;
+ *margin-left: 34.14776066768544%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.45303867403315%;
+ *margin-left: 28.346655695309746%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.69060773480663%;
+ *margin-left: 25.584224756083227%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.88950276243094%;
+ *margin-left: 19.783119783707537%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.12707182320442%;
+ *margin-left: 17.02068884448102%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.32596685082873%;
+ *margin-left: 11.219583872105325%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.56353591160221%;
+ *margin-left: 8.457152932878806%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 710px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 648px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 586px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 524px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 462px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 400px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 338px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 276px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 214px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 152px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 90px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 28px;
+ }
+}
+
+@media (max-width: 767px) {
+ body {
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .container-fluid {
+ padding: 0;
+ }
+ .dl-horizontal dt {
+ float: none;
+ width: auto;
+ clear: none;
+ text-align: left;
+ }
+ .dl-horizontal dd {
+ margin-left: 0;
+ }
+ .container {
+ width: auto;
+ }
+ .row-fluid {
+ width: 100%;
+ }
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0;
+ }
+ [class*="span"],
+ .row-fluid [class*="span"] {
+ display: block;
+ float: none;
+ width: 100%;
+ margin-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block;
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+ .modal {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ left: 20px;
+ width: auto;
+ margin: 0;
+ }
+ .modal.fade.in {
+ top: auto;
+ }
+}
+
+@media (max-width: 480px) {
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0);
+ }
+ .page-header h1 small {
+ display: block;
+ line-height: 20px;
+ }
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+ .form-horizontal .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ .form-horizontal .controls {
+ margin-left: 0;
+ }
+ .form-horizontal .control-list {
+ padding-top: 0;
+ }
+ .form-horizontal .form-actions {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+ .modal {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+ .carousel-caption {
+ position: static;
+ }
+}
+
+@media (max-width: 979px) {
+ body {
+ padding-top: 0;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: 20px;
+ }
+ .navbar-fixed-bottom {
+ margin-top: 20px;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ .navbar .brand {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin: 0 0 0 -5px;
+ }
+ .nav-collapse {
+ clear: both;
+ }
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 10px;
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: #777777;
+ text-shadow: none;
+ }
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: #777777;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ }
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: #f2f2f2;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: #111111;
+ }
+ .nav-collapse.in .btn-group {
+ padding: 0;
+ margin-top: 5px;
+ }
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ display: block;
+ float: none;
+ max-width: none;
+ padding: 0;
+ margin: 0 15px;
+ background-color: transparent;
+ border: none;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .nav > li > .dropdown-menu:before,
+ .nav-collapse .nav > li > .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: 10px 15px;
+ margin: 10px 0;
+ border-top: 1px solid #f2f2f2;
+ border-bottom: 1px solid #f2f2f2;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ }
+ .navbar-inverse .nav-collapse .navbar-form,
+ .navbar-inverse .nav-collapse .navbar-search {
+ border-top-color: #111111;
+ border-bottom-color: #111111;
+ }
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ .nav-collapse,
+ .nav-collapse.collapse {
+ height: 0;
+ overflow: hidden;
+ }
+ .navbar .btn-navbar {
+ display: block;
+ }
+ .navbar-static .navbar-inner {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+}
+
+@media (min-width: 980px) {
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+}
diff --git a/old/css/bootstrap-responsive.min.css b/old/css/bootstrap-responsive.min.css
new file mode 100644
index 0000000..7b0158d
--- /dev/null
+++ b/old/css/bootstrap-responsive.min.css
@@ -0,0 +1,9 @@
+/*!
+ * Bootstrap Responsive v2.1.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
diff --git a/old/css/bootstrap.min.css b/old/css/bootstrap.min.css
new file mode 100644
index 0000000..be1c0ca
--- /dev/null
+++ b/old/css/bootstrap.min.css
@@ -0,0 +1,854 @@
+@import url(https://fonts.googleapis.com/css?family=Telex);
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+a:hover,a:active{outline:0;}
+sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}
+#map_canvas img{max-width:none;}
+button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
+button,input{*overflow:visible;line-height:normal;}
+button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}
+input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;}
+.clearfix:after{clear:both;}
+.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}
+.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#555555;background-color:#ffffff;}
+a{color:#2fa4e7;text-decoration:none;}
+a:hover{color:#157ab5;text-decoration:underline;}
+.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);}
+.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;}
+.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;}
+.row:after{clear:both;}
+[class*="span"]{float:left;min-height:1px;margin-left:20px;}
+.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.span12{width:940px;}
+.span11{width:860px;}
+.span10{width:780px;}
+.span9{width:700px;}
+.span8{width:620px;}
+.span7{width:540px;}
+.span6{width:460px;}
+.span5{width:380px;}
+.span4{width:300px;}
+.span3{width:220px;}
+.span2{width:140px;}
+.span1{width:60px;}
+.offset12{margin-left:980px;}
+.offset11{margin-left:900px;}
+.offset10{margin-left:820px;}
+.offset9{margin-left:740px;}
+.offset8{margin-left:660px;}
+.offset7{margin-left:580px;}
+.offset6{margin-left:500px;}
+.offset5{margin-left:420px;}
+.offset4{margin-left:340px;}
+.offset3{margin-left:260px;}
+.offset2{margin-left:180px;}
+.offset1{margin-left:100px;}
+.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;}
+.row-fluid:after{clear:both;}
+.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;}
+.row-fluid [class*="span"]:first-child{margin-left:0;}
+.row-fluid .span12{width:100%;*width:99.94680851063829%;}
+.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;}
+.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;}
+.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;}
+.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;}
+.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;}
+.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;}
+.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;}
+.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;}
+.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;}
+.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;}
+.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;}
+.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;}
+.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;}
+.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;}
+.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;}
+.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;}
+.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;}
+.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;}
+.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;}
+.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;}
+.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;}
+.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;}
+.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;}
+.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;}
+.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;}
+.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;}
+.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;}
+.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;}
+.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;}
+.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;}
+.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;}
+.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;}
+.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;}
+.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;}
+.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;}
+[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;}
+[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;}
+.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;}
+.container:after{clear:both;}
+.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;}
+.container-fluid:after{clear:both;}
+p{margin:0 0 10px;}
+.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}
+small{font-size:85%;}
+strong{font-weight:bold;}
+em{font-style:italic;}
+cite{font-style:normal;}
+.muted{color:#999999;}
+.text-warning{color:#126b9e;}
+.text-error{color:#bd4247;}
+.text-info{color:#817b58;}
+.text-success{color:#669533;}
+h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:'Telex',sans-serif;font-weight:bold;line-height:1;color:#317eac;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;}
+h1{font-size:36px;line-height:40px;}
+h2{font-size:30px;line-height:40px;}
+h3{font-size:24px;line-height:40px;}
+h4{font-size:18px;line-height:20px;}
+h5{font-size:14px;line-height:20px;}
+h6{font-size:12px;line-height:20px;}
+h1 small{font-size:24px;}
+h2 small{font-size:18px;}
+h3 small{font-size:14px;}
+h4 small{font-size:14px;}
+.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #f5f5f5;}
+ul,ol{padding:0;margin:0 0 10px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+li{line-height:20px;}
+ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
+dl{margin-bottom:20px;}
+dt,dd{line-height:20px;}
+dt{font-weight:bold;}
+dd{margin-left:10px;}
+.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;}
+.dl-horizontal:after{clear:both;}
+.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
+.dl-horizontal dd{margin-left:180px;}
+hr{margin:20px 0;border:0;border-top:1px solid #f5f5f5;border-bottom:1px solid #ffffff;}
+abbr[title]{cursor:help;border-bottom:1px dotted #999999;}
+abbr.initialism{font-size:90%;text-transform:uppercase;}
+blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #f5f5f5;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px;}
+blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
+blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #f5f5f5;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
+blockquote.pull-right small:before{content:'';}
+blockquote.pull-right small:after{content:'\00A0 \2014';}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;}
+code,pre{padding:0 3px 2px;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
+pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;}
+pre code{padding:0;color:inherit;background-color:transparent;border:0;}
+.pre-scrollable{max-height:340px;overflow-y:scroll;}
+form{margin:0 0 20px;}
+fieldset{padding:0;margin:0;border:0;}
+legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;}
+label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;}
+input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
+label{display:block;margin-bottom:5px;}
+select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:9px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+input,textarea,.uneditable-input{width:206px;}
+textarea{height:auto;}
+textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);}
+input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;cursor:pointer;}
+input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}
+select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;}
+select{width:220px;border:1px solid #cccccc;background-color:#ffffff;}
+select[multiple],select[size]{height:auto;}
+select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+.uneditable-input{overflow:hidden;white-space:nowrap;}
+.uneditable-textarea{width:auto;height:auto;}
+input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;}
+input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;}
+input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;}
+.radio,.checkbox{min-height:18px;padding-left:18px;}
+.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
+.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
+.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
+.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
+.input-mini{width:60px;}
+.input-small{width:90px;}
+.input-medium{width:150px;}
+.input-large{width:210px;}
+.input-xlarge{width:270px;}
+.input-xxlarge{width:530px;}
+input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;}
+.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;}
+input,textarea,.uneditable-input{margin-left:0;}
+.controls-row [class*="span"]+[class*="span"]{margin-left:20px;}
+input.span12, textarea.span12, .uneditable-input.span12{width:926px;}
+input.span11, textarea.span11, .uneditable-input.span11{width:846px;}
+input.span10, textarea.span10, .uneditable-input.span10{width:766px;}
+input.span9, textarea.span9, .uneditable-input.span9{width:686px;}
+input.span8, textarea.span8, .uneditable-input.span8{width:606px;}
+input.span7, textarea.span7, .uneditable-input.span7{width:526px;}
+input.span6, textarea.span6, .uneditable-input.span6{width:446px;}
+input.span5, textarea.span5, .uneditable-input.span5{width:366px;}
+input.span4, textarea.span4, .uneditable-input.span4{width:286px;}
+input.span3, textarea.span3, .uneditable-input.span3{width:206px;}
+input.span2, textarea.span2, .uneditable-input.span2{width:126px;}
+input.span1, textarea.span1, .uneditable-input.span1{width:46px;}
+.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;}
+.controls-row:after{clear:both;}
+.controls-row [class*="span"]{float:left;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#f5f5f5;}
+input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;}
+.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#126b9e;}
+.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#126b9e;}
+.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#126b9e;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#0d4c70;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #2fa4e7;}
+.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#126b9e;background-color:#8accf2;border-color:#126b9e;}
+.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#bd4247;}
+.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#bd4247;}
+.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#bd4247;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#983538;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d88e90;}
+.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#bd4247;background-color:#eddbe3;border-color:#bd4247;}
+.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#669533;}
+.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#669533;}
+.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#669533;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#4c6f26;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #99ca63;}
+.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#669533;background-color:#ccddbb;border-color:#669533;}
+.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#817b58;}
+.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#817b58;}
+.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#817b58;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#625e43;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #b1ad8d;}
+.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#817b58;background-color:#a7dff1;border-color:#817b58;}
+input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;}
+.form-actions:after{clear:both;}
+.help-block,.help-inline{color:#7b7b7b;}
+.help-block{display:block;margin-bottom:10px;}
+.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
+.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;font-size:14px;vertical-align:top;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;}
+.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#f5f5f5;border:1px solid #ccc;}
+.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.input-append .active,.input-prepend .active{background-color:#bede9c;border-color:#73a839;}
+.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
+.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on,.input-append .btn{margin-left:-1px;}
+.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;}
+.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
+.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;}
+.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
+.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
+.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;}
+.control-group{margin-bottom:10px;}
+legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;}
+.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;}
+.form-horizontal .control-group:after{clear:both;}
+.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;}
+.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;}
+.form-horizontal .help-block{margin-bottom:0;}
+.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px;}
+.form-horizontal .form-actions{padding-left:180px;}
+table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
+.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
+.table th{font-weight:bold;}
+.table thead th{vertical-align:bottom;}
+.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
+.table tbody+tbody{border-top:2px solid #dddddd;}
+.table-condensed th,.table-condensed td{padding:4px 5px;}
+.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
+.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
+.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;}
+.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px;}
+.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;}
+.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;}
+.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px;}
+.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topleft:4px;}
+.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5;}
+table [class*=span],.row-fluid table [class*=span]{display:table-cell;float:none;margin-left:0;}
+.table .span1{float:none;width:44px;margin-left:0;}
+.table .span2{float:none;width:124px;margin-left:0;}
+.table .span3{float:none;width:204px;margin-left:0;}
+.table .span4{float:none;width:284px;margin-left:0;}
+.table .span5{float:none;width:364px;margin-left:0;}
+.table .span6{float:none;width:444px;margin-left:0;}
+.table .span7{float:none;width:524px;margin-left:0;}
+.table .span8{float:none;width:604px;margin-left:0;}
+.table .span9{float:none;width:684px;margin-left:0;}
+.table .span10{float:none;width:764px;margin-left:0;}
+.table .span11{float:none;width:844px;margin-left:0;}
+.table .span12{float:none;width:924px;margin-left:0;}
+.table .span13{float:none;width:1004px;margin-left:0;}
+.table .span14{float:none;width:1084px;margin-left:0;}
+.table .span15{float:none;width:1164px;margin-left:0;}
+.table .span16{float:none;width:1244px;margin-left:0;}
+.table .span17{float:none;width:1324px;margin-left:0;}
+.table .span18{float:none;width:1404px;margin-left:0;}
+.table .span19{float:none;width:1484px;margin-left:0;}
+.table .span20{float:none;width:1564px;margin-left:0;}
+.table .span21{float:none;width:1644px;margin-left:0;}
+.table .span22{float:none;width:1724px;margin-left:0;}
+.table .span23{float:none;width:1804px;margin-left:0;}
+.table .span24{float:none;width:1884px;margin-left:0;}
+.table tbody tr.success td{background-color:#ccddbb;}
+.table tbody tr.error td{background-color:#eddbe3;}
+.table tbody tr.warning td{background-color:#8accf2;}
+.table tbody tr.info td{background-color:#a7dff1;}
+.table-hover tbody tr.success:hover td{background-color:#bfd4aa;}
+.table-hover tbody tr.error:hover td{background-color:#e4cad6;}
+.table-hover tbody tr.warning:hover td{background-color:#74c2ef;}
+.table-hover tbody tr.info:hover td{background-color:#91d7ee;}
+[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;}
+.icon-white,.nav-tabs>.active>a>[class^="icon-"],.nav-tabs>.active>a>[class*=" icon-"],.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png");}
+.icon-glass{background-position:0 0;}
+.icon-music{background-position:-24px 0;}
+.icon-search{background-position:-48px 0;}
+.icon-envelope{background-position:-72px 0;}
+.icon-heart{background-position:-96px 0;}
+.icon-star{background-position:-120px 0;}
+.icon-star-empty{background-position:-144px 0;}
+.icon-user{background-position:-168px 0;}
+.icon-film{background-position:-192px 0;}
+.icon-th-large{background-position:-216px 0;}
+.icon-th{background-position:-240px 0;}
+.icon-th-list{background-position:-264px 0;}
+.icon-ok{background-position:-288px 0;}
+.icon-remove{background-position:-312px 0;}
+.icon-zoom-in{background-position:-336px 0;}
+.icon-zoom-out{background-position:-360px 0;}
+.icon-off{background-position:-384px 0;}
+.icon-signal{background-position:-408px 0;}
+.icon-cog{background-position:-432px 0;}
+.icon-trash{background-position:-456px 0;}
+.icon-home{background-position:0 -24px;}
+.icon-file{background-position:-24px -24px;}
+.icon-time{background-position:-48px -24px;}
+.icon-road{background-position:-72px -24px;}
+.icon-download-alt{background-position:-96px -24px;}
+.icon-download{background-position:-120px -24px;}
+.icon-upload{background-position:-144px -24px;}
+.icon-inbox{background-position:-168px -24px;}
+.icon-play-circle{background-position:-192px -24px;}
+.icon-repeat{background-position:-216px -24px;}
+.icon-refresh{background-position:-240px -24px;}
+.icon-list-alt{background-position:-264px -24px;}
+.icon-lock{background-position:-287px -24px;}
+.icon-flag{background-position:-312px -24px;}
+.icon-headphones{background-position:-336px -24px;}
+.icon-volume-off{background-position:-360px -24px;}
+.icon-volume-down{background-position:-384px -24px;}
+.icon-volume-up{background-position:-408px -24px;}
+.icon-qrcode{background-position:-432px -24px;}
+.icon-barcode{background-position:-456px -24px;}
+.icon-tag{background-position:0 -48px;}
+.icon-tags{background-position:-25px -48px;}
+.icon-book{background-position:-48px -48px;}
+.icon-bookmark{background-position:-72px -48px;}
+.icon-print{background-position:-96px -48px;}
+.icon-camera{background-position:-120px -48px;}
+.icon-font{background-position:-144px -48px;}
+.icon-bold{background-position:-167px -48px;}
+.icon-italic{background-position:-192px -48px;}
+.icon-text-height{background-position:-216px -48px;}
+.icon-text-width{background-position:-240px -48px;}
+.icon-align-left{background-position:-264px -48px;}
+.icon-align-center{background-position:-288px -48px;}
+.icon-align-right{background-position:-312px -48px;}
+.icon-align-justify{background-position:-336px -48px;}
+.icon-list{background-position:-360px -48px;}
+.icon-indent-left{background-position:-384px -48px;}
+.icon-indent-right{background-position:-408px -48px;}
+.icon-facetime-video{background-position:-432px -48px;}
+.icon-picture{background-position:-456px -48px;}
+.icon-pencil{background-position:0 -72px;}
+.icon-map-marker{background-position:-24px -72px;}
+.icon-adjust{background-position:-48px -72px;}
+.icon-tint{background-position:-72px -72px;}
+.icon-edit{background-position:-96px -72px;}
+.icon-share{background-position:-120px -72px;}
+.icon-check{background-position:-144px -72px;}
+.icon-move{background-position:-168px -72px;}
+.icon-step-backward{background-position:-192px -72px;}
+.icon-fast-backward{background-position:-216px -72px;}
+.icon-backward{background-position:-240px -72px;}
+.icon-play{background-position:-264px -72px;}
+.icon-pause{background-position:-288px -72px;}
+.icon-stop{background-position:-312px -72px;}
+.icon-forward{background-position:-336px -72px;}
+.icon-fast-forward{background-position:-360px -72px;}
+.icon-step-forward{background-position:-384px -72px;}
+.icon-eject{background-position:-408px -72px;}
+.icon-chevron-left{background-position:-432px -72px;}
+.icon-chevron-right{background-position:-456px -72px;}
+.icon-plus-sign{background-position:0 -96px;}
+.icon-minus-sign{background-position:-24px -96px;}
+.icon-remove-sign{background-position:-48px -96px;}
+.icon-ok-sign{background-position:-72px -96px;}
+.icon-question-sign{background-position:-96px -96px;}
+.icon-info-sign{background-position:-120px -96px;}
+.icon-screenshot{background-position:-144px -96px;}
+.icon-remove-circle{background-position:-168px -96px;}
+.icon-ok-circle{background-position:-192px -96px;}
+.icon-ban-circle{background-position:-216px -96px;}
+.icon-arrow-left{background-position:-240px -96px;}
+.icon-arrow-right{background-position:-264px -96px;}
+.icon-arrow-up{background-position:-289px -96px;}
+.icon-arrow-down{background-position:-312px -96px;}
+.icon-share-alt{background-position:-336px -96px;}
+.icon-resize-full{background-position:-360px -96px;}
+.icon-resize-small{background-position:-384px -96px;}
+.icon-plus{background-position:-408px -96px;}
+.icon-minus{background-position:-433px -96px;}
+.icon-asterisk{background-position:-456px -96px;}
+.icon-exclamation-sign{background-position:0 -120px;}
+.icon-gift{background-position:-24px -120px;}
+.icon-leaf{background-position:-48px -120px;}
+.icon-fire{background-position:-72px -120px;}
+.icon-eye-open{background-position:-96px -120px;}
+.icon-eye-close{background-position:-120px -120px;}
+.icon-warning-sign{background-position:-144px -120px;}
+.icon-plane{background-position:-168px -120px;}
+.icon-calendar{background-position:-192px -120px;}
+.icon-random{background-position:-216px -120px;width:16px;}
+.icon-comment{background-position:-240px -120px;}
+.icon-magnet{background-position:-264px -120px;}
+.icon-chevron-up{background-position:-288px -120px;}
+.icon-chevron-down{background-position:-313px -119px;}
+.icon-retweet{background-position:-336px -120px;}
+.icon-shopping-cart{background-position:-360px -120px;}
+.icon-folder-close{background-position:-384px -120px;}
+.icon-folder-open{background-position:-408px -120px;width:16px;}
+.icon-resize-vertical{background-position:-432px -119px;}
+.icon-resize-horizontal{background-position:-456px -118px;}
+.icon-hdd{background-position:0 -144px;}
+.icon-bullhorn{background-position:-24px -144px;}
+.icon-bell{background-position:-48px -144px;}
+.icon-certificate{background-position:-72px -144px;}
+.icon-thumbs-up{background-position:-96px -144px;}
+.icon-thumbs-down{background-position:-120px -144px;}
+.icon-hand-right{background-position:-144px -144px;}
+.icon-hand-left{background-position:-168px -144px;}
+.icon-hand-up{background-position:-192px -144px;}
+.icon-hand-down{background-position:-216px -144px;}
+.icon-circle-arrow-right{background-position:-240px -144px;}
+.icon-circle-arrow-left{background-position:-264px -144px;}
+.icon-circle-arrow-up{background-position:-288px -144px;}
+.icon-circle-arrow-down{background-position:-312px -144px;}
+.icon-globe{background-position:-336px -144px;}
+.icon-wrench{background-position:-360px -144px;}
+.icon-tasks{background-position:-384px -144px;}
+.icon-filter{background-position:-408px -144px;}
+.icon-briefcase{background-position:-432px -144px;}
+.icon-fullscreen{background-position:-456px -144px;}
+.dropup,.dropdown{position:relative;}
+.dropdown-toggle{*margin-bottom:-3px;}
+.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
+.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";}
+.dropdown .caret{margin-top:8px;margin-left:2px;}
+.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;}
+.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.dropdown-menu a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;}
+.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{text-decoration:none;color:#ffffff;background-color:#2fa4e7;background-color:#27a0e5;background-image:-moz-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2));background-image:-webkit-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-o-linear-gradient(top, #2fa4e7, #1a99e2);background-image:linear-gradient(to bottom, #2fa4e7, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0);}
+.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;outline:0;background-color:#2fa4e7;background-color:#27a0e5;background-image:-moz-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#1a99e2));background-image:-webkit-linear-gradient(top, #2fa4e7, #1a99e2);background-image:-o-linear-gradient(top, #2fa4e7, #1a99e2);background-image:linear-gradient(to bottom, #2fa4e7, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff1a99e2', GradientType=0);}
+.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999999;}
+.dropdown-menu .disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;}
+.open{*z-index:1000;}.open >.dropdown-menu{display:block;}
+.pull-right>.dropdown-menu{right:0;left:auto;}
+.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";}
+.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
+.dropdown-submenu{position:relative;}
+.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.dropdown-submenu:hover>.dropdown-menu{display:block;}
+.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
+.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
+.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;}
+.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;}
+.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;}
+.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);}
+button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;}
+.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 14px;margin-bottom:0;font-size:14px;line-height:20px;*line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
+.btn:active,.btn.active{background-color:#cccccc \9;}
+.btn:first-child{*margin-left:0;}
+.btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
+.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-large{padding:9px 14px;font-size:16px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.btn-large [class^="icon-"]{margin-top:2px;}
+.btn-small{padding:3px 9px;font-size:12px;line-height:18px;}
+.btn-small [class^="icon-"]{margin-top:0;}
+.btn-mini{padding:2px 6px;font-size:11px;line-height:17px;}
+.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+.btn-block+.btn-block{margin-top:5px;}
+input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
+.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
+.btn{border-color:#c5c5c5;border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);}
+.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#2f92e7;background-image:-moz-linear-gradient(top, #2fa4e7, #2f76e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#2fa4e7), to(#2f76e7));background-image:-webkit-linear-gradient(top, #2fa4e7, #2f76e7);background-image:-o-linear-gradient(top, #2fa4e7, #2f76e7);background-image:linear-gradient(to bottom, #2fa4e7, #2f76e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff2fa4e7', endColorstr='#ff2f76e7', GradientType=0);border-color:#2f76e7 #2f76e7 #1553b5;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f76e7;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#2f76e7;*background-color:#1a67e2;}
+.btn-primary:active,.btn-primary.active{background-color:#175dcc \9;}
+.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#dd5600;background-image:-moz-linear-gradient(top, #dd5600, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#dd5600), to(#dd5600));background-image:-webkit-linear-gradient(top, #dd5600, #dd5600);background-image:-o-linear-gradient(top, #dd5600, #dd5600);background-image:linear-gradient(to bottom, #dd5600, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd5600', endColorstr='#ffdd5600', GradientType=0);border-color:#dd5600 #dd5600 #913800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#dd5600;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#dd5600;*background-color:#c44c00;}
+.btn-warning:active,.btn-warning.active{background-color:#aa4200 \9;}
+.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#c32627;background-image:-moz-linear-gradient(top, #c71c22, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#c71c22), to(#bd362f));background-image:-webkit-linear-gradient(top, #c71c22, #bd362f);background-image:-o-linear-gradient(top, #c71c22, #bd362f);background-image:linear-gradient(to bottom, #c71c22, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffc71c22', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;}
+.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
+.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#65a643;background-image:-moz-linear-gradient(top, #73a839, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#73a839), to(#51a351));background-image:-webkit-linear-gradient(top, #73a839, #51a351);background-image:-o-linear-gradient(top, #73a839, #51a351);background-image:linear-gradient(to bottom, #73a839, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff73a839', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;}
+.btn-success:active,.btn-success.active{background-color:#408140 \9;}
+.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#6d76b3;background-image:-moz-linear-gradient(top, #9760b3, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#9760b3), to(#2f96b4));background-image:-webkit-linear-gradient(top, #9760b3, #2f96b4);background-image:-o-linear-gradient(top, #9760b3, #2f96b4);background-image:linear-gradient(to bottom, #9760b3, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff9760b3', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;}
+.btn-info:active,.btn-info.active{background-color:#24748c \9;}
+.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0f3253;background-image:-moz-linear-gradient(top, #033c73, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#222222));background-image:-webkit-linear-gradient(top, #033c73, #222222);background-image:-o-linear-gradient(top, #033c73, #222222);background-image:linear-gradient(to bottom, #033c73, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;}
+.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
+button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
+button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
+button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
+button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
+.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-link{border-color:transparent;cursor:pointer;color:#2fa4e7;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-link:hover{color:#157ab5;text-decoration:underline;background-color:transparent;}
+.btn-link[disabled]:hover{color:#333333;text-decoration:none;}
+.btn-group{position:relative;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;}
+.btn-group+.btn-group{margin-left:5px;}
+.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
+.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px;}
+.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group>.btn+.btn{margin-left:-1px;}
+.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px;}
+.btn-group>.btn-mini{font-size:11px;}
+.btn-group>.btn-small{font-size:12px;}
+.btn-group>.btn-large{font-size:16px;}
+.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
+.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
+.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
+.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;}
+.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
+.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:5px;*padding-bottom:5px;}
+.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;}
+.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;}
+.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;}
+.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;}
+.btn-group.open .btn-primary.dropdown-toggle{background-color:#2f76e7;}
+.btn-group.open .btn-warning.dropdown-toggle{background-color:#dd5600;}
+.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;}
+.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;}
+.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;}
+.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;}
+.btn .caret{margin-top:8px;margin-left:0;}
+.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px;}
+.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;}
+.dropup .btn-large .caret{border-bottom:5px solid #000000;border-top:0;}
+.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;}
+.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group-vertical .btn+.btn{margin-left:0;margin-top:-1px;}
+.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
+.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
+.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;}
+.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#8accf2;border:1px solid #7dd9f0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;color:#126b9e;}
+.alert h4{margin:0;}
+.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;}
+.alert-success{background-color:#ccddbb;border-color:#c6d4aa;color:#669533;}
+.alert-danger,.alert-error{background-color:#eddbe3;border-color:#e8d1df;color:#bd4247;}
+.alert-info{background-color:#a7dff1;border-color:#88e4ec;color:#817b58;}
+.alert-block{padding-top:14px;padding-bottom:14px;}
+.alert-block>p,.alert-block>ul{margin-bottom:0;}
+.alert-block p+p{margin-top:5px;}
+.nav{margin-left:0;margin-bottom:20px;list-style:none;}
+.nav>li>a{display:block;}
+.nav>li>a:hover{text-decoration:none;background-color:#f5f5f5;}
+.nav>.pull-right{float:right;}
+.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
+.nav li+.nav-header{margin-top:9px;}
+.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
+.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.nav-list>li>a{padding:3px 15px;}
+.nav-list>.active>a,.nav-list>.active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#2fa4e7;}
+.nav-list [class^="icon-"]{margin-right:2px;}
+.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;}
+.nav-tabs:after,.nav-pills:after{clear:both;}
+.nav-tabs>li,.nav-pills>li{float:left;}
+.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
+.nav-tabs{border-bottom:1px solid #ddd;}
+.nav-tabs>li{margin-bottom:-1px;}
+.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #dddddd;}
+.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#ffffff;background-color:#2fa4e7;}
+.nav-stacked>li{float:none;}
+.nav-stacked>li>a{margin-right:0;}
+.nav-tabs.nav-stacked{border-bottom:0;}
+.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
+.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
+.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
+.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
+.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.nav .dropdown-toggle .caret{border-top-color:#2fa4e7;border-bottom-color:#2fa4e7;margin-top:6px;}
+.nav .dropdown-toggle:hover .caret{border-top-color:#157ab5;border-bottom-color:#157ab5;}
+.nav-tabs .dropdown-toggle .caret{margin-top:8px;}
+.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;}
+.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.nav>.dropdown.active>a:hover{cursor:pointer;}
+.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
+.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
+.tabs-stacked .open>a:hover{border-color:#999999;}
+.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;}
+.tabbable:after{clear:both;}
+.tab-content{overflow:auto;}
+.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.tabs-below>.nav-tabs{border-top:1px solid #ddd;}
+.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;}
+.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
+.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd;}
+.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;}
+.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
+.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
+.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.tabs-left>.nav-tabs>li>a:hover{border-color:#f5f5f5 #dddddd #f5f5f5 #f5f5f5;}
+.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
+.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
+.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.tabs-right>.nav-tabs>li>a:hover{border-color:#f5f5f5 #f5f5f5 #f5f5f5 #dddddd;}
+.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
+.nav>.disabled>a{color:#999999;}
+.nav>.disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default;}
+.navbar{overflow:visible;margin-bottom:20px;color:#f5f5f5;*position:relative;*z-index:2;}
+.navbar-inner{min-height:50px;padding-left:20px;padding-right:20px;background-color:#45aeea;background-image:-moz-linear-gradient(top, #54b4eb, #2fa4e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#54b4eb), to(#2fa4e7));background-image:-webkit-linear-gradient(top, #54b4eb, #2fa4e7);background-image:-o-linear-gradient(top, #54b4eb, #2fa4e7);background-image:linear-gradient(to bottom, #54b4eb, #2fa4e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff54b4eb', endColorstr='#ff2fa4e7', GradientType=0);border:1px solid #1684c2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;}
+.navbar-inner:after{clear:both;}
+.navbar .container{width:auto;}
+.nav-collapse.collapse{height:auto;}
+.navbar .brand{float:left;display:block;padding:15px 20px 15px;margin-left:-20px;font-size:20px;font-weight:200;color:#ffffff;text-shadow:0 1px 0 #54b4eb;}.navbar .brand:hover{text-decoration:none;}
+.navbar-text{margin-bottom:0;line-height:50px;}
+.navbar-link{color:#ffffff;}.navbar-link:hover{color:#ffffff;}
+.navbar .divider-vertical{height:50px;margin:0 9px;border-left:1px solid #2fa4e7;border-right:1px solid #54b4eb;}
+.navbar .btn,.navbar .btn-group{margin-top:10px;}
+.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0;}
+.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;}
+.navbar-form:after{clear:both;}
+.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:10px;}
+.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;}
+.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
+.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
+.navbar-search{position:relative;float:left;margin-top:10px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.navbar-static-top{position:static;width:100%;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;}
+.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;}
+.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.navbar-fixed-top{top:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.1), 0 1px 10px rgba(0, 0, 0, 0.1);}
+.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.1), 0 -1px 10px rgba(0, 0, 0, 0.1);}
+.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
+.navbar .nav.pull-right{float:right;margin-right:0;}
+.navbar .nav>li{float:left;}
+.navbar .nav>li>a{float:none;padding:15px 15px 15px;color:#ffffff;text-decoration:none;text-shadow:0 1px 0 #54b4eb;}
+.navbar .nav .dropdown-toggle .caret{margin-top:8px;}
+.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:rgba(0, 0, 0, 0.2);color:#ffffff;text-decoration:none;}
+.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#ffffff;text-decoration:none;background-color:rgba(0, 0, 0, 0.2);-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);}
+.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#2fa3e6;background-image:-moz-linear-gradient(top, #3daae9, #1a99e2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#3daae9), to(#1a99e2));background-image:-webkit-linear-gradient(top, #3daae9, #1a99e2);background-image:-o-linear-gradient(top, #3daae9, #1a99e2);background-image:linear-gradient(to bottom, #3daae9, #1a99e2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3daae9', endColorstr='#ff1a99e2', GradientType=0);border-color:#1a99e2 #1a99e2 #126b9e;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#1a99e2;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#1a99e2;*background-color:#178acc;}
+.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#157ab5 \9;}
+.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
+.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
+.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
+.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
+.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(0, 0, 0, 0.2);color:#ffffff;}
+.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;}
+.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;}
+.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+.navbar-inverse{color:#ffffff;}.navbar-inverse .navbar-inner{background-color:#034482;background-image:-moz-linear-gradient(top, #04498c, #033c73);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73));background-image:-webkit-linear-gradient(top, #04498c, #033c73);background-image:-o-linear-gradient(top, #04498c, #033c73);background-image:linear-gradient(to bottom, #04498c, #033c73);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0);border-color:rgba(0, 0, 0, 0.1);}
+.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#ffffff;}
+.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:rgba(0, 0, 0, 0.05);color:#ffffff;}
+.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:rgba(0, 0, 0, 0.05);}
+.navbar-inverse .navbar-link{color:#ffffff;}.navbar-inverse .navbar-link:hover{color:#ffffff;}
+.navbar-inverse .divider-vertical{border-left-color:#033c73;border-right-color:#04498c;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:rgba(0, 0, 0, 0.05);color:#ffffff;}
+.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#ffffff;border-color:#033c73;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#999999;}
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#999999;}
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#999999;}
+.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
+.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#033769;background-image:-moz-linear-gradient(top, #033c73, #022f5a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#033c73), to(#022f5a));background-image:-webkit-linear-gradient(top, #033c73, #022f5a);background-image:-o-linear-gradient(top, #033c73, #022f5a);background-image:linear-gradient(to bottom, #033c73, #022f5a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff033c73', endColorstr='#ff022f5a', GradientType=0);border-color:#022f5a #022f5a #000810;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#022f5a;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#022f5a;*background-color:#022241;}
+.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#011528 \9;}
+.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#ccc;}
+.breadcrumb .active{color:#999999;}
+.pagination{height:40px;margin:20px 0;}
+.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination ul>li{display:inline;}
+.pagination ul>li>a,.pagination ul>li>span{float:left;padding:0 14px;line-height:38px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;}
+.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;}
+.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;}
+.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999999;background-color:transparent;cursor:default;}
+.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.pagination-centered{text-align:center;}
+.pagination-right{text-align:right;}
+.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;}
+.pager:after{clear:both;}
+.pager li{display:inline;}
+.pager a,.pager span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.pager a:hover{text-decoration:none;background-color:#f5f5f5;}
+.pager .next a,.pager .next span{float:right;}
+.pager .previous a{float:left;}
+.pager .disabled a,.pager .disabled a:hover,.pager .disabled span{color:#999999;background-color:#fff;cursor:default;}
+.modal-open .modal .dropdown-menu{z-index:2050;}
+.modal-open .modal .dropdown.open{*z-index:2050;}
+.modal-open .modal .popover{z-index:2060;}
+.modal-open .modal .tooltip{z-index:2070;}
+.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
+.modal{position:fixed;top:50%;left:50%;z-index:1050;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
+.modal-header h3{margin:0;line-height:30px;}
+.modal-body{overflow-y:auto;max-height:400px;padding:15px;}
+.modal-form{margin-bottom:0;}
+.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
+.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
+.tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
+.tooltip.top{margin-top:-3px;}
+.tooltip.right{margin-left:3px;}
+.tooltip.bottom{margin-top:3px;}
+.tooltip.left{margin-left:-3px;}
+.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;}
+.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;}
+.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;}
+.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;}
+.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;}
+.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);}.popover.top{margin-bottom:10px;}
+.popover.right{margin-left:10px;}
+.popover.bottom{margin-top:10px;}
+.popover.left{margin-right:10px;}
+.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}
+.popover-content{padding:9px 14px;}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0;}
+.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid;}
+.popover .arrow:after{content:"";z-index:-1;}
+.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-width:10px 10px 0;border-top-color:#ffffff;}.popover.top .arrow:after{border-width:11px 11px 0;border-top-color:rgba(0, 0, 0, 0.25);bottom:-1px;left:-11px;}
+.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-width:10px 10px 10px 0;border-right-color:#ffffff;}.popover.right .arrow:after{border-width:11px 11px 11px 0;border-right-color:rgba(0, 0, 0, 0.25);bottom:-11px;left:-1px;}
+.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-width:0 10px 10px;border-bottom-color:#ffffff;}.popover.bottom .arrow:after{border-width:0 11px 11px;border-bottom-color:rgba(0, 0, 0, 0.25);top:-1px;left:-11px;}
+.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-width:10px 0 10px 10px;border-left-color:#ffffff;}.popover.left .arrow:after{border-width:11px 0 11px 11px;border-left-color:rgba(0, 0, 0, 0.25);bottom:-11px;right:-1px;}
+.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;}
+.thumbnails:after{clear:both;}
+.row-fluid .thumbnails{margin-left:0;}
+.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;}
+.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;}
+a.thumbnail:hover{border-color:#2fa4e7;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
+.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
+.thumbnail .caption{padding:9px;color:#555555;}
+.label,.badge{font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;}
+.label{padding:1px 4px 2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.badge{padding:1px 9px 2px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
+a.label:hover,a.badge:hover{color:#ffffff;text-decoration:none;cursor:pointer;}
+.label-important,.badge-important{background-color:#bd4247;}
+.label-important[href],.badge-important[href]{background-color:#983538;}
+.label-warning,.badge-warning{background-color:#dd5600;}
+.label-warning[href],.badge-warning[href]{background-color:#aa4200;}
+.label-success,.badge-success{background-color:#669533;}
+.label-success[href],.badge-success[href]{background-color:#4c6f26;}
+.label-info,.badge-info{background-color:#817b58;}
+.label-info[href],.badge-info[href]{background-color:#625e43;}
+.label-inverse,.badge-inverse{background-color:#333333;}
+.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;}
+.btn .label,.btn .badge{position:relative;top:-1px;}
+.btn-mini .label,.btn-mini .badge{top:0;}
+@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
+.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);}
+.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
+.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
+.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);}
+.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);}
+.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);}
+.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-warning .bar,.progress .bar-warning{background-color:#f16e1a;background-image:-moz-linear-gradient(top, #ff7d2b, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ff7d2b), to(#dd5600));background-image:-webkit-linear-gradient(top, #ff7d2b, #dd5600);background-image:-o-linear-gradient(top, #ff7d2b, #dd5600);background-image:linear-gradient(to bottom, #ff7d2b, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff7d2b', endColorstr='#ffdd5600', GradientType=0);}
+.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#ff7d2b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.accordion{margin-bottom:20px;}
+.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.accordion-heading{border-bottom:0;}
+.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
+.accordion-toggle{cursor:pointer;}
+.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
+.carousel{position:relative;margin-bottom:20px;line-height:1;}
+.carousel-inner{overflow:hidden;width:100%;position:relative;}
+.carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
+.carousel .item>img{display:block;line-height:1;}
+.carousel .active,.carousel .next,.carousel .prev{display:block;}
+.carousel .active{left:0;}
+.carousel .next,.carousel .prev{position:absolute;top:0;width:100%;}
+.carousel .next{left:100%;}
+.carousel .prev{left:-100%;}
+.carousel .next.left,.carousel .prev.right{left:0;}
+.carousel .active.left{left:-100%;}
+.carousel .active.right{left:100%;}
+.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
+.carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
+.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);}
+.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;}
+.carousel-caption h4{margin:0 0 5px;}
+.carousel-caption p{margin-bottom:0;}
+.hero-unit{padding:60px;margin-bottom:30px;background-color:#f5f5f5;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:30px;color:inherit;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.invisible{visibility:hidden;}
+.affix{position:fixed;}
+.navbar .brand{padding:14px 20px 16px;font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);}
+.navbar li{line-height:20px;}
+.navbar .nav>li>a{padding:16px 10px 14px;font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);}
+.navbar .search-query{border:1px solid #178acc;line-height:normal;}
+.navbar .navbar-text{padding:19px 10px 18px;line-height:13px;color:rgba(0, 0, 0, 0.5);text-shadow:1px 1px 0 rgba(255, 255, 255, 0.3);}
+.navbar-inverse .navbar-search .search-query{color:#555555;}
+@media (max-width:979px){.navbar .nav-collapse .nav li>a{font-family:'Telex',sans-serif;font-weight:normal;color:#ffffff;text-shadow:1px 1px 0 rgba(0, 0, 0, 0.2);}.navbar .nav-collapse .nav li>a:hover{background-color:#2B7CAC;} .navbar .nav-collapse .nav .active>a{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;background-color:#2B7CAC;} .navbar .nav-collapse .dropdown-menu li>a:hover,.navbar .nav-collapse .dropdown-menu li>a:focus,.navbar .nav-collapse .dropdown-submenu:hover>a{background-image:none;} .navbar .nav-collapse .navbar-form,.navbar .nav-collapse .navbar-search{border:none;} .navbar .nav-collapse .nav-header{color:#2B7CAC;} .navbar-inverse .nav-collapse .nav li>a{color:#ffffff;}.navbar-inverse .nav-collapse .nav li>a:hover{background-color:rgba(0, 0, 0, 0.1);} .navbar-inverse .nav-collapse .nav .active>a,.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:rgba(0, 0, 0, 0.1) !important;}}div.subnav{font-family:'Telex',sans-serif;text-shadow:1px 1px 0 rgba(255, 255, 255, 0.2);}div.subnav-fixed{top:50px;}
+.btn{background-color:#ffffff;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(5%, #ffffff), to(#ffffff));background-image:-webkit-linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 5%, #ffffff);background-image:-o-linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-image:linear-gradient(#ffffff, #ffffff 5%, #ffffff);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffffffff', GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);}.btn:hover{background-position:0 0;}
+.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#3daae9;background-image:-moz-linear-gradient(top, #46aeea, #2fa4e7);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#46aeea), to(#2fa4e7));background-image:-webkit-linear-gradient(top, #46aeea, #2fa4e7);background-image:-o-linear-gradient(top, #46aeea, #2fa4e7);background-image:linear-gradient(to bottom, #46aeea, #2fa4e7);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff46aeea', endColorstr='#ff2fa4e7', GradientType=0);border-color:#2fa4e7 #2fa4e7 #157ab5;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2fa4e7;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#2fa4e7;*background-color:#1a99e2;}
+.btn-primary:active,.btn-primary.active{background-color:#178acc \9;}
+.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#9e6ab8;background-image:-moz-linear-gradient(top, #a271bb, #9760b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#a271bb), to(#9760b3));background-image:-webkit-linear-gradient(top, #a271bb, #9760b3);background-image:-o-linear-gradient(top, #a271bb, #9760b3);background-image:linear-gradient(to bottom, #a271bb, #9760b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffa271bb', endColorstr='#ff9760b3', GradientType=0);border-color:#9760b3 #9760b3 #6f4086;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#9760b3;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#9760b3;*background-color:#8b51a9;}
+.btn-info:active,.btn-info.active{background-color:#7d4898 \9;}
+.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#7bb33d;background-image:-moz-linear-gradient(top, #80bb3f, #73a839);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#80bb3f), to(#73a839));background-image:-webkit-linear-gradient(top, #80bb3f, #73a839);background-image:-o-linear-gradient(top, #80bb3f, #73a839);background-image:linear-gradient(to bottom, #80bb3f, #73a839);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff80bb3f', endColorstr='#ff73a839', GradientType=0);border-color:#73a839 #73a839 #4c6f26;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#73a839;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#73a839;*background-color:#669533;}
+.btn-success:active,.btn-success.active{background-color:#59822c \9;}
+.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ec5c00;background-image:-moz-linear-gradient(top, #f76000, #dd5600);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f76000), to(#dd5600));background-image:-webkit-linear-gradient(top, #f76000, #dd5600);background-image:-o-linear-gradient(top, #f76000, #dd5600);background-image:linear-gradient(to bottom, #f76000, #dd5600);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff76000', endColorstr='#ffdd5600', GradientType=0);border-color:#dd5600 #dd5600 #913800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#dd5600;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#dd5600;*background-color:#c44c00;}
+.btn-warning:active,.btn-warning.active{background-color:#aa4200 \9;}
+.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#d41e24;background-image:-moz-linear-gradient(top, #dd1f26, #c71c22);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#dd1f26), to(#c71c22));background-image:-webkit-linear-gradient(top, #dd1f26, #c71c22);background-image:-o-linear-gradient(top, #dd1f26, #c71c22);background-image:linear-gradient(to bottom, #dd1f26, #c71c22);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdd1f26', endColorstr='#ffc71c22', GradientType=0);border-color:#c71c22 #c71c22 #841317;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#c71c22;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#c71c22;*background-color:#b1191e;}
+.btn-danger:active,.btn-danger.active{background-color:#9a161a \9;}
+.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#034482;background-image:-moz-linear-gradient(top, #04498c, #033c73);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#04498c), to(#033c73));background-image:-webkit-linear-gradient(top, #04498c, #033c73);background-image:-o-linear-gradient(top, #04498c, #033c73);background-image:linear-gradient(to bottom, #04498c, #033c73);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04498c', endColorstr='#ff033c73', GradientType=0);border-color:#033c73 #033c73 #011528;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#033c73;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#033c73;*background-color:#022f5a;}
+.btn-inverse:active,.btn-inverse.active{background-color:#022241 \9;}
+i[class^="icon-"]{opacity:0.8;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.invisible{visibility:hidden;}
+.affix{position:fixed;}
diff --git a/old/css/jquery.miniColors.css b/old/css/jquery.miniColors.css
new file mode 100644
index 0000000..f0777a2
--- /dev/null
+++ b/old/css/jquery.miniColors.css
@@ -0,0 +1,125 @@
+INPUT.miniColors {
+ margin-right: 4px;
+}
+
+.miniColors-selector {
+ position: absolute;
+ width: 175px;
+ height: 150px;
+ background: white;
+ border: solid 1px #bababa;
+ -moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ padding: 5px;
+ z-index: 999999;
+}
+
+.miniColors.opacity.miniColors-selector {
+ width: 200px;
+}
+
+.miniColors-selector.black {
+ background: black;
+ border-color: black;
+}
+
+.miniColors-colors {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ width: 150px;
+ height: 150px;
+ background: url(../img/colors.png) -40px 0 no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors.opacity .miniColors-colors {
+ left: 30px;
+}
+
+.miniColors-hues {
+ position: absolute;
+ top: 5px;
+ left: 160px;
+ width: 20px;
+ height: 150px;
+ background: url(../img/colors.png) 0 0 no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors.opacity .miniColors-hues {
+ left: 185px;
+}
+
+.miniColors-opacity {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ width: 20px;
+ height: 150px;
+ background: url(../img/colors.png) -20px 0 no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors-colorPicker {
+ position: absolute;
+ width: 11px;
+ height: 11px;
+ border: 1px solid black;
+ -moz-border-radius: 11px;
+ -webkit-border-radius: 11px;
+ border-radius: 11px;
+}
+.miniColors-colorPicker-inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 7px;
+ height: 7px;
+ border: 2px solid white;
+ -moz-border-radius: 9px;
+ -webkit-border-radius: 9px;
+ border-radius: 9px;
+}
+
+.miniColors-huePicker,
+.miniColors-opacityPicker {
+ position: absolute;
+ left: -2px;
+ width: 22px;
+ height: 2px;
+ border: 1px solid black;
+ background: white;
+ margin-top: -1px;
+ border-radius: 2px;
+}
+
+.miniColors-trigger,
+.miniColors-triggerWrap {
+ width: 22px;
+ height: 22px;
+ display: inline-block;
+}
+
+.miniColors-triggerWrap {
+ background: url(../img/trigger.png) -22px 0 no-repeat;
+}
+
+.miniColors-triggerWrap.disabled {
+ filter: alpha(opacity=50);
+ opacity: .5;
+}
+
+.miniColors-trigger {
+ vertical-align: middle;
+ outline: none;
+ background: url(../img/trigger.png) 0 0 no-repeat;
+}
+
+.miniColors-triggerWrap.disabled .miniColors-trigger {
+ cursor: default;
+} \ No newline at end of file
diff --git a/old/css/jquery.simplecolorpicker.css b/old/css/jquery.simplecolorpicker.css
new file mode 100644
index 0000000..9a6f33a
--- /dev/null
+++ b/old/css/jquery.simplecolorpicker.css
@@ -0,0 +1,95 @@
+/**
+ * Very simple jQuery Color Picker CSS.
+ *
+ * Copyright (C) 2012 Tanguy Krotoff
+ *
+ * Licensed under the MIT license.
+ *
+ * Inspired by Bootstrap Twitter.
+ * See https://github.com/twitter/bootstrap/blob/master/less/dropdowns.less
+ * See http://twitter.github.com/bootstrap/assets/css/bootstrap.css
+ */
+
+.simplecolorpicker:before {
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ display: inline-block;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-left: 7px solid transparent;
+ border-bottom-color: rgba(0, 0, 0, 0.2);
+ content: '';
+}
+
+.simplecolorpicker:after {
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ display: inline-block;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid #ffffff;
+ border-left: 6px solid transparent;
+ content: '';
+}
+
+.simplecolorpicker.picker {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+
+ min-width: 160px;
+ max-width: 264px;
+
+ padding: 4px 0 0 4px;
+ margin: 1px 0 0;
+ list-style: none;
+ background-color: #ffffff;
+
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+}
+
+ .simplecolorpicker.inline {
+ display: inline-block;
+ height: 18px;
+ padding: 4px 0;
+ }
+
+ .simplecolorpicker.icon,
+ .simplecolorpicker div {
+ cursor: pointer;
+ display: inline-block;
+
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+
+ border: 1px solid transparent;
+ }
+ .simplecolorpicker div {
+ margin: 0 4px 4px 0;
+ }
+
+ .simplecolorpicker div:hover,
+ .simplecolorpicker div.selected {
+ border: 1px solid black;
+ }
diff --git a/old/demo.gif b/old/demo.gif
new file mode 100644
index 0000000..44af586
--- /dev/null
+++ b/old/demo.gif
Binary files differ
diff --git a/old/img/Actions-format-text-bold-icon.png b/old/img/Actions-format-text-bold-icon.png
new file mode 100644
index 0000000..2d20694
--- /dev/null
+++ b/old/img/Actions-format-text-bold-icon.png
Binary files differ
diff --git a/old/img/Actions-format-text-italic-icon.png b/old/img/Actions-format-text-italic-icon.png
new file mode 100644
index 0000000..1b472ef
--- /dev/null
+++ b/old/img/Actions-format-text-italic-icon.png
Binary files differ
diff --git a/old/img/Actions-format-text-strikethrough-icon.png b/old/img/Actions-format-text-strikethrough-icon.png
new file mode 100644
index 0000000..f3078bc
--- /dev/null
+++ b/old/img/Actions-format-text-strikethrough-icon.png
Binary files differ
diff --git a/old/img/Actions-format-text-underline-icon.png b/old/img/Actions-format-text-underline-icon.png
new file mode 100644
index 0000000..ada09d5
--- /dev/null
+++ b/old/img/Actions-format-text-underline-icon.png
Binary files differ
diff --git a/old/img/colors.png b/old/img/colors.png
new file mode 100644
index 0000000..deb50a9
--- /dev/null
+++ b/old/img/colors.png
Binary files differ
diff --git a/old/img/crew_back.png b/old/img/crew_back.png
new file mode 100644
index 0000000..f755ddc
--- /dev/null
+++ b/old/img/crew_back.png
Binary files differ
diff --git a/old/img/crew_front.png b/old/img/crew_front.png
new file mode 100644
index 0000000..c9e85bd
--- /dev/null
+++ b/old/img/crew_front.png
Binary files differ
diff --git a/old/img/delete.png b/old/img/delete.png
new file mode 100644
index 0000000..4713c13
--- /dev/null
+++ b/old/img/delete.png
Binary files differ
diff --git a/old/img/drag.png b/old/img/drag.png
new file mode 100644
index 0000000..5aa700d
--- /dev/null
+++ b/old/img/drag.png
Binary files differ
diff --git a/old/img/font_bold.png b/old/img/font_bold.png
new file mode 100644
index 0000000..52d41e0
--- /dev/null
+++ b/old/img/font_bold.png
Binary files differ
diff --git a/old/img/font_italic.png b/old/img/font_italic.png
new file mode 100644
index 0000000..1017f96
--- /dev/null
+++ b/old/img/font_italic.png
Binary files differ
diff --git a/old/img/font_strikethrough.png b/old/img/font_strikethrough.png
new file mode 100644
index 0000000..42abc79
--- /dev/null
+++ b/old/img/font_strikethrough.png
Binary files differ
diff --git a/old/img/font_underline.png b/old/img/font_underline.png
new file mode 100644
index 0000000..a90947a
--- /dev/null
+++ b/old/img/font_underline.png
Binary files differ
diff --git a/old/img/glyphicons-halflings-white.png b/old/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..3bf6484
--- /dev/null
+++ b/old/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/old/img/glyphicons-halflings.png b/old/img/glyphicons-halflings.png
new file mode 100644
index 0000000..a996999
--- /dev/null
+++ b/old/img/glyphicons-halflings.png
Binary files differ
diff --git a/old/img/invisibleman.jpg b/old/img/invisibleman.jpg
new file mode 100644
index 0000000..dd057c8
--- /dev/null
+++ b/old/img/invisibleman.jpg
Binary files differ
diff --git a/old/img/mens_hoodie_back.png b/old/img/mens_hoodie_back.png
new file mode 100644
index 0000000..ae7c1d3
--- /dev/null
+++ b/old/img/mens_hoodie_back.png
Binary files differ
diff --git a/old/img/mens_hoodie_front.png b/old/img/mens_hoodie_front.png
new file mode 100644
index 0000000..178d227
--- /dev/null
+++ b/old/img/mens_hoodie_front.png
Binary files differ
diff --git a/old/img/mens_longsleeve_back.png b/old/img/mens_longsleeve_back.png
new file mode 100644
index 0000000..43e29b2
--- /dev/null
+++ b/old/img/mens_longsleeve_back.png
Binary files differ
diff --git a/old/img/mens_longsleeve_front.png b/old/img/mens_longsleeve_front.png
new file mode 100644
index 0000000..7cd405b
--- /dev/null
+++ b/old/img/mens_longsleeve_front.png
Binary files differ
diff --git a/old/img/mens_tank_back.png b/old/img/mens_tank_back.png
new file mode 100644
index 0000000..160498c
--- /dev/null
+++ b/old/img/mens_tank_back.png
Binary files differ
diff --git a/old/img/mens_tank_front.png b/old/img/mens_tank_front.png
new file mode 100644
index 0000000..c672d3f
--- /dev/null
+++ b/old/img/mens_tank_front.png
Binary files differ
diff --git a/old/img/phones/GalaxyS3.png b/old/img/phones/GalaxyS3.png
new file mode 100644
index 0000000..32d558e
--- /dev/null
+++ b/old/img/phones/GalaxyS3.png
Binary files differ
diff --git a/old/img/phones/GalaxyS3A.png b/old/img/phones/GalaxyS3A.png
new file mode 100644
index 0000000..0ddeb4f
--- /dev/null
+++ b/old/img/phones/GalaxyS3A.png
Binary files differ
diff --git a/old/img/phones/GalaxyS3Base.png b/old/img/phones/GalaxyS3Base.png
new file mode 100644
index 0000000..0ab5838
--- /dev/null
+++ b/old/img/phones/GalaxyS3Base.png
Binary files differ
diff --git a/old/img/phones/GalaxyS3Mask.png b/old/img/phones/GalaxyS3Mask.png
new file mode 100644
index 0000000..0f52d08
--- /dev/null
+++ b/old/img/phones/GalaxyS3Mask.png
Binary files differ
diff --git a/old/img/phones/designs/1.jpg b/old/img/phones/designs/1.jpg
new file mode 100644
index 0000000..5118cd7
--- /dev/null
+++ b/old/img/phones/designs/1.jpg
Binary files differ
diff --git a/old/img/phones/designs/2.jpg b/old/img/phones/designs/2.jpg
new file mode 100644
index 0000000..8efabb8
--- /dev/null
+++ b/old/img/phones/designs/2.jpg
Binary files differ
diff --git a/old/img/phones/designs/3.jpg b/old/img/phones/designs/3.jpg
new file mode 100644
index 0000000..080a8a2
--- /dev/null
+++ b/old/img/phones/designs/3.jpg
Binary files differ
diff --git a/old/img/phones/designs/4.jpg b/old/img/phones/designs/4.jpg
new file mode 100644
index 0000000..0072bb0
--- /dev/null
+++ b/old/img/phones/designs/4.jpg
Binary files differ
diff --git a/old/img/phones/designs/5.jpg b/old/img/phones/designs/5.jpg
new file mode 100644
index 0000000..a8f2558
--- /dev/null
+++ b/old/img/phones/designs/5.jpg
Binary files differ
diff --git a/old/img/phones/designs/6.jpg b/old/img/phones/designs/6.jpg
new file mode 100644
index 0000000..5ac30f3
--- /dev/null
+++ b/old/img/phones/designs/6.jpg
Binary files differ
diff --git a/old/img/phones/designs/7.jpg b/old/img/phones/designs/7.jpg
new file mode 100644
index 0000000..b3f21d5
--- /dev/null
+++ b/old/img/phones/designs/7.jpg
Binary files differ
diff --git a/old/img/phones/designs/8.jpg b/old/img/phones/designs/8.jpg
new file mode 100644
index 0000000..0f597bd
--- /dev/null
+++ b/old/img/phones/designs/8.jpg
Binary files differ
diff --git a/old/img/phones/iPhone3A.png b/old/img/phones/iPhone3A.png
new file mode 100644
index 0000000..135d8bd
--- /dev/null
+++ b/old/img/phones/iPhone3A.png
Binary files differ
diff --git a/old/img/phones/iPhone3GHCOverlay.png b/old/img/phones/iPhone3GHCOverlay.png
new file mode 100644
index 0000000..984791d
--- /dev/null
+++ b/old/img/phones/iPhone3GHCOverlay.png
Binary files differ
diff --git a/old/img/phones/iPhone4.png b/old/img/phones/iPhone4.png
new file mode 100644
index 0000000..19b4ca0
--- /dev/null
+++ b/old/img/phones/iPhone4.png
Binary files differ
diff --git a/old/img/phones/iPhone4A.png b/old/img/phones/iPhone4A.png
new file mode 100644
index 0000000..71fb864
--- /dev/null
+++ b/old/img/phones/iPhone4A.png
Binary files differ
diff --git a/old/img/phones/iPhone4HCOverlay.png b/old/img/phones/iPhone4HCOverlay.png
new file mode 100644
index 0000000..a470366
--- /dev/null
+++ b/old/img/phones/iPhone4HCOverlay.png
Binary files differ
diff --git a/old/img/phones/iPhone5A.png b/old/img/phones/iPhone5A.png
new file mode 100644
index 0000000..33aa90c
--- /dev/null
+++ b/old/img/phones/iPhone5A.png
Binary files differ
diff --git a/old/img/phones/iphone4Base.png b/old/img/phones/iphone4Base.png
new file mode 100644
index 0000000..c523a94
--- /dev/null
+++ b/old/img/phones/iphone4Base.png
Binary files differ
diff --git a/old/img/phones/iphone4Mask.png b/old/img/phones/iphone4Mask.png
new file mode 100644
index 0000000..d398023
--- /dev/null
+++ b/old/img/phones/iphone4Mask.png
Binary files differ
diff --git a/old/img/phones/iphone5.png b/old/img/phones/iphone5.png
new file mode 100644
index 0000000..1a56333
--- /dev/null
+++ b/old/img/phones/iphone5.png
Binary files differ
diff --git a/old/img/phones/iphone5Base.png b/old/img/phones/iphone5Base.png
new file mode 100644
index 0000000..e39ba70
--- /dev/null
+++ b/old/img/phones/iphone5Base.png
Binary files differ
diff --git a/old/img/phones/iphone5Mask.png b/old/img/phones/iphone5Mask.png
new file mode 100644
index 0000000..fb9d5b6
--- /dev/null
+++ b/old/img/phones/iphone5Mask.png
Binary files differ
diff --git a/old/img/rotate.png b/old/img/rotate.png
new file mode 100644
index 0000000..9883184
--- /dev/null
+++ b/old/img/rotate.png
Binary files differ
diff --git a/old/img/sample.jpg b/old/img/sample.jpg
new file mode 100644
index 0000000..433d6bb
--- /dev/null
+++ b/old/img/sample.jpg
Binary files differ
diff --git a/old/img/scale.png b/old/img/scale.png
new file mode 100644
index 0000000..b783f8b
--- /dev/null
+++ b/old/img/scale.png
Binary files differ
diff --git a/old/img/trigger.png b/old/img/trigger.png
new file mode 100644
index 0000000..96c9129
--- /dev/null
+++ b/old/img/trigger.png
Binary files differ
diff --git a/old/img/womens_crew_back.png b/old/img/womens_crew_back.png
new file mode 100644
index 0000000..7f5b8e2
--- /dev/null
+++ b/old/img/womens_crew_back.png
Binary files differ
diff --git a/old/img/womens_crew_front.png b/old/img/womens_crew_front.png
new file mode 100644
index 0000000..f1e1006
--- /dev/null
+++ b/old/img/womens_crew_front.png
Binary files differ
diff --git a/old/index.html b/old/index.html
new file mode 100644
index 0000000..91f8876
--- /dev/null
+++ b/old/index.html
@@ -0,0 +1,434 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>TShirt Editor</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+ <!--[if IE]><script type="text/javascript" src="js/excanvas.js"></script><![endif]-->
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
+ <script type="text/javascript" src="js/fabric.js"></script>
+
+
+ <!-- Le styles -->
+ <link type="text/css" rel="stylesheet" href="css/jquery.miniColors.css" />
+ <link href="css/bootstrap.min.css" rel="stylesheet">
+ <link href="css/bootstrap-responsive.min.css" rel="stylesheet">
+ <script type="text/javascript">
+ </script>
+ <style type="text/css">
+ .footer {
+ padding: 70px 0;
+ margin-top: 70px;
+ border-top: 1px solid #E5E5E5;
+ background-color: whiteSmoke;
+ }
+ body {
+ padding-top: 60px;
+ }
+ .color-preview {
+ border: 1px solid #CCC;
+ margin: 2px;
+ zoom: 1;
+ vertical-align: top;
+ display: inline-block;
+ cursor: pointer;
+ overflow: hidden;
+ width: 20px;
+ height: 20px;
+ }
+ .rotate {
+ -webkit-transform:rotate(90deg);
+ -moz-transform:rotate(90deg);
+ -o-transform:rotate(90deg);
+ /* filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1.5); */
+ -ms-transform:rotate(90deg);
+ }
+ .Arial{font-family:"Arial";}
+ .Helvetica{font-family:"Helvetica";}
+ .MyriadPro{font-family:"Myriad Pro";}
+ .Delicious{font-family:"Delicious";}
+ .Verdana{font-family:"Verdana";}
+ .Georgia{font-family:"Georgia";}
+ .Courier{font-family:"Courier";}
+ .ComicSansMS{font-family:"Comic Sans MS";}
+ .Impact{font-family:"Impact";}
+ .Monaco{font-family:"Monaco";}
+ .Optima{font-family:"Optima";}
+ .HoeflerText{font-family:"Hoefler Text";}
+ .Plaster{font-family:"Plaster";}
+ .Engagement{font-family:"Engagement";}
+
+ </style>
+ </head>
+
+ <body class="preview" data-spy="scroll" data-target=".subnav" data-offset="80">
+
+ <!-- Navbar
+ ================================================== -->
+
+ <div class="container">
+ <section id="typography">
+ <div class="page-header">
+ <h1>Customize T-Shirt</h1>
+ </div>
+
+ <!-- Headings & Paragraph Copy -->
+ <div class="row">
+ <div class="span3">
+
+ <div class="tabbable"> <!-- Only required for left/right tabs -->
+ <ul class="nav nav-tabs">
+ <li class="active"><a href="#tab1" data-toggle="tab">T-Shirt Options</a></li>
+ <li><a href="#tab2" data-toggle="tab">Gravatar</a></li>
+ </ul>
+ <div class="tab-content">
+ <div class="tab-pane active" id="tab1">
+ <div class="well">
+<!-- <h3>Tee Styles</h3>-->
+<!-- <p>-->
+ <select id="tshirttype">
+ <option value="img/crew_front.png" selected="selected">Short Sleeve Shirts</option>
+ <option value="img/mens_longsleeve_front.png">Long Sleeve Shirts</option>
+ <option value="img/mens_hoodie_front.png">Hoodies</option>
+ <option value="img/mens_tank_front.png">Tank tops</option>
+ </select>
+<!-- </p>-->
+ </div>
+ <div class="well">
+ <ul class="nav">
+ <li class="color-preview" title="White" style="background-color:#ffffff;"></li>
+ <li class="color-preview" title="Dark Heather" style="background-color:#616161;"></li>
+ <li class="color-preview" title="Gray" style="background-color:#f0f0f0;"></li>
+ <li class="color-preview" title="Charcoal" style="background-color:#5b5b5b;"></li>
+ <li class="color-preview" title="Black" style="background-color:#222222;"></li>
+ <li class="color-preview" title="Heather Orange" style="background-color:#fc8d74;"></li>
+ <li class="color-preview" title="Heather Dark Chocolate" style="background-color:#432d26;"></li>
+ <li class="color-preview" title="Salmon" style="background-color:#eead91;"></li>
+ <li class="color-preview" title="Chesnut" style="background-color:#806355;"></li>
+ <li class="color-preview" title="Dark Chocolate" style="background-color:#382d21;"></li>
+ <li class="color-preview" title="Citrus Yellow" style="background-color:#faef93;"></li>
+ <li class="color-preview" title="Avocado" style="background-color:#aeba5e;"></li>
+ <li class="color-preview" title="Kiwi" style="background-color:#8aa140;"></li>
+ <li class="color-preview" title="Irish Green" style="background-color:#1f6522;"></li>
+ <li class="color-preview" title="Scrub Green" style="background-color:#13afa2;"></li>
+ <li class="color-preview" title="Teal Ice" style="background-color:#b8d5d7;"></li>
+ <li class="color-preview" title="Heather Sapphire" style="background-color:#15aeda;"></li>
+ <li class="color-preview" title="Sky" style="background-color:#a5def8;"></li>
+ <li class="color-preview" title="Antique Sapphire" style="background-color:#0f77c0;"></li>
+ <li class="color-preview" title="Heather Navy" style="background-color:#3469b7;"></li>
+ <li class="color-preview" title="Cherry Red" style="background-color:#c50404;"></li>
+ </ul>
+ </div>
+ </div>
+ <div class="tab-pane" id="tab2">
+ <div class="well">
+ <div class="input-append">
+ <input class="span2" id="text-string" type="text" placeholder="add text here..."><button id="add-text" class="btn" title="Add text"><i class="icon-share-alt"></i></button>
+ <hr>
+ </div>
+ <div id="avatarlist">
+ <img style="cursor:pointer;" class="img-polaroid" src="img/invisibleman.jpg">
+ <img style="cursor:pointer;" class="img-polaroid" src="data:image/jpeg;base64,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">
+ <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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">
+ <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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%3D%3D">
+ <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,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">
+ <img style="cursor:pointer;" class="img-polaroid" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAABL0lEQVR4nO3cMQ6CQBRAQVFvaWOsbT2FrbVn9QaEJ26QZKZHNi+/WMjidLpeDixz3HoBeyJWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWcN7qxu/74+trb6/nD1eynMkKxArECsQKxArECsQKxArECgbu4Of36Gt24eN+eZ7JCsQKxArECsQKxArECsQKxAqm+S9Z9/imfNyaTVYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFYgViBWIFaw2SmaPTJZgViBWIFYgViBWIFYgViBWMGqHfz8mZP/PAe/5rnCZAViBWIFYgViBWIFYgViBWIFA9/BjzsH779odkCsQKxArECsQKxArECsQKzgA29sHs723zLdAAAAAElFTkSuQmCC">
+ </div>
+ <div>
+ <hr>
+ <form action="" method="post" enctype="multipart/form-data">
+ <input type="file" name="fileToUpload" id="fileToUpload">
+ <input class="btn btn-primary" type="submit" value="Upload Custom Image" name="submit">
+ </form>
+
+ </div>
+
+ </div>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ <div class="span6">
+ <div align="center" style="min-height: 32px;">
+ <div class="clearfix">
+ <div class="btn-group inline pull-left" id="texteditor" style="display:none">
+ <button id="font-family" class="btn dropdown-toggle" data-toggle="dropdown" title="Font Style"><i class="icon-font" style="width:19px;height:19px;"></i></button>
+ <ul class="dropdown-menu" role="menu" aria-labelledby="font-family-X">
+ <li><a tabindex="-1" href="#" onclick="setFont('Arial');" class="Arial">Arial</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Helvetica');" class="Helvetica">Helvetica</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Myriad Pro');" class="MyriadPro">Myriad Pro</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Delicious');" class="Delicious">Delicious</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Verdana');" class="Verdana">Verdana</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Georgia');" class="Georgia">Georgia</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Courier');" class="Courier">Courier</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Comic Sans MS');" class="ComicSansMS">Comic Sans MS</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Impact');" class="Impact">Impact</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Monaco');" class="Monaco">Monaco</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Optima');" class="Optima">Optima</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Hoefler Text');" class="Hoefler Text">Hoefler Text</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Plaster');" class="Plaster">Plaster</a></li>
+ <li><a tabindex="-1" href="#" onclick="setFont('Engagement');" class="Engagement">Engagement</a></li>
+ </ul>
+ <button id="text-bold" class="btn" data-original-title="Bold"><img src="img/font_bold.png" height="" width=""></button>
+ <button id="text-italic" class="btn" data-original-title="Italic"><img src="img/font_italic.png" height="" width=""></button>
+ <button id="text-strike" class="btn" title="Strike" style=""><img src="img/font_strikethrough.png" height="" width=""></button>
+ <button id="text-underline" class="btn" title="Underline" style=""><img src="img/font_underline.png"></button>
+ <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Color"><input type="hidden" id="text-fontcolor" class="color-picker" size="7" value="#000000"></a>
+ <a class="btn" href="#" rel="tooltip" data-placement="top" data-original-title="Font Border Color"><input type="hidden" id="text-strokecolor" class="color-picker" size="7" value="#000000"></a>
+ <!--- Background <input type="hidden" id="text-bgcolor" class="color-picker" size="7" value="#ffffff"> --->
+ </div>
+ <div class="pull-right" align="" id="imageeditor" style="display:none">
+ <div class="btn-group">
+ <button class="btn" id="bring-to-front" title="Bring to Front"><i class="icon-fast-backward rotate" style="height:19px;"></i></button>
+ <button class="btn" id="send-to-back" title="Send to Back"><i class="icon-fast-forward rotate" style="height:19px;"></i></button>
+ <button id="flip" type="button" class="btn" title="Show Back View"><i class="icon-retweet" style="height:19px;"></i></button>
+ <button id="remove-selected" class="btn" title="Delete selected item"><i class="icon-trash" style="height:19px;"></i></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <!-- EDITOR -->
+ <button id="flipback" type="button" class="btn" title="Rotate View"><i class="icon-retweet" style="height:19px;"></i></button>
+ <div id="shirtDiv" class="page" style="width: 530px; height: 630px; position: relative; background-color: rgb(255, 255, 255);">
+ <img name="tshirtview" id="tshirtFacing" src="img/crew_front.png">
+ <div id="drawingArea" style="position: absolute;top: 100px;left: 160px;z-index: 10;width: 200px;height: 400px;">
+ <canvas id="tcanvas" width=200 height="400" class="hover" style="-webkit-user-select: none;"></canvas>
+ </div>
+ </div>
+<!-- <div id="shirtBack" class="page" style="width: 530px; height: 630px; position: relative; background-color: rgb(255, 255, 255); display:none;">-->
+<!-- <img src="img/crew_back.png"></img>-->
+<!-- <div id="drawingArea" style="position: absolute;top: 100px;left: 160px;z-index: 10;width: 200px;height: 400px;"> -->
+<!-- <canvas id="backCanvas" width=200 height="400" class="hover" style="-webkit-user-select: none;"></canvas>-->
+<!-- </div>-->
+<!-- </div> -->
+
+ <!-- /EDITOR -->
+ </div>
+
+ <div class="span3">
+ <div class="well">
+ <h3>Select Sizes</h3>
+ <p>
+ <table class="table">
+ <tr>
+ <td><input type="checkbox">&emsp;S</td>
+ <td align="right"><input min="0" style="width: 40px;" value="1" type="number"></td>
+ </tr>
+ <tr>
+ <td><input type="checkbox">&emsp;M</td>
+ <td align="right"><input min="0" style="width: 40px;" placeholder="1" type="number"></td>
+ </tr>
+ <tr>
+ <td><input type="checkbox">&emsp;L</td>
+ <td align="right"><input min="0" style="width: 40px;" placeholder="1" type="number"></td>
+ </tr>
+ <tr>
+ <td><input type="checkbox">&emsp;XL</td>
+ <td align="right"><input min="0" style="width: 40px;" placeholder="1" type="number"></td>
+ </tr>
+ <tr>
+ <td><input type="checkbox">&emsp;XXL</td>
+ <td align="right"><input min="0" style="width: 40px;" placeholder="1" type="number"></td>
+ </tr>
+ </table>
+ </p>
+ <button type="button" class="btn btn-large btn-block btn-success" name="addToTheBag" id="addToTheBag">Add to bag <i class="icon-briefcase icon-white"></i></button>
+ </div>
+ </div>
+
+ </div>
+
+ </section>
+ </div><!-- /container -->
+
+<!-- Footer ================================================== -->
+ <script>
+ $(document).ready(function(){
+ $("#tshirttype").change(function(){
+ $("img[name=tshirtview]").attr("src",$(this).val());
+
+ });
+
+});
+ </script>
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script>
+ var valueSelect = $("#tshirttype").val();
+ $("#tshirttype").change(function(){
+ valueSelect = $(this).val();
+ });
+ $('#flipback').click(
+ function() {
+ if (valueSelect === "img/crew_front.png") {
+ if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/crew_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/crew_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ }
+ }
+
+ else if (valueSelect === "img/mens_longsleeve_front.png") {
+ if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/mens_longsleeve_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/mens_longsleeve_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ }
+ }
+ else if (valueSelect === "img/mens_tank_front.png") {
+ if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/mens_tank_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/mens_tank_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ }
+ }
+ else if (valueSelect === "img/mens_hoodie_front.png") {
+ if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/mens_hoodie_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/mens_hoodie_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ }
+ }
+ /* if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/crew_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/crew_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ } */
+ canvas.renderAll();
+ setTimeout(function() {
+ canvas.calcOffset();
+ },200);
+ });
+ </script>
+ <script src="js/bootstrap.min.js"></script>
+<script type="text/javascript" src="js/tshirtEditor.js"></script>
+ <script type="text/javascript" src="js/jquery.miniColors.min.js"></script>
+ <script type="text/javascript">
+
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-35639689-1']);
+ _gaq.push(['_trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
+
+</script>
+ </body>
+</html>
diff --git a/old/js/bootstrap.js b/old/js/bootstrap.js
new file mode 100644
index 0000000..f73fcb8
--- /dev/null
+++ b/old/js/bootstrap.js
@@ -0,0 +1,2027 @@
+/* ===================================================
+ * bootstrap-transition.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ $(function () {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(function () {
+ $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(function () {
+ $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(function () {
+ $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ getParent($(toggle))
+ .removeClass('open')
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(function () {
+ $('html')
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ $('body')
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+ })
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ $('body').addClass('modal-open')
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+ .focus()
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
+ that.$element.trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ $('body').removeClass('modal-open')
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(document.body)
+
+ if (this.options.backdrop != 'static') {
+ this.$backdrop.click($.proxy(this.hide, this))
+ }
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(function () {
+ $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+ })
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-tooltip.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .remove()
+ .css({ top: 0, left: 0, display: 'block' })
+ .appendTo(inside ? this.$element : document.body)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .css(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).remove()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.remove()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.remove()
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function () {
+ this[this.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: true
+ }
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-popover.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active a').last()[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(function () {
+ $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '<strong>' + match + '</strong>'
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if ($.browser.chrome || $.browser.webkit || $.browser.msie) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: '<ul class="typeahead dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(function () {
+ $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+ })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.1.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery); \ No newline at end of file
diff --git a/old/js/bootstrap.min.js b/old/js/bootstrap.min.js
new file mode 100644
index 0000000..0e33fb1
--- /dev/null
+++ b/old/js/bootstrap.min.js
@@ -0,0 +1,6 @@
+/*!
+* Bootstrap.js by @fat & @mdo
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a<r.length-1&&a++,~a||(a=0),r.eq(a).focus()}},e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e(function(){e("html").on("click.dropdown.data-api touchstart.dropdown.data-api",r),e("body").on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;e("body").addClass("modal-open"),this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1).focus(),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.trigger("shown")}):t.$element.trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,e("body").removeClass("modal-open"),this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(e){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active a").last()[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(function(){e("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),(e.browser.chrome||e.browser.webkit||e.browser.msie)&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e(function(){e("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery); \ No newline at end of file
diff --git a/old/js/caseEditor.js b/old/js/caseEditor.js
new file mode 100644
index 0000000..5e4c46a
--- /dev/null
+++ b/old/js/caseEditor.js
@@ -0,0 +1,344 @@
+var canvas;
+var tshirts = new Array(); //prototype: [{style:'x',color:'white',front:'a',back:'b',price:{tshirt:'12.95',frontPrint:'4.99',backPrint:'4.99',total:'22.47'}}]
+var a;
+var b;
+var line1;
+var line2;
+var line3;
+var line4;
+ $(document).ready(function() {
+ //setup front side canvas
+ canvas = new fabric.Canvas('tcanvas', {
+ hoverCursor: 'pointer',
+ selection: true,
+ selectionBorderColor:'blue'
+ });
+ canvas.on({
+ 'object:moving': function(e) {
+ e.target.opacity = 0.5;
+ },
+ 'object:modified': function(e) {
+ e.target.opacity = 1;
+ },
+ 'object:selected':onObjectSelected,
+ 'selection:cleared':onSelectedCleared
+ });
+ // piggyback on `canvas.findTarget`, to fire "object:over" and "object:out" events
+ canvas.findTarget = (function(originalFn) {
+ return function() {
+ var target = originalFn.apply(this, arguments);
+ if (target) {
+ if (this._hoveredTarget !== target) {
+ canvas.fire('object:over', { target: target });
+ if (this._hoveredTarget) {
+ canvas.fire('object:out', { target: this._hoveredTarget });
+ }
+ this._hoveredTarget = target;
+ }
+ }
+ else if (this._hoveredTarget) {
+ canvas.fire('object:out', { target: this._hoveredTarget });
+ this._hoveredTarget = null;
+ }
+ return target;
+ };
+ })(canvas.findTarget);
+
+ canvas.on('object:over', function(e) {
+ //e.target.setFill('red');
+ //canvas.renderAll();
+ });
+
+ canvas.on('object:out', function(e) {
+ //e.target.setFill('green');
+ //canvas.renderAll();
+ });
+
+ document.getElementById('add-text').onclick = function() {
+ var text = $("#text-string").val();
+ var textSample = new fabric.Text(text, {
+ left: fabric.util.getRandomInt(0, 200),
+ top: fabric.util.getRandomInt(0, 400),
+ fontFamily: 'helvetica',
+ angle: 0,
+ fill: '#000000',
+ scaleX: 0.5,
+ scaleY: 0.5,
+ fontWeight: '',
+ hasRotatingPoint:true
+ });
+ canvas.add(textSample);
+ canvas.item(canvas.item.length-1).hasRotatingPoint = true;
+ $("#texteditor").css('display', 'block');
+ $("#imageeditor").css('display', 'block');
+ };
+ $("#text-string").keyup(function(){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.text = this.value;
+ canvas.renderAll();
+ }
+ });
+
+ $("#phoneTypes").change(function(e){
+ debugger;
+ if($(this).val() == "1"){
+ $("#phoneDiv").css('height','590');
+ $("#phone")[0].src = "img/phones/iphone5A.png";
+ //$("#borderMask")[0].src = "img/phones/iphone5Mask.png";
+ line1 = new fabric.Line([0,0,225,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line2 = new fabric.Line([224,0,225,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line3 = new fabric.Line([0,0,0,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line4 = new fabric.Line([0,450,225,449], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ }
+ else if ($(this).val() == "2"){
+ $("#phoneDiv").css('height','540');
+ $("#phone")[0].src = "img/phones/iPhone4A.png";
+ //$("#borderMask")[0].src = "img/phones/iphone4Mask.png";
+ line1 = new fabric.Line([0,20,220,20], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line2 = new fabric.Line([220,20,220,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line3 = new fabric.Line([0,20,0,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line4 = new fabric.Line([0,420,220,420], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ }
+ else if ($(this).val() == "3"){
+ $("#phoneDiv").css('height','535');
+ $("#phone")[0].src = "img/phones/GalaxyS3A.png";
+ //$("#borderMask")[0].src = "img/phones/GalaxyS3Mask.png";
+ line1 = new fabric.Line([0,30,225,30], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line2 = new fabric.Line([224,30,225,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line3 = new fabric.Line([0,30,0,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line4 = new fabric.Line([0,400,225,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ }
+ });
+
+ line1 = new fabric.Line([0,0,225,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line2 = new fabric.Line([224,0,225,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line3 = new fabric.Line([0,0,0,450], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line4 = new fabric.Line([0,450,225,449], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+
+ $(".img-polaroid").click(function(e){
+ var el = e.target;
+ var design = $(this).attr("src");
+ $('#phoneDiv').css({
+ 'backgroundImage': 'url(' + design +')',
+ 'backgroundRepeat': 'no-repeat',
+ 'backgroundPosition': 'top center',
+ 'background-size': '100% 100%'
+
+ });
+ // document.getElementById("phoneDiv").style.backgroundImage="url("+ design +")";
+ });
+ document.getElementById('remove-selected').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ canvas.remove(activeObject);
+ $("#text-string").val("");
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ canvas.remove(object);
+ });
+ }
+ };
+ document.getElementById('bring-to-front').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ activeObject.bringToFront();
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ object.bringToFront();
+ });
+ }
+ };
+ document.getElementById('send-to-back').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ activeObject.sendToBack();
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ object.sendToBack();
+ });
+ }
+ };
+ $("#text-bold").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontWeight = (activeObject.fontWeight == 'bold' ? '' : 'bold');
+ canvas.renderAll();
+ }
+ });
+ $("#text-italic").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontStyle = (activeObject.fontStyle == 'italic' ? '' : 'italic');
+ canvas.renderAll();
+ }
+ });
+ $("#text-strike").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textDecoration = (activeObject.textDecoration == 'line-through' ? '' : 'line-through');
+ canvas.renderAll();
+ }
+ });
+ $("#text-underline").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textDecoration = (activeObject.textDecoration == 'underline' ? '' : 'underline');
+ canvas.renderAll();
+ }
+ });
+ $("#text-left").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'left';
+ canvas.renderAll();
+ }
+ });
+ $("#text-center").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'center';
+ canvas.renderAll();
+ }
+ });
+ $("#text-right").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'right';
+ canvas.renderAll();
+ }
+ });
+ $("#font-family").change(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontFamily = this.value;
+ canvas.renderAll();
+ }
+ });
+ $('#text-bgcolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.backgroundColor = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+ $('#text-fontcolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fill = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+
+ $('#text-strokecolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.strokeStyle = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+
+ //canvas.add(new fabric.fabric.Object({hasBorders:true,hasControls:false,hasRotatingPoint:false,selectable:false,type:'rect'}));
+ $("#drawingArea").hover(
+ function() {
+ canvas.add(line1);
+ canvas.add(line2);
+ canvas.add(line3);
+ canvas.add(line4);
+ canvas.renderAll();
+ },
+ function() {
+ canvas.remove(line1);
+ canvas.remove(line2);
+ canvas.remove(line3);
+ canvas.remove(line4);
+ canvas.renderAll();
+ }
+ );
+
+ $('.color-preview').click(function(){
+ var color = $(this).css("background-color");
+ document.getElementById("phoneDiv").style.backgroundColor = color;
+ });
+
+ $(".clearfix button,a").tooltip();
+ });//doc ready
+
+
+ function getRandomNum(min, max) {
+ return Math.random() * (max - min) + min;
+ }
+
+ function onObjectSelected(e) {
+ var selectedObject = e.target;
+ $("#text-string").val("");
+ selectedObject.hasRotatingPoint = true
+ if (selectedObject && selectedObject.type === 'text') {
+ //display text editor
+ $("#texteditor").css('display', 'block');
+ $("#text-string").val(selectedObject.getText());
+ $('#text-fontcolor').miniColors('value',selectedObject.fill);
+ $('#text-strokecolor').miniColors('value',selectedObject.strokeStyle);
+ $("#imageeditor").css('display', 'block');
+ }
+ else if (selectedObject && selectedObject.type === 'image'){
+ //display image editor
+ $("#texteditor").css('display', 'none');
+ $("#imageeditor").css('display', 'block');
+ }
+ }
+ function onSelectedCleared(e){
+ $("#texteditor").css('display', 'none');
+ $("#text-string").val("");
+ $("#imageeditor").css('display', 'none');
+ }
+ function setFont(font){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontFamily = font;
+ canvas.renderAll();
+ }
+ }
+ function removeWhite(){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'image') {
+ activeObject.filters[2] = new fabric.Image.filters.RemoveWhite({hreshold: 100, distance: 10});//0-255, 0-255
+ activeObject.applyFilters(canvas.renderAll.bind(canvas));
+ }
+ } \ No newline at end of file
diff --git a/old/js/excanvas.js b/old/js/excanvas.js
new file mode 100644
index 0000000..367764b
--- /dev/null
+++ b/old/js/excanvas.js
@@ -0,0 +1,924 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ /**
+ * This funtion is assigned to the <canvas> elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ }
+ },
+
+ init_: function(doc) {
+ // create xmlns
+ if (!doc.namespaces['g_vml_']) {
+ doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+ '#default#VML');
+
+ }
+ if (!doc.namespaces['g_o_']) {
+ doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+ '#default#VML');
+ }
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}' +
+ 'g_vml_\\:*{behavior:url(#default#VML)}' +
+ 'g_o_\\:*{behavior:url(#default#VML)}';
+
+ }
+
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+
+ el.getContext = getContext;
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ el.getContext().clearRect();
+ break;
+ case 'height':
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.getContext().clearRect();
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var dec2hex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ function processStyle(styleString) {
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.substring(0, 3) == 'rgb') {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var guts = styleString.substring(start + 1, end).split(',');
+
+ str = '#';
+ for (var i = 0; i < 3; i++) {
+ str += dec2hex[Number(guts[i])];
+ }
+
+ if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
+ alpha = guts[3];
+ }
+ } else {
+ str = styleString;
+ }
+
+ return {color: str, alpha: alpha};
+ }
+
+ function processLineCap(lineCap) {
+ switch (lineCap) {
+ case 'butt':
+ return 'flat';
+ case 'round':
+ return 'round';
+ case 'square':
+ default:
+ return 'square';
+ }
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} surfaceElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(surfaceElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.canvas = surfaceElement;
+
+ var el = surfaceElement.ownerDocument.createElement('div');
+ el.style.width = surfaceElement.clientWidth + 'px';
+ el.style.height = surfaceElement.clientHeight + 'px';
+ el.style.overflow = 'hidden';
+ el.style.position = 'absolute';
+ surfaceElement.appendChild(el);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = this.getCoords_(aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = this.getCoords_(aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = this.getCoords_(aX, aY);
+ var cp1 = this.getCoords_(aCP1x, aCP1y);
+ var cp2 = this.getCoords_(aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = this.getCoords_(aCPx, aCPy);
+ var p = this.getCoords_(aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = this.getCoords_(aX, aY);
+ var pStart = this.getCoords_(xStart, yStart);
+ var pEnd = this.getCoords_(xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = this.getCoords_(dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' <g_vml_:group',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' coordorigin="0,0"' ,
+ ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+ // If filters are necessary (rotation exists), create them
+ // filters are bog-slow, so only create them if abbsolutely necessary
+ // The following check doesn't account for skews (which don't exist
+ // in the canvas spec (yet) anyway.
+
+ if (this.m_[0][0] != 1 || this.m_[0][1]) {
+ var filter = [];
+
+ // Note the 12/21 reversal
+ filter.push('M11=', this.m_[0][0], ',',
+ 'M12=', this.m_[1][0], ',',
+ 'M21=', this.m_[0][1], ',',
+ 'M22=', this.m_[1][1], ',',
+ 'Dx=', mr(d.x / Z), ',',
+ 'Dy=', mr(d.y / Z), '');
+
+ // Bounding box calculation (need to minimize displayed area so that
+ // filters don't waste time on unused pixels.
+ var max = d;
+ var c2 = this.getCoords_(dx + dw, dy);
+ var c3 = this.getCoords_(dx, dy + dh);
+ var c4 = this.getCoords_(dx + dw, dy + dh);
+
+ max.x = m.max(max.x, c2.x, c3.x, c4.x);
+ max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+ vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+ 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+ filter.join(''), ", sizingmethod='clip');")
+ } else {
+ vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+ }
+
+ vmlStr.push(' ">' ,
+ '<g_vml_:image src="', image.src, '"',
+ ' style="width:', Z * dw, 'px;',
+ ' height:', Z * dh, 'px;"',
+ ' cropleft="', sx / w, '"',
+ ' croptop="', sy / h, '"',
+ ' cropright="', (w - sx - sw) / w, '"',
+ ' cropbottom="', (h - sy - sh) / h, '"',
+ ' />',
+ '</g_vml_:group>');
+
+ this.element_.insertAdjacentHTML('BeforeEnd',
+ vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var lineStr = [];
+ var lineOpen = false;
+ var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * this.globalAlpha;
+
+ var W = 10;
+ var H = 10;
+
+ lineStr.push('<g_vml_:shape',
+ ' filled="', !!aFill, '"',
+ ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+ ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+ ' stroked="', !aFill, '"',
+ ' path="');
+
+ var newSeq = false;
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var i = 0; i < this.currentPath_.length; i++) {
+ var p = this.currentPath_[i];
+ var c;
+
+ switch (p.type) {
+ case 'moveTo':
+ c = p;
+ lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'lineTo':
+ lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'close':
+ lineStr.push(' x ');
+ p = null;
+ break;
+ case 'bezierCurveTo':
+ lineStr.push(' c ',
+ mr(p.cp1x), ',', mr(p.cp1y), ',',
+ mr(p.cp2x), ',', mr(p.cp2y), ',',
+ mr(p.x), ',', mr(p.y));
+ break;
+ case 'at':
+ case 'wa':
+ lineStr.push(' ', p.type, ' ',
+ mr(p.x - this.arcScaleX_ * p.radius), ',',
+ mr(p.y - this.arcScaleY_ * p.radius), ' ',
+ mr(p.x + this.arcScaleX_ * p.radius), ',',
+ mr(p.y + this.arcScaleY_ * p.radius), ' ',
+ mr(p.xStart), ',', mr(p.yStart), ' ',
+ mr(p.xEnd), ',', mr(p.yEnd));
+ break;
+ }
+
+
+ // TODO: Following is broken for curves due to
+ // move to proper paths.
+
+ // Figure out dimensions so we can do gradient fills
+ // properly
+ if (p) {
+ if (min.x == null || p.x < min.x) {
+ min.x = p.x;
+ }
+ if (max.x == null || p.x > max.x) {
+ max.x = p.x;
+ }
+ if (min.y == null || p.y < min.y) {
+ min.y = p.y;
+ }
+ if (max.y == null || p.y > max.y) {
+ max.y = p.y;
+ }
+ }
+ }
+ lineStr.push(' ">');
+
+ if (!aFill) {
+ var lineWidth = this.lineScale_ * this.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity, '"',
+ ' joinstyle="', this.lineJoin, '"',
+ ' miterlimit="', this.miterLimit, '"',
+ ' endcap="', processLineCap(this.lineCap), '"',
+ ' weight="', lineWidth, 'px"',
+ ' color="', color, '" />'
+ );
+ } else if (typeof this.fillStyle == 'object') {
+ var fillStyle = this.fillStyle;
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / this.arcScaleX_;
+ var y0 = fillStyle.y0_ / this.arcScaleY_;
+ var x1 = fillStyle.x1_ / this.arcScaleX_;
+ var y1 = fillStyle.y1_ / this.arcScaleY_;
+ var p0 = this.getCoords_(x0, y0);
+ var p1 = this.getCoords_(x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= this.arcScaleX_ * Z;
+ height /= this.arcScaleY_ * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * this.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+ ' method="none" focus="100%"',
+ ' color="', color1, '"',
+ ' color2="', color2, '"',
+ ' colors="', colors.join(','), '"',
+ ' opacity="', opacity2, '"',
+ ' g_o_:opacity2="', opacity1, '"',
+ ' angle="', angle, '"',
+ ' focusposition="', focus.x, ',', focus.y, '" />');
+ } else {
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+ '" />');
+ }
+
+ lineStr.push('</g_vml_:shape>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ }
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ /**
+ * @private
+ */
+ contextPrototype.getCoords_ = function(aX, aY) {
+ var m = this.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ }
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ };
+
+ function matrixIsFinite(m) {
+ for (var j = 0; j < 3; j++) {
+ for (var k = 0; k < 2; k++) {
+ if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function() {
+ return new CanvasPattern_;
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_() {}
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
diff --git a/old/js/fabric.js b/old/js/fabric.js
new file mode 100644
index 0000000..e438f56
--- /dev/null
+++ b/old/js/fabric.js
@@ -0,0 +1,13962 @@
+/* build: `node build.js modules=ALL` */
+/*! Fabric.js Copyright 2008-2012, Printio (Juriy Zaytsev, Maxim Chernyak) */
+
+var fabric = fabric || { version: "0.9.13" };
+
+if (typeof exports != 'undefined') {
+ exports.fabric = fabric;
+}
+
+if (typeof document != 'undefined' && typeof window != 'undefined') {
+ fabric.document = document;
+ fabric.window = window;
+}
+else {
+ // assume we're running under node.js when document/window are not present
+ fabric.document = require("jsdom").jsdom("<!DOCTYPE html><html><head></head><body></body></html>");
+ fabric.window = fabric.document.createWindow();
+}
+
+/**
+ * True when in environment that supports touch events
+ * @property isTouchSupported
+ * @type boolean
+ */
+fabric.isTouchSupported = "ontouchstart" in fabric.document.documentElement;
+
+/**
+ * True when in environment that's probably Node.js
+ * @property isLikelyNode
+ * @type boolean
+ */
+fabric.isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined';
+/*!
+ * Copyright (c) 2009 Simo Kinnunen.
+ * Licensed under the MIT license.
+ */
+
+var Cufon = (function() {
+
+ var api = function() {
+ return api.replace.apply(null, arguments);
+ };
+
+ var DOM = api.DOM = {
+
+ ready: (function() {
+
+ var complete = false, readyStatus = { loaded: 1, complete: 1 };
+
+ var queue = [], perform = function() {
+ if (complete) return;
+ complete = true;
+ for (var fn; fn = queue.shift(); fn());
+ };
+
+ // Gecko, Opera, WebKit r26101+
+
+ if (fabric.document.addEventListener) {
+ fabric.document.addEventListener('DOMContentLoaded', perform, false);
+ fabric.window.addEventListener('pageshow', perform, false); // For cached Gecko pages
+ }
+
+ // Old WebKit, Internet Explorer
+
+ if (!fabric.window.opera && fabric.document.readyState) (function() {
+ readyStatus[fabric.document.readyState] ? perform() : setTimeout(arguments.callee, 10);
+ })();
+
+ // Internet Explorer
+
+ if (fabric.document.readyState && fabric.document.createStyleSheet) (function() {
+ try {
+ fabric.document.body.doScroll('left');
+ perform();
+ }
+ catch (e) {
+ setTimeout(arguments.callee, 1);
+ }
+ })();
+
+ addEvent(fabric.window, 'load', perform); // Fallback
+
+ return function(listener) {
+ if (!arguments.length) perform();
+ else complete ? listener() : queue.push(listener);
+ };
+
+ })()
+
+ };
+
+ var CSS = api.CSS = {
+
+ Size: function(value, base) {
+
+ this.value = parseFloat(value);
+ this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
+
+ this.convert = function(value) {
+ return value / base * this.value;
+ };
+
+ this.convertFrom = function(value) {
+ return value / this.value * base;
+ };
+
+ this.toString = function() {
+ return this.value + this.unit;
+ };
+
+ },
+
+ getStyle: function(el) {
+ return new Style(el.style);
+ /*
+ var view = document.defaultView;
+ if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
+ if (el.currentStyle) return new Style(el.currentStyle);
+ return new Style(el.style);
+ */
+ },
+
+ quotedList: cached(function(value) {
+ // doesn't work properly with empty quoted strings (""), but
+ // it's not worth the extra code.
+ var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
+ while (match = re.exec(value)) list.push(match[3] || match[1]);
+ return list;
+ }),
+
+ ready: (function() {
+
+ var complete = false;
+
+ var queue = [], perform = function() {
+ complete = true;
+ for (var fn; fn = queue.shift(); fn());
+ };
+
+ // Safari 2 does not include <style> elements in document.styleSheets.
+ // Safari 2 also does not support Object.prototype.propertyIsEnumerable.
+
+ var styleElements = Object.prototype.propertyIsEnumerable ? elementsByTagName('style') : { length: 0 };
+ var linkElements = elementsByTagName('link');
+
+ DOM.ready(function() {
+ // These checks are actually only needed for WebKit-based browsers, but don't really hurt other browsers.
+ var linkStyles = 0, link;
+ for (var i = 0, l = linkElements.length; link = linkElements[i], i < l; ++i) {
+ // WebKit does not load alternate stylesheets.
+ if (!link.disabled && link.rel.toLowerCase() == 'stylesheet') ++linkStyles;
+ }
+ if (fabric.document.styleSheets.length >= styleElements.length + linkStyles) perform();
+ else setTimeout(arguments.callee, 10);
+ });
+
+ return function(listener) {
+ if (complete) listener();
+ else queue.push(listener);
+ };
+
+ })(),
+
+ supports: function(property, value) {
+ var checker = fabric.document.createElement('span').style;
+ if (checker[property] === undefined) return false;
+ checker[property] = value;
+ return checker[property] === value;
+ },
+
+ textAlign: function(word, style, position, wordCount) {
+ if (style.get('textAlign') == 'right') {
+ if (position > 0) word = ' ' + word;
+ }
+ else if (position < wordCount - 1) word += ' ';
+ return word;
+ },
+
+ textDecoration: function(el, style) {
+ if (!style) style = this.getStyle(el);
+ var types = {
+ underline: null,
+ overline: null,
+ 'line-through': null
+ };
+ for (var search = el; search.parentNode && search.parentNode.nodeType == 1; ) {
+ var foundAll = true;
+ for (var type in types) {
+ if (types[type]) continue;
+ if (style.get('textDecoration').indexOf(type) != -1) types[type] = style.get('color');
+ foundAll = false;
+ }
+ if (foundAll) break; // this is rather unlikely to happen
+ style = this.getStyle(search = search.parentNode);
+ }
+ return types;
+ },
+
+ textShadow: cached(function(value) {
+ if (value == 'none') return null;
+ var shadows = [], currentShadow = {}, result, offCount = 0;
+ var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig;
+ while (result = re.exec(value)) {
+ if (result[0] == ',') {
+ shadows.push(currentShadow);
+ currentShadow = {}, offCount = 0;
+ }
+ else if (result[1]) {
+ currentShadow.color = result[1];
+ }
+ else {
+ currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2];
+ }
+ }
+ shadows.push(currentShadow);
+ return shadows;
+ }),
+
+ color: cached(function(value) {
+ var parsed = {};
+ parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) {
+ parsed.opacity = parseFloat($2);
+ return 'rgb(' + $1 + ')';
+ });
+ return parsed;
+ }),
+
+ textTransform: function(text, style) {
+ return text[{
+ uppercase: 'toUpperCase',
+ lowercase: 'toLowerCase'
+ }[style.get('textTransform')] || 'toString']();
+ }
+
+ };
+
+ function Font(data) {
+
+ var face = this.face = data.face;
+ this.glyphs = data.glyphs;
+ this.w = data.w;
+ this.baseSize = parseInt(face['units-per-em'], 10);
+
+ this.family = face['font-family'].toLowerCase();
+ this.weight = face['font-weight'];
+ this.style = face['font-style'] || 'normal';
+
+ this.viewBox = (function () {
+ var parts = face.bbox.split(/\s+/);
+ var box = {
+ minX: parseInt(parts[0], 10),
+ minY: parseInt(parts[1], 10),
+ maxX: parseInt(parts[2], 10),
+ maxY: parseInt(parts[3], 10)
+ };
+ box.width = box.maxX - box.minX,
+ box.height = box.maxY - box.minY;
+ box.toString = function() {
+ return [ this.minX, this.minY, this.width, this.height ].join(' ');
+ };
+ return box;
+ })();
+
+ this.ascent = -parseInt(face.ascent, 10);
+ this.descent = -parseInt(face.descent, 10);
+
+ this.height = -this.ascent + this.descent;
+
+ }
+
+ function FontFamily() {
+
+ var styles = {}, mapping = {
+ oblique: 'italic',
+ italic: 'oblique'
+ };
+
+ this.add = function(font) {
+ (styles[font.style] || (styles[font.style] = {}))[font.weight] = font;
+ };
+
+ this.get = function(style, weight) {
+ var weights = styles[style] || styles[mapping[style]]
+ || styles.normal || styles.italic || styles.oblique;
+ if (!weights) return null;
+ // we don't have to worry about "bolder" and "lighter"
+ // because IE's currentStyle returns a numeric value for it,
+ // and other browsers use the computed value anyway
+ weight = {
+ normal: 400,
+ bold: 700
+ }[weight] || parseInt(weight, 10);
+ if (weights[weight]) return weights[weight];
+ // http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight
+ // Gecko uses x99/x01 for lighter/bolder
+ var up = {
+ 1: 1,
+ 99: 0
+ }[weight % 100], alts = [], min, max;
+ if (up === undefined) up = weight > 400;
+ if (weight == 500) weight = 400;
+ for (var alt in weights) {
+ alt = parseInt(alt, 10);
+ if (!min || alt < min) min = alt;
+ if (!max || alt > max) max = alt;
+ alts.push(alt);
+ }
+ if (weight < min) weight = min;
+ if (weight > max) weight = max;
+ alts.sort(function(a, b) {
+ return (up
+ ? (a > weight && b > weight) ? a < b : a > b
+ : (a < weight && b < weight) ? a > b : a < b) ? -1 : 1;
+ });
+ return weights[alts[0]];
+ };
+
+ }
+
+ function HoverHandler() {
+
+ function contains(node, anotherNode) {
+ if (node.contains) return node.contains(anotherNode);
+ return node.compareDocumentPosition(anotherNode) & 16;
+ }
+
+ function onOverOut(e) {
+ var related = e.relatedTarget;
+ if (!related || contains(this, related)) return;
+ trigger(this);
+ }
+
+ function onEnterLeave(e) {
+ trigger(this);
+ }
+
+ function trigger(el) {
+ // A timeout is needed so that the event can actually "happen"
+ // before replace is triggered. This ensures that styles are up
+ // to date.
+ setTimeout(function() {
+ api.replace(el, sharedStorage.get(el).options, true);
+ }, 10);
+ }
+
+ this.attach = function(el) {
+ if (el.onmouseenter === undefined) {
+ addEvent(el, 'mouseover', onOverOut);
+ addEvent(el, 'mouseout', onOverOut);
+ }
+ else {
+ addEvent(el, 'mouseenter', onEnterLeave);
+ addEvent(el, 'mouseleave', onEnterLeave);
+ }
+ };
+
+ }
+
+ function Storage() {
+
+ var map = {}, at = 0;
+
+ function identify(el) {
+ return el.cufid || (el.cufid = ++at);
+ }
+
+ this.get = function(el) {
+ var id = identify(el);
+ return map[id] || (map[id] = {});
+ };
+
+ }
+
+ function Style(style) {
+
+ var custom = {}, sizes = {};
+
+ this.get = function(property) {
+ return custom[property] != undefined ? custom[property] : style[property];
+ };
+
+ this.getSize = function(property, base) {
+ return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base));
+ };
+
+ this.extend = function(styles) {
+ for (var property in styles) custom[property] = styles[property];
+ return this;
+ };
+
+ }
+
+ function addEvent(el, type, listener) {
+ if (el.addEventListener) {
+ el.addEventListener(type, listener, false);
+ }
+ else if (el.attachEvent) {
+ el.attachEvent('on' + type, function() {
+ return listener.call(el, fabric.window.event);
+ });
+ }
+ }
+
+ function attach(el, options) {
+ var storage = sharedStorage.get(el);
+ if (storage.options) return el;
+ if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) {
+ hoverHandler.attach(el);
+ }
+ storage.options = options;
+ return el;
+ }
+
+ function cached(fun) {
+ var cache = {};
+ return function(key) {
+ if (!cache.hasOwnProperty(key)) cache[key] = fun.apply(null, arguments);
+ return cache[key];
+ };
+ }
+
+ function getFont(el, style) {
+ if (!style) style = CSS.getStyle(el);
+ var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
+ for (var i = 0, l = families.length; i < l; ++i) {
+ family = families[i];
+ if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight'));
+ }
+ return null;
+ }
+
+ function elementsByTagName(query) {
+ return fabric.document.getElementsByTagName(query);
+ }
+
+ function merge() {
+ var merged = {}, key;
+ for (var i = 0, l = arguments.length; i < l; ++i) {
+ for (key in arguments[i]) merged[key] = arguments[i][key];
+ }
+ return merged;
+ }
+
+ function process(font, text, style, options, node, el) {
+
+ var separate = options.separate;
+ if (separate == 'none') return engines[options.engine].apply(null, arguments);
+ var fragment = fabric.document.createDocumentFragment(), processed;
+ var parts = text.split(separators[separate]), needsAligning = (separate == 'words');
+ if (needsAligning && HAS_BROKEN_REGEXP) {
+ // @todo figure out a better way to do this
+ if (/^\s/.test(text)) parts.unshift('');
+ if (/\s$/.test(text)) parts.push('');
+ }
+ for (var i = 0, l = parts.length; i < l; ++i) {
+ processed = engines[options.engine](font,
+ needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i],
+ style, options, node, el, i < l - 1);
+ if (processed) fragment.appendChild(processed);
+ }
+ return fragment;
+ }
+
+ function replaceElement(el, options) {
+ var font, style, nextNode, redraw;
+ for (var node = attach(el, options).firstChild; node; node = nextNode) {
+ nextNode = node.nextSibling;
+ redraw = false;
+ if (node.nodeType == 1) {
+ if (!node.firstChild) continue;
+ if (!/cufon/.test(node.className)) {
+ arguments.callee(node, options);
+ continue;
+ }
+ else redraw = true;
+ }
+ if (!style) style = CSS.getStyle(el).extend(options);
+ if (!font) font = getFont(el, style);
+
+ if (!font) continue;
+ if (redraw) {
+ engines[options.engine](font, null, style, options, node, el);
+ continue;
+ }
+ var text = node.data;
+ //for some reason, the carriage return is not stripped by IE but "\n" is, so let's keep \r as a new line marker...
+ if (typeof G_vmlCanvasManager != 'undefined') {
+ text = text.replace(/\r/g, "\n");
+ }
+ if (text === '') continue;
+ var processed = process(font, text, style, options, node, el);
+ if (processed) node.parentNode.replaceChild(processed, node);
+ else node.parentNode.removeChild(node);
+ }
+ }
+
+ var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;
+
+ var sharedStorage = new Storage();
+ var hoverHandler = new HoverHandler();
+ var replaceHistory = [];
+
+ var engines = {}, fonts = {}, defaultOptions = {
+ engine: null,
+ //fontScale: 1,
+ //fontScaling: false,
+ hover: false,
+ hoverables: {
+ a: true
+ },
+ printable: true,
+ //rotation: 0,
+ //selectable: false,
+ selector: (
+ fabric.window.Sizzle
+ || (fabric.window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
+ || (fabric.window.dojo && dojo.query)
+ || (fabric.window.$$ && function(query) { return $$(query); })
+ || (fabric.window.$ && function(query) { return $(query); })
+ || (fabric.document.querySelectorAll && function(query) { return fabric.document.querySelectorAll(query); })
+ || elementsByTagName
+ ),
+ separate: 'words', // 'none' and 'characters' are also accepted
+ textShadow: 'none'
+ };
+
+ var separators = {
+ words: /\s+/,
+ characters: ''
+ };
+
+ api.now = function() {
+ DOM.ready();
+ return api;
+ };
+
+ api.refresh = function() {
+ var currentHistory = replaceHistory.splice(0, replaceHistory.length);
+ for (var i = 0, l = currentHistory.length; i < l; ++i) {
+ api.replace.apply(null, currentHistory[i]);
+ }
+ return api;
+ };
+
+ api.registerEngine = function(id, engine) {
+ if (!engine) return api;
+ engines[id] = engine;
+ return api.set('engine', id);
+ };
+
+ api.registerFont = function(data) {
+ var font = new Font(data), family = font.family;
+ if (!fonts[family]) fonts[family] = new FontFamily();
+ fonts[family].add(font);
+ return api.set('fontFamily', '"' + family + '"');
+ };
+
+ api.replace = function(elements, options, ignoreHistory) {
+ options = merge(defaultOptions, options);
+ if (!options.engine) return api; // there's no browser support so we'll just stop here
+ if (typeof options.textShadow == 'string' && options.textShadow)
+ options.textShadow = CSS.textShadow(options.textShadow);
+ if (!ignoreHistory) replaceHistory.push(arguments);
+ if (elements.nodeType || typeof elements == 'string') elements = [ elements ];
+ CSS.ready(function() {
+ for (var i = 0, l = elements.length; i < l; ++i) {
+ var el = elements[i];
+ if (typeof el == 'string') api.replace(options.selector(el), options, true);
+ else replaceElement(el, options);
+ }
+ });
+ return api;
+ };
+
+ api.replaceElement = function(el, options) {
+ options = merge(defaultOptions, options);
+ if (typeof options.textShadow == 'string' && options.textShadow)
+ options.textShadow = CSS.textShadow(options.textShadow);
+ return replaceElement(el, options);
+ };
+
+ // ==>
+ api.engines = engines;
+ api.fonts = fonts;
+ api.getOptions = function() {
+ return merge(defaultOptions);
+ }
+ // <==
+
+ api.set = function(option, value) {
+ defaultOptions[option] = value;
+ return api;
+ };
+
+ return api;
+
+})();
+
+Cufon.registerEngine('canvas', (function() {
+
+ // Safari 2 doesn't support .apply() on native methods
+ var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block');
+
+ // Firefox 2 w/ non-strict doctype (almost standards mode)
+ var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (fabric.document.compatMode == 'BackCompat' || /frameset|transitional/i.test(fabric.document.doctype.publicId));
+
+ var styleSheet = fabric.document.createElement('style');
+ styleSheet.type = 'text/css';
+
+ var textNode = fabric.document.createTextNode(
+ '.cufon-canvas{text-indent:0}' +
+ '@media screen,projection{' +
+ '.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle' +
+ (HAS_BROKEN_LINEHEIGHT
+ ? ''
+ : ';font-size:1px;line-height:1px') +
+ '}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}' +
+ (HAS_INLINE_BLOCK
+ ? '.cufon-canvas canvas{position:relative}'
+ : '.cufon-canvas canvas{position:absolute}') +
+ '}' +
+ '@media print{' +
+ '.cufon-canvas{padding:0 !important}' +
+ '.cufon-canvas canvas{display:none}' +
+ '.cufon-canvas .cufon-alt{display:inline}' +
+ '}'
+ )
+
+ try {
+ styleSheet.appendChild(textNode);
+ } catch(e) {
+ //IE8- can't do this...
+ styleSheet.setAttribute("type", "text/css");
+ styleSheet.styleSheet.cssText = textNode.data;
+ }
+ fabric.document.getElementsByTagName('head')[0].appendChild(styleSheet);
+
+ function generateFromVML(path, context) {
+ var atX = 0, atY = 0;
+ var code = [], re = /([mrvxe])([^a-z]*)/g, match;
+ generate: for (var i = 0; match = re.exec(path); ++i) {
+ var c = match[2].split(',');
+ switch (match[1]) {
+ case 'v':
+ code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] };
+ break;
+ case 'r':
+ code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] };
+ break;
+ case 'm':
+ code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] };
+ break;
+ case 'x':
+ code[i] = { m: 'closePath', a: [] };
+ break;
+ case 'e':
+ break generate;
+ }
+ context[code[i].m].apply(context, code[i].a);
+ }
+ return code;
+ }
+
+ function interpret(code, context) {
+ for (var i = 0, l = code.length; i < l; ++i) {
+ var line = code[i];
+ context[line.m].apply(context, line.a);
+ }
+ }
+
+ return function(font, text, style, options, node, el) {
+
+ var redraw = (text === null);
+
+ var viewBox = font.viewBox;
+
+ var size = style.getSize('fontSize', font.baseSize);
+
+ var letterSpacing = style.get('letterSpacing');
+ letterSpacing = (letterSpacing == 'normal') ? 0 : size.convertFrom(parseInt(letterSpacing, 10));
+
+ var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0;
+ var shadows = options.textShadow, shadowOffsets = [];
+
+ Cufon.textOptions.shadowOffsets = [ ];
+ Cufon.textOptions.shadows = null;
+
+ if (shadows) {
+ Cufon.textOptions.shadows = shadows;
+ for (var i = 0, l = shadows.length; i < l; ++i) {
+ var shadow = shadows[i];
+ var x = size.convertFrom(parseFloat(shadow.offX));
+ var y = size.convertFrom(parseFloat(shadow.offY));
+ shadowOffsets[i] = [ x, y ];
+ //if (y < expandTop) expandTop = y;
+ //if (x > expandRight) expandRight = x;
+ //if (y > expandBottom) expandBottom = y;
+ //if (x < expandLeft) expandLeft = x;
+ }
+ }
+
+ var chars = Cufon.CSS.textTransform(redraw ? node.alt : text, style).split('');
+
+ var width = 0, lastWidth = null;
+
+ var maxWidth = 0, lines = 1, lineWidths = [ ];
+ for (var i = 0, l = chars.length; i < l; ++i) {
+ if (chars[i] === '\n') {
+ lines++;
+ if (width > maxWidth) {
+ maxWidth = width;
+ }
+ lineWidths.push(width);
+ width = 0;
+ continue;
+ }
+ var glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+ width += lastWidth = Number(glyph.w || font.w) + letterSpacing;
+ }
+ lineWidths.push(width);
+
+ width = Math.max(maxWidth, width);
+
+ var lineOffsets = [ ];
+ for (var i = lineWidths.length; i--; ) {
+ lineOffsets[i] = width - lineWidths[i];
+ }
+
+ if (lastWidth === null) return null; // there's nothing to render
+
+ expandRight += (viewBox.width - lastWidth);
+ expandLeft += viewBox.minX;
+
+ var wrapper, canvas;
+
+ if (redraw) {
+ wrapper = node;
+ canvas = node.firstChild;
+ }
+ else {
+ wrapper = fabric.document.createElement('span');
+ wrapper.className = 'cufon cufon-canvas';
+ wrapper.alt = text;
+
+ canvas = fabric.document.createElement('canvas');
+ wrapper.appendChild(canvas);
+
+ if (options.printable) {
+ var print = fabric.document.createElement('span');
+ print.className = 'cufon-alt';
+ print.appendChild(fabric.document.createTextNode(text));
+ wrapper.appendChild(print);
+ }
+ }
+
+ var wStyle = wrapper.style;
+ var cStyle = canvas.style || { };
+
+ var height = size.convert(viewBox.height - expandTop + expandBottom);
+ var roundedHeight = Math.ceil(height);
+ var roundingFactor = roundedHeight / height;
+
+ canvas.width = Math.ceil(size.convert(width + expandRight - expandLeft) * roundingFactor);
+ canvas.height = roundedHeight;
+
+ expandTop += viewBox.minY;
+
+ cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px';
+ cStyle.left = Math.round(size.convert(expandLeft)) + 'px';
+
+ var _width = Math.ceil(size.convert(width * roundingFactor));
+ var wrapperWidth = _width + 'px';
+ var _height = size.convert(font.height);
+ var totalLineHeight = (options.lineHeight - 1) * size.convert(-font.ascent / 5) * (lines - 1);
+
+ Cufon.textOptions.width = _width;
+ Cufon.textOptions.height = (_height * lines) + totalLineHeight;
+ Cufon.textOptions.lines = lines;
+ Cufon.textOptions.totalLineHeight = totalLineHeight;
+
+ if (HAS_INLINE_BLOCK) {
+ wStyle.width = wrapperWidth;
+ wStyle.height = _height + 'px';
+ }
+ else {
+ wStyle.paddingLeft = wrapperWidth;
+ wStyle.paddingBottom = (_height - 1) + 'px';
+ }
+
+ var g = Cufon.textOptions.context || canvas.getContext('2d'),
+ scale = roundedHeight / viewBox.height;
+
+ Cufon.textOptions.fontAscent = font.ascent * scale;
+ Cufon.textOptions.boundaries = null;
+
+ for (var offsets = Cufon.textOptions.shadowOffsets, i = shadowOffsets.length; i--; ) {
+ offsets[i] = [ shadowOffsets[i][0] * scale, shadowOffsets[i][1] * scale ];
+ }
+
+ g.save();
+ g.scale(scale, scale);
+
+ g.translate(
+ // we're at the center of an object and need to jump to the top left corner
+ // where first character is to be drawn
+ -expandLeft - ((1/scale * canvas.width) / 2) + (Cufon.fonts[font.family].offsetLeft || 0),
+ -expandTop - ((Cufon.textOptions.height / scale) / 2) + (Cufon.fonts[font.family].offsetTop || 0)
+ );
+
+ g.lineWidth = font.face['underline-thickness'];
+
+ g.save();
+
+ function line(y, color) {
+ g.strokeStyle = color;
+
+ g.beginPath();
+
+ g.moveTo(0, y);
+ g.lineTo(width, y);
+
+ g.stroke();
+ }
+
+ var textDecoration = Cufon.getTextDecoration(options),
+ isItalic = options.fontStyle === 'italic';
+
+ function renderBackground() {
+ g.save();
+
+ g.fillStyle = options.backgroundColor;
+
+ var left = 0, lineNum = 0, boundaries = [{ left: 0 }];
+
+ if (options.textAlign === 'right') {
+ g.translate(lineOffsets[lineNum], 0);
+ boundaries[0].left = lineOffsets[lineNum] * scale;
+ }
+ else if (options.textAlign === 'center') {
+ g.translate(lineOffsets[lineNum] / 2, 0);
+ boundaries[0].left = lineOffsets[lineNum] / 2 * scale;
+ }
+
+ for (var i = 0, l = chars.length; i < l; ++i) {
+ if (chars[i] === '\n') {
+
+ lineNum++;
+
+ var topOffset = -font.ascent - ((font.ascent / 5) * options.lineHeight);
+ var boundary = boundaries[boundaries.length - 1];
+ var nextBoundary = { left: 0 };
+
+ boundary.width = left * scale;
+ boundary.height = (-font.ascent + font.descent) * scale;
+
+ if (options.textAlign === 'right') {
+ g.translate(-width, topOffset);
+ g.translate(lineOffsets[lineNum], 0);
+ nextBoundary.left = lineOffsets[lineNum] * scale;
+ }
+ else if (options.textAlign === 'center') {
+ // offset to the start of text in previous line AND half of its offset
+ // (essentially moving caret to the left edge of bounding box)
+ g.translate(-left - (lineOffsets[lineNum - 1] / 2), topOffset);
+ g.translate(lineOffsets[lineNum] / 2, 0);
+ nextBoundary.left = lineOffsets[lineNum] / 2 * scale;
+ }
+ else {
+ g.translate(-left, topOffset);
+ }
+
+ /* push next boundary (for the next line) */
+ boundaries.push(nextBoundary);
+
+ left = 0;
+
+ continue;
+ }
+ var glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+
+ var charWidth = Number(glyph.w || font.w) + letterSpacing;
+
+ // only draw background when there's some kind of value
+ if (options.backgroundColor) {
+ g.save();
+ g.translate(0, font.ascent);
+ g.fillRect(0, 0, charWidth + 10, -font.ascent + font.descent);
+ g.restore();
+ }
+
+ g.translate(charWidth, 0);
+ left += charWidth;
+
+ if (i == l-1) {
+ boundaries[boundaries.length - 1].width = left * scale;
+ boundaries[boundaries.length - 1].height = (-font.ascent + font.descent) * scale;
+ }
+ }
+ g.restore();
+
+ Cufon.textOptions.boundaries = boundaries;
+ }
+
+ function renderText(color) {
+ g.fillStyle = color || Cufon.textOptions.color || style.get('color');
+
+ var left = 0, lineNum = 0;
+
+ if (options.textAlign === 'right') {
+ g.translate(lineOffsets[lineNum], 0);
+ }
+ else if (options.textAlign === 'center') {
+ g.translate(lineOffsets[lineNum] / 2, 0);
+ }
+
+ for (var i = 0, l = chars.length; i < l; ++i) {
+ if (chars[i] === '\n') {
+
+ lineNum++;
+
+ var topOffset = -font.ascent - ((font.ascent / 5) * options.lineHeight);
+
+ if (options.textAlign === 'right') {
+ g.translate(-width, topOffset);
+ g.translate(lineOffsets[lineNum], 0);
+ }
+ else if (options.textAlign === 'center') {
+ // offset to the start of text in previous line AND half of its offset
+ // (essentially moving caret to the left edge of bounding box)
+ g.translate(-left - (lineOffsets[lineNum - 1] / 2), topOffset);
+ g.translate(lineOffsets[lineNum] / 2, 0);
+ }
+ else {
+ g.translate(-left, topOffset);
+ }
+
+ left = 0;
+
+ continue;
+ }
+ var glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+
+ var charWidth = Number(glyph.w || font.w) + letterSpacing;
+
+ if (textDecoration) {
+ g.save();
+ g.strokeStyle = g.fillStyle;
+
+ // add 2x more thickness — closer to SVG rendering
+ g.lineWidth += g.lineWidth;
+
+ g.beginPath();
+ if (textDecoration.underline) {
+ g.moveTo(0, -font.face['underline-position'] + 0.5);
+ g.lineTo(charWidth, -font.face['underline-position'] + 0.5);
+ }
+ if (textDecoration.overline) {
+ g.moveTo(0, font.ascent + 0.5);
+ g.lineTo(charWidth, font.ascent + 0.5);
+ }
+ if (textDecoration['line-through']) {
+ g.moveTo(0, -font.descent + 0.5);
+ g.lineTo(charWidth, -font.descent + 0.5);
+ }
+ g.stroke();
+ g.restore();
+ }
+
+ if (isItalic) {
+ g.save();
+ g.transform(1, 0, -0.25, 1, 0, 0);
+ }
+
+ g.beginPath();
+ if (glyph.d) {
+ if (glyph.code) interpret(glyph.code, g);
+ else glyph.code = generateFromVML('m' + glyph.d, g);
+ }
+
+ g.fill();
+
+ if (options.strokeStyle) {
+ g.closePath();
+ g.save();
+ g.lineWidth = options.strokeWidth;
+ g.strokeStyle = options.strokeStyle;
+ g.stroke();
+ g.restore();
+ }
+
+ if (isItalic) {
+ g.restore();
+ }
+
+ g.translate(charWidth, 0);
+ left += charWidth;
+ }
+ }
+
+ g.save();
+ renderBackground();
+ if (shadows) {
+ for (var i = 0, l = shadows.length; i < l; ++i) {
+ var shadow = shadows[i];
+ g.save();
+ g.translate.apply(g, shadowOffsets[i]);
+ renderText(shadow.color);
+ g.restore();
+ }
+ }
+ renderText();
+ g.restore();
+ g.restore();
+ g.restore();
+
+ return wrapper;
+
+ };
+
+})());
+
+Cufon.registerEngine('vml', (function() {
+
+ if (!fabric.document.namespaces) return;
+
+ var canvasEl = fabric.document.createElement('canvas');
+ if (canvasEl && canvasEl.getContext && canvasEl.getContext.apply) return;
+
+ if (fabric.document.namespaces.cvml == null) {
+ fabric.document.namespaces.add('cvml', 'urn:schemas-microsoft-com:vml');
+ }
+
+ var check = fabric.document.createElement('cvml:shape');
+ check.style.behavior = 'url(#default#VML)';
+ if (!check.coordsize) return; // VML isn't supported
+ check = null;
+
+ fabric.document.write('<style type="text/css">' +
+ '.cufon-vml-canvas{text-indent:0}' +
+ '@media screen{' +
+ 'cvml\\:shape,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute}' +
+ '.cufon-vml-canvas{position:absolute;text-align:left}' +
+ '.cufon-vml{display:inline-block;position:relative;vertical-align:middle}' +
+ '.cufon-vml .cufon-alt{position:absolute;left:-10000in;font-size:1px}' +
+ 'a .cufon-vml{cursor:pointer}' +
+ '}' +
+ '@media print{' +
+ '.cufon-vml *{display:none}' +
+ '.cufon-vml .cufon-alt{display:inline}' +
+ '}' +
+ '</style>');
+
+ function getFontSizeInPixels(el, value) {
+ return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value);
+ }
+
+ // Original by Dead Edwards.
+ // Combined with getFontSizeInPixels it also works with relative units.
+ function getSizeInPixels(el, value) {
+ if (/px$/i.test(value)) return parseFloat(value);
+ var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
+ el.runtimeStyle.left = el.currentStyle.left;
+ el.style.left = value;
+ var result = el.style.pixelLeft;
+ el.style.left = style;
+ el.runtimeStyle.left = runtimeStyle;
+ return result;
+ }
+
+ return function(font, text, style, options, node, el, hasNext) {
+ var redraw = (text === null);
+
+ if (redraw) text = node.alt;
+
+ // @todo word-spacing, text-decoration
+
+ var viewBox = font.viewBox;
+
+ var size = style.computedFontSize ||
+ (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
+
+ var letterSpacing = style.computedLSpacing;
+
+ if (letterSpacing == undefined) {
+ letterSpacing = style.get('letterSpacing');
+ style.computedLSpacing = letterSpacing =
+ (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing));
+ }
+
+ var wrapper, canvas;
+
+ if (redraw) {
+ wrapper = node;
+ canvas = node.firstChild;
+ }
+ else {
+ wrapper = fabric.document.createElement('span');
+ wrapper.className = 'cufon cufon-vml';
+ wrapper.alt = text;
+
+ canvas = fabric.document.createElement('span');
+ canvas.className = 'cufon-vml-canvas';
+ wrapper.appendChild(canvas);
+
+ if (options.printable) {
+ var print = fabric.document.createElement('span');
+ print.className = 'cufon-alt';
+ print.appendChild(fabric.document.createTextNode(text));
+ wrapper.appendChild(print);
+ }
+
+ // ie6, for some reason, has trouble rendering the last VML element in the document.
+ // we can work around this by injecting a dummy element where needed.
+ // @todo find a better solution
+ if (!hasNext) wrapper.appendChild(fabric.document.createElement('cvml:shape'));
+ }
+
+ var wStyle = wrapper.style;
+ var cStyle = canvas.style;
+
+ var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
+ var roundingFactor = roundedHeight / height;
+ var minX = viewBox.minX, minY = viewBox.minY;
+
+ cStyle.height = roundedHeight;
+ cStyle.top = Math.round(size.convert(minY - font.ascent));
+ cStyle.left = Math.round(size.convert(minX));
+
+ wStyle.height = size.convert(font.height) + 'px';
+
+ var textDecoration = Cufon.getTextDecoration(options);
+
+ var color = style.get('color');
+
+ var chars = Cufon.CSS.textTransform(text, style).split('');
+
+ var width = 0, offsetX = 0, advance = null;
+
+ var glyph, shape, shadows = options.textShadow;
+
+ // pre-calculate width
+ for (var i = 0, k = 0, l = chars.length; i < l; ++i) {
+ glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing;
+ }
+
+ if (advance === null) return null;
+
+ var fullWidth = -minX + width + (viewBox.width - advance);
+
+ var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth);
+
+ var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
+ var stretch = 'r' + coordSize + 'nsnf';
+
+ for (i = 0; i < l; ++i) {
+
+ glyph = font.glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+
+ if (redraw) {
+ // some glyphs may be missing so we can't use i
+ shape = canvas.childNodes[k];
+ if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow
+ }
+ else {
+ shape = fabric.document.createElement('cvml:shape');
+ canvas.appendChild(shape);
+ }
+
+ shape.stroked = 'f';
+ shape.coordsize = coordSize;
+ shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
+ shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
+ shape.fillcolor = color;
+
+ // it's important to not set top/left or IE8 will grind to a halt
+ var sStyle = shape.style;
+ sStyle.width = roundedShapeWidth;
+ sStyle.height = roundedHeight;
+
+ if (shadows) {
+ // due to the limitations of the VML shadow element there
+ // can only be two visible shadows. opacity is shared
+ // for all shadows.
+ var shadow1 = shadows[0], shadow2 = shadows[1];
+ var color1 = Cufon.CSS.color(shadow1.color), color2;
+ var shadow = fabric.document.createElement('cvml:shadow');
+ shadow.on = 't';
+ shadow.color = color1.color;
+ shadow.offset = shadow1.offX + ',' + shadow1.offY;
+ if (shadow2) {
+ color2 = Cufon.CSS.color(shadow2.color);
+ shadow.type = 'double';
+ shadow.color2 = color2.color;
+ shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
+ }
+ shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
+ shape.appendChild(shadow);
+ }
+
+ offsetX += ~~(glyph.w || font.w) + letterSpacing;
+
+ ++k;
+
+ }
+
+ wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0);
+
+ return wrapper;
+
+ };
+
+})());
+
+Cufon.getTextDecoration = function(options) {
+ return {
+ underline: options.textDecoration === 'underline',
+ overline: options.textDecoration === 'overline',
+ 'line-through': options.textDecoration === 'line-through'
+ };
+};
+
+if (typeof exports != 'undefined') {
+ exports.Cufon = Cufon;
+}
+
+/*
+ json2.js
+ 2011-10-19
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON;
+if (!JSON) {
+ JSON = {};
+}
+
+(function () {
+ 'use strict';
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf())
+ ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z'
+ : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string'
+ ? c
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' : '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0
+ ? '[]'
+ : gap
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+ : '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ if (typeof rep[i] === 'string') {
+ k = rep[i];
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0
+ ? '{}'
+ : gap
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+ : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ text = String(text);
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function'
+ ? walk({'': j}, '')
+ : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
+/**
+ * Wrapper around `console.log` (when available)
+ * @method log
+ * @param {Any} Values to log
+ */
+fabric.log = function() { };
+
+/**
+ * Wrapper around `console.warn` (when available)
+ * @method warn
+ * @param {Any} Values to log as a warning
+ */
+fabric.warn = function() { };
+
+if (typeof console !== 'undefined') {
+ if (typeof console.log !== 'undefined' && console.log.apply) {
+ fabric.log = function() {
+ return console.log.apply(console, arguments);
+ };
+ }
+ if (typeof console.warn !== 'undefined' && console.warn.apply) {
+ fabric.warn = function() {
+ return console.warn.apply(console, arguments);
+ };
+ }
+}
+
+/**
+ * @namespace
+ */
+fabric.Observable = {
+
+ /**
+ * Observes specified event
+ * @method observe
+ * @depracated Since 0.8.34. Use `on` instead.
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ observe: function(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);
+ }
+ },
+
+ /**
+ * Stops event observing for a particular event handler
+ * @method stopObserving
+ * @depracated Since 0.8.34. Use `off` instead.
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ stopObserving: function(eventName, handler) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = { };
+ }
+ if (this.__eventListeners[eventName]) {
+ fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
+ }
+ },
+
+ /**
+ * Fires event with an optional options object
+ * @method fire
+ * @param {String} eventName
+ * @param {Object} [options]
+ */
+ fire: function(eventName, options) {
+ if (!this.__eventListeners) {
+ this.__eventListeners = { }
+ }
+ var listenersForEvent = this.__eventListeners[eventName];
+ if (!listenersForEvent) return;
+ for (var i = 0, len = listenersForEvent.length; i < len; i++) {
+ // avoiding try/catch for perf. reasons
+ listenersForEvent[i](options || { });
+ }
+ }
+};
+
+/**
+ * Alias for observe
+ * @method observe
+ * @memberOf fabric.Observable
+ */
+fabric.Observable.on = fabric.Observable.observe;
+
+/**
+ * Alias for stopObserving
+ * @method off
+ */
+fabric.Observable.off = fabric.Observable.stopObserving;
+(function() {
+
+ /**
+ * @namespace
+ */
+ fabric.util = { };
+
+ /**
+ * Removes value from an array.
+ * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf`
+ * @static
+ * @memberOf fabric.util
+ * @method removeFromArray
+ * @param {Array} array
+ * @param {Any} value
+ * @return {Array} original array
+ */
+ function removeFromArray(array, value) {
+ var idx = array.indexOf(value);
+ if (idx !== -1) {
+ array.splice(idx, 1);
+ }
+ return array;
+ };
+
+ /**
+ * Returns random number between 2 specified ones.
+ * @static
+ * @method getRandomInt
+ * @memberOf fabric.util
+ * @param {Number} min lower limit
+ * @param {Number} max upper limit
+ * @return {Number} random value (between min and max)
+ */
+ function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ var PiBy180 = Math.PI / 180;
+
+ /**
+ * Transforms degrees to radians.
+ * @static
+ * @method degreesToRadians
+ * @memberOf fabric.util
+ * @param {Number} degrees value in degrees
+ * @return {Number} value in radians
+ */
+ function degreesToRadians(degrees) {
+ return degrees * PiBy180;
+ }
+
+ /**
+ * A wrapper around Number#toFixed, which contrary to native method returns number, not string.
+ * @static
+ * @method toFixed
+ * @memberOf fabric.util
+ * @param {Number | String} number number to operate on
+ * @param {Number} fractionDigits number of fraction digits to "leave"
+ * @return {Number}
+ */
+ function toFixed(number, fractionDigits) {
+ return parseFloat(Number(number).toFixed(fractionDigits));
+ }
+
+ /**
+ * Function which always returns `false`.
+ * @static
+ * @method falseFunction
+ * @memberOf fabric.util
+ * @return {Boolean}
+ */
+ function falseFunction() {
+ return false;
+ }
+
+ /**
+ * Changes value from one to another within certain period of time, invoking callbacks as value is being changed.
+ * @method animate
+ * @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
+ */
+ function animate(options) {
+
+ options || (options = { });
+
+ var start = +new Date(),
+ duration = options.duration || 500,
+ finish = start + duration, time, pos,
+ onChange = options.onChange || function() { },
+ abort = options.abort || function() { return false; },
+ easing = options.easing || function(t, b, c, d) {return -c * Math.cos(t/d * (Math.PI/2)) + c + b;},
+ 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() {
+ time = +new Date();
+ currentTime = time > finish ? duration : (time - start);
+ onChange(easing(currentTime, startValue, byValue, duration));
+ if (time > finish || abort()) {
+ options.onComplete && options.onComplete();
+ return;
+ }
+ requestAnimFrame(tick);
+ })();
+ }
+
+ var _requestAnimFrame = fabric.window.requestAnimationFrame ||
+ fabric.window.webkitRequestAnimationFrame ||
+ fabric.window.mozRequestAnimationFrame ||
+ fabric.window.oRequestAnimationFrame ||
+ fabric.window.msRequestAnimationFrame ||
+ function(callback, element) {
+ fabric.window.setTimeout(callback, 1000 / 60);
+ };
+ /**
+ * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+ * @method requestAnimFrame
+ * @memberOf fabric.util
+ * @param {Function} callback Callback to invoke
+ * @param {DOMElement} element optional Element to associate with animation
+ */
+ var requestAnimFrame = function() {
+ return _requestAnimFrame.apply(fabric.window, arguments);
+ };
+
+ /**
+ * Loads image element from given url and passes it to a callback
+ * @method loadImage
+ * @memberOf fabric.util
+ * @param {String} url URL representing an image
+ * @param {Function} callback Callback; invoked with loaded image
+ * @param {Any} context optional Context to invoke callback in
+ */
+ function loadImage(url, callback, context) {
+ if (url) {
+ var img = new Image();
+ /** @ignore */
+ img.onload = function () {
+ callback && callback.call(context, img);
+ img = img.onload = null;
+ };
+ img.src = url;
+ }
+ else {
+ callback && callback.call(context, url);
+ }
+ }
+
+ function enlivenObjects(objects, callback) {
+
+ function getKlass(type) {
+ return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(type))];
+ }
+
+ function onLoaded() {
+ if (++numLoadedObjects === numTotalObjects) {
+ if (callback) {
+ callback(enlivenedObjects);
+ }
+ }
+ }
+
+ var enlivenedObjects = [ ],
+ numLoadedObjects = 0,
+ numTotalObjects = objects.length;
+
+ objects.forEach(function (o, index) {
+ if (!o.type) {
+ return;
+ }
+ var klass = getKlass(o.type);
+ if (klass.async) {
+ klass.fromObject(o, function (o) {
+ enlivenedObjects[index] = o;
+ onLoaded();
+ });
+ }
+ else {
+ enlivenedObjects[index] = klass.fromObject(o);
+ onLoaded();
+ }
+ });
+ }
+
+ function groupSVGElements(elements, options, path) {
+ var object = elements.length > 1
+ ? new fabric.PathGroup(elements, options)
+ : elements[0];
+
+ if (typeof path !== 'undefined') {
+ object.setSourcePath(path);
+ }
+ return object;
+ }
+
+ fabric.util.removeFromArray = removeFromArray;
+ fabric.util.degreesToRadians = degreesToRadians;
+ fabric.util.toFixed = toFixed;
+ fabric.util.getRandomInt = getRandomInt;
+ fabric.util.falseFunction = falseFunction;
+ fabric.util.animate = animate;
+ fabric.util.requestAnimFrame = requestAnimFrame;
+ fabric.util.loadImage = loadImage;
+ fabric.util.enlivenObjects = enlivenObjects;
+ fabric.util.groupSVGElements = groupSVGElements;
+})();
+(function() {
+
+ var slice = Array.prototype.slice;
+
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+ var t = Object(this), len = t.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) { // shortcut for verifying if it's NaN
+ n = 0;
+ }
+ else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ return -1;
+ }
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ fn.call(context, this[i], i, this);
+ }
+ }
+ };
+ }
+
+ if (!Array.prototype.map) {
+ Array.prototype.map = function(fn, context) {
+ var result = [ ];
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ result[i] = fn.call(context, this[i], i, this);
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.every) {
+ Array.prototype.every = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && !fn.call(context, this[i], i, this)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ }
+
+ if (!Array.prototype.some) {
+ Array.prototype.some = function(fn, context) {
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this && fn.call(context, this[i], i, this)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ if (!Array.prototype.filter) {
+ Array.prototype.filter = function(fn, context) {
+ var result = [ ], val;
+ for (var i = 0, len = this.length >>> 0; i < len; i++) {
+ if (i in this) {
+ val = this[i]; // in case fn mutates this
+ if (fn.call(context, val, i, this)) {
+ result.push(val);
+ }
+ }
+ }
+ return result;
+ };
+ }
+
+ if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function(fn /*, initial*/) {
+ var len = this.length >>> 0,
+ i = 0,
+ rv;
+
+ if (arguments.length > 1) {
+ rv = arguments[1];
+ }
+ else {
+ do {
+ if (i in this) {
+ rv = this[i++];
+ break;
+ }
+ // if array contains no values, no initial value to return
+ if (++i >= len) {
+ throw new TypeError();
+ }
+ }
+ while (true);
+ }
+ for (; i < len; i++) {
+ if (i in this) {
+ rv = fn.call(null, rv, this[i], i, this);
+ }
+ }
+ return rv;
+ };
+ }
+
+ /**
+ * Invokes method on all items in a given array
+ * @method invoke
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} method Name of a method to invoke
+ */
+ 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)
+ * @method max
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ */
+ function max(array, byProperty) {
+ if (!array || array.length === 0) return undefined;
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+ if (byProperty) {
+ while (i--) {
+ if (array[i][byProperty] >= result) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (array[i] >= result) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Finds minimum value in array (not necessarily "first" one)
+ * @method min
+ * @memberOf fabric.util.array
+ * @param {Array} array Array to iterate over
+ * @param {String} byProperty
+ */
+ function min(array, byProperty) {
+ if (!array || array.length === 0) return undefined;
+
+ var i = array.length - 1,
+ result = byProperty ? array[i][byProperty] : array[i];
+
+ if (byProperty) {
+ while (i--) {
+ if (array[i][byProperty] < result) {
+ result = array[i][byProperty];
+ }
+ }
+ }
+ else {
+ while (i--) {
+ if (array[i] < result) {
+ result = array[i];
+ }
+ }
+ }
+ return result;
+ }
+
+ /** @namespace */
+ fabric.util.array = {
+ invoke: invoke,
+ min: min,
+ max: max
+ };
+
+})();
+(function(){
+
+ /**
+ * Copies all enumerable properties of one object to another
+ * @memberOf fabric.util.object
+ * @method extend
+ * @param {Object} destination Where to copy to
+ * @param {Object} source Where to copy from
+ */
+ function extend(destination, source) {
+ // JScript DontEnum bug is not taken care of
+ 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
+ * @method clone
+ * @memberOf fabric.util.object
+ * @param {Object} object Object to clone
+ */
+ function clone(object) {
+ return extend({ }, object);
+ }
+
+ /** @namespace fabric.util.object */
+ fabric.util.object = {
+ extend: extend,
+ clone: clone
+ };
+
+})();
+(function() {
+
+if (!String.prototype.trim) {
+ /**
+ * Trims a string (removing whitespace from the beginning and the end)
+ * @method trim
+ * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String/Trim">String#trim on MDN</a>
+ */
+ String.prototype.trim = function () {
+ // this trim is not fully ES3 or ES5 compliant, but it should cover most cases for now
+ return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
+ };
+}
+
+/**
+ * Camelizes a string
+ * @memberOf fabric.util.string
+ * @method camelize
+ * @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
+ * @method capitalize
+ * @param {String} string String to capitalize
+ * @return {String} Capitalized version of a string
+ */
+function capitalize(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+}
+
+function escapeXml(string) {
+ return string.replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&apos;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
+
+/** @namespace */
+fabric.util.string = {
+ camelize: camelize,
+ capitalize: capitalize,
+ escapeXml: escapeXml
+};
+}());
+
+(function() {
+
+ var slice = Array.prototype.slice,
+ apply = Function.prototype.apply,
+ dummy = function() { };
+
+ if (!Function.prototype.bind) {
+ /**
+ * Cross-browser approximation of ES5 Function.prototype.bind (not fully spec conforming)
+ * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">Function#bind on MDN</a>
+ * @param {Object} thisArg Object to bind function to
+ * @param {Any[]} [...] Values to pass to a bound function
+ * @return {Function}
+ */
+ Function.prototype.bind = function(thisArg) {
+ var fn = this, args = slice.call(arguments, 1), bound;
+ if (args.length) {
+ bound = function() {
+ return apply.call(fn, this instanceof dummy ? this : thisArg, args.concat(slice.call(arguments)));
+ };
+ }
+ else {
+ bound = function() {
+ return apply.call(fn, this instanceof dummy ? this : thisArg, arguments);
+ };
+ }
+ dummy.prototype = this.prototype;
+ bound.prototype = new dummy;
+
+ return bound;
+ };
+ }
+
+})();
+(function() {
+
+ var slice = Array.prototype.slice, emptyFunction = function() { };
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
+ /** @ignore */
+ var addMethods = function(klass, source, parent) {
+ for (var property in source) {
+
+ if (property in klass.prototype && typeof klass.prototype[property] == 'function') {
+
+ 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() { };
+
+ /**
+ * Helper for creation of "classes"
+ * @method createClass
+ * @memberOf fabric.util
+ */
+ 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;
+ return klass;
+ }
+
+ fabric.util.createClass = createClass;
+})();
+(function (global) {
+
+ /* EVENT HANDLING */
+
+ function areHostMethods(object) {
+ var methodNames = Array.prototype.slice.call(arguments, 1),
+ t, i, len = methodNames.length;
+ for (i = 0; i < len; i++) {
+ t = typeof object[methodNames[i]];
+ if (!(/^(?:function|object|unknown)$/).test(t)) return false;
+ }
+ return true;
+ }
+ var getUniqueId = (function () {
+ if (typeof fabric.document.documentElement.uniqueID !== 'undefined') {
+ return function (element) {
+ return element.uniqueID;
+ };
+ }
+ var uid = 0;
+ return function (element) {
+ return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++);
+ };
+ })();
+
+ /** @ignore */
+ var getElement, setElement;
+
+ (function () {
+ var elements = { };
+ /** @ignore */
+ getElement = function (uid) {
+ return elements[uid];
+ };
+ /** @ignore */
+ setElement = function (uid, element) {
+ elements[uid] = element;
+ };
+ })();
+
+ function createListener(uid, handler) {
+ return {
+ handler: handler,
+ wrappedHandler: createWrappedHandler(uid, handler)
+ };
+ }
+
+ function createWrappedHandler(uid, handler) {
+ return function (e) {
+ handler.call(getElement(uid), e || fabric.window.event);
+ };
+ }
+
+ function createDispatcher(uid, eventName) {
+ return function (e) {
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ handlersForEvent[i].call(this, e || fabric.window.event);
+ }
+ }
+ };
+ }
+
+ var shouldUseAddListenerRemoveListener = (
+ areHostMethods(fabric.document.documentElement, 'addEventListener', 'removeEventListener') &&
+ areHostMethods(fabric.window, 'addEventListener', 'removeEventListener')),
+
+ shouldUseAttachEventDetachEvent = (
+ areHostMethods(fabric.document.documentElement, 'attachEvent', 'detachEvent') &&
+ areHostMethods(fabric.window, 'attachEvent', 'detachEvent')),
+
+ // IE branch
+ listeners = { },
+
+ // DOM L0 branch
+ handlers = { },
+
+ addListener, removeListener;
+
+ if (shouldUseAddListenerRemoveListener) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ element.addEventListener(eventName, handler, false);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ element.removeEventListener(eventName, handler, false);
+ };
+ }
+
+ else if (shouldUseAttachEventDetachEvent) {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ setElement(uid, element);
+ if (!listeners[uid]) {
+ listeners[uid] = { };
+ }
+ if (!listeners[uid][eventName]) {
+ listeners[uid][eventName] = [ ];
+
+ }
+ var listener = createListener(uid, handler);
+ listeners[uid][eventName].push(listener);
+ element.attachEvent('on' + eventName, listener.wrappedHandler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element), listener;
+ if (listeners[uid] && listeners[uid][eventName]) {
+ for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) {
+ listener = listeners[uid][eventName][i];
+ if (listener && listener.handler === handler) {
+ element.detachEvent('on' + eventName, listener.wrappedHandler);
+ listeners[uid][eventName][i] = null;
+ }
+ }
+ }
+ };
+ }
+ else {
+ /** @ignore */
+ addListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (!handlers[uid]) {
+ handlers[uid] = { };
+ }
+ if (!handlers[uid][eventName]) {
+ handlers[uid][eventName] = [ ];
+ var existingHandler = element['on' + eventName];
+ if (existingHandler) {
+ handlers[uid][eventName].push(existingHandler);
+ }
+ element['on' + eventName] = createDispatcher(uid, eventName);
+ }
+ handlers[uid][eventName].push(handler);
+ };
+ /** @ignore */
+ removeListener = function (element, eventName, handler) {
+ var uid = getUniqueId(element);
+ if (handlers[uid] && handlers[uid][eventName]) {
+ var handlersForEvent = handlers[uid][eventName];
+ for (var i = 0, len = handlersForEvent.length; i < len; i++) {
+ if (handlersForEvent[i] === handler) {
+ handlersForEvent.splice(i, 1);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Adds an event listener to an element
+ * @mthod addListener
+ * @memberOf fabric.util
+ * @function
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.addListener = addListener;
+
+ /**
+ * Removes an event listener from an element
+ * @mthod removeListener
+ * @memberOf fabric.util
+ * @function
+ * @param {HTMLElement} element
+ * @param {String} eventName
+ * @param {Function} handler
+ */
+ fabric.util.removeListener = removeListener;
+
+ /**
+ * Cross-browser wrapper for getting event's coordinates
+ * @method getPointer
+ * @memberOf fabric.util
+ * @param {Event} event
+ */
+ function getPointer(event) {
+ // TODO (kangax): this method needs fixing
+ return { x: pointerX(event), y: pointerY(event) };
+ }
+
+ function pointerX(event) {
+ var docElement = fabric.document.documentElement,
+ body = fabric.document.body || { scrollLeft: 0 };
+
+ // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element)
+ // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]]
+ // need to investigate later
+ return event.pageX || ((typeof event.clientX != 'unknown' ? event.clientX : 0) +
+ (docElement.scrollLeft || body.scrollLeft) -
+ (docElement.clientLeft || 0));
+ }
+
+ function pointerY(event) {
+ var docElement = fabric.document.documentElement,
+ body = fabric.document.body || { scrollTop: 0 };
+
+ return event.pageY || ((typeof event.clientY != 'unknown' ? event.clientY : 0) +
+ (docElement.scrollTop || body.scrollTop) -
+ (docElement.clientTop || 0));
+ }
+
+ if (fabric.isTouchSupported) {
+ pointerX = function(event) {
+ return event.touches && event.touches[0] && event.touches[0].pageX || event.clientX;
+ };
+ pointerY = function(event) {
+ return event.touches && event.touches[0] && event.touches[0].pageY || event.clientY;
+ };
+ }
+
+ fabric.util.getPointer = getPointer;
+
+ fabric.util.object.extend(fabric.util, fabric.Observable);
+
+})(this);
+(function () {
+
+ /**
+ * Cross-browser wrapper for setting element's style
+ * @method setStyle
+ * @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, match;
+ 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',
+ view = fabric.document.defaultView,
+ supportsGCS = view && typeof view.getComputedStyle !== 'undefined',
+ 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)
+ * @method getById
+ * @memberOf fabric.util
+ * @param {String|HTMLElement} id
+ * @return {HTMLElement|null}
+ */
+ function getById(id) {
+ return typeof id === 'string' ? fabric.document.getElementById(id) : id;
+ }
+
+ /**
+ * Converts an array-like object (e.g. arguments or NodeList) to an array
+ * @method toArray
+ * @memberOf fabric.util
+ * @param {Object} arrayLike
+ * @return {Array}
+ */
+ function toArray(arrayLike) {
+ return _slice.call(arrayLike, 0);
+ }
+
+ try {
+ var 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
+ * @method makeElement
+ * @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
+ * @method addClass
+ * @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.className + ' ').indexOf(' ' + className + ' ') === -1) {
+ element.className += (element.className ? ' ' : '') + className;
+ }
+ }
+
+ /**
+ * Wraps element with another element
+ * @method wrapElement
+ * @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 offset for a given element
+ * @method getElementOffset
+ * @function
+ * @memberOf fabric.util
+ * @param {HTMLElement} element Element to get offset for
+ * @return {Object} Object with "left" and "top" properties
+ */
+ function getElementOffset(element) {
+ // TODO (kangax): need to fix this method
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ }
+ while (element);
+ return ({ left: valueL, top: valueT });
+ }
+
+ (function () {
+ var style = fabric.document.documentElement.style;
+
+ var selectProp = 'userSelect' in style
+ ? 'userSelect'
+ : 'MozUserSelect' in style
+ ? 'MozUserSelect'
+ : 'WebkitUserSelect' in style
+ ? 'WebkitUserSelect'
+ : 'KhtmlUserSelect' in style
+ ? 'KhtmlUserSelect'
+ : '';
+
+ /**
+ * Makes element unselectable
+ * @method makeElementUnselectable
+ * @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
+ * @method makeElementSelectable
+ * @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() {
+
+ /**
+ * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
+ * @method getScript
+ * @memberOf fabric.util
+ * @param {String} url URL of a script to load
+ * @param {Function} callback Callback to execute when script is finished loading
+ */
+ function getScript(url, callback) {
+ var headEl = fabric.document.getElementsByTagName("head")[0],
+ scriptEl = fabric.document.createElement('script'),
+ loading = true;
+
+ scriptEl.type = 'text/javascript';
+ scriptEl.setAttribute('runat', 'server');
+
+ /** @ignore */
+ scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
+ if (loading) {
+ if (typeof this.readyState == 'string' &&
+ this.readyState !== 'loaded' &&
+ this.readyState !== 'complete') return;
+ loading = false;
+ callback(e || fabric.window.event);
+ scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
+ }
+ };
+ scriptEl.src = url;
+ headEl.appendChild(scriptEl);
+ // causes issue in Opera
+ // headEl.removeChild(scriptEl);
+ }
+
+ fabric.util.getScript = getScript;
+ })();
+
+ fabric.util.getById = getById;
+ fabric.util.toArray = toArray;
+ fabric.util.makeElement = makeElement;
+ fabric.util.addClass = addClass;
+ fabric.util.wrapElement = wrapElement;
+ fabric.util.getElementOffset = getElementOffset;
+
+})();
+(function(){
+
+ function addParamToUrl(url, param) {
+ return url + (/\?/.test(url) ? '&' : '?') + param;
+ }
+
+ var makeXHR = (function() {
+ var factories = [
+ function() { return new ActiveXObject("Microsoft.XMLHTTP"); },
+ function() { return new ActiveXObject("Msxml2.XMLHTTP"); },
+ function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); },
+ function() { return new XMLHttpRequest(); }
+ ];
+ for (var i = factories.length; i--; ) {
+ try {
+ var req = factories[i]();
+ if (req) {
+ return factories[i];
+ }
+ }
+ catch (err) { }
+ }
+ })();
+
+ function emptyFn() { };
+
+ /**
+ * Cross-browser abstraction for sending XMLHttpRequest
+ * @method request
+ * @memberOf fabric.util
+ * @param {String} url URL to send XMLHttpRequest to
+ * @param {Object} [options] Options object
+ * @param {String} [options.method="GET"]
+ * @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() { },
+ request = makeXHR(),
+ body;
+
+ /** @ignore */
+ request.onreadystatechange = function() {
+ if (request.readyState === 4) {
+ onComplete(request);
+ request.onreadystatechange = emptyFn;
+ }
+ };
+
+ if (method === 'GET') {
+ body = null;
+ if (typeof options.parameters == 'string') {
+ url = addParamToUrl(url, options.parameters);
+ }
+ }
+
+ request.open(method, url, true);
+
+ if (method === 'POST' || method === 'PUT') {
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+ }
+
+ request.send(body);
+ return request;
+ };
+
+ fabric.util.request = request;
+})();
+(function() {
+
+ /**
+ * @method easeInQuad
+ * @memberOf fabric.util.ease
+ */
+ function easeInQuad(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ }
+
+ /**
+ * @method easeOutQuad
+ * @memberOf fabric.util.ease
+ */
+ function easeOutQuad(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ }
+
+ /**
+ * @method easeInOutQuad
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutQuad(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ }
+
+ /**
+ * @method easeInCubic
+ * @memberOf fabric.util.ease
+ */
+ function easeInCubic(t, b, c, d) {
+ return c*(t/=d)*t*t + b;
+ }
+
+ /**
+ * @method easeOutCubic
+ * @memberOf fabric.util.ease
+ */
+ function easeOutCubic(t, b, c, d) {
+ return c*((t=t/d-1)*t*t + 1) + b;
+ }
+
+ /**
+ * @method easeInOutCubic
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutCubic(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t + b;
+ return c/2*((t-=2)*t*t + 2) + b;
+ }
+
+ /**
+ * @method easeInQuart
+ * @memberOf fabric.util.ease
+ */
+ function easeInQuart(t, b, c, d) {
+ return c*(t/=d)*t*t*t + b;
+ }
+
+ /**
+ * @method easeOutQuart
+ * @memberOf fabric.util.ease
+ */
+ function easeOutQuart(t, b, c, d) {
+ return -c * ((t=t/d-1)*t*t*t - 1) + b;
+ }
+
+ /**
+ * @method easeInOutQuart
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutQuart(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
+ return -c/2 * ((t-=2)*t*t*t - 2) + b;
+ }
+
+ /**
+ * @method easeInQuint
+ * @memberOf fabric.util.ease
+ */
+ function easeInQuint(t, b, c, d) {
+ return c*(t/=d)*t*t*t*t + b;
+ }
+
+ /**
+ * @method easeOutQuint
+ * @memberOf fabric.util.ease
+ */
+ function easeOutQuint(t, b, c, d) {
+ return c*((t=t/d-1)*t*t*t*t + 1) + b;
+ }
+
+ /**
+ * @method easeInOutQuint
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutQuint(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
+ return c/2*((t-=2)*t*t*t*t + 2) + b;
+ }
+
+ /**
+ * @method easeInSine
+ * @memberOf fabric.util.ease
+ */
+ function easeInSine(t, b, c, d) {
+ return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
+ }
+
+ /**
+ * @method easeOutSine
+ * @memberOf fabric.util.ease
+ */
+ function easeOutSine(t, b, c, d) {
+ return c * Math.sin(t/d * (Math.PI/2)) + b;
+ }
+
+ /**
+ * @method easeInOutSine
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutSine(t, b, c, d) {
+ return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
+ }
+
+ /**
+ * @method easeInExpo
+ * @memberOf fabric.util.ease
+ */
+ function easeInExpo(t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ }
+
+ /**
+ * @method easeOutExpo
+ * @memberOf fabric.util.ease
+ */
+ function easeOutExpo(t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ }
+
+ /**
+ * @method easeInOutExpo
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutExpo(t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ }
+
+ /**
+ * @method easeInCirc
+ * @memberOf fabric.util.ease
+ */
+ function easeInCirc(t, b, c, d) {
+ return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
+ }
+
+ /**
+ * @method easeOutCirc
+ * @memberOf fabric.util.ease
+ */
+ function easeOutCirc(t, b, c, d) {
+ return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
+ }
+
+ /**
+ * @method easeInOutCirc
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutCirc(t, b, c, d) {
+ if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
+ return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
+ }
+
+ /**
+ * @method easeInElastic
+ * @memberOf fabric.util.ease
+ */
+ function easeInElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+ }
+
+ /**
+ * @method easeOutElastic
+ * @memberOf fabric.util.ease
+ */
+ function easeOutElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
+ }
+
+ /**
+ * @method easeInOutElastic
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutElastic(t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+ return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
+ }
+
+ /**
+ * @method easeInBack
+ * @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;
+ }
+
+ /**
+ * @method easeOutBack
+ * @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;
+ }
+
+ /**
+ * @method easeInOutBack
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutBack(t, b, c, d, s) {
+ if (s == undefined) s = 1.70158;
+ if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
+ return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
+ }
+
+ /**
+ * @method easeInBounce
+ * @memberOf fabric.util.ease
+ */
+ function easeInBounce(t, b, c, d) {
+ return c - easeOutBounce (d-t, 0, c, d) + b;
+ }
+
+ /**
+ * @method easeOutBounce
+ * @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 + .75) + b;
+ } else if (t < (2.5/2.75)) {
+ return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
+ } else {
+ return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
+ }
+ }
+
+ /**
+ * @method easeInOutBounce
+ * @memberOf fabric.util.ease
+ */
+ function easeInOutBounce(t, b, c, d) {
+ if (t < d/2) return easeInBounce (t*2, 0, c, d) * .5 + b;
+ return easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
+ }
+
+ /** @namespace fabric.util.ease */
+ fabric.util.ease = {
+ easeInQuad: easeInQuad,
+ easeOutQuad: easeOutQuad,
+ easeInOutQuad: easeInOutQuad,
+ easeInCubic: easeInCubic,
+ 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,
+ capitalize = fabric.util.string.capitalize,
+ clone = fabric.util.object.clone;
+
+ var attributesMap = {
+ 'cx': 'left',
+ 'x': 'left',
+ 'cy': 'top',
+ 'y': 'top',
+ 'r': 'radius',
+ 'fill-opacity': 'opacity',
+ 'fill-rule': 'fillRule',
+ 'stroke-width': 'strokeWidth',
+ 'transform': 'transformMatrix',
+ 'text-decoration': 'textDecoration',
+ 'font-size': 'fontSize',
+ 'font-weight': 'fontWeight',
+ 'font-style': 'fontStyle',
+ 'font-family': 'fontFamily'
+ };
+
+ function normalizeAttr(attr) {
+ // transform attribute names
+ if (attr in attributesMap) {
+ return attributesMap[attr];
+ }
+ return attr;
+ }
+
+ /**
+ * Returns an object of attributes' name/value, given element and an array of attribute names;
+ * Parses parent "g" nodes recursively upwards.
+ * @static
+ * @memberOf fabric
+ * @method parseAttributes
+ * @param {DOMElement} element Element to parse
+ * @param {Array} attributes Array of attributes to parse
+ * @return {Object} object containing parsed attributes' names/values
+ */
+ function parseAttributes(element, attributes) {
+
+ if (!element) {
+ return;
+ }
+
+ var value,
+ parsed,
+ parentAttributes = { };
+
+ // if there's a parent container (`g` node), parse its attributes recursively upwards
+ if (element.parentNode && /^g$/i.test(element.parentNode.nodeName)) {
+ parentAttributes = fabric.parseAttributes(element.parentNode, attributes);
+ }
+
+ var ownAttributes = attributes.reduce(function(memo, attr) {
+ value = element.getAttribute(attr);
+ parsed = parseFloat(value);
+ if (value) {
+ // "normalize" attribute values
+ if ((attr === 'fill' || attr === 'stroke') && value === 'none') {
+ value = '';
+ }
+ if (attr === 'fill-rule') {
+ value = (value === 'evenodd') ? 'destination-over' : value;
+ }
+ if (attr === 'transform') {
+ value = fabric.parseTransformAttribute(value);
+ }
+ attr = normalizeAttr(attr);
+ memo[attr] = isNaN(parsed) ? value : parsed;
+ }
+ return memo;
+ }, { });
+
+ // add values parsed from style, which take precedence over attributes
+ // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
+
+ ownAttributes = extend(ownAttributes, extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element)));
+ return extend(parentAttributes, ownAttributes);
+ };
+
+ /**
+ * Parses "transform" attribute, returning an array of values
+ * @static
+ * @function
+ * @memberOf fabric
+ * @method parseTransformAttribute
+ * @param attributeValue {String} string containing attribute value
+ * @return {Array} array of 6 elements representing transformation matrix
+ */
+ fabric.parseTransformAttribute = (function() {
+ function rotateMatrix(matrix, args) {
+ var angle = args[0];
+
+ matrix[0] = Math.cos(angle);
+ matrix[1] = Math.sin(angle);
+ matrix[2] = -Math.sin(angle);
+ matrix[3] = Math.cos(angle);
+ }
+
+ function scaleMatrix(matrix, args) {
+ var multiplierX = args[0],
+ multiplierY = (args.length === 2) ? args[1] : args[0];
+
+ matrix[0] = multiplierX;
+ matrix[3] = multiplierY;
+ }
+
+ function skewXMatrix(matrix, args) {
+ matrix[2] = args[0];
+ }
+
+ function skewYMatrix(matrix, args) {
+ matrix[1] = args[0];
+ }
+
+ function translateMatrix(matrix, args) {
+ matrix[4] = args[0];
+ if (args.length === 2) {
+ matrix[5] = args[1];
+ }
+ }
+
+ // identity matrix
+ var iMatrix = [
+ 1, // a
+ 0, // b
+ 0, // c
+ 1, // d
+ 0, // e
+ 0 // f
+ ],
+
+ // == begin transform regexp
+ number = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)',
+ comma_wsp = '(?:\\s+,?\\s*|,\\s*)',
+
+ skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))',
+ skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))',
+ rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + ')' + comma_wsp + '(' + number + '))?\\s*\\))',
+ scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
+ translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + comma_wsp + '(' + number + '))?\\s*\\))',
+
+ matrix = '(?:(matrix)\\s*\\(\\s*' +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' + comma_wsp +
+ '(' + number + ')' +
+ '\\s*\\))',
+
+ transform = '(?:' +
+ matrix + '|' +
+ translate + '|' +
+ scale + '|' +
+ rotate + '|' +
+ skewX + '|' +
+ skewY +
+ ')',
+
+ transforms = '(?:' + transform + '(?:' + comma_wsp + transform + ')*' + ')',
+
+ transform_list = '^\\s*(?:' + transforms + '?)\\s*$',
+
+ // http://www.w3.org/TR/SVG/coords.html#TransformAttribute
+ reTransformList = new RegExp(transform_list),
+ // == end transform regexp
+
+ reTransform = new RegExp(transform);
+
+ return function(attributeValue) {
+
+ // start with identity matrix
+ var matrix = iMatrix.concat();
+
+ // 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) {
+ return (match !== '' && match != null);
+ }),
+ operation = m[1],
+ args = m.slice(2).map(parseFloat);
+
+ switch(operation) {
+ case 'translate':
+ translateMatrix(matrix, args);
+ break;
+ case 'rotate':
+ rotateMatrix(matrix, args);
+ break;
+ case 'scale':
+ scaleMatrix(matrix, args);
+ break;
+ case 'skewX':
+ skewXMatrix(matrix, args);
+ break;
+ case 'skewY':
+ skewYMatrix(matrix, args);
+ break;
+ case 'matrix':
+ matrix = args;
+ break;
+ }
+ })
+ return matrix;
+ }
+ })();
+
+ /**
+ * Parses "points" attribute, returning an array of values
+ * @static
+ * @memberOf fabric
+ * @method parsePointsAttribute
+ * @param points {String} points attribute string
+ * @return {Array} array of points
+ */
+ function parsePointsAttribute(points) {
+
+ // points attribute is required and must not be empty
+ if (!points) return null;
+
+ points = points.trim();
+ var asPairs = points.indexOf(',') > -1;
+
+ points = points.split(/\s+/);
+ var parsedPoints = [ ];
+
+ // points could look like "10,20 30,40" or "10 20 30 40"
+ if (asPairs) {
+ for (var i = 0, len = points.length; i < len; i++) {
+ var pair = points[i].split(',');
+ parsedPoints.push({ x: parseFloat(pair[0]), y: parseFloat(pair[1]) });
+ }
+ }
+ else {
+ for (var 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;
+ };
+
+ /**
+ * Parses "style" attribute, retuning an object with values
+ * @static
+ * @memberOf fabric
+ * @method parseStyleAttribute
+ * @param {SVGElement} element Element to parse
+ * @return {Object} Objects with values parsed from style attribute of an element
+ */
+ function parseStyleAttribute(element) {
+ var oStyle = { },
+ style = element.getAttribute('style');
+ if (style) {
+ if (typeof style == 'string') {
+ style = style.replace(/;$/, '').split(';').forEach(function (current) {
+ var attr = current.split(':');
+ oStyle[normalizeAttr(attr[0].trim().toLowerCase())] = attr[1].trim();
+ });
+ } else {
+ for (var prop in style) {
+ if (typeof style[prop] !== 'undefined') {
+ oStyle[normalizeAttr(prop.toLowerCase())] = style[prop];
+ }
+ }
+ }
+ }
+ return oStyle;
+ };
+
+ function resolveGradients(instances) {
+ for (var i = instances.length; i--; ) {
+ var instanceFillValue = instances[i].get('fill');
+
+ if (/^url\(/.test(instanceFillValue)) {
+
+ var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1);
+
+ if (fabric.gradientDefs[gradientId]) {
+ instances[i].set('fill',
+ fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], instances[i]));
+ }
+ }
+ }
+ }
+
+ /**
+ * Transforms an array of svg elements to corresponding fabric.* instances
+ * @static
+ * @memberOf fabric
+ * @method parseElements
+ * @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.
+ */
+ function parseElements(elements, callback, options, reviver) {
+ var instances = Array(elements.length), i = elements.length;
+
+ function checkIfDone() {
+ if (--i === 0) {
+ instances = instances.filter(function(el) {
+ return el != null;
+ });
+ resolveGradients(instances);
+ callback(instances);
+ }
+ }
+
+ for (var index = 0, el, len = elements.length; index < len; index++) {
+ el = elements[index];
+ var klass = fabric[capitalize(el.tagName)];
+ if (klass && klass.fromElement) {
+ try {
+ if (klass.async) {
+ klass.fromElement(el, (function(index, el) {
+ return function(obj) {
+ reviver && reviver(el, obj);
+ instances.splice(index, 0, obj);
+ checkIfDone();
+ };
+ })(index), options);
+ }
+ else {
+ var obj = klass.fromElement(el, options);
+ reviver && reviver(el, obj);
+ instances.splice(index, 0, obj);
+ checkIfDone();
+ }
+ }
+ catch(e) {
+ fabric.log(e.message || e);
+ }
+ }
+ else {
+ checkIfDone();
+ }
+ }
+ };
+
+ /**
+ * Returns CSS rules for a given SVG document
+ * @static
+ * @function
+ * @memberOf fabric
+ * @method getCSSRules
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} CSS rules of this document
+ */
+ function getCSSRules(doc) {
+ var styles = doc.getElementsByTagName('style'),
+ allRules = { },
+ rules;
+
+ // very crude parsing of style contents
+ for (var i = 0, len = styles.length; i < len; i++) {
+ var styleContents = styles[0].textContent;
+
+ // remove comments
+ styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, '');
+
+ rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
+ rules = rules.map(function(rule) { return rule.trim() });
+
+ rules.forEach(function(rule) {
+ var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/),
+ rule = match[1],
+ declaration = match[2].trim(),
+ propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/);
+
+ if (!allRules[rule]) {
+ allRules[rule] = { };
+ }
+
+ for (var i = 0, len = propertyValuePairs.length; i < len; i++) {
+ var pair = propertyValuePairs[i].split(/\s*:\s*/),
+ property = pair[0],
+ value = pair[1];
+
+ allRules[rule][property] = value;
+ }
+ });
+ }
+
+ return allRules;
+ }
+
+ function getGlobalStylesForElement(element) {
+ var nodeName = element.nodeName,
+ className = element.getAttribute('class'),
+ id = element.getAttribute('id'),
+ styles = { };
+
+ for (var rule in fabric.cssRules) {
+ var ruleMatchesElement = (className && new RegExp('^\\.' + className).test(rule)) ||
+ (id && new RegExp('^#' + id).test(rule)) ||
+ (new RegExp('^' + nodeName).test(rule));
+
+ if (ruleMatchesElement) {
+ for (var property in fabric.cssRules[rule]) {
+ styles[property] = fabric.cssRules[rule][property];
+ }
+ }
+ }
+
+ return styles;
+ }
+
+ /**
+ * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback
+ * @static
+ * @function
+ * @memberOf fabric
+ * @method parseSVGDocument
+ * @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.
+ */
+ fabric.parseSVGDocument = (function() {
+
+ var reAllowedSVGTagNames = /^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/;
+
+ // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute
+ // \d doesn't quite cut it (as we need to match an actual float number)
+
+ // matches, e.g.: +14.56e-12, etc.
+ var reNum = '(?:[-+]?\\d+(?:\\.\\d+)?(?:e[-+]?\\d+)?)';
+
+ var reViewBoxAttrValue = new RegExp(
+ '^' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*,?' +
+ '\\s*(' + reNum + '+)\\s*' +
+ '$'
+ );
+
+ function hasAncestorWithNodeName(element, nodeName) {
+ while (element && (element = element.parentNode)) {
+ if (nodeName.test(element.nodeName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return function(doc, callback, reviver) {
+ if (!doc) return;
+
+ var startTime = new Date(),
+ descendants = fabric.util.toArray(doc.getElementsByTagName('*'));
+
+ if (descendants.length === 0) {
+ // 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 (var i = 0, len = descendants.length; i < len; i++) {
+ arr[i] = descendants[i];
+ }
+ descendants = arr;
+ }
+
+ var elements = descendants.filter(function(el) {
+ return reAllowedSVGTagNames.test(el.tagName) &&
+ !hasAncestorWithNodeName(el, /^(?:pattern|defs)$/); // http://www.w3.org/TR/SVG/struct.html#DefsElement
+ });
+
+ if (!elements || (elements && !elements.length)) return;
+
+ var viewBoxAttr = doc.getAttribute('viewBox'),
+ widthAttr = doc.getAttribute('width'),
+ heightAttr = doc.getAttribute('height'),
+ width = null,
+ height = null,
+ minX,
+ minY;
+
+ if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) {
+ minX = parseInt(viewBoxAttr[1], 10);
+ minY = parseInt(viewBoxAttr[2], 10);
+ width = parseInt(viewBoxAttr[3], 10);
+ height = parseInt(viewBoxAttr[4], 10);
+ }
+
+ // values of width/height attributes overwrite those extracted from viewbox attribute
+ width = widthAttr ? parseFloat(widthAttr) : width;
+ height = heightAttr ? parseFloat(heightAttr) : height;
+
+ var options = {
+ width: width,
+ height: height
+ };
+
+ fabric.gradientDefs = fabric.getGradientDefs(doc);
+ fabric.cssRules = getCSSRules(doc);
+
+ // Precedence of rules: style > class > attribute
+
+ fabric.parseElements(elements, function(instances) {
+ fabric.documentParsingTime = new Date() - startTime;
+ if (callback) {
+ callback(instances, options);
+ }
+ }, clone(options), reviver);
+ };
+ })();
+
+ /**
+ * Used for caching SVG documents (loaded via `fabric.Canvas#loadSVGFromURL`)
+ * @property
+ * @namespace
+ */
+ var svgCache = {
+
+ /**
+ * @method has
+ * @param {String} name
+ * @param {Function} callback
+ */
+ has: function (name, callback) {
+ callback(false);
+ },
+
+ /**
+ * @method get
+ * @param {String} url
+ * @param {Function} callback
+ */
+ get: function (url, callback) {
+ /* NOOP */
+ },
+
+ /**
+ * @method set
+ * @param {String} url
+ * @param {Object} object
+ */
+ set: function (url, object) {
+ /* NOOP */
+ }
+ };
+
+ /**
+ * Takes url corresponding to an SVG document, and parses it into a set of fabric objects
+ * @method loadSVGFromURL
+ * @param {String} url
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ function loadSVGFromURL(url, callback, reviver) {
+
+ url = url.replace(/^\n\s*/, '').trim();
+
+ svgCache.has(url, function (hasUrl) {
+ if (hasUrl) {
+ svgCache.get(url, function (value) {
+ var enlivedRecord = _enlivenCachedObject(value);
+ callback(enlivedRecord.objects, enlivedRecord.options);
+ });
+ }
+ else {
+ new fabric.util.request(url, {
+ method: 'get',
+ onComplete: onComplete
+ });
+ }
+ });
+
+ function onComplete(r) {
+
+ var xml = r.responseXML;
+ if (!xml.documentElement && fabric.window.ActiveXObject && r.responseText) {
+ xml = new ActiveXObject('Microsoft.XMLDOM');
+ xml.async = 'false';
+ //IE chokes on DOCTYPE
+ xml.loadXML(r.responseText.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,''));
+ }
+ if (!xml.documentElement) return;
+
+ fabric.parseSVGDocument(xml.documentElement, function (results, options) {
+ svgCache.set(url, {
+ objects: fabric.util.array.invoke(results, 'toObject'),
+ options: options
+ });
+ callback(results, options);
+ }, reviver);
+ }
+ }
+
+ /**
+ * @method _enlivenCachedObject
+ */
+ function _enlivenCachedObject(cachedObject) {
+
+ var objects = cachedObject.objects,
+ options = cachedObject.options;
+
+ objects = objects.map(function (o) {
+ return fabric[capitalize(o.type)].fromObject(o);
+ });
+
+ return ({ objects: objects, options: options });
+ }
+
+ /**
+ * Takes string corresponding to an SVG document, and parses it into a set of fabric objects
+ * @method loadSVGFromString
+ * @param {String} string
+ * @param {Function} callback
+ * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created.
+ */
+ function loadSVGFromString(string, callback, reviver) {
+ string = string.trim();
+ var doc;
+ if (typeof DOMParser !== 'undefined') {
+ var parser = new DOMParser();
+ if (parser && parser.parseFromString) {
+ doc = parser.parseFromString(string, 'text/xml');
+ }
+ }
+ else if (fabric.window.ActiveXObject) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ //IE chokes on DOCTYPE
+ doc.loadXML(string.replace(/<!DOCTYPE[\s\S]*?(\[[\s\S]*\])*?>/i,''));
+ }
+
+ fabric.parseSVGDocument(doc.documentElement, function (results, options) {
+ callback(results, options);
+ }, reviver);
+ }
+
+ function createSVGFontFacesMarkup(objects) {
+ var markup = '';
+
+ for (var i = 0, len = objects.length; i < len; i++) {
+ if (objects[i].type !== 'text' || !objects[i].path) continue;
+
+ markup += [
+ '@font-face {',
+ 'font-family: ', objects[i].fontFamily, '; ',
+ 'src: url(\'', objects[i].path, '\')',
+ '}'
+ ].join('');
+ }
+
+ if (markup) {
+ markup = [
+ '<defs>',
+ '<style type="text/css">',
+ '<![CDATA[',
+ markup,
+ ']]>',
+ '</style>',
+ '</defs>'
+ ].join('');
+ }
+
+ return markup;
+ }
+
+ extend(fabric, {
+
+ parseAttributes: parseAttributes,
+ parseElements: parseElements,
+ parseStyleAttribute: parseStyleAttribute,
+ parsePointsAttribute: parsePointsAttribute,
+ getCSSRules: getCSSRules,
+
+ loadSVGFromURL: loadSVGFromURL,
+ loadSVGFromString: loadSVGFromString,
+
+ createSVGFontFacesMarkup: createSVGFontFacesMarkup
+ });
+
+})(typeof exports != 'undefined' ? exports : this);
+
+(function() {
+
+ function getColorStopFromStyle(el) {
+ var style = el.getAttribute('style');
+
+ if (style) {
+ var keyValuePairs = style.split(/\s*;\s*/);
+
+ if (keyValuePairs[keyValuePairs.length-1] === '') {
+ keyValuePairs.pop();
+ }
+
+ for (var i = keyValuePairs.length; i--; ) {
+
+ var split = keyValuePairs[i].split(/\s*:\s*/),
+ key = split[0].trim(),
+ value = split[1].trim();
+
+ if (key === 'stop-color') {
+ return value;
+ }
+ }
+ }
+ }
+
+ /**
+ * @class Object
+ * @memberOf fabric
+ */
+ fabric.Gradient = fabric.util.createClass(/** @scope fabric.Gradient.prototype */ {
+
+ initialize: function(options) {
+
+ options || (options = { });
+
+ this.x1 = options.x1 || 0;
+ this.y1 = options.y1 || 0;
+ this.x2 = options.x2 || 0;
+ this.y2 = options.y2 || 0;
+
+ this.colorStops = options.colorStops;
+ },
+
+ toObject: function() {
+ return {
+ x1: this.x1,
+ x2: this.x2,
+ y1: this.y1,
+ y2: this.y2,
+ colorStops: this.colorStops
+ };
+ },
+
+ toLiveGradient: function(ctx) {
+ var gradient = ctx.createLinearGradient(
+ this.x1, this.y1, this.x2 || ctx.canvas.width, this.y2);
+
+ for (var position in this.colorStops) {
+ var colorValue = this.colorStops[position];
+ gradient.addColorStop(parseFloat(position), colorValue);
+ }
+
+ return gradient;
+ }
+ });
+
+ fabric.util.object.extend(fabric.Gradient, {
+
+ /**
+ * @method fromElement
+ * @static
+ * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement
+ */
+ fromElement: function(el, instance) {
+
+ /**
+ * @example:
+ *
+ * <linearGradient id="grad1">
+ * <stop offset="0%" stop-color="white"/>
+ * <stop offset="100%" stop-color="black"/>
+ * </linearGradient>
+ *
+ * OR
+ *
+ * <linearGradient id="grad1">
+ * <stop offset="0%" style="stop-color:rgb(255,255,255)"/>
+ * <stop offset="100%" style="stop-color:rgb(0,0,0)"/>
+ * </linearGradient>
+ *
+ */
+
+ var colorStopEls = el.getElementsByTagName('stop'),
+ el,
+ offset,
+ colorStops = { },
+ colorStopFromStyle,
+ coords = {
+ x1: el.getAttribute('x1') || 0,
+ y1: el.getAttribute('y1') || 0,
+ x2: el.getAttribute('x2') || '100%',
+ y2: el.getAttribute('y2') || 0
+ };
+
+ for (var i = colorStopEls.length; i--; ) {
+ el = colorStopEls[i];
+ offset = el.getAttribute('offset');
+
+ // convert percents to absolute values
+ offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1);
+ colorStops[offset] = getColorStopFromStyle(el) || el.getAttribute('stop-color');
+ }
+
+ _convertPercentUnitsToValues(instance, coords);
+
+ return new fabric.Gradient({
+ x1: coords.x1,
+ y1: coords.y1,
+ x2: coords.x2,
+ y2: coords.y2,
+ colorStops: colorStops
+ });
+ },
+
+ /**
+ * @method forObject
+ * @static
+ */
+ forObject: function(obj, options) {
+ options || (options = { });
+ _convertPercentUnitsToValues(obj, options);
+ return new fabric.Gradient(options);
+ }
+ });
+
+ function _convertPercentUnitsToValues(object, options) {
+ for (var prop in options) {
+ if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) {
+ var percents = parseFloat(options[prop], 10);
+ if (prop === 'x1' || prop === 'x2') {
+ options[prop] = object.width * percents / 100;
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] = object.height * percents / 100;
+ }
+ }
+ // normalize rendering point (should be from top/left corner rather than center of the shape)
+ if (prop === 'x1' || prop === 'x2') {
+ options[prop] -= object.width / 2;
+ }
+ else if (prop === 'y1' || prop === 'y2') {
+ options[prop] -= object.height / 2;
+ }
+ }
+ }
+
+ /**
+ * Parses an SVG document, returning all of the gradient declarations found in it
+ * @static
+ * @function
+ * @memberOf fabric
+ * @method getGradientDefs
+ * @param {SVGDocument} doc SVG document to parse
+ * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element
+ */
+ function getGradientDefs(doc) {
+ var linearGradientEls = doc.getElementsByTagName('linearGradient'),
+ radialGradientEls = doc.getElementsByTagName('radialGradient'),
+ el,
+ gradientDefs = { };
+
+ for (var i = linearGradientEls.length; i--; ) {
+ el = linearGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ for (var i = radialGradientEls.length; i--; ) {
+ el = radialGradientEls[i];
+ gradientDefs[el.getAttribute('id')] = el;
+ }
+
+ return gradientDefs;
+ }
+
+ fabric.getGradientDefs = getGradientDefs;
+
+})();
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey ([email protected]) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Point) {
+ fabric.warn('fabric.Point is already defined');
+ return;
+ }
+
+ fabric.Point = Point;
+
+ /**
+ * @name Point
+ * @memberOf fabric
+ * @constructor
+ * @param {Number} x
+ * @param {Number} y
+ * @return {fabric.Point} thisArg
+ */
+ function Point(x, y) {
+ if (arguments.length > 0) {
+ this.init(x, y);
+ }
+ }
+
+ Point.prototype = /** @scope fabric.Point.prototype */ {
+
+ constructor: Point,
+
+ /**
+ * @method init
+ * @param {Number} x
+ * @param {Number} y
+ */
+ init: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+
+ /**
+ * @method add
+ * @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);
+ },
+
+ /**
+ * @method addEquals
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ addEquals: function (that) {
+ this.x += that.x;
+ this.y += that.y;
+ return this;
+ },
+
+ /**
+ * @method scalarAdd
+ * @param {Number} scalar
+ * @return {fabric.Point} new Point with added value
+ */
+ scalarAdd: function (scalar) {
+ return new Point(this.x + scalar, this.y + scalar);
+ },
+
+ /**
+ * @method scalarAddEquals
+ * @param {Number} scalar
+ * @param {fabric.Point} thisArg
+ */
+ scalarAddEquals: function (scalar) {
+ this.x += scalar;
+ this.y += scalar;
+ return this;
+ },
+
+ /**
+ * @method subtract
+ * @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);
+ },
+
+ /**
+ * @method subtractEquals
+ * @param {fabric.Point} that
+ * @return {fabric.Point} thisArg
+ */
+ subtractEquals: function (that) {
+ this.x -= that.x;
+ this.y -= that.y;
+ return this;
+ },
+
+ scalarSubtract: function (scalar) {
+ return new Point(this.x - scalar, this.y - scalar);
+ },
+
+ scalarSubtractEquals: function (scalar) {
+ this.x -= scalar;
+ this.y -= scalar;
+ return this;
+ },
+
+ multiply: function (scalar) {
+ return new Point(this.x * scalar, this.y * scalar);
+ },
+
+ multiplyEquals: function (scalar) {
+ this.x *= scalar;
+ this.y *= scalar;
+ return this;
+ },
+
+ divide: function (scalar) {
+ return new Point(this.x / scalar, this.y / scalar);
+ },
+
+ divideEquals: function (scalar) {
+ this.x /= scalar;
+ this.y /= scalar;
+ return this;
+ },
+
+ eq: function (that) {
+ return (this.x == that.x && this.y == that.y);
+ },
+
+ lt: function (that) {
+ return (this.x < that.x && this.y < that.y);
+ },
+
+ lte: function (that) {
+ return (this.x <= that.x && this.y <= that.y);
+ },
+
+ gt: function (that) {
+ return (this.x > that.x && this.y > that.y);
+ },
+
+ gte: function (that) {
+ return (this.x >= that.x && this.y >= that.y);
+ },
+
+ lerp: function (that, t) {
+ return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t);
+ },
+
+ distanceFrom: function (that) {
+ var dx = this.x - that.x,
+ dy = this.y - that.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ min: function (that) {
+ return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y));
+ },
+
+ max: function (that) {
+ return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y));
+ },
+
+ toString: function () {
+ return this.x + "," + this.y;
+ },
+
+ setXY: function (x, y) {
+ this.x = x;
+ this.y = y;
+ },
+
+ setFromPoint: function (that) {
+ this.x = that.x;
+ this.y = that.y;
+ },
+
+ swap: function (that) {
+ var x = this.x,
+ y = this.y;
+ this.x = that.x;
+ this.y = that.y;
+ that.x = x;
+ that.y = y;
+ }
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ /* Adaptation of work of Kevin Lindsey ([email protected]) */
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Intersection) {
+ fabric.warn('fabric.Intersection is already defined');
+ return;
+ }
+
+ /**
+ * @class Intersection
+ * @memberOf fabric
+ */
+ function Intersection(status) {
+ if (arguments.length > 0) {
+ this.init(status);
+ }
+ }
+
+ fabric.Intersection = Intersection;
+
+ fabric.Intersection.prototype = /** @scope fabric.Intersection.prototype */ {
+
+ /**
+ * @method init
+ * @param {String} status
+ */
+ init: function (status) {
+ this.status = status;
+ this.points = [];
+ },
+
+ /**
+ * @method appendPoint
+ * @param {String} status
+ */
+ appendPoint: function (point) {
+ this.points.push(point);
+ },
+
+ /**
+ * @method appendPoints
+ * @param {String} status
+ */
+ appendPoints: function (points) {
+ this.points = this.points.concat(points);
+ }
+ };
+
+ /**
+ * @static
+ * @method intersectLineLine
+ */
+ fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) {
+ var result,
+ ua_t = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x),
+ ub_t = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x),
+ u_b = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y);
+ if (u_b != 0) {
+ var ua = ua_t / u_b,
+ ub = ub_t / u_b;
+ if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
+ result = new Intersection("Intersection");
+ result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y)));
+ }
+ else {
+ result = new Intersection("No Intersection");
+ }
+ }
+ else {
+ if (ua_t == 0 || ub_t == 0) {
+ result = new Intersection("Coincident");
+ }
+ else {
+ result = new Intersection("Parallel");
+ }
+ }
+ return result;
+ };
+
+ /**
+ * @method intersectLinePolygon
+ */
+ fabric.Intersection.intersectLinePolygon = function(a1,a2,points){
+ var result = new Intersection("No Intersection"),
+ length = points.length;
+
+ for (var i = 0; i < length; i++) {
+ var 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;
+ };
+
+ /**
+ * @method intersectPolygonPolygon
+ */
+ fabric.Intersection.intersectPolygonPolygon = function (points1, points2) {
+ var result = new Intersection("No Intersection"),
+ length = points1.length;
+
+ for (var 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;
+ };
+
+ /**
+ * @method intersectPolygonRectangle
+ */
+ 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("No 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;
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Color) {
+ fabric.warn('fabric.Color is already defined.');
+ return;
+ }
+
+ /**
+ * 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 Color
+ * @memberOf fabric
+ * @param {String} color (optional) in hex or rgb(a) format
+ */
+ function Color(color) {
+ if (!color) {
+ this.setSource([0, 0, 0, 1]);
+ }
+ else {
+ this._tryParsingColor(color);
+ }
+ }
+
+ fabric.Color = Color;
+
+ fabric.Color.prototype = /** @scope fabric.Color.prototype */ {
+
+ /**
+ * @private
+ * @method _tryParsingColor
+ */
+ _tryParsingColor: function(color) {
+ var source = Color.sourceFromHex(color);
+ if (!source) {
+ source = Color.sourceFromRgb(color);
+ }
+ if (source) {
+ this.setSource(source);
+ }
+ },
+
+ /**
+ * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @method getSource
+ * @return {Array}
+ */
+ getSource: function() {
+ return this._source;
+ },
+
+ /**
+ * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1])
+ * @method setSource
+ * @param {Array} source
+ */
+ setSource: function(source) {
+ this._source = source;
+ },
+
+ /**
+ * Returns color represenation in RGB format
+ * @method toRgb
+ * @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 represenation in RGBA format
+ * @method toRgba
+ * @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 represenation in HEX format
+ * @method toHex
+ * @return {String} ex: FF5555
+ */
+ toHex: function() {
+ var source = this.getSource();
+
+ var r = source[0].toString(16);
+ r = (r.length == 1) ? ('0' + r) : r;
+
+ var g = source[1].toString(16);
+ g = (g.length == 1) ? ('0' + g) : g;
+
+ var b = source[2].toString(16);
+ b = (b.length == 1) ? ('0' + b) : b;
+
+ return r.toUpperCase() + g.toUpperCase() + b.toUpperCase();
+ },
+
+ /**
+ * Gets value of alpha channel for this color
+ * @method getAlpha
+ * @return {Number} 0-1
+ */
+ getAlpha: function() {
+ return this.getSource()[3];
+ },
+
+ /**
+ * Sets value of alpha channel for this color
+ * @method setAlpha
+ * @param {Number} 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
+ * @method toGrayscale
+ * @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
+ * @method toGrayscale
+ * @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
+ * @method overlayWith
+ * @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();
+
+ for (var 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), rgb(255, 100, 10, 0.5), rgb(1,1,1))
+ * @static
+ * @field
+ */
+ fabric.Color.reRGBa = /^rgba?\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(\d+(?:\.\d+)?))?\)$/;
+
+ /**
+ * Regex matching color in HEX format (ex: #FF5555, 010155, aff)
+ * @static
+ * @field
+ */
+ fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i;
+
+ /**
+ * Returns new color object, when given a color in RGB format
+ * @method fromRgb
+ * @param {String} color ex: rgb(0-255,0-255,0-255)
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgb = function(color) {
+ return Color.fromSource(Color.sourceFromRgb(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format
+ * @method sourceFromRgb
+ * @param {String} color ex: rgb(0-255,0-255,0-255)
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromRgb = function(color) {
+ var match = color.match(Color.reRGBa);
+ if (match) {
+ return [
+ parseInt(match[1], 10),
+ parseInt(match[2], 10),
+ parseInt(match[3], 10),
+ match[4] ? parseFloat(match[4]) : 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given a color in RGBA format
+ * @static
+ * @function
+ * @method fromRgba
+ * @param {String} color
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromRgba = Color.fromRgb;
+
+ /**
+ * Returns new color object, when given a color in HEX format
+ * @static
+ * @method fromHex
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromHex = function(color) {
+ return Color.fromSource(Color.sourceFromHex(color));
+ };
+
+ /**
+ * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format
+ * @static
+ * @method sourceFromHex
+ * @param {String} color ex: FF5555
+ * @return {Array} source
+ */
+ fabric.Color.sourceFromHex = function(color) {
+ if (color.match(Color.reHex)) {
+ var value = color.slice(color.indexOf('#') + 1),
+ isShortNotation = (value.length === 3),
+ 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);
+
+ return [
+ parseInt(r, 16),
+ parseInt(g, 16),
+ parseInt(b, 16),
+ 1
+ ];
+ }
+ };
+
+ /**
+ * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5])
+ * @static
+ * @method fromSource
+ * @return {fabric.Color}
+ */
+ fabric.Color.fromSource = function(source) {
+ var oColor = new Color();
+ oColor.setSource(source);
+ return oColor;
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function (global) {
+
+ "use strict";
+
+ if (fabric.StaticCanvas) {
+ fabric.warn('fabric.StaticCanvas is already defined.');
+ return;
+ }
+
+ // aliases for faster resolution
+ var extend = fabric.util.object.extend,
+ getElementOffset = fabric.util.getElementOffset,
+ removeFromArray = fabric.util.removeFromArray,
+ removeListener = fabric.util.removeListener,
+
+ CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
+
+ /**
+ * @class fabric.StaticCanvas
+ * @constructor
+ * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ */
+ fabric.StaticCanvas = function (el, options) {
+ options || (options = { });
+
+ this._initStatic(el, options);
+ fabric.StaticCanvas.activeInstance = this;
+ };
+
+ extend(fabric.StaticCanvas.prototype, fabric.Observable);
+
+ extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
+
+ /**
+ * Background color of canvas instance
+ * @property
+ * @type String
+ */
+ backgroundColor: 'rgba(0, 0, 0, 0)',
+
+ /**
+ * Background image of canvas instance
+ * Should be set via `setBackgroundImage`
+ * @property
+ * @type String
+ */
+ backgroundImage: '',
+
+ /**
+ * Opacity of the background image of the canvas instance
+ * @property
+ * @type Float
+ */
+ backgroundImageOpacity: 1.0,
+
+ /**
+ * Indicatus whether the background image should be stretched to fit the
+ * dimensions of the canvas instance.
+ * @property
+ * @type Boolean
+ */
+ backgroundImageStretch: true,
+
+ /**
+ * Indicates whether toObject/toDatalessObject should include default values
+ * @property
+ * @type Boolean
+ */
+ includeDefaultValues: true,
+
+ /**
+ * Indicates whether objects' state should be saved
+ * @property
+ * @type Boolean
+ */
+ stateful: true,
+
+ /**
+ * Indicates whether fabric.Canvas#add should also re-render canvas.
+ * Disabling this option could give a great performance boost when adding a lot of objects to canvas at once
+ * (followed by a manual rendering after addition)
+ */
+ renderOnAddition: true,
+
+ /**
+ * Function that determines clipping of entire canvas area
+ * Being passed context as first argument. See clipping canvas area in https://github.com/kangax/fabric.js/wiki/FAQ
+ * @property
+ * @type Function
+ */
+ clipTo: null,
+
+ /**
+ * Indicates whether object controls (borders/corners) are rendered above overlay image
+ * @property
+ * @type Boolean
+ */
+ controlsAboveOverlay: false,
+
+ /**
+ * Callback; invoked right before object is about to be scaled/rotated
+ * @method onBeforeScaleRotate
+ * @param {fabric.Object} target Object that's about to be scaled/rotated
+ */
+ onBeforeScaleRotate: function (target) {
+ /* NOOP */
+ },
+
+ /**
+ * Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
+ * @method onFpsUpdate
+ * @param {Number} fps
+ */
+ onFpsUpdate: null,
+
+ _initStatic: function(el, options) {
+ this._objects = [];
+
+ this._createLowerCanvas(el);
+ this._initOptions(options);
+
+ if (options.overlayImage) {
+ this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
+ }
+ if (options.backgroundImage) {
+ this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
+ }
+ this.calcOffset();
+ },
+
+ /**
+ * Calculates canvas element offset relative to the document
+ * This method is also attached as "resize" event handler of window
+ * @method calcOffset
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ calcOffset: function () {
+ this._offset = getElementOffset(this.lowerCanvasEl);
+ return this;
+ },
+
+ /**
+ * Sets overlay image for this canvas
+ * @method setOverlayImage
+ * @param {String} url url of an image to set overlay to
+ * @param {Function} callback callback to invoke when image is loaded and set as an overlay
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setOverlayImage: function (url, callback) { // TODO (kangax): test callback
+ fabric.util.loadImage(url, function(img) {
+ this.overlayImage = img;
+ callback && callback();
+ }, this);
+ return this;
+ },
+
+ /**
+ * Sets background image for this canvas
+ * @method setBackgroundImage
+ * @param {String} url url of an image to set background to
+ * @param {Function} callback callback to invoke when image is loaded and set as background
+ * @param {Object} options optional options to set for the background image
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setBackgroundImage: function (url, callback, options) {
+ return fabric.util.loadImage(url, function(img) {
+ this.backgroundImage = img;
+ if (options && ('backgroundImageOpacity' in options)) {
+ this.backgroundImageOpacity = options.backgroundImageOpacity;
+ }
+ if (options && ('backgroundImageStretch' in options)) {
+ this.backgroundImageStretch = options.backgroundImageStretch;
+ }
+ callback && callback();
+ }, this);
+ },
+
+ /**
+ * @private
+ * @method _createCanvasElement
+ * @param {Element} element
+ */
+ _createCanvasElement: function() {
+ var element = fabric.document.createElement('canvas');
+ if (!element.style) {
+ element.style = { };
+ }
+ if (!element) {
+ throw CANVAS_INIT_ERROR;
+ }
+ this._initCanvasElement(element);
+ return element;
+ },
+
+ _initCanvasElement: function(element) {
+ if (typeof element.getContext === 'undefined' &&
+ typeof G_vmlCanvasManager !== 'undefined' &&
+ G_vmlCanvasManager.initElement) {
+
+ G_vmlCanvasManager.initElement(element);
+ }
+ if (typeof element.getContext === 'undefined') {
+ throw CANVAS_INIT_ERROR;
+ }
+ },
+
+ /**
+ * @method _initOptions
+ * @param {Object} options
+ */
+ _initOptions: function (options) {
+ for (var prop in options) {
+ this[prop] = options[prop];
+ }
+
+ this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
+ this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
+
+ if (!this.lowerCanvasEl.style) return;
+
+ this.lowerCanvasEl.style.width = this.width + 'px';
+ this.lowerCanvasEl.style.height = this.height + 'px';
+ },
+
+ /**
+ * Creates a secondary canvas
+ * @method _createLowerCanvas
+ */
+ _createLowerCanvas: function (canvasEl) {
+ this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
+ this._initCanvasElement(this.lowerCanvasEl);
+
+ fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
+
+ if (this.interactive) {
+ this._applyCanvasStyle(this.lowerCanvasEl);
+ }
+
+ this.contextContainer = this.lowerCanvasEl.getContext('2d');
+ },
+
+ /**
+ * Returns canvas width
+ * @method getWidth
+ * @return {Number}
+ */
+ getWidth: function () {
+ return this.width;
+ },
+
+ /**
+ * Returns canvas height
+ * @method getHeight
+ * @return {Number}
+ */
+ getHeight: function () {
+ return this.height;
+ },
+
+ /**
+ * Sets width of this canvas instance
+ * @method setWidth
+ * @param {Number} width value to set width to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setWidth: function (value) {
+ return this._setDimension('width', value);
+ },
+
+ /**
+ * Sets height of this canvas instance
+ * @method setHeight
+ * @param {Number} height value to set height to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ setHeight: function (value) {
+ return this._setDimension('height', value);
+ },
+
+ /**
+ * Sets dimensions (width, height) of this canvas instance
+ * @method setDimensions
+ * @param {Object} dimensions
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setDimensions: function(dimensions) {
+ for (var prop in dimensions) {
+ this._setDimension(prop, dimensions[prop]);
+ }
+ return this;
+ },
+
+ /**
+ * Helper for setting width/height
+ * @private
+ * @method _setDimensions
+ * @param {String} prop property (width|height)
+ * @param {Number} value value to set property to
+ * @return {fabric.Canvas} instance
+ * @chainable true
+ */
+ _setDimension: function (prop, value) {
+ this.lowerCanvasEl[prop] = value;
+ this.lowerCanvasEl.style[prop] = value + 'px';
+
+ if (this.upperCanvasEl) {
+ this.upperCanvasEl[prop] = value;
+ this.upperCanvasEl.style[prop] = value + 'px';
+ }
+
+ if (this.wrapperEl) {
+ this.wrapperEl.style[prop] = value + 'px';
+ }
+
+ this[prop] = value;
+
+ this.calcOffset();
+ this.renderAll();
+
+ return this;
+ },
+
+ /**
+ * Returns &lt;canvas> element corresponding to this instance
+ * @method getElement
+ * @return {HTMLCanvasElement}
+ */
+ getElement: function () {
+ return this.lowerCanvasEl;
+ },
+
+ // placeholder
+ getActiveObject: function() {
+ return null;
+ },
+
+ // placeholder
+ getActiveGroup: function() {
+ return null;
+ },
+
+ /**
+ * Given a context, renders an object on that context
+ * @param ctx {Object} context to render object on
+ * @param object {Object} object to render
+ * @private
+ */
+ _draw: function (ctx, object) {
+ if (!object) return;
+
+ if (this.controlsAboveOverlay) {
+ var hasBorders = object.hasBorders, hasCorners = object.hasCorners;
+ object.hasBorders = object.hasCorners = false;
+ object.render(ctx);
+ object.hasBorders = hasBorders;
+ object.hasCorners = hasCorners;
+ }
+ else {
+ object.render(ctx);
+ }
+ },
+
+ /**
+ * Adds objects to canvas, then renders canvas;
+ * Objects should be instances of (or inherit from) fabric.Object
+ * @method add
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ add: function () {
+ this._objects.push.apply(this._objects, arguments);
+ for (var i = arguments.length; i--; ) {
+ this._initObject(arguments[i]);
+ }
+ this.renderOnAddition && this.renderAll();
+ return this;
+ },
+
+ /**
+ * @private
+ * @method _initObject
+ */
+ _initObject: function(obj) {
+ this.stateful && obj.setupState();
+ obj.setCoords();
+ obj.canvas = this;
+ this.fire('object:added', { target: obj });
+ obj.fire('added');
+ },
+
+ /**
+ * Inserts an object to canvas at specified index and renders canvas.
+ * An object should be an instance of (or inherit from) fabric.Object
+ * @method insertAt
+ * @param object {Object} Object to insert
+ * @param index {Number} index to insert object at
+ * @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs
+ * @return {fabric.Canvas} instance
+ */
+ insertAt: function (object, index, nonSplicing) {
+ if (nonSplicing) {
+ this._objects[index] = object;
+ }
+ else {
+ this._objects.splice(index, 0, object);
+ }
+ this._initObject(object);
+ this.renderOnAddition && this.renderAll();
+ return this;
+ },
+
+ /**
+ * Returns an array of objects this instance has
+ * @method getObjects
+ * @return {Array}
+ */
+ getObjects: function () {
+ return this._objects;
+ },
+
+ /**
+ * Clears specified context of canvas element
+ * @method clearContext
+ * @param context {Object} ctx context to clear
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clearContext: function(ctx) {
+ ctx.clearRect(0, 0, this.width, this.height);
+ return this;
+ },
+
+ /**
+ * Returns context of canvas where objects are drawn
+ * @method getContext
+ * @return {CanvasRenderingContext2D}
+ */
+ getContext: function () {
+ return this.contextContainer;
+ },
+
+ /**
+ * Clears all contexts (background, main, top) of an instance
+ * @method clear
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ clear: function () {
+ this._objects.length = 0;
+ this.clearContext(this.contextContainer);
+ if (this.contextTop) {
+ this.clearContext(this.contextTop);
+ }
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Renders both the top canvas and the secondary container canvas.
+ * @method renderAll
+ * @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ renderAll: function (allOnTop) {
+
+ var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
+
+ if (this.contextTop) {
+ this.clearContext(this.contextTop);
+ }
+
+ if (allOnTop === false || (typeof allOnTop === 'undefined')) {
+ this.clearContext(canvasToDrawOn);
+ }
+
+ var length = this._objects.length,
+ activeGroup = this.getActiveGroup(),
+ startTime = new Date();
+
+ if (this.clipTo) {
+ canvasToDrawOn.save();
+ canvasToDrawOn.beginPath();
+ this.clipTo(canvasToDrawOn);
+ canvasToDrawOn.clip();
+ }
+
+ canvasToDrawOn.fillStyle = this.backgroundColor;
+ canvasToDrawOn.fillRect(0, 0, this.width, this.height);
+
+ if (typeof this.backgroundImage == 'object') {
+ canvasToDrawOn.save();
+ canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
+
+ if (this.backgroundImageStretch) {
+ canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
+ }
+ else {
+ canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
+ }
+ canvasToDrawOn.restore();
+ }
+
+ if (length) {
+ for (var i = 0; i < length; ++i) {
+ if (!activeGroup ||
+ (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
+ this._draw(canvasToDrawOn, this._objects[i]);
+ }
+ }
+ }
+
+ if (this.clipTo) {
+ canvasToDrawOn.restore();
+ }
+
+ // delegate rendering to group selection (if one exists)
+ if (activeGroup) {
+ this._draw(this.contextTop, activeGroup);
+ }
+
+ if (this.overlayImage) {
+ this.contextContainer.drawImage(this.overlayImage, 0, 0);
+ }
+
+ if (this.controlsAboveOverlay) {
+ this.drawControls(this.contextContainer);
+ }
+
+ if (this.onFpsUpdate) {
+ var elapsedTime = new Date() - startTime;
+ this.onFpsUpdate(~~(1000 / elapsedTime));
+ }
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * Method to render only the top canvas.
+ * Also used to render the group selection box.
+ * @method renderTop
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ renderTop: function () {
+ this.clearContext(this.contextTop || this.contextContainer);
+
+ if (this.overlayImage) {
+ this.contextContainer.drawImage(this.overlayImage, 0, 0);
+ }
+
+ // we render the top context - last object
+ if (this.selection && this._groupSelector) {
+ this._drawSelection();
+ }
+
+ // delegate rendering to group selection if one exists
+ // used for drawing selection borders/corners
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.render(this.contextTop);
+ }
+
+ this.fire('after:render');
+
+ return this;
+ },
+
+ /**
+ * Draws objects' controls (borders/corners)
+ * @method drawControls
+ * @param {Object} ctx context to render controls on
+ */
+ drawControls: function(ctx) {
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ ctx.save();
+ fabric.Group.prototype.transform.call(activeGroup, ctx);
+ activeGroup.drawBorders(ctx).drawCorners(ctx);
+ ctx.restore();
+ }
+ else {
+ for (var i = 0, len = this._objects.length; i < len; ++i) {
+ if (!this._objects[i].active) continue;
+
+ ctx.save();
+ fabric.Object.prototype.transform.call(this._objects[i], ctx);
+ this._objects[i].drawBorders(ctx).drawCorners(ctx);
+ ctx.restore();
+ }
+ }
+ },
+
+ /**
+ * Exports canvas element to a dataurl image.
+ * @method toDataURL
+ * @param {String} format the format of the output image. Either "jpeg" or "png".
+ * @param {Number} quality quality level (0..1)
+ * @return {String}
+ */
+ toDataURL: function (format, quality) {
+ var canvasEl = this.upperCanvasEl || this.lowerCanvasEl;
+
+ this.renderAll(true);
+ var data = (fabric.StaticCanvas.supports('toDataURLWithQuality'))
+ ? canvasEl.toDataURL('image/' + format, quality)
+ : canvasEl.toDataURL('image/' + format);
+ this.renderAll();
+ return data;
+ },
+
+ /**
+ * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
+ * @method toDataURLWithMultiplier
+ * @param {String} format (png|jpeg)
+ * @param {Number} multiplier
+ * @param {Number} quality (0..1)
+ * @return {String}
+ */
+ toDataURLWithMultiplier: function (format, multiplier, quality) {
+
+ var origWidth = this.getWidth(),
+ origHeight = this.getHeight(),
+ scaledWidth = origWidth * multiplier,
+ scaledHeight = origHeight * multiplier,
+ activeObject = this.getActiveObject(),
+ activeGroup = this.getActiveGroup();
+
+ this.setWidth(scaledWidth).setHeight(scaledHeight);
+ this.contextTop.scale(multiplier, multiplier);
+
+ if (activeGroup) {
+ // not removing group due to complications with restoring it with correct state afterwords
+ this._tempRemoveBordersCornersFromGroup(activeGroup);
+ }
+ else if (activeObject) {
+ this.deactivateAll();
+ }
+
+ // restoring width, height for `renderAll` to draw
+ // background properly (while context is scaled)
+ this.width = origWidth;
+ this.height = origHeight;
+
+ this.renderAll(true);
+
+ var dataURL = this.toDataURL(format, quality);
+
+ this.contextTop.scale(1 / multiplier, 1 / multiplier);
+ this.setWidth(origWidth).setHeight(origHeight);
+
+ if (activeGroup) {
+ this._restoreBordersCornersOnGroup(activeGroup);
+ }
+ else if (activeObject) {
+ this.setActiveObject(activeObject);
+ }
+
+ this.renderAll();
+
+ return dataURL;
+ },
+
+ _tempRemoveBordersCornersFromGroup: function(group) {
+ group.origHideCorners = group.hideCorners;
+ group.origBorderColor = group.borderColor;
+
+ group.hideCorners = true;
+ group.borderColor = 'rgba(0,0,0,0)';
+
+ group.forEachObject(function(o) {
+ o.origBorderColor = o.borderColor;
+ o.borderColor = 'rgba(0,0,0,0)';
+ });
+ },
+ _restoreBordersCornersOnGroup: function(group) {
+ group.hideCorners = group.origHideCorners;
+ group.borderColor = group.origBorderColor;
+
+ group.forEachObject(function(o) {
+ o.borderColor = o.origBorderColor;
+ delete o.origBorderColor;
+ });
+ },
+
+ /**
+ * Returns coordinates of a center of canvas.
+ * Returned value is an object with top and left properties
+ * @method getCenter
+ * @return {Object} object with "top" and "left" number values
+ */
+ getCenter: function () {
+ return {
+ top: this.getHeight() / 2,
+ left: this.getWidth() / 2
+ };
+ },
+
+ /**
+ * Centers object horizontally.
+ * @method centerObjectH
+ * @param {fabric.Object} object Object to center
+ * @return {fabric.Canvas} thisArg
+ */
+ centerObjectH: function (object) {
+ object.set('left', this.getCenter().left);
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically.
+ * @method centerObjectH
+ * @param {fabric.Object} object Object to center
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObjectV: function (object) {
+ object.set('top', this.getCenter().top);
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Centers object vertically and horizontally.
+ * @method centerObject
+ * @param {fabric.Object} object Object to center
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ centerObject: function (object) {
+ return this.centerObjectH(object).centerObjectV(object);
+ },
+
+ /**
+ * Returs dataless JSON representation of canvas
+ * @method toDatalessJSON
+ * @return {String} json string
+ */
+ toDatalessJSON: function () {
+ return this.toDatalessObject();
+ },
+
+ /**
+ * Returns object representation of canvas
+ * @method toObject
+ * @return {Object}
+ */
+ toObject: function () {
+ return this._toObjectMethod('toObject');
+ },
+
+ /**
+ * Returns dataless object representation of canvas
+ * @method toDatalessObject
+ * @return {Object}
+ */
+ toDatalessObject: function () {
+ return this._toObjectMethod('toDatalessObject');
+ },
+
+ /**
+ * @private
+ * @method _toObjectMethod
+ */
+ _toObjectMethod: function (methodName) {
+ var data = {
+ objects: this._objects.map(function (instance) {
+ // TODO (kangax): figure out how to clean this up
+ if (!this.includeDefaultValues) {
+ var originalValue = instance.includeDefaultValues;
+ instance.includeDefaultValues = false;
+ }
+ var object = instance[methodName]();
+ if (!this.includeDefaultValues) {
+ instance.includeDefaultValues = originalValue;
+ }
+ return object;
+ }, this),
+ background: this.backgroundColor
+ };
+ if (this.backgroundImage) {
+ data.backgroundImage = this.backgroundImage.src;
+ data.backgroundImageOpacity = this.backgroundImageOpacity;
+ data.backgroundImageStretch = this.backgroundImageStretch;
+ }
+ return data;
+ },
+
+ /**
+ * Returns SVG representation of canvas
+ * @function
+ * @method toSVG
+ * @return {String}
+ */
+ toSVG: function() {
+ var markup = [
+ '<?xml version="1.0" standalone="no" ?>',
+ '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ',
+ '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
+ '<svg ',
+ 'xmlns="http://www.w3.org/2000/svg" ',
+ 'xmlns:xlink="http://www.w3.org/1999/xlink" ',
+ 'version="1.1" ',
+ 'width="', this.width, '" ',
+ 'height="', this.height, '" ',
+ 'xml:space="preserve">',
+ '<desc>Created with Fabric.js ', fabric.version, '</desc>',
+ fabric.createSVGFontFacesMarkup(this.getObjects())
+ ];
+
+ if (this.backgroundImage) {
+ markup.push(
+ '<image x="0" y="0" ',
+ 'width="', this.width,
+ '" height="', this.height,
+ '" preserveAspectRatio="', (this.backgroundImageStretch ? 'none' : 'defer'),
+ '" xlink:href="', this.backgroundImage.src,
+ '" style="opacity:', this.backgroundImageOpacity,
+ '"></image>'
+ );
+ }
+
+ for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
+ markup.push(objects[i].toSVG());
+ }
+ markup.push('</svg>');
+
+ return markup.join('');
+ },
+
+ /**
+ * Returns true if canvas contains no objects
+ * @method isEmpty
+ * @return {Boolean} true if canvas is empty
+ */
+ isEmpty: function () {
+ return this._objects.length === 0;
+ },
+
+ /**
+ * Removes an object from canvas and returns it
+ * @method remove
+ * @param object {Object} Object to remove
+ * @return {Object} removed object
+ */
+ remove: function (object) {
+ removeFromArray(this._objects, object);
+ if (this.getActiveObject() === object) {
+
+ // removing active object should fire "selection:cleared" events
+ this.fire('before:selection:cleared', { target: object });
+ this.discardActiveObject();
+ this.fire('selection:cleared');
+ }
+ this.renderAll();
+ return object;
+ },
+
+ /**
+ * Moves an object to the bottom of the stack of drawn objects
+ * @method sendToBack
+ * @param object {fabric.Object} Object to send to back
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ sendToBack: function (object) {
+ removeFromArray(this._objects, object);
+ this._objects.unshift(object);
+ return this.renderAll();
+ },
+
+ /**
+ * Moves an object to the top of the stack of drawn objects
+ * @method bringToFront
+ * @param object {fabric.Object} Object to send
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ bringToFront: function (object) {
+ removeFromArray(this._objects, object);
+ this._objects.push(object);
+ return this.renderAll();
+ },
+
+ /**
+ * Moves an object one level down in stack of drawn objects
+ * @method sendBackwards
+ * @param object {fabric.Object} Object to send
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ sendBackwards: function (object) {
+ var idx = this._objects.indexOf(object),
+ nextIntersectingIdx = idx;
+
+ // if object is not on the bottom of stack
+ if (idx !== 0) {
+
+ // traverse down the stack looking for the nearest intersecting object
+ for (var i=idx-1; i>=0; --i) {
+ if (object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i])) {
+ nextIntersectingIdx = i;
+ break;
+ }
+ }
+ removeFromArray(this._objects, object);
+ this._objects.splice(nextIntersectingIdx, 0, object);
+ }
+ return this.renderAll();
+ },
+
+ /**
+ * Moves an object one level up in stack of drawn objects
+ * @method bringForward
+ * @param object {fabric.Object} Object to send
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ bringForward: function (object) {
+ var objects = this.getObjects(),
+ idx = objects.indexOf(object),
+ nextIntersectingIdx = idx;
+
+
+ // if object is not on top of stack (last item in an array)
+ if (idx !== objects.length-1) {
+
+ // traverse up the stack looking for the nearest intersecting object
+ for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
+ if (object.intersectsWithObject(objects[i]) || object.isContainedWithinObject(this._objects[i])) {
+ nextIntersectingIdx = i;
+ break;
+ }
+ }
+ removeFromArray(objects, object);
+ objects.splice(nextIntersectingIdx, 0, object);
+ }
+ this.renderAll();
+ },
+
+ /**
+ * Returns object at specified index
+ * @method item
+ * @param {Number} index
+ * @return {fabric.Object}
+ */
+ item: function (index) {
+ return this.getObjects()[index];
+ },
+
+ /**
+ * Returns number representation of an instance complexity
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function () {
+ return this.getObjects().reduce(function (memo, current) {
+ memo += current.complexity ? current.complexity() : 0;
+ return memo;
+ }, 0);
+ },
+
+ /**
+ * Iterates over all objects, invoking callback for each one of them
+ * @method forEachObject
+ * @return {fabric.Canvas} thisArg
+ */
+ forEachObject: function(callback, context) {
+ var objects = this.getObjects(),
+ i = objects.length;
+ while (i--) {
+ callback.call(context, objects[i], i, objects);
+ }
+ return this;
+ },
+
+ /**
+ * Clears a canvas element and removes all event handlers.
+ * @method dispose
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ dispose: function () {
+ this.clear();
+ if (this.interactive) {
+ removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
+ removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
+ removeListener(fabric.window, 'resize', this._onResize);
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * @method _resizeImageToFit
+ * @param {HTMLImageElement} imgEl
+ */
+ _resizeImageToFit: function (imgEl) {
+
+ var imageWidth = imgEl.width || imgEl.offsetWidth,
+ widthScaleFactor = this.getWidth() / imageWidth;
+
+ // scale image down so that it has original dimensions when printed in large resolution
+ if (imageWidth) {
+ imgEl.width = imageWidth * widthScaleFactor;
+ }
+ }
+ });
+
+ /**
+ * Returns a string representation of an instance
+ * @method toString
+ * @return {String} string representation of an instance
+ */
+ fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
+ return '#<fabric.Canvas (' + this.complexity() + '): '+
+ '{ objects: ' + this.getObjects().length + ' }>';
+ };
+
+ extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ {
+
+ /**
+ * @static
+ * @property EMPTY_JSON
+ * @type String
+ */
+ EMPTY_JSON: '{"objects": [], "background": "white"}',
+
+ /**
+ * Takes <canvas> element and transforms its data in such way that it becomes grayscale
+ * @static
+ * @method toGrayscale
+ * @param {HTMLCanvasElement} canvasEl
+ */
+ toGrayscale: function (canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = imageData.width,
+ jLen = imageData.height,
+ index, average, i, j;
+
+ for (i = 0; i < iLen; i++) {
+ for (j = 0; j < jLen; j++) {
+
+ index = (i * 4) * jLen + (j * 4);
+ average = (data[index] + data[index + 1] + data[index + 2]) / 3;
+
+ data[index] = average;
+ data[index + 1] = average;
+ data[index + 2] = average;
+ }
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * Provides a way to check support of some of the canvas methods
+ * (either those of HTMLCanvasElement itself, or rendering context)
+ *
+ * @method supports
+ * @param methodName {String} Method to check support for;
+ * Could be one of "getImageData", "toDataURL" or "toDataURLWithQuality"
+ * @return {Boolean | null} `true` if method is supported (or at least exists),
+ * `null` if canvas element or context can not be initialized
+ */
+ supports: function (methodName) {
+ var el = fabric.document.createElement('canvas');
+
+ if (typeof G_vmlCanvasManager !== 'undefined') {
+ G_vmlCanvasManager.initElement(el);
+ }
+ if (!el || !el.getContext) {
+ return null;
+ }
+
+ var ctx = el.getContext('2d');
+ if (!ctx) {
+ return null;
+ }
+
+ switch (methodName) {
+
+ case 'getImageData':
+ return typeof ctx.getImageData !== 'undefined';
+
+ case 'toDataURL':
+ return typeof el.toDataURL !== 'undefined';
+
+ case 'toDataURLWithQuality':
+ try {
+ el.toDataURL('image/jpeg', 0);
+ return true;
+ } catch (e) {
+ return false;
+ }
+
+ default:
+ return null;
+ }
+ }
+ });
+
+ /**
+ * Returs JSON representation of canvas
+ * @function
+ * @method toJSON
+ * @return {String} json string
+ */
+ fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
+
+})(typeof exports != 'undefined' ? exports : this);
+
+(function() {
+
+ var extend = fabric.util.object.extend,
+ getPointer = fabric.util.getPointer,
+ addListener = fabric.util.addListener,
+ removeListener = fabric.util.removeListener,
+ cursorMap = {
+ 'tr': 'ne-resize',
+ 'br': 'se-resize',
+ 'bl': 'sw-resize',
+ 'tl': 'nw-resize',
+ 'ml': 'w-resize',
+ 'mt': 'n-resize',
+ 'mr': 'e-resize',
+ 'mb': 's-resize'
+ },
+
+ utilMin = fabric.util.array.min,
+ utilMax = fabric.util.array.max,
+
+ sqrt = Math.sqrt,
+ pow = Math.pow,
+ atan2 = Math.atan2,
+ abs = Math.abs,
+ min = Math.min,
+ max = Math.max,
+
+ STROKE_OFFSET = 0.5;
+
+ /**
+ * @class fabric.Canvas
+ * @constructor
+ * @extends fabric.StaticCanvas
+ * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
+ * @param {Object} [options] Options object
+ */
+ fabric.Canvas = function(el, options) {
+ options || (options = { });
+
+ this._initStatic(el, options);
+ this._initInteractive();
+ this._createCacheCanvas();
+
+ fabric.Canvas.activeInstance = this;
+ };
+
+ function ProtoProxy(){ }
+ ProtoProxy.prototype = fabric.StaticCanvas.prototype;
+ fabric.Canvas.prototype = new ProtoProxy;
+
+ var InteractiveMethods = /** @scope fabric.Canvas.prototype */ {
+
+ /**
+ * Indicates that canvas is interactive. This property should not be changed.
+ * @property
+ * @type Boolean
+ */
+ interactive: true,
+
+ /**
+ * Indicates whether group selection should be enabled
+ * @property
+ * @type Boolean
+ */
+ selection: true,
+
+ /**
+ * Color of selection
+ * @property
+ * @type String
+ */
+ selectionColor: 'rgba(100, 100, 255, 0.3)', // blue
+
+ /**
+ * Color of the border of selection (usually slightly darker than color of selection itself)
+ * @property
+ * @type String
+ */
+ selectionBorderColor: 'rgba(255, 255, 255, 0.3)',
+
+ /**
+ * Width of a line used in object/group selection
+ * @property
+ * @type Number
+ */
+ selectionLineWidth: 1,
+
+ /**
+ * Color of the line used in free drawing mode
+ * @property
+ * @type String
+ */
+ freeDrawingColor: 'rgb(0, 0, 0)',
+
+ /**
+ * Width of a line used in free drawing mode
+ * @property
+ * @type Number
+ */
+ freeDrawingLineWidth: 1,
+
+ /**
+ * Default cursor value used when hovering over an object on canvas
+ * @property
+ * @type String
+ */
+ hoverCursor: 'move',
+
+ /**
+ * Default cursor value used when moving an object on canvas
+ * @property
+ * @type String
+ */
+ moveCursor: 'move',
+
+ /**
+ * Default cursor value used for the entire canvas
+ * @property
+ * @type String
+ */
+ defaultCursor: 'default',
+
+ /**
+ * Cursor value used for rotation point
+ * @property
+ * @type String
+ */
+ rotationCursor: 'crosshair',
+
+ /**
+ * Default element class that's given to wrapper (div) element of canvas
+ * @property
+ * @type String
+ */
+ containerClass: 'canvas-container',
+
+ perPixelTargetFind: false,
+
+ targetFindTolerance: 0,
+
+ _initInteractive: function() {
+ this._currentTransform = null;
+ this._groupSelector = null;
+ this._freeDrawingXPoints = [ ];
+ this._freeDrawingYPoints = [ ];
+ this._initWrapperElement();
+ this._createUpperCanvas();
+ this._initEvents();
+ this.calcOffset();
+ },
+
+ /**
+ * Adds mouse listeners to canvas
+ * @method _initEvents
+ * @private
+ * See configuration documentation for more details.
+ */
+ _initEvents: function () {
+ var _this = this;
+
+ this._onMouseDown = function (e) {
+ _this.__onMouseDown(e);
+
+ addListener(fabric.document, 'mouseup', _this._onMouseUp);
+ fabric.isTouchSupported && addListener(fabric.document, 'touchend', _this._onMouseUp);
+
+ addListener(fabric.document, 'mousemove', _this._onMouseMove);
+ fabric.isTouchSupported && addListener(fabric.document, 'touchmove', _this._onMouseMove);
+
+ removeListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
+ fabric.isTouchSupported && removeListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
+ };
+
+ this._onMouseUp = function (e) {
+ _this.__onMouseUp(e);
+
+ removeListener(fabric.document, 'mouseup', _this._onMouseUp);
+ fabric.isTouchSupported && removeListener(fabric.document, 'touchend', _this._onMouseUp);
+
+ removeListener(fabric.document, 'mousemove', _this._onMouseMove);
+ fabric.isTouchSupported && removeListener(fabric.document, 'touchmove', _this._onMouseMove);
+
+ addListener(_this.upperCanvasEl, 'mousemove', _this._onMouseMove);
+ fabric.isTouchSupported && addListener(_this.upperCanvasEl, 'touchmove', _this._onMouseMove);
+ };
+
+ this._onMouseMove = function (e) {
+ e.preventDefault && e.preventDefault();
+ _this.__onMouseMove(e);
+ };
+
+ this._onResize = function (e) {
+ _this.calcOffset();
+ };
+
+
+ addListener(fabric.window, 'resize', this._onResize);
+
+ if (fabric.isTouchSupported) {
+ addListener(this.upperCanvasEl, 'touchstart', this._onMouseDown);
+ addListener(this.upperCanvasEl, 'touchmove', this._onMouseMove);
+ }
+ else {
+ addListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
+ addListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
+ }
+ },
+
+ /**
+ * Method that defines the actions when mouse is released on canvas.
+ * The method resets the currentTransform parameters, store the image corner
+ * position in the image object and render the canvas on top.
+ * @method __onMouseUp
+ * @param {Event} e Event object fired on mouseup
+ *
+ */
+ __onMouseUp: function (e) {
+
+ if (this.isDrawingMode && this._isCurrentlyDrawing) {
+ this._finalizeDrawingPath();
+ this.fire('mouse:up', { e: e });
+ return;
+ }
+
+ if (this._currentTransform) {
+
+ var transform = this._currentTransform,
+ target = transform.target;
+
+ if (target._scaling) {
+ target._scaling = false;
+ }
+
+ // determine the new coords everytime the image changes its position
+ var i = this._objects.length;
+ while (i--) {
+ this._objects[i].setCoords();
+ }
+
+ // only fire :modified event if target coordinates were changed during mousedown-mouseup
+ if (this.stateful && target.hasStateChanged()) {
+ target.isMoving = false;
+ this.fire('object:modified', { target: target });
+ target.fire('modified');
+ }
+ }
+
+ this._currentTransform = null;
+
+ if (this._groupSelector) {
+ // group selection was completed, determine its bounds
+ this._findSelectedObjects(e);
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ activeGroup.setObjectsCoords();
+ activeGroup.set('isMoving', false);
+ this._setCursor(this.defaultCursor);
+ }
+
+ // clear selection
+ this._groupSelector = null;
+ this.renderAll();
+
+ this._setCursorFromEvent(e, target);
+
+ // fix for FF
+ this._setCursor('');
+
+ var _this = this;
+ setTimeout(function () {
+ _this._setCursorFromEvent(e, target);
+ }, 50);
+
+ this.fire('mouse:up', { target: target, e: e });
+ target && target.fire('mouseup', { e: e })
+ },
+
+ /**
+ * Method that defines the actions when mouse is clic ked on canvas.
+ * The method inits the currentTransform parameters and renders all the
+ * canvas so the current image can be placed on the top canvas and the rest
+ * in on the container one.
+ * @method __onMouseDown
+ * @param e {Event} Event object fired on mousedown
+ *
+ */
+ __onMouseDown: function (e) {
+
+ // accept only left clicks
+ var isLeftClick = 'which' in e ? e.which == 1 : e.button == 1;
+ if (!isLeftClick && !fabric.isTouchSupported) return;
+
+ if (this.isDrawingMode) {
+ this._prepareForDrawing(e);
+
+ // capture coordinates immediately; this allows to draw dots (when movement never occurs)
+ this._captureDrawingPath(e);
+ this.fire('mouse:down', { e: e });
+ return;
+ }
+
+ // ignore if some object is being transformed at this moment
+ if (this._currentTransform) return;
+
+ var target = this.findTarget(e),
+ pointer = this.getPointer(e),
+ activeGroup = this.getActiveGroup(),
+ corner;
+
+ if (this._shouldClearSelection(e)) {
+
+ this._groupSelector = {
+ ex: pointer.x,
+ ey: pointer.y,
+ top: 0,
+ left: 0
+ };
+
+ this.deactivateAllWithDispatch();
+ }
+ else {
+ // determine if it's a drag or rotate case
+ // rotate and scale will happen at the same time
+ this.stateful && target.saveState();
+
+ if (corner = target._findTargetCorner(e, this._offset)) {
+ this.onBeforeScaleRotate(target);
+ }
+
+ this._setupCurrentTransform(e, target);
+
+ var shouldHandleGroupLogic = e.shiftKey && (activeGroup || this.getActiveObject()) && this.selection;
+ if (shouldHandleGroupLogic) {
+ this._handleGroupLogic(e, target);
+ }
+ else {
+ if (target !== this.getActiveGroup()) {
+ this.deactivateAll();
+ }
+ this.setActiveObject(target, e);
+ }
+ }
+ // we must renderAll so that active image is placed on the top canvas
+ this.renderAll();
+
+ this.fire('mouse:down', { target: target, e: e });
+ target && target.fire('mousedown', { e: e });
+ },
+
+ /**
+ * Method that defines the actions when mouse is hovering the canvas.
+ * The currentTransform parameter will definde whether the user is rotating/scaling/translating
+ * an image or neither of them (only hovering). A group selection is also possible and would cancel
+ * all any other type of action.
+ * In case of an image transformation only the top canvas will be rendered.
+ * @method __onMouseMove
+ * @param e {Event} Event object fired on mousemove
+ *
+ */
+ __onMouseMove: function (e) {
+
+ if (this.isDrawingMode) {
+ if (this._isCurrentlyDrawing) {
+ this._captureDrawingPath(e);
+ }
+ this.fire('mouse:move', { e: e });
+ return;
+ }
+
+ var groupSelector = this._groupSelector;
+
+ // We initially clicked in an empty area, so we draw a box for multiple selection.
+ if (groupSelector !== null) {
+ var pointer = getPointer(e);
+ groupSelector.left = pointer.x - this._offset.left - groupSelector.ex;
+ groupSelector.top = pointer.y - this._offset.top - groupSelector.ey;
+ this.renderTop();
+ }
+ else if (!this._currentTransform) {
+
+ // alias style to elimintate unnecessary lookup
+ var style = this.upperCanvasEl.style;
+
+ // Here we are hovering the canvas then we will determine
+ // what part of the pictures we are hovering to change the caret symbol.
+ // We won't do that while dragging or rotating in order to improve the
+ // performance.
+ var target = this.findTarget(e);
+
+ if (!target) {
+ // image/text was hovered-out from, we remove its borders
+ for (var i = this._objects.length; i--; ) {
+ if (this._objects[i] && !this._objects[i].active) {
+ this._objects[i].setActive(false);
+ }
+ }
+ style.cursor = this.defaultCursor;
+ }
+ else {
+ // set proper cursor
+ this._setCursorFromEvent(e, target);
+ if (target.isActive()) {
+ // display corners when hovering over an image
+ target.setCornersVisibility && target.setCornersVisibility(true);
+ }
+ }
+ }
+ else {
+ // object is being transformed (scaled/rotated/moved/etc.)
+ var pointer = getPointer(e),
+ x = pointer.x,
+ y = pointer.y;
+
+ this._currentTransform.target.isMoving = true;
+
+ if (this._currentTransform.action === 'rotate') {
+ // rotate object only if shift key is not pressed
+ // and if it is not a group we are transforming
+
+ if (!e.shiftKey) {
+ this._rotateObject(x, y);
+
+ this.fire('object:rotating', {
+ target: this._currentTransform.target
+ });
+ this._currentTransform.target.fire('rotating');
+ }
+ if (!this._currentTransform.target.hasRotatingPoint) {
+ this._scaleObject(x, y);
+ this.fire('object:scaling', {
+ target: this._currentTransform.target
+ });
+ this._currentTransform.target.fire('scaling');
+ }
+ }
+ else if (this._currentTransform.action === 'scale') {
+ this._scaleObject(x, y);
+ this.fire('object:scaling', {
+ target: this._currentTransform.target
+ });
+ this._currentTransform.target.fire('scaling');
+ }
+ else if (this._currentTransform.action === 'scaleX') {
+ this._scaleObject(x, y, 'x');
+
+ this.fire('object:scaling', {
+ target: this._currentTransform.target
+ });
+ this._currentTransform.target.fire('scaling');
+ }
+ else if (this._currentTransform.action === 'scaleY') {
+ this._scaleObject(x, y, 'y');
+
+ this.fire('object:scaling', {
+ target: this._currentTransform.target
+ });
+ this._currentTransform.target.fire('scaling');
+ }
+ else {
+ this._translateObject(x, y);
+
+ this.fire('object:moving', {
+ target: this._currentTransform.target
+ });
+
+ this._setCursor(this.moveCursor);
+
+ this._currentTransform.target.fire('moving');
+ }
+ // only commit here. when we are actually moving the pictures
+ this.renderAll();
+ }
+ this.fire('mouse:move', { target: target, e: e });
+ target && target.fire('mousemove', { e: e });
+ },
+
+ /**
+ * Applies one implementation of 'point inside polygon' algorithm
+ * @method containsPoint
+ * @param e { Event } event object
+ * @param target { fabric.Object } object to test against
+ * @return {Boolean} true if point contains within area of given object
+ */
+ containsPoint: function (e, target) {
+ var pointer = this.getPointer(e),
+ xy = this._normalizePointer(target, pointer),
+ x = xy.x,
+ y = xy.y;
+
+ // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html
+ // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html
+
+ // we iterate through each object. If target found, return it.
+ var iLines = target._getImageLines(target.oCoords),
+ xpoints = target._findCrossPoints(x, y, iLines);
+
+ // if xcount is odd then we clicked inside the object
+ // For the specific case of square images xcount === 1 in all true cases
+ if ((xpoints && xpoints % 2 === 1) || target._findTargetCorner(e, this._offset)) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * @private
+ * @method _normalizePointer
+ */
+ _normalizePointer: function (object, pointer) {
+
+ var activeGroup = this.getActiveGroup(),
+ x = pointer.x,
+ y = pointer.y;
+
+ var isObjectInGroup = (
+ activeGroup &&
+ object.type !== 'group' &&
+ activeGroup.contains(object)
+ );
+
+ if (isObjectInGroup) {
+ x -= activeGroup.left;
+ y -= activeGroup.top;
+ }
+ return { x: x, y: y };
+ },
+
+ _isTargetTransparent: function (target, x, y) {
+ var cacheContext = this.contextCache;
+
+ var hasBorders = target.hasBorders, transparentCorners = target.transparentCorners;
+ target.hasBorders = target.transparentCorners = false;
+
+ this._draw(cacheContext, target);
+
+ target.hasBorders = hasBorders;
+ target.transparentCorners = transparentCorners;
+
+ // If tolerance is > 0 adjust start coords to take into account. If moves off Canvas fix to 0
+ if (this.targetFindTolerance > 0) {
+ if (x > this.targetFindTolerance) {
+ x -= this.targetFindTolerance;
+ }
+ else {
+ x = 0;
+ }
+ if (y > this.targetFindTolerance) {
+ y -= this.targetFindTolerance;
+ }
+ else {
+ y = 0;
+ }
+ }
+
+ var isTransparent = true;
+ var imageData = cacheContext.getImageData(
+ x, y, (this.targetFindTolerance * 2) || 1, (this.targetFindTolerance * 2) || 1);
+
+ // Split image data - for tolerance > 1, pixelDataSize = 4;
+ for (var i = 3; i < imageData.data.length; i += 4) {
+ var temp = imageData.data[i];
+ isTransparent = temp <= 0;
+ if (isTransparent === false) break; //Stop if colour found
+ }
+
+ imageData = null;
+ this.clearContext(cacheContext);
+ return isTransparent;
+ },
+
+ /**
+ * @private
+ * @method _shouldClearSelection
+ */
+ _shouldClearSelection: function (e) {
+ var target = this.findTarget(e),
+ activeGroup = this.getActiveGroup();
+ return (
+ !target || (
+ target &&
+ activeGroup &&
+ !activeGroup.contains(target) &&
+ activeGroup !== target &&
+ !e.shiftKey
+ )
+ );
+ },
+
+ /**
+ * @private
+ * @method _setupCurrentTransform
+ */
+ _setupCurrentTransform: function (e, target) {
+ var action = 'drag',
+ corner,
+ pointer = getPointer(e);
+
+ if (corner = target._findTargetCorner(e, this._offset)) {
+ action = (corner === 'ml' || corner === 'mr')
+ ? 'scaleX'
+ : (corner === 'mt' || corner === 'mb')
+ ? 'scaleY'
+ : corner === 'mtr'
+ ? 'rotate'
+ : (target.hasRotatingPoint)
+ ? 'scale'
+ : 'rotate';
+ }
+
+ this._currentTransform = {
+ target: target,
+ action: action,
+ scaleX: target.scaleX,
+ scaleY: target.scaleY,
+ offsetX: pointer.x - target.left,
+ offsetY: pointer.y - target.top,
+ ex: pointer.x,
+ ey: pointer.y,
+ left: target.left,
+ top: target.top,
+ theta: target._theta,
+ width: target.width * target.scaleX
+ };
+
+ this._currentTransform.original = {
+ left: target.left,
+ top: target.top
+ };
+ },
+
+ _handleGroupLogic: function (e, target) {
+ if (target === this.getActiveGroup()) {
+ // if it's a group, find target again, this time skipping group
+ target = this.findTarget(e, true);
+ // if even object is not found, bail out
+ if (!target || target.isType('group')) {
+ return;
+ }
+ }
+ var activeGroup = this.getActiveGroup();
+ if (activeGroup) {
+ if (activeGroup.contains(target)) {
+ activeGroup.removeWithUpdate(target);
+ target.setActive(false);
+ if (activeGroup.size() === 1) {
+ // remove group alltogether if after removal it only contains 1 object
+ this.discardActiveGroup();
+ }
+ }
+ else {
+ activeGroup.addWithUpdate(target);
+ }
+ this.fire('selection:created', { target: activeGroup, e: e });
+ activeGroup.setActive(true);
+ }
+ else {
+ // group does not exist
+ if (this._activeObject) {
+ // only if there's an active object
+ if (target !== this._activeObject) {
+ // and that object is not the actual target
+ var group = new fabric.Group([ this._activeObject, target ]);
+ this.setActiveGroup(group);
+ activeGroup = this.getActiveGroup();
+ }
+ }
+ // activate target object in any case
+ target.setActive(true);
+ }
+
+ if (activeGroup) {
+ activeGroup.saveCoords();
+ }
+ },
+
+ /**
+ * @private
+ * @method _prepareForDrawing
+ */
+ _prepareForDrawing: function(e) {
+
+ this._isCurrentlyDrawing = true;
+
+ this.discardActiveObject().renderAll();
+
+ var pointer = this.getPointer(e);
+
+ this._freeDrawingXPoints.length = this._freeDrawingYPoints.length = 0;
+
+ this._freeDrawingXPoints.push(pointer.x);
+ this._freeDrawingYPoints.push(pointer.y);
+
+ this.contextTop.beginPath();
+ this.contextTop.moveTo(pointer.x, pointer.y);
+ this.contextTop.strokeStyle = this.freeDrawingColor;
+ this.contextTop.lineWidth = this.freeDrawingLineWidth;
+ this.contextTop.lineCap = this.contextTop.lineJoin = 'round';
+ },
+
+ /**
+ * @private
+ * @method _captureDrawingPath
+ */
+ _captureDrawingPath: function(e) {
+ var pointer = this.getPointer(e);
+
+ this._freeDrawingXPoints.push(pointer.x);
+ this._freeDrawingYPoints.push(pointer.y);
+
+ this.contextTop.lineTo(pointer.x, pointer.y);
+ this.contextTop.stroke();
+ },
+
+ /**
+ * @private
+ * @method _finalizeDrawingPath
+ */
+ _finalizeDrawingPath: function() {
+
+ this.contextTop.closePath();
+
+ this._isCurrentlyDrawing = false;
+
+ var minX = utilMin(this._freeDrawingXPoints),
+ minY = utilMin(this._freeDrawingYPoints),
+ maxX = utilMax(this._freeDrawingXPoints),
+ maxY = utilMax(this._freeDrawingYPoints),
+ ctx = this.contextTop,
+ path = [ ],
+ xPoint,
+ yPoint,
+ xPoints = this._freeDrawingXPoints,
+ yPoints = this._freeDrawingYPoints;
+
+ path.push('M ', xPoints[0] - minX, ' ', yPoints[0] - minY, ' ');
+
+ for (var i = 1; xPoint = xPoints[i], yPoint = yPoints[i]; i++) {
+ path.push('L ', xPoint - minX, ' ', yPoint - minY, ' ');
+ }
+
+ // TODO (kangax): maybe remove Path creation from here, to decouple fabric.Canvas from fabric.Path,
+ // and instead fire something like "drawing:completed" event with path string
+
+ path = path.join('');
+
+ if (path === "M 0 0 L 0 0 ") {
+ // do not create 0 width/height paths, as they are rendered inconsistently across browsers
+ // Firefox 4, for example, renders a dot, whereas Chrome 10 renders nothing
+ this.renderAll();
+ return;
+ }
+
+ var p = new fabric.Path(path);
+
+ p.fill = null;
+ p.stroke = this.freeDrawingColor;
+ p.strokeWidth = this.freeDrawingLineWidth;
+ this.add(p);
+ p.set("left", minX + (maxX - minX) / 2).set("top", minY + (maxY - minY) / 2).setCoords();
+ this.renderAll();
+ this.fire('path:created', { path: p });
+ },
+
+ /**
+ * Translates object by "setting" its left/top
+ * @method _translateObject
+ * @param x {Number} pointer's x coordinate
+ * @param y {Number} pointer's y coordinate
+ */
+ _translateObject: function (x, y) {
+ var target = this._currentTransform.target;
+ target.lockMovementX || target.set('left', x - this._currentTransform.offsetX);
+ target.lockMovementY || target.set('top', y - this._currentTransform.offsetY);
+ },
+
+ /**
+ * Scales object by invoking its scaleX/scaleY methods
+ * @method _scaleObject
+ * @param x {Number} pointer's x coordinate
+ * @param y {Number} pointer's y coordinate
+ * @param by {String} Either 'x' or 'y' - specifies dimension constraint by which to scale an object.
+ * When not provided, an object is scaled by both dimensions equally
+ */
+ _scaleObject: function (x, y, by) {
+ var t = this._currentTransform,
+ offset = this._offset,
+ target = t.target;
+
+ if (target.lockScalingX && target.lockScalingY) return;
+
+ var lastLen = sqrt(pow(t.ey - t.top - offset.top, 2) + pow(t.ex - t.left - offset.left, 2)),
+ curLen = sqrt(pow(y - t.top - offset.top, 2) + pow(x - t.left - offset.left, 2));
+
+ target._scaling = true;
+
+ if (!by) {
+ target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
+ target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
+ }
+ else if (by === 'x' && !target.lockUniScaling) {
+ target.lockScalingX || target.set('scaleX', t.scaleX * curLen/lastLen);
+ }
+ else if (by === 'y' && !target.lockUniScaling) {
+ target.lockScalingY || target.set('scaleY', t.scaleY * curLen/lastLen);
+ }
+ },
+
+ /**
+ * Rotates object by invoking its rotate method
+ * @method _rotateObject
+ * @param x {Number} pointer's x coordinate
+ * @param y {Number} pointer's y coordinate
+ */
+ _rotateObject: function (x, y) {
+
+ var t = this._currentTransform,
+ o = this._offset;
+
+ if (t.target.lockRotation) return;
+
+ var lastAngle = atan2(t.ey - t.top - o.top, t.ex - t.left - o.left),
+ curAngle = atan2(y - t.top - o.top, x - t.left - o.left);
+
+ t.target._theta = (curAngle - lastAngle) + t.theta;
+ },
+
+ /**
+ * @method _setCursor
+ */
+ _setCursor: function (value) {
+ this.upperCanvasEl.style.cursor = value;
+ },
+
+ /**
+ * Sets the cursor depending on where the canvas is being hovered.
+ * Note: very buggy in Opera
+ * @method _setCursorFromEvent
+ * @param e {Event} Event object
+ * @param target {Object} Object that the mouse is hovering, if so.
+ */
+ _setCursorFromEvent: function (e, target) {
+ var s = this.upperCanvasEl.style;
+ if (!target) {
+ s.cursor = this.defaultCursor;
+ return false;
+ }
+ else {
+ var activeGroup = this.getActiveGroup();
+ // only show proper corner when group selection is not active
+ var corner = !!target._findTargetCorner
+ && (!activeGroup || !activeGroup.contains(target))
+ && target._findTargetCorner(e, this._offset);
+
+ if (!corner) {
+ s.cursor = this.hoverCursor;
+ }
+ else {
+ if (corner in cursorMap) {
+ s.cursor = cursorMap[corner];
+ } else if (corner === 'mtr' && target.hasRotatingPoint) {
+ s.cursor = this.rotationCursor;
+ } else {
+ s.cursor = this.defaultCursor;
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * @method _drawSelection
+ * @private
+ */
+ _drawSelection: function () {
+ var groupSelector = this._groupSelector,
+ left = groupSelector.left,
+ top = groupSelector.top,
+ aleft = abs(left),
+ atop = abs(top);
+
+ this.contextTop.fillStyle = this.selectionColor;
+
+ this.contextTop.fillRect(
+ groupSelector.ex - ((left > 0) ? 0 : -left),
+ groupSelector.ey - ((top > 0) ? 0 : -top),
+ aleft,
+ atop
+ );
+
+ this.contextTop.lineWidth = this.selectionLineWidth;
+ this.contextTop.strokeStyle = this.selectionBorderColor;
+
+ this.contextTop.strokeRect(
+ groupSelector.ex + STROKE_OFFSET - ((left > 0) ? 0 : aleft),
+ groupSelector.ey + STROKE_OFFSET - ((top > 0) ? 0 : atop),
+ aleft,
+ atop
+ );
+ },
+
+ _findSelectedObjects: function (e) {
+ var target,
+ targetRegion,
+ group = [ ],
+ x1 = this._groupSelector.ex,
+ y1 = this._groupSelector.ey,
+ x2 = x1 + this._groupSelector.left,
+ y2 = y1 + this._groupSelector.top,
+ currentObject,
+ selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)),
+ selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2));
+
+ for (var i = 0, len = this._objects.length; i < len; ++i) {
+ currentObject = this._objects[i];
+
+ if (!currentObject) continue;
+
+ if (currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2) ||
+ currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2)) {
+
+ if (this.selection && currentObject.selectable) {
+ currentObject.setActive(true);
+ group.push(currentObject);
+ }
+ }
+ }
+
+ // do not create group for 1 element only
+ if (group.length === 1) {
+ this.setActiveObject(group[0], e);
+ }
+ else if (group.length > 1) {
+ var group = new fabric.Group(group);
+ this.setActiveGroup(group);
+ group.saveCoords();
+ this.fire('selection:created', { target: group });
+ }
+
+ this.renderAll();
+ },
+
+ /**
+ * Method that determines what object we are clicking on
+ * @method findTarget
+ * @param {Event} e mouse event
+ * @param {Boolean} skipGroup when true, group is skipped and only objects are traversed through
+ */
+ findTarget: function (e, skipGroup) {
+
+ var target,
+ pointer = this.getPointer(e);
+
+ // first check current group (if one exists)
+ var activeGroup = this.getActiveGroup();
+
+ if (activeGroup && !skipGroup && this.containsPoint(e, activeGroup)) {
+ target = activeGroup;
+ return target;
+ }
+
+ // then check all of the objects on canvas
+ // Cache all targets where their bounding box contains point.
+ var possibleTargets = [];
+ for (var i = this._objects.length; i--; ) {
+ if (this._objects[i] && this.containsPoint(e, this._objects[i])) {
+ if (this.perPixelTargetFind || this._objects[i].perPixelTargetFind) {
+ possibleTargets[possibleTargets.length] = this._objects[i];
+ }
+ else {
+ target = this._objects[i];
+ this.relatedTarget = target;
+ break;
+ }
+ }
+ }
+ for (var i = 0, len = possibleTargets.length; i < len; i++) {
+ var pointer = this.getPointer(e);
+ var isTransparent = this._isTargetTransparent(possibleTargets[i], pointer.x, pointer.y);
+ if (!isTransparent) {
+ target = possibleTargets[i];
+ this.relatedTarget = target;
+ break;
+ }
+ }
+ if (target && target.selectable) {
+ return target;
+ }
+ },
+
+ /**
+ * Returns pointer coordinates relative to canvas.
+ * @method getPointer
+ * @return {Object} object with "x" and "y" number values
+ */
+ getPointer: function (e) {
+ var pointer = getPointer(e);
+ return {
+ x: pointer.x - this._offset.left,
+ y: pointer.y - this._offset.top
+ };
+ },
+
+ /**
+ * @method _createUpperCanvas
+ * @param {HTMLElement|String} canvasEl Canvas element
+ * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized
+ */
+ _createUpperCanvas: function () {
+ this.upperCanvasEl = this._createCanvasElement();
+ this.upperCanvasEl.className = 'upper-canvas';
+
+ this.wrapperEl.appendChild(this.upperCanvasEl);
+
+ this._applyCanvasStyle(this.upperCanvasEl);
+ this.contextTop = this.upperCanvasEl.getContext('2d');
+ },
+
+ _createCacheCanvas: function () {
+ this.cacheCanvasEl = this._createCanvasElement();
+ this.cacheCanvasEl.setAttribute('width', this.width);
+ this.cacheCanvasEl.setAttribute('height', this.height);
+ this.contextCache = this.cacheCanvasEl.getContext('2d');
+ },
+
+ /**
+ * @private
+ * @method _initWrapperElement
+ * @param {Number} width
+ * @param {Number} height
+ */
+ _initWrapperElement: function () {
+ this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', {
+ 'class': this.containerClass
+ });
+ fabric.util.setStyle(this.wrapperEl, {
+ width: this.getWidth() + 'px',
+ height: this.getHeight() + 'px',
+ position: 'relative'
+ });
+ fabric.util.makeElementUnselectable(this.wrapperEl);
+ },
+
+ /**
+ * @private
+ * @method _applyCanvasStyle
+ * @param {Element} element
+ */
+ _applyCanvasStyle: function (element) {
+ var width = this.getWidth() || element.width,
+ height = this.getHeight() || element.height;
+
+ fabric.util.setStyle(element, {
+ position: 'absolute',
+ width: width + 'px',
+ height: height + 'px',
+ left: 0,
+ top: 0
+ });
+ element.width = width;
+ element.height = height;
+ fabric.util.makeElementUnselectable(element);
+ },
+
+ /**
+ * Returns context of canvas where object selection is drawn
+ * @method getSelectionContext
+ * @return {CanvasRenderingContext2D}
+ */
+ getSelectionContext: function() {
+ return this.contextTop;
+ },
+
+ /**
+ * Returns &lt;canvas> element on which object selection is drawn
+ * @method getSelectionElement
+ * @return {HTMLCanvasElement}
+ */
+ getSelectionElement: function () {
+ return this.upperCanvasEl;
+ },
+
+ /**
+ * Sets given object as active
+ * @method setActiveObject
+ * @param object {fabric.Object} Object to set as an active one
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setActiveObject: function (object, e) {
+ if (this._activeObject) {
+ this._activeObject.setActive(false);
+ }
+ this._activeObject = object;
+ object.setActive(true);
+
+ this.renderAll();
+
+ this.fire('object:selected', { target: object, e: e });
+ object.fire('selected', { e: e });
+ return this;
+ },
+
+ /**
+ * Returns currently active object
+ * @method getActiveObject
+ * @return {fabric.Object} active object
+ */
+ getActiveObject: function () {
+ return this._activeObject;
+ },
+
+ /**
+ * Discards currently active object
+ * @method discardActiveObject
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ discardActiveObject: function () {
+ if (this._activeObject) {
+ this._activeObject.setActive(false);
+ }
+ this._activeObject = null;
+ return this;
+ },
+
+ /**
+ * Sets active group to a speicified one
+ * @method setActiveGroup
+ * @param {fabric.Group} group Group to set as a current one
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ setActiveGroup: function (group) {
+ this._activeGroup = group;
+ group && group.setActive(true);
+ return this;
+ },
+
+ /**
+ * Returns currently active group
+ * @method getActiveGroup
+ * @return {fabric.Group} Current group
+ */
+ getActiveGroup: function () {
+ return this._activeGroup;
+ },
+
+ /**
+ * Removes currently active group
+ * @method discardActiveGroup
+ * @return {fabric.Canvas} thisArg
+ */
+ discardActiveGroup: function () {
+ var g = this.getActiveGroup();
+ if (g) {
+ g.destroy();
+ }
+ return this.setActiveGroup(null);
+ },
+
+ /**
+ * Deactivates all objects by calling their setActive(false)
+ * @method deactivateAll
+ * @return {fabric.Canvas} thisArg
+ */
+ deactivateAll: function () {
+ var allObjects = this.getObjects(),
+ i = 0,
+ len = allObjects.length;
+ for ( ; i < len; i++) {
+ allObjects[i].setActive(false);
+ }
+ this.discardActiveGroup();
+ this.discardActiveObject();
+ return this;
+ },
+
+ /**
+ * Deactivates all objects and dispatches appropriate events
+ * @method deactivateAllWithDispatch
+ * @return {fabric.Canvas} thisArg
+ */
+ deactivateAllWithDispatch: function () {
+ var activeObject = this.getActiveGroup() || this.getActiveObject();
+ if (activeObject) {
+ this.fire('before:selection:cleared', { target: activeObject });
+ }
+ this.deactivateAll();
+ if (activeObject) {
+ this.fire('selection:cleared');
+ }
+ return this;
+ }
+ };
+
+ fabric.Canvas.prototype.toString = fabric.StaticCanvas.prototype.toString;
+ extend(fabric.Canvas.prototype, InteractiveMethods);
+
+ // iterating manually to workaround Opera's bug
+ // where "prototype" property is enumerable and overrides existing prototype
+ for (var prop in fabric.StaticCanvas) {
+ if (prop !== 'prototype') {
+ fabric.Canvas[prop] = fabric.StaticCanvas[prop];
+ }
+ }
+
+ if (fabric.isTouchSupported) {
+ fabric.Canvas.prototype._setCursorFromEvent = function() { };
+ }
+
+ /**
+ * @class fabric.Element
+ * @alias fabric.Canvas
+ * @deprecated
+ * @constructor
+ */
+ fabric.Element = fabric.Canvas;
+})();
+fabric.util.object.extend(fabric.StaticCanvas.prototype, {
+
+ FX_DURATION: 500,
+
+ /**
+ * Centers object horizontally with animation.
+ * @method fxCenterObjectH
+ * @param {fabric.Object} object Object to center
+ * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ fxCenterObjectH: function (object, callbacks) {
+ callbacks = callbacks || { };
+
+ var empty = function() { },
+ onComplete = callbacks.onComplete || empty,
+ onChange = callbacks.onChange || empty,
+ _this = this;
+
+ fabric.util.animate({
+ startValue: object.get('left'),
+ endValue: this.getCenter().left,
+ duration: this.FX_DURATION,
+ onChange: function(value) {
+ object.set('left', value);
+ _this.renderAll();
+ onChange();
+ },
+ onComplete: function() {
+ object.setCoords();
+ onComplete();
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ * Centers object vertically with animation.
+ * @method fxCenterObjectV
+ * @param {fabric.Object} object Object to center
+ * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ fxCenterObjectV: function (object, callbacks) {
+ callbacks = callbacks || { };
+
+ var empty = function() { },
+ onComplete = callbacks.onComplete || empty,
+ onChange = callbacks.onChange || empty,
+ _this = this;
+
+ fabric.util.animate({
+ startValue: object.get('top'),
+ endValue: this.getCenter().top,
+ duration: this.FX_DURATION,
+ onChange: function(value) {
+ object.set('top', value);
+ _this.renderAll();
+ onChange();
+ },
+ onComplete: function() {
+ object.setCoords();
+ onComplete();
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ * Same as `fabric.Canvas#remove` but animated
+ * @method fxRemove
+ * @param {fabric.Object} object Object to remove
+ * @param {Function} callback Callback, invoked on effect completion
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ fxRemove: function (object, callbacks) {
+ callbacks = callbacks || { };
+
+ var empty = function() { },
+ onComplete = callbacks.onComplete || empty,
+ onChange = callbacks.onChange || empty,
+ _this = this;
+
+ fabric.util.animate({
+ startValue: object.get('opacity'),
+ endValue: 0,
+ duration: this.FX_DURATION,
+ onStart: function() {
+ object.setActive(false);
+ },
+ onChange: function(value) {
+ object.set('opacity', value);
+ _this.renderAll();
+ onChange();
+ },
+ onComplete: function () {
+ _this.remove(object);
+ onComplete();
+ }
+ });
+
+ return this;
+ }
+});
+fabric.util.object.extend(fabric.StaticCanvas.prototype, {
+
+ /**
+ * Populates canvas with data from the specified dataless JSON
+ * JSON format must conform to the one of `fabric.Canvas#toDatalessJSON`
+ * @method loadFromDatalessJSON
+ * @param {String} json JSON string
+ * @param {Function} callback Callback, invoked when json is parsed
+ * and corresponding objects (e.g: fabric.Image)
+ * are initialized
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ loadFromDatalessJSON: function (json, callback) {
+
+ if (!json) {
+ return;
+ }
+
+ // serialize if it wasn't already
+ var serialized = (typeof json === 'string')
+ ? JSON.parse(json)
+ : json;
+
+ if (!serialized || (serialized && !serialized.objects)) return;
+
+ this.clear();
+
+ // TODO: test this
+ this.backgroundColor = serialized.background;
+ this._enlivenDatalessObjects(serialized.objects, callback);
+ },
+
+ /**
+ * @method _enlivenDatalessObjects
+ * @param {Array} objects
+ * @param {Function} callback
+ */
+ _enlivenDatalessObjects: function (objects, callback) {
+
+ /** @ignore */
+ function onObjectLoaded(object, index) {
+ _this.insertAt(object, index, true);
+ object.setCoords();
+ if (++numLoadedObjects === numTotalObjects) {
+ callback && callback();
+ }
+ }
+
+ var _this = this,
+ numLoadedObjects = 0,
+ numTotalObjects = objects.length;
+
+ if (numTotalObjects === 0 && callback) {
+ callback();
+ }
+
+ try {
+ objects.forEach(function (obj, index) {
+
+ var pathProp = obj.paths ? 'paths' : 'path';
+ var path = obj[pathProp];
+
+ delete obj[pathProp];
+
+ if (typeof path !== 'string') {
+ switch (obj.type) {
+ case 'image':
+ fabric[fabric.util.string.capitalize(obj.type)].fromObject(obj, function (o) {
+ onObjectLoaded(o, index);
+ });
+ break;
+ default:
+ var klass = fabric[fabric.util.string.camelize(fabric.util.string.capitalize(obj.type))];
+ if (klass && klass.fromObject) {
+ // restore path
+ if (path) {
+ obj[pathProp] = path;
+ }
+ onObjectLoaded(klass.fromObject(obj), index);
+ }
+ break;
+ }
+ }
+ else {
+ if (obj.type === 'image') {
+ fabric.util.loadImage(path, function (image) {
+ var oImg = new fabric.Image(image);
+
+ oImg.setSourcePath(path);
+
+ fabric.util.object.extend(oImg, obj);
+ oImg.setAngle(obj.angle);
+
+ onObjectLoaded(oImg, index);
+ });
+ }
+ else if (obj.type === 'text') {
+
+ if (obj.useNative) {
+ onObjectLoaded(fabric.Text.fromObject(obj), index);
+ }
+ else {
+ obj.path = path;
+ var object = fabric.Text.fromObject(obj);
+ var onscriptload = function () {
+ // TODO (kangax): find out why Opera refuses to work without this timeout
+ if (Object.prototype.toString.call(fabric.window.opera) === '[object Opera]') {
+ setTimeout(function () {
+ onObjectLoaded(object, index);
+ }, 500);
+ }
+ else {
+ onObjectLoaded(object, index);
+ }
+ }
+
+ fabric.util.getScript(path, onscriptload);
+ }
+ }
+ else {
+ fabric.loadSVGFromURL(path, function (elements, options) {
+ var object = fabric.util.groupSVGElements(elements, obj, path);
+
+ // copy parameters from serialied json to object (left, top, scaleX, scaleY, etc.)
+ // skip this step if an object is a PathGroup, since we already passed it options object before
+ if (!(object instanceof fabric.PathGroup)) {
+ fabric.util.object.extend(object, obj);
+ if (typeof obj.angle !== 'undefined') {
+ object.setAngle(obj.angle);
+ }
+ }
+
+ onObjectLoaded(object, index);
+ });
+ }
+ }
+ }, this);
+ }
+ catch(e) {
+ fabric.log(e.message);
+ }
+ },
+
+ /**
+ * Populates canvas with data from the specified JSON
+ * JSON format must conform to the one of `fabric.Canvas#toJSON`
+ * @method loadFromJSON
+ * @param {String} json JSON string
+ * @param {Function} callback Callback, invoked when json is parsed
+ * and corresponding objects (e.g: fabric.Image)
+ * are initialized
+ * @return {fabric.Canvas} instance
+ * @chainable
+ */
+ loadFromJSON: function (json, callback) {
+ if (!json) return;
+
+ var serialized = JSON.parse(json);
+ if (!serialized || (serialized && !serialized.objects)) return;
+
+ this.clear();
+ var _this = this;
+ this._enlivenObjects(serialized.objects, function () {
+ _this.backgroundColor = serialized.background;
+
+ if (serialized.backgroundImage) {
+ _this.setBackgroundImage(serialized.backgroundImage, function() {
+
+ _this.backgroundImageOpacity = serialized.backgroundImageOpacity;
+ _this.backgroundImageStretch = serialized.backgroundImageStretch;
+
+ _this.renderAll();
+
+ callback && callback();
+ });
+ }
+ else {
+ callback && callback();
+ }
+ });
+
+ return this;
+ },
+
+ /**
+ * @method _enlivenObjects
+ * @param {Array} objects
+ * @param {Function} callback
+ */
+ _enlivenObjects: function (objects, callback) {
+ var _this = this;
+ fabric.util.enlivenObjects(objects, function(enlivenedObjects) {
+ enlivenedObjects.forEach(function(obj, index) {
+ _this.insertAt(obj, index, true);
+ });
+ callback && callback();
+ });
+ },
+
+ /**
+ * @private
+ * @method _toDataURL
+ * @param {String} format
+ * @param {Function} callback
+ */
+ _toDataURL: function (format, callback) {
+ this.clone(function (clone) {
+ callback(clone.toDataURL(format));
+ });
+ },
+
+ /**
+ * @private
+ * @method _toDataURLWithMultiplier
+ * @param {String} format
+ * @param {Number} multiplier
+ * @param {Function} callback
+ */
+ _toDataURLWithMultiplier: function (format, multiplier, callback) {
+ this.clone(function (clone) {
+ callback(clone.toDataURLWithMultiplier(format, multiplier));
+ });
+ },
+
+ /**
+ * Clones canvas instance
+ * @method clone
+ * @param {Object} [callback] Receives cloned instance as a first argument
+ */
+ clone: function (callback) {
+ var data = JSON.stringify(this);
+ this.cloneWithoutData(function(clone) {
+ clone.loadFromJSON(data, function() {
+ callback && callback(clone);
+ });
+ });
+ },
+
+ /**
+ * Clones canvas instance without cloning existing data.
+ * This essentially copies canvas dimensions, clipping properties, etc.
+ * but leaves data empty (so that you can populate it with your own)
+ * @method cloneWithoutData
+ * @param {Object} [callback] Receives cloned instance as a first argument
+ */
+ cloneWithoutData: function(callback) {
+ var el = fabric.document.createElement('canvas');
+
+ el.width = this.getWidth();
+ el.height = this.getHeight();
+
+ var clone = new fabric.Canvas(el);
+ clone.clipTo = this.clipTo;
+ if (this.backgroundImage) {
+ clone.setBackgroundImage(this.backgroundImage.src, function() {
+ clone.renderAll();
+ callback && callback(clone);
+ });
+ clone.backgroundImageOpacity = this.backgroundImageOpacity;
+ clone.backgroundImageStretch = this.backgroundImageStretch;
+ }
+ else {
+ callback && callback(clone);
+ }
+ }
+});
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ clone = fabric.util.object.clone,
+ toFixed = fabric.util.toFixed,
+ capitalize = fabric.util.string.capitalize,
+ getPointer = fabric.util.getPointer,
+ degreesToRadians = fabric.util.degreesToRadians,
+ slice = Array.prototype.slice;
+
+ if (fabric.Object) {
+ return;
+ }
+
+ /**
+ * @class Object
+ * @memberOf fabric
+ */
+ fabric.Object = fabric.util.createClass(/** @scope fabric.Object.prototype */ {
+
+ /**
+ * Type of an object (rect, circle, path, etc)
+ * @property
+ * @type String
+ */
+ type: 'object',
+
+ /**
+ * @property
+ * @type Number
+ */
+ top: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ left: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ width: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ height: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ scaleX: 1,
+
+ /**
+ * @property
+ * @type Number
+ */
+ scaleY: 1,
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ flipX: false,
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ flipY: false,
+
+ /**
+ * @property
+ * @type Number
+ */
+ opacity: 1,
+
+ /**
+ * @property
+ * @type Number
+ */
+ angle: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ cornersize: 12,
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ transparentCorners: true,
+
+ /**
+ * @property
+ * @type Number
+ */
+ padding: 0,
+
+ /**
+ * @property
+ * @type String
+ */
+ borderColor: 'rgba(102,153,255,0.75)',
+
+ /**
+ * @property
+ * @type String
+ */
+ cornerColor: 'rgba(102,153,255,0.5)',
+
+ /**
+ * @property
+ * @type String
+ */
+ fill: 'rgb(0,0,0)',
+
+ /**
+ * @property
+ * @type String
+ */
+ fillRule: 'source-over',
+
+ /**
+ * @property
+ * @type String
+ */
+ overlayFill: null,
+
+ /**
+ * @property
+ * @type String
+ */
+ stroke: null,
+
+ /**
+ * @property
+ * @type Number
+ */
+ strokeWidth: 1,
+
+ /**
+ * @property
+ * @type Array
+ */
+ strokeDashArray: null,
+
+ /**
+ * @property
+ * @type Number
+ */
+ borderOpacityWhenMoving: 0.4,
+
+ /**
+ * @property
+ * @type Number
+ */
+ borderScaleFactor: 1,
+
+ /**
+ * Transform matrix
+ * @property
+ * @type Array
+ */
+ transformMatrix: null,
+
+ /**
+ * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection)
+ * @property
+ * @type Boolean
+ */
+ selectable: true,
+
+ /**
+ * When set to `false`, object's controls are not displayed and can not be used to manipulate object
+ * @property
+ * @type Boolean
+ */
+ hasControls: true,
+
+ /**
+ * When set to `false`, object's borders are not rendered
+ * @property
+ * @type Boolean
+ */
+ hasBorders: true,
+
+ /**
+ * When set to `false`, object's rotating point will not be visible or selectable
+ * @property
+ * @type Boolean
+ */
+ hasRotatingPoint: false,
+
+ /**
+ * Offset for object's rotating point (when enabled)
+ * @property
+ * @type Number
+ */
+ rotatingPointOffset: 40,
+
+ /**
+ * @private
+ * @property
+ * @type Number
+ */
+ _theta: 0,
+
+ perPixelTargetFind: false,
+
+ includeDefaultValues: true,
+
+ /**
+ * List of properties to consider when checking if state of an object is changed (fabric.Object#hasStateChanged);
+ * as well as for history (undo/redo) purposes
+ * @property
+ * @type Array
+ */
+ stateProperties: (
+ 'top left width height scaleX scaleY flipX flipY ' +
+ 'theta angle opacity cornersize fill overlayFill ' +
+ 'stroke strokeWidth strokeDashArray fillRule ' +
+ 'borderScaleFactor transformMatrix selectable'
+ ).split(' '),
+
+ /**
+ * @method callSuper
+ * @param {String} methodName
+ */
+ callSuper: function(methodName) {
+ var fn = this.constructor.superclass.prototype[methodName];
+ return (arguments.length > 1)
+ ? fn.apply(this, slice.call(arguments, 1))
+ : fn.call(this);
+ },
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ if (options) {
+ this.setOptions(options);
+ this._initGradient(options);
+ }
+ },
+
+ /**
+ * @method initGradient
+ */
+ _initGradient: function(options) {
+ if (options.fill && typeof options.fill == 'object' && !(options.fill instanceof fabric.Gradient)) {
+ this.set('fill', new fabric.Gradient(options.fill));
+ }
+ },
+
+ /**
+ * @method setOptions
+ * @param {Object} [options]
+ */
+ setOptions: function(options) {
+ var i = this.stateProperties.length, prop;
+ while (i--) {
+ prop = this.stateProperties[i];
+ if (prop in options) {
+ this.set(prop, options[prop]);
+ }
+ }
+ },
+
+ /**
+ * @method transform
+ * @param {CanvasRenderingContext2D} ctx Context
+ */
+ transform: function(ctx) {
+ ctx.globalAlpha = this.opacity;
+ ctx.translate(this.left, this.top);
+ ctx.rotate(this._theta);
+ ctx.scale(
+ this.scaleX * (this.flipX ? -1 : 1),
+ this.scaleY * (this.flipY ? -1 : 1)
+ );
+ },
+
+ /**
+ * Returns an object representation of an instance
+ * @method toObject
+ * @return {Object}
+ */
+ toObject: function() {
+
+ var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS;
+
+ var object = {
+ type: this.type,
+ left: toFixed(this.left, NUM_FRACTION_DIGITS),
+ top: toFixed(this.top, NUM_FRACTION_DIGITS),
+ width: toFixed(this.width, NUM_FRACTION_DIGITS),
+ height: toFixed(this.height, NUM_FRACTION_DIGITS),
+ fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill,
+ overlayFill: this.overlayFill,
+ stroke: this.stroke,
+ strokeWidth: this.strokeWidth,
+ strokeDashArray: this.strokeDashArray,
+ scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS),
+ scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS),
+ angle: toFixed(this.getAngle(), NUM_FRACTION_DIGITS),
+ flipX: this.flipX,
+ flipY: this.flipY,
+ opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS),
+ selectable: this.selectable,
+ hasControls: this.hasControls,
+ hasBorders: this.hasBorders,
+ hasRotatingPoint: this.hasRotatingPoint,
+ transparentCorners: this.transparentCorners,
+ perPixelTargetFind: this.perPixelTargetFind
+ };
+
+ if (!this.includeDefaultValues) {
+ object = this._removeDefaultValues(object);
+ }
+
+ return object;
+ },
+
+ /**
+ * Returns (dataless) object representation of an instance
+ * @method toDatalessObject
+ */
+ toDatalessObject: function() {
+ // will be overwritten by subclasses
+ return this.toObject();
+ },
+
+ /**
+ * Returns styles-string for svg-export
+ * @method getSvgStyles
+ * @return {string}
+ */
+ getSvgStyles: function() {
+ return [
+ "stroke: ", (this.stroke ? this.stroke : 'none'), "; ",
+ "stroke-width: ", (this.strokeWidth ? this.strokeWidth : '0'), "; ",
+ "stroke-dasharray: ", (this.strokeDashArray ? this.strokeDashArray.join(' ') : "; "),
+ "fill: ", (this.fill ? this.fill : 'none'), "; ",
+ "opacity: ", (this.opacity ? this.opacity : '1'), ";"
+ ].join("");
+ },
+
+ /**
+ * Returns transform-string for svg-export
+ * @method getSvgTransform
+ * @return {string}
+ */
+ getSvgTransform: function() {
+ var angle = this.getAngle();
+ return [
+ "translate(", toFixed(this.left, 2), " ", toFixed(this.top, 2), ")",
+ angle !== 0 ? (" rotate(" + toFixed(angle, 2) + ")") : '',
+ (this.scaleX === 1 && this.scaleY === 1) ? '' : (" scale(" + toFixed(this.scaleX, 2) + " " + toFixed(this.scaleY, 2) + ")")
+ ].join('');
+ },
+
+ /**
+ * @private
+ * @method _removeDefaultValues
+ */
+ _removeDefaultValues: function(object) {
+ var defaultOptions = fabric.Object.prototype.options;
+ if (defaultOptions) {
+ this.stateProperties.forEach(function(prop) {
+ if (object[prop] === defaultOptions[prop]) {
+ delete object[prop];
+ }
+ });
+ }
+ return object;
+ },
+
+ /**
+ * Returns true if an object is in its active state
+ * @return {Boolean} true if an object is in its active state
+ */
+ isActive: function() {
+ return !!this.active;
+ },
+
+ /**
+ * Sets state of an object - `true` makes it active, `false` - inactive
+ * @param {Boolean} active
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ setActive: function(active) {
+ this.active = !!active;
+ return this;
+ },
+
+ /**
+ * Returns a string representation of an instance
+ * @return {String}
+ */
+ toString: function() {
+ return "#<fabric." + capitalize(this.type) + ">";
+ },
+
+ /**
+ * Sets property to a given value
+ * @method set
+ * @param {String} name
+ * @param {Object|Function} value
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ set: function(key, value) {
+ if (typeof key === 'object') {
+ for (var prop in key) {
+ this._set(prop, key[prop]);
+ }
+ }
+ else {
+ if (typeof value === 'function') {
+ this._set(key, value(this.get(key)));
+ }
+ else {
+ this._set(key, value);
+ }
+ }
+ return this;
+ },
+
+ _set: function(key, value) {
+ var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY') &&
+ value < fabric.Object.MIN_SCALE_LIMIT;
+
+ if (shouldConstrainValue) {
+ value = fabric.Object.MIN_SCALE_LIMIT;
+ }
+ if (key === 'angle') {
+ this.setAngle(value);
+ }
+ else {
+ this[key] = value;
+ }
+ },
+
+ /**
+ * Toggles specified property from `true` to `false` or from `false` to `true`
+ * @method toggle
+ * @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;
+ },
+
+ /**
+ * @method setSourcePath
+ * @param {String} value
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ setSourcePath: function(value) {
+ this.sourcePath = value;
+ return this;
+ },
+
+ /**
+ * Basic getter
+ * @method get
+ * @param {Any} property
+ * @return {Any} value of a property
+ */
+ get: function(property) {
+ return (property === 'angle')
+ ? this.getAngle()
+ : this[property];
+ },
+
+ /**
+ * @method render
+ * @param {CanvasRenderingContext2D} ctx context to render on
+ * @param {Boolean} noTransform
+ */
+ render: function(ctx, noTransform) {
+
+ // do not render if width or height are zeros
+ if (this.width === 0 || this.height === 0) return;
+
+ ctx.save();
+
+ var m = this.transformMatrix;
+ if (m && !this.group) {
+ ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+
+ if (!noTransform) {
+ this.transform(ctx);
+ }
+
+ if (this.stroke || this.strokeDashArray) {
+ ctx.lineWidth = this.strokeWidth;
+ ctx.strokeStyle = this.stroke;
+ }
+
+ if (this.overlayFill) {
+ ctx.fillStyle = this.overlayFill;
+ }
+ else if (this.fill) {
+ ctx.fillStyle = this.fill.toLiveGradient
+ ? this.fill.toLiveGradient(ctx)
+ : this.fill;
+ }
+
+ if (m && this.group) {
+ ctx.translate(-this.group.width/2, -this.group.height/2);
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+
+ this._render(ctx, noTransform);
+
+ if (this.active && !noTransform) {
+ this.drawBorders(ctx);
+ this.drawCorners(ctx);
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Returns width of an object
+ * @method getWidth
+ * @return {Number} width value
+ */
+ getWidth: function() {
+ return this.width * this.scaleX;
+ },
+
+ /**
+ * Returns height of an object
+ * @method getHeight
+ * @return {Number} height value
+ */
+ getHeight: function() {
+ return this.height * this.scaleY;
+ },
+
+ /**
+ * Scales an object (equally by x and y)
+ * @method scale
+ * @param value {Number} scale factor
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ scale: function(value) {
+ this.scaleX = value;
+ this.scaleY = value;
+ this.setCoords();
+ return this;
+ },
+
+ /**
+ * Scales an object to a given width, with respect to bounding box (scaling by x/y equally)
+ * @method scaleToWidth
+ * @param value {Number} new width value
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ scaleToWidth: function(value) {
+ // adjust to bounding rect factor so that rotated shapes would fit as well
+ var boundingRectFactor = this.getBoundingRectWidth() / this.getWidth();
+ return this.scale(value / this.width / boundingRectFactor);
+ },
+
+ /**
+ * Scales an object to a given height, with respect to bounding box (scaling by x/y equally)
+ * @method scaleToHeight
+ * @param value {Number} new height value
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ scaleToHeight: function(value) {
+ // adjust to bounding rect factor so that rotated shapes would fit as well
+ var boundingRectFactor = this.getBoundingRectHeight() / this.getHeight();
+ return this.scale(value / this.height / boundingRectFactor);
+ },
+
+ /**
+ * Sets object opacity
+ * @method setOpacity
+ * @param value {Number} value 0-1
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ setOpacity: function(value) {
+ this.set('opacity', value);
+ return this;
+ },
+
+ /**
+ * Returns object's angle value
+ * @method getAngle
+ * @return {Number} angle value
+ */
+ getAngle: function() {
+ return this._theta * 180 / Math.PI;
+ },
+
+ /**
+ * Sets object's angle
+ * @method setAngle
+ * @param value {Number} angle value
+ * @return {Object} thisArg
+ */
+ setAngle: function(value) {
+ this._theta = value / 180 * Math.PI;
+ this.angle = value;
+ return this;
+ },
+
+ /**
+ * Sets corner position coordinates based on current angle, width and height.
+ * @method setCoords
+ * return {fabric.Object} thisArg
+ * @chainable
+ */
+ setCoords: function() {
+
+ var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0,
+ padding = this.padding;
+
+ this.currentWidth = (this.width + strokeWidth) * this.scaleX + padding * 2;
+ this.currentHeight = (this.height + strokeWidth) * this.scaleY + padding * 2;
+
+ this._hypotenuse = Math.sqrt(
+ Math.pow(this.currentWidth / 2, 2) +
+ Math.pow(this.currentHeight / 2, 2));
+
+ this._angle = Math.atan(this.currentHeight / this.currentWidth);
+
+ // offset added for rotate and scale actions
+ var offsetX = Math.cos(this._angle + this._theta) * this._hypotenuse,
+ offsetY = Math.sin(this._angle + this._theta) * this._hypotenuse,
+ theta = this._theta,
+ sinTh = Math.sin(theta),
+ cosTh = Math.cos(theta);
+
+ var tl = {
+ x: this.left - offsetX,
+ y: this.top - offsetY
+ };
+ var tr = {
+ x: tl.x + (this.currentWidth * cosTh),
+ y: tl.y + (this.currentWidth * sinTh)
+ };
+ var br = {
+ x: tr.x - (this.currentHeight * sinTh),
+ y: tr.y + (this.currentHeight * cosTh)
+ };
+ var bl = {
+ x: tl.x - (this.currentHeight * sinTh),
+ y: tl.y + (this.currentHeight * cosTh)
+ };
+ var ml = {
+ x: tl.x - (this.currentHeight/2 * sinTh),
+ y: tl.y + (this.currentHeight/2 * cosTh)
+ };
+ var mt = {
+ x: tl.x + (this.currentWidth/2 * cosTh),
+ y: tl.y + (this.currentWidth/2 * sinTh)
+ };
+ var mr = {
+ x: tr.x - (this.currentHeight/2 * sinTh),
+ y: tr.y + (this.currentHeight/2 * cosTh)
+ };
+ var mb = {
+ x: bl.x + (this.currentWidth/2 * cosTh),
+ y: bl.y + (this.currentWidth/2 * sinTh)
+ };
+ var mtr = {
+ x: tl.x + (this.currentWidth/2 * cosTh),
+ y: tl.y + (this.currentWidth/2 * sinTh)
+ };
+
+ // debugging
+
+ // setTimeout(function() {
+ // canvas.contextTop.fillStyle = 'green';
+ // canvas.contextTop.fillRect(mb.x, mb.y, 3, 3);
+ // canvas.contextTop.fillRect(bl.x, bl.y, 3, 3);
+ // canvas.contextTop.fillRect(br.x, br.y, 3, 3);
+ // canvas.contextTop.fillRect(tl.x, tl.y, 3, 3);
+ // canvas.contextTop.fillRect(tr.x, tr.y, 3, 3);
+ // canvas.contextTop.fillRect(ml.x, ml.y, 3, 3);
+ // canvas.contextTop.fillRect(mr.x, mr.y, 3, 3);
+ // canvas.contextTop.fillRect(mt.x, mt.y, 3, 3);
+ // }, 50);
+
+ // clockwise
+ this.oCoords = { tl: tl, tr: tr, br: br, bl: bl, ml: ml, mt: mt, mr: mr, mb: mb, mtr: mtr };
+
+ // set coordinates of the draggable boxes in the corners used to scale/rotate the image
+ this._setCornerCoords();
+
+ return this;
+ },
+
+ /**
+ * Returns width of an object's bounding rectangle
+ * @method getBoundingRectWidth
+ * @return {Number} width value
+ */
+ getBoundingRectWidth: function() {
+ this.oCoords || this.setCoords();
+ var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x];
+ var minX = fabric.util.array.min(xCoords);
+ var maxX = fabric.util.array.max(xCoords);
+ return Math.abs(minX - maxX);
+ },
+
+ /**
+ * Returns height of an object's bounding rectangle
+ * @method getBoundingRectHeight
+ * @return {Number} height value
+ */
+ getBoundingRectHeight: function() {
+ this.oCoords || this.setCoords();
+ var yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y];
+ var minY = fabric.util.array.min(yCoords);
+ var maxY = fabric.util.array.max(yCoords);
+ return Math.abs(minY - maxY);
+ },
+
+ /**
+ * Draws borders of an object's bounding box.
+ * Requires public properties: width, height
+ * Requires public options: padding, borderColor
+ * @method drawBorders
+ * @param {CanvasRenderingContext2D} ctx Context to draw on
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ drawBorders: function(ctx) {
+ if (!this.hasBorders) return;
+
+ var MIN_SCALE_LIMIT = fabric.Object.MIN_SCALE_LIMIT,
+ padding = this.padding,
+ padding2 = padding * 2,
+ strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0;
+
+ ctx.save();
+
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
+ ctx.strokeStyle = this.borderColor;
+
+ var scaleX = 1 / (this.scaleX < MIN_SCALE_LIMIT ? MIN_SCALE_LIMIT : this.scaleX),
+ scaleY = 1 / (this.scaleY < MIN_SCALE_LIMIT ? MIN_SCALE_LIMIT : this.scaleY);
+
+ ctx.lineWidth = 1 / this.borderScaleFactor;
+
+ ctx.scale(scaleX, scaleY);
+
+ var w = this.getWidth(),
+ h = this.getHeight();
+
+ ctx.strokeRect(
+ ~~(-(w / 2) - padding - strokeWidth / 2 * this.scaleX) + 0.5, // offset needed to make lines look sharper
+ ~~(-(h / 2) - padding - strokeWidth / 2 * this.scaleY) + 0.5,
+ ~~(w + padding2 + strokeWidth * this.scaleX),
+ ~~(h + padding2 + strokeWidth * this.scaleY)
+ );
+
+ if (this.hasRotatingPoint && !this.lockRotation && this.hasControls) {
+
+ var rotateHeight = (
+ this.flipY
+ ? h + (strokeWidth * this.scaleY) + (padding * 2)
+ : -h - (strokeWidth * this.scaleY) - (padding * 2)
+ ) / 2;
+
+ var rotateWidth = (-w/2);
+
+ ctx.beginPath();
+ ctx.moveTo(0, rotateHeight);
+ ctx.lineTo(0, rotateHeight + (this.flipY ? this.rotatingPointOffset : -this.rotatingPointOffset));
+ ctx.closePath();
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ return this;
+ },
+
+ _renderDashedStroke: function(ctx) {
+
+ if (1 & this.strokeDashArray.length /* if odd number of items */) {
+ /* duplicate items */
+ this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray);
+ }
+
+ var i = 0,
+ x = -this.width/2, y = -this.height/2,
+ _this = this,
+ padding = this.padding,
+ width = this.getWidth(),
+ height = this.getHeight(),
+ dashedArrayLength = this.strokeDashArray.length;
+
+ ctx.save();
+ ctx.beginPath();
+
+ function renderSide(xMultiplier, yMultiplier) {
+
+ var lineLength = 0,
+ sideLength = (yMultiplier ? _this.height : _this.width) + padding * 2;
+
+ while (lineLength < sideLength) {
+
+ var lengthOfSubPath = _this.strokeDashArray[i++];
+ lineLength += lengthOfSubPath;
+
+ if (lineLength > sideLength) {
+ var lengthDiff = lineLength - sideLength;
+ }
+
+ // track coords
+ if (xMultiplier) {
+ x += (lengthOfSubPath * xMultiplier) - (lengthDiff * xMultiplier || 0);
+ }
+ else {
+ y += (lengthOfSubPath * yMultiplier) - (lengthDiff * yMultiplier || 0);
+ }
+
+ ctx[1 & i /* odd */ ? 'moveTo' : 'lineTo'](x, y);
+ if (i >= dashedArrayLength) {
+ i = 0;
+ }
+ }
+ }
+
+ renderSide(1, 0);
+ renderSide(0, 1);
+ renderSide(-1, 0);
+ renderSide(0, -1);
+
+ ctx.stroke();
+ ctx.closePath();
+ ctx.restore();
+ },
+
+ /**
+ * Draws corners of an object's bounding box.
+ * Requires public properties: width, height, scaleX, scaleY
+ * Requires public options: cornersize, padding
+ * @method drawCorners
+ * @param {CanvasRenderingContext2D} ctx Context to draw on
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ drawCorners: function(ctx) {
+ if (!this.hasControls) return;
+
+ var size = this.cornersize,
+ size2 = size / 2,
+ strokeWidth2 = this.strokeWidth / 2,
+ left = -(this.width / 2),
+ top = -(this.height / 2),
+ _left,
+ _top,
+ sizeX = size / this.scaleX,
+ sizeY = size / this.scaleY,
+ paddingX = this.padding / this.scaleX,
+ paddingY = this.padding / this.scaleY,
+ scaleOffsetY = size2 / this.scaleY,
+ scaleOffsetX = size2 / this.scaleX,
+ scaleOffsetSizeX = (size2 - size) / this.scaleX,
+ scaleOffsetSizeY = (size2 - size) / this.scaleY,
+ height = this.height,
+ width = this.width,
+ methodName = this.transparentCorners ? 'strokeRect' : 'fillRect';
+
+ ctx.save();
+
+ ctx.lineWidth = 1 / Math.max(this.scaleX, this.scaleY);
+
+ ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
+ ctx.strokeStyle = ctx.fillStyle = this.cornerColor;
+
+ // top-left
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // top-right
+ _left = left + width - scaleOffsetX + strokeWidth2 + paddingX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // bottom-left
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // bottom-right
+ _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ if (!this.lockUniScaling) {
+ // middle-top
+ _left = left + width/2 - scaleOffsetX;
+ _top = top - scaleOffsetY - strokeWidth2 - paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // middle-bottom
+ _left = left + width/2 - scaleOffsetX;
+ _top = top + height + scaleOffsetSizeY + strokeWidth2 + paddingY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // middle-right
+ _left = left + width + scaleOffsetSizeX + strokeWidth2 + paddingX;
+ _top = top + height/2 - scaleOffsetY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+
+ // middle-left
+ _left = left - scaleOffsetX - strokeWidth2 - paddingX;
+ _top = top + height/2 - scaleOffsetY;
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ }
+
+ // middle-top-rotate
+ if (this.hasRotatingPoint) {
+
+ _left = left + width/2 - scaleOffsetX;
+
+ _top = this.flipY ?
+ (top + height + (this.rotatingPointOffset / this.scaleY) - sizeY/2 + strokeWidth2 + paddingY)
+ : (top - (this.rotatingPointOffset / this.scaleY) - sizeY/2 - strokeWidth2 - paddingY);
+
+ ctx.clearRect(_left, _top, sizeX, sizeY);
+ ctx[methodName](_left, _top, sizeX, sizeY);
+ }
+
+ ctx.restore();
+
+ return this;
+ },
+
+ /**
+ * Clones an instance
+ * @method clone
+ * @param {Object} options object
+ * @return {fabric.Object} clone of an instance
+ */
+ clone: function(options) {
+ if (this.constructor.fromObject) {
+ return this.constructor.fromObject(this.toObject(), options);
+ }
+ return new fabric.Object(this.toObject());
+ },
+
+ /**
+ * Creates an instance of fabric.Image out of an object
+ * @method cloneAsImage
+ * @param callback {Function} callback, invoked with an instance as a first argument
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ cloneAsImage: function(callback) {
+ if (fabric.Image) {
+ var i = new Image();
+
+ /** @ignore */
+ i.onload = function() {
+ if (callback) {
+ callback(new fabric.Image(i), orig);
+ }
+ i = i.onload = null;
+ };
+
+ var orig = {
+ angle: this.get('angle'),
+ flipX: this.get('flipX'),
+ flipY: this.get('flipY')
+ };
+
+ // normalize angle
+ this.set('angle', 0).set('flipX', false).set('flipY', false);
+ this.toDataURL(function(dataURL) {
+ i.src = dataURL;
+ });
+ }
+ return this;
+ },
+
+ /**
+ * Converts an object into a data-url-like string
+ * @method toDataURL
+ * @return {String} string of data
+ */
+ toDataURL: function(callback) {
+ var el = fabric.document.createElement('canvas');
+ if (!el.getContext && typeof G_vmlCanvasManager != 'undefined') {
+ G_vmlCanvasManager.initElement(el);
+ }
+
+ el.width = this.getBoundingRectWidth();
+ el.height = this.getBoundingRectHeight();
+
+ fabric.util.wrapElement(el, 'div');
+
+ var canvas = new fabric.Canvas(el);
+ canvas.backgroundColor = 'transparent';
+ canvas.renderAll();
+
+ if (this.constructor.async) {
+ this.clone(proceed);
+ }
+ else {
+ proceed(this.clone());
+ }
+
+ function proceed(clone) {
+ clone.left = el.width / 2;
+ clone.top = el.height / 2;
+
+ clone.setActive(false);
+
+ canvas.add(clone);
+ var data = canvas.toDataURL('png');
+
+ canvas.dispose();
+ canvas = clone = null;
+
+ callback && callback(data);
+ }
+ },
+
+ /**
+ * @method hasStateChanged
+ * @return {Boolean} true if instance' state has changed
+ */
+ hasStateChanged: function() {
+ return this.stateProperties.some(function(prop) {
+ return this[prop] !== this.originalState[prop];
+ }, this);
+ },
+
+ /**
+ * @method saveState
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ saveState: function() {
+ this.stateProperties.forEach(function(prop) {
+ this.originalState[prop] = this.get(prop);
+ }, this);
+ return this;
+ },
+
+ /**
+ * @method setupState
+ */
+ setupState: function() {
+ this.originalState = { };
+ this.saveState();
+ },
+
+ /**
+ * Returns true if object intersects with an area formed by 2 points
+ * @method intersectsWithRect
+ * @param {Object} selectionTL
+ * @param {Object} selectionBR
+ * @return {Boolean}
+ */
+ intersectsWithRect: function(selectionTL, selectionBR) {
+ var oCoords = this.oCoords,
+ tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
+ tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
+ bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
+ br = new fabric.Point(oCoords.br.x, oCoords.br.y);
+
+ var intersection = fabric.Intersection.intersectPolygonRectangle(
+ [tl, tr, br, bl],
+ selectionTL,
+ selectionBR
+ );
+ return (intersection.status === 'Intersection');
+ },
+
+ /**
+ * Returns true if object intersects with another object
+ * @method intersectsWithObject
+ * @param {Object} other Object to test
+ * @return {Boolean}
+ */
+ intersectsWithObject: function(other) {
+ // extracts coords
+ function getCoords(oCoords) {
+ return {
+ tl: new fabric.Point(oCoords.tl.x, oCoords.tl.y),
+ tr: new fabric.Point(oCoords.tr.x, oCoords.tr.y),
+ bl: new fabric.Point(oCoords.bl.x, oCoords.bl.y),
+ br: new fabric.Point(oCoords.br.x, oCoords.br.y)
+ }
+ }
+ var thisCoords = getCoords(this.oCoords),
+ otherCoords = getCoords(other.oCoords);
+
+ var intersection = fabric.Intersection.intersectPolygonPolygon(
+ [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl],
+ [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl]
+ );
+
+ return (intersection.status === 'Intersection');
+ },
+
+ /**
+ * Returns true if object is fully contained within area of another object
+ * @method isContainedWithinObject
+ * @param {Object} other Object to test
+ * @return {Boolean}
+ */
+ isContainedWithinObject: function(other) {
+ return this.isContainedWithinRect(other.oCoords.tl, other.oCoords.br);
+ },
+
+ /**
+ * Returns true if object is fully contained within area formed by 2 points
+ * @method isContainedWithinRect
+ * @param {Object} selectionTL
+ * @param {Object} selectionBR
+ * @return {Boolean}
+ */
+ isContainedWithinRect: function(selectionTL, selectionBR) {
+ var oCoords = this.oCoords,
+ tl = new fabric.Point(oCoords.tl.x, oCoords.tl.y),
+ tr = new fabric.Point(oCoords.tr.x, oCoords.tr.y),
+ bl = new fabric.Point(oCoords.bl.x, oCoords.bl.y),
+ br = new fabric.Point(oCoords.br.x, oCoords.br.y);
+
+ return tl.x > selectionTL.x
+ && tr.x < selectionBR.x
+ && tl.y > selectionTL.y
+ && bl.y < selectionBR.y;
+ },
+
+ /**
+ * @method isType
+ * @param type {String} type to check against
+ * @return {Boolean} true if specified type is identical to the type of instance
+ */
+ isType: function(type) {
+ return this.type === type;
+ },
+
+ /**
+ * Determines which one of the four corners has been clicked
+ * @method _findTargetCorner
+ * @private
+ * @param e {Event} event object
+ * @param offset {Object} canvas offset
+ * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found
+ */
+ _findTargetCorner: function(e, offset) {
+ if (!this.hasControls || !this.active) return false;
+
+ var pointer = getPointer(e),
+ ex = pointer.x - offset.left,
+ ey = pointer.y - offset.top,
+ xpoints,
+ lines;
+
+ for (var i in this.oCoords) {
+
+ if (i === 'mtr' && !this.hasRotatingPoint) {
+ continue;
+ }
+
+ if (this.lockUniScaling && (i === 'mt' || i === 'mr' || i === 'mb' || i === 'ml')) {
+ continue;
+ }
+
+ lines = this._getImageLines(this.oCoords[i].corner, i);
+
+ // debugging
+
+ // canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2);
+ // canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2);
+
+ // canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2);
+ // canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2);
+
+ // canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2);
+ // canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2);
+
+ // canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2);
+ // canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2);
+
+ xpoints = this._findCrossPoints(ex, ey, lines);
+ if (xpoints % 2 == 1 && xpoints != 0) {
+ this.__corner = i;
+ return i;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Helper method to determine how many cross points are between the 4 image edges
+ * and the horizontal line determined by the position of our mouse when clicked on canvas
+ * @method _findCrossPoints
+ * @private
+ * @param ex {Number} x coordinate of the mouse
+ * @param ey {Number} y coordinate of the mouse
+ * @param oCoords {Object} Coordinates of the image being evaluated
+ */
+ _findCrossPoints: function(ex, ey, oCoords) {
+ var b1, b2, a1, a2, xi, yi,
+ xcount = 0,
+ iLine;
+
+ for (var lineKey in oCoords) {
+ iLine = oCoords[lineKey];
+ // optimisation 1: line below dot. no cross
+ if ((iLine.o.y < ey) && (iLine.d.y < ey)) {
+ continue;
+ }
+ // optimisation 2: line above dot. no cross
+ if ((iLine.o.y >= ey) && (iLine.d.y >= ey)) {
+ continue;
+ }
+ // optimisation 3: vertical line case
+ if ((iLine.o.x == iLine.d.x) && (iLine.o.x >= ex)) {
+ xi = iLine.o.x;
+ yi = ey;
+ }
+ // calculate the intersection point
+ else {
+ b1 = 0;
+ b2 = (iLine.d.y-iLine.o.y)/(iLine.d.x-iLine.o.x);
+ a1 = ey-b1*ex;
+ a2 = iLine.o.y-b2*iLine.o.x;
+
+ xi = - (a1-a2)/(b1-b2);
+ yi = a1+b1*xi;
+ }
+ // dont count xi < ex cases
+ if (xi >= ex) {
+ xcount += 1;
+ }
+ // optimisation 4: specific for square images
+ if (xcount == 2) {
+ break;
+ }
+ }
+ return xcount;
+ },
+
+ /**
+ * Method that returns an object with the image lines in it given the coordinates of the corners
+ * @method _getImageLines
+ * @private
+ * @param oCoords {Object} coordinates of the image corners
+ */
+ _getImageLines: function(oCoords, i) {
+ return {
+ topline: {
+ o: oCoords.tl,
+ d: oCoords.tr
+ },
+ rightline: {
+ o: oCoords.tr,
+ d: oCoords.br
+ },
+ bottomline: {
+ o: oCoords.br,
+ d: oCoords.bl
+ },
+ leftline: {
+ o: oCoords.bl,
+ d: oCoords.tl
+ }
+ }
+ },
+
+ /**
+ * Sets the coordinates of the draggable boxes in the corners of
+ * the image used to scale/rotate it.
+ * @method _setCornerCoords
+ * @private
+ */
+ _setCornerCoords: function() {
+ var coords = this.oCoords,
+ theta = degreesToRadians(45 - this.getAngle()),
+ cornerHypotenuse = Math.sqrt(2 * Math.pow(this.cornersize, 2)) / 2,
+ cosHalfOffset = cornerHypotenuse * Math.cos(theta),
+ sinHalfOffset = cornerHypotenuse * Math.sin(theta),
+ sinTh = Math.sin(this._theta),
+ cosTh = Math.cos(this._theta);
+
+ coords.tl.corner = {
+ tl: {
+ x: coords.tl.x - sinHalfOffset,
+ y: coords.tl.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.tl.x + cosHalfOffset,
+ y: coords.tl.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.tl.x - cosHalfOffset,
+ y: coords.tl.y + sinHalfOffset
+ },
+ br: {
+ x: coords.tl.x + sinHalfOffset,
+ y: coords.tl.y + cosHalfOffset
+ }
+ };
+
+ coords.tr.corner = {
+ tl: {
+ x: coords.tr.x - sinHalfOffset,
+ y: coords.tr.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.tr.x + cosHalfOffset,
+ y: coords.tr.y - sinHalfOffset
+ },
+ br: {
+ x: coords.tr.x + sinHalfOffset,
+ y: coords.tr.y + cosHalfOffset
+ },
+ bl: {
+ x: coords.tr.x - cosHalfOffset,
+ y: coords.tr.y + sinHalfOffset
+ }
+ };
+
+ coords.bl.corner = {
+ tl: {
+ x: coords.bl.x - sinHalfOffset,
+ y: coords.bl.y - cosHalfOffset
+ },
+ bl: {
+ x: coords.bl.x - cosHalfOffset,
+ y: coords.bl.y + sinHalfOffset
+ },
+ br: {
+ x: coords.bl.x + sinHalfOffset,
+ y: coords.bl.y + cosHalfOffset
+ },
+ tr: {
+ x: coords.bl.x + cosHalfOffset,
+ y: coords.bl.y - sinHalfOffset
+ }
+ };
+
+ coords.br.corner = {
+ tr: {
+ x: coords.br.x + cosHalfOffset,
+ y: coords.br.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.br.x - cosHalfOffset,
+ y: coords.br.y + sinHalfOffset
+ },
+ br: {
+ x: coords.br.x + sinHalfOffset,
+ y: coords.br.y + cosHalfOffset
+ },
+ tl: {
+ x: coords.br.x - sinHalfOffset,
+ y: coords.br.y - cosHalfOffset
+ }
+ };
+
+ coords.ml.corner = {
+ tl: {
+ x: coords.ml.x - sinHalfOffset,
+ y: coords.ml.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.ml.x + cosHalfOffset,
+ y: coords.ml.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.ml.x - cosHalfOffset,
+ y: coords.ml.y + sinHalfOffset
+ },
+ br: {
+ x: coords.ml.x + sinHalfOffset,
+ y: coords.ml.y + cosHalfOffset
+ }
+ };
+
+ coords.mt.corner = {
+ tl: {
+ x: coords.mt.x - sinHalfOffset,
+ y: coords.mt.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mt.x + cosHalfOffset,
+ y: coords.mt.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mt.x - cosHalfOffset,
+ y: coords.mt.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mt.x + sinHalfOffset,
+ y: coords.mt.y + cosHalfOffset
+ }
+ };
+
+ coords.mr.corner = {
+ tl: {
+ x: coords.mr.x - sinHalfOffset,
+ y: coords.mr.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mr.x + cosHalfOffset,
+ y: coords.mr.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mr.x - cosHalfOffset,
+ y: coords.mr.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mr.x + sinHalfOffset,
+ y: coords.mr.y + cosHalfOffset
+ }
+ };
+
+ coords.mb.corner = {
+ tl: {
+ x: coords.mb.x - sinHalfOffset,
+ y: coords.mb.y - cosHalfOffset
+ },
+ tr: {
+ x: coords.mb.x + cosHalfOffset,
+ y: coords.mb.y - sinHalfOffset
+ },
+ bl: {
+ x: coords.mb.x - cosHalfOffset,
+ y: coords.mb.y + sinHalfOffset
+ },
+ br: {
+ x: coords.mb.x + sinHalfOffset,
+ y: coords.mb.y + cosHalfOffset
+ }
+ };
+
+ coords.mtr.corner = {
+ tl: {
+ x: coords.mtr.x - sinHalfOffset + (sinTh * this.rotatingPointOffset),
+ y: coords.mtr.y - cosHalfOffset - (cosTh * this.rotatingPointOffset)
+ },
+ tr: {
+ x: coords.mtr.x + cosHalfOffset + (sinTh * this.rotatingPointOffset),
+ y: coords.mtr.y - sinHalfOffset - (cosTh * this.rotatingPointOffset)
+ },
+ bl: {
+ x: coords.mtr.x - cosHalfOffset + (sinTh * this.rotatingPointOffset),
+ y: coords.mtr.y + sinHalfOffset - (cosTh * this.rotatingPointOffset)
+ },
+ br: {
+ x: coords.mtr.x + sinHalfOffset + (sinTh * this.rotatingPointOffset),
+ y: coords.mtr.y + cosHalfOffset - (cosTh * this.rotatingPointOffset)
+ }
+ };
+ },
+
+ /**
+ * Makes object's color grayscale
+ * @method toGrayscale
+ * @return {fabric.Object} thisArg
+ */
+ toGrayscale: function() {
+ var fillValue = this.get('fill');
+ if (fillValue) {
+ this.set('overlayFill', new fabric.Color(fillValue).toGrayscale().toRgb());
+ }
+ return this;
+ },
+
+ /**
+ * @method complexity
+ * @return {Number}
+ */
+ complexity: function() {
+ return 0;
+ },
+
+ /**
+ * Returns a JSON representation of an instance
+ * @method toJSON
+ * @return {String} json
+ */
+ toJSON: function() {
+ // delegate, not alias
+ return this.toObject();
+ },
+
+ /**
+ * @method setGradientFill
+ */
+ setGradientFill: function(options) {
+ this.set('fill', fabric.Gradient.forObject(this, options));
+ },
+
+ /**
+ * @method animate
+ *
+ * As object — multiple properties
+ *
+ * object.animate({ left: ..., top: ... });
+ * object.animate({ left: ..., top: ... }, { duration: ... });
+ *
+ * As string — one property
+ *
+ * object.animate('left', ...);
+ * object.animate('left', { duration: ... });
+ *
+ */
+ animate: function() {
+ if (arguments[0] && typeof arguments[0] == 'object') {
+ for (var prop in arguments[0]) {
+ this._animate(prop, arguments[0][prop], arguments[1]);
+ }
+ }
+ else {
+ this._animate.apply(this, arguments);
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * @method _animate
+ */
+ _animate: function(property, to, options) {
+ var obj = this;
+
+ options || (options = { });
+
+ if (!('from' in options)) {
+ options.from = this.get(property);
+ }
+
+ if (/[+-]/.test((to + '').charAt(0))) {
+ to = this.get(property) + parseFloat(to);
+ }
+
+ fabric.util.animate({
+ startValue: options.from,
+ endValue: to,
+ byValue: options.by,
+ easing: options.easing,
+ duration: options.duration,
+ onChange: function(value) {
+ obj.set(property, value);
+ options.onChange && options.onChange();
+ },
+ onComplete: function() {
+ obj.setCoords();
+ options.onComplete && options.onComplete();
+ }
+ });
+ },
+
+ /**
+ * Centers object horizontally on canvas to which it was added last
+ * @method centerH
+ * @return {fabric.Object} thisArg
+ */
+ centerH: function () {
+ this.canvas.centerObjectH(this);
+ return this;
+ },
+
+ /**
+ * Centers object vertically on canvas to which it was added last
+ * @method centerV
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ centerV: function () {
+ this.canvas.centerObjectV(this);
+ return this;
+ },
+
+ /**
+ * Centers object vertically and horizontally on canvas to which is was added last
+ * @method center
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ center: function () {
+ return this.centerH().centerV();
+ },
+
+ /**
+ * Removes object from canvas to which it was added last
+ * @method remove
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ remove: function() {
+ return this.canvas.remove(this);
+ },
+
+ /**
+ * Moves an object to the bottom of the stack of drawn objects
+ * @method sendToBack
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ sendToBack: function() {
+ this.canvas.sendToBack(this);
+ return this;
+ },
+
+ /**
+ * Moves an object to the top of the stack of drawn objects
+ * @method bringToFront
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ bringToFront: function() {
+ this.canvas.bringToFront(this);
+ return this;
+ },
+
+ /**
+ * Moves an object one level down in stack of drawn objects
+ * @method sendBackwards
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ sendBackwards: function() {
+ this.canvas.sendBackwards(this);
+ return this;
+ },
+
+ /**
+ * Moves an object one level up in stack of drawn objects
+ * @method bringForward
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ bringForward: function() {
+ this.canvas.bringForward(this);
+ return this;
+ }
+ });
+
+ /**
+ * @alias rotate -> setAngle
+ */
+ fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle;
+
+ var proto = fabric.Object.prototype;
+ for (var i = proto.stateProperties.length; i--; ) {
+
+ var propName = proto.stateProperties[i],
+ capitalizedPropName = propName.charAt(0).toUpperCase() + propName.slice(1),
+ setterName = 'set' + capitalizedPropName,
+ getterName = 'get' + capitalizedPropName;
+
+ // using `new Function` for better introspection
+ if (!proto[getterName]) {
+ proto[getterName] = (function(property) {
+ return new Function('return this.get("' + property + '")');
+ })(propName);
+ }
+ if (!proto[setterName]) {
+ proto[setterName] = (function(property) {
+ return new Function('value', 'return this.set("' + property + '", value)');
+ })(propName);
+ }
+ }
+
+ extend(fabric.Object.prototype, fabric.Observable);
+
+ extend(fabric.Object, {
+
+ /**
+ * @static
+ * @constant
+ * @type Number
+ */
+ NUM_FRACTION_DIGITS: 2,
+
+ /**
+ * @static
+ * @constant
+ * @type Number
+ */
+ MIN_SCALE_LIMIT: 0.1
+
+ });
+
+})(typeof exports != 'undefined' ? exports : this);
+
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ parentSet = fabric.Object.prototype.set,
+ coordProps = { 'x1': 1, 'x2': 1, 'y1': 1, 'y2': 1 };
+
+ if (fabric.Line) {
+ fabric.warn('fabric.Line is already defined');
+ return;
+ }
+
+ /**
+ * @class Line
+ * @extends fabric.Object
+ */
+ fabric.Line = fabric.util.createClass(fabric.Object, /** @scope fabric.Line.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'line',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Array} points Array of points
+ * @param {Object} [options] Options object
+ * @return {fabric.Line} thisArg
+ */
+ initialize: function(points, options) {
+ if (!points) {
+ points = [0, 0, 0, 0];
+ }
+
+ this.callSuper('initialize', options);
+
+ this.set('x1', points[0]);
+ this.set('y1', points[1]);
+ this.set('x2', points[2]);
+ this.set('y2', points[3]);
+
+ this._setWidthHeight(options);
+ },
+
+ /**
+ * @private
+ * @method _setWidthHeight
+ * @param {Object} options
+ */
+ _setWidthHeight: function(options) {
+ options || (options = { });
+
+ this.set('width', (this.x2 - this.x1) || 1);
+ this.set('height', (this.y2 - this.y1) || 1);
+
+ this.set('left', 'left' in options ? options.left : (this.x1 + this.width / 2));
+ this.set('top', 'top' in options ? options.top : (this.y1 + this.height / 2));
+ },
+
+ /**
+ * @private
+ * @method _set
+ * @param {String} key
+ * @param {Any} value
+ */
+ _set: function(key, value) {
+ this[key] = value;
+ if (key in coordProps) {
+ this._setWidthHeight();
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _render: function(ctx) {
+ ctx.beginPath();
+
+ if (this.group) {
+ ctx.translate(-this.group.width/2 + this.left, -this.group.height / 2 + this.top);
+ }
+
+ // move from center (of virtual box) to its left/top corner
+ ctx.moveTo(this.width === 1 ? 0 : (-this.width / 2), this.height === 1 ? 0 : (-this.height / 2));
+ ctx.lineTo(this.width === 1 ? 0 : (this.width / 2), this.height === 1 ? 0 : (this.height / 2));
+
+ ctx.lineWidth = this.strokeWidth;
+
+ // TODO: test this
+ // make sure setting "fill" changes color of a line
+ // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
+ var origStrokeStyle = ctx.strokeStyle;
+ ctx.strokeStyle = ctx.fillStyle;
+ ctx.stroke();
+ ctx.strokeStyle = origStrokeStyle;
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return 1;
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @methd toObject
+ * @return {Object}
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ x1: this.get('x1'),
+ y1: this.get('y1'),
+ x2: this.get('x2'),
+ y2: this.get('y2')
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ return [
+ '<line ',
+ 'x1="', this.get('x1'), '" ',
+ 'y1="', this.get('y1'), '" ',
+ 'x2="', this.get('x2'), '" ',
+ 'y2="', this.get('y2'), '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ '/>'
+ ].join('');
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Line.fromElement`)
+ * @static
+ * @see http://www.w3.org/TR/SVG/shapes.html#LineElement
+ */
+ fabric.Line.ATTRIBUTE_NAMES = 'x1 y1 x2 y2 stroke stroke-width transform'.split(' ');
+
+ /**
+ * Returns fabric.Line instance from an SVG element
+ * @static
+ * @method fabric.Line.fromElement
+ * @param {SVGElement} element Element to parse
+ * @param {Object} [options] Options object
+ * @return {fabric.Line} instance of fabric.Line
+ */
+ fabric.Line.fromElement = function(element, options) {
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES);
+ var points = [
+ parsedAttributes.x1 || 0,
+ parsedAttributes.y1 || 0,
+ parsedAttributes.x2 || 0,
+ parsedAttributes.y2 || 0
+ ];
+ return new fabric.Line(points, extend(parsedAttributes, options));
+ };
+
+ /**
+ * Returns fabric.Line instance from an object representation
+ * @static
+ * @method fabric.Line.fromObject
+ * @param {Object} object Object to create an instance from
+ * @return {fabric.Line} instance of fabric.Line
+ */
+ fabric.Line.fromObject = function(object) {
+ var points = [object.x1, object.y1, object.x2, object.y2];
+ return new fabric.Line(points, object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ piBy2 = Math.PI * 2,
+ extend = fabric.util.object.extend;
+
+ if (fabric.Circle) {
+ fabric.warn('fabric.Circle is already defined.');
+ return;
+ }
+
+ /**
+ * @class Circle
+ * @extends fabric.Object
+ */
+ fabric.Circle = fabric.util.createClass(fabric.Object, /** @scope fabric.Circle.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'circle',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Object} [options] Options object
+ * @return {fabric.Circle} thisArg
+ */
+ initialize: function(options) {
+ options = options || { };
+
+ this.set('radius', options.radius || 0);
+ this.callSuper('initialize', options);
+
+ var diameter = this.get('radius') * 2;
+ this.set('width', diameter).set('height', diameter);
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ radius: this.get('radius')
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ return ('<circle ' +
+ 'cx="0" cy="0" ' +
+ 'r="' + this.radius + '" ' +
+ 'style="' + this.getSvgStyles() + '" ' +
+ 'transform="' + this.getSvgTransform() + '" ' +
+ '/>');
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ _render: function(ctx, noTransform) {
+ ctx.beginPath();
+ // multiply by currently set alpha (the one that was set by path group where this object is contained, for example)
+ ctx.globalAlpha *= this.opacity;
+ ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.radius, 0, piBy2, false);
+ ctx.closePath();
+ if (this.fill) {
+ ctx.fill();
+ }
+ if (this.stroke) {
+ ctx.stroke();
+ }
+ },
+
+ /**
+ * Returns horizontal radius of an object (according to how an object is scaled)
+ * @method getRadiusX
+ * @return {Number}
+ */
+ getRadiusX: function() {
+ return this.get('radius') * this.get('scaleX');
+ },
+
+ /**
+ * Returns vertical radius of an object (according to how an object is scaled)
+ * @method getRadiusY
+ * @return {Number}
+ */
+ getRadiusY: function() {
+ return this.get('radius') * this.get('scaleY');
+ },
+
+ /**
+ * Sets radius of an object (and updates width accordingly)
+ * @method setRadius
+ * @return {Number}
+ */
+ setRadius: function(value) {
+ this.radius = value;
+ this.set('width', value * 2).set('height', value * 2);
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity of this instance
+ */
+ complexity: function() {
+ return 1;
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement})
+ * @static
+ * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement
+ */
+ fabric.Circle.ATTRIBUTE_NAMES = 'cx cy r fill fill-opacity opacity stroke stroke-width transform'.split(' ');
+
+ /**
+ * Returns {@link fabric.Circle} instance from an SVG element
+ * @static
+ * @method fabric.Circle.fromElement
+ * @param element {SVGElement} element to parse
+ * @param options {Object} options object
+ * @throws {Error} If value of `r` attribute is missing or invalid
+ * @return {Object} instance of fabric.Circle
+ */
+ fabric.Circle.fromElement = function(element, options) {
+ options || (options = { });
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES);
+ if (!isValidRadius(parsedAttributes)) {
+ throw Error('value of `r` attribute is required and can not be negative');
+ }
+ if ('left' in parsedAttributes) {
+ parsedAttributes.left -= (options.width / 2) || 0;
+ }
+ if ('top' in parsedAttributes) {
+ parsedAttributes.top -= (options.height / 2) || 0;
+ }
+ return new fabric.Circle(extend(parsedAttributes, options));
+ };
+
+ /**
+ * @private
+ */
+ function isValidRadius(attributes) {
+ return (('radius' in attributes) && (attributes.radius > 0));
+ }
+
+ /**
+ * Returns {@link fabric.Circle} instance from an object representation
+ * @static
+ * @method fabric.Circle.fromObject
+ * @param {Object} object Object to create an instance from
+ * @return {Object} Instance of fabric.Circle
+ */
+ fabric.Circle.fromObject = function(object) {
+ return new fabric.Circle(object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Triangle) {
+ fabric.warn('fabric.Triangle is already defined');
+ return;
+ }
+
+ /**
+ * @class Triangle
+ * @extends fabric.Object
+ */
+ fabric.Triangle = fabric.util.createClass(fabric.Object, /** @scope fabric.Triangle.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'triangle',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param options {Object} options object
+ * @return {Object} thisArg
+ */
+ initialize: function(options) {
+ options = options || { };
+
+ this.callSuper('initialize', options);
+
+ this.set('width', options.width || 100)
+ .set('height', options.height || 100);
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param ctx {CanvasRenderingContext2D} Context to render on
+ */
+ _render: function(ctx) {
+ var widthBy2 = this.width / 2,
+ heightBy2 = this.height / 2;
+
+ ctx.beginPath();
+ ctx.moveTo(-widthBy2, heightBy2);
+ ctx.lineTo(0, -heightBy2);
+ ctx.lineTo(widthBy2, heightBy2);
+ ctx.closePath();
+
+ if (this.fill) {
+ ctx.fill();
+ }
+ if (this.stroke) {
+ ctx.stroke();
+ }
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity of this instance
+ */
+ complexity: function() {
+ return 1;
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+
+ var widthBy2 = this.width / 2,
+ heightBy2 = this.height / 2;
+
+ var points = [
+ -widthBy2 + " " + heightBy2,
+ "0 " + -heightBy2,
+ widthBy2 + " " + heightBy2
+ ].join(",");
+
+ return '<polygon ' +
+ 'points="' + points + '" ' +
+ 'style="' + this.getSvgStyles() + '" ' +
+ 'transform="' + this.getSvgTransform() + '" ' +
+ '/>';
+ }
+ });
+
+ /**
+ * Returns fabric.Triangle instance from an object representation
+ * @static
+ * @method Canvas.Trangle.fromObject
+ * @param object {Object} object to create an instance from
+ * @return {Object} instance of Canvas.Triangle
+ */
+ fabric.Triangle.fromObject = function(object) {
+ return new fabric.Triangle(object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global){
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ piBy2 = Math.PI * 2,
+ extend = fabric.util.object.extend;
+
+ if (fabric.Ellipse) {
+ fabric.warn('fabric.Ellipse is already defined.');
+ return;
+ }
+
+ /**
+ * @class Ellipse
+ * @extends fabric.Object
+ */
+ fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @scope fabric.Ellipse.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'ellipse',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Object} [options] Options object
+ * @return {Object} thisArg
+ */
+ initialize: function(options) {
+ options = options || { };
+
+ this.callSuper('initialize', options);
+
+ this.set('rx', options.rx || 0);
+ this.set('ry', options.ry || 0);
+
+ this.set('width', this.get('rx') * 2);
+ this.set('height', this.get('ry') * 2);
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ rx: this.get('rx'),
+ ry: this.get('ry')
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ return [
+ '<ellipse ',
+ 'rx="', this.get('rx'), '" ',
+ 'ry="', this.get('ry'), '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ 'transform="', this.getSvgTransform(), '" ',
+ '/>'
+ ].join('');
+ },
+
+ /**
+ * Renders this instance on a given context
+ * @method render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ * @param noTransform {Boolean} context is not transformed when set to true
+ */
+ render: function(ctx, noTransform) {
+ // do not use `get` for perf. reasons
+ if (this.rx === 0 || this.ry === 0) return;
+ return this.callSuper('render', ctx, noTransform);
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ _render: function(ctx, noTransform) {
+ ctx.beginPath();
+ ctx.save();
+ ctx.globalAlpha *= this.opacity;
+ if (this.transformMatrix && this.group) {
+ ctx.translate(this.cx, this.cy);
+ }
+ ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0);
+ ctx.arc(noTransform ? this.left : 0, noTransform ? this.top : 0, this.rx, 0, piBy2, false);
+ if (this.stroke) {
+ ctx.stroke();
+ }
+ if (this.fill) {
+ ctx.fill();
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return 1;
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement})
+ * @static
+ * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement
+ */
+ fabric.Ellipse.ATTRIBUTE_NAMES = 'cx cy rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' ');
+
+ /**
+ * Returns {@link fabric.Ellipse} instance from an SVG element
+ * @static
+ * @method fabric.Ellipse.fromElement
+ * @param {SVGElement} element Element to parse
+ * @param {Object} [options] Options object
+ * @return {fabric.Ellipse}
+ */
+ fabric.Ellipse.fromElement = function(element, options) {
+ options || (options = { });
+
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES);
+ var cx = parsedAttributes.left;
+ var cy = parsedAttributes.top;
+
+ if ('left' in parsedAttributes) {
+ parsedAttributes.left -= (options.width / 2) || 0;
+ }
+ if ('top' in parsedAttributes) {
+ parsedAttributes.top -= (options.height / 2) || 0;
+ }
+
+ var ellipse = new fabric.Ellipse(extend(parsedAttributes, options));
+
+ ellipse.cx = cx || 0;
+ ellipse.cy = cy || 0;
+
+ return ellipse;
+ };
+
+ /**
+ * Returns fabric.Ellipse instance from an object representation
+ * @static
+ * @method fabric.Ellipse.fromObject
+ * @param {Object} object Object to create an instance from
+ * @return {fabric.Ellipse}
+ */
+ fabric.Ellipse.fromObject = function(object) {
+ return new fabric.Ellipse(object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { });
+
+ if (fabric.Rect) {
+ console.warn('fabric.Rect is already defined');
+ return;
+ }
+
+ /**
+ * @class Rect
+ * @extends fabric.Object
+ */
+ fabric.Rect = fabric.util.createClass(fabric.Object, /** @scope fabric.Rect.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'rect',
+
+ /**
+ * @property
+ * @type Number
+ */
+ rx: 0,
+
+ /**
+ * @property
+ * @type Number
+ */
+ ry: 0,
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param options {Object} options object
+ * @return {Object} thisArg
+ */
+ initialize: function(options) {
+ this._initStateProperties();
+ this.callSuper('initialize', options);
+ this._initRxRy();
+ },
+
+ /**
+ * Creates `stateProperties` list on an instance, and adds `fabric.Rect` -specific ones to it
+ * (such as "rx", "ry", etc.)
+ * @private
+ * @method _initStateProperties
+ */
+ _initStateProperties: function() {
+ this.stateProperties = this.stateProperties.concat(['rx', 'ry']);
+ },
+
+ /**
+ * @private
+ * @method _initRxRy
+ */
+ _initRxRy: function() {
+ if (this.rx && !this.ry) {
+ this.ry = this.rx;
+ }
+ else if (this.ry && !this.rx) {
+ this.rx = this.ry;
+ }
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ _render: function(ctx) {
+ var rx = this.rx || 0,
+ ry = this.ry || 0,
+ x = -this.width / 2,
+ y = -this.height / 2,
+ w = this.width,
+ h = this.height;
+
+ ctx.beginPath();
+ ctx.globalAlpha *= this.opacity;
+
+ if (this.transformMatrix && this.group) {
+ ctx.translate(
+ this.width / 2 + this.x,
+ this.height / 2 + this.y);
+ }
+ if (!this.transformMatrix && this.group) {
+ ctx.translate(
+ -this.group.width / 2 + this.width / 2 + this.x,
+ -this.group.height / 2 + this.height / 2 + this.y);
+ }
+
+ ctx.moveTo(x+rx, y);
+ ctx.lineTo(x+w-rx, y);
+ ctx.quadraticCurveTo(x+w, y, x+w, y+ry, x+w, y+ry);
+ ctx.lineTo(x+w, y+h-ry);
+ ctx.quadraticCurveTo(x+w,y+h,x+w-rx,y+h,x+w-rx,y+h);
+ ctx.lineTo(x+rx,y+h);
+ ctx.quadraticCurveTo(x,y+h,x,y+h-ry,x,y+h-ry);
+ ctx.lineTo(x,y+ry);
+ ctx.quadraticCurveTo(x,y,x+rx,y,x+rx,y);
+ ctx.closePath();
+
+ if (this.fill) {
+ ctx.fill();
+ }
+
+ if (this.strokeDashArray) {
+ this._renderDashedStroke(ctx);
+ }
+ else if (this.stroke) {
+ ctx.stroke();
+ }
+ },
+
+ // since our coordinate system differs from that of SVG
+ _normalizeLeftTopProperties: function(parsedAttributes) {
+ if (parsedAttributes.left) {
+ this.set('left', parsedAttributes.left + this.getWidth() / 2);
+ }
+ this.set('x', parsedAttributes.left || 0);
+ if (parsedAttributes.top) {
+ this.set('top', parsedAttributes.top + this.getHeight() / 2);
+ }
+ this.set('y', parsedAttributes.top || 0);
+ return this;
+ },
+
+ /**
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return 1;
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return fabric.util.object.extend(this.callSuper('toObject'), {
+ rx: this.get('rx') || 0,
+ ry: this.get('ry') || 0
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ return '<rect ' +
+ 'x="' + (-1 * this.width / 2) + '" y="' + (-1 * this.height / 2) + '" ' +
+ 'rx="' + this.get('rx') + '" ry="' + this.get('ry') + '" ' +
+ 'width="' + this.width + '" height="' + this.height + '" ' +
+ 'style="' + this.getSvgStyles() + '" ' +
+ 'transform="' + this.getSvgTransform() + '" ' +
+ '/>';
+ }
+ });
+
+ // TODO (kangax): implement rounded rectangles (both parsing and rendering)
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`)
+ * @static
+ */
+ fabric.Rect.ATTRIBUTE_NAMES = 'x y width height rx ry fill fill-opacity opacity stroke stroke-width transform'.split(' ');
+
+ /**
+ * @private
+ */
+ function _setDefaultLeftTopValues(attributes) {
+ attributes.left = attributes.left || 0;
+ attributes.top = attributes.top || 0;
+ return attributes;
+ }
+
+ /**
+ * Returns fabric.Rect instance from an SVG element
+ * @static
+ * @method fabric.Rect.fromElement
+ * @param element {SVGElement} element to parse
+ * @param options {Object} options object
+ * @return {fabric.Rect} instance of fabric.Rect
+ */
+ fabric.Rect.fromElement = function(element, options) {
+ if (!element) {
+ return null;
+ }
+
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES);
+ parsedAttributes = _setDefaultLeftTopValues(parsedAttributes);
+
+ var rect = new fabric.Rect(fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes));
+ rect._normalizeLeftTopProperties(parsedAttributes);
+
+ return rect;
+ };
+
+ /**
+ * Returns fabric.Rect instance from an object representation
+ * @static
+ * @method fabric.Rect.fromObject
+ * @param object {Object} object to create an instance from
+ * @return {Object} instance of fabric.Rect
+ */
+ fabric.Rect.fromObject = function(object) {
+ return new fabric.Rect(object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ toFixed = fabric.util.toFixed;
+
+ if (fabric.Polyline) {
+ fabric.warn('fabric.Polyline is already defined');
+ return;
+ }
+
+ /**
+ * @class Polyline
+ * @extends fabric.Object
+ */
+ fabric.Polyline = fabric.util.createClass(fabric.Object, /** @scope fabric.Polyline.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'polyline',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Array} points array of points
+ * @param {Object} [options] Options object
+ * @return {Object} thisArg
+ */
+ initialize: function(points, options) {
+ options = options || { };
+ this.set('points', points);
+ this.callSuper('initialize', options);
+ this._calcDimensions();
+ },
+
+ /**
+ * @private
+ * @method _calcDimensions
+ */
+ _calcDimensions: function() {
+ return fabric.Polygon.prototype._calcDimensions.call(this);
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} Object representation of an instance
+ */
+ toObject: function() {
+ return fabric.Polygon.prototype.toObject.call(this);
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ var points = [];
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
+ }
+
+ return [
+ '<polyline ',
+ 'points="', points.join(''), '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ 'transform="', this.getSvgTransform(), '" ',
+ '/>'
+ ].join('');
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _render: function(ctx) {
+ var point;
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ point = this.points[i];
+ ctx.lineTo(point.x, point.y);
+ }
+ if (this.fill) {
+ ctx.fill();
+ }
+ if (this.stroke) {
+ ctx.stroke();
+ }
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return this.get('points').length;
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Polyline.fromElement`)
+ * @static
+ * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement
+ */
+ fabric.Polyline.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' ');
+
+ /**
+ * Returns fabric.Polyline instance from an SVG element
+ * @static
+ * @method fabric.Polyline.fromElement
+ * @param {SVGElement} element Element to parse
+ * @param {Object} [options] Options object
+ * @return {Object} instance of fabric.Polyline
+ */
+ fabric.Polyline.fromElement = function(element, options) {
+ if (!element) {
+ return null;
+ }
+ options || (options = { });
+
+ var points = fabric.parsePointsAttribute(element.getAttribute('points')),
+ parsedAttributes = fabric.parseAttributes(element, fabric.Polyline.ATTRIBUTE_NAMES);
+
+ for (var i = 0, len = points.length; i < len; i++) {
+ // normalize coordinates, according to containing box (dimensions of which are passed via `options`)
+ points[i].x -= (options.width / 2) || 0;
+ points[i].y -= (options.height / 2) || 0;
+ }
+
+ return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options));
+ };
+
+ /**
+ * Returns fabric.Polyline instance from an object representation
+ * @static
+ * @method fabric.Polyline.fromObject
+ * @param {Object} [object] Object to create an instance from
+ * @return {fabric.Polyline}
+ */
+ fabric.Polyline.fromObject = function(object) {
+ var points = object.points;
+ return new fabric.Polyline(points, object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ min = fabric.util.array.min,
+ max = fabric.util.array.max,
+ toFixed = fabric.util.toFixed;
+
+ if (fabric.Polygon) {
+ fabric.warn('fabric.Polygon is already defined');
+ return;
+ }
+
+ function byX(p) { return p.x; }
+ function byY(p) { return p.y; }
+
+ /**
+ * @class Polygon
+ * @extends fabric.Object
+ */
+ fabric.Polygon = fabric.util.createClass(fabric.Object, /** @scope fabric.Polygon.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'polygon',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Array} points Array of points
+ * @param {Object} options Options object
+ * @return {fabric.Polygon} thisArg
+ */
+ initialize: function(points, options) {
+ options = options || { };
+ this.points = points;
+ this.callSuper('initialize', options);
+ this._calcDimensions();
+ },
+
+ /**
+ * @private
+ * @method _calcDimensions
+ */
+ _calcDimensions: function() {
+
+ var points = this.points,
+ minX = min(points, 'x'),
+ minY = min(points, 'y'),
+ maxX = max(points, 'x'),
+ maxY = max(points, 'y');
+
+ this.width = (maxX - minX) || 1;
+ this.height = (maxY - minY) || 1;
+
+ this.minX = minX;
+ this.minY = minY;
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ points: this.points.concat()
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ var points = [];
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ points.push(toFixed(this.points[i].x, 2), ',', toFixed(this.points[i].y, 2), ' ');
+ }
+
+ return [
+ '<polygon ',
+ 'points="', points.join(''), '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ 'transform="', this.getSvgTransform(), '" ',
+ '/>'
+ ].join('');
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ _render: function(ctx) {
+ var point;
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ for (var i = 0, len = this.points.length; i < len; i++) {
+ point = this.points[i];
+ ctx.lineTo(point.x, point.y);
+ }
+ if (this.fill) {
+ ctx.fill();
+ }
+ if (this.stroke) {
+ ctx.closePath();
+ ctx.stroke();
+ }
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity of this instance
+ */
+ complexity: function() {
+ return this.points.length;
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`)
+ * @static
+ * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement
+ */
+ fabric.Polygon.ATTRIBUTE_NAMES = 'fill fill-opacity opacity stroke stroke-width transform'.split(' ');
+
+ /**
+ * Returns fabric.Polygon instance from an SVG element
+ * @static
+ * @method fabric.Polygon.fromElement
+ * @param {SVGElement} element Element to parse
+ * @param {Object} options Options object
+ * @return {fabric.Polygon}
+ */
+ fabric.Polygon.fromElement = function(element, options) {
+ if (!element) {
+ return null;
+ }
+ options || (options = { });
+
+ var points = fabric.parsePointsAttribute(element.getAttribute('points')),
+ parsedAttributes = fabric.parseAttributes(element, fabric.Polygon.ATTRIBUTE_NAMES);
+
+ for (var i = 0, len = points.length; i < len; i++) {
+ // normalize coordinates, according to containing box (dimensions of which are passed via `options`)
+ points[i].x -= (options.width / 2) || 0;
+ points[i].y -= (options.height / 2) || 0;
+ }
+
+ return new fabric.Polygon(points, extend(parsedAttributes, options));
+ };
+
+ /**
+ * Returns fabric.Polygon instance from an object representation
+ * @static
+ * @method fabric.Polygon.fromObject
+ * @param {Object} object Object to create an instance from
+ * @return {fabric.Polygon}
+ */
+ fabric.Polygon.fromObject = function(object) {
+ return new fabric.Polygon(object.points, object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ var commandLengths = {
+ m: 2,
+ l: 2,
+ h: 1,
+ v: 1,
+ c: 6,
+ s: 4,
+ q: 4,
+ t: 2,
+ a: 7
+ };
+
+ function drawArc(ctx, x, y, coords) {
+ var rx = coords[0];
+ var ry = coords[1];
+ var rot = coords[2];
+ var large = coords[3];
+ var sweep = coords[4];
+ var ex = coords[5];
+ var ey = coords[6];
+ var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
+ for (var i=0; i<segs.length; i++) {
+ var bez = segmentToBezier.apply(this, segs[i]);
+ ctx.bezierCurveTo.apply(ctx, bez);
+ }
+ }
+
+ var arcToSegmentsCache = { },
+ segmentToBezierCache = { },
+ _join = Array.prototype.join,
+ argsString;
+
+ // Copied from Inkscape svgtopdf, thanks!
+ function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
+ argsString = _join.call(arguments);
+ if (arcToSegmentsCache[argsString]) {
+ return arcToSegmentsCache[argsString];
+ }
+
+ var th = rotateX * (Math.PI/180);
+ var sin_th = Math.sin(th);
+ var cos_th = Math.cos(th);
+ rx = Math.abs(rx);
+ ry = Math.abs(ry);
+ var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
+ var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
+ var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry);
+ if (pl > 1) {
+ pl = Math.sqrt(pl);
+ rx *= pl;
+ ry *= pl;
+ }
+
+ var a00 = cos_th / rx;
+ var a01 = sin_th / rx;
+ var a10 = (-sin_th) / ry;
+ var a11 = (cos_th) / ry;
+ var x0 = a00 * ox + a01 * oy;
+ var y0 = a10 * ox + a11 * oy;
+ var x1 = a00 * x + a01 * y;
+ var y1 = a10 * x + a11 * y;
+
+ var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
+ var sfactor_sq = 1 / d - 0.25;
+ if (sfactor_sq < 0) sfactor_sq = 0;
+ var sfactor = Math.sqrt(sfactor_sq);
+ if (sweep == large) sfactor = -sfactor;
+ var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
+ var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
+
+ var th0 = Math.atan2(y0-yc, x0-xc);
+ var th1 = Math.atan2(y1-yc, x1-xc);
+
+ var th_arc = th1-th0;
+ if (th_arc < 0 && sweep == 1){
+ th_arc += 2*Math.PI;
+ } else if (th_arc > 0 && sweep == 0) {
+ th_arc -= 2 * Math.PI;
+ }
+
+ var segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
+ var result = [];
+ for (var i=0; i<segments; i++) {
+ var th2 = th0 + i * th_arc / segments;
+ var th3 = th0 + (i+1) * th_arc / segments;
+ result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
+ }
+
+ return (arcToSegmentsCache[argsString] = result);
+ }
+
+ function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
+ argsString = _join.call(arguments);
+ if (segmentToBezierCache[argsString]) {
+ return segmentToBezierCache[argsString];
+ }
+
+ var a00 = cos_th * rx;
+ var a01 = -sin_th * ry;
+ var a10 = sin_th * rx;
+ var a11 = cos_th * ry;
+
+ var th_half = 0.5 * (th1 - th0);
+ var t = (8/3) * Math.sin(th_half * 0.5) * Math.sin(th_half * 0.5) / Math.sin(th_half);
+ var x1 = cx + Math.cos(th0) - t * Math.sin(th0);
+ var y1 = cy + Math.sin(th0) + t * Math.cos(th0);
+ var x3 = cx + Math.cos(th1);
+ var y3 = cy + Math.sin(th1);
+ var x2 = x3 + t * Math.sin(th1);
+ var y2 = y3 - t * Math.cos(th1);
+
+ return (segmentToBezierCache[argsString] = [
+ a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
+ a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
+ a00 * x3 + a01 * y3, a10 * x3 + a11 * y3
+ ]);
+ }
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ min = fabric.util.array.min,
+ max = fabric.util.array.max,
+ extend = fabric.util.object.extend,
+ _toString = Object.prototype.toString;
+
+ if (fabric.Path) {
+ fabric.warn('fabric.Path is already defined');
+ return;
+ }
+ if (!fabric.Object) {
+ fabric.warn('fabric.Path requires fabric.Object');
+ return;
+ }
+
+ /**
+ * @private
+ */
+ function getX(item) {
+ if (item[0] === 'H') {
+ return item[1];
+ }
+ return item[item.length - 2];
+ }
+
+ /**
+ * @private
+ */
+ function getY(item) {
+ if (item[0] === 'V') {
+ return item[1];
+ }
+ return item[item.length - 1];
+ }
+
+ /**
+ * @class Path
+ * @extends fabric.Object
+ */
+ fabric.Path = fabric.util.createClass(fabric.Object, /** @scope fabric.Path.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'path',
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens)
+ * @param {Object} [options] Options object
+ */
+ initialize: function(path, options) {
+ options = options || { };
+
+ this.setOptions(options);
+
+ if (!path) {
+ throw Error('`path` argument is required');
+ }
+
+ var fromArray = _toString.call(path) === '[object Array]';
+
+ this.path = fromArray
+ ? path
+ // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values)
+ : path.match && path.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi);
+
+ if (!this.path) return;
+
+ if (!fromArray) {
+ this._initializeFromString(options);
+ }
+
+ if (options.sourcePath) {
+ this.setSourcePath(options.sourcePath);
+ }
+ },
+
+ /**
+ * @private
+ * @method _initializeFromString
+ */
+ _initializeFromString: function(options) {
+ var isWidthSet = 'width' in options,
+ isHeightSet = 'height' in options;
+
+ this.path = this._parsePath();
+
+ if (!isWidthSet || !isHeightSet) {
+ extend(this, this._parseDimensions());
+ if (isWidthSet) {
+ this.width = options.width;
+ }
+ if (isHeightSet) {
+ this.height = options.height;
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @method _render
+ */
+ _render: function(ctx) {
+ var current, // current instruction
+ previous = null,
+ x = 0, // current x
+ y = 0, // current y
+ controlX = 0, // current control point x
+ controlY = 0, // current control point y
+ tempX,
+ tempY,
+ tempControlX,
+ tempControlY,
+ l = -(this.width / 2),
+ t = -(this.height / 2);
+
+ for (var i = 0, len = this.path.length; i < len; ++i) {
+
+ current = this.path[i];
+
+ switch (current[0]) { // first letter
+
+ case 'l': // lineto, relative
+ x += current[1];
+ y += current[2];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'L': // lineto, absolute
+ x = current[1];
+ y = current[2];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'h': // horizontal lineto, relative
+ x += current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'H': // horizontal lineto, absolute
+ x = current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'v': // vertical lineto, relative
+ y += current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'V': // verical lineto, absolute
+ y = current[1];
+ ctx.lineTo(x + l, y + t);
+ break;
+
+ case 'm': // moveTo, relative
+ x += current[1];
+ y += current[2];
+ ctx.moveTo(x + l, y + t);
+ break;
+
+ case 'M': // moveTo, absolute
+ x = current[1];
+ y = current[2];
+ ctx.moveTo(x + l, y + t);
+ break;
+
+ case 'c': // bezierCurveTo, relative
+ tempX = x + current[5];
+ tempY = y + current[6];
+ controlX = x + current[3];
+ controlY = y + current[4];
+ ctx.bezierCurveTo(
+ x + current[1] + l, // x1
+ y + current[2] + t, // y1
+ controlX + l, // x2
+ controlY + t, // y2
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+ break;
+
+ case 'C': // bezierCurveTo, absolute
+ x = current[5];
+ y = current[6];
+ controlX = current[3];
+ controlY = current[4];
+ ctx.bezierCurveTo(
+ current[1] + l,
+ current[2] + t,
+ controlX + l,
+ controlY + t,
+ x + l,
+ y + t
+ );
+ break;
+
+ case 's': // shorthand cubic bezierCurveTo, relative
+ // transform to absolute x,y
+ tempX = x + current[3];
+ tempY = y + current[4];
+ // calculate reflection of previous control points
+ controlX = 2 * x - controlX;
+ controlY = 2 * y - controlY;
+ ctx.bezierCurveTo(
+ controlX + l,
+ controlY + t,
+ x + current[1] + l,
+ y + current[2] + t,
+ tempX + l,
+ tempY + t
+ );
+ // set control point to 2nd one of this command
+ // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
+ controlX = x + current[1];
+ controlY = y + current[2];
+
+ x = tempX;
+ y = tempY;
+ break;
+
+ case 'S': // shorthand cubic bezierCurveTo, absolute
+ tempX = current[3];
+ tempY = current[4];
+ // calculate reflection of previous control points
+ controlX = 2*x - controlX;
+ controlY = 2*y - controlY;
+ ctx.bezierCurveTo(
+ controlX + l,
+ controlY + t,
+ current[1] + l,
+ current[2] + t,
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+
+ // set control point to 2nd one of this command
+ // "... the first control point is assumed to be the reflection of the second control point on the previous command relative to the current point."
+ controlX = current[1];
+ controlY = current[2];
+
+ break;
+
+ case 'q': // quadraticCurveTo, relative
+ // transform to absolute x,y
+ tempX = x + current[3];
+ tempY = y + current[4];
+
+ controlX = x + current[1];
+ controlY = y + current[2];
+
+ ctx.quadraticCurveTo(
+ controlX + l,
+ controlY + t,
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+ break;
+
+ case 'Q': // quadraticCurveTo, absolute
+ tempX = current[3];
+ tempY = current[4];
+
+ ctx.quadraticCurveTo(
+ current[1] + l,
+ current[2] + t,
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+ controlX = current[1];
+ controlY = current[2];
+ break;
+
+ case 't': // shorthand quadraticCurveTo, relative
+
+ // transform to absolute x,y
+ tempX = x + current[1];
+ tempY = y + current[2];
+
+
+ if (previous[0].match(/[QqTt]/) === null) {
+ // 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;
+ }
+ else if (previous[0] === 't') {
+ // calculate reflection of previous control points for t
+ controlX = 2 * x - tempControlX;
+ controlY = 2 * y - tempControlY;
+ }
+ else if (previous[0] === 'q') {
+ // calculate reflection of previous control points for q
+ controlX = 2 * x - controlX;
+ controlY = 2 * y - controlY;
+ }
+
+ tempControlX = controlX;
+ tempControlY = controlY;
+
+ ctx.quadraticCurveTo(
+ controlX + l,
+ controlY + t,
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+ controlX = x + current[1];
+ controlY = y + current[2];
+ break;
+
+ case 'T':
+ tempX = current[1];
+ tempY = current[2];
+
+ // calculate reflection of previous control points
+ controlX = 2 * x - controlX;
+ controlY = 2 * y - controlY;
+ ctx.quadraticCurveTo(
+ controlX + l,
+ controlY + t,
+ tempX + l,
+ tempY + t
+ );
+ x = tempX;
+ y = tempY;
+ break;
+
+ case 'a':
+ // TODO: optimize this
+ drawArc(ctx, x + l, y + t, [
+ current[1],
+ current[2],
+ current[3],
+ current[4],
+ current[5],
+ current[6] + x + l,
+ current[7] + y + t
+ ]);
+ x += current[6];
+ y += current[7];
+ break;
+
+ case 'A':
+ // TODO: optimize this
+ drawArc(ctx, x + l, y + t, [
+ current[1],
+ current[2],
+ current[3],
+ current[4],
+ current[5],
+ current[6] + l,
+ current[7] + t
+ ]);
+ x = current[6];
+ y = current[7];
+ break;
+
+ case 'z':
+ case 'Z':
+ ctx.closePath();
+ break;
+ }
+ previous = current;
+ }
+ },
+
+ /**
+ * Renders path on a specified context
+ * @method render
+ * @param {CanvasRenderingContext2D} ctx context to render path on
+ * @param {Boolean} noTransform When true, context is not transformed
+ */
+ render: function(ctx, noTransform) {
+ ctx.save();
+ var m = this.transformMatrix;
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ if (!noTransform) {
+ this.transform(ctx);
+ }
+ // ctx.globalCompositeOperation = this.fillRule;
+
+ if (this.overlayFill) {
+ ctx.fillStyle = this.overlayFill;
+ }
+ else if (this.fill) {
+ ctx.fillStyle = this.fill.toLiveGradient
+ ? this.fill.toLiveGradient(ctx)
+ : this.fill;
+ }
+
+ if (this.stroke) {
+ ctx.strokeStyle = this.stroke;
+ }
+ ctx.beginPath();
+
+ this._render(ctx);
+
+ if (this.fill) {
+ ctx.fill();
+ }
+ if (this.stroke) {
+ ctx.strokeStyle = this.stroke;
+ ctx.lineWidth = this.strokeWidth;
+ ctx.lineCap = ctx.lineJoin = 'round';
+ ctx.stroke();
+ }
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.hideCorners || this.drawCorners(ctx);
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Returns string representation of an instance
+ * @method toString
+ * @return {String} string representation of an instance
+ */
+ toString: function() {
+ return '#<fabric.Path (' + this.complexity() +
+ '): { "top": ' + this.top + ', "left": ' + this.left + ' }>';
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object}
+ */
+ toObject: function() {
+ var o = extend(this.callSuper('toObject'), {
+ path: this.path
+ });
+ if (this.sourcePath) {
+ o.sourcePath = this.sourcePath;
+ }
+ if (this.transformMatrix) {
+ o.transformMatrix = this.transformMatrix;
+ }
+ return o;
+ },
+
+ /**
+ * Returns dataless object representation of an instance
+ * @method toDatalessObject
+ * @return {Object}
+ */
+ toDatalessObject: function() {
+ var o = this.toObject();
+ if (this.sourcePath) {
+ o.path = this.sourcePath;
+ }
+ delete o.sourcePath;
+ return o;
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ var chunks = [];
+ for (var i = 0, len = this.path.length; i < len; i++) {
+ chunks.push(this.path[i].join(' '));
+ }
+ var path = chunks.join(' ');
+
+ return [
+ '<g transform="', this.getSvgTransform(), '">',
+ '<path ',
+ 'width="', this.width, '" height="', this.height, '" ',
+ 'd="', path, '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ 'transform="translate(', (-this.width / 2), ' ', (-this.height/2), ')" />',
+ '</g>'
+ ].join('');
+ },
+
+ /**
+ * Returns number representation of an instance complexity
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return this.path.length;
+ },
+
+ /**
+ * @private
+ * @method _parsePath
+ */
+ _parsePath: function() {
+ var result = [ ],
+ currentPath,
+ chunks,
+ parsed;
+
+ for (var i = 0, j, chunksParsed, len = this.path.length; i < len; i++) {
+ currentPath = this.path[i];
+ chunks = currentPath.slice(1).trim().replace(/(\d)-/g, '$1###-').split(/\s|,|###/);
+ chunksParsed = [ currentPath.charAt(0) ];
+
+ for (var j = 0, jlen = chunks.length; j < jlen; j++) {
+ parsed = parseFloat(chunks[j]);
+ if (!isNaN(parsed)) {
+ chunksParsed.push(parsed);
+ }
+ }
+
+ var command = chunksParsed[0].toLowerCase(),
+ commandLength = commandLengths[command];
+
+ if (chunksParsed.length - 1 > commandLength) {
+ for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) {
+ result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength)));
+ }
+ }
+ else {
+ result.push(chunksParsed);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * @method _parseDimensions
+ */
+ _parseDimensions: function() {
+ var aX = [],
+ aY = [],
+ previousX,
+ previousY,
+ isLowerCase = false,
+ x,
+ y;
+
+ this.path.forEach(function(item, i) {
+ if (item[0] !== 'H') {
+ previousX = (i === 0) ? getX(item) : getX(this.path[i-1]);
+ }
+ if (item[0] !== 'V') {
+ previousY = (i === 0) ? getY(item) : getY(this.path[i-1]);
+ }
+
+ // lowercased letter denotes relative position;
+ // transform to absolute
+ if (item[0] === item[0].toLowerCase()) {
+ isLowerCase = true;
+ }
+
+ // last 2 items in an array of coordinates are the actualy x/y (except H/V);
+ // collect them
+
+ // TODO (kangax): support relative h/v commands
+
+ x = isLowerCase
+ ? previousX + getX(item)
+ : item[0] === 'V'
+ ? previousX
+ : getX(item);
+
+ y = isLowerCase
+ ? previousY + getY(item)
+ : item[0] === 'H'
+ ? previousY
+ : getY(item);
+
+ var val = parseInt(x, 10);
+ if (!isNaN(val)) aX.push(val);
+
+ val = parseInt(y, 10);
+ if (!isNaN(val)) aY.push(val);
+
+ }, this);
+
+ var minX = min(aX),
+ minY = min(aY),
+ deltaX = 0,
+ deltaY = 0;
+
+ var o = {
+ top: minY - deltaY,
+ left: minX - deltaX,
+ bottom: max(aY) - deltaY,
+ right: max(aX) - deltaX
+ };
+
+ o.width = o.right - o.left;
+ o.height = o.bottom - o.top;
+
+ return o;
+ }
+ });
+
+ /**
+ * Creates an instance of fabric.Path from an object
+ * @static
+ * @method fabric.Path.fromObject
+ * @return {fabric.Path} Instance of fabric.Path
+ */
+ fabric.Path.fromObject = function(object) {
+ return new fabric.Path(object.path, object);
+ };
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`)
+ * @static
+ * @see http://www.w3.org/TR/SVG/paths.html#PathElement
+ */
+ fabric.Path.ATTRIBUTE_NAMES = 'd fill fill-opacity opacity fill-rule stroke stroke-width transform'.split(' ');
+
+ /**
+ * Creates an instance of fabric.Path from an SVG <path> element
+ * @static
+ * @method fabric.Path.fromElement
+ * @param {SVGElement} element to parse
+ * @param {Object} options object
+ * @return {fabric.Path} Instance of fabric.Path
+ */
+ fabric.Path.fromElement = function(element, options) {
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES);
+ return new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options));
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ invoke = fabric.util.array.invoke,
+ parentSet = fabric.Object.prototype.set,
+ parentToObject = fabric.Object.prototype.toObject,
+ camelize = fabric.util.string.camelize,
+ capitalize = fabric.util.string.capitalize;
+
+ if (fabric.PathGroup) {
+ fabric.warn('fabric.PathGroup is already defined');
+ return;
+ }
+
+ /**
+ * @class PathGroup
+ * @extends fabric.Path
+ */
+ fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @scope fabric.PathGroup.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'path-group',
+
+ /**
+ * @property
+ * @type String
+ */
+ fill: '',
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ forceFillOverwrite: false,
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {Array} paths
+ * @param {Object} [options] Options object
+ * @return {fabric.PathGroup} thisArg
+ */
+ initialize: function(paths, options) {
+
+ options = options || { };
+ this.paths = paths || [ ];
+
+ for (var i = this.paths.length; i--; ) {
+ this.paths[i].group = this;
+ }
+
+ this.setOptions(options);
+ this.setCoords();
+
+ if (options.sourcePath) {
+ this.setSourcePath(options.sourcePath);
+ }
+ },
+
+ /**
+ * Renders this group on a specified context
+ * @method render
+ * @param {CanvasRenderingContext2D} ctx Context to render this instance on
+ */
+ render: function(ctx) {
+ ctx.save();
+
+ var m = this.transformMatrix;
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+
+ this.transform(ctx);
+ for (var i = 0, l = this.paths.length; i < l; ++i) {
+ this.paths[i].render(ctx, true);
+ }
+ if (this.active) {
+ this.drawBorders(ctx);
+ this.hideCorners || this.drawCorners(ctx);
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Sets certain property to a certain value
+ * @method _set
+ * @param {String} prop
+ * @param {Any} value
+ * @return {fabric.PathGroup} thisArg
+ */
+ _set: function(prop, value) {
+
+ if ((prop === 'fill' || prop === 'overlayFill') && value && this.isSameColor()) {
+ var i = this.paths.length;
+ while (i--) {
+ this.paths[i]._set(prop, value);
+ }
+ }
+
+ return this.callSuper('_set', prop, value);
+ },
+
+ /**
+ * Returns object representation of this path group
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return extend(parentToObject.call(this), {
+ paths: invoke(this.getObjects(), 'clone'),
+ sourcePath: this.sourcePath
+ });
+ },
+
+ /**
+ * Returns dataless object representation of this path group
+ * @method toDatalessObject
+ * @return {Object} dataless object representation of an instance
+ */
+ toDatalessObject: function() {
+ var o = this.toObject();
+ if (this.sourcePath) {
+ o.paths = this.sourcePath;
+ }
+ return o;
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ var objects = this.getObjects();
+ var markup = [
+ '<g ',
+ 'width="', this.width, '" ',
+ 'height="', this.height, '" ',
+ 'style="', this.getSvgStyles(), '" ',
+ 'transform="', this.getSvgTransform(), '" ',
+ '>'
+ ];
+
+ for (var i = 0, len = objects.length; i < len; i++) {
+ markup.push(objects[i].toSVG());
+ }
+ markup.push('</g>');
+
+ return markup.join('');
+ },
+
+ /**
+ * Returns a string representation of this path group
+ * @method toString
+ * @return {String} string representation of an object
+ */
+ toString: function() {
+ return '#<fabric.PathGroup (' + this.complexity() +
+ '): { top: ' + this.top + ', left: ' + this.left + ' }>';
+ },
+
+ /**
+ * Returns true if all paths in this group are of same color
+ * @method isSameColor
+ * @return {Boolean} true if all paths are of the same color (`fill`)
+ */
+ isSameColor: function() {
+ var firstPathFill = this.getObjects()[0].get('fill');
+ return this.getObjects().every(function(path) {
+ return path.get('fill') === firstPathFill;
+ });
+ },
+
+ /**
+ * Returns number representation of object's complexity
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return this.paths.reduce(function(total, path) {
+ return total + ((path && path.complexity) ? path.complexity() : 0);
+ }, 0);
+ },
+
+ /**
+ * Makes path group grayscale
+ * @method toGrayscale
+ * @return {fabric.PathGroup} thisArg
+ */
+ toGrayscale: function() {
+ var i = this.paths.length;
+ while (i--) {
+ this.paths[i].toGrayscale();
+ }
+ return this;
+ },
+
+ /**
+ * Returns all paths in this path group
+ * @method getObjects
+ * @return {Array} array of path objects included in this path group
+ */
+ getObjects: function() {
+ return this.paths;
+ }
+ });
+
+ /**
+ * @private
+ * @method instantiatePaths
+ */
+ function instantiatePaths(paths) {
+ for (var i = 0, len = paths.length; i < len; i++) {
+ if (!(paths[i] instanceof fabric.Object)) {
+ var klassName = camelize(capitalize(paths[i].type));
+ paths[i] = fabric[klassName].fromObject(paths[i]);
+ }
+ }
+ return paths;
+ }
+
+ /**
+ * Creates fabric.Triangle instance from an object representation
+ * @static
+ * @method fabric.PathGroup.fromObject
+ * @param {Object} object
+ * @return {fabric.PathGroup}
+ */
+ fabric.PathGroup.fromObject = function(object) {
+ var paths = instantiatePaths(object.paths);
+ return new fabric.PathGroup(paths, object);
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global){
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ min = fabric.util.array.min,
+ max = fabric.util.array.max,
+ invoke = fabric.util.array.invoke,
+ removeFromArray = fabric.util.removeFromArray;
+
+ if (fabric.Group) {
+ return;
+ }
+
+ /**
+ * @class Group
+ * @extends fabric.Object
+ */
+ fabric.Group = fabric.util.createClass(fabric.Object, /** @scope fabric.Group.prototype */ {
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'group',
+
+ /**
+ * Constructor
+ * @method initialized
+ * @param {Object} objects Group objects
+ * @param {Object} [options] Options object
+ * @return {Object} thisArg
+ */
+ initialize: function(objects, options) {
+ this.objects = objects || [];
+ this.originalState = { };
+
+ this.callSuper('initialize');
+
+ this._calcBounds();
+ this._updateObjectsCoords();
+
+ if (options) {
+ extend(this, options);
+ }
+ this._setOpacityIfSame();
+
+ // group is active by default
+ this.setCoords(true);
+ this.saveCoords();
+
+ //this.activateAllObjects();
+ },
+
+ /**
+ * @private
+ * @method _updateObjectsCoords
+ */
+ _updateObjectsCoords: function() {
+ var groupDeltaX = this.left,
+ groupDeltaY = this.top;
+
+ this.forEachObject(function(object) {
+
+ var objectLeft = object.get('left'),
+ objectTop = object.get('top');
+
+ object.set('originalLeft', objectLeft);
+ object.set('originalTop', objectTop);
+
+ object.set('left', objectLeft - groupDeltaX);
+ object.set('top', objectTop - groupDeltaY);
+
+ object.setCoords();
+
+ // do not display corners of objects enclosed in a group
+ object.hideCorners = true;
+ }, this);
+ },
+
+ /**
+ * Returns string represenation of a group
+ * @method toString
+ * @return {String}
+ */
+ toString: function() {
+ return '#<fabric.Group: (' + this.complexity() + ')>';
+ },
+
+ /**
+ * Returns an array of all objects in this group
+ * @method getObjects
+ * @return {Array} group objects
+ */
+ getObjects: function() {
+ return this.objects;
+ },
+
+ /**
+ * Adds an object to a group; Then recalculates group's dimension, position.
+ * @method addWithUpdate
+ * @param {Object} object
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ addWithUpdate: function(object) {
+ this._restoreObjectsState();
+ this.objects.push(object);
+ this._calcBounds();
+ this._updateObjectsCoords();
+ return this;
+ },
+
+ /**
+ * Removes an object from a group; Then recalculates group's dimension, position.
+ * @method removeWithUpdate
+ * @param {Object} object
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ removeWithUpdate: function(object) {
+ this._restoreObjectsState();
+ removeFromArray(this.objects, object);
+ object.setActive(false);
+ this._calcBounds();
+ this._updateObjectsCoords();
+ return this;
+ },
+
+ /**
+ * Adds an object to a group
+ * @method add
+ * @param {Object} object
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ add: function(object) {
+ this.objects.push(object);
+ return this;
+ },
+
+ /**
+ * Removes an object from a group
+ * @method remove
+ * @param {Object} object
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ remove: function(object) {
+ removeFromArray(this.objects, object);
+ return this;
+ },
+
+ /**
+ * Returns a size of a group (i.e: length of an array containing its objects)
+ * @return {Number} Group size
+ */
+ size: function() {
+ return this.getObjects().length;
+ },
+
+ /**
+ * @private
+ */
+ _set: function(key, value) {
+ if (key === 'fill' || key === 'opacity') {
+ var i = this.objects.length;
+ this[key] = value;
+ while (i--) {
+ this.objects[i].set(key, value);
+ }
+ }
+ else {
+ this[key] = value;
+ }
+ },
+
+ /**
+ * Returns true if a group contains an object
+ * @method contains
+ * @param {Object} object Object to check against
+ * @return {Boolean} `true` if group contains an object
+ */
+ contains: function(object) {
+ return this.objects.indexOf(object) > -1;
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} object representation of an instance
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ objects: invoke(this.objects, 'toObject')
+ });
+ },
+
+ /**
+ * Renders instance on a given context
+ * @method render
+ * @param {CanvasRenderingContext2D} ctx context to render instance on
+ */
+ render: function(ctx, noTransform) {
+ ctx.save();
+ this.transform(ctx);
+
+ var groupScaleFactor = Math.max(this.scaleX, this.scaleY);
+
+ for (var i = 0, len = this.objects.length, object; object = this.objects[i]; i++) {
+ var originalScaleFactor = object.borderScaleFactor;
+ object.borderScaleFactor = groupScaleFactor;
+ object.render(ctx);
+ object.borderScaleFactor = originalScaleFactor;
+ }
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.hideCorners || this.drawCorners(ctx);
+ }
+ ctx.restore();
+ this.setCoords();
+ },
+
+ /**
+ * Returns object from the group at the specified index
+ * @method item
+ * @param index {Number} index of item to get
+ * @return {fabric.Object}
+ */
+ item: function(index) {
+ return this.getObjects()[index];
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return this.getObjects().reduce(function(total, object) {
+ total += (typeof object.complexity == 'function') ? object.complexity() : 0;
+ return total;
+ }, 0);
+ },
+
+ /**
+ * Retores original state of each of group objects (original state is that which was before group was created).
+ * @private
+ * @method _restoreObjectsState
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ _restoreObjectsState: function() {
+ this.objects.forEach(this._restoreObjectState, this);
+ return this;
+ },
+
+ /**
+ * Restores original state of a specified object in group
+ * @private
+ * @method _restoreObjectState
+ * @param {fabric.Object} object
+ * @return {fabric.Group} thisArg
+ */
+ _restoreObjectState: function(object) {
+
+ var groupLeft = this.get('left'),
+ groupTop = this.get('top'),
+ groupAngle = this.getAngle() * (Math.PI / 180),
+ objectLeft = object.get('originalLeft'),
+ objectTop = object.get('originalTop'),
+ rotatedTop = Math.cos(groupAngle) * object.get('top') + Math.sin(groupAngle) * object.get('left'),
+ rotatedLeft = -Math.sin(groupAngle) * object.get('top') + Math.cos(groupAngle) * object.get('left');
+
+ object.setAngle(object.getAngle() + this.getAngle());
+
+ object.set('left', groupLeft + rotatedLeft * this.get('scaleX'));
+ object.set('top', groupTop + rotatedTop * this.get('scaleY'));
+
+ object.set('scaleX', object.get('scaleX') * this.get('scaleX'));
+ object.set('scaleY', object.get('scaleY') * this.get('scaleY'));
+
+ object.setCoords();
+ object.hideCorners = false;
+ object.setActive(false);
+ object.setCoords();
+
+ return this;
+ },
+
+ /**
+ * Destroys a group (restoring state of its objects)
+ * @method destroy
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ destroy: function() {
+ return this._restoreObjectsState();
+ },
+
+ /**
+ * Saves coordinates of this instance (to be used together with `hasMoved`)
+ * @saveCoords
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ saveCoords: function() {
+ this._originalLeft = this.get('left');
+ this._originalTop = this.get('top');
+ return this;
+ },
+
+ /**
+ * Checks whether this group was moved (since `saveCoords` was called last)
+ * @method hasMoved
+ * @return {Boolean} true if an object was moved (since fabric.Group#saveCoords was called)
+ */
+ hasMoved: function() {
+ return this._originalLeft !== this.get('left') ||
+ this._originalTop !== this.get('top');
+ },
+
+ /**
+ * Sets coordinates of all group objects
+ * @method setObjectsCoords
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ setObjectsCoords: function() {
+ this.forEachObject(function(object) {
+ object.setCoords();
+ });
+ return this;
+ },
+
+ /**
+ * Activates (makes active) all group objects
+ * @method activateAllObjects
+ * @return {fabric.Group} thisArg
+ * @chainable
+ */
+ activateAllObjects: function() {
+ this.forEachObject(function(object) {
+ object.setActive();
+ });
+ return this;
+ },
+
+ /**
+ * Executes given function for each object in this group
+ * @method forEachObject
+ * @param {Function} callback
+ * Callback invoked with current object as first argument,
+ * index - as second and an array of all objects - as third.
+ * Iteration happens in reverse order (for performance reasons).
+ * 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 {fabric.Group} thisArg
+ * @chainable
+ */
+ forEachObject: fabric.StaticCanvas.prototype.forEachObject,
+
+ /**
+ * @private
+ * @method _setOpacityIfSame
+ */
+ _setOpacityIfSame: function() {
+ var objects = this.getObjects(),
+ firstValue = objects[0] ? objects[0].get('opacity') : 1;
+
+ var isSameOpacity = objects.every(function(o) {
+ return o.get('opacity') === firstValue;
+ });
+
+ if (isSameOpacity) {
+ this.opacity = firstValue;
+ }
+ },
+
+ /**
+ * @private
+ * @method _calcBounds
+ */
+ _calcBounds: function() {
+ var aX = [],
+ aY = [],
+ minX, minY, maxX, maxY, o, width, height,
+ i = 0,
+ len = this.objects.length;
+
+ for (; i < len; ++i) {
+ o = this.objects[i];
+ o.setCoords();
+ for (var prop in o.oCoords) {
+ aX.push(o.oCoords[prop].x);
+ aY.push(o.oCoords[prop].y);
+ }
+ };
+
+ minX = min(aX);
+ maxX = max(aX);
+ minY = min(aY);
+ maxY = max(aY);
+
+ width = (maxX - minX) || 0;
+ height = (maxY - minY) || 0;
+
+ this.width = width;
+ this.height = height;
+
+ this.left = (minX + width / 2) || 0;
+ this.top = (minY + height / 2) || 0;
+ },
+
+ /**
+ * Checks if point is contained within the group
+ * @method containsPoint
+ * @param {fabric.Point} point point with `x` and `y` properties
+ * @return {Boolean} true if point is contained within group
+ */
+ containsPoint: function(point) {
+
+ var halfWidth = this.get('width') / 2,
+ halfHeight = this.get('height') / 2,
+ centerX = this.get('left'),
+ centerY = this.get('top');
+
+ return centerX - halfWidth < point.x &&
+ centerX + halfWidth > point.x &&
+ centerY - halfHeight < point.y &&
+ centerY + halfHeight > point.y;
+ },
+
+ /**
+ * Makes all of this group's objects grayscale (i.e. calling `toGrayscale` on them)
+ * @method toGrayscale
+ */
+ toGrayscale: function() {
+ var i = this.objects.length;
+ while (i--) {
+ this.objects[i].toGrayscale();
+ }
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ var objectsMarkup = [ ];
+ for (var i = 0, len = this.objects.length; i < len; i++) {
+ objectsMarkup.push(this.objects[i].toSVG());
+ }
+
+ return (
+ '<g transform="' + this.getSvgTransform() + '">' +
+ objectsMarkup.join('') +
+ '</g>');
+ }
+ });
+
+ /**
+ * Returns fabric.Group instance from an object representation
+ * @static
+ * @method fabric.Group.fromObject
+ * @param object {Object} object to create a group from
+ * @param options {Object} options object
+ * @return {fabric.Group} an instance of fabric.Group
+ */
+ fabric.Group.fromObject = function(object, callback) {
+ fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) {
+ delete object.objects;
+ callback && callback(new fabric.Group(enlivenedObjects, object));
+ });
+ };
+
+ fabric.Group.async = true;
+
+})(typeof exports != 'undefined' ? exports : this);
+(function(global) {
+
+ "use strict";
+
+ var extend = fabric.util.object.extend;
+
+ if (!global.fabric) {
+ global.fabric = { };
+ }
+
+ if (global.fabric.Image) {
+ fabric.warn('fabric.Image is already defined.');
+ return;
+ };
+
+ if (!fabric.Object) {
+ fabric.warn('fabric.Object is required for fabric.Image initialization');
+ return;
+ }
+
+ /**
+ * @class Image
+ * @extends fabric.Object
+ */
+ fabric.Image = fabric.util.createClass(fabric.Object, /** @scope fabric.Image.prototype */ {
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ active: false,
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ bordervisibility: false,
+
+ /**
+ * @property
+ * @type Boolean
+ */
+ cornervisibility: false,
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'image',
+
+ /**
+ * Constructor
+ * @param {HTMLImageElement | String} element Image element
+ * @param {Object} options optional
+ */
+ initialize: function(element, options) {
+ options || (options = { });
+
+ this.callSuper('initialize', options);
+ this._initElement(element);
+ this._originalImage = this.getElement();
+ this._initConfig(options);
+
+ this.filters = [ ];
+
+ if (options.filters) {
+ this.filters = options.filters;
+ this.applyFilters();
+ }
+ },
+
+ /**
+ * Returns image element which this instance if based on
+ * @method getElement
+ * @return {HTMLImageElement} image element
+ */
+ getElement: function() {
+ return this._element;
+ },
+
+ /**
+ * Sets image element for this instance to a specified one
+ * @method setElement
+ * @param {HTMLImageElement} element
+ * @return {fabric.Image} thisArg
+ * @chainable
+ */
+ setElement: function(element) {
+ this._element = element;
+ this._initConfig();
+ return this;
+ },
+
+ /**
+ * Returns original size of an image
+ * @method getOriginalSize
+ * @return {Object} object with "width" and "height" properties
+ */
+ getOriginalSize: function() {
+ var element = this.getElement();
+ return {
+ width: element.width,
+ height: element.height
+ };
+ },
+
+ /**
+ * Sets border visibility
+ * @method setBorderVisibility
+ * @param {Boolean} visible When true, border is set to be visible
+ */
+ setBorderVisibility: function(visible) {
+ this._resetWidthHeight();
+ this._adjustWidthHeightToBorders(showBorder);
+ this.setCoords();
+ },
+
+ /**
+ * Sets corner visibility
+ * @method setCornersVisibility
+ * @param {Boolean} visible When true, corners are set to be visible
+ */
+ setCornersVisibility: function(visible) {
+ this.cornervisibility = !!visible;
+ },
+
+ /**
+ * Renders image on a specified context
+ * @method render
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ render: function(ctx, noTransform) {
+ ctx.save();
+ var m = this.transformMatrix;
+ this._resetWidthHeight();
+ if (this.group) {
+ ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2);
+ }
+ if (m) {
+ ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
+ }
+ if (!noTransform) {
+ this.transform(ctx);
+ }
+ this._render(ctx);
+ if (this.active && !noTransform) {
+ this.drawBorders(ctx);
+ this.hideCorners || this.drawCorners(ctx);
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} Object representation of an instance
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ src: this._originalImage.src || this._originalImage._src,
+ filters: this.filters.concat()
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+ return '<g transform="' + this.getSvgTransform() + '">'+
+ '<image xlink:href="' + this.getSvgSrc() + '" '+
+ 'style="' + this.getSvgStyles() + '" ' +
+ // we're essentially moving origin of transformation from top/left corner to the center of the shape
+ // by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
+ // so that object's center aligns with container's left/top
+ 'transform="translate('+ (-this.width/2) + ' ' + (-this.height/2) + ')" ' +
+ 'width="' + this.width + '" ' +
+ 'height="' + this.height + '"' + '/>'+
+ '</g>';
+ },
+
+ /**
+ * Returns source of an image
+ * @method getSrc
+ * @return {String} Source of an image
+ */
+ getSrc: function() {
+ return this.getElement().src || this.getElement()._src;
+ },
+
+ /**
+ * Returns string representation of an instance
+ * @method toString
+ * @return {String} String representation of an instance
+ */
+ toString: function() {
+ return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
+ },
+
+ /**
+ * Returns a clone of an instance
+ * @mthod clone
+ * @param {Function} callback Callback is invoked with a clone as a first argument
+ */
+ clone: function(callback) {
+ this.constructor.fromObject(this.toObject(), callback);
+ },
+
+ /**
+ * Applies filters assigned to this image (from "filters" array)
+ * @mthod applyFilters
+ * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated
+ */
+ applyFilters: function(callback) {
+
+ if (this.filters.length === 0) {
+ this.setElement(this._originalImage);
+ callback && callback();
+ return;
+ }
+
+ var isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined',
+ imgEl = this._originalImage,
+ canvasEl = fabric.document.createElement('canvas'),
+ replacement = isLikelyNode ? new (require('canvas').Image) : fabric.document.createElement('img'),
+ _this = this;
+
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager != 'undefined') {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+
+ canvasEl.width = imgEl.width;
+ canvasEl.height = imgEl.height;
+
+ canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height);
+
+ this.filters.forEach(function(filter) {
+ filter && filter.applyTo(canvasEl);
+ });
+
+ /** @ignore */
+ replacement.onload = function() {
+ _this._element = replacement;
+ callback && callback();
+ replacement.onload = canvasEl = imgEl = null;
+ };
+ replacement.width = imgEl.width;
+ replacement.height = imgEl.height;
+
+ if (isLikelyNode) {
+ var base64str = canvasEl.toDataURL('image/png').replace(/data:image\/png;base64,/, '');
+ replacement.src = new Buffer(base64str, 'base64');
+ _this._element = replacement;
+
+ // onload doesn't fire in node, so we invoke callback manually
+ callback && callback();
+ }
+ else {
+ replacement.src = canvasEl.toDataURL('image/png');
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ _render: function(ctx) {
+ ctx.drawImage(
+ this.getElement(),
+ - this.width / 2,
+ -this.height / 2,
+ this.width,
+ this.height
+ );
+ },
+
+ /**
+ * @private
+ */
+ _adjustWidthHeightToBorders: function(showBorder) {
+ if (showBorder) {
+ this.currentBorder = this.borderwidth;
+ this.width += (2 * this.currentBorder);
+ this.height += (2 * this.currentBorder);
+ }
+ else {
+ this.currentBorder = 0;
+ }
+ },
+
+ /**
+ * @private
+ */
+ _resetWidthHeight: function() {
+ var element = this.getElement();
+
+ this.set('width', element.width);
+ this.set('height', element.height);
+ },
+
+ /**
+ * The Image class's initialization method. This method is automatically
+ * called by the constructor.
+ * @method _initElement
+ * @param {HTMLImageElement|String} el The element representing the image
+ */
+ _initElement: function(element) {
+ this.setElement(fabric.util.getById(element));
+ fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
+ },
+
+ /**
+ * @method _initConfig
+ * @param {Object} options Options object
+ */
+ _initConfig: function(options) {
+ options || (options = { });
+ this.setOptions(options);
+ this._setBorder();
+ this._setWidthHeight(options);
+ },
+
+ /**
+ * @method _initFilters
+ * @param {Object} object Object with filters property
+ */
+ _initFilters: function(object) {
+ if (object.filters && object.filters.length) {
+ this.filters = object.filters.map(function(filterObj) {
+ return filterObj && fabric.Image.filters[filterObj.type].fromObject(filterObj);
+ });
+ }
+ },
+
+ /**
+ * @private
+ */
+ _setBorder: function() {
+ if (this.bordervisibility) {
+ this.currentBorder = this.borderwidth;
+ }
+ else {
+ this.currentBorder = 0;
+ }
+ },
+
+ /**
+ * @private
+ */
+ _setWidthHeight: function(options) {
+ var sidesBorderWidth = 2 * this.currentBorder;
+
+ this.width = 'width' in options
+ ? options.width
+ : ((this.getElement().width || 0) + sidesBorderWidth);
+
+ this.height = 'height' in options
+ ? options.height
+ : ((this.getElement().height || 0) + sidesBorderWidth);
+ },
+
+ /**
+ * Returns complexity of an instance
+ * @method complexity
+ * @return {Number} complexity
+ */
+ complexity: function() {
+ return 1;
+ }
+ });
+
+ /**
+ * Default CSS class name for canvas
+ * @static
+ * @type String
+ */
+ fabric.Image.CSS_CANVAS = "canvas-img";
+
+ fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
+
+ /**
+ * Creates an instance of fabric.Image from its object representation
+ * @static
+ * @method fromObject
+ * @param object {Object}
+ * @param callback {Function} optional
+ */
+ fabric.Image.fromObject = function(object, callback) {
+ var img = fabric.document.createElement('img'),
+ src = object.src;
+
+ if (object.width) {
+ img.width = object.width;
+ }
+ if (object.height) {
+ img.height = object.height;
+ }
+
+ /** @ignore */
+ img.onload = function() {
+ fabric.Image.prototype._initFilters.call(object, object);
+
+ var instance = new fabric.Image(img, object);
+ callback && callback(instance);
+ img = img.onload = null;
+ };
+ img.src = src;
+ };
+
+ /**
+ * Creates an instance of fabric.Image from an URL string
+ * @static
+ * @method fromURL
+ * @param {String} url URL to create an image from
+ * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
+ * @param {Object} [imgOptions] Options object
+ */
+ fabric.Image.fromURL = function(url, callback, imgOptions) {
+ var img = fabric.document.createElement('img');
+
+ /** @ignore */
+ img.onload = function() {
+ if (callback) {
+ callback(new fabric.Image(img, imgOptions));
+ }
+ img = img.onload = null;
+ };
+ img.src = url;
+ };
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
+ * @static
+ * @see http://www.w3.org/TR/SVG/struct.html#ImageElement
+ */
+ fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href'.split(' ');
+
+ /**
+ * Returns {@link fabric.Image} instance from an SVG element
+ * @static
+ * @method fabric.Image.fromElement
+ * @param {SVGElement} element Element to parse
+ * @param {Function} callback Callback to execute when fabric.Image object is created
+ * @param {Object} [options] Options object
+ * @return {fabric.Image}
+ */
+ fabric.Image.fromElement = function(element, callback, options) {
+ options || (options = { });
+
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
+
+ fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend(parsedAttributes, options));
+ };
+
+ fabric.Image.async = true;
+
+})(typeof exports != 'undefined' ? exports : this);
+
+fabric.util.object.extend(fabric.Object.prototype, {
+
+ /**
+ * @method _getAngleValueForStraighten
+ * @return {Number} angle value
+ * @private
+ */
+ _getAngleValueForStraighten: function() {
+ var angle = this.get('angle');
+
+ // TODO (kangax): can this be simplified?
+
+ if (angle > -225 && angle <= -135) { return -180; }
+ else if (angle > -135 && angle <= -45) { return -90; }
+ else if (angle > -45 && angle <= 45) { return 0; }
+ else if (angle > 45 && angle <= 135) { return 90; }
+ else if (angle > 135 && angle <= 225 ) { return 180; }
+ else if (angle > 225 && angle <= 315) { return 270; }
+ else if (angle > 315) { return 360; }
+
+ return 0;
+ },
+
+ /**
+ * @method straighten
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ straighten: function() {
+ var angle = this._getAngleValueForStraighten();
+ this.setAngle(angle);
+ return this;
+ },
+
+ /**
+ * @method fxStraighten
+ * @param {Object} callbacks
+ * - onComplete: invoked on completion
+ * - onChange: invoked on every step of animation
+ *
+ * @return {fabric.Object} thisArg
+ * @chainable
+ */
+ fxStraighten: function(callbacks) {
+ callbacks = callbacks || { };
+
+ var empty = function() { },
+ onComplete = callbacks.onComplete || empty,
+ onChange = callbacks.onChange || empty,
+ _this = this;
+
+ fabric.util.animate({
+ startValue: this.get('angle'),
+ endValue: this._getAngleValueForStraighten(),
+ duration: this.FX_DURATION,
+ onChange: function(value) {
+ _this.setAngle(value);
+ onChange();
+ },
+ onComplete: function() {
+ _this.setCoords();
+ onComplete();
+ },
+ onStart: function() {
+ _this.setActive(false);
+ }
+ });
+
+ return this;
+ }
+});
+
+fabric.util.object.extend(fabric.StaticCanvas.prototype, {
+
+ /**
+ * Straightens object, then rerenders canvas
+ * @method straightenObject
+ * @param {fabric.Object} object Object to straighten
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ straightenObject: function (object) {
+ object.straighten();
+ this.renderAll();
+ return this;
+ },
+
+ /**
+ * Same as `fabric.Canvas#straightenObject`, but animated
+ * @method fxStraightenObject
+ * @param {fabric.Object} object Object to straighten
+ * @return {fabric.Canvas} thisArg
+ * @chainable
+ */
+ fxStraightenObject: function (object) {
+ object.fxStraighten({
+ onChange: this.renderAll.bind(this)
+ });
+ return this;
+ }
+});
+/**
+ * @namespace
+ */
+fabric.Image.filters = { };
+
+/**
+ * @class fabric.Image.filters.Grayscale
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Grayscale = fabric.util.createClass( /** @scope fabric.Image.filters.Grayscale.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Grayscale",
+
+ /**
+ * @method applyTo
+ * @memberOf fabric.Image.filters.Grayscale.prototype
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = imageData.width,
+ jLen = imageData.height,
+ index, average, i, j;
+
+ for (i = 0; i < iLen; i++) {
+ for (j = 0; j < jLen; j++) {
+
+ index = (i * 4) * jLen + (j * 4);
+ average = (data[index] + data[index + 1] + data[index + 2]) / 3;
+
+ data[index] = average;
+ data[index + 1] = average;
+ data[index + 2] = average;
+ }
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return { type: this.type };
+ }
+});
+
+fabric.Image.filters.Grayscale.fromObject = function() {
+ return new fabric.Image.filters.Grayscale();
+};
+
+/**
+ * @class fabric.Image.filters.RemoveWhite
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.RemoveWhite = fabric.util.createClass( /** @scope fabric.Image.filters.RemoveWhite.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "RemoveWhite",
+
+ /**
+ * @memberOf fabric.Image.filters.RemoveWhite.prototype
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ options || (options = { });
+ this.threshold = options.threshold || 30;
+ this.distance = options.distance || 20;
+ },
+
+ /**
+ * @method applyTo
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ threshold = this.threshold,
+ distance = this.distance,
+ limit = 255 - threshold,
+ abs = Math.abs,
+ r, g, b;
+
+ for (var i = 0, len = data.length; i < len; i += 4) {
+
+ r = data[i];
+ g = data[i+1];
+ b = data[i+2];
+
+ if (r > limit &&
+ g > limit &&
+ b > limit &&
+ abs(r-g) < distance &&
+ abs(r-b) < distance &&
+ abs(g-b) < distance) {
+
+ data[i+3] = 1;
+ }
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return {
+ type: this.type,
+ threshold: this.threshold,
+ distance: this.distance
+ };
+ }
+});
+
+fabric.Image.filters.RemoveWhite.fromObject = function(object) {
+ return new fabric.Image.filters.RemoveWhite(object);
+};
+
+/**
+ * @class fabric.Image.filters.Invert
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Invert = fabric.util.createClass( /** @scope fabric.Image.filters.Invert.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Invert",
+
+ /**
+ * @method applyTo
+ * @memberOf fabric.Image.filters.Invert.prototype
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = data.length, i;
+
+ for (i = 0; i < iLen; i+=4) {
+ data[i] = 255 - data[i];
+ data[i + 1] = 255 - data[i + 1];
+ data[i + 2] = 255 - data[i + 2];
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return { type: this.type };
+ }
+});
+
+fabric.Image.filters.Invert.fromObject = function() {
+ return new fabric.Image.filters.Invert();
+};
+
+/**
+ * @class fabric.Image.filters.Sepia
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Sepia = fabric.util.createClass( /** @scope fabric.Image.filters.Sepia.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Sepia",
+
+ /**
+ * @method applyTo
+ * @memberOf fabric.Image.filters.Sepia.prototype
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = data.length, i, avg;
+
+ for (i = 0; i < iLen; i+=4) {
+ avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
+ data[i] = avg + 100;
+ data[i + 1] = avg + 50;
+ data[i + 2] = avg + 255;
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return { type: this.type };
+ }
+});
+
+fabric.Image.filters.Sepia.fromObject = function() {
+ return new fabric.Image.filters.Sepia();
+};
+
+/**
+ * @class fabric.Image.filters.Sepia2
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Sepia2 = fabric.util.createClass( /** @scope fabric.Image.filters.Sepia2.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Sepia2",
+
+ /**
+ * @method applyTo
+ * @memberOf fabric.Image.filters.Sepia.prototype
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = data.length, i, r, g, b;
+
+ for (i = 0; i < iLen; i+=4) {
+
+ r = data[i];
+ g = data[i + 1];
+ b = data[i + 2];
+
+ data[i] = (r * 0.393 + g * 0.769 + b * 0.189 ) / 1.351;
+ data[i + 1] = (r * 0.349 + g * 0.686 + b * 0.168 ) / 1.203;
+ data[i + 2] = (r * 0.272 + g * 0.534 + b * 0.131 ) / 2.140;
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return { type: this.type };
+ }
+});
+
+fabric.Image.filters.Sepia2.fromObject = function() {
+ return new fabric.Image.filters.Sepia2();
+};
+
+/**
+ * @class fabric.Image.filters.Brightness
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Brightness = fabric.util.createClass( /** @scope fabric.Image.filters.Brightness.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Brightness",
+
+ /**
+ * @memberOf fabric.Image.filters.Brightness.prototype
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ options || (options = { });
+ this.brightness = options.brightness || 100;
+ },
+
+ /**
+ * @method applyTo
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ brightness = this.brightness;
+
+ for (var i = 0, len = data.length; i < len; i += 4) {
+ data[i] += brightness;
+ data[i + 1] += brightness;
+ data[i + 2] += brightness;
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return {
+ type: this.type,
+ brightness: this.brightness
+ };
+ }
+});
+
+fabric.Image.filters.Brightness.fromObject = function(object) {
+ return new fabric.Image.filters.Brightness(object);
+};
+
+/**
+ * @class fabric.Image.filters.Brightness
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Noise = fabric.util.createClass( /** @scope fabric.Image.filters.Noise.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Noise",
+
+ /**
+ * @memberOf fabric.Image.filters.Brightness.prototype
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ options || (options = { });
+ this.noise = options.noise || 100;
+ },
+
+ /**
+ * @method applyTo
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ noise = this.noise, rand;
+
+ for (var i = 0, len = data.length; i < len; i += 4) {
+
+ rand = (0.5 - Math.random()) * noise;
+
+ data[i] += rand;
+ data[i + 1] += rand;
+ data[i + 2] += rand;
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return {
+ type: this.type,
+ noise: this.noise
+ };
+ }
+});
+
+fabric.Image.filters.Noise.fromObject = function(object) {
+ return new fabric.Image.filters.Noise(object);
+};
+
+/**
+ * @class fabric.Image.filters.Brightness
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.GradientTransparency = fabric.util.createClass( /** @scope fabric.Image.filters.GradientTransparency.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "GradientTransparency",
+
+ /**
+ * @memberOf fabric.Image.filters.GradientTransparency.prototype
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ options || (options = { });
+ this.threshold = options.threshold || 100;
+ },
+
+ /**
+ * @method applyTo
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ threshold = this.threshold,
+ total = data.length;
+
+ for (var i = 0, len = data.length; i < len; i += 4) {
+ data[i + 3] = threshold + 255 * (total - i) / total;
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return {
+ type: this.type,
+ threshold: this.threshold
+ };
+ }
+});
+
+fabric.Image.filters.GradientTransparency.fromObject = function(object) {
+ return new fabric.Image.filters.GradientTransparency(object);
+};
+
+/**
+ * @class fabric.Image.filters.Tint
+ * @memberOf fabric.Image.filters
+ */
+fabric.Image.filters.Tint = fabric.util.createClass( /** @scope fabric.Image.filters.Tint.prototype */ {
+
+ /**
+ * @param {String} type
+ */
+ type: "Tint",
+
+ /**
+ * @memberOf fabric.Image.filters.RemoveWhite.prototype
+ * @param {Object} [options] Options object
+ */
+ initialize: function(options) {
+ options || (options = { });
+ this.color = options.color || 0;
+ },
+
+ /**
+ * @method applyTo
+ * @param {Object} canvasEl Canvas element to apply filter to
+ */
+ applyTo: function(canvasEl) {
+
+ var context = canvasEl.getContext('2d'),
+ imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
+ data = imageData.data,
+ iLen = data.length, i,
+ r, g, b, a;
+
+ var rgb = parseInt(this.color).toString(16);
+ var cr = parseInt('0x'+rgb.substr(0, 2));
+ var cg = parseInt('0x'+rgb.substr(2, 2));
+ var cb = parseInt('0x'+rgb.substr(4, 2));
+
+ for (i = 0; i < iLen; i+=4) {
+
+ a = data[i+3];
+
+ if (a > 0){
+ data[i] = cr;
+ data[i+1] = cg;
+ data[i+2] = cb;
+ }
+ }
+
+ context.putImageData(imageData, 0, 0);
+ },
+
+ /**
+ * @method toJSON
+ * @return {String} json representation of filter
+ */
+ toJSON: function() {
+ return {
+ type: this.type,
+ color: this.color
+ };
+ }
+});
+
+fabric.Image.filters.Tint.fromObject = function(object) {
+ return new fabric.Image.filters.Tint(object);
+};
+(function(global) {
+
+ "use strict";
+
+ var fabric = global.fabric || (global.fabric = { }),
+ extend = fabric.util.object.extend,
+ clone = fabric.util.object.clone,
+ toFixed = fabric.util.toFixed;
+
+ if (fabric.Text) {
+ fabric.warn('fabric.Text is already defined');
+ return;
+ }
+ if (!fabric.Object) {
+ fabric.warn('fabric.Text requires fabric.Object');
+ return;
+ }
+
+ /**
+ * @class Text
+ * @extends fabric.Object
+ */
+ fabric.Text = fabric.util.createClass(fabric.Object, /** @scope fabric.Text.prototype */ {
+
+ /**
+ * @property
+ * @type Number
+ */
+ fontSize: 40,
+
+ /**
+ * @property
+ * @type Number
+ */
+ fontWeight: 100,
+
+ /**
+ * @property
+ * @type String
+ */
+ fontFamily: 'Times New Roman',
+
+ /**
+ * @property
+ * @type String
+ */
+ textDecoration: '',
+
+ /**
+ * @property
+ * @type String | null
+ */
+ textShadow: '',
+
+ /**
+ * Determines text alignment. Possible values: "left", "center", or "right".
+ * @property
+ * @type String
+ */
+ textAlign: 'left',
+
+ /**
+ * @property
+ * @type String
+ */
+ fontStyle: '',
+
+ /**
+ * @property
+ * @type Number
+ */
+ lineHeight: 1.3,
+
+ /**
+ * @property
+ * @type String
+ */
+ strokeStyle: '',
+
+ /**
+ * @property
+ * @type Number
+ */
+ strokeWidth: 1,
+
+ /**
+ * @property
+ * @type String
+ */
+ backgroundColor: '',
+
+
+ /**
+ * @property
+ * @type String | null
+ */
+ path: null,
+
+ /**
+ * @property
+ * @type String
+ */
+ type: 'text',
+
+ /**
+ * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used)
+ * @property
+ * @type Boolean
+ */
+ useNative: true,
+
+ /**
+ * Constructor
+ * @method initialize
+ * @param {String} text
+ * @param {Object} [options]
+ * @return {fabric.Text} thisArg
+ */
+ initialize: function(text, options) {
+ this._initStateProperties();
+ this.text = text;
+ this.setOptions(options || { });
+ this._theta = this.angle * Math.PI / 180;
+ this._initDimensions();
+ this.setCoords();
+ },
+
+ /**
+ * Renders text object on offscreen canvas, so that it would get dimensions
+ * @private
+ * @method _initDimensions
+ */
+ _initDimensions: function() {
+ var canvasEl = fabric.document.createElement('canvas');
+
+ if (!canvasEl.getContext && typeof G_vmlCanvasManager != 'undefined') {
+ G_vmlCanvasManager.initElement(canvasEl);
+ }
+
+ this._render(canvasEl.getContext('2d'));
+ },
+
+ /**
+ * Creates `stateProperties` list on an instance, and adds `fabric.Text` -specific ones to it
+ * (such as "fontFamily", "fontWeight", etc.)
+ * @private
+ * @method _initStateProperties
+ */
+ _initStateProperties: function() {
+ this.stateProperties = this.stateProperties.concat();
+ this.stateProperties.push(
+ 'fontFamily',
+ 'fontWeight',
+ 'fontSize',
+ 'path',
+ 'text',
+ 'textDecoration',
+ 'textShadow',
+ 'textAlign',
+ 'fontStyle',
+ 'lineHeight',
+ 'strokeStyle',
+ 'strokeWidth',
+ 'backgroundColor',
+ 'useNative'
+ );
+ fabric.util.removeFromArray(this.stateProperties, 'width');
+ },
+
+ /**
+ * Returns string representation of an instance
+ * @method toString
+ * @return {String} String representation of text object
+ */
+ toString: function() {
+ return '#<fabric.Text (' + this.complexity() +
+ '): { "text": "' + this.text + '", "fontFamily": "' + this.fontFamily + '" }>';
+ },
+
+ /**
+ * @private
+ * @method _render
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _render: function(ctx) {
+ if (typeof Cufon === 'undefined' || this.useNative === true) {
+ this._renderViaNative(ctx);
+ }
+ else {
+ this._renderViaCufon(ctx);
+ }
+ },
+
+ /**
+ * @private
+ * @method _renderViaCufon
+ */
+ _renderViaCufon: function(ctx) {
+ var o = Cufon.textOptions || (Cufon.textOptions = { });
+
+ // export options to be used by cufon.js
+ o.left = this.left;
+ o.top = this.top;
+ o.context = ctx;
+ o.color = this.fill;
+
+ var el = this._initDummyElementForCufon();
+
+ // set "cursor" to top/left corner
+ this.transform(ctx);
+
+ // draw text
+ Cufon.replaceElement(el, {
+ engine: 'canvas',
+ separate: 'none',
+ fontFamily: this.fontFamily,
+ fontWeight: this.fontWeight,
+ textDecoration: this.textDecoration,
+ textShadow: this.textShadow,
+ textAlign: this.textAlign,
+ fontStyle: this.fontStyle,
+ lineHeight: this.lineHeight,
+ strokeStyle: this.strokeStyle,
+ strokeWidth: this.strokeWidth,
+ backgroundColor: this.backgroundColor
+ });
+
+ // update width, height
+ this.width = o.width;
+ this.height = o.height;
+
+ this._totalLineHeight = o.totalLineHeight;
+ this._fontAscent = o.fontAscent;
+ this._boundaries = o.boundaries;
+ this._shadowOffsets = o.shadowOffsets;
+ this._shadows = o.shadows || [ ];
+
+ el = null;
+
+ // need to set coords _after_ the width/height was retreived from Cufon
+ this.setCoords();
+ },
+
+ /**
+ * @private
+ * @method _render_native
+ * @param {CanvasRenderingContext2D} ctx Context to render on
+ */
+ _renderViaNative: function(ctx) {
+
+ this.transform(ctx);
+ this._setTextStyles(ctx);
+
+ var textLines = this.text.split(/\r?\n/);
+
+ this.width = this._getTextWidth(ctx, textLines);
+ this.height = this._getTextHeight(ctx, textLines);
+
+ this._renderTextBackground(ctx, textLines);
+
+ if (this.textAlign !== 'left') {
+ ctx.save();
+ ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0);
+ }
+
+ this._setTextShadow(ctx);
+ this._renderTextFill(ctx, textLines);
+ this.textShadow && ctx.restore();
+
+ this._renderTextStroke(ctx, textLines);
+ if (this.textAlign !== 'left') {
+ ctx.restore();
+ }
+
+ this._renderTextDecoration(ctx, textLines);
+ this._setBoundaries(ctx, textLines);
+ this._totalLineHeight = 0;
+
+ this.setCoords();
+ },
+
+ /**
+ * @private
+ * @method _setBoundaries
+ */
+ _setBoundaries: function(ctx, textLines) {
+ this._boundaries = [ ];
+
+ for (var i = 0, len = textLines.length; i < len; i++) {
+
+ var lineWidth = ctx.measureText(textLines[i]).width;
+ var lineLeftOffset = this._getLineLeftOffset(lineWidth);
+
+ this._boundaries.push({
+ height: this.fontSize,
+ width: lineWidth,
+ left: lineLeftOffset
+ });
+ }
+ },
+
+ /**
+ * @private
+ * @method _setTextStyles
+ */
+ _setTextStyles: function(ctx) {
+ ctx.fillStyle = this.fill;
+ ctx.strokeStyle = this.strokeStyle;
+ ctx.lineWidth = this.strokeWidth;
+ ctx.textBaseline = 'top';
+ ctx.textAlign = this.textAlign;
+ ctx.font = this._getFontDeclaration();
+ },
+
+ /**
+ * @private
+ * @method _getTextHeight
+ */
+ _getTextHeight: function(ctx, textLines) {
+ return this.fontSize * textLines.length * this.lineHeight;
+ },
+
+ /**
+ * @private
+ * @method _getTextWidth
+ */
+ _getTextWidth: function(ctx, textLines) {
+ var maxWidth = ctx.measureText(textLines[0]).width;
+
+ for (var i = 1, len = textLines.length; i < len; i++) {
+ var currentLineWidth = ctx.measureText(textLines[i]).width;
+ if (currentLineWidth > maxWidth) {
+ maxWidth = currentLineWidth;
+ }
+ }
+ return maxWidth;
+ },
+
+ /**
+ * @private
+ * @method _setTextShadow
+ */
+ _setTextShadow: function(ctx) {
+ if (this.textShadow) {
+
+ // "rgba(0,0,0,0.2) 2px 2px 10px"
+ // "rgb(0, 100, 0) 0 0 5px"
+ // "red 2px 2px 1px"
+ // "#f55 123 345 567"
+ var reOffsetsAndBlur = /\s+(-?\d+)(?:px)?\s+(-?\d+)(?:px)?\s+(\d+)(?:px)?\s*/;
+
+ var shadowDeclaration = this.textShadow;
+ var offsetsAndBlur = reOffsetsAndBlur.exec(this.textShadow);
+ var shadowColor = shadowDeclaration.replace(reOffsetsAndBlur, '');
+
+ ctx.save();
+ ctx.shadowColor = shadowColor;
+ ctx.shadowOffsetX = parseInt(offsetsAndBlur[1], 10);
+ ctx.shadowOffsetY = parseInt(offsetsAndBlur[2], 10);
+ ctx.shadowBlur = parseInt(offsetsAndBlur[3], 10);
+
+ this._shadows = [{
+ blur: ctx.shadowBlur,
+ color: ctx.shadowColor,
+ offX: ctx.shadowOffsetX,
+ offY: ctx.shadowOffsetY
+ }];
+
+ this._shadowOffsets = [[
+ parseInt(ctx.shadowOffsetX, 10), parseInt(ctx.shadowOffsetY, 10)
+ ]];
+ }
+ },
+
+ _renderTextFill: function(ctx, textLines) {
+ this._boundaries = [ ];
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ ctx.fillText(
+ textLines[i],
+ -this.width / 2,
+ (-this.height / 2) + (i * this.fontSize * this.lineHeight)
+ );
+ }
+ },
+
+ /**
+ * @private
+ * @method _renderTextStroke
+ */
+ _renderTextStroke: function(ctx, textLines) {
+ if (this.strokeStyle) {
+ for (var i = 0, len = textLines.length; i < len; i++) {
+ ctx.strokeText(
+ textLines[i],
+ -this.width / 2,
+ (-this.height / 2) + (i * this.fontSize * this.lineHeight)
+ );
+ }
+ }
+ },
+
+ /**
+ * @private
+ * @_renderTextBackground
+ */
+ _renderTextBackground: function(ctx, textLines) {
+ if (this.backgroundColor) {
+ ctx.save();
+ ctx.fillStyle = this.backgroundColor;
+
+ for (var i = 0, len = textLines.length; i < len; i++) {
+
+ var lineWidth = ctx.measureText(textLines[i]).width;
+ var lineLeftOffset = this._getLineLeftOffset(lineWidth);
+
+ ctx.fillRect(
+ (-this.width / 2) + lineLeftOffset,
+ (-this.height / 2) + (i * this.fontSize * this.lineHeight),
+ lineWidth,
+ this.fontSize
+ );
+ }
+ ctx.restore();
+ }
+ },
+
+ /**
+ * @private
+ * @method _getLineLeftOffset
+ */
+ _getLineLeftOffset: function(lineWidth) {
+ if (this.textAlign === 'center') {
+ return (this.width - lineWidth) / 2;
+ }
+ if (this.textAlign === 'right') {
+ return this.width - lineWidth;
+ }
+ return 0;
+ },
+
+ /**
+ * @private
+ * @method _renderTextDecoration
+ */
+ _renderTextDecoration: function(ctx, textLines) {
+
+ var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2;
+
+ function renderLinesAtOffset(offset) {
+ for (var i = 0, len = textLines.length; i < len; i++) {
+
+ var lineWidth = ctx.measureText(textLines[i]).width;
+ var lineLeftOffset = this._getLineLeftOffset(lineWidth);
+
+ ctx.fillRect(
+ (-this.width / 2) + lineLeftOffset,
+ (offset + (i * this.fontSize * this.lineHeight)) - halfOfVerticalBox,
+ lineWidth,
+ 1);
+ }
+ }
+
+ if (this.textDecoration.indexOf('underline') > -1) {
+ renderLinesAtOffset.call(this, this.fontSize);
+ }
+ if (this.textDecoration.indexOf('line-through') > -1) {
+ renderLinesAtOffset.call(this, this.fontSize / 2);
+ }
+ if (this.textDecoration.indexOf('overline') > -1) {
+ renderLinesAtOffset.call(this, 0);
+ }
+ },
+
+ /**
+ * @private
+ * @method _getFontDeclaration
+ */
+ _getFontDeclaration: function() {
+ return [
+ this.fontStyle,
+ this.fontWeight,
+ this.fontSize + 'px',
+ (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily)
+ ].join(' ');
+ },
+
+ /**
+ * @private
+ * @method _initDummyElement
+ */
+ _initDummyElementForCufon: function() {
+ var el = fabric.document.createElement('pre'),
+ container = fabric.document.createElement('div');
+
+ // Cufon doesn't play nice with textDecoration=underline if element doesn't have a parent
+ container.appendChild(el);
+
+ if (typeof G_vmlCanvasManager == 'undefined') {
+ el.innerHTML = this.text;
+ }
+ else {
+ // IE 7 & 8 drop newlines and white space on text nodes
+ // see: http://web.student.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
+ // see: http://www.w3schools.com/dom/dom_mozilla_vs_ie.asp
+ el.innerText = this.text.replace(/\r?\n/gi, '\r');
+ }
+
+ el.style.fontSize = this.fontSize + 'px';
+ el.style.letterSpacing = 'normal';
+
+ return el;
+ },
+
+ /**
+ * Renders text instance on a specified context
+ * @method render
+ * @param ctx {CanvasRenderingContext2D} context to render on
+ */
+ render: function(ctx, noTransform) {
+ ctx.save();
+ this._render(ctx);
+ if (!noTransform && this.active) {
+ this.drawBorders(ctx);
+ this.hideCorners || this.drawCorners(ctx);
+ }
+ ctx.restore();
+ },
+
+ /**
+ * Returns object representation of an instance
+ * @method toObject
+ * @return {Object} Object representation of text object
+ */
+ toObject: function() {
+ return extend(this.callSuper('toObject'), {
+ text: this.text,
+ fontSize: this.fontSize,
+ fontWeight: this.fontWeight,
+ fontFamily: this.fontFamily,
+ fontStyle: this.fontStyle,
+ lineHeight: this.lineHeight,
+ textDecoration: this.textDecoration,
+ textShadow: this.textShadow,
+ textAlign: this.textAlign,
+ path: this.path,
+ strokeStyle: this.strokeStyle,
+ strokeWidth: this.strokeWidth,
+ backgroundColor: this.backgroundColor,
+ useNative: this.useNative
+ });
+ },
+
+ /**
+ * Returns svg representation of an instance
+ * @method toSVG
+ * @return {string} svg representation of an instance
+ */
+ toSVG: function() {
+
+ var textLines = this.text.split(/\r?\n/),
+ lineTopOffset = this.useNative
+ ? this.fontSize * this.lineHeight
+ : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)),
+
+ textLeftOffset = -(this.width/2),
+ textTopOffset = this.useNative
+ ? this.fontSize - 1
+ : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight,
+
+ textAndBg = this._getSVGTextAndBg(lineTopOffset, textLeftOffset, textLines),
+ shadowSpans = this._getSVGShadows(lineTopOffset, textLines);
+
+ // move top offset by an ascent
+ textTopOffset += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0);
+
+ return [
+ '<g transform="', this.getSvgTransform(), '">',
+ textAndBg.textBgRects.join(''),
+ '<text ',
+ (this.fontFamily ? 'font-family="\'' + this.fontFamily + '\'" ': ''),
+ (this.fontSize ? 'font-size="' + this.fontSize + '" ': ''),
+ (this.fontStyle ? 'font-style="' + this.fontStyle + '" ': ''),
+ (this.fontWeight ? 'font-weight="' + this.fontWeight + '" ': ''),
+ (this.textDecoration ? 'text-decoration="' + this.textDecoration + '" ': ''),
+ 'style="', this.getSvgStyles(), '" ',
+ /* svg starts from left/bottom corner so we normalize height */
+ 'transform="translate(', toFixed(textLeftOffset, 2), ' ', toFixed(textTopOffset, 2), ')">',
+ shadowSpans.join(''),
+ textAndBg.textSpans.join(''),
+ '</text>',
+ '</g>'
+ ].join('');
+ },
+
+ _getSVGShadows: function(lineTopOffset, textLines) {
+ var shadowSpans = [], j, i, jlen, ilen, lineTopOffsetMultiplier = 1;
+
+ if (!this._shadows || !this._boundaries) {
+ return shadowSpans;
+ }
+
+ for (j = 0, jlen = this._shadows.length; j < jlen; j++) {
+ for (i = 0, ilen = textLines.length; i < ilen; i++) {
+ if (textLines[i] !== '') {
+ var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0;
+ shadowSpans.push(
+ '<tspan x="',
+ toFixed((lineLeftOffset + lineTopOffsetMultiplier) + this._shadowOffsets[j][0], 2),
+ ((i === 0 || this.useNative) ? '" y' : '" dy'), '="',
+ toFixed(this.useNative
+ ? ((lineTopOffset * i) - this.height / 2 + this._shadowOffsets[j][1])
+ : (lineTopOffset + (i === 0 ? this._shadowOffsets[j][1] : 0)), 2),
+ '" ',
+ this._getFillAttributes(this._shadows[j].color), '>',
+ fabric.util.string.escapeXml(textLines[i]),
+ '</tspan>');
+ lineTopOffsetMultiplier = 1;
+ } else {
+ // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
+ // prevents empty tspans
+ lineTopOffsetMultiplier++;
+ }
+ }
+ }
+ return shadowSpans;
+ },
+
+ _getSVGTextAndBg: function(lineTopOffset, textLeftOffset, textLines) {
+ var textSpans = [ ], textBgRects = [ ], i, lineLeftOffset, len, lineTopOffsetMultiplier = 1;
+
+ // text and background
+ for (i = 0, len = textLines.length; i < len; i++) {
+ if (textLines[i] !== '') {
+ lineLeftOffset = (this._boundaries && this._boundaries[i]) ? toFixed(this._boundaries[i].left, 2) : 0;
+ textSpans.push(
+ '<tspan x="',
+ lineLeftOffset, '" ',
+ (i === 0 || this.useNative ? 'y' : 'dy'), '="',
+ toFixed(this.useNative ? ((lineTopOffset * i) - this.height / 2) : (lineTopOffset * lineTopOffsetMultiplier), 2) , '" ',
+ // doing this on <tspan> elements since setting opacity on containing <text> one doesn't work in Illustrator
+ this._getFillAttributes(this.fill), '>',
+ fabric.util.string.escapeXml(textLines[i]),
+ '</tspan>'
+ );
+ lineTopOffsetMultiplier = 1;
+ } else {
+ // in some environments (e.g. IE 7 & 8) empty tspans are completely ignored, using a lineTopOffsetMultiplier
+ // prevents empty tspans
+ lineTopOffsetMultiplier++;
+ }
+
+ if (!this.backgroundColor || !this._boundaries) continue;
+
+ textBgRects.push(
+ '<rect ',
+ this._getFillAttributes(this.backgroundColor),
+ ' x="',
+ toFixed(textLeftOffset + this._boundaries[i].left, 2),
+ '" y="',
+ /* an offset that seems to straighten things out */
+ toFixed((lineTopOffset * i) - this.height / 2, 2),
+ '" width="',
+ toFixed(this._boundaries[i].width, 2),
+ '" height="',
+ toFixed(this._boundaries[i].height, 2),
+ '"></rect>');
+ }
+ return {
+ textSpans: textSpans,
+ textBgRects: textBgRects
+ };
+ },
+
+ // Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values
+ // we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1
+ _getFillAttributes: function(value) {
+ var fillColor = value ? new fabric.Color(value) : '';
+ if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) {
+ return 'fill="' + value + '"';
+ }
+ return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"';
+ },
+
+ /**
+ * Sets "color" of an instance (alias of `set('fill', &hellip;)`)
+ * @method setColor
+ * @param {String} value
+ * @return {fabric.Text} thisArg
+ * @chainable
+ */
+ setColor: function(value) {
+ this.set('fill', value);
+ return this;
+ },
+
+ /**
+ * Sets fontSize of an instance and updates its coordinates
+ * @method setFontsize
+ * @param {Number} value
+ * @return {fabric.Text} thisArg
+ * @chainable
+ */
+ setFontsize: function(value) {
+ this.set('fontSize', value);
+ this._initDimensions();
+ this.setCoords();
+ return this;
+ },
+
+ /**
+ * Returns actual text value of an instance
+ * @method getText
+ * @return {String}
+ */
+ getText: function() {
+ return this.text;
+ },
+
+ /**
+ * Sets text of an instance, and updates its coordinates
+ * @method setText
+ * @param {String} value
+ * @return {fabric.Text} thisArg
+ * @chainable
+ */
+ setText: function(value) {
+ this.set('text', value);
+ this._initDimensions();
+ this.setCoords();
+ return this;
+ },
+
+ /**
+ * Sets specified property to a specified value
+ * @method set
+ * @param {String} name
+ * @param {Any} value
+ * @return {fabric.Text} thisArg
+ * @chainable
+ */
+ _set: function(name, value) {
+ if (name === 'fontFamily' && this.path) {
+ this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3');
+ }
+ this.callSuper('_set', name, value);
+ }
+ });
+
+ /**
+ * List of attribute names to account for when parsing SVG element (used by `fabric.Text.fromElement`)
+ * @static
+ */
+ fabric.Text.ATTRIBUTE_NAMES =
+ ('x y fill fill-opacity opacity stroke stroke-width transform ' +
+ 'font-family font-style font-weight font-size text-decoration').split(' ');
+
+ /**
+ * Returns fabric.Text instance from an object representation
+ * @static
+ * @method fromObject
+ * @param {Object} object to create an instance from
+ * @return {fabric.Text} an instance
+ */
+ fabric.Text.fromObject = function(object) {
+ return new fabric.Text(object.text, clone(object));
+ };
+
+ /**
+ * Returns fabric.Text instance from an SVG element (<b>not yet implemented</b>)
+ * @static
+ * @method fabric.Text.fromElement
+ * @param element
+ * @param options
+ * @return {fabric.Text} an instance
+ */
+ fabric.Text.fromElement = function(element, options) {
+ if (!element) {
+ return null;
+ }
+
+ var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES);
+ var options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes);
+ var text = new fabric.Text(element.textContent, options);
+
+ return text;
+ };
+
+})(typeof exports != 'undefined' ? exports : this);
+(function() {
+
+ if (typeof document != 'undefined' && typeof window != 'undefined') {
+ return;
+ }
+
+ var DOMParser = new require('xmldom').DOMParser,
+ URL = require('url'),
+ HTTP = require('http'),
+
+ Canvas = require('canvas'),
+ Image = require('canvas').Image;
+
+ function request(url, encoding, callback) {
+ var oURL = URL.parse(url),
+ client = HTTP.createClient(oURL.port, oURL.hostname),
+ request = client.request('GET', oURL.pathname, { 'host': oURL.hostname });
+
+ client.addListener('error', function(err) {
+ if (err.errno === process.ECONNREFUSED) {
+ fabric.log('ECONNREFUSED: connection refused to ' + client.host + ':' + client.port);
+ }
+ else {
+ fabric.log(err.message);
+ }
+ });
+
+ request.end();
+ request.on('response', function (response) {
+ var body = "";
+ if (encoding) {
+ response.setEncoding(encoding);
+ }
+ response.on('end', function () {
+ callback(body);
+ });
+ response.on('data', function (chunk) {
+ if (response.statusCode == 200) {
+ body += chunk;
+ }
+ });
+ });
+ }
+
+ fabric.util.loadImage = function(url, callback) {
+ request(url, 'binary', function(body) {
+ var img = new Image();
+ img.src = new Buffer(body, 'binary');
+ // preserving original url, which seems to be lost in node-canvas
+ img._src = url;
+ callback(img);
+ });
+ };
+
+ fabric.loadSVGFromURL = function(url, callback) {
+ url = url.replace(/^\n\s*/, '').replace(/\?.*$/, '').trim();
+ request(url, '', function(body) {
+ fabric.loadSVGFromString(body, callback);
+ });
+ };
+
+ fabric.loadSVGFromString = function(string, callback) {
+ var doc = new DOMParser().parseFromString(string);
+ fabric.parseSVGDocument(doc.documentElement, function(results, options) {
+ callback(results, options);
+ });
+ };
+
+ fabric.util.getScript = function(url, callback) {
+ request(url, '', function(body) {
+ eval(body);
+ callback && callback();
+ });
+ };
+
+ fabric.Image.fromObject = function(object, callback) {
+ fabric.util.loadImage(object.src, function(img) {
+ var oImg = new fabric.Image(img);
+
+ oImg._initConfig(object);
+ oImg._initFilters(object);
+ callback(oImg);
+ });
+ };
+
+ /**
+ * Only available when running fabric on node.js
+ * @method createCanvasForNode
+ * @param width Canvas width
+ * @param height Canvas height
+ * @return {Object} wrapped canvas instance
+ */
+ fabric.createCanvasForNode = function(width, height) {
+
+ var canvasEl = fabric.document.createElement('canvas'),
+ nodeCanvas = new Canvas(width || 600, height || 600);
+
+ // jsdom doesn't create style on canvas element, so here be temp. workaround
+ canvasEl.style = { };
+
+ canvasEl.width = nodeCanvas.width;
+ canvasEl.height = nodeCanvas.height;
+
+ var canvas = fabric.Canvas || fabric.StaticCanvas;
+ var fabricCanvas = new canvas(canvasEl);
+ fabricCanvas.contextContainer = nodeCanvas.getContext('2d');
+ fabricCanvas.nodeCanvas = nodeCanvas;
+
+ return fabricCanvas;
+ };
+
+ fabric.StaticCanvas.prototype.createPNGStream = function() {
+ return this.nodeCanvas.createPNGStream();
+ };
+ if (fabric.Canvas) {
+ fabric.Canvas.prototype.createPNGStream
+ }
+
+ var origSetWidth = fabric.StaticCanvas.prototype.setWidth;
+ fabric.StaticCanvas.prototype.setWidth = function(width) {
+ origSetWidth.call(this);
+ this.nodeCanvas.width = width;
+ return this;
+ };
+ if (fabric.Canvas) {
+ fabric.Canvas.prototype.setWidth = fabric.StaticCanvas.prototype.setWidth;
+ }
+
+ var origSetHeight = fabric.StaticCanvas.prototype.setHeight;
+ fabric.StaticCanvas.prototype.setHeight = function(height) {
+ origSetHeight.call(this);
+ this.nodeCanvas.height = height;
+ return this;
+ };
+ if (fabric.Canvas) {
+ fabric.Canvas.prototype.setHeight = fabric.StaticCanvas.prototype.setHeight;
+ }
+
+})();
diff --git a/old/js/jquery.miniColors.min.js b/old/js/jquery.miniColors.min.js
new file mode 100644
index 0000000..1d33464
--- /dev/null
+++ b/old/js/jquery.miniColors.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery miniColors: A small color selector
+ *
+ * Copyright 2012 Cory LaViska for A Beautiful Site, LLC. (http://www.abeautifulsite.net/)
+ *
+ * Dual licensed under the MIT or GPL Version 2 licenses
+ *
+*/
+if(jQuery)(function($){$.extend($.fn,{miniColors:function(o,data){var create=function(input,o,data){var color=expandHex(input.val())||'ffffff',hsb=hex2hsb(color),rgb=hsb2rgb(hsb),alpha=parseFloat(input.attr('data-opacity')).toFixed(2);if(alpha>1)alpha=1;if(alpha<0)alpha=0;var trigger=$('<a class="miniColors-trigger" style="background-color: #'+color+'" href="#"></a>');trigger.insertAfter(input);trigger.wrap('<span class="miniColors-triggerWrap"></span>');if(o.opacity){trigger.css('backgroundColor','rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', '+alpha+')')}input.addClass('miniColors').data('original-maxlength',input.attr('maxlength')||null).data('original-autocomplete',input.attr('autocomplete')||null).data('letterCase',o.letterCase==='uppercase'?'uppercase':'lowercase').data('opacity',o.opacity?true:false).data('alpha',alpha).data('trigger',trigger).data('hsb',hsb).data('change',o.change?o.change:null).data('close',o.close?o.close:null).data('open',o.open?o.open:null).attr('maxlength',7).attr('autocomplete','off').val('#'+convertCase(color,o.letterCase));if(o.readonly||input.prop('readonly'))input.prop('readonly',true);if(o.disabled||input.prop('disabled'))disable(input);trigger.on('click.miniColors',function(event){event.preventDefault();if(input.val()==='')input.val('#');show(input)});input.on('focus.miniColors',function(event){if(input.val()==='')input.val('#');show(input)});input.on('blur.miniColors',function(event){var hex=expandHex(hsb2hex(input.data('hsb')));input.val(hex?'#'+convertCase(hex,input.data('letterCase')):'')});input.on('keydown.miniColors',function(event){if(event.keyCode===9)hide(input)});input.on('keyup.miniColors',function(event){setColorFromInput(input)});input.on('paste.miniColors',function(event){setTimeout(function(){setColorFromInput(input)},5)})};var destroy=function(input){hide();input=$(input);input.data('trigger').parent().remove();input.attr('autocomplete',input.data('original-autocomplete')).attr('maxlength',input.data('original-maxlength')).removeData().removeClass('miniColors').off('.miniColors');$(document).off('.miniColors')};var enable=function(input){input.prop('disabled',false).data('trigger').parent().removeClass('disabled')};var disable=function(input){hide(input);input.prop('disabled',true).data('trigger').parent().addClass('disabled')};var show=function(input){if(input.prop('disabled'))return false;hide();var selector=$('<div class="miniColors-selector"></div>');selector.append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>').append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"><div class="miniColors-colorPicker-inner"></div></div>').css('display','none').addClass(input.attr('class'));if(input.data('opacity')){selector.addClass('opacity').prepend('<div class="miniColors-opacity"><div class="miniColors-opacityPicker"></div></div>')}var hsb=input.data('hsb');selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100})).end().find('.miniColors-opacity').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:hsb.s,b:hsb.b})).end();var colorPosition=input.data('colorPosition');if(!colorPosition)colorPosition=getColorPositionFromHSB(hsb);selector.find('.miniColors-colorPicker').css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=input.data('huePosition');if(!huePosition)huePosition=getHuePositionFromHSB(hsb);selector.find('.miniColors-huePicker').css('top',huePosition+'px');var opacityPosition=input.data('opacityPosition');if(!opacityPosition)opacityPosition=getOpacityPositionFromAlpha(input.attr('data-opacity'));selector.find('.miniColors-opacityPicker').css('top',opacityPosition+'px');input.data('selector',selector).data('huePicker',selector.find('.miniColors-huePicker')).data('opacityPicker',selector.find('.miniColors-opacityPicker')).data('colorPicker',selector.find('.miniColors-colorPicker')).data('mousebutton',0);$('BODY').append(selector);var trigger=input.data('trigger'),hidden=!input.is(':visible'),top=hidden?trigger.offset().top+trigger.outerHeight():input.offset().top+input.outerHeight(),left=hidden?trigger.offset().left:input.offset().left,selectorWidth=selector.outerWidth(),selectorHeight=selector.outerHeight(),triggerWidth=trigger.outerWidth(),triggerHeight=trigger.outerHeight(),windowHeight=$(window).height(),windowWidth=$(window).width(),scrollTop=$(window).scrollTop(),scrollLeft=$(window).scrollLeft();if((top+selectorHeight)>windowHeight+scrollTop)top=top-selectorHeight-triggerHeight;if((left+selectorWidth)>windowWidth+scrollLeft)left=left-selectorWidth+triggerWidth;selector.css({top:top,left:left}).fadeIn(100);selector.on('selectstart',function(){return false});if(!$.browser.msie||($.browser.msie&&$.browser.version>=9)){$(window).on('resize.miniColors',function(event){hide(input)})}$(document).on('mousedown.miniColors touchstart.miniColors',function(event){input.data('mousebutton',1);var testSubject=$(event.target).parents().andSelf();if(testSubject.hasClass('miniColors-colors')){event.preventDefault();input.data('moving','colors');moveColor(input,event)}if(testSubject.hasClass('miniColors-hues')){event.preventDefault();input.data('moving','hues');moveHue(input,event)}if(testSubject.hasClass('miniColors-opacity')){event.preventDefault();input.data('moving','opacity');moveOpacity(input,event)}if(testSubject.hasClass('miniColors-selector')){event.preventDefault();return}if(testSubject.hasClass('miniColors'))return;hide(input)}).on('mouseup.miniColors touchend.miniColors',function(event){event.preventDefault();input.data('mousebutton',0).removeData('moving')}).on('mousemove.miniColors touchmove.miniColors',function(event){event.preventDefault();if(input.data('mousebutton')===1){if(input.data('moving')==='colors')moveColor(input,event);if(input.data('moving')==='hues')moveHue(input,event);if(input.data('moving')==='opacity')moveOpacity(input,event)}});if(input.data('open')){input.data('open').call(input.get(0),'#'+hsb2hex(hsb),$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}))}};var hide=function(input){if(!input)input=$('.miniColors');input.each(function(){var selector=$(this).data('selector');$(this).removeData('selector');$(selector).fadeOut(100,function(){if(input.data('close')){var hsb=input.data('hsb'),hex=hsb2hex(hsb);input.data('close').call(input.get(0),'#'+hex,$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}))}$(this).remove()})});$(document).off('.miniColors')};var moveColor=function(input,event){var colorPicker=input.data('colorPicker');colorPicker.hide();var position={x:event.pageX,y:event.pageY};if(event.originalEvent.changedTouches){position.x=event.originalEvent.changedTouches[0].pageX;position.y=event.originalEvent.changedTouches[0].pageY}position.x=position.x-input.data('selector').find('.miniColors-colors').offset().left-6;position.y=position.y-input.data('selector').find('.miniColors-colors').offset().top-6;if(position.x<=-5)position.x=-5;if(position.x>=144)position.x=144;if(position.y<=-5)position.y=-5;if(position.y>=144)position.y=144;input.data('colorPosition',position);colorPicker.css('left',position.x).css('top',position.y).show();var s=Math.round((position.x+5)*0.67);if(s<0)s=0;if(s>100)s=100;var b=100-Math.round((position.y+5)*0.67);if(b<0)b=0;if(b>100)b=100;var hsb=input.data('hsb');hsb.s=s;hsb.b=b;setColor(input,hsb,true)};var moveHue=function(input,event){var huePicker=input.data('huePicker');huePicker.hide();var position=event.pageY;if(event.originalEvent.changedTouches){position=event.originalEvent.changedTouches[0].pageY}position=position-input.data('selector').find('.miniColors-colors').offset().top-1;if(position<=-1)position=-1;if(position>=149)position=149;input.data('huePosition',position);huePicker.css('top',position).show();var h=Math.round((150-position-1)*2.4);if(h<0)h=0;if(h>360)h=360;var hsb=input.data('hsb');hsb.h=h;setColor(input,hsb,true)};var moveOpacity=function(input,event){var opacityPicker=input.data('opacityPicker');opacityPicker.hide();var position=event.pageY;if(event.originalEvent.changedTouches){position=event.originalEvent.changedTouches[0].pageY}position=position-input.data('selector').find('.miniColors-colors').offset().top-1;if(position<=-1)position=-1;if(position>=149)position=149;input.data('opacityPosition',position);opacityPicker.css('top',position).show();var alpha=parseFloat((150-position-1)/150).toFixed(2);if(alpha<0)alpha=0;if(alpha>1)alpha=1;input.data('alpha',alpha).attr('data-opacity',alpha);setColor(input,input.data('hsb'),true)};var setColor=function(input,hsb,updateInput){input.data('hsb',hsb);var hex=hsb2hex(hsb),selector=$(input.data('selector'));if(updateInput)input.val('#'+convertCase(hex,input.data('letterCase')));selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100})).end().find('.miniColors-opacity').css('backgroundColor','#'+hex).end();var rgb=hsb2rgb(hsb);input.data('trigger').css('backgroundColor','#'+hex);if(input.data('opacity')){input.data('trigger').css('backgroundColor','rgba('+rgb.r+', '+rgb.g+', '+rgb.b+', '+input.attr('data-opacity')+')')}if(input.data('change')){if((hex+','+input.attr('data-opacity'))===input.data('lastChange'))return;input.data('change').call(input.get(0),'#'+hex,$.extend(hsb2rgb(hsb),{a:parseFloat(input.attr('data-opacity'))}));input.data('lastChange',hex+','+input.attr('data-opacity'))}};var setColorFromInput=function(input){input.val('#'+cleanHex(input.val()));var hex=expandHex(input.val());if(!hex)return false;var hsb=hex2hsb(hex);var colorPosition=getColorPositionFromHSB(hsb);var colorPicker=$(input.data('colorPicker'));colorPicker.css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');input.data('colorPosition',colorPosition);var huePosition=getHuePositionFromHSB(hsb);var huePicker=$(input.data('huePicker'));huePicker.css('top',huePosition+'px');input.data('huePosition',huePosition);var opacityPosition=getOpacityPositionFromAlpha(input.attr('data-opacity'));var opacityPicker=$(input.data('opacityPicker'));opacityPicker.css('top',opacityPosition+'px');input.data('opacityPosition',opacityPosition);setColor(input,hsb);return true};var convertCase=function(string,letterCase){if(letterCase==='uppercase'){return string.toUpperCase()}else{return string.toLowerCase()}};var getColorPositionFromHSB=function(hsb){var x=Math.ceil(hsb.s/0.67);if(x<0)x=0;if(x>150)x=150;var y=150-Math.ceil(hsb.b/0.67);if(y<0)y=0;if(y>150)y=150;return{x:x-5,y:y-5}};var getHuePositionFromHSB=function(hsb){var y=150-(hsb.h/2.4);if(y<0)h=0;if(y>150)h=150;return y};var getOpacityPositionFromAlpha=function(alpha){var y=150*alpha;if(y<0)y=0;if(y>150)y=150;return 150-y};var cleanHex=function(hex){return hex.replace(/[^A-F0-9]/ig,'')};var expandHex=function(hex){hex=cleanHex(hex);if(!hex)return null;if(hex.length===3)hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];return hex.length===6?hex:null};var hsb2rgb=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s===0){rgb.r=rgb.g=rgb.b=v}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h===360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3}else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3}else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3}else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3}else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3}else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3}else{rgb.r=0;rgb.g=0;rgb.b=0}}return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)}};var rgb2hex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length===1)hex[nr]='0'+val});return hex.join('')};var hex2rgb=function(hex){hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)}};var rgb2hsb=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!==0?255*delta/max:0;if(hsb.s!==0){if(rgb.r===max){hsb.h=(rgb.g-rgb.b)/delta}else if(rgb.g===max){hsb.h=2+(rgb.b-rgb.r)/delta}else{hsb.h=4+(rgb.r-rgb.g)/delta}}else{hsb.h=-1}hsb.h*=60;if(hsb.h<0){hsb.h+=360}hsb.s*=100/255;hsb.b*=100/255;return hsb};var hex2hsb=function(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb};var hsb2hex=function(hsb){return rgb2hex(hsb2rgb(hsb))};switch(o){case'readonly':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).prop('readonly',data)});return $(this);case'disabled':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;if(data){disable($(this))}else{enable($(this))}});return $(this);case'value':if(data===undefined){if(!$(this).hasClass('miniColors'))return;var input=$(this),hex=expandHex(input.val());return hex?'#'+convertCase(hex,input.data('letterCase')):null}$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).val(data);setColorFromInput($(this))});return $(this);case'opacity':if(data===undefined){if(!$(this).hasClass('miniColors'))return;if($(this).data('opacity')){return parseFloat($(this).attr('data-opacity'))}else{return null}}$(this).each(function(){if(!$(this).hasClass('miniColors'))return;if(data<0)data=0;if(data>1)data=1;$(this).attr('data-opacity',data).data('alpha',data);setColorFromInput($(this))});return $(this);case'destroy':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;destroy($(this))});return $(this);default:if(!o)o={};$(this).each(function(){if($(this)[0].tagName.toLowerCase()!=='input')return;if($(this).data('trigger'))return;create($(this),o,data)});return $(this)}}})})(jQuery); \ No newline at end of file
diff --git a/old/js/tshirtEditor.js b/old/js/tshirtEditor.js
new file mode 100644
index 0000000..1b3f8c0
--- /dev/null
+++ b/old/js/tshirtEditor.js
@@ -0,0 +1,355 @@
+var canvas;
+var tshirts = new Array(); //prototype: [{style:'x',color:'white',front:'a',back:'b',price:{tshirt:'12.95',frontPrint:'4.99',backPrint:'4.99',total:'22.47'}}]
+var a;
+var b;
+var line1;
+var line2;
+var line3;
+var line4;
+ $(document).ready(function() {
+ //setup front side canvas
+ canvas = new fabric.Canvas('tcanvas', {
+ hoverCursor: 'pointer',
+ selection: true,
+ selectionBorderColor:'blue'
+ });
+ canvas.on({
+ 'object:moving': function(e) {
+ e.target.opacity = 0.5;
+ },
+ 'object:modified': function(e) {
+ e.target.opacity = 1;
+ },
+ 'object:selected':onObjectSelected,
+ 'selection:cleared':onSelectedCleared
+ });
+ // piggyback on `canvas.findTarget`, to fire "object:over" and "object:out" events
+ canvas.findTarget = (function(originalFn) {
+ return function() {
+ var target = originalFn.apply(this, arguments);
+ if (target) {
+ if (this._hoveredTarget !== target) {
+ canvas.fire('object:over', { target: target });
+ if (this._hoveredTarget) {
+ canvas.fire('object:out', { target: this._hoveredTarget });
+ }
+ this._hoveredTarget = target;
+ }
+ }
+ else if (this._hoveredTarget) {
+ canvas.fire('object:out', { target: this._hoveredTarget });
+ this._hoveredTarget = null;
+ }
+ return target;
+ };
+ })(canvas.findTarget);
+
+ canvas.on('object:over', function(e) {
+ //e.target.setFill('red');
+ //canvas.renderAll();
+ });
+
+ canvas.on('object:out', function(e) {
+ //e.target.setFill('green');
+ //canvas.renderAll();
+ });
+
+ document.getElementById('add-text').onclick = function() {
+ var text = $("#text-string").val();
+ var textSample = new fabric.Text(text, {
+ left: fabric.util.getRandomInt(0, 200),
+ top: fabric.util.getRandomInt(0, 400),
+ fontFamily: 'helvetica',
+ angle: 0,
+ fill: '#000000',
+ scaleX: 0.5,
+ scaleY: 0.5,
+ fontWeight: '',
+ hasRotatingPoint:true
+ });
+ canvas.add(textSample);
+ canvas.item(canvas.item.length-1).hasRotatingPoint = true;
+ $("#texteditor").css('display', 'block');
+ $("#imageeditor").css('display', 'block');
+ };
+ $("#text-string").keyup(function(){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.text = this.value;
+ canvas.renderAll();
+ }
+ });
+ $(".img-polaroid").click(function(e){
+ var el = e.target;
+ /*temp code*/
+ var offset = 50;
+ var left = fabric.util.getRandomInt(0 + offset, 200 - offset);
+ var top = fabric.util.getRandomInt(0 + offset, 400 - offset);
+ var angle = fabric.util.getRandomInt(-20, 40);
+ var width = fabric.util.getRandomInt(30, 50);
+ var opacity = (function(min, max){ return Math.random() * (max - min) + min; })(0.5, 1);
+
+ fabric.Image.fromURL(el.src, function(image) {
+ image.set({
+ left: left,
+ top: top,
+ angle: 0,
+ padding: 10,
+ cornersize: 10,
+ hasRotatingPoint:true
+ });
+ //image.scale(getRandomNum(0.1, 0.25)).setCoords();
+ canvas.add(image);
+ });
+ });
+ document.getElementById('remove-selected').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ canvas.remove(activeObject);
+ $("#text-string").val("");
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ canvas.remove(object);
+ });
+ }
+ };
+ document.getElementById('bring-to-front').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ activeObject.bringToFront();
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ object.bringToFront();
+ });
+ }
+ };
+ document.getElementById('send-to-back').onclick = function() {
+ var activeObject = canvas.getActiveObject(),
+ activeGroup = canvas.getActiveGroup();
+ if (activeObject) {
+ activeObject.sendToBack();
+ }
+ else if (activeGroup) {
+ var objectsInGroup = activeGroup.getObjects();
+ canvas.discardActiveGroup();
+ objectsInGroup.forEach(function(object) {
+ object.sendToBack();
+ });
+ }
+ };
+ $("#text-bold").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontWeight = (activeObject.fontWeight == 'bold' ? '' : 'bold');
+ canvas.renderAll();
+ }
+ });
+ $("#text-italic").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontStyle = (activeObject.fontStyle == 'italic' ? '' : 'italic');
+ canvas.renderAll();
+ }
+ });
+ $("#text-strike").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textDecoration = (activeObject.textDecoration == 'line-through' ? '' : 'line-through');
+ canvas.renderAll();
+ }
+ });
+ $("#text-underline").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textDecoration = (activeObject.textDecoration == 'underline' ? '' : 'underline');
+ canvas.renderAll();
+ }
+ });
+ $("#text-left").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'left';
+ canvas.renderAll();
+ }
+ });
+ $("#text-center").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'center';
+ canvas.renderAll();
+ }
+ });
+ $("#text-right").click(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.textAlign = 'right';
+ canvas.renderAll();
+ }
+ });
+ $("#font-family").change(function() {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontFamily = this.value;
+ canvas.renderAll();
+ }
+ });
+ $('#text-bgcolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.backgroundColor = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+ $('#text-fontcolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fill = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+
+ $('#text-strokecolor').miniColors({
+ change: function(hex, rgb) {
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.strokeStyle = this.value;
+ canvas.renderAll();
+ }
+ },
+ open: function(hex, rgb) {
+ //
+ },
+ close: function(hex, rgb) {
+ //
+ }
+ });
+
+ //canvas.add(new fabric.fabric.Object({hasBorders:true,hasControls:false,hasRotatingPoint:false,selectable:false,type:'rect'}));
+ $("#drawingArea").hover(
+ function() {
+ canvas.add(line1);
+ canvas.add(line2);
+ canvas.add(line3);
+ canvas.add(line4);
+ canvas.renderAll();
+ },
+ function() {
+ canvas.remove(line1);
+ canvas.remove(line2);
+ canvas.remove(line3);
+ canvas.remove(line4);
+ canvas.renderAll();
+ }
+ );
+
+ $('.color-preview').click(function(){
+ var color = $(this).css("background-color");
+ document.getElementById("shirtDiv").style.backgroundColor = color;
+ });
+
+ $('#flip').click(
+ function() {
+ if ($(this).attr("data-original-title") == "Show Back View") {
+ $(this).attr('data-original-title', 'Show Front View');
+ $("#tshirtFacing").attr("src","img/crew_back.png");
+ a = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(b);
+ canvas.loadFromJSON(b);
+ }
+ catch(e)
+ {}
+
+ } else {
+ $(this).attr('data-original-title', 'Show Back View');
+ $("#tshirtFacing").attr("src","img/crew_front.png");
+ b = JSON.stringify(canvas);
+ canvas.clear();
+ try
+ {
+ var json = JSON.parse(a);
+ canvas.loadFromJSON(a);
+ }
+ catch(e)
+ {}
+ }
+ canvas.renderAll();
+ setTimeout(function() {
+ canvas.calcOffset();
+ },200);
+ });
+ $(".clearfix button,a").tooltip();
+ line1 = new fabric.Line([0,0,200,0], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line2 = new fabric.Line([199,0,200,399], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line3 = new fabric.Line([0,0,0,400], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ line4 = new fabric.Line([0,400,200,399], {"stroke":"#000000", "strokeWidth":1,hasBorders:false,hasControls:false,hasRotatingPoint:false,selectable:false});
+ });//doc ready
+
+
+ function getRandomNum(min, max) {
+ return Math.random() * (max - min) + min;
+ }
+
+ function onObjectSelected(e) {
+ var selectedObject = e.target;
+ $("#text-string").val("");
+ selectedObject.hasRotatingPoint = true
+ if (selectedObject && selectedObject.type === 'text') {
+ //display text editor
+ $("#texteditor").css('display', 'block');
+ $("#text-string").val(selectedObject.getText());
+ $('#text-fontcolor').miniColors('value',selectedObject.fill);
+ $('#text-strokecolor').miniColors('value',selectedObject.strokeStyle);
+ $("#imageeditor").css('display', 'block');
+ }
+ else if (selectedObject && selectedObject.type === 'image'){
+ //display image editor
+ $("#texteditor").css('display', 'none');
+ $("#imageeditor").css('display', 'block');
+ }
+ }
+ function onSelectedCleared(e){
+ $("#texteditor").css('display', 'none');
+ $("#text-string").val("");
+ $("#imageeditor").css('display', 'none');
+ }
+ function setFont(font){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'text') {
+ activeObject.fontFamily = font;
+ canvas.renderAll();
+ }
+ }
+ function removeWhite(){
+ var activeObject = canvas.getActiveObject();
+ if (activeObject && activeObject.type === 'image') {
+ activeObject.filters[2] = new fabric.Image.filters.RemoveWhite({hreshold: 100, distance: 10});//0-255, 0-255
+ activeObject.applyFilters(canvas.renderAll.bind(canvas));
+ }
+ } \ No newline at end of file