From 7395fcaa4ab9d0584262a42dc9873f2e47c204e0 Mon Sep 17 00:00:00 2001 From: Sean McGee Date: Thu, 21 Jun 2018 12:20:11 +0100 Subject: [PATCH 1/8] Adding pulse to github.io --- pulse/css/style.css | 117 ++++++++++ pulse/images/PointIconImages/0.png | Bin 0 -> 694 bytes pulse/images/PointIconImages/1.png | Bin 0 -> 894 bytes pulse/images/PointIconImages/2.png | Bin 0 -> 1072 bytes pulse/images/PointIconImages/3.png | Bin 0 -> 2460 bytes pulse/images/PointIconImages/4.png | Bin 0 -> 1324 bytes pulse/images/PointIconImages/5.png | Bin 0 -> 1430 bytes pulse/images/PointIconImages/6.png | Bin 0 -> 1379 bytes pulse/images/githubLogo.svg | 14 ++ pulse/index.html | 37 +++ pulse/js/main.js | 362 +++++++++++++++++++++++++++++ 11 files changed, 530 insertions(+) create mode 100644 pulse/css/style.css create mode 100644 pulse/images/PointIconImages/0.png create mode 100644 pulse/images/PointIconImages/1.png create mode 100644 pulse/images/PointIconImages/2.png create mode 100644 pulse/images/PointIconImages/3.png create mode 100644 pulse/images/PointIconImages/4.png create mode 100644 pulse/images/PointIconImages/5.png create mode 100644 pulse/images/PointIconImages/6.png create mode 100644 pulse/images/githubLogo.svg create mode 100644 pulse/index.html create mode 100644 pulse/js/main.js diff --git a/pulse/css/style.css b/pulse/css/style.css new file mode 100644 index 0000000..0159bd6 --- /dev/null +++ b/pulse/css/style.css @@ -0,0 +1,117 @@ +body{ + margin: 0px; +} +#viewDiv { + background: #232227; + padding: 0; + margin: 0; + height: 100%; + width: 100%; +} +#configFs { + position: absolute; + width: 22%; + padding: 20px; + margin: .5rem 0 1rem 0; + background-color: #221c38; + color: white; + bottom: 0px; + right: 0px; + z-index: 99999; + opacity: 0.2; + -webkit-transition: .5s; + transition: all .5s; +} +#feature-layer-name { + font-style: italic; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +#configFs p, a{ + color: white; + font-family: Arial; +} +#configFs:hover { + opacity: 1; + -webkit-transition: .2s; + transition: opacity .2s; +} +#fs-text { + padding:5px; + width: 100%; +} +#fs-url { + padding:5px; + width: 100%; + background: white; + border-radius: 5px; + border-width: 0px; + border-bottom-color: green; + border-bottom-width: 4px; +} +#play{ + padding:5px; + width: 15%; + background: white; + border-radius: 5px; + border-width: 0px; +} +#play:hover{ + color: #5ab35a +} +#selection { + padding:5px; + width: 35%; + background: white; + border-radius: 5px; + border-width: 0px; + border-bottom-color: green; + border-bottom-width: 4px; +} +#animation-time { + padding:5px; + width: 12%; + background: white; + border-radius: 5px; + border-width: 0px; + border-bottom-color: green; + border-bottom-width: 4px; +} +#footer{ + bottom: 35px; + width: 24px; + float: right; + opacity: 0.6; +} +#applicationDiv { + position: absolute; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} +#viewDiv { + flex: 1 1 auto; + order: 1; +} + + +@media only screen and (max-width: 1180px) { + #configFs { + width: calc(40% - 40px); + } +} + +@media only screen and (max-width: 750px) { + #configFs { + width: calc(100% - 40px); + opacity:1; + } + #play { + width:9%; + } +} + diff --git a/pulse/images/PointIconImages/0.png b/pulse/images/PointIconImages/0.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f0a89ddfb3dc5ce48ada4f89c9b9d9849fa2f GIT binary patch literal 694 zcmV;n0!jUeP)LWhQBbx0f#$q372juj={k^hQ-#MUcx&#fDn+lc-=)4$8_*nDPLE0 zF(i5U-k+Y5m1RjAt@GCaUQ6JyErG|j1RmQGcx+4Hv8}hy#j^1<1FQp->gDxlZ-Y^` zk0C1?Pdh-D@B}{v2R6WKc|F=RzGo9~QMmho>juC8SG~y-aDyA@o_JrpXW%i=YhWe5 z7I(upiE)MQ137RYo*Q@!bO9{ceHzHI+U8PR)XoL^06406UI8zFmx?b5>K7i>y^3or zTkun002~8{X6-ewVEZH>gpYa`hw8NzH)`hsJP_z};0!ns@V;EK67Xl>5qJQefEhvg z0*C4z#Wg-vb=|l_bJ(p^#$8-%H;U3g2Oh!G(mHM%60>)xPGn$*d}j= zvwM5M64!7aRPE(=1H&^2vx&gIxbe(MHY&|YxOepOAIizZzrVAXOsGaD$(^>)o4 zo40dnCDG=iwfw6FG#7YV!QOow(VtTSXS zG0Eyt8=6grRw+#~Z8AR|YrQxBe=ia{i?r?4?cHK+ds+SqYwfI(z++nik8KG&wk7b` cmcV1%8}{l0G2vFPLjV8(07*qoM6N<$f?<9rRR910 literal 0 HcmV?d00001 diff --git a/pulse/images/PointIconImages/1.png b/pulse/images/PointIconImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..0a11afacca30d51919d4da94552dbb8926a4e3c3 GIT binary patch literal 894 zcmV-^1A+XBP)Q7^!bL!_HjV$ns^ zgb;#_yI}sz&tQcaLY=zxlb$d(GydlB&wI~~@;pZst?||X+gc4?w$06aR* zYlQa#3Q32WK^W;afDV8I0Q=^c|A2xs!c#L=Wu$u`f=!(}1J{k%G;nPLeJIYzD>x(F zgq0AruN#%V(Y&N$WJ2vy76$;j0N#mrv5a~fHYllqo&vZ7@Jsv-zyww{DS)|;Tzn(W zzt+trEd$DEcg=!24mc%3S*YF2P9xLwJr)9dIuTi8z5Y+AQO&h!Je|C@Mhr#-{?W^3i*}Xdz9O=5Fm#4&h(&?;W4O z%4m*d)l7YOLZm`%H_T0#(jvJ&$L1#M0k+Ij>$+LJY#WVJDl@Ehc{~@=*0Lwh5UBBg z|3O0FtM}+u0AHPlWr45W>xt*VReT||Hc^#c!M&yZ7j5ldWi@!&R)d#qHF(+f8#Lgh UTFn9>j{pDw07*qoM6N<$f?`~Y`Tzg` literal 0 HcmV?d00001 diff --git a/pulse/images/PointIconImages/2.png b/pulse/images/PointIconImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..957436c9cb1517984db0dfda496b869e412b4ebe GIT binary patch literal 1072 zcmV-01kd}4P)UPTW8c#y|Va9b5=VQKEE|6ubi+ZOY^UcradqmX1e2A`(RkS|kcW$o;dO zg8AU{c-QtF1mcL5M!W3Yt$#C~nVnsdvMkZZmcBN?s_hN@rCV>ov*%bOIXO)@GzqoR z1m(g0lQTlHYF05=zA**%eHrLEE>=}yY4GIaG!;<7Cjrq`2Bgrx%Y*&muYo5grvnF@ ziX?9A!3%*c0)Lm~!G8Yi-~v4mz=4QYLS4nf<{~=R8NL&^K`U5hu$dcEnGiT37dBqF z@q=Pr;9J3+r3uJ>&1C52HE`(;3ONHXH_>^%g;vWqf?FWcim5>=@W`NtGN-OXZ~_r_ zW-@gO%;ozTp1z;BTW>ekNz=dBY2euAfgqHT7S5hLX z*(bH%F9l9`%_MXaT6}Eq?*x7n0iQ;c(rm+?&y4zbu*fnu>EhoT2E7U3y~!4UZR>T+ z&s9-%FDgVgO8_KN2UXg#!Kv<|em*uBy3G((tIkmMdv%|ge4g2SQ?b?xuF&c(8R}M7 z_z*ZV4NwhM`aIW-?Y^DC^@o0Y_wW@3HV1?8(cbRPmrozI0O0uQ`sVQb;_^1nJ+x+^ z4E54MpI-~mF7>$gcdbt}V21#PM|-^X=^W58S%}{W)`M`6{!qwVi+x zH}(o>p8^=r(%Nj!7iZdsfE(d^dvt39FWp#6)ZFz2fc*IC`o=F}bH0StUe^viyVkn+ zcnFxn!iXtt?eP5K5K3f9T6?hm$N;3bc@7IipT~V&GPQZF z%k{0Ou{whT;ggGp);z3vdL9A43n-xQ{zwfS=ainb05)io{MrZ5H=#lH%Tax)CYd4DDqt z;c58^ssXFdjJ76loC-4G4`1lx^@M}(*M*RS@7EPv0KPw$o&w*$Yi6fFzb^KJ qi}dMVDX-oB+oRh*+up!my8Qt)S7h^xyRQ5I0000Bv+lC<#H*8;x^R1$4izR^iKgLdGR za>_#>uY96gD4!` zR2v4Fc81^&M$HD4t~9}f5n(<^PT0Zgsa8TD~fE`ER1g`#Ga!Z5lLa#OV8o)rW{E>Pr>kN~nuAV@TmuK0yb5fXRx_M5!h*RelA}x0XkO&K}T#2J|DpinR(ukyD9ivs77Lw+8HEBW~%Km-Ft7#d9?XP13) z2s(!B4A=HM>~>d3_DDWBpyXy}ppGm%OIcVJr}b2XuG7gm0*cB91mMWDVK6m%NfJ51 z$npZ~@!*u%MB*&Nkhq1nP&g|v1^^ff5P7D1ex@vxXaeY-FT(jomL++frEr?I0KCx1 zhwTuse^dgw8Td0osJL(Ew`p?99 zqnYxUVCe+L3~-Z~p>azgP2mE~vVbx3qQztE4m?Ucs#1nCS~$kkt>^{=Y`3t8kuJE0Yt8840Y9S04+y|4 zh2Y^*8v7ZIAU$gwndxrtGly!L1D}$E=6>x;7~UZ2hFD_^1Gn4HxDscy;rm~hn3A&$ zEl%{l)^hOe_}Kc&k0?cAV+$efxa< zqE}j1eH`l@uzp}(kJizete!i`@)DL1E-x!9{LON z;kvpLYm(dQidQdk&1-3IBG9CBlfM!gDop^@Ev(qfX9Ix)OTSe9gDBopDJ1JuG!}pRLzGKv>$#=cby5(X*ZGrWa zi;ml69H)Qh#g{jE51#xQzmjz6^thVV<*S@iFTClvr}V;X_dQj`1u36zot^rqldc*& z^@yo?v6!&j{MM*~jxUd`mb_&p>6Pur!)XUME<5&4%gPF*>5U}3=C_oA6oY)s^!{Pj zx$oxZyg$A@SuA?_oI6DRlPrDj~J9J3966 zF{dV1K3mhbL3d#3`LS2G4f8(s(t`7jT3^TEwBeU~ve~m&rmjuqwErhZ*2K(>88a&W E0xT_T!2kdN literal 0 HcmV?d00001 diff --git a/pulse/images/PointIconImages/4.png b/pulse/images/PointIconImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..df9dc789526e17de46758a957a295a12f11889ba GIT binary patch literal 1324 zcmV+{1=IS8P)?xan#AMfMGSyHD+K`sjnc6sDdzZuRBXO>DiXFO&*%x9zM_sev8_n3bt zJrw*fpCtekNbh3+$iX-R$d~E#K>%xk8>~aul<2@#j@MG+-rxm#B!D`Ol<}4;a;M|_ z!ViKE0FO*+@D8f-AO(8C1)5Oox60Uig^$1qdmHo$e>VhQ1vhAyae-A)1r%PP6;x%3 z?vv<+55X(Y3ONR_En`;%295x9S;h+)QwHHX;62b|lWhPy6$NN@g44SitGZIl%B_gr@J`#(tzispULqYT^DNkJ>k_=JWjiB6LJs0egU79 z23^i!xRhr^j%$x>}yrZx-nZj*nia zm(P#!{T={J@pb+-fB1Zk=>@tWl9A>b-KeB#L?`QXANLH<^Am;F@VEzH@_YLA$5(jq zOpJk_a5(ub{qn=lI68Qf-+w|kuIoLc=)@;+Eo2KAidg(+>Sv}br1K56@UOuluvz2Sq6;z_UUZYP7KB~w)(eW@DDYHeN z?rY_mTJhn3qmL;B--s50d(!T&;;;mp;_H0qkCkh@Z1a2axR0p??%M`6NKg8g02Xia zhtEUc<(jB)VCfbq<69XWa6PFLz2wp?uNj|Xdhre)zto`L;p3MQ%R6&XFW+lUtX@REMhIC;vgT#}w<0qUwSH7ED`U%5 z*sEW=wV9TBd8U9vY3;l~UkSWhJ}TRkvE$=R*wxy!p!#!^PgB96Lc*fZBkO=bUS=|=as5>eN2>o=CaOsBVMakaWvBqOZ$J!<%xeHz3 z$u~V6A!yg*7ymucL%M@fNAb zN|6zYOdwt_upf6^FlYCF|9fV*8$}jmq@$U=d(HiwbI+WQSr;J$EIYQtUID-a&YT*- znZreKvNS|d7hGUHfvi2~AnOIGt3)mE2)#mJD>CzSQ$V2spcK$j<^Z6q1D^%I2iOx2 zUZKl`ETupaoS+r9L32Z{=^HR`wr~kxO7m2%RTNCYyWj$?z)gbYbwlpw!IqtpOk}MJ zB;gmp9q5Kg6F@7@DhBUa+@L0oj71546E1uOJOj;eEdcE}v#|9I-oV+?2Ffxbc#|xA zv-$#(&oM>x z$qlXTd$oQEU}{z`LI~<23~d%}M_7|i6#LBZW_1Oh2=q;q>nJxQb|1UX%xx};lVGmG zaat8hB&n`?YOk-ozSx7UH?VZ->32iA6WzXu{TJ zS6KtFPVj7XhB9!ACbzir=bSrfJfFmSrq8V(Cr)) z`#Z1ja&s5ow*a7zkK63pfBMimmbm1FBVf05$+T1+W=q zD}LSp`T39d;pzM0-Sb^MYr`3RoQ+bbjk1GIQqI?GM4}dEGe*?ITauu%+01PpY z^S4pSiI$;Wbn%iAXU)|fx}6*}0PNyf`+f25c@JHV`(EbF6>T%&^3zK~UU*!PgJORt z2hH#Y#r{t2Z(-3rw9W@m?N&Uircy^itWvMzYM z*K6NWwTZ`8v3LqNm5OI_7>>^SI2+vu-N)JJFdUud{#HhbiuJk9RcottlikYz!|T`K z&1nwa63_rcPA1y*czvg9^+xCCT{lu-FHJxSalq->%A{EWX25YaMGVB}$y) z&+#uf{`6CL+dso!1I#;~A?~uL&AevixRtYtF%(jgBHG!)F3T`J{6<=p?&*CF29t!0e;kvEcFk^N0?Ucns^P)Hj+hNa( zCf*So*TnpG##pXdW~=iiRf}tQoSmVwkLTdEkoOLGmOY*@U$=QE)^?#mKF;8F1fcMG zh^a8PY5$7@A&wqiX)r&@0v_oRoQ>_U#}VUO9(t-@yP@!+IGG)K>S=9>^w2tn!{s+Q zW@ecG$H9IE-W0IIqKEfWN&^0I{CMTSKc1fpmr&s0{Xpx$=LE9W!5xg(yv)*h9iH0Y k871J%sS%v{U;QV9uf@aiW3|`;X#fBK07*qoM6N<$f>vgyWB>pF literal 0 HcmV?d00001 diff --git a/pulse/images/PointIconImages/6.png b/pulse/images/PointIconImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..79228776e1bce39550c8c9b0e9cbcf693ec095f8 GIT binary patch literal 1379 zcmV-p1)TbcP)j}~jMrt)` z4H#fglhl#CO_3euzqy&cJ4OkT0|rYGP4~puV%zBoRRUbNH5kPmi8@;TUy?eR0M7P<@kxLafa5Q zp749pH>YvdGy_+uufQ{OFK~tyxTzr}wU*{RjcZL;0G_F3lXIaNT$_kM&l2EFiCzS2 zohO!wcvEWVvIlh3!nP^eW@wRlk{PwK05%&vxds<)i+MZ)*g1>V;Ee@naM8ACa|Yld z!Ai@E&{`h?{%rK~%#7V>4KfvNA_6Ut7fLLx4+YQl$w8m|xOROW_^TwWdDeK@6I{%M zH?`HrJG4&VmN(%IDO!F-tu~h-8ZQala-R|H4b3O&5x`L&=Q&fE6Fi%X2hNW+m99l< zXgOIAso%jfl^nbW&VD=x-WOW$2U+j|z}Rz#?58caXxX^;rx$QJIps*#-h+X8FR+|= zwW(-3x8SXVh&7%Aw|un)y-(wc+iacAmLK=vy<>kWgI?mQybd?B*H}z0aa91o8msyX zmixzezguHfq1#o8c}zWM0q+wPb_H7H8+^D}gxmQDWkc29;rrqmKTUrKKTenNZu^9X zje=$qvVu1&0xdMSP8c|h^#+{0!H0{t;o}^cXM_@O!^b%y@Pr3}o+NGSkeT<46*d=M z#?vLI_bzcYS%ljxG(U@QJHN!0)?(i|PsvJG+Ln_7Kye*zX4*V*&j=;1!_DlRxma|- z?k&WV@EVIrZ|d}nop!~Uht|)t+@Cx-tUg6dP5&k;W6It z&Y6p)Fb1$HOJ1evu*Pb?jCb3<;LCWoU1P;z>X11biG)?h<6x&kJG_*N(+EKEgoh2{ zpNEU*(~qb?M?P1sbd7~u3;bw-TNNhG%B{>L0KgL->aX$lsz;EeGkr_+{4K4IGtd%J zayFh#0@hg7pW~=w zOI%)c#Vpdd5godhLfp)@umoJda@y7rvdIc@9BCBA=* lzwRaf^(*o4fBh$fKLDrI1u!%^pm_iQ002ovPDHLkV1h>kdDj2{ literal 0 HcmV?d00001 diff --git a/pulse/images/githubLogo.svg b/pulse/images/githubLogo.svg new file mode 100644 index 0000000..7497bca --- /dev/null +++ b/pulse/images/githubLogo.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/pulse/index.html b/pulse/index.html new file mode 100644 index 0000000..85f7a60 --- /dev/null +++ b/pulse/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + +
+
+

Enter feature layer URL

+

+ +

...
+

Select attribute to animate

+

+ + for + seconds + +

+ +
+ \ No newline at end of file diff --git a/pulse/js/main.js b/pulse/js/main.js new file mode 100644 index 0000000..012b5a4 --- /dev/null +++ b/pulse/js/main.js @@ -0,0 +1,362 @@ +require([ + + "esri/Map", + "esri/views/MapView", + "esri/layers/FeatureLayer", + "esri/layers/VectorTileLayer", + "esri/symbols/SimpleLineSymbol", + "esri/core/watchUtils", + "esri/geometry/support/webMercatorUtils", + "esri/geometry/Point", + "dojo/dom", + "dojo/domReady!" + +], function(Map, MapView, FeatureLayer, VectorTileLayer, SimpleLineSymbol, watchUtils, webMercatorUtils, Point, dom) { + //global vars + var mapLongLattZoom = [0, 0, 1] //default + var endNo //highest number in the attribute + var startNo //lowest number in attribute + var fieldToAnimate //attribute selected + var stepNumber //increment value + var setIntervalSpeed = 16.6 //refresh speed in ms + var restarting = false //flag to control removing animation + var updateField = false //check for attribute change + var intervalFunc //animation interval name + var overRidingField //casts url field as no.1 selection in attribute selector + var view + var map + + //for recasting global symbols + var geometryType //the geometry type of the feature + var newSymbol + var newType + + initalise() + + function initalise(){ + map = new Map({ + basemap: "dark-gray-vector" + }); + + view = new MapView({ + container: "viewDiv", + map: map, + zoom: 3, + center: [0, 0] + }); + + //event listeners + document.getElementById("play").addEventListener("click", play) + document.getElementById("fs-url").addEventListener("blur", addFeatureLayer) + document.getElementById("fs-url").addEventListener("change", addFeatureLayer) + + //check URL for paramaters, if there's some. Add it in. + var browserURL = window.location.search + if (browserURL != "") { + updateField = true + browserURL = browserURL.replace("?", '') + var partsOfStr = browserURL.split(',') + document.getElementById("fs-url").value = partsOfStr[0] + overRidingField = partsOfStr[1] + document.getElementById("animation-time").value = partsOfStr[2] + mapLongLattZoom = [parseInt(partsOfStr[3]), parseInt(partsOfStr[4]), parseInt(partsOfStr[5])] + + } else { + defaultService() + } + + view.when(function() { + watchUtils.when(view, "stationary", updateMapLongLatt) + + var pt = new Point({ + longitude: mapLongLattZoom[0], + latitude: mapLongLattZoom[1] + }); + + view.goTo({ + target: pt, + zoom: mapLongLattZoom[2] + }) + }) + //once feature layer url has been set, now add it to the map. + addFeatureLayer() + } + + //if there's no paramaters, then add these in as a default. + function defaultService() { + document.getElementById("fs-url").value = "https://services.arcgis.com/Qo2anKIAMzIEkIJB/arcgis/rest/services/hurricanes/FeatureServer/0" + document.getElementById("animation-time").value = 10 + } + + //this generates a new, sharable url link. + function updateBrowserURL() { + history.pushState({ + id: 'homepage' + }, 'Home', '?' + document.getElementById("fs-url").value + ',' + document.getElementById("selection").value + ',' + document.getElementById("animation-time").value + ',' + mapLongLattZoom); + } + + //when map moves, update url. + function updateMapLongLatt() { + mapLongLattZoom = [view.center.longitude, view.center.latitude, view.zoom] + updateBrowserURL() + } + + //adds the feature layer to the map. + function addFeatureLayer() { + var flURL = document.getElementById("fs-url").value + + if (flURL != "") { + featureLayer = new FeatureLayer({ + url: flURL + }); + map.removeAll() + map.add(featureLayer) + + //overides ANY scale threshold added to feature layer. + featureLayer.maxScale = 0 + featureLayer.minScale = 100000000000 + + //rest call to get attribute minimum and maximum values. + getFields(flURL) + + document.getElementById("fs-url").style.borderBottomColor = "green" + } else { + map.remove(featureLayer) + document.getElementById("fs-url").style.borderBottomColor = "red" + } + + } + + //populating selection drop down based on featurelayer. + function getFields(flURL) { + $.ajax({ + url: flURL + "?f=json", + type: "GET" + }).done(function(FLfields) { + var fieldsObj = JSON.parse(FLfields) + document.getElementById("feature-layer-name").innerHTML = fieldsObj.name + updateExtent(fieldsObj.extent) + select = document.getElementById('selection') + select.innerHTML = '' + + geometryType = fieldsObj.geometryType + symbolSwitcher(geometryType) + + for (i = 0; i < fieldsObj.fields.length; i++) { + if (fieldsObj.fields[i].sqlType != "sqlTypeNVarchar") { + + var opt = document.createElement('option') + opt.value = fieldsObj.fields[i].name + opt.innerHTML = fieldsObj.fields[i].name + + if (i === 0 && updateField === true) { + opt.value = overRidingField + opt.innerHTML = overRidingField + } + + if (updateField === true && fieldsObj.fields[i].name === overRidingField) { + opt.value = fieldsObj.fields[0].name + opt.innerHTML = fieldsObj.fields[0].name + updateField = false + } + + select.appendChild(opt) + } + + } + updateBrowserURL() + }); + } + + function updateExtent(newExtent) { + if (newExtent.spatialReference.wkid === 102100) { + view.extent = newExtent + } + if (newExtent.spatialReference.wkid != 102100) { + view.extent = { + xmax: 20026375.71466102, + xmin: -20026375.71466102, + ymax: 9349764.174146919, + ymin: -5558767.721795811 + } + } + } + + function play() { + //Stops any previously added animations in the frame + stopAnimation() + + //There's an unknown issue caused by "ObjectID" + //This is currently a workaround for it. + if(document.getElementById("selection").value === "OBJECTID"){ + if (document.getElementById("fs-url").value != "") { + featureLayer = new FeatureLayer({ + url: document.getElementById("fs-url").value + }); + map.removeAll() + map.add(featureLayer) + } + } + + //update with changed values. + updateBrowserURL() + + //queries the current feature layer url and field to work out start and end frame. + getMaxMin(); + } + + function getMaxMin() { + var flURL = document.getElementById("fs-url").value + var field = document.getElementById("selection").value + + $.ajax({ + url: flURL + "/query", + type: "GET", + data: { + 'f': 'pjson', + 'outStatistics': '[{"statisticType":"min","onStatisticField":"' + field + + '", "outStatisticFieldName":"MinID"},{"statisticType":"max","onStatisticField":"' + + field + '", "outStatisticFieldName":"MaxID"}]' + } + }).done(function(data) { + var dataJSONObj = JSON.parse(data) + + fieldToAnimate = field + startNumber(dataJSONObj.features[0].attributes.MinID) + endNo = dataJSONObj.features[0].attributes.MaxID + + //generate step number here too + var difference = Math.abs(dataJSONObj.features[0].attributes.MinID - dataJSONObj.features[ + 0].attributes.MaxID) + var differencePerSecond = difference / document.getElementById("animation-time").value + stepNumber = differencePerSecond / setIntervalSpeed + startNo = dataJSONObj.features[0].attributes.MinID + animate(dataJSONObj.features[0].attributes.MinID) + + //adding empty frames at the start and end for fade in/out + endNo += stepNumber * 40 + startNo -= stepNumber * 2 + }); + + } + + function stopAnimation() { + startNumber(null) + stepNumber = null + fieldToAnimate = null + startNo = null + endNo = null + restarting = true; + } + + function startNumber(value) { + featureLayer.renderer = createRenderer(value); + } + + function animate(startValue) { + var currentFrame = startValue + + var frame = function(timestamp) { + if (restarting) { + clearTimeout(intervalFunc); + restating = false + } + + currentFrame += stepNumber + + if (currentFrame > endNo) { + currentFrame = startNo + } + + startNumber(currentFrame) + + //animation loop. + intervalFunc = setTimeout(function() { + //stops it from overloading. + requestAnimationFrame(frame) + }, setIntervalSpeed) + } + + //recusrive function, starting the animation. + frame() + + return { + remove: function() { + animating = false + } + }; + } + + + //CHANGE SYMBOLOGY TYPE HERE. (Point, Line or Polygon style) + function symbolSwitcher(geometryType) { + //Depending on the feature layer currently added, the symbology will change here. + //Supporting points, lines and polygons. + if (geometryType === "esriGeometryPoint") { + newSymbol = { + type: "picture-marker", + url: "images/PointIconImages/2.png", + width: 20, + height: 20 + } + + newType = 'simple' + } + + if (geometryType === "esriGeometryPolyline") { + newSymbol = { + type: 'simple-line', + width: 3, + color: 'rgb(55, 55, 255)', + opacity: 1 + } + + newType = 'simple' + } + + if (geometryType === "esriGeometryPolygon") { + newSymbol = { + type: "simple-fill", + color: "rgb(55, 55, 255)" + } + + newType = 'simple' + } + } + + function createRenderer(now) { + return { + type: newType, + symbol: newSymbol, + visualVariables: [{ + type: 'opacity', + field: fieldToAnimate, + //stops control the fade out + stops: [{ + value: now - stepNumber * 40, + opacity: 0.0 + //Change this to 0.1 if you always want it on screen during animation + }, + { + value: now - stepNumber * 20, + opacity: 0.3 + }, + { + value: now - stepNumber * 1, + opacity: 1 + }, + { + value: now, + opacity: 1 + }, + { + value: now + stepNumber * 2, + opacity: 0 + } + + ] + }] + }; + } + +}) \ No newline at end of file From 23de3b9f30a9f8c33c2a47083d5af232fb5c1795 Mon Sep 17 00:00:00 2001 From: Richard Mumford Date: Fri, 22 Jun 2018 14:36:56 +0100 Subject: [PATCH 2/8] Added hipstamap --- hipstamap/css/generator.css | 77 + hipstamap/css/hipsta.css | 359 + hipstamap/css/jquery.sidr.css | 165 + hipstamap/css/jquery.sidr.dark.css | 1 + hipstamap/css/jquery.sidr.light.css | 1 + hipstamap/css/style.css | 81 + hipstamap/dist/html2canvas.js | 3375 +++++ hipstamap/dist/html2canvas.min.js | 8 + hipstamap/dist/html2canvas.svg.js | 17702 ++++++++++++++++++++++++ hipstamap/dist/html2canvas.svg.min.js | 12 + hipstamap/images/carousel-sprite.png | Bin 0 -> 220576 bytes hipstamap/images/download.png | Bin 0 -> 1191 bytes hipstamap/images/logo.png | Bin 0 -> 12991 bytes hipstamap/images/settings.png | Bin 0 -> 1612 bytes hipstamap/index.html | 123 + hipstamap/js/common.js | 107 + hipstamap/js/jquery.sidr.min.js | 4 + 17 files changed, 22015 insertions(+) create mode 100644 hipstamap/css/generator.css create mode 100644 hipstamap/css/hipsta.css create mode 100644 hipstamap/css/jquery.sidr.css create mode 100644 hipstamap/css/jquery.sidr.dark.css create mode 100644 hipstamap/css/jquery.sidr.light.css create mode 100644 hipstamap/css/style.css create mode 100644 hipstamap/dist/html2canvas.js create mode 100644 hipstamap/dist/html2canvas.min.js create mode 100644 hipstamap/dist/html2canvas.svg.js create mode 100644 hipstamap/dist/html2canvas.svg.min.js create mode 100644 hipstamap/images/carousel-sprite.png create mode 100644 hipstamap/images/download.png create mode 100644 hipstamap/images/logo.png create mode 100644 hipstamap/images/settings.png create mode 100644 hipstamap/index.html create mode 100644 hipstamap/js/common.js create mode 100644 hipstamap/js/jquery.sidr.min.js diff --git a/hipstamap/css/generator.css b/hipstamap/css/generator.css new file mode 100644 index 0000000..54514f4 --- /dev/null +++ b/hipstamap/css/generator.css @@ -0,0 +1,77 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:700"); + +html, body, #map { + height: 100%; + width: 100%; + margin: 0; + padding: 0; +} + +body { + font-family: 'Open Sans', sans-serif; +} + +#map_zoom_slider { + display: none; +} + +#logo { + background: url("../images/logo.png"); + height: 51px; + width: 200px; + position: absolute; + top: 6px; + z-index: 10; + left: 324px; +} + +#panel { + padding: 20px; + position: absolute; + top: 0; + left: 0; + width:15%; + bottom: 0; + z-index: 10; + background: #efefef; +} + +#panel h3 { + margin-top: 0; +} + +#panel hr { + margin: 30px 0 25px; + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.5); +} + +#panel .slider-holder {} + +#panel .slider-holder label { + font-size: 0.8em; +} + +#panel .slider-holder .slider { + margin: 10px 0; + height: 0.4em; + border-radius: 0; + background: #E8E8E8; +} + +#panel .slider-holder .ui-slider-handle { + top: -1px; + margin-left: -0.2em; + width: 0.4em; + height: 0.4em; + border: 1px solid #900808; + background: #900808; + border-radius: 0; +} + +#panel .slider-holder .ui-slider-handle:focus { + outline: none; +} + diff --git a/hipstamap/css/hipsta.css b/hipstamap/css/hipsta.css new file mode 100644 index 0000000..22409e3 --- /dev/null +++ b/hipstamap/css/hipsta.css @@ -0,0 +1,359 @@ +@import url("https://fonts.googleapis.com/css?family=Open+Sans:700"); + +html, body, #map { + height: 100%; + width: 100%; + margin: 0; + padding: 0; +} + +body { + font-family: 'Open Sans', sans-serif; +} + +#switcher-list { + position: absolute; + bottom: 60px; + left: 50%; + z-index: 10; + right: 20px; + background: rgba(0,0,0,.9); + margin: 0; + padding: 6px; + height: 80px; + width: 1026px; + margin-left: -513px; +} + +#switcher-list li { + float: left; + height: 80px; + width: 80px; + margin-right: 6px; +} + +#switcher-list li:last-child { + margin-right: 0; +} + +#switcher-list li a { + float: left; + height: 80px; + width: 80px; + cursor: pointer; + background: url("../images/carousel-sprite.png"); +} + +#switcher-list li a.active { + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + border: 1px solid #FCED4D; +} + +#switcher-list li a.grid { background-position: 0 0; } +#switcher-list li a.grid.active { background-position: -1px -1px; } + +#switcher-list li a.aden { background-position: -80px 0; } +#switcher-list li a.aden.active { background-position: -81px -1px; } + +#switcher-list li a.walden { background-position: -160px 0; } +#switcher-list li a.walden.active { background-position: -161px -1px; } + +#switcher-list li a.inkwell { background-position: -240px 0; } +#switcher-list li a.inkwell.active { background-position: -241px -1px; } + +#switcher-list li a.perpetua { background-position: -320px 0; } +#switcher-list li a.perpetua.active { background-position: -321px -1px; } + +#switcher-list li a.reyes { background-position: -400px 0; } +#switcher-list li a.reyes.active { background-position: -401px -1px; } + +#switcher-list li a.gingham { background-position: -480px 0; } +#switcher-list li a.gingham.active { background-position: -481px -1px; } + +#switcher-list li a.toaster { background-position: -560px 0; } +#switcher-list li a.toaster.active { background-position: -561px -1px; } + +#switcher-list li a.lofi { background-position: -640px 0; } +#switcher-list li a.lofi.active { background-position: -641px -1px; } + +#switcher-list li a.xpro2 { background-position: -720px 0; } +#switcher-list li a.xpro2.active { background-position: -721px -1px; } + +#switcher-list li a.earlybird { background-position: -800px 0; } +#switcher-list li a.earlybird.active { background-position: -801px -1px; } + +#switcher-list li a.hudson { background-position: -1040px 0; } +#switcher-list li a.hudson.active { background-position: -1041px -1px; } + +#switcher-list li a.mayfair { background-position: -880px 0; } +#switcher-list li a.mayfair.active { background-position: -881px -1px; } + +#switcher-list li a.sutro { background-position: -960px 0; } +#switcher-list li a.sutro.active { background-position: -961px -1px; } + +#switcher-list li a.brooklyn { background-position: -1040px 0; } +#switcher-list li a.brooklyn.active { background-position: -1041px -1px; } + +#switcher-list li a.sepia { background-position: -1120px 0; } +#switcher-list li a.sepia.active { background-position: -1121px -1px; } + +#logo { + background: url("../images/logo.png"); + height: 51px; + width: 200px; + position: fixed; + top: 6px; + z-index: 10; + left: 6px; +} + +#adv-menu { + background: url("../images/settings.png"); + height: 52px; + width: 52px; + position: absolute; + top: 64px; + z-index: 10; + right: 0; +} + +#capture { + background: url("../images/download.png"); + height: 52px; + width: 52px; + position: absolute; + top: 6px; + right: 0; + margin: 0; + z-index: 2; + outline: none; +} + +#map_zoom_slider { + display: none; +} + +/*--- _1977 ---*/ +._1977 #map_container { + -webkit-filter: contrast(1.1) brightness(1.1) saturate(1.3); + filter: contrast(1.1) brightness(1.1) saturate(1.3) +} + +._1977 #map_container:after { + background: rgba(243, 106, 188, .3); + mix-blend-mode: screen +} + +/*--- aden ---*/ +.aden #map_container { + -webkit-filter: hue-rotate(-20deg) contrast(.9) saturate(.85) brightness(1.2); + filter: hue-rotate(-20deg) contrast(.9) saturate(.85) brightness(1.2) +} +.aden #map_container:after { + background: -webkit-linear-gradient(left, rgba(66, 10, 14, .2), transparent); + background: linear-gradient(to right, rgba(66, 10, 14, .2), transparent); + mix-blend-mode: darken +} + +/*--- walden ---*/ +.walden #map_container { + -webkit-filter: brightness(1.1) hue-rotate(-10deg) sepia(.3) saturate(1.6); + filter: brightness(1.1) hue-rotate(-10deg) sepia(.3) saturate(1.6) +} +.walden #map_container:after { + background: #04c; + mix-blend-mode: screen; + opacity: .3 +} + +/*--- inkwell ---*/ +.inkwell #map_container { + -webkit-filter: sepia(.3) contrast(1.1) brightness(1.1) grayscale(1); + filter: sepia(.3) contrast(1.1) brightness(1.1) grayscale(1) +} + +/*--- perpetua ---*/ +.perpetua #map_container:after { + background: -webkit-linear-gradient(top, #005b9a, #e6c13d); + background: linear-gradient(to bottom, #005b9a, #e6c13d); + mix-blend-mode: soft-light; + opacity: .5 +} + +/*--- reyes ---*/ +.reyes #map_container { + -webkit-filter: sepia(.22) brightness(1.1) contrast(.85) saturate(.75); + filter: sepia(.22) brightness(1.1) contrast(.85) saturate(.75) +} +.reyes #map_container:after { + background: #efcdad; + mix-blend-mode: soft-light; + opacity: .5 +} + +/*--- tint ---*/ +.tint #map_container { + -webkit-filter: sepia(1) hue-rotate(200deg); + filter: sepia(1) hue-rotate(200deg); +} + +/*--- gingham ---*/ +.gingham #map_container { + -webkit-filter: brightness(1.05) hue-rotate(-10deg); + filter: brightness(1.05) hue-rotate(-10deg) +} +.gingham #map_container:after { + background: -webkit-linear-gradient(left, rgba(66, 10, 14, .2), transparent); + background: linear-gradient(to right, rgba(66, 10, 14, .2), transparent); + mix-blend-mode: darken +} + +/*--- toaster ---*/ +.toaster #map_container { + -webkit-filter: contrast(1.5) brightness(.9); + filter: contrast(1.5) brightness(.9); +} +.toaster #map_container:after { + background: -webkit-radial-gradient(circle, #804e0f, #3b003b); + background: radial-gradient(circle, #804e0f, #3b003b); + mix-blend-mode: screen +} + +/*--- hudson ---*/ +.hudson #map_container { + -webkit-filter: brightness(1.2) contrast(.9) saturate(1.1); + filter: brightness(1.2) contrast(.9) saturate(1.1) +} +.hudson #map_container:after { + background: -webkit-radial-gradient(circle, #a6b1ff 50%, #342134); + background: radial-gradient(circle, #a6b1ff 50%, #342134); + mix-blend-mode: multiply; + opacity: .5 +} + +/*--- xpro2 ---*/ +.xpro2 #map_container { + -webkit-filter: sepia(.3); + filter: sepia(.3) +} +.xpro2 #map_container:after { + background: -webkit-radial-gradient(circle, #E6E7E0 40%, rgba(43, 42, 161, .6) 110%); + background: radial-gradient(circle, #E6E7E0 40%, rgba(43, 42, 161, .6) 110%); + mix-blend-mode: color-burn +} + +/*--- earlybird ---*/ +.earlybird #map_container { + -webkit-filter: contrast(.9) sepia(.2); + filter: contrast(.9) sepia(.2) +} +.earlybird #map_container:after { + background: -webkit-radial-gradient(circle, #d0ba8e 20%, #360309 85%, #1d0210 100%); + background: radial-gradient(circle, #d0ba8e 20%, #360309 85%, #1d0210 100%); + mix-blend-mode: overlay +} + +/*--- mayfair ---*/ +.mayfair #map_container { + -webkit-filter: contrast(1.1) saturate(1.1); + filter: contrast(1.1) saturate(1.1) +} +.mayfair #map_container:after { + background: -webkit-radial-gradient(40% 40%, circle, rgba(255, 255, 255, .8), rgba(255, 200, 200, .6), #111 60%); + background: radial-gradient(circle at 40% 40%, rgba(255, 255, 255, .8), rgba(255, 200, 200, .6), #111 60%); + mix-blend-mode: overlay; + opacity: .4 +} + +/*--- lofi ---*/ +.lofi #map_container { + filter: contrast(1.4) brightness(0.9) sepia(0.05); + -webkit-filter: contrast(1.4) brightness(0.9) sepia(0.05); +} +.lofi #map_container:after { + background: -webkit-radial-gradient(circle, transparent 70%, #222 150%); + background: radial-gradient(circle, transparent 70%, #222 150%); + mix-blend-mode: multiply +} + +/*-- sutro ---*/ +.sutro #map_container { + -webkit-filter: brightness(0.75) contrast(1.3) sepia(0.5) hue-rotate(-25deg); + filter: brightness(0.75) contrast(1.3) sepia(0.5) hue-rotate(-25deg); +} + +/*-- sierra ---*/ +.sierra #map_container { + -webkit-filter: contrast(0.8) saturate(1.2) sepia(0.15); + filter: contrast(0.8) saturate(1.2) sepia(0.15); +} + +/*--- brooklyn ---*/ +.brooklyn #map_container { + -webkit-filter: contrast(.9) brightness(1.1); + filter: contrast(.9) brightness(1.1) +} +.brooklyn #map_container:after { + background: -webkit-radial-gradient(circle, rgba(168, 223, 193, .4) 70%, #c4b7c8); + background: radial-gradient(circle, rgba(168, 223, 193, .4) 70%, #c4b7c8); + mix-blend-mode: overlay +} + +/*--- sepia ---*/ +.sepia #map_container { + -webkit-filter: contrast(0.9) sepia(1); +} + +/*--- grid ---*/ +.grid #map_container:after { + box-sizing: border-box; + content: ""; + position: absolute; + top: 0; + left: 0; + z-index: 1; + background-color: transparent; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAGElEQVQIW2NkYGD4D8SMMBrEQAFwGZwqAKzeBAUqvbkLAAAAAElFTkSuQmCC'); + background-size: 4px 4px; +} + +/*-- glossy ---*/ +.glossy #map_container:after { + box-sizing: border-box; + content: ""; + position: absolute; + top: 0; + left: 0; + z-index: 1; + background-color: transparent; + background-image: linear-gradient(45deg, transparent 50%, rgba(255,255,255,.15) 50%); +} + +/*-- stripes-v ---*/ +.stripes-v #map_container:after { + box-sizing: border-box; + content: ""; + position: absolute; + top: 0; + left: 0; + z-index: 1; + background-color: transparent; + background-image: linear-gradient(90deg, transparent 50%, rgba(255,255,255,.5) 50%); + background-size: 2px 10px; +} + +#map_container { + position: relative +} + +#map_container:after { + content: ''; + display: block; + height: 100%; + width: 100%; + top: 0; + left: 0; + position: absolute +} \ No newline at end of file diff --git a/hipstamap/css/jquery.sidr.css b/hipstamap/css/jquery.sidr.css new file mode 100644 index 0000000..810ec5b --- /dev/null +++ b/hipstamap/css/jquery.sidr.css @@ -0,0 +1,165 @@ +.sidr { + display: none; + position: absolute; + position: fixed; + top: 0; + height: 100%; + z-index: 999999; + width: 260px; + overflow-x: none; + overflow-y: auto; + background: #f8f8f8; + color: #333; + -webkit-box-shadow: inset 0 0 5px 5px #ebebeb; + -moz-box-shadow: inset 0 0 5px 5px #ebebeb; + box-shadow: inset 0 0 5px 5px #ebebeb +} + +.sidr .sidr-inner { + padding: 0 0 15px +} + +.sidr .sidr-inner >p { + margin-left: 15px; + margin-right: 15px +} + +.sidr.right { + left: auto; + right: -260px +} + +.sidr.left { + left: -260px; + right: auto +} + +.sidr p { + margin: 0 0 12px +} +.sidr p a { + color: rgba(51, 51, 51, 0.9) +} +.sidr>p { + margin-left: 15px; + margin-right: 15px +} +.sidr ul { + display: block; + margin: 0 0 15px; + padding: 0; + border-top: 1px solid #dfdfdf; + border-bottom: 1px solid #fff +} +.sidr ul li { + display: block; + margin: 0; + line-height: 48px; + border-top: 1px solid #fff; + border-bottom: 1px solid #dfdfdf +} +.sidr ul li:hover, +.sidr ul li.active, +.sidr ul li.sidr-class-active { + border-top: none; + line-height: 49px +} +.sidr ul li:hover>a, +.sidr ul li:hover>span, +.sidr ul li.active>a, +.sidr ul li.active>span, +.sidr ul li.sidr-class-active>a, +.sidr ul li.sidr-class-active>span { + -webkit-box-shadow: inset 0 0 15px 3px #ebebeb; + -moz-box-shadow: inset 0 0 15px 3px #ebebeb; + box-shadow: inset 0 0 15px 3px #ebebeb +} +.sidr ul li a, +.sidr ul li span { + padding: 0 15px; + display: block; + text-decoration: none; + color: #333 +} +.sidr ul li ul { + border-bottom: none; + margin: 0 +} +.sidr ul li ul li { + line-height: 40px; + font-size: 13px +} +.sidr ul li ul li:last-child { + border-bottom: none +} +.sidr ul li ul li:hover, +.sidr ul li ul li.active, +.sidr ul li ul li.sidr-class-active { + border-top: none; + line-height: 41px +} +.sidr ul li ul li:hover>a, +.sidr ul li ul li:hover>span, +.sidr ul li ul li.active>a, +.sidr ul li ul li.active>span, +.sidr ul li ul li.sidr-class-active>a, +.sidr ul li ul li.sidr-class-active>span { + -webkit-box-shadow: inset 0 0 15px 3px #ebebeb; + -moz-box-shadow: inset 0 0 15px 3px #ebebeb; + box-shadow: inset 0 0 15px 3px #ebebeb +} +.sidr ul li ul li a, +.sidr ul li ul li span { + color: rgba(51, 51, 51, 0.8); + padding-left: 30px +} +.sidr form { + margin: 0 15px +} +.sidr label { + font-size: 13px +} +.sidr input[type="text"], +.sidr input[type="password"], +.sidr input[type="date"], +.sidr input[type="datetime"], +.sidr input[type="email"], +.sidr input[type="number"], +.sidr input[type="search"], +.sidr input[type="tel"], +.sidr input[type="time"], +.sidr input[type="url"], +.sidr textarea, +.sidr select { + width: 100%; + font-size: 13px; + padding: 5px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0 0 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + -ms-border-radius: 2px; + -o-border-radius: 2px; + border-radius: 2px; + border: none; + background: rgba(0, 0, 0, 0.1); + color: rgba(51, 51, 51, 0.6); + display: block; + clear: both +} +.sidr input[type=checkbox] { + width: auto; + display: inline; + clear: none +} +.sidr input[type=button], +.sidr input[type=submit] { + color: #f8f8f8; + background: #333 +} +.sidr input[type=button]:hover, +.sidr input[type=submit]:hover { + background: rgba(51, 51, 51, 0.9) +} \ No newline at end of file diff --git a/hipstamap/css/jquery.sidr.dark.css b/hipstamap/css/jquery.sidr.dark.css new file mode 100644 index 0000000..cb3f183 --- /dev/null +++ b/hipstamap/css/jquery.sidr.dark.css @@ -0,0 +1 @@ +.sidr{display:none;position:absolute;position:fixed;top:0;height:100%;z-index:999999;width:260px;overflow-x:none;overflow-y:auto;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:15px;background:#333;color:#fff;-webkit-box-shadow:inset 0 0 5px 5px #222;-moz-box-shadow:inset 0 0 5px 5px #222;box-shadow:inset 0 0 5px 5px #222}.sidr .sidr-inner{padding:0 0 15px}.sidr .sidr-inner>p{margin-left:15px;margin-right:15px}.sidr.right{left:auto;right:-260px}.sidr.left{left:-260px;right:auto}.sidr h1,.sidr h2,.sidr h3,.sidr h4,.sidr h5,.sidr h6{font-size:11px;font-weight:normal;padding:0 15px;margin:0 0 5px;color:#fff;line-height:24px;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4d4d4d), color-stop(100%, #1a1a1a));background-image:-webkit-linear-gradient(#4d4d4d,#1a1a1a);background-image:-moz-linear-gradient(#4d4d4d,#1a1a1a);background-image:-o-linear-gradient(#4d4d4d,#1a1a1a);background-image:linear-gradient(#4d4d4d,#1a1a1a);-webkit-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);box-shadow:0 5px 5px 3px rgba(0,0,0,0.2)}.sidr p{font-size:13px;margin:0 0 12px}.sidr p a{color:rgba(255,255,255,0.9)}.sidr>p{margin-left:15px;margin-right:15px}.sidr ul{display:block;margin:0 0 15px;padding:0;border-top:1px solid #1a1a1a;border-bottom:1px solid #4d4d4d}.sidr ul li{display:block;margin:0;line-height:48px;border-top:1px solid #4d4d4d;border-bottom:1px solid #1a1a1a}.sidr ul li:hover,.sidr ul li.active,.sidr ul li.sidr-class-active{border-top:none;line-height:49px}.sidr ul li:hover>a,.sidr ul li:hover>span,.sidr ul li.active>a,.sidr ul li.active>span,.sidr ul li.sidr-class-active>a,.sidr ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #222;-moz-box-shadow:inset 0 0 15px 3px #222;box-shadow:inset 0 0 15px 3px #222}.sidr ul li a,.sidr ul li span{padding:0 15px;display:block;text-decoration:none;color:#fff}.sidr ul li ul{border-bottom:none;margin:0}.sidr ul li ul li{line-height:40px;font-size:13px}.sidr ul li ul li:last-child{border-bottom:none}.sidr ul li ul li:hover,.sidr ul li ul li.active,.sidr ul li ul li.sidr-class-active{border-top:none;line-height:41px}.sidr ul li ul li:hover>a,.sidr ul li ul li:hover>span,.sidr ul li ul li.active>a,.sidr ul li ul li.active>span,.sidr ul li ul li.sidr-class-active>a,.sidr ul li ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #222;-moz-box-shadow:inset 0 0 15px 3px #222;box-shadow:inset 0 0 15px 3px #222}.sidr ul li ul li a,.sidr ul li ul li span{color:rgba(255,255,255,0.8);padding-left:30px}.sidr form{margin:0 15px}.sidr label{font-size:13px}.sidr input[type="text"],.sidr input[type="password"],.sidr input[type="date"],.sidr input[type="datetime"],.sidr input[type="email"],.sidr input[type="number"],.sidr input[type="search"],.sidr input[type="tel"],.sidr input[type="time"],.sidr input[type="url"],.sidr textarea,.sidr select{width:100%;font-size:13px;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0 0 10px;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;border:none;background:rgba(0,0,0,0.1);color:rgba(255,255,255,0.6);display:block;clear:both}.sidr input[type=checkbox]{width:auto;display:inline;clear:none}.sidr input[type=button],.sidr input[type=submit]{color:#333;background:#fff}.sidr input[type=button]:hover,.sidr input[type=submit]:hover{background:rgba(255,255,255,0.9)} diff --git a/hipstamap/css/jquery.sidr.light.css b/hipstamap/css/jquery.sidr.light.css new file mode 100644 index 0000000..50d0685 --- /dev/null +++ b/hipstamap/css/jquery.sidr.light.css @@ -0,0 +1 @@ +.sidr{display:none;position:absolute;position:fixed;top:0;height:100%;z-index:999999;width:260px;overflow-x:none;overflow-y:auto;font-family:"lucida grande",tahoma,verdana,arial,sans-serif;font-size:15px;background:#f8f8f8;color:#333;-webkit-box-shadow:inset 0 0 5px 5px #ebebeb;-moz-box-shadow:inset 0 0 5px 5px #ebebeb;box-shadow:inset 0 0 5px 5px #ebebeb}.sidr .sidr-inner{padding:0 0 15px}.sidr .sidr-inner>p{margin-left:15px;margin-right:15px}.sidr.right{left:auto;right:-260px}.sidr.left{left:-260px;right:auto}.sidr h1,.sidr h2,.sidr h3,.sidr h4,.sidr h5,.sidr h6{font-size:11px;font-weight:normal;padding:0 15px;margin:0 0 5px;color:#333;line-height:24px;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #dfdfdf));background-image:-webkit-linear-gradient(#ffffff,#dfdfdf);background-image:-moz-linear-gradient(#ffffff,#dfdfdf);background-image:-o-linear-gradient(#ffffff,#dfdfdf);background-image:linear-gradient(#ffffff,#dfdfdf);-webkit-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 5px 3px rgba(0,0,0,0.2);box-shadow:0 5px 5px 3px rgba(0,0,0,0.2)}.sidr p{font-size:13px;margin:0 0 12px}.sidr p a{color:rgba(51,51,51,0.9)}.sidr>p{margin-left:15px;margin-right:15px}.sidr ul{display:block;margin:0 0 15px;padding:0;border-top:1px solid #dfdfdf;border-bottom:1px solid #fff}.sidr ul li{display:block;margin:0;line-height:48px;border-top:1px solid #fff;border-bottom:1px solid #dfdfdf}.sidr ul li:hover,.sidr ul li.active,.sidr ul li.sidr-class-active{border-top:none;line-height:49px}.sidr ul li:hover>a,.sidr ul li:hover>span,.sidr ul li.active>a,.sidr ul li.active>span,.sidr ul li.sidr-class-active>a,.sidr ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #ebebeb;-moz-box-shadow:inset 0 0 15px 3px #ebebeb;box-shadow:inset 0 0 15px 3px #ebebeb}.sidr ul li a,.sidr ul li span{padding:0 15px;display:block;text-decoration:none;color:#333}.sidr ul li ul{border-bottom:none;margin:0}.sidr ul li ul li{line-height:40px;font-size:13px}.sidr ul li ul li:last-child{border-bottom:none}.sidr ul li ul li:hover,.sidr ul li ul li.active,.sidr ul li ul li.sidr-class-active{border-top:none;line-height:41px}.sidr ul li ul li:hover>a,.sidr ul li ul li:hover>span,.sidr ul li ul li.active>a,.sidr ul li ul li.active>span,.sidr ul li ul li.sidr-class-active>a,.sidr ul li ul li.sidr-class-active>span{-webkit-box-shadow:inset 0 0 15px 3px #ebebeb;-moz-box-shadow:inset 0 0 15px 3px #ebebeb;box-shadow:inset 0 0 15px 3px #ebebeb}.sidr ul li ul li a,.sidr ul li ul li span{color:rgba(51,51,51,0.8);padding-left:30px}.sidr form{margin:0 15px}.sidr label{font-size:13px}.sidr input[type="text"],.sidr input[type="password"],.sidr input[type="date"],.sidr input[type="datetime"],.sidr input[type="email"],.sidr input[type="number"],.sidr input[type="search"],.sidr input[type="tel"],.sidr input[type="time"],.sidr input[type="url"],.sidr textarea,.sidr select{width:100%;font-size:13px;padding:5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0 0 10px;-webkit-border-radius:2px;-moz-border-radius:2px;-ms-border-radius:2px;-o-border-radius:2px;border-radius:2px;border:none;background:rgba(0,0,0,0.1);color:rgba(51,51,51,0.6);display:block;clear:both}.sidr input[type=checkbox]{width:auto;display:inline;clear:none}.sidr input[type=button],.sidr input[type=submit]{color:#f8f8f8;background:#333}.sidr input[type=button]:hover,.sidr input[type=submit]:hover{background:rgba(51,51,51,0.9)} diff --git a/hipstamap/css/style.css b/hipstamap/css/style.css new file mode 100644 index 0000000..53b2350 --- /dev/null +++ b/hipstamap/css/style.css @@ -0,0 +1,81 @@ +#panel { + padding: 20px; +} + +#panel h3 { + margin-top: 0; +} + +#panel hr { + margin: 30px 0 25px; + border: 0; + height: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(255, 255, 255, 0.5); +} + +#panel .slider-holder {} + +#panel .slider-holder label { + font-size: 0.8em; +} + +#panel .slider-holder .slider { + margin: 10px 0; + height: 0.4em; + border-radius: 0; + background: #E8E8E8; +} + +#panel .slider-holder .ui-slider-handle { + top: -1px; + margin-left: -0.2em; + width: 0.4em; + height: 0.4em; + border: 1px solid #900808; + background: #900808; + border-radius: 0; + cursor:w-resize; +} + +#panel .slider-holder .ui-slider-handle:focus { + outline: none; +} + +#reset { + margin-top: 16px; +} + +.btn, .btn-large, .btn-flat { + border: none; + border-radius: 2px; + display: inline-block; + height: 36px; + line-height: 36px; + outline: 0; + padding: 0 2rem; + text-transform: uppercase; + vertical-align: middle; + -webkit-tap-highlight-color: transparent; +} + +.btn, .btn-large { + text-decoration: none; + color: #fff; + background-color: #26a69a; + text-align: center; + letter-spacing: .5px; + -webkit-transition: .2s ease-out; + -moz-transition: .2s ease-out; + -o-transition: .2s ease-out; + -ms-transition: .2s ease-out; + transition: .2s ease-out; + cursor: pointer; +} + +.map .container .layersDiv .layerTile { + position: absolute !important; + border: none !important; + margin: 0px !important; + padding: 0px !important; +} \ No newline at end of file diff --git a/hipstamap/dist/html2canvas.js b/hipstamap/dist/html2canvas.js new file mode 100644 index 0000000..8792636 --- /dev/null +++ b/hipstamap/dist/html2canvas.js @@ -0,0 +1,3375 @@ +/* + html2canvas 0.5.0-alpha1 + Copyright (c) 2015 Niklas von Hertzen + + Released under MIT License +*/ + +(function(window, document, exports, global, define, undefined){ + +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version 2.0.1 + */ + +(function(){function r(a,b){n[l]=a;n[l+1]=b;l+=2;2===l&&A()}function s(a){return"function"===typeof a}function F(){return function(){process.nextTick(t)}}function G(){var a=0,b=new B(t),c=document.createTextNode("");b.observe(c,{characterData:!0});return function(){c.data=a=++a%2}}function H(){var a=new MessageChannel;a.port1.onmessage=t;return function(){a.port2.postMessage(0)}}function I(){return function(){setTimeout(t,1)}}function t(){for(var a=0;a= 0x80 (not a basic code point)', + 'invalid-input': 'Invalid input' + }, + + /** Convenience shortcuts */ + baseMinusTMin = base - tMin, + floor = Math.floor, + stringFromCharCode = String.fromCharCode, + + /** Temporary variable */ + key; + + /*--------------------------------------------------------------------------*/ + + /** + * A generic error utility function. + * @private + * @param {String} type The error type. + * @returns {Error} Throws a `RangeError` with the applicable error message. + */ + function error(type) { + throw RangeError(errors[type]); + } + + /** + * A generic `Array#map` utility function. + * @private + * @param {Array} array The array to iterate over. + * @param {Function} callback The function that gets called for every array + * item. + * @returns {Array} A new array of values returned by the callback function. + */ + function map(array, fn) { + var length = array.length; + var result = []; + while (length--) { + result[length] = fn(array[length]); + } + return result; + } + + /** + * A simple `Array#map`-like wrapper to work with domain name strings or email + * addresses. + * @private + * @param {String} domain The domain name or email address. + * @param {Function} callback The function that gets called for every + * character. + * @returns {Array} A new string of characters returned by the callback + * function. + */ + function mapDomain(string, fn) { + var parts = string.split('@'); + var result = ''; + if (parts.length > 1) { + // In email addresses, only the domain name should be punycoded. Leave + // the local part (i.e. everything up to `@`) intact. + result = parts[0] + '@'; + string = parts[1]; + } + var labels = string.split(regexSeparators); + var encoded = map(labels, fn).join('.'); + return result + encoded; + } + + /** + * Creates an array containing the numeric code points of each Unicode + * character in the string. While JavaScript uses UCS-2 internally, + * this function will convert a pair of surrogate halves (each of which + * UCS-2 exposes as separate characters) into a single code point, + * matching UTF-16. + * @see `punycode.ucs2.encode` + * @see + * @memberOf punycode.ucs2 + * @name decode + * @param {String} string The Unicode input string (UCS-2). + * @returns {Array} The new array of code points. + */ + function ucs2decode(string) { + var output = [], + counter = 0, + length = string.length, + value, + extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + /** + * Creates a string based on an array of numeric code points. + * @see `punycode.ucs2.decode` + * @memberOf punycode.ucs2 + * @name encode + * @param {Array} codePoints The array of numeric code points. + * @returns {String} The new Unicode string (UCS-2). + */ + function ucs2encode(array) { + return map(array, function(value) { + var output = ''; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + return output; + }).join(''); + } + + /** + * Converts a basic code point into a digit/integer. + * @see `digitToBasic()` + * @private + * @param {Number} codePoint The basic numeric code point value. + * @returns {Number} The numeric value of a basic code point (for use in + * representing integers) in the range `0` to `base - 1`, or `base` if + * the code point does not represent a value. + */ + function basicToDigit(codePoint) { + if (codePoint - 48 < 10) { + return codePoint - 22; + } + if (codePoint - 65 < 26) { + return codePoint - 65; + } + if (codePoint - 97 < 26) { + return codePoint - 97; + } + return base; + } + + /** + * Converts a digit/integer into a basic code point. + * @see `basicToDigit()` + * @private + * @param {Number} digit The numeric value of a basic code point. + * @returns {Number} The basic code point whose value (when used for + * representing integers) is `digit`, which needs to be in the range + * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is + * used; else, the lowercase form is used. The behavior is undefined + * if `flag` is non-zero and `digit` has no uppercase form. + */ + function digitToBasic(digit, flag) { + // 0..25 map to ASCII a..z or A..Z + // 26..35 map to ASCII 0..9 + return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); + } + + /** + * Bias adaptation function as per section 3.4 of RFC 3492. + * http://tools.ietf.org/html/rfc3492#section-3.4 + * @private + */ + function adapt(delta, numPoints, firstTime) { + var k = 0; + delta = firstTime ? floor(delta / damp) : delta >> 1; + delta += floor(delta / numPoints); + for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) { + delta = floor(delta / baseMinusTMin); + } + return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); + } + + /** + * Converts a Punycode string of ASCII-only symbols to a string of Unicode + * symbols. + * @memberOf punycode + * @param {String} input The Punycode string of ASCII-only symbols. + * @returns {String} The resulting string of Unicode symbols. + */ + function decode(input) { + // Don't use UCS-2 + var output = [], + inputLength = input.length, + out, + i = 0, + n = initialN, + bias = initialBias, + basic, + j, + index, + oldi, + w, + k, + digit, + t, + /** Cached calculation results */ + baseMinusT; + + // Handle the basic code points: let `basic` be the number of input code + // points before the last delimiter, or `0` if there is none, then copy + // the first basic code points to the output. + + basic = input.lastIndexOf(delimiter); + if (basic < 0) { + basic = 0; + } + + for (j = 0; j < basic; ++j) { + // if it's not a basic code point + if (input.charCodeAt(j) >= 0x80) { + error('not-basic'); + } + output.push(input.charCodeAt(j)); + } + + // Main decoding loop: start just after the last delimiter if any basic code + // points were copied; start at the beginning otherwise. + + for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + + // `index` is the index of the next character to be consumed. + // Decode a generalized variable-length integer into `delta`, + // which gets added to `i`. The overflow checking is easier + // if we increase `i` as we go, then subtract off its starting + // value at the end to obtain `delta`. + for (oldi = i, w = 1, k = base; /* no condition */; k += base) { + + if (index >= inputLength) { + error('invalid-input'); + } + + digit = basicToDigit(input.charCodeAt(index++)); + + if (digit >= base || digit > floor((maxInt - i) / w)) { + error('overflow'); + } + + i += digit * w; + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + + if (digit < t) { + break; + } + + baseMinusT = base - t; + if (w > floor(maxInt / baseMinusT)) { + error('overflow'); + } + + w *= baseMinusT; + + } + + out = output.length + 1; + bias = adapt(i - oldi, out, oldi == 0); + + // `i` was supposed to wrap around from `out` to `0`, + // incrementing `n` each time, so we'll fix that now: + if (floor(i / out) > maxInt - n) { + error('overflow'); + } + + n += floor(i / out); + i %= out; + + // Insert `n` at position `i` of the output + output.splice(i++, 0, n); + + } + + return ucs2encode(output); + } + + /** + * Converts a string of Unicode symbols (e.g. a domain name label) to a + * Punycode string of ASCII-only symbols. + * @memberOf punycode + * @param {String} input The string of Unicode symbols. + * @returns {String} The resulting Punycode string of ASCII-only symbols. + */ + function encode(input) { + var n, + delta, + handledCPCount, + basicLength, + bias, + j, + m, + q, + k, + t, + currentValue, + output = [], + /** `inputLength` will hold the number of code points in `input`. */ + inputLength, + /** Cached calculation results */ + handledCPCountPlusOne, + baseMinusT, + qMinusT; + + // Convert the input in UCS-2 to Unicode + input = ucs2decode(input); + + // Cache the length + inputLength = input.length; + + // Initialize the state + n = initialN; + delta = 0; + bias = initialBias; + + // Handle the basic code points + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue < 0x80) { + output.push(stringFromCharCode(currentValue)); + } + } + + handledCPCount = basicLength = output.length; + + // `handledCPCount` is the number of code points that have been handled; + // `basicLength` is the number of basic code points. + + // Finish the basic string - if it is not empty - with a delimiter + if (basicLength) { + output.push(delimiter); + } + + // Main encoding loop: + while (handledCPCount < inputLength) { + + // All non-basic code points < n have been handled already. Find the next + // larger one: + for (m = maxInt, j = 0; j < inputLength; ++j) { + currentValue = input[j]; + if (currentValue >= n && currentValue < m) { + m = currentValue; + } + } + + // Increase `delta` enough to advance the decoder's state to , + // but guard against overflow + handledCPCountPlusOne = handledCPCount + 1; + if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { + error('overflow'); + } + + delta += (m - n) * handledCPCountPlusOne; + n = m; + + for (j = 0; j < inputLength; ++j) { + currentValue = input[j]; + + if (currentValue < n && ++delta > maxInt) { + error('overflow'); + } + + if (currentValue == n) { + // Represent delta as a generalized variable-length integer + for (q = delta, k = base; /* no condition */; k += base) { + t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); + if (q < t) { + break; + } + qMinusT = q - t; + baseMinusT = base - t; + output.push( + stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) + ); + q = floor(qMinusT / baseMinusT); + } + + output.push(stringFromCharCode(digitToBasic(q, 0))); + bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); + delta = 0; + ++handledCPCount; + } + } + + ++delta; + ++n; + + } + return output.join(''); + } + + /** + * Converts a Punycode string representing a domain name or an email address + * to Unicode. Only the Punycoded parts of the input will be converted, i.e. + * it doesn't matter if you call it on a string that has already been + * converted to Unicode. + * @memberOf punycode + * @param {String} input The Punycoded domain name or email address to + * convert to Unicode. + * @returns {String} The Unicode representation of the given Punycode + * string. + */ + function toUnicode(input) { + return mapDomain(input, function(string) { + return regexPunycode.test(string) + ? decode(string.slice(4).toLowerCase()) + : string; + }); + } + + /** + * Converts a Unicode string representing a domain name or an email address to + * Punycode. Only the non-ASCII parts of the domain name will be converted, + * i.e. it doesn't matter if you call it with a domain that's already in + * ASCII. + * @memberOf punycode + * @param {String} input The domain name or email address to convert, as a + * Unicode string. + * @returns {String} The Punycode representation of the given domain name or + * email address. + */ + function toASCII(input) { + return mapDomain(input, function(string) { + return regexNonASCII.test(string) + ? 'xn--' + encode(string) + : string; + }); + } + + /*--------------------------------------------------------------------------*/ + + /** Define the public API */ + punycode = { + /** + * A string representing the current Punycode.js version number. + * @memberOf punycode + * @type String + */ + 'version': '1.3.1', + /** + * An object of methods to convert from JavaScript's internal character + * representation (UCS-2) to Unicode code points, and back. + * @see + * @memberOf punycode + * @type Object + */ + 'ucs2': { + 'decode': ucs2decode, + 'encode': ucs2encode + }, + 'decode': decode, + 'encode': encode, + 'toASCII': toASCII, + 'toUnicode': toUnicode + }; + + /** Expose `punycode` */ + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define('punycode', function() { + return punycode; + }); + } else if (freeExports && freeModule) { + if (module.exports == freeExports) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = punycode; + } else { // in Narwhal or RingoJS v0.7.0- + for (key in punycode) { + punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); + } + } + } else { // in Rhino or a web browser + root.punycode = punycode; + } + +}(this)); + +var html2canvasNodeAttribute = "data-html2canvas-node"; +var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone"; +var html2canvasCanvasCloneIndex = 0; +var html2canvasCloneIndex = 0; + +window.html2canvas = function(nodeList, options) { + var index = html2canvasCloneIndex++; + options = options || {}; + if (options.logging) { + window.html2canvas.logging = true; + window.html2canvas.start = Date.now(); + } + + options.async = typeof(options.async) === "undefined" ? true : options.async; + options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint; + options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer; + options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled; + options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout; + options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer; + options.strict = !!options.strict; + + if (typeof(nodeList) === "string") { + if (typeof(options.proxy) !== "string") { + return Promise.reject("Proxy must be used when rendering url"); + } + var width = options.width != null ? options.width : window.innerWidth; + var height = options.height != null ? options.height : window.innerHeight; + return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) { + return renderWindow(container.contentWindow.document.documentElement, container, options, width, height); + }); + } + + var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0]; + node.setAttribute(html2canvasNodeAttribute + index, index); + return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) { + if (typeof(options.onrendered) === "function") { + log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas"); + options.onrendered(canvas); + } + return canvas; + }); +}; + +window.html2canvas.punycode = this.punycode; +window.html2canvas.proxy = {}; + +function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) { + return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) { + log("Document cloned"); + var attributeName = html2canvasNodeAttribute + html2canvasIndex; + var selector = "[" + attributeName + "='" + html2canvasIndex + "']"; + document.querySelector(selector).removeAttribute(attributeName); + var clonedWindow = container.contentWindow; + var node = clonedWindow.document.querySelector(selector); + var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true); + return oncloneHandler.then(function() { + return renderWindow(node, container, options, windowWidth, windowHeight); + }); + }); +} + +function renderWindow(node, container, options, windowWidth, windowHeight) { + var clonedWindow = container.contentWindow; + var support = new Support(clonedWindow.document); + var imageLoader = new ImageLoader(options, support); + var bounds = getBounds(node); + var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document); + var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document); + var renderer = new options.renderer(width, height, imageLoader, options, document); + var parser = new NodeParser(node, renderer, support, imageLoader, options); + return parser.ready.then(function() { + log("Finished rendering"); + var canvas; + + if (options.type === "view") { + canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0}); + } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) { + canvas = renderer.canvas; + } else { + canvas = crop(renderer.canvas, {width: options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: clonedWindow.pageXOffset, y: clonedWindow.pageYOffset}); + } + + cleanupContainer(container, options); + return canvas; + }); +} + +function cleanupContainer(container, options) { + if (options.removeContainer) { + container.parentNode.removeChild(container); + log("Cleaned up container"); + } +} + +function crop(canvas, bounds) { + var croppedCanvas = document.createElement("canvas"); + var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left)); + var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width)); + var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top)); + var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height)); + croppedCanvas.width = bounds.width; + croppedCanvas.height = bounds.height; + log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", (x2-x1), "height:", (y2-y1)); + log("Resulting crop with width", bounds.width, "and height", bounds.height, " with x", x1, "and y", y1); + croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, x2-x1, y2-y1, bounds.x, bounds.y, x2-x1, y2-y1); + return croppedCanvas; +} + +function documentWidth (doc) { + return Math.max( + Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), + Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth), + Math.max(doc.body.clientWidth, doc.documentElement.clientWidth) + ); +} + +function documentHeight (doc) { + return Math.max( + Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight), + Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight), + Math.max(doc.body.clientHeight, doc.documentElement.clientHeight) + ); +} + +function smallImage() { + return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; +} + +function isIE9() { + return document.documentMode && document.documentMode <= 9; +} + +// https://github.com/niklasvh/html2canvas/issues/503 +function cloneNodeIE9(node, javascriptEnabled) { + var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); + + var child = node.firstChild; + while(child) { + if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { + clone.appendChild(cloneNodeIE9(child, javascriptEnabled)); + } + child = child.nextSibling; + } + + return clone; +} + +function createWindowClone(ownerDocument, containerDocument, width, height, options, x ,y) { + labelCanvasElements(ownerDocument); + var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true); + var container = containerDocument.createElement("iframe"); + + container.className = "html2canvas-container"; + container.style.visibility = "hidden"; + container.style.position = "fixed"; + container.style.left = "-10000px"; + container.style.top = "0px"; + container.style.border = "0"; + container.width = width; + container.height = height; + container.scrolling = "no"; // ios won't scroll without it + containerDocument.body.appendChild(container); + + return new Promise(function(resolve) { + var documentClone = container.contentWindow.document; + + cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea"); + cloneNodeValues(ownerDocument.documentElement, documentElement, "select"); + + /* Chrome doesn't detect relative background-images assigned in inline '); + + 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 + 2014-02-04 + + 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 ' '), + 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. + +if (typeof JSON !== 'object') { + 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 () { + + 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 () { + return this.valueOf(); + }; + } + + var cx, + escapable, + gap, + indent, + meta, + 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') { + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + 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') { + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + 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'); + }; + } +}()); + + +(function(){ + + /** + * @private + * @param {String} eventName + * @param {Function} handler + */ + function _removeEventListener(eventName, handler) { + if (!this.__eventListeners[eventName]) { + return; + } + + if (handler) { + fabric.util.removeFromArray(this.__eventListeners[eventName], handler); + } + else { + this.__eventListeners[eventName].length = 0; + } + } + + /** + * Observes specified event + * @deprecated `observe` deprecated since 0.8.34 (use `on` instead) + * @memberOf fabric.Observable + * @alias on + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function that receives a notification when an event of the specified type occurs + * @return {Self} thisArg + * @chainable + */ + function observe(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = { }; + } + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } + else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = [ ]; + } + this.__eventListeners[eventName].push(handler); + } + return this; + } + + /** + * Stops event observing for a particular event handler. Calling this method + * without arguments removes all handlers for all events + * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead) + * @memberOf fabric.Observable + * @alias off + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function to be deleted from EventListeners + * @return {Self} thisArg + * @chainable + */ + function stopObserving(eventName, handler) { + if (!this.__eventListeners) { + return; + } + + // remove all key/value pairs (event name -> event handler) + if (arguments.length === 0) { + this.__eventListeners = { }; + } + // one object with key/value pairs was passed + else if (arguments.length === 1 && typeof arguments[0] === 'object') { + for (var prop in eventName) { + _removeEventListener.call(this, prop, eventName[prop]); + } + } + else { + _removeEventListener.call(this, eventName, handler); + } + return this; + } + + /** + * Fires event with an optional options object + * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead) + * @memberOf fabric.Observable + * @alias trigger + * @param {String} eventName Event name to fire + * @param {Object} [options] Options object + * @return {Self} thisArg + * @chainable + */ + function fire(eventName, options) { + if (!this.__eventListeners) { + return; + } + + 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].call(this, options || { }); + } + return this; + } + + /** + * @namespace fabric.Observable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#events} + * @see {@link http://fabricjs.com/events/|Events demo} + */ + fabric.Observable = { + observe: observe, + stopObserving: stopObserving, + fire: fire, + + on: observe, + off: stopObserving, + trigger: fire + }; +})(); + + +/** + * @namespace fabric.Collection + */ +fabric.Collection = { + + /** + * Adds objects to collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * Objects should be instances of (or inherit from) fabric.Object + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + */ + add: function () { + this._objects.push.apply(this._objects, arguments); + for (var i = 0, length = arguments.length; i < length; i++) { + this._onObjectAdded(arguments[i]); + } + this.renderOnAddRemove && this.renderAll(); + return this; + }, + + /** + * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) + * An object should be an instance of (or inherit from) fabric.Object + * @param {Object} object Object to insert + * @param {Number} index Index to insert object at + * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs + * @return {Self} thisArg + * @chainable + */ + insertAt: function (object, index, nonSplicing) { + var objects = this.getObjects(); + if (nonSplicing) { + objects[index] = object; + } + else { + objects.splice(index, 0, object); + } + this._onObjectAdded(object); + this.renderOnAddRemove && this.renderAll(); + return this; + }, + + /** + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + * @chainable + */ + remove: function() { + var objects = this.getObjects(), + index; + + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + objects.splice(index, 1); + this._onObjectRemoved(arguments[i]); + } + } + + this.renderOnAddRemove && this.renderAll(); + return this; + }, + + /** + * Executes given function for each object in this group + * @param {Function} callback + * Callback invoked with current object as first argument, + * index - as second and an array of all objects - as third. + * 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 {Self} thisArg + */ + forEachObject: function(callback, context) { + var objects = this.getObjects(), + i = objects.length; + while (i--) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + + /** + * Returns an array of children objects of this instance + * Type parameter introduced in 1.3.10 + * @param {String} [type] When specified, only objects of this type are returned + * @return {Array} + */ + getObjects: function(type) { + if (typeof type === 'undefined') { + return this._objects; + } + return this._objects.filter(function(o) { + return o.type === type; + }); + }, + + /** + * Returns object at specified index + * @param {Number} index + * @return {Self} thisArg + */ + item: function (index) { + return this.getObjects()[index]; + }, + + /** + * Returns true if collection contains no objects + * @return {Boolean} true if collection is empty + */ + isEmpty: function () { + return this.getObjects().length === 0; + }, + + /** + * Returns a size of a collection (i.e: length of an array containing its objects) + * @return {Number} Collection size + */ + size: function() { + return this.getObjects().length; + }, + + /** + * Returns true if collection contains an object + * @param {Object} object Object to check against + * @return {Boolean} `true` if collection contains an object + */ + contains: function(object) { + return this.getObjects().indexOf(object) > -1; + }, + + /** + * Returns number representation of a collection complexity + * @return {Number} complexity + */ + complexity: function () { + return this.getObjects().reduce(function (memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + } +}; + + +(function(global) { + + var sqrt = Math.sqrt, + atan2 = Math.atan2, + PiBy180 = Math.PI / 180; + + /** + * @namespace fabric.util + */ + 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 + * @param {Array} array + * @param {Any} value + * @return {Array} original array + */ + removeFromArray: function(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); + } + return array; + }, + + /** + * Returns random number between 2 specified ones. + * @static + * @memberOf fabric.util + * @param {Number} min lower limit + * @param {Number} max upper limit + * @return {Number} random value (between min and max) + */ + getRandomInt: function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + /** + * Transforms degrees to radians. + * @static + * @memberOf fabric.util + * @param {Number} degrees value in degrees + * @return {Number} value in radians + */ + degreesToRadians: function(degrees) { + return degrees * PiBy180; + }, + + /** + * Transforms radians to degrees. + * @static + * @memberOf fabric.util + * @param {Number} radians value in radians + * @return {Number} value in degrees + */ + radiansToDegrees: function(radians) { + return radians / PiBy180; + }, + + /** + * Rotates `point` around `origin` with `radians` + * @static + * @memberOf fabric.util + * @param {fabric.Point} point The point to rotate + * @param {fabric.Point} origin The origin of the rotation + * @param {Number} radians The radians of the angle for the rotation + * @return {fabric.Point} The new rotated point + */ + rotatePoint: function(point, origin, radians) { + var sin = Math.sin(radians), + cos = Math.cos(radians); + + point.subtractEquals(origin); + + var rx = point.x * cos - point.y * sin, + ry = point.x * sin + point.y * cos; + + return new fabric.Point(rx, ry).addEquals(origin); + }, + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[1] * p.y, + t[2] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[1] * p.y + t[4], + t[2] * p.x + t[3] * p.y + t[5] + ); + }, + + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + invertTransform: function(t) { + var r = t.slice(), + a = 1 / (t[0] * t[3] - t[1] * t[2]); + r = [a * t[3], -a * t[1], -a * t[2], a * t[0], 0, 0]; + var o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r); + r[4] = -o.x; + r[5] = -o.y; + return r; + }, + + /** + * A wrapper around Number#toFixed, which contrary to native method returns number, not string. + * @static + * @memberOf fabric.util + * @param {Number|String} number number to operate on + * @param {Number} fractionDigits number of fraction digits to "leave" + * @return {Number} + */ + toFixed: function(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + }, + + /** + * Converts from attribute value to pixel value if applicable. + * Returns converted pixels or original value not converted. + * @param {Number|String} value number to operate on + * @return {Number|String} + */ + parseUnit: function(value) { + var unit = /\D{0,2}$/.exec(value), + number = parseFloat(value); + + switch (unit[0]) { + case 'mm': + return number * fabric.DPI / 25.4; + + case 'cm': + return number * fabric.DPI / 2.54; + + case 'in': + return number * fabric.DPI; + + case 'pt': + return number * fabric.DPI / 72; // or * 4 / 3 + + case 'pc': + return number * fabric.DPI / 72 * 12; // or * 16 + + default: + return number; + } + }, + + /** + * Function which always returns `false`. + * @static + * @memberOf fabric.util + * @return {Boolean} + */ + falseFunction: function() { + return false; + }, + + /** + * Returns klass "Class" object of given namespace + * @memberOf fabric.util + * @param {String} type Type of object (eg. 'circle') + * @param {String} namespace Namespace to get klass "Class" object from + * @return {Object} klass "Class" + */ + getKlass: function(type, namespace) { + // capitalize first letter only + type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); + return fabric.util.resolveNamespace(namespace)[type]; + }, + + /** + * Returns object of given namespace + * @memberOf fabric.util + * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' + * @return {Object} Object for given namespace (default fabric) + */ + resolveNamespace: function(namespace) { + if (!namespace) { + return fabric; + } + + var parts = namespace.split('.'), + len = parts.length, + obj = global || fabric.window; + + for (var i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + + return obj; + }, + + /** + * Loads image element from given url and passes it to a callback + * @memberOf fabric.util + * @param {String} url URL representing an image + * @param {Function} callback Callback; invoked with loaded image + * @param {Any} [context] Context to invoke callback in + * @param {Object} [crossOrigin] crossOrigin value to set image element to + */ + loadImage: function(url, callback, context, crossOrigin) { + if (!url) { + callback && callback.call(context, url); + return; + } + + var img = fabric.util.createImage(); + + /** @ignore */ + img.onload = function () { + callback && callback.call(context, img); + img = img.onload = img.onerror = null; + }; + + /** @ignore */ + img.onerror = function() { + fabric.log('Error loading ' + img.src); + callback && callback.call(context, null, true); + img = img.onload = img.onerror = null; + }; + + // data-urls appear to be buggy with crossOrigin + // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 + // see https://code.google.com/p/chromium/issues/detail?id=315152 + // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 + if (url.indexOf('data') !== 0 && typeof crossOrigin !== 'undefined') { + img.crossOrigin = crossOrigin; + } + + img.src = url; + }, + + /** + * Creates corresponding fabric instances from their object representations + * @static + * @memberOf fabric.util + * @param {Array} objects Objects to enliven + * @param {Function} callback Callback to invoke when all objects are created + * @param {String} namespace Namespace to get klass "Class" object from + * @param {Function} reviver Method for further parsing of object elements, + * called after each fabric object created. + */ + enlivenObjects: function(objects, callback, namespace, reviver) { + objects = objects || [ ]; + + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + callback && callback(enlivenedObjects); + } + } + + var enlivenedObjects = [ ], + numLoadedObjects = 0, + numTotalObjects = objects.length; + + if (!numTotalObjects) { + callback && callback(enlivenedObjects); + return; + } + + objects.forEach(function (o, index) { + // if sparse array + if (!o || !o.type) { + onLoaded(); + return; + } + var klass = fabric.util.getKlass(o.type, namespace); + if (klass.async) { + klass.fromObject(o, function (obj, error) { + if (!error) { + enlivenedObjects[index] = obj; + reviver && reviver(o, enlivenedObjects[index]); + } + onLoaded(); + }); + } + else { + enlivenedObjects[index] = klass.fromObject(o); + reviver && reviver(o, enlivenedObjects[index]); + onLoaded(); + } + }); + }, + + /** + * Groups SVG elements (usually those retrieved from SVG document) + * @static + * @memberOf fabric.util + * @param {Array} elements SVG elements to group + * @param {Object} [options] Options object + * @return {fabric.Object|fabric.PathGroup} + */ + groupSVGElements: function(elements, options, path) { + var object; + + object = new fabric.PathGroup(elements, options); + + if (typeof path !== 'undefined') { + object.setSourcePath(path); + } + return object; + }, + + /** + * Populates an object with properties of another object + * @static + * @memberOf fabric.util + * @param {Object} source Source object + * @param {Object} destination Destination object + * @return {Array} properties Propertie names to include + */ + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === '[object Array]') { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } + } + } + }, + + /** + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x start x coordinate + * @param {Number} y start y coordinate + * @param {Number} x2 end x coordinate + * @param {Number} y2 end y coordinate + * @param {Array} da dash array pattern + */ + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, + dy = y2 - y, + len = sqrt(dx * dx + dy * dy), + rot = atan2(dy, dx), + dc = da.length, + di = 0, + draw = true; + + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); + + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; + } + ctx[draw ? 'lineTo' : 'moveTo'](x, 0); + draw = !draw; + } + + ctx.restore(); + }, + + /** + * Creates canvas element and initializes it via excanvas if necessary + * @static + * @memberOf fabric.util + * @param {CanvasElement} [canvasEl] optional canvas element to initialize; + * when not given, element is created implicitly + * @return {CanvasElement} initialized canvas element + */ + createCanvasElement: function(canvasEl) { + canvasEl || (canvasEl = fabric.document.createElement('canvas')); + //jscs:disable requireCamelCaseOrUpperCaseIdentifiers + if (!canvasEl.getContext && typeof G_vmlCanvasManager !== 'undefined') { + G_vmlCanvasManager.initElement(canvasEl); + } + //jscs:enable requireCamelCaseOrUpperCaseIdentifiers + return canvasEl; + }, + + /** + * Creates image element (works on client and node) + * @static + * @memberOf fabric.util + * @return {HTMLImageElement} HTML image element + */ + createImage: function() { + return fabric.isLikelyNode + ? new (require('canvas').Image)() + : fabric.document.createElement('img'); + }, + + /** + * Creates accessors (getXXX, setXXX) for a "class", based on "stateProperties" array + * @static + * @memberOf fabric.util + * @param {Object} klass "Class" to create accessors for + */ + createAccessors: function(klass) { + var proto = klass.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); + } + } + }, + + /** + * @static + * @memberOf fabric.util + * @param {fabric.Object} receiver Object implementing `clipTo` method + * @param {CanvasRenderingContext2D} ctx Context to clip + */ + clipContext: function(receiver, ctx) { + ctx.save(); + ctx.beginPath(); + receiver.clipTo(ctx); + ctx.clip(); + }, + + /** + * Multiply matrix A by matrix B to nest transformations + * @static + * @memberOf fabric.util + * @param {Array} matrixA First transformMatrix + * @param {Array} matrixB Second transformMatrix + * @return {Array} The product of the two transform matrices + */ + multiplyTransformMatrices: function(matrixA, matrixB) { + // Matrix multiply matrixA * matrixB + var a = [ + [matrixA[0], matrixA[2], matrixA[4]], + [matrixA[1], matrixA[3], matrixA[5]], + [0, 0, 1 ] + ], + + b = [ + [matrixB[0], matrixB[2], matrixB[4]], + [matrixB[1], matrixB[3], matrixB[5]], + [0, 0, 1 ] + ], + + result = []; + + for (var r = 0; r < 3; r++) { + result[r] = []; + for (var c = 0; c < 3; c++) { + var sum = 0; + for (var k = 0; k < 3; k++) { + sum += a[r][k] * b[k][c]; + } + + result[r][c] = sum; + } + } + + return [ + result[0][0], + result[1][0], + result[0][1], + result[1][1], + result[0][2], + result[1][2] + ]; + }, + + /** + * Returns string representation of function body + * @param {Function} fn Function to get body of + * @return {String} Function body + */ + getFunctionBody: function(fn) { + return (String(fn).match(/function[^{]*\{([\s\S]*)\}/) || {})[1]; + }, + + /** + * Returns true if context has transparent pixel + * at specified location (taking tolerance into account) + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @param {Number} tolerance Tolerance + */ + isTransparent: function(ctx, x, y, tolerance) { + + // If tolerance is > 0 adjust start coords to take into account. + // If moves off Canvas fix to 0 + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } + else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } + else { + y = 0; + } + } + + var _isTransparent = true, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1); + + // Split image data - for tolerance > 1, pixelDataSize = 4; + for (var i = 3, l = imageData.data.length; i < l; i += 4) { + var temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) { + break; // Stop if colour found + } + } + + imageData = null; + + return _isTransparent; + } + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + var arcToSegmentsCache = { }, + segmentToBezierCache = { }, + _join = Array.prototype.join; + + /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp + * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here + * http://mozilla.org/MPL/2.0/ + */ + function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { + var argsString = _join.call(arguments); + if (arcToSegmentsCache[argsString]) { + return arcToSegmentsCache[argsString]; + } + + var PI = Math.PI, th = rotateX * (PI / 180), + sinTh = Math.sin(th), + cosTh = Math.cos(th), + fromX = 0, fromY = 0; + + rx = Math.abs(rx); + ry = Math.abs(ry); + + var px = -cosTh * toX - sinTh * toY, + py = -cosTh * toY + sinTh * toX, + rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, + pl = 4 * rx2 * ry2 - rx2 * py2 - ry2 * px2, + root = 0; + + if (pl < 0) { + var s = Math.sqrt(1 - 0.25 * pl/(rx2 * ry2)); + rx *= s; + ry *= s; + } + else { + root = (large === sweep ? -0.5 : 0.5) * + Math.sqrt( pl /(rx2 * py2 + ry2 * px2)); + } + + var cx = root * rx * py / ry, + cy = -root * ry * px / rx, + cx1 = cosTh * cx - sinTh * cy + toX / 2, + cy1 = sinTh * cx + cosTh * cy + toY / 2, + mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), + dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); + + if (sweep === 0 && dtheta > 0) { + dtheta -= 2 * PI; + } + else if (sweep === 1 && dtheta < 0) { + dtheta += 2 * PI; + } + + // Convert into cubic bezier segments <= 90deg + var segments = Math.ceil(Math.abs(dtheta / (PI * 0.5))), + result = [], mDelta = dtheta / segments, + mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), + th3 = mTheta + mDelta; + + for (var i = 0; i < segments; i++) { + result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); + fromX = result[i][4]; + fromY = result[i][5]; + mTheta += mDelta; + th3 += mDelta; + } + arcToSegmentsCache[argsString] = result; + return result; + } + + function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { + var argsString2 = _join.call(arguments); + if (segmentToBezierCache[argsString2]) { + return segmentToBezierCache[argsString2]; + } + + var costh2 = Math.cos(th2), + sinth2 = Math.sin(th2), + costh3 = Math.cos(th3), + sinth3 = Math.sin(th3), + toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, + toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, + cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2), + cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2), + cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), + cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); + + segmentToBezierCache[argsString2] = [ + cp1X, cp1Y, + cp2X, cp2Y, + toX, toY + ]; + return segmentToBezierCache[argsString2]; + } + + /* + * Private + */ + function calcVectorAngle(ux, uy, vx, vy) { + var ta = Math.atan2(uy, ux), + tb = Math.atan2(vy, vx); + if (tb >= ta) { + return tb - ta; + } + else { + return 2 * Math.PI - (ta - tb); + } + } + + /** + * Draws arc + * @param {CanvasRenderingContext2D} ctx + * @param {Number} fx + * @param {Number} fy + * @param {Array} coords + */ + fabric.util.drawArc = function(ctx, fx, fy, coords) { + var rx = coords[0], + ry = coords[1], + rot = coords[2], + large = coords[3], + sweep = coords[4], + tx = coords[5], + ty = coords[6], + segs = [[ ], [ ], [ ], [ ]], + segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + + for (var i = 0, len = segsNorm.length; i < len; i++) { + segs[i][0] = segsNorm[i][0] + fx; + segs[i][1] = segsNorm[i][1] + fy; + segs[i][2] = segsNorm[i][2] + fx; + segs[i][3] = segsNorm[i][3] + fy; + segs[i][4] = segsNorm[i][4] + fx; + segs[i][5] = segsNorm[i][5] + fy; + ctx.bezierCurveTo.apply(ctx, segs[i]); + } + }; +})(); + + +(function() { + + var slice = Array.prototype.slice; + + + + /** + * Invokes method on all items in a given array + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} method Name of a method to invoke + * @return {Array} + */ + function invoke(array, method) { + var args = slice.call(arguments, 2), result = [ ]; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + + /** + * Finds maximum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {Any} + */ + function max(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 >= value2; + }); + } + + /** + * Finds minimum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {Any} + */ + function min(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 < value2; + }); + } + + /** + * @private + */ + function find(array, byProperty, condition) { + if (!array || array.length === 0) { + return; + } + + var i = array.length - 1, + result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (condition(array[i][byProperty], result)) { + result = array[i][byProperty]; + } + } + } + else { + while (i--) { + if (condition(array[i], result)) { + result = array[i]; + } + } + } + return result; + } + + /** + * @namespace fabric.util.array + */ + fabric.util.array = { + invoke: invoke, + min: min, + max: max + }; + +})(); + + +(function(){ + + /** + * Copies all enumerable properties of one object to another + * @memberOf fabric.util.object + * @param {Object} destination Where to copy to + * @param {Object} source Where to copy from + * @return {Object} + */ + 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 + * @memberOf fabric.util.object + * @param {Object} object Object to clone + * @return {Object} + */ + function clone(object) { + return extend({ }, object); + } + + /** @namespace fabric.util.object */ + fabric.util.object = { + extend: extend, + clone: clone + }; + +})(); + + +(function() { + + + + /** + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string + */ + function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ''; + }); + } + + /** + * Capitalizes a string + * @memberOf fabric.util.string + * @param {String} string String to capitalize + * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized + * and other letters stay untouched, if false first letter is capitalized + * and other letters are converted to lowercase. + * @return {String} Capitalized version of a string + */ + function capitalize(string, firstLetterOnly) { + return string.charAt(0).toUpperCase() + + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); + } + + /** + * Escapes XML in a string + * @memberOf fabric.util.string + * @param {String} string String to escape + * @return {String} Escaped version of a string + */ + function escapeXml(string) { + return string.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + + /** + * String utilities + * @namespace fabric.util.string + */ + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml + }; +}()); + + + + + +(function() { + + var slice = Array.prototype.slice, emptyFunction = function() { }, + + IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') { + return false; + } + } + return true; + })(), + + /** @ignore */ + addMethods = function(klass, source, parent) { + for (var property in source) { + + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { + + klass.prototype[property] = (function(property) { + return function() { + + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + + if (property !== 'initialize') { + return returnValue; + } + }; + })(property); + } + else { + klass.prototype[property] = source[property]; + } + + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; + } + } + } + }; + + function Subclass() { } + + function callSuper(methodName) { + var fn = this.constructor.superclass.prototype[methodName]; + return (arguments.length > 1) + ? fn.apply(this, slice.call(arguments, 1)) + : fn.call(this); + } + + /** + * Helper for creation of "classes". + * @memberOf fabric.util + * @param {Function} [parent] optional "Class" to inherit from + * @param {Object} [properties] Properties shared by all instances of this class + * (be careful modifying objects defined here as this would affect all instances) + */ + function createClass() { + var parent = null, + properties = slice.call(arguments, 0); + + if (typeof properties[0] === 'function') { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + + klass.superclass = parent; + klass.subclasses = [ ]; + + if (parent) { + Subclass.prototype = parent.prototype; + klass.prototype = new Subclass(); + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + klass.prototype.callSuper = callSuper; + return klass; + } + + fabric.util.createClass = createClass; +})(); + + +(function () { + + var unknown = 'unknown'; + + /* 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; + } + + /** @ignore */ + var getElement, + setElement, + getUniqueId = (function () { + var uid = 0; + return function (element) { + return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); + }; + })(); + + (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 + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.addListener = addListener; + + /** + * Removes an event listener from an element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.removeListener = removeListener; + + /** + * Cross-browser wrapper for getting event's coordinates + * @memberOf fabric.util + * @param {Event} event Event object + * @param {HTMLCanvasElement} upperCanvasEl <canvas> element on which object selection is drawn + */ + function getPointer(event, upperCanvasEl) { + event || (event = fabric.window.event); + + var element = event.target || + (typeof event.srcElement !== unknown ? event.srcElement : null), + + scroll = fabric.util.getScrollLeftTop(element, upperCanvasEl); + + return { + x: pointerX(event) + scroll.left, + y: pointerY(event) + scroll.top + }; + } + + var pointerX = function(event) { + // 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 (typeof event.clientX !== unknown ? event.clientX : 0); + }, + + pointerY = function(event) { + return (typeof event.clientY !== unknown ? event.clientY : 0); + }; + + function _getPointer(event, pageProp, clientProp) { + var touchProp = event.type === 'touchend' ? 'changedTouches' : 'touches'; + + return (event[touchProp] && event[touchProp][0] + ? (event[touchProp][0][pageProp] - (event[touchProp][0][pageProp] - event[touchProp][0][clientProp])) + || event[clientProp] + : event[clientProp]); + } + + if (fabric.isTouchSupported) { + pointerX = function(event) { + return _getPointer(event, 'pageX', 'clientX'); + }; + pointerY = function(event) { + return _getPointer(event, 'pageY', 'clientY'); + }; + } + + fabric.util.getPointer = getPointer; + + fabric.util.object.extend(fabric.util, fabric.Observable); + +})(); + + +(function () { + + /** + * Cross-browser wrapper for setting element's style + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {Object} styles + * @return {HTMLElement} Element that was passed as a first argument + */ + function setStyle(element, styles) { + var elementStyle = element.style; + if (!elementStyle) { + return element; + } + if (typeof styles === 'string') { + element.style.cssText += ';' + styles; + return styles.indexOf('opacity') > -1 + ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) + : element; + } + for (var property in styles) { + if (property === 'opacity') { + setOpacity(element, styles[property]); + } + else { + var normalizedProperty = (property === 'float' || property === 'cssFloat') + ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') + : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + + var parseEl = fabric.document.createElement('div'), + supportsOpacity = typeof parseEl.style.opacity === 'string', + supportsFilters = typeof parseEl.style.filter === 'string', + reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, + + /** @ignore */ + setOpacity = function (element) { return element; }; + + if (supportsOpacity) { + /** @ignore */ + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } + else if (supportsFilters) { + /** @ignore */ + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); + es.filter = es.filter.replace(reOpacity, value); + } + else { + es.filter += ' alpha(opacity=' + (value * 100) + ')'; + } + return element; + }; + } + + fabric.util.setStyle = setStyle; + +})(); + + +(function() { + + var _slice = Array.prototype.slice; + + /** + * Takes id and returns an element with that id (if one exists in a document) + * @memberOf fabric.util + * @param {String|HTMLElement} id + * @return {HTMLElement|null} + */ + function getById(id) { + return typeof id === 'string' ? fabric.document.getElementById(id) : id; + } + + var sliceCanConvertNodelists, + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; + + try { + sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } + catch (err) { } + + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + /** + * Creates specified element with specified attributes + * @memberOf fabric.util + * @param {String} tagName Type of an element to create + * @param {Object} [attributes] Attributes to set on an element + * @return {HTMLElement} Newly created element + */ + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === 'class') { + el.className = attributes[prop]; + } + else if (prop === 'for') { + el.htmlFor = attributes[prop]; + } + else { + el.setAttribute(prop, attributes[prop]); + } + } + return el; + } + + /** + * Adds class to an element + * @memberOf fabric.util + * @param {HTMLElement} element Element to add class to + * @param {String} className Class to add to an element + */ + function addClass(element, className) { + if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + element.className += (element.className ? ' ' : '') + className; + } + } + + /** + * Wraps element with another element + * @memberOf fabric.util + * @param {HTMLElement} element Element to wrap + * @param {HTMLElement|String} wrapper Element to wrap with + * @param {Object} [attributes] Attributes to set on a wrapper + * @return {HTMLElement} wrapper + */ + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === 'string') { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + + /** + * Returns element scroll offsets + * @memberOf fabric.util + * @param {HTMLElement} element Element to operate on + * @param {HTMLElement} upperCanvasEl Upper canvas element + * @return {Object} Object with left/top values + */ + function getScrollLeftTop(element, upperCanvasEl) { + + var firstFixedAncestor, + origElement, + left = 0, + top = 0, + docElement = fabric.document.documentElement, + body = fabric.document.body || { + scrollLeft: 0, scrollTop: 0 + }; + + origElement = element; + + while (element && element.parentNode && !firstFixedAncestor) { + + element = element.parentNode; + + if (element !== fabric.document && + fabric.util.getElementStyle(element, 'position') === 'fixed') { + firstFixedAncestor = element; + } + + if (element !== fabric.document && + origElement !== upperCanvasEl && + fabric.util.getElementStyle(element, 'position') === 'absolute') { + left = 0; + top = 0; + } + else if (element === fabric.document) { + left = body.scrollLeft || docElement.scrollLeft || 0; + top = body.scrollTop || docElement.scrollTop || 0; + } + else { + left += element.scrollLeft || 0; + top += element.scrollTop || 0; + } + } + + return { left: left, top: top }; + } + + /** + * Returns offset for a given element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element Element to get offset for + * @return {Object} Object with "left" and "top" properties + */ + function getElementOffset(element) { + var docElem, + doc = element && element.ownerDocument, + box = { left: 0, top: 0 }, + offset = { left: 0, top: 0 }, + scrollLeftTop, + offsetAttributes = { + borderLeftWidth: 'left', + borderTopWidth: 'top', + paddingLeft: 'left', + paddingTop: 'top' + }; + + if (!doc) { + return { left: 0, top: 0 }; + } + + for (var attr in offsetAttributes) { + offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; + } + + docElem = doc.documentElement; + if ( typeof element.getBoundingClientRect !== 'undefined' ) { + box = element.getBoundingClientRect(); + } + + scrollLeftTop = fabric.util.getScrollLeftTop(element, null); + + return { + left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, + top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top + }; + } + + /** + * Returns style attribute value of a given element + * @memberOf fabric.util + * @param {HTMLElement} element Element to get style attribute for + * @param {String} attr Style attribute to get for element + * @return {String} Style attribute value of the given element. + */ + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { + return fabric.document.defaultView.getComputedStyle(element, null)[attr]; + }; + } + else { + getElementStyle = function(element, attr) { + var value = element.style[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } + return value; + }; + } + + (function () { + var style = fabric.document.documentElement.style, + selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; + + /** + * Makes element unselectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make unselectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = 'none'; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = 'on'; + } + return element; + } + + /** + * Makes element selectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make selectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementSelectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ''; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = ''; + } + return element; + } + + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + + (function() { + + /** + * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading + * @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; + + /** @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.getScrollLeftTop = getScrollLeftTop; + fabric.util.getElementOffset = getElementOffset; + fabric.util.getElementStyle = getElementStyle; + +})(); + + +(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 + * @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() { }, + xhr = makeXHR(), + body; + + /** @ignore */ + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + onComplete(xhr); + xhr.onreadystatechange = emptyFn; + } + }; + + if (method === 'GET') { + body = null; + if (typeof options.parameters === 'string') { + url = addParamToUrl(url, options.parameters); + } + } + + xhr.open(method, url, true); + + if (method === 'POST' || method === 'PUT') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + xhr.send(body); + return xhr; + } + + fabric.util.request = request; +})(); + + +/** + * Wrapper around `console.log` (when available) + * @param {Any} [values] Values to log + */ +fabric.log = function() { }; + +/** + * Wrapper around `console.warn` (when available) + * @param {Any} [values] Values to log as a warning + */ +fabric.warn = function() { }; + +if (typeof console !== 'undefined') { + ['log', 'warn'].forEach(function(methodName) { + if (typeof console[methodName] !== 'undefined' && console[methodName].apply) { + fabric[methodName] = function() { + return console[methodName].apply(console, arguments); + }; + } + }); +} + + +(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, + toFixed = fabric.util.toFixed, + parseUnit = fabric.util.parseUnit, + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, + + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'originX' + }, + + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }; + + function normalizeAttr(attr) { + // transform attribute names + if (attr in attributesMap) { + return attributesMap[attr]; + } + return attr; + } + + function normalizeValue(attr, value, parentAttributes) { + var isArray = Object.prototype.toString.call(value) === '[object Array]', + parsed; + + if ((attr === 'fill' || attr === 'stroke') && value === 'none') { + value = ''; + } + else if (attr === 'fillRule') { + value = (value === 'evenodd') ? 'destination-over' : value; + } + else if (attr === 'strokeDashArray') { + value = value.replace(/,/g, ' ').split(/\s+/).map(function(n) { + return parseInt(n); + }); + } + else if (attr === 'transformMatrix') { + if (parentAttributes && parentAttributes.transformMatrix) { + value = multiplyTransformMatrices( + parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); + } + else { + value = fabric.parseTransformAttribute(value); + } + } + else if (attr === 'visible') { + value = (value === 'none' || value === 'hidden') ? false : true; + // display=none on parent element always takes precedence over child element + if (parentAttributes && parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'originX' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } + else { + parsed = isArray ? value.map(parseUnit) : parseUnit(value); + } + + return (!isArray && isNaN(parsed) ? value : parsed); + } + + /** + * @private + * @param {Object} attributes Array of attributes to parse + */ + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + + if (!attributes[attr] || typeof attributes[colorAttributes[attr]] === 'undefined') { + continue; + } + + if (attributes[attr].indexOf('url(') === 0) { + continue; + } + + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + } + return attributes; + } + + /** + * Parses "transform" attribute, returning an array of values + * @static + * @function + * @memberOf fabric + * @param {String} attributeValue String containing attribute value + * @return {Array} Array of 6 elements representing transformation matrix + */ + fabric.parseTransformAttribute = (function() { + function rotateMatrix(matrix, args) { + var 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*\\.\\d+)(?:e[-+]?\\d+)?)', + + commaWsp = '(?:\\s+,?\\s*|,\\s*)', + + skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', + + skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', + + rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', + + scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + matrix = '(?:(matrix)\\s*\\(\\s*' + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + + '\\s*\\))', + + transform = '(?:' + + matrix + '|' + + translate + '|' + + scale + '|' + + rotate + '|' + + skewX + '|' + + skewY + + ')', + + transforms = '(?:' + transform + '(?:' + commaWsp + transform + ')*' + ')', + + transformList = '^\\s*(?:' + transforms + '?)\\s*$', + + // http://www.w3.org/TR/SVG/coords.html#TransformAttribute + reTransformList = new RegExp(transformList), + // == end transform regexp + + reTransform = new RegExp(transform, 'g'); + + return function(attributeValue) { + + // start with identity matrix + var matrix = iMatrix.concat(), + matrices = [ ]; + + // return if no argument was given or + // an argument does not match transform attribute regexp + if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { + return matrix; + } + + attributeValue.replace(reTransform, function(match) { + + var m = new RegExp(transform).exec(match).filter(function (match) { + return (match !== '' && match != null); + }), + operation = m[1], + args = m.slice(2).map(parseFloat); + + switch (operation) { + case 'translate': + translateMatrix(matrix, args); + break; + case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); + rotateMatrix(matrix, args); + break; + case 'scale': + scaleMatrix(matrix, args); + break; + case 'skewX': + skewXMatrix(matrix, args); + break; + case 'skewY': + skewYMatrix(matrix, args); + break; + case 'matrix': + matrix = args; + break; + } + + // snapshot current matrix into matrices array + matrices.push(matrix.concat()); + // reset + matrix = iMatrix.concat(); + }); + + var combinedMatrix = matrices[0]; + while (matrices.length > 1) { + matrices.shift(); + combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); + } + return combinedMatrix; + }; + })(); + + function parseFontDeclaration(value, oStyle) { + + // TODO: support non-px font size + var match = value.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/); + + if (!match) { + return; + } + + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; + + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseFloat(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; + } + } + + /** + * @private + */ + function parseStyleString(style, oStyle) { + var attr, value; + style.replace(/;$/, '').split(';').forEach(function (chunk) { + var pair = chunk.split(':'); + + attr = normalizeAttr(pair[0].trim().toLowerCase()); + value = normalizeValue(attr, pair[1].trim()); + + if (attr === 'font') { + parseFontDeclaration(value, oStyle); + } + else { + oStyle[attr] = value; + } + }); + } + + /** + * @private + */ + function parseStyleObject(style, oStyle) { + var attr, value; + for (var prop in style) { + if (typeof style[prop] === 'undefined') { + continue; + } + + attr = normalizeAttr(prop.toLowerCase()); + value = normalizeValue(attr, style[prop]); + + if (attr === 'font') { + parseFontDeclaration(value, oStyle); + } + else { + oStyle[attr] = value; + } + } + } + + /** + * @private + */ + function getGlobalStylesForElement(element) { + var styles = { }; + + for (var rule in fabric.cssRules) { + if (elementMatchesRule(element, rule.split(' '))) { + for (var property in fabric.cssRules[rule]) { + styles[property] = fabric.cssRules[rule][property]; + } + } + } + return styles; + } + + /** + * @private + */ + function elementMatchesRule(element, selectors) { + var firstMatching, parentMatching = true; + //start from rightmost selector. + firstMatching = selectorMatches(element, selectors.pop()); + if (firstMatching && selectors.length) { + parentMatching = doesSomeParentMatch(element, selectors); + } + return firstMatching && parentMatching && (selectors.length === 0); + } + + function doesSomeParentMatch(element, selectors) { + var selector, parentMatching = true; + while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { + if (parentMatching) { + selector = selectors.pop(); + } + element = element.parentNode; + parentMatching = selectorMatches(element, selector); + } + return selectors.length === 0; + } + /** + * @private + */ + function selectorMatches(element, selector) { + var nodeName = element.nodeName, + classNames = element.getAttribute('class'), + id = element.getAttribute('id'), matcher; + // i check if a selector matches slicing away part from it. + // if i get empty string i should match + matcher = new RegExp('^' + nodeName, 'i'); + selector = selector.replace(matcher, ''); + if (id && selector.length) { + matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + if (classNames && selector.length) { + classNames = classNames.split(' '); + for (var i = classNames.length; i--;) { + matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + } + return selector.length === 0; + } + + /** + * @private + */ + function parseUseDirectives(doc) { + var nodelist = doc.getElementsByTagName('use'); + while (nodelist.length) { + var el = nodelist[0], + xlink = el.getAttribute('xlink:href').substr(1), + x = el.getAttribute('x') || 0, + y = el.getAttribute('y') || 0, + el2 = doc.getElementById(xlink).cloneNode(true), + currentTrans = (el.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', + parentNode; + + for (var j = 0, attrs = el.attributes, l = attrs.length; j < l; j++) { + var attr = attrs.item(j); + if (attr.nodeName === 'x' || attr.nodeName === 'y' || attr.nodeName === 'xlink:href') { + continue; + } + + if (attr.nodeName === 'transform') { + currentTrans = currentTrans + ' ' + attr.nodeValue; + } + else { + el2.setAttribute(attr.nodeName, attr.nodeValue); + } + } + + el2.setAttribute('transform', currentTrans); + el2.removeAttribute('id'); + parentNode = el.parentNode; + parentNode.replaceChild(el2, el); + } + } + + /** + * Add a element that envelop all SCG elements and makes the viewbox transformMatrix descend on all elements + */ + function addSvgTransform(doc, matrix) { + matrix[3] = matrix[0] = (matrix[0] > matrix[3] ? matrix[3] : matrix[0]); + if (!(matrix[0] !== 1 || matrix[3] !== 1 || matrix[4] !== 0 || matrix[5] !== 0)) { + return; + } + // default is to preserve aspect ratio + // preserveAspectRatio attribute to be implemented + var el = doc.ownerDocument.createElement('g'); + while (doc.firstChild != null) { + el.appendChild(doc.firstChild); + } + el.setAttribute('transform','matrix(' + matrix[0] + ' ' + matrix[1] + ' ' + matrix[2] + ' ' + matrix[3] + ' ' + matrix[4] + ' ' + matrix[5] + ')'); + doc.appendChild(el); + } + + /** + * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @param {Function} callback Callback to call when parsing is finished; It's being passed an array of elements (parsed from a document). + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + 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. + reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)', + + 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(); + + parseUseDirectives(doc); + /* http://www.w3.org/TR/SVG/struct.html#SVGElementWidthAttribute + * as per spec, width and height attributes are to be considered + * 100% if no value is specified. + */ + var viewBoxAttr = doc.getAttribute('viewBox'), + widthAttr = parseUnit(doc.getAttribute('width') || '100%'), + heightAttr = parseUnit(doc.getAttribute('height') || '100%'), + viewBoxWidth, + viewBoxHeight; + + if (viewBoxAttr && (viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))) { + var minX = parseFloat(viewBoxAttr[1]), + minY = parseFloat(viewBoxAttr[2]), + scaleX = 1, scaleY = 1; + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); + if (widthAttr && widthAttr !== viewBoxWidth ) { + scaleX = widthAttr / viewBoxWidth; + } + if (heightAttr && heightAttr !== viewBoxHeight) { + scaleY = heightAttr / viewBoxHeight; + } + addSvgTransform(doc, [scaleX, 0, 0, scaleY, scaleX * -minX, scaleY * -minY]); + } + + var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); + + if (descendants.length === 0 && fabric.isLikelyNode) { + // we're likely in node, where "o3-xml" library fails to gEBTN("*") + // https://github.com/ajaxorg/node-o3-xml/issues/21 + descendants = doc.selectNodes('//*[name(.)!="svg"]'); + var arr = [ ]; + for (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)) { + callback && callback([], {}); + return; + } + + var options = { + width: widthAttr ? widthAttr : viewBoxWidth, + height: heightAttr ? heightAttr : viewBoxHeight, + widthAttr: widthAttr, + heightAttr: heightAttr + }; + + fabric.gradientDefs = fabric.getGradientDefs(doc); + fabric.cssRules = fabric.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`) + * @namespace + */ + var svgCache = { + + /** + * @param {String} name + * @param {Function} callback + */ + has: function (name, callback) { + callback(false); + }, + + get: function () { + /* NOOP */ + }, + + set: function () { + /* NOOP */ + } + }; + + /** + * @private + */ + 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 }); + } + + /** + * @private + */ + function _createSVGPattern(markup, canvas, property) { + if (canvas[property] && canvas[property].toSVG) { + markup.push( + '', + '' + ); + } + } + + extend(fabric, { + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element + */ + getGradientDefs: function(doc) { + var linearGradientEls = doc.getElementsByTagName('linearGradient'), + radialGradientEls = doc.getElementsByTagName('radialGradient'), + el, i, j = 0, id, xlink, elList = [ ], + gradientDefs = { }, idsToXlinkMap = { }; + + elList.length = linearGradientEls.length + radialGradientEls.length; + i = linearGradientEls.length; + while (i--) { + elList[j++] = linearGradientEls[i]; + } + i = radialGradientEls.length; + while (i--) { + elList[j++] = radialGradientEls[i]; + } + + while (j--) { + el = elList[j]; + xlink = el.getAttribute('xlink:href'); + id = el.getAttribute('id'); + if (xlink) { + idsToXlinkMap[id] = xlink.substr(1); + } + gradientDefs[id] = el; + } + + for (id in idsToXlinkMap) { + var el2 = gradientDefs[idsToXlinkMap[id]].cloneNode(true); + el = gradientDefs[id]; + while (el2.firstChild) { + el.appendChild(el2.firstChild); + } + } + return gradientDefs; + }, + + /** + * Returns an object of attributes' name/value, given element and an array of attribute names; + * Parses parent "g" nodes recursively upwards. + * @static + * @memberOf fabric + * @param {DOMElement} element Element to parse + * @param {Array} attributes Array of attributes to parse + * @return {Object} object containing parsed attributes' names/values + */ + parseAttributes: function(element, attributes) { + + if (!element) { + return; + } + + var value, + parentAttributes = { }; + + // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards + if (element.parentNode && /^symbol|[g|a]$/i.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes); + } + + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + if (value) { + attr = normalizeAttr(attr); + value = normalizeValue(attr, value, parentAttributes); + + memo[attr] = value; + } + return memo; + }, { }); + + // add values parsed from style, which take precedence over attributes + // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) + ownAttributes = extend(ownAttributes, + extend(getGlobalStylesForElement(element), fabric.parseStyleAttribute(element))); + + return _setStrokeFillOpacity(extend(parentAttributes, ownAttributes)); + }, + + /** + * Transforms an array of svg elements to corresponding fabric.* instances + * @static + * @memberOf fabric + * @param {Array} elements Array of elements to parse + * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) + * @param {Object} [options] Options object + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + parseElements: function(elements, callback, options, reviver) { + new fabric.ElementsParser(elements, callback, options, reviver).parse(); + }, + + /** + * Parses "style" attribute, retuning an object with values + * @static + * @memberOf fabric + * @param {SVGElement} element Element to parse + * @return {Object} Objects with values parsed from style attribute of an element + */ + parseStyleAttribute: function(element) { + var oStyle = { }, + style = element.getAttribute('style'); + + if (!style) { + return oStyle; + } + + if (typeof style === 'string') { + parseStyleString(style, oStyle); + } + else { + parseStyleObject(style, oStyle); + } + + return oStyle; + }, + + /** + * Parses "points" attribute, returning an array of values + * @static + * @memberOf fabric + * @param {String} points points attribute string + * @return {Array} array of points + */ + parsePointsAttribute: function(points) { + + // points attribute is required and must not be empty + if (!points) { + return null; + } + + // replace commas with whitespace and remove bookending whitespace + points = points.replace(/,/g, ' ').trim(); + + points = points.split(/\s+/); + var parsedPoints = [ ], i, len; + + i = 0; + len = points.length; + for (; i < len; i+=2) { + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); + } + + // odd number of points is an error + // if (parsedPoints.length % 2 !== 0) { + // return null; + // } + + return parsedPoints; + }, + + /** + * Returns CSS rules for a given SVG document + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} CSS rules of this document + */ + getCSSRules: function(doc) { + var styles = doc.getElementsByTagName('style'), + 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*\{([^}]*)\}/), + ruleObj = { }, declaration = match[2].trim(), + propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); + + for (var i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), + property = normalizeAttr(pair[0]), + value = normalizeValue(property,pair[1],pair[0]); + ruleObj[property] = value; + } + rule = match[1]; + rule.split(',').forEach(function(_rule) { + allRules[_rule.trim()] = fabric.util.object.clone(ruleObj); + }); + }); + } + return allRules; + }, + + /** + * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) + * @memberof fabric + * @param {String} url + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + loadSVGFromURL: function(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 && !xml.documentElement && fabric.window.ActiveXObject && r.responseText) { + xml = new ActiveXObject('Microsoft.XMLDOM'); + xml.async = 'false'; + //IE chokes on DOCTYPE + xml.loadXML(r.responseText.replace(//i,'')); + } + if (!xml || !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); + } + }, + + /** + * Takes string corresponding to an SVG document, and parses it into a set of fabric objects + * @memberof fabric + * @param {String} string + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + loadSVGFromString: function(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) { + doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.async = 'false'; + //IE chokes on DOCTYPE + doc.loadXML(string.replace(//i,'')); + } + + fabric.parseSVGDocument(doc.documentElement, function (results, options) { + callback(results, options); + }, reviver); + }, + + /** + * Creates markup containing SVG font faces + * @param {Array} objects Array of fabric objects + * @return {String} + */ + createSVGFontFacesMarkup: function(objects) { + var markup = ''; + + for (var i = 0, len = objects.length; i < len; i++) { + if (objects[i].type !== 'text' || !objects[i].path) { + continue; + } + + markup += [ + //jscs:disable validateIndentation + '@font-face {', + 'font-family: ', objects[i].fontFamily, '; ', + 'src: url(\'', objects[i].path, '\')', + '}' + //jscs:enable validateIndentation + ].join(''); + } + + if (markup) { + markup = [ + //jscs:disable validateIndentation + '' + //jscs:enable validateIndentation + ].join(''); + } + + return markup; + }, + + /** + * Creates markup containing SVG referenced elements like patterns, gradients etc. + * @param {fabric.Canvas} canvas instance of fabric.Canvas + * @return {String} + */ + createSVGRefElementsMarkup: function(canvas) { + var markup = [ ]; + + _createSVGPattern(markup, canvas, 'backgroundColor'); + _createSVGPattern(markup, canvas, 'overlayColor'); + + return markup.join(''); + } + }); + +})(typeof exports !== 'undefined' ? exports : this); + + +fabric.ElementsParser = function(elements, callback, options, reviver) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; +}; + +fabric.ElementsParser.prototype.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; + + this.createObjects(); +}; + +fabric.ElementsParser.prototype.createObjects = function() { + for (var i = 0, len = this.elements.length; i < len; i++) { + (function(_this, i) { + setTimeout(function() { + _this.createObject(_this.elements[i], i); + }, 0); + })(this, i); + } +}; + +fabric.ElementsParser.prototype.createObject = function(el, index) { + var klass = fabric[fabric.util.string.capitalize(el.tagName)]; + if (klass && klass.fromElement) { + try { + this._createObject(klass, el, index); + } + catch (err) { + fabric.log(err); + } + } + else { + this.checkIfDone(); + } +}; + +fabric.ElementsParser.prototype._createObject = function(klass, el, index) { + if (klass.async) { + klass.fromElement(el, this.createCallback(index, el), this.options); + } + else { + var obj = klass.fromElement(el, this.options); + this.resolveGradient(obj, 'fill'); + this.resolveGradient(obj, 'stroke'); + this.reviver && this.reviver(el, obj); + this.instances[index] = obj; + this.checkIfDone(); + } +}; + +fabric.ElementsParser.prototype.createCallback = function(index, el) { + var _this = this; + return function(obj) { + _this.resolveGradient(obj, 'fill'); + _this.resolveGradient(obj, 'stroke'); + _this.reviver && _this.reviver(el, obj); + _this.instances[index] = obj; + _this.checkIfDone(); + }; +}; + +fabric.ElementsParser.prototype.resolveGradient = function(obj, property) { + + var instanceFillValue = obj.get(property); + if (!(/^url\(/).test(instanceFillValue)) { + return; + } + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + if (fabric.gradientDefs[gradientId]) { + obj.set(property, + fabric.Gradient.fromElement(fabric.gradientDefs[gradientId], obj)); + } +}; + +fabric.ElementsParser.prototype.checkIfDone = function() { + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + return el != null; + }); + this.callback(this.instances); + } +}; + + +(function(global) { + + 'use strict'; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Point) { + fabric.warn('fabric.Point is already defined'); + return; + } + + fabric.Point = Point; + + /** + * Point class + * @class fabric.Point + * @memberOf fabric + * @constructor + * @param {Number} x + * @param {Number} y + * @return {fabric.Point} thisArg + */ + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype = /** @lends fabric.Point.prototype */ { + + constructor: Point, + + /** + * Adds another point to this one and returns another one + * @param {fabric.Point} that + * @return {fabric.Point} new Point instance with added values + */ + add: function (that) { + return new Point(this.x + that.x, this.y + that.y); + }, + + /** + * Adds another point to this one + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + */ + addEquals: function (that) { + this.x += that.x; + this.y += that.y; + return this; + }, + + /** + * Adds value to this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} new Point with added value + */ + scalarAdd: function (scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + + /** + * Adds value to this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + */ + scalarAddEquals: function (scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + + /** + * Subtracts another point from this point and returns a new one + * @param {fabric.Point} that + * @return {fabric.Point} new Point object with subtracted values + */ + subtract: function (that) { + return new Point(this.x - that.x, this.y - that.y); + }, + + /** + * Subtracts another point from this point + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + */ + subtractEquals: function (that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + + /** + * Subtracts value from this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} + */ + scalarSubtract: function (scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + + /** + * Subtracts value from this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + */ + scalarSubtractEquals: function (scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + + /** + * Miltiplies this point by a value and returns a new one + * @param {Number} scalar + * @return {fabric.Point} + */ + multiply: function (scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + + /** + * Miltiplies this point by a value + * @param {Number} scalar + * @return {fabric.Point} thisArg + */ + multiplyEquals: function (scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + + /** + * Divides this point by a value and returns a new one + * @param {Number} scalar + * @return {fabric.Point} + */ + divide: function (scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + + /** + * Divides this point by a value + * @param {Number} scalar + * @return {fabric.Point} thisArg + */ + divideEquals: function (scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + + /** + * Returns true if this point is equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + eq: function (that) { + return (this.x === that.x && this.y === that.y); + }, + + /** + * Returns true if this point is less than another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lt: function (that) { + return (this.x < that.x && this.y < that.y); + }, + + /** + * Returns true if this point is less than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lte: function (that) { + return (this.x <= that.x && this.y <= that.y); + }, + + /** + + * Returns true if this point is greater another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gt: function (that) { + return (this.x > that.x && this.y > that.y); + }, + + /** + * Returns true if this point is greater than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gte: function (that) { + return (this.x >= that.x && this.y >= that.y); + }, + + /** + * Returns new point which is the result of linear interpolation with this one and another one + * @param {fabric.Point} that + * @param {Number} t + * @return {fabric.Point} + */ + lerp: function (that, t) { + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + + /** + * Returns distance from this point and another one + * @param {fabric.Point} that + * @return {Number} + */ + distanceFrom: function (that) { + var dx = this.x - that.x, + dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * Returns the point between this point and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + midPointFrom: function (that) { + return new Point(this.x + (that.x - this.x)/2, this.y + (that.y - this.y)/2); + }, + + /** + * Returns a new point which is the min of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + min: function (that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + + /** + * Returns a new point which is the max of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + max: function (that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + + /** + * Returns string representation of this point + * @return {String} + */ + toString: function () { + return this.x + ',' + this.y; + }, + + /** + * Sets x/y of this point + * @param {Number} x + * @return {Number} y + */ + setXY: function (x, y) { + this.x = x; + this.y = y; + }, + + /** + * Sets x/y of this point from another point + * @param {fabric.Point} that + */ + setFromPoint: function (that) { + this.x = that.x; + this.y = that.y; + }, + + /** + * Swaps x/y of this point and another point + * @param {fabric.Point} that + */ + swap: function (that) { + var x = this.x, + y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; + } + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Intersection) { + fabric.warn('fabric.Intersection is already defined'); + return; + } + + /** + * Intersection class + * @class fabric.Intersection + * @memberOf fabric + * @constructor + */ + function Intersection(status) { + this.status = status; + this.points = []; + } + + fabric.Intersection = Intersection; + + fabric.Intersection.prototype = /** @lends fabric.Intersection.prototype */ { + + /** + * Appends a point to intersection + * @param {fabric.Point} point + */ + appendPoint: function (point) { + this.points.push(point); + }, + + /** + * Appends points to intersection + * @param {Array} points + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + } + }; + + /** + * Checks if one line intersects another + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {fabric.Point} b1 + * @param {fabric.Point} b2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection('Intersection'); + result.points.push(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection(); + } + } + else { + if (uaT === 0 || ubT === 0) { + result = new Intersection('Coincident'); + } + else { + result = new Intersection('Parallel'); + } + } + return result; + }; + + /** + * Checks if line intersects polygon + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {Array} points + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLinePolygon = function(a1,a2,points){ + var result = new Intersection(), + length = points.length; + + 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; + }; + + /** + * Checks if polygon intersects another polygon + * @static + * @param {Array} points1 + * @param {Array} points2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection(), + length = points1.length; + + 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; + }; + + /** + * Checks if polygon intersects rectangle + * @static + * @param {Array} points + * @param {Number} r1 + * @param {Number} r2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection(); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + +})(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; + } + + /** + * Color class + * The purpose of {@link fabric.Color} is to abstract and encapsulate common color operations; + * {@link fabric.Color} is a constructor and creates instances of {@link fabric.Color} objects. + * + * @class fabric.Color + * @param {String} color optional in hex or rgb(a) format + * @return {fabric.Color} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} + */ + function Color(color) { + if (!color) { + this.setSource([0, 0, 0, 1]); + } + else { + this._tryParsingColor(color); + } + } + + fabric.Color = Color; + + fabric.Color.prototype = /** @lends fabric.Color.prototype */ { + + /** + * @private + * @param {String|Array} color Color value to parse + */ + _tryParsingColor: function(color) { + var source; + + if (color in Color.colorNameMap) { + color = Color.colorNameMap[color]; + } + + if (color === 'transparent') { + this.setSource([255,255,255,0]); + return; + } + + source = Color.sourceFromHex(color); + + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (source) { + this.setSource(source); + } + }, + + /** + * Adapted from https://github.com/mjijackson + * @private + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @return {Array} Hsl color + */ + _rgbToHsl: function(r, g, b) { + r /= 255, g /= 255, b /= 255; + + var h, s, l, + max = fabric.util.array.max([r, g, b]), + min = fabric.util.array.min([r, g, b]); + + l = (max + min) / 2; + + if (max === min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [ + Math.round(h * 360), + Math.round(s * 100), + Math.round(l * 100) + ]; + }, + + /** + * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @return {Array} + */ + getSource: function() { + return this._source; + }, + + /** + * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @param {Array} source + */ + setSource: function(source) { + this._source = source; + }, + + /** + * Returns color represenation in RGB format + * @return {String} ex: rgb(0-255,0-255,0-255) + */ + toRgb: function() { + var source = this.getSource(); + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + }, + + /** + * Returns color represenation in RGBA format + * @return {String} ex: rgba(0-255,0-255,0-255,0-1) + */ + toRgba: function() { + var source = this.getSource(); + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + }, + + /** + * Returns color represenation in HSL format + * @return {String} ex: hsl(0-360,0%-100%,0%-100%) + */ + toHsl: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + }, + + /** + * Returns color represenation in HSLA format + * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) + */ + toHsla: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + }, + + /** + * Returns color represenation in HEX format + * @return {String} ex: FF5555 + */ + toHex: function() { + var source = this.getSource(), r, g, b; + + r = source[0].toString(16); + r = (r.length === 1) ? ('0' + r) : r; + + g = source[1].toString(16); + g = (g.length === 1) ? ('0' + g) : g; + + b = source[2].toString(16); + b = (b.length === 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + + /** + * Gets value of alpha channel for this color + * @return {Number} 0-1 + */ + getAlpha: function() { + return this.getSource()[3]; + }, + + /** + * Sets value of alpha channel for this color + * @param {Number} alpha Alpha value 0-1 + * @return {fabric.Color} thisArg + */ + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + + /** + * Transforms color to its grayscale representation + * @return {fabric.Color} thisArg + */ + toGrayscale: function() { + var source = this.getSource(), + average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), + currentAlpha = source[3]; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Transforms color to its black and white representation + * @param {Number} threshold + * @return {fabric.Color} thisArg + */ + toBlackWhite: function(threshold) { + var source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + currentAlpha = source[3]; + + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Overlays color with another color + * @param {String|fabric.Color} otherColor + * @return {fabric.Color} thisArg + */ + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + + var result = [], + alpha = this.getAlpha(), + otherAlpha = 0.5, + source = this.getSource(), + otherSource = otherColor.getSource(); + + 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), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + + /** + * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/; + + /** + * Regex matching color in HEX format (ex: #FF5555, 010155, aff) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHex = /^#?([0-9a-f]{6}|[0-9a-f]{3})$/i; + + /** + * Map of the 17 basic color names with HEX code + * @static + * @field + * @memberOf fabric.Color + * @see: http://www.w3.org/TR/CSS2/syndata.html#color-units + */ + fabric.Color.colorNameMap = { + aqua: '#00FFFF', + black: '#000000', + blue: '#0000FF', + fuchsia: '#FF00FF', + gray: '#808080', + green: '#008000', + lime: '#00FF00', + maroon: '#800000', + navy: '#000080', + olive: '#808000', + orange: '#FFA500', + purple: '#800080', + red: '#FF0000', + silver: '#C0C0C0', + teal: '#008080', + white: '#FFFFFF', + yellow: '#FFFF00' + }; + + /** + * @private + * @param {Number} p + * @param {Number} q + * @param {Number} t + * @return {Number} + */ + function hue2rgb(p, q, t){ + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1/6) { + return p + (q - p) * 6 * t; + } + if (t < 1/2) { + return q; + } + if (t < 2/3) { + return p + (q - p) * (2/3 - t) * 6; + } + return p; + } + + /** + * Returns new color object, when given a color in RGB format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255) + * @return {fabric.Color} + */ + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) + * @return {Array} source + */ + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), + g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), + b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + + return [ + parseInt(r, 10), + parseInt(g, 10), + parseInt(b, 10), + match[4] ? parseFloat(match[4]) : 1 + ]; + } + }; + + /** + * Returns new color object, when given a color in RGBA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromRgba = Color.fromRgb; + + /** + * Returns new color object, when given a color in HSL format + * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) + * @memberOf fabric.Color + * @return {fabric.Color} + */ + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. + * Adapted from https://github.com/mjijackson + * @memberOf fabric.Color + * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) + * @return {Array} source + * @see http://http://www.w3.org/TR/css3-color/#hsl-color + */ + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) { + return; + } + + var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; + + if (s === 0) { + r = g = b = l; + } + else { + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; + + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + match[4] ? parseFloat(match[4]) : 1 + ]; + }; + + /** + * Returns new color object, when given a color in HSLA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromHsla = Color.fromHsl; + + /** + * Returns new color object, when given a color in HEX format + * @static + * @memberOf fabric.Color + * @param {String} color Color value ex: FF5555 + * @return {fabric.Color} + */ + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + + /** + * Returns array represenatation (ex: [100, 100, 200, 1]) of a color that's in HEX format + * @static + * @memberOf fabric.Color + * @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 + * @memberOf fabric.Color + * @param {Array} source + * @return {fabric.Color} + */ + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + /* _FROM_SVG_START_ */ + function getColorStop(el) { + var style = el.getAttribute('style'), + offset = el.getAttribute('offset'), + color, colorAlpha, opacity; + + // convert percents to absolute values + offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); + offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; + if (style) { + var keyValuePairs = style.split(/\s*;\s*/); + + if (keyValuePairs[keyValuePairs.length - 1] === '') { + keyValuePairs.pop(); + } + + for (var i = keyValuePairs.length; i--; ) { + + var split = keyValuePairs[i].split(/\s*:\s*/), + key = split[0].trim(), + value = split[1].trim(); + + if (key === 'stop-color') { + color = value; + } + else if (key === 'stop-opacity') { + opacity = value; + } + } + } + + if (!color) { + color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; + } + if (!opacity) { + opacity = el.getAttribute('stop-opacity'); + } + + color = new fabric.Color(color); + colorAlpha = color.getAlpha(); + opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); + opacity *= colorAlpha; + + return { + offset: offset, + color: color.toRgb(), + opacity: opacity + }; + } + + function getLinearCoords(el) { + return { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0 + }; + } + + function getRadialCoords(el) { + return { + x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', + y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', + r1: 0, + x2: el.getAttribute('cx') || '50%', + y2: el.getAttribute('cy') || '50%', + r2: el.getAttribute('r') || '50%' + }; + } + /* _FROM_SVG_END_ */ + + /** + * Gradient class + * @class fabric.Gradient + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#gradients} + * @see {@link fabric.Gradient#initialize} for constructor definition + */ + fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { + + /** + * Horizontal offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetX: 0, + + /** + * Vertical offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetY: 0, + + /** + * Constructor + * @param {Object} [options] Options object with type, coords, gradientUnits and colorStops + * @return {fabric.Gradient} thisArg + */ + initialize: function(options) { + options || (options = { }); + + var coords = { }; + + this.id = fabric.Object.__uid++; + this.type = options.type || 'linear'; + + coords = { + x1: options.coords.x1 || 0, + y1: options.coords.y1 || 0, + x2: options.coords.x2 || 0, + y2: options.coords.y2 || 0 + }; + + if (this.type === 'radial') { + coords.r1 = options.coords.r1 || 0; + coords.r2 = options.coords.r2 || 0; + } + this.coords = coords; + this.colorStops = options.colorStops.slice(); + if (options.gradientTransform) { + this.gradientTransform = options.gradientTransform; + } + this.offsetX = options.offsetX || this.offsetX; + this.offsetY = options.offsetY || this.offsetY; + }, + + /** + * Adds another colorStop + * @param {Object} colorStop Object with offset and color + * @return {fabric.Gradient} thisArg + */ + addColorStop: function(colorStop) { + for (var position in colorStop) { + var color = new fabric.Color(colorStop[position]); + this.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + return this; + }, + + /** + * Returns object representation of a gradient + * @return {Object} + */ + toObject: function() { + return { + type: this.type, + coords: this.coords, + colorStops: this.colorStops, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an gradient + * @param {Object} object Object to create a gradient for + * @param {Boolean} normalize Whether coords should be normalized + * @return {String} SVG representation of an gradient (linear/radial) + */ + toSVG: function(object) { + var coords = fabric.util.object.clone(this.coords), + markup, commonAttributes; + + // colorStops must be sorted ascending + this.colorStops.sort(function(a, b) { + return a.offset - b.offset; + }); + + if (!(object.group && object.group.type === 'path-group')) { + for (var prop in coords) { + if (prop === 'x1' || prop === 'x2' || prop === 'r2') { + coords[prop] += this.offsetX - object.width / 2; + } + else if (prop === 'y1' || prop === 'y2') { + coords[prop] += this.offsetY - object.height / 2; + } + } + } + + commonAttributes = 'id="SVGID_' + this.id + + '" gradientUnits="userSpaceOnUse"'; + if (this.gradientTransform) { + commonAttributes += ' gradientTransform="matrix(' + this.gradientTransform.join(' ') + ')" '; + } + if (this.type === 'linear') { + markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + } + else if (this.type === 'radial') { + markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + } + + for (var i = 0; i < this.colorStops.length; i++) { + markup.push( + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ); + } + + markup.push((this.type === 'linear' ? '\n' : '\n')); + + return markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns an instance of CanvasGradient + * @param {CanvasRenderingContext2D} ctx Context to render on + * @return {CanvasGradient} + */ + toLive: function(ctx) { + var gradient; + + if (!this.type) { + return; + } + + if (this.type === 'linear') { + gradient = ctx.createLinearGradient( + this.coords.x1, this.coords.y1, this.coords.x2, this.coords.y2); + } + else if (this.type === 'radial') { + gradient = ctx.createRadialGradient( + this.coords.x1, this.coords.y1, this.coords.r1, this.coords.x2, this.coords.y2, this.coords.r2); + } + + for (var i = 0, len = this.colorStops.length; i < len; i++) { + var color = this.colorStops[i].color, + opacity = this.colorStops[i].opacity, + offset = this.colorStops[i].offset; + + if (typeof opacity !== 'undefined') { + color = new fabric.Color(color).setAlpha(opacity).toRgba(); + } + gradient.addColorStop(parseFloat(offset), color); + } + + return gradient; + } + }); + + fabric.util.object.extend(fabric.Gradient, { + + /* _FROM_SVG_START_ */ + /** + * Returns {@link fabric.Gradient} instance from an SVG element + * @static + * @memberof fabric.Gradient + * @param {SVGGradientElement} el SVG gradient element + * @param {fabric.Object} instance + * @return {fabric.Gradient} Gradient instance + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement + */ + fromElement: function(el, instance) { + + /** + * @example: + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + */ + + var colorStopEls = el.getElementsByTagName('stop'), + type = (el.nodeName === 'linearGradient' ? 'linear' : 'radial'), + gradientUnits = el.getAttribute('gradientUnits') || 'objectBoundingBox', + gradientTransform = el.getAttribute('gradientTransform'), + colorStops = [], + coords = { }, ellipseMatrix; + + if (type === 'linear') { + coords = getLinearCoords(el); + } + else if (type === 'radial') { + coords = getRadialCoords(el); + } + + for (var i = colorStopEls.length; i--; ) { + colorStops.push(getColorStop(colorStopEls[i])); + } + + ellipseMatrix = _convertPercentUnitsToValues(instance, coords, gradientUnits); + + var gradient = new fabric.Gradient({ + type: type, + coords: coords, + colorStops: colorStops, + offsetX: -instance.left, + offsetY: -instance.top + }); + + if (gradientTransform || ellipseMatrix !== '') { + gradient.gradientTransform = fabric.parseTransformAttribute((gradientTransform || '') + ellipseMatrix); + } + return gradient; + }, + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Gradient} instance from its object representation + * @static + * @memberof fabric.Gradient + * @param {Object} obj + * @param {Object} [options] Options object + */ + forObject: function(obj, options) { + options || (options = { }); + _convertPercentUnitsToValues(obj, options.coords, 'userSpaceOnUse'); + return new fabric.Gradient(options); + } + }); + + /** + * @private + */ + function _convertPercentUnitsToValues(object, options, gradientUnits) { + var propValue, addFactor = 0, multFactor = 1, ellipseMatrix = ''; + for (var prop in options) { + propValue = parseFloat(options[prop], 10); + if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { + multFactor = 0.01; + } + else { + multFactor = 1; + } + if (prop === 'x1' || prop === 'x2' || prop === 'r2') { + multFactor *= gradientUnits === 'objectBoundingBox' ? object.width : 1; + addFactor = gradientUnits === 'objectBoundingBox' ? object.left || 0 : 0; + } + else if (prop === 'y1' || prop === 'y2') { + multFactor *= gradientUnits === 'objectBoundingBox' ? object.height : 1; + addFactor = gradientUnits === 'objectBoundingBox' ? object.top || 0 : 0; + } + options[prop] = propValue * multFactor + addFactor; + } + if (object.type === 'ellipse' && options.r2 !== null && gradientUnits === 'objectBoundingBox' && object.rx !== object.ry) { + var scaleFactor = object.ry/object.rx; + ellipseMatrix = ' scale(1, ' + scaleFactor + ')'; + if (options.y1) { + options.y1 /= scaleFactor; + } + if (options.y2) { + options.y2 /= scaleFactor; + } + } + return ellipseMatrix; + } +})(); + + +/** + * Pattern class + * @class fabric.Pattern + * @see {@link http://fabricjs.com/patterns/|Pattern demo} + * @see {@link http://fabricjs.com/dynamic-patterns/|DynamicPattern demo} + * @see {@link fabric.Pattern#initialize} for constructor definition + */ +fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { + + /** + * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) + * @type String + * @default + */ + repeat: 'repeat', + + /** + * Pattern horizontal offset from object's left/top corner + * @type Number + * @default + */ + offsetX: 0, + + /** + * Pattern vertical offset from object's left/top corner + * @type Number + * @default + */ + offsetY: 0, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Pattern} thisArg + */ + initialize: function(options) { + options || (options = { }); + + this.id = fabric.Object.__uid++; + + if (options.source) { + if (typeof options.source === 'string') { + // function string + if (typeof fabric.util.getFunctionBody(options.source) !== 'undefined') { + this.source = new Function(fabric.util.getFunctionBody(options.source)); + } + else { + // img src string + var _this = this; + this.source = fabric.util.createImage(); + fabric.util.loadImage(options.source, function(img) { + _this.source = img; + }); + } + } + else { + // img element + this.source = options.source; + } + } + if (options.repeat) { + this.repeat = options.repeat; + } + if (options.offsetX) { + this.offsetX = options.offsetX; + } + if (options.offsetY) { + this.offsetY = options.offsetY; + } + }, + + /** + * Returns object representation of a pattern + * @return {Object} Object representation of a pattern instance + */ + toObject: function() { + + var source; + + // callback + if (typeof this.source === 'function') { + source = String(this.source); + } + // element + else if (typeof this.source.src === 'string') { + source = this.source.src; + } + + return { + source: source, + repeat: this.repeat, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a pattern + * @param {fabric.Object} object + * @return {String} SVG representation of a pattern + */ + toSVG: function(object) { + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.getWidth(), + patternHeight = patternSource.height / object.getHeight(), + patternImgSrc = ''; + + if (patternSource.src) { + patternImgSrc = patternSource.src; + } + else if (patternSource.toDataURL) { + patternImgSrc = patternSource.toDataURL(); + } + + return '' + + '' + + ''; + }, + /* _TO_SVG_END_ */ + + /** + * Returns an instance of CanvasPattern + * @param {CanvasRenderingContext2D} ctx Context to create pattern + * @return {CanvasPattern} + */ + toLive: function(ctx) { + var source = typeof this.source === 'function' + ? this.source() + : this.source; + + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + + // if an image + if (typeof source.src !== 'undefined') { + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } + } + return ctx.createPattern(source, this.repeat); + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Shadow) { + fabric.warn('fabric.Shadow is already defined.'); + return; + } + + /** + * Shadow class + * @class fabric.Shadow + * @see {@link http://fabricjs.com/shadows/|Shadow demo} + * @see {@link fabric.Shadow#initialize} for constructor definition + */ + fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { + + /** + * Shadow color + * @type String + * @default + */ + color: 'rgb(0,0,0)', + + /** + * Shadow blur + * @type Number + */ + blur: 0, + + /** + * Shadow horizontal offset + * @type Number + * @default + */ + offsetX: 0, + + /** + * Shadow vertical offset + * @type Number + * @default + */ + offsetY: 0, + + /** + * Whether the shadow should affect stroke operations + * @type Boolean + * @default + */ + affectStroke: false, + + /** + * Indicates whether toObject should include default values + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Constructor + * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetX properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px, "2px 2px 10px rgba(0,0,0,0.2)") + * @return {fabric.Shadow} thisArg + */ + initialize: function(options) { + + if (typeof options === 'string') { + options = this._parseShadow(options); + } + + for (var prop in options) { + this[prop] = options[prop]; + } + + this.id = fabric.Object.__uid++; + }, + + /** + * @private + * @param {String} shadow Shadow value to parse + * @return {Object} Shadow object with color, offsetX, offsetY and blur + */ + _parseShadow: function(shadow) { + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [ ], + color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; + + return { + color: color.trim(), + offsetX: parseInt(offsetsAndBlur[1], 10) || 0, + offsetY: parseInt(offsetsAndBlur[2], 10) || 0, + blur: parseInt(offsetsAndBlur[3], 10) || 0 + }; + }, + + /** + * Returns a string representation of an instance + * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow + * @return {String} Returns CSS3 text-shadow declaration + */ + toString: function() { + return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a shadow + * @param {fabric.Object} object + * @return {String} SVG representation of a shadow + */ + toSVG: function(object) { + var mode = 'SourceAlpha'; + + if (object && (object.fill === this.color || object.stroke === this.color)) { + mode = 'SourceGraphic'; + } + + return ( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns object representation of a shadow + * @return {Object} Object representation of a shadow instance + */ + toObject: function() { + if (this.includeDefaultValues) { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY + }; + } + var obj = { }, proto = fabric.Shadow.prototype; + if (this.color !== proto.color) { + obj.color = this.color; + } + if (this.blur !== proto.blur) { + obj.blur = this.blur; + } + if (this.offsetX !== proto.offsetX) { + obj.offsetX = this.offsetX; + } + if (this.offsetY !== proto.offsetY) { + obj.offsetY = this.offsetY; + } + return obj; + } + }); + + /** + * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") + * @static + * @field + * @memberOf fabric.Shadow + */ + fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function () { + + 'use strict'; + + if (fabric.StaticCanvas) { + fabric.warn('fabric.StaticCanvas is already defined.'); + return; + } + + // aliases for faster resolution + var extend = fabric.util.object.extend, + getElementOffset = fabric.util.getElementOffset, + removeFromArray = fabric.util.removeFromArray, + + CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); + + /** + * Static canvas class + * @class fabric.StaticCanvas + * @mixes fabric.Collection + * @mixes fabric.Observable + * @see {@link http://fabricjs.com/static_canvas/|StaticCanvas demo} + * @see {@link fabric.StaticCanvas#initialize} for constructor definition + * @fires before:render + * @fires after:render + * @fires canvas:cleared + * @fires object:added + * @fires object:removed + */ + fabric.StaticCanvas = fabric.util.createClass(/** @lends fabric.StaticCanvas.prototype */ { + + /** + * Constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(el, options) { + options || (options = { }); + + this._initStatic(el, options); + fabric.StaticCanvas.activeInstance = this; + }, + + /** + * Background color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. + * @type {(String|fabric.Pattern)} + * @default + */ + backgroundColor: '', + + /** + * Background image of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setBackgroundImage}. + * Backwards incompatibility note: The "backgroundImageOpacity" + * and "backgroundImageStretch" properties are deprecated since 1.3.9. + * Use {@link fabric.Image#opacity}, {@link fabric.Image#width} and {@link fabric.Image#height}. + * @type fabric.Image + * @default + */ + backgroundImage: null, + + /** + * Overlay color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setOverlayColor} + * @since 1.3.9 + * @type {(String|fabric.Pattern)} + * @default + */ + overlayColor: '', + + /** + * Overlay image of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setOverlayImage}. + * Backwards incompatibility note: The "overlayImageLeft" + * and "overlayImageTop" properties are deprecated since 1.3.9. + * Use {@link fabric.Image#left} and {@link fabric.Image#top}. + * @type fabric.Image + * @default + */ + overlayImage: null, + + /** + * Indicates whether toObject/toDatalessObject should include default values + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Indicates whether objects' state should be saved + * @type Boolean + * @default + */ + stateful: true, + + /** + * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove} should also re-render canvas. + * Disabling this option could give a great performance boost when adding/removing a lot of objects to/from canvas at once + * (followed by a manual rendering after addition/deletion) + * @type Boolean + * @default + */ + renderOnAddRemove: true, + + /** + * Function that determines clipping of entire canvas area + * Being passed context as first argument. See clipping canvas area in {@link https://github.com/kangax/fabric.js/wiki/FAQ} + * @type Function + * @default + */ + clipTo: null, + + /** + * Indicates whether object controls (borders/controls) are rendered above overlay image + * @type Boolean + * @default + */ + controlsAboveOverlay: false, + + /** + * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas + * @type Boolean + * @default + */ + allowTouchScrolling: false, + + /** + * Indicates whether this canvas will use image smoothing, this is on by default in browsers + * @type Boolean + * @default + */ + imageSmoothingEnabled: true, + + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: [1, 0, 0, 1, 0, 0], + + /** + * Callback; invoked right before object is about to be scaled/rotated + */ + onBeforeScaleRotate: function () { + /* NOOP */ + }, + + /** + * @private + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + */ + _initStatic: function(el, options) { + this._objects = []; + + this._createLowerCanvas(el); + this._initOptions(options); + this._setImageSmoothing(); + + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, this.renderAll.bind(this)); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this)); + } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, this.renderAll.bind(this)); + } + if (options.overlayColor) { + this.setOverlayColor(options.overlayColor, 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 + * @return {fabric.Canvas} instance + * @chainable + */ + calcOffset: function () { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to + * @param {Function} callback callback to invoke when image is loaded and set as an overlay + * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} + * @example Normal overlayImage with left/top = 0 + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example overlayImage with different properties + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched overlayImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched overlayImage #2 - width/height correspond to canvas width/height + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + */ + setOverlayImage: function (image, callback, options) { + return this.__setBgOverlayImage('overlayImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to + * @param {Function} callback Callback to invoke when image is loaded and set as background + * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/YH9yD/|jsFiddle demo} + * @example Normal backgroundImage with left/top = 0 + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example backgroundImage with different properties + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + */ + setBackgroundImage: function (image, callback, options) { + return this.__setBgOverlayImage('backgroundImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayColor|background color} for this canvas + * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set background color to + * @param {Function} callback Callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} + * @example Normal overlayColor - color value + * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor with repeat and offset + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setOverlayColor: function(overlayColor, callback) { + return this.__setBgOverlayColor('overlayColor', overlayColor, callback); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas + * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to + * @param {Function} callback Callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} + * @example Normal backgroundColor - color value + * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor with repeat and offset + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setBackgroundColor: function(backgroundColor, callback) { + return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); + }, + + /** + * @private + * @see {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-imagesmoothingenabled|WhatWG Canvas Standard} + */ + _setImageSmoothing: function(){ + var ctx = this.getContext(); + + ctx.imageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.webkitImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.mozImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.msImageSmoothingEnabled = this.imageSmoothingEnabled; + ctx.oImageSmoothingEnabled = this.imageSmoothingEnabled; + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} + * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) + * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to + * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay + * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. + */ + __setBgOverlayImage: function(property, image, callback, options) { + if (typeof image === 'string') { + fabric.util.loadImage(image, function(img) { + this[property] = new fabric.Image(img, options); + callback && callback(); + }, this); + } + else { + this[property] = image; + callback && callback(); + } + + return this; + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} + * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) + * @param {(Object|String|null)} color Object with pattern information, color value or null + * @param {Function} [callback] Callback is invoked when color is set + */ + __setBgOverlayColor: function(property, color, callback) { + if (color && color.source) { + var _this = this; + fabric.util.loadImage(color.source, function(img) { + _this[property] = new fabric.Pattern({ + source: img, + repeat: color.repeat, + offsetX: color.offsetX, + offsetY: color.offsetY + }); + callback && callback(); + }); + } + else { + this[property] = color; + callback && callback(); + } + + return this; + }, + + /** + * @private + */ + _createCanvasElement: function() { + var element = fabric.document.createElement('canvas'); + if (!element.style) { + element.style = { }; + } + if (!element) { + throw CANVAS_INIT_ERROR; + } + this._initCanvasElement(element); + return element; + }, + + /** + * @private + * @param {HTMLElement} element + */ + _initCanvasElement: function(element) { + fabric.util.createCanvasElement(element); + + if (typeof element.getContext === 'undefined') { + throw CANVAS_INIT_ERROR; + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initOptions: function (options) { + for (var prop in options) { + this[prop] = options[prop]; + } + + this.width = this.width || parseInt(this.lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(this.lowerCanvasEl.height, 10) || 0; + + if (!this.lowerCanvasEl.style) { + return; + } + + this.lowerCanvasEl.width = this.width; + this.lowerCanvasEl.height = this.height; + + this.lowerCanvasEl.style.width = this.width + 'px'; + this.lowerCanvasEl.style.height = this.height + 'px'; + + this.viewportTransform = this.viewportTransform.slice(); + }, + + /** + * Creates a bottom canvas + * @private + * @param {HTMLElement} [canvasEl] + */ + _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 (in px) + * @return {Number} + */ + getWidth: function () { + return this.width; + }, + + /** + * Returns canvas height (in px) + * @return {Number} + */ + getHeight: function () { + return this.height; + }, + + /** + * Sets width of this canvas instance + * @param {Number|String} value Value to set width to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setWidth: function (value, options) { + return this.setDimensions({ width: value }, options); + }, + + /** + * Sets height of this canvas instance + * @param {Number|String} value Value to set height to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setHeight: function (value, options) { + return this.setDimensions({ height: value }, options); + }, + + /** + * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) + * @param {Object} dimensions Object with width/height properties + * @param {Number|String} [dimensions.width] Width of canvas element + * @param {Number|String} [dimensions.height] Height of canvas element + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} thisArg + * @chainable + */ + setDimensions: function (dimensions, options) { + var cssValue; + + options = options || {}; + + for (var prop in dimensions) { + cssValue = dimensions[prop]; + + if (!options.cssOnly) { + this._setBackstoreDimension(prop, dimensions[prop]); + cssValue += 'px'; + } + + if (!options.backstoreOnly) { + this._setCssDimension(prop, cssValue); + } + } + + if (!options.cssOnly) { + this.renderAll(); + } + + this.calcOffset(); + + return this; + }, + + /** + * Helper for setting width/height + * @private + * @param {String} prop property (width|height) + * @param {Number} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setBackstoreDimension: function (prop, value) { + this.lowerCanvasEl[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + } + + if (this.cacheCanvasEl) { + this.cacheCanvasEl[prop] = value; + } + + this[prop] = value; + + return this; + }, + + /** + * Helper for setting css width/height + * @private + * @param {String} prop property (width|height) + * @param {String} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setCssDimension: function (prop, value) { + this.lowerCanvasEl.style[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl.style[prop] = value; + } + + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value; + } + + return this; + }, + + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return Math.sqrt(this.viewportTransform[0] * this.viewportTransform[3]); + }, + + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + this.viewportTransform = vpt; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Sets zoom level of this canvas instance, zoom centered around point + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = point; + point = fabric.util.transformPoint(point, fabric.util.invertTransform(this.viewportTransform)); + this.viewportTransform[0] = value; + this.viewportTransform[3] = value; + var after = fabric.util.transformPoint(point, this.viewportTransform); + this.viewportTransform[4] += before.x - after.x; + this.viewportTransform[5] += before.y - after.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + this.zoomToPoint(new fabric.Point(0, 0), value); + return this; + }, + + /** + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to + * @return {fabric.Canvas} instance + * @chainable true + */ + absolutePan: function (point) { + this.viewportTransform[4] = -point.x; + this.viewportTransform[5] = -point.y; + this.renderAll(); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].setCoords(); + } + return this; + }, + + /** + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by + * @return {fabric.Canvas} instance + * @chainable true + */ + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); + }, + + /** + * Returns <canvas> element corresponding to this instance + * @return {HTMLCanvasElement} + */ + getElement: function () { + return this.lowerCanvasEl; + }, + + /** + * Returns currently selected object, if any + * @return {fabric.Object} + */ + getActiveObject: function() { + return null; + }, + + /** + * Returns currently selected group of object, if any + * @return {fabric.Group} + */ + getActiveGroup: function() { + return null; + }, + + /** + * Given a context, renders an object on that context + * @param {CanvasRenderingContext2D} ctx Context to render object on + * @param {fabric.Object} object Object to render + * @private + */ + _draw: function (ctx, object) { + if (!object) { + return; + } + + ctx.save(); + var v = this.viewportTransform; + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + object.render(ctx); + ctx.restore(); + if (!this.controlsAboveOverlay) { + object._renderControls(ctx); + } + }, + + /** + * @private + * @param {fabric.Object} obj Object that was added + */ + _onObjectAdded: function(obj) { + this.stateful && obj.setupState(); + obj.canvas = this; + obj.setCoords(); + this.fire('object:added', { target: obj }); + obj.fire('added'); + }, + + /** + * @private + * @param {fabric.Object} obj Object that was removed + */ + _onObjectRemoved: function(obj) { + // removing active object should fire "selection:cleared" events + if (this.getActiveObject() === obj) { + this.fire('before:selection:cleared', { target: obj }); + this._discardActiveObject(); + this.fire('selection:cleared'); + } + + this.fire('object:removed', { target: obj }); + obj.fire('removed'); + }, + + /** + * Clears specified context of canvas element + * @param {CanvasRenderingContext2D} ctx Context to clear + * @return {fabric.Canvas} thisArg + * @chainable + */ + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + + /** + * Returns context of canvas where objects are drawn + * @return {CanvasRenderingContext2D} + */ + getContext: function () { + return this.contextContainer; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + this._objects.length = 0; + if (this.discardActiveGroup) { + this.discardActiveGroup(); + } + if (this.discardActiveObject) { + this.discardActiveObject(); + } + this.clearContext(this.contextContainer); + if (this.contextTop) { + this.clearContext(this.contextTop); + } + this.fire('canvas:cleared'); + this.renderAll(); + return this; + }, + + /** + * Renders both the top canvas and the secondary container canvas. + * @param {Boolean} [allOnTop] 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'], + activeGroup = this.getActiveGroup(); + + if (this.contextTop && this.selection && !this._groupSelector) { + this.clearContext(this.contextTop); + } + + if (!allOnTop) { + this.clearContext(canvasToDrawOn); + } + + this.fire('before:render'); + + if (this.clipTo) { + fabric.util.clipContext(this, canvasToDrawOn); + } + + this._renderBackground(canvasToDrawOn); + this._renderObjects(canvasToDrawOn, activeGroup); + this._renderActiveGroup(canvasToDrawOn, activeGroup); + + if (this.clipTo) { + canvasToDrawOn.restore(); + } + + this._renderOverlay(canvasToDrawOn); + + if (this.controlsAboveOverlay && this.interactive) { + this.drawControls(canvasToDrawOn); + } + + this.fire('after:render'); + + return this; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {fabric.Group} activeGroup + */ + _renderObjects: function(ctx, activeGroup) { + var i, length; + + // fast path + if (!activeGroup) { + for (i = 0, length = this._objects.length; i < length; ++i) { + this._draw(ctx, this._objects[i]); + } + } + else { + for (i = 0, length = this._objects.length; i < length; ++i) { + if (this._objects[i] && !activeGroup.contains(this._objects[i])) { + this._draw(ctx, this._objects[i]); + } + } + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {fabric.Group} activeGroup + */ + _renderActiveGroup: function(ctx, activeGroup) { + + // delegate rendering to group selection (if one exists) + if (activeGroup) { + + //Store objects in group preserving order, then replace + var sortedObjects = []; + this.forEachObject(function (object) { + if (activeGroup.contains(object)) { + sortedObjects.push(object); + } + }); + activeGroup._set('objects', sortedObjects); + this._draw(ctx, activeGroup); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderBackground: function(ctx) { + if (this.backgroundColor) { + ctx.fillStyle = this.backgroundColor.toLive + ? this.backgroundColor.toLive(ctx) + : this.backgroundColor; + + ctx.fillRect( + this.backgroundColor.offsetX || 0, + this.backgroundColor.offsetY || 0, + this.width, + this.height); + } + if (this.backgroundImage) { + this._draw(ctx, this.backgroundImage); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderOverlay: function(ctx) { + if (this.overlayColor) { + ctx.fillStyle = this.overlayColor.toLive + ? this.overlayColor.toLive(ctx) + : this.overlayColor; + + ctx.fillRect( + this.overlayColor.offsetX || 0, + this.overlayColor.offsetY || 0, + this.width, + this.height); + } + if (this.overlayImage) { + this._draw(ctx, this.overlayImage); + } + }, + + /** + * Method to render only the top canvas. + * Also used to render the group selection box. + * @return {fabric.Canvas} thisArg + * @chainable + */ + renderTop: function () { + var ctx = this.contextTop || this.contextContainer; + this.clearContext(ctx); + + // 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/controls + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + activeGroup.render(ctx); + } + + this._renderOverlay(ctx); + + this.fire('after:render'); + + return this; + }, + + /** + * Returns coordinates of a center of canvas. + * Returned value is an object with top and left properties + * @return {Object} object with "top" and "left" number values + */ + getCenter: function () { + return { + top: this.getHeight() / 2, + left: this.getWidth() / 2 + }; + }, + + /** + * Centers object horizontally. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @param {fabric.Object} object Object to center horizontally + * @return {fabric.Canvas} thisArg + */ + centerObjectH: function (object) { + this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); + this.renderAll(); + return this; + }, + + /** + * Centers object vertically. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @param {fabric.Object} object Object to center vertically + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObjectV: function (object) { + this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); + this.renderAll(); + return this; + }, + + /** + * Centers object vertically and horizontally. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObject: function(object) { + var center = this.getCenter(); + + this._centerObject(object, new fabric.Point(center.left, center.top)); + this.renderAll(); + return this; + }, + + /** + * @private + * @param {fabric.Object} object Object to center + * @param {fabric.Point} center Center point + * @return {fabric.Canvas} thisArg + * @chainable + */ + _centerObject: function(object, center) { + object.setPositionByOrigin(center, 'center', 'center'); + return this; + }, + + /** + * Returs dataless JSON representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {String} json string + */ + toDatalessJSON: function (propertiesToInclude) { + return this.toDatalessObject(propertiesToInclude); + }, + + /** + * Returns object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function (propertiesToInclude) { + return this._toObjectMethod('toObject', propertiesToInclude); + }, + + /** + * Returns dataless object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function (propertiesToInclude) { + return this._toObjectMethod('toDatalessObject', propertiesToInclude); + }, + + /** + * @private + */ + _toObjectMethod: function (methodName, propertiesToInclude) { + + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this.discardActiveGroup(); + } + + var data = { + objects: this._toObjects(methodName, propertiesToInclude) + }; + + extend(data, this.__serializeBgOverlay()); + + fabric.util.populateWithProperties(this, data, propertiesToInclude); + + if (activeGroup) { + this.setActiveGroup(new fabric.Group(activeGroup.getObjects(), { + originX: 'center', + originY: 'center' + })); + activeGroup.forEachObject(function(o) { + o.set('active', true); + }); + + if (this._currentTransform) { + this._currentTransform.target = this.getActiveGroup(); + } + } + + return data; + }, + + /** + * @private + */ + _toObjects: function(methodName, propertiesToInclude) { + return this.getObjects().map(function(instance) { + return this._toObject(instance, methodName, propertiesToInclude); + }, this); + }, + + /** + * @private + */ + _toObject: function(instance, methodName, propertiesToInclude) { + var originalValue; + + if (!this.includeDefaultValues) { + originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + var object = instance[methodName](propertiesToInclude); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + return object; + }, + + /** + * @private + */ + __serializeBgOverlay: function() { + var data = { + background: (this.backgroundColor && this.backgroundColor.toObject) + ? this.backgroundColor.toObject() + : this.backgroundColor + }; + + if (this.overlayColor) { + data.overlay = this.overlayColor.toObject + ? this.overlayColor.toObject() + : this.overlayColor; + } + if (this.backgroundImage) { + data.backgroundImage = this.backgroundImage.toObject(); + } + if (this.overlayImage) { + data.overlayImage = this.overlayImage.toObject(); + } + + return data; + }, + + /* _TO_SVG_START_ */ + /** + * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, + * a zoomed canvas will then produce zoomed SVG output. + * @type Boolean + * @default + */ + svgViewportTransformation: true, + + /** + * Returns SVG representation of canvas + * @function + * @param {Object} [options] Options object for SVG output + * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included + * @param {Object} [options.viewBox] SVG viewbox object + * @param {Number} [options.viewBox.x] x-cooridnate of viewbox + * @param {Number} [options.viewBox.y] y-coordinate of viewbox + * @param {Number} [options.viewBox.width] Width of viewbox + * @param {Number} [options.viewBox.height] Height of viewbox + * @param {String} [options.encoding=UTF-8] Encoding of SVG output + * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. + * @return {String} SVG string + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} + * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} + * @example Normal SVG output + * var svg = canvas.toSVG(); + * @example SVG output without preamble (without <?xml ../>) + * var svg = canvas.toSVG({suppressPreamble: true}); + * @example SVG output with viewBox attribute + * var svg = canvas.toSVG({ + * viewBox: { + * x: 100, + * y: 100, + * width: 200, + * height: 300 + * } + * }); + * @example SVG output with different encoding (default: UTF-8) + * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); + * @example Modify SVG output with reviver function + * var svg = canvas.toSVG(null, function(svg) { + * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); + * }); + */ + toSVG: function(options, reviver) { + options || (options = { }); + + var markup = []; + + this._setSVGPreamble(markup, options); + this._setSVGHeader(markup, options); + + this._setSVGBgOverlayColor(markup, 'backgroundColor'); + this._setSVGBgOverlayImage(markup, 'backgroundImage'); + + this._setSVGObjects(markup, reviver); + + this._setSVGBgOverlayColor(markup, 'overlayColor'); + this._setSVGBgOverlayImage(markup, 'overlayImage'); + + markup.push(''); + + return markup.join(''); + }, + + /** + * @private + */ + _setSVGPreamble: function(markup, options) { + if (!options.suppressPreamble) { + markup.push( + '', + '\n' + ); + } + }, + + /** + * @private + */ + _setSVGHeader: function(markup, options) { + var width, height, vpt; + + if (options.viewBox) { + width = options.viewBox.width; + height = options.viewBox.height; + } + else { + width = this.width; + height = this.height; + if (!this.svgViewportTransformation) { + vpt = this.viewportTransform; + width /= vpt[0]; + height /= vpt[3]; + } + } + + markup.push( + '', + 'Created with Fabric.js ', fabric.version, '', + '', + fabric.createSVGFontFacesMarkup(this.getObjects()), + fabric.createSVGRefElementsMarkup(this), + '' + ); + }, + + /** + * @private + */ + _setSVGObjects: function(markup, reviver) { + var activeGroup = this.getActiveGroup(); + if (activeGroup) { + this.discardActiveGroup(); + } + for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + if (activeGroup) { + this.setActiveGroup(new fabric.Group(activeGroup.getObjects())); + activeGroup.forEachObject(function(o) { + o.set('active', true); + }); + } + }, + + /** + * @private + */ + _setSVGBgOverlayImage: function(markup, property) { + if (this[property] && this[property].toSVG) { + markup.push(this[property].toSVG()); + } + }, + + /** + * @private + */ + _setSVGBgOverlayColor: function(markup, property) { + if (this[property] && this[property].source) { + markup.push( + '' + ); + } + else if (this[property] && property === 'overlayColor') { + markup.push( + '' + ); + } + }, + /* _TO_SVG_END_ */ + + /** + * Moves an object to the bottom of the stack of drawn objects + * @param {fabric.Object} object Object to send to back + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendToBack: function (object) { + removeFromArray(this._objects, object); + this._objects.unshift(object); + return this.renderAll && this.renderAll(); + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @param {fabric.Object} object Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringToFront: function (object) { + removeFromArray(this._objects, object); + this._objects.push(object); + return this.renderAll && this.renderAll(); + }, + + /** + * Moves an object down in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendBackwards: function (object, intersecting) { + var idx = this._objects.indexOf(object); + + // if object is not on the bottom of stack + if (idx !== 0) { + var newIdx = this._findNewLowerIndex(object, idx, intersecting); + + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + + /** + * @private + */ + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx; + + if (intersecting) { + newIdx = idx; + + // traverse down the stack looking for the nearest intersecting object + for (var i = idx - 1; i >= 0; --i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx - 1; + } + + return newIdx; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringForward: function (object, intersecting) { + var idx = this._objects.indexOf(object); + + // if object is not on top of stack (last item in an array) + if (idx !== this._objects.length - 1) { + var newIdx = this._findNewUpperIndex(object, idx, intersecting); + + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + this.renderAll && this.renderAll(); + } + return this; + }, + + /** + * @private + */ + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx; + + if (intersecting) { + newIdx = idx; + + // traverse up the stack looking for the nearest intersecting object + for (var i = idx + 1; i < this._objects.length; ++i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx + 1; + } + + return newIdx; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Number} index Position to move to + * @return {fabric.Canvas} thisArg + * @chainable + */ + moveTo: function (object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderAll && this.renderAll(); + }, + + /** + * Clears a canvas element and removes all event listeners + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + this.clear(); + this.interactive && this.removeListeners(); + return this; + }, + + /** + * Returns a string representation of an instance + * @return {String} string representation of an instance + */ + toString: function () { + return '#'; + } + }); + + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + + extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { + + /** + * @static + * @type String + * @default + */ + EMPTY_JSON: '{"objects": [], "background": "white"}', + + /** + * Provides a way to check support of some of the canvas methods + * (either those of HTMLCanvasElement itself, or rendering context) + * + * @param {String} methodName Method to check support for; + * Could be one of "getImageData", "toDataURL", "toDataURLWithQuality" or "setLineDash" + * @return {Boolean | null} `true` if method is supported (or at least exists), + * `null` if canvas element or context can not be initialized + */ + supports: function (methodName) { + var el = fabric.util.createCanvasElement(); + + 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 'setLineDash': + return typeof ctx.setLineDash !== '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; + } + } + }); + + /** + * Returns JSON representation of canvas + * @function + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {String} JSON string + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#serialization} + * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} + * @example JSON without additional properties + * var json = canvas.toJSON(); + * @example JSON with additional properties included + * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY', 'lockUniScaling']); + * @example JSON without default values + * canvas.includeDefaultValues = false; + * var json = canvas.toJSON(); + */ + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; + +})(); + + +/** + * BaseBrush class + * @class fabric.BaseBrush + * @see {@link http://fabricjs.com/freedrawing/|Freedrawing demo} + */ +fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { + + /** + * Color of a brush + * @type String + * @default + */ + color: 'rgb(0, 0, 0)', + + /** + * Width of a brush + * @type Number + * @default + */ + width: 1, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), + * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Line endings style of a brush (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'round', + + /** + * Corner style of a brush (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'round', + + /** + * Sets shadow of an object + * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") + * @return {fabric.Object} thisArg + * @chainable + */ + setShadow: function(options) { + this.shadow = new fabric.Shadow(options); + return this; + }, + + /** + * Sets brush styles + * @private + */ + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + }, + + /** + * Sets brush shadow styles + * @private + */ + _setShadow: function() { + if (!this.shadow) { + return; + } + + var ctx = this.canvas.contextTop; + + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; + }, + + /** + * Removes brush shadow styles + * @private + */ + _resetShadow: function() { + var ctx = this.canvas.contextTop; + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + } +}); + + +(function() { + + var utilMin = fabric.util.array.min, + utilMax = fabric.util.array.max; + + /** + * PencilBrush class + * @class fabric.PencilBrush + * @extends fabric.BaseBrush + */ + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.PencilBrush} Instance of a pencil brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this._points = [ ]; + }, + + /** + * Inovoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this._prepareForDrawing(pointer); + // capture coordinates immediately + // this allows to draw dots (when movement never occurs) + this._captureDrawingPath(pointer); + this._render(); + }, + + /** + * Inovoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this._captureDrawingPath(pointer); + // redraw curve + // clear top canvas + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + this._finalizeAndAddPath(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _prepareForDrawing: function(pointer) { + + var p = new fabric.Point(pointer.x, pointer.y); + + this._reset(); + this._addPoint(p); + + this.canvas.contextTop.moveTo(p.x, p.y); + }, + + /** + * @private + * @param {fabric.Point} point Point to be added to points array + */ + _addPoint: function(point) { + this._points.push(point); + }, + + /** + * Clear points array and set contextTop canvas style. + * @private + */ + _reset: function() { + this._points.length = 0; + + this._setBrushStyles(); + this._setShadow(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + this._addPoint(pointerPoint); + }, + + /** + * Draw a smooth path on the topCanvas using quadraticCurveTo + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform, + p1 = this._points[0], + p2 = this._points[1]; + + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + ctx.beginPath(); + + //if we only have 2 points in the path and they are the same + //it means that the user only clicked the canvas without moving the mouse + //then we should be drawing a dot. A path isn't drawn between two identical dots + //that's why we set them apart a bit + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + p1.x -= 0.5; + p2.x += 0.5; + } + ctx.moveTo(p1.x, p1.y); + + for (var i = 1, len = this._points.length; i < len; i++) { + // we pick the point between pi + 1 & pi + 2 as the + // end point and p1 as our control point. + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + // Draw last line as a straight line while + // we wait for the next point to be able to calculate + // the bezier control point + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + + /** + * Return an SVG path based on our captured points and their bounding box + * @private + */ + _getSVGPathData: function() { + this.box = this.getPathBoundingBox(this._points); + return this.convertPointsToSVGPath( + this._points, this.box.minX, this.box.minY); + }, + + /** + * Returns bounding box of a path based on given points + * @param {Array} points Array of points + * @return {Object} Object with minX, minY, maxX, maxY + */ + getPathBoundingBox: function(points) { + var xBounds = [], + yBounds = [], + p1 = points[0], + p2 = points[1], + startPoint = p1; + + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + // with startPoint, p1 as control point, midpoint as end point + xBounds.push(startPoint.x); + xBounds.push(midPoint.x); + yBounds.push(startPoint.y); + yBounds.push(midPoint.y); + + p1 = points[i]; + p2 = points[i + 1]; + startPoint = midPoint; + } + + xBounds.push(p1.x); + yBounds.push(p1.y); + + return { + minX: utilMin(xBounds), + minY: utilMin(yBounds), + maxX: utilMax(xBounds), + maxY: utilMax(yBounds) + }; + }, + + /** + * Converts points to SVG path + * @param {Array} points Array of points + * @param {Number} minX + * @param {Number} minY + * @return {String} SVG path + */ + convertPointsToSVGPath: function(points, minX, minY) { + var path = [], + p1 = new fabric.Point(points[0].x - minX, points[0].y - minY), + p2 = new fabric.Point(points[1].x - minX, points[1].y - minY); + + path.push('M ', points[0].x - minX, ' ', points[0].y - minY, ' '); + for (var i = 1, len = points.length; i < len; i++) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push('Q ', p1.x, ' ', p1.y, ' ', midPoint.x, ' ', midPoint.y, ' '); + p1 = new fabric.Point(points[i].x - minX, points[i].y - minY); + if ((i + 1) < points.length) { + p2 = new fabric.Point(points[i + 1].x - minX, points[i + 1].y - minY); + } + } + path.push('L ', p1.x, ' ', p1.y, ' '); + return path; + }, + + /** + * Creates fabric.Path object to add on canvas + * @param {String} pathData Path data + * @return {fabric.Path} Path to add on canvas + */ + createPath: function(pathData) { + var path = new fabric.Path(pathData); + path.fill = null; + path.stroke = this.color; + path.strokeWidth = this.width; + path.strokeLineCap = this.strokeLineCap; + path.strokeLineJoin = this.strokeLineJoin; + + if (this.shadow) { + this.shadow.affectStroke = true; + path.setShadow(this.shadow); + } + + return path; + }, + + /** + * On mouseup after drawing the path on contextTop canvas + * we use the points captured to create an new fabric path object + * and add it to the fabric canvas. + */ + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + + var pathData = this._getSVGPathData().join(''); + if (pathData === 'M 0 0 Q 0 0 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.canvas.renderAll(); + return; + } + + // set path origin coordinates based on our bounding box + var originLeft = this.box.minX + (this.box.maxX - this.box.minX) / 2, + originTop = this.box.minY + (this.box.maxY - this.box.minY) / 2; + + this.canvas.contextTop.arc(originLeft, originTop, 3, 0, Math.PI * 2, false); + + var path = this.createPath(pathData); + path.set({ + left: originLeft, + top: originTop, + originX: 'center', + originY: 'center' + }); + + this.canvas.add(path); + path.setCoords(); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderAll(); + + // fire event 'path' created + this.canvas.fire('path:created', { path: path }); + } + }); +})(); + + +/** + * CircleBrush class + * @class fabric.CircleBrush + */ +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { + + /** + * Width of a brush + * @type Number + * @default + */ + width: 10, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.CircleBrush} Instance of a circle brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.points = [ ]; + }, + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + drawDot: function(pointer) { + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop, + v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + ctx.fillStyle = point.fill; + ctx.beginPath(); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); + ctx.closePath(); + ctx.fill(); + + ctx.restore(); + }, + + /** + * Invoked on mouse down + */ + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this.drawDot(pointer); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var circles = [ ]; + + for (var i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); + + this.shadow && circle.setShadow(this.shadow); + + circles.push(circle); + } + var group = new fabric.Group(circles, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; + + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + + /** + * @param {Object} pointer + * @return {fabric.Point} Just added pointer point + */ + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), + + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, + + circleColor = new fabric.Color(this.color) + .setAlpha(fabric.util.getRandomInt(0, 100) / 100) + .toRgba(); + + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + + this.points.push(pointerPoint); + + return pointerPoint; + } +}); + + +/** + * SprayBrush class + * @class fabric.SprayBrush + */ +fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { + + /** + * Width of a spray + * @type Number + * @default + */ + width: 10, + + /** + * Density of a spray (number of dots per chunk) + * @type Number + * @default + */ + density: 20, + + /** + * Width of spray dots + * @type Number + * @default + */ + dotWidth: 1, + + /** + * Width variance of spray dots + * @type Number + * @default + */ + dotWidthVariance: 1, + + /** + * Whether opacity of a dot should be random + * @type Boolean + * @default + */ + randomOpacity: false, + + /** + * Whether overlapping dots (rectangles) should be removed (for performance reasons) + * @type Boolean + * @default + */ + optimizeOverlapping: true, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.SprayBrush} Instance of a spray brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = [ ]; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + this.addSprayChunk(pointer); + this.render(); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var rects = [ ]; + + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: 'center', + originY: 'center', + fill: this.color + }); + + this.shadow && rect.setShadow(this.shadow); + rects.push(rect); + } + } + + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + + var group = new fabric.Group(rects, { originX: 'center', originY: 'center' }); + group.canvas = this.canvas; + + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.renderAll(); + }, + + /** + * @private + * @param {Array} rects + */ + _getOptimizedRects: function(rects) { + + // avoid creating duplicate rects at the same coordinates + var uniqueRects = { }, key; + + for (var i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + '' + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = [ ]; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + + return uniqueRectsArray; + }, + + /** + * Renders brush + */ + render: function() { + var ctx = this.canvas.contextTop; + ctx.fillStyle = this.color; + + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + + for (var i = 0, len = this.sprayChunkPoints.length; i < len; i++) { + var point = this.sprayChunkPoints[i]; + if (typeof point.opacity !== 'undefined') { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + + /** + * @param {Object} pointer + */ + addSprayChunk: function(pointer) { + this.sprayChunkPoints = [ ]; + + var x, y, width, radius = this.width / 2; + + for (var i = 0; i < this.density; i++) { + + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt( + // bottom clamp width to 1 + Math.max(1, this.dotWidth - this.dotWidthVariance), + this.dotWidth + this.dotWidthVariance); + } + else { + width = this.dotWidth; + } + + var point = new fabric.Point(x, y); + point.width = width; + + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + + this.sprayChunkPoints.push(point); + } + + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + + +/** + * PatternBrush class + * @class fabric.PatternBrush + * @extends fabric.BaseBrush + */ +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { + + getPatternSrc: function() { + + var dotWidth = 20, + dotDistance = 5, + patternCanvas = fabric.document.createElement('canvas'), + patternCtx = patternCanvas.getContext('2d'); + + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + + return patternCanvas; + }, + + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); + }, + + /** + * Creates "pattern" instance property + */ + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); + }, + + /** + * Sets brush styles + */ + _setBrushStyles: function() { + this.callSuper('_setBrushStyles'); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + + /** + * Creates path + */ + createPath: function(pathData) { + var path = this.callSuper('createPath', pathData); + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction() + }); + return path; + } +}); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately + * @param {Object} [options] Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} + * @example Generate jpeg dataURL with lower quality + * var dataURL = canvas.toDataURL({ + * format: 'jpeg', + * quality: 0.8 + * }); + * @example Generate cropped png dataURL (clipping of canvas) + * var dataURL = canvas.toDataURL({ + * format: 'png', + * left: 100, + * top: 100, + * width: 200, + * height: 200 + * }); + * @example Generate double scaled png dataURL + * var dataURL = canvas.toDataURL({ + * format: 'png', + * multiplier: 2 + * }); + */ + toDataURL: function (options) { + options || (options = { }); + + var format = options.format || 'png', + quality = options.quality || 1, + multiplier = options.multiplier || 1, + cropping = { + left: options.left, + top: options.top, + width: options.width, + height: options.height + }; + + if (multiplier !== 1) { + return this.__toDataURLWithMultiplier(format, quality, cropping, multiplier); + } + else { + return this.__toDataURL(format, quality, cropping); + } + }, + + /** + * @private + */ + __toDataURL: function(format, quality, cropping) { + + this.renderAll(true); + + var canvasEl = this.upperCanvasEl || this.lowerCanvasEl, + croppedCanvasEl = this.__getCroppedCanvas(canvasEl, cropping); + + // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 + if (format === 'jpg') { + format = 'jpeg'; + } + + var data = (fabric.StaticCanvas.supports('toDataURLWithQuality')) + ? (croppedCanvasEl || canvasEl).toDataURL('image/' + format, quality) + : (croppedCanvasEl || canvasEl).toDataURL('image/' + format); + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + + if (croppedCanvasEl) { + croppedCanvasEl = null; + } + + return data; + }, + + /** + * @private + */ + __getCroppedCanvas: function(canvasEl, cropping) { + + var croppedCanvasEl, + croppedCtx, + shouldCrop = 'left' in cropping || + 'top' in cropping || + 'width' in cropping || + 'height' in cropping; + + if (shouldCrop) { + + croppedCanvasEl = fabric.util.createCanvasElement(); + croppedCtx = croppedCanvasEl.getContext('2d'); + + croppedCanvasEl.width = cropping.width || this.width; + croppedCanvasEl.height = cropping.height || this.height; + + croppedCtx.drawImage(canvasEl, -cropping.left || 0, -cropping.top || 0); + } + + return croppedCanvasEl; + }, + + /** + * @private + */ + __toDataURLWithMultiplier: function(format, quality, cropping, multiplier) { + + var origWidth = this.getWidth(), + origHeight = this.getHeight(), + scaledWidth = origWidth * multiplier, + scaledHeight = origHeight * multiplier, + activeObject = this.getActiveObject(), + activeGroup = this.getActiveGroup(), + + ctx = this.contextTop || this.contextContainer; + + if (multiplier > 1) { + this.setWidth(scaledWidth).setHeight(scaledHeight); + } + ctx.scale(multiplier, multiplier); + + if (cropping.left) { + cropping.left *= multiplier; + } + if (cropping.top) { + cropping.top *= multiplier; + } + if (cropping.width) { + cropping.width *= multiplier; + } + else if (multiplier < 1) { + cropping.width = scaledWidth; + } + if (cropping.height) { + cropping.height *= multiplier; + } + else if (multiplier < 1) { + cropping.height = scaledHeight; + } + + if (activeGroup) { + // not removing group due to complications with restoring it with correct state afterwords + this._tempRemoveBordersControlsFromGroup(activeGroup); + } + else if (activeObject && this.deactivateAll) { + this.deactivateAll(); + } + + this.renderAll(true); + + var data = this.__toDataURL(format, quality, cropping); + + // restoring width, height for `renderAll` to draw + // background properly (while context is scaled) + this.width = origWidth; + this.height = origHeight; + + ctx.scale(1 / multiplier, 1 / multiplier); + this.setWidth(origWidth).setHeight(origHeight); + + if (activeGroup) { + this._restoreBordersControlsOnGroup(activeGroup); + } + else if (activeObject && this.setActiveObject) { + this.setActiveObject(activeObject); + } + + this.contextTop && this.clearContext(this.contextTop); + this.renderAll(); + + return data; + }, + + /** + * Exports canvas element to a dataurl image (allowing to change image size via multiplier). + * @deprecated since 1.0.13 + * @param {String} format (png|jpeg) + * @param {Number} multiplier + * @param {Number} quality (0..1) + * @return {String} + */ + toDataURLWithMultiplier: function (format, multiplier, quality) { + return this.toDataURL({ + format: format, + multiplier: multiplier, + quality: quality + }); + }, + + /** + * @private + */ + _tempRemoveBordersControlsFromGroup: function(group) { + group.origHasControls = group.hasControls; + group.origBorderColor = group.borderColor; + + group.hasControls = true; + group.borderColor = 'rgba(0,0,0,0)'; + + group.forEachObject(function(o) { + o.origBorderColor = o.borderColor; + o.borderColor = 'rgba(0,0,0,0)'; + }); + }, + + /** + * @private + */ + _restoreBordersControlsOnGroup: function(group) { + group.hideControls = group.origHideControls; + group.borderColor = group.origBorderColor; + + group.forEachObject(function(o) { + o.borderColor = o.origBorderColor; + delete o.origBorderColor; + }); + } +}); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Populates canvas with data from the specified dataless JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toDatalessJSON} + * @deprecated since 1.2.2 + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} + */ + loadFromDatalessJSON: function (json, callback, reviver) { + return this.loadFromJSON(json, callback, reviver); + }, + + /** + * Populates canvas with data from the specified JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toJSON} + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#deserialization} + * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} + * @example loadFromJSON + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); + * @example loadFromJSON with reviver + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { + * // `o` = json object + * // `object` = fabric.Object instance + * // ... do some stuff ... + * }); + */ + loadFromJSON: function (json, callback, reviver) { + if (!json) { + return; + } + + // serialize if it wasn't already + var serialized = (typeof json === 'string') + ? JSON.parse(json) + : json; + + this.clear(); + + var _this = this; + this._enlivenObjects(serialized.objects, function () { + _this._setBgOverlay(serialized, callback); + }, reviver); + + return this; + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + _setBgOverlay: function(serialized, callback) { + var _this = this, + loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; + } + + var cbIfLoaded = function () { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + _this.renderAll(); + callback && callback(); + } + }; + + this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); + + cbIfLoaded(); + }, + + /** + * @private + * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) + * @param {(Object|String)} value Value to set + * @param {Object} loaded Set loaded property to true if property is set + * @param {Object} callback Callback function to invoke after property is set + */ + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + + if (!value) { + loaded[property] = true; + return; + } + + if (property === 'backgroundImage' || property === 'overlayImage') { + fabric.Image.fromObject(value, function(img) { + _this[property] = img; + loaded[property] = true; + callback && callback(); + }); + } + else { + this['set' + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); + } + }, + + /** + * @private + * @param {Array} objects + * @param {Function} callback + * @param {Function} [reviver] + */ + _enlivenObjects: function (objects, callback, reviver) { + var _this = this; + + if (!objects || objects.length === 0) { + callback && callback(); + return; + } + + var renderOnAddRemove = this.renderOnAddRemove; + this.renderOnAddRemove = false; + + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + enlivenedObjects.forEach(function(obj, index) { + _this.insertAt(obj, index, true); + }); + + _this.renderOnAddRemove = renderOnAddRemove; + callback && callback(); + }, null, reviver); + }, + + /** + * @private + * @param {String} format + * @param {Function} callback + */ + _toDataURL: function (format, callback) { + this.clone(function (clone) { + callback(clone.toDataURL(format)); + }); + }, + + /** + * @private + * @param {String} format + * @param {Number} multiplier + * @param {Function} callback + */ + _toDataURLWithMultiplier: function (format, multiplier, callback) { + this.clone(function (clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + + /** + * Clones canvas instance + * @param {Object} [callback] Receives cloned instance as a first argument + * @param {Array} [properties] Array of properties to include in the cloned canvas and children + */ + clone: function (callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + + /** + * Clones canvas instance without cloning existing data. + * This essentially copies canvas dimensions, clipping properties, etc. + * but leaves data empty (so that you can populate it with your own) + * @param {Object} [callback] Receives cloned instance as a first argument + */ + cloneWithoutData: function(callback) { + var el = fabric.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, + toFixed = fabric.util.toFixed, + capitalize = fabric.util.string.capitalize, + degreesToRadians = fabric.util.degreesToRadians, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Object) { + return; + } + + /** + * Root object class from which all 2d shape classes inherit from + * @class fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#objects} + * @see {@link fabric.Object#initialize} for constructor definition + * + * @fires added + * @fires removed + * + * @fires selected + * @fires modified + * @fires rotating + * @fires scaling + * @fires moving + * + * @fires mousedown + * @fires mouseup + */ + fabric.Object = fabric.util.createClass(/** @lends fabric.Object.prototype */ { + + /** + * Retrieves object's {@link fabric.Object#clipTo|clipping function} + * @method getClipTo + * @memberOf fabric.Object.prototype + * @return {Function} + */ + + /** + * Sets object's {@link fabric.Object#clipTo|clipping function} + * @method setClipTo + * @memberOf fabric.Object.prototype + * @param {Function} clipTo Clipping function + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#transformMatrix|transformMatrix} + * @method getTransformMatrix + * @memberOf fabric.Object.prototype + * @return {Array} transformMatrix + */ + + /** + * Sets object's {@link fabric.Object#transformMatrix|transformMatrix} + * @method setTransformMatrix + * @memberOf fabric.Object.prototype + * @param {Array} transformMatrix + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#visible|visible} state + * @method getVisible + * @memberOf fabric.Object.prototype + * @return {Boolean} True if visible + */ + + /** + * Sets object's {@link fabric.Object#visible|visible} state + * @method setVisible + * @memberOf fabric.Object.prototype + * @param {Boolean} value visible value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#shadow|shadow} + * @method getShadow + * @memberOf fabric.Object.prototype + * @return {Object} Shadow instance + */ + + /** + * Retrieves object's {@link fabric.Object#stroke|stroke} + * @method getStroke + * @memberOf fabric.Object.prototype + * @return {String} stroke value + */ + + /** + * Sets object's {@link fabric.Object#stroke|stroke} + * @method setStroke + * @memberOf fabric.Object.prototype + * @param {String} value stroke value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#strokeWidth|strokeWidth} + * @method getStrokeWidth + * @memberOf fabric.Object.prototype + * @return {Number} strokeWidth value + */ + + /** + * Sets object's {@link fabric.Object#strokeWidth|strokeWidth} + * @method setStrokeWidth + * @memberOf fabric.Object.prototype + * @param {Number} value strokeWidth value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#originX|originX} + * @method getOriginX + * @memberOf fabric.Object.prototype + * @return {String} originX value + */ + + /** + * Sets object's {@link fabric.Object#originX|originX} + * @method setOriginX + * @memberOf fabric.Object.prototype + * @param {String} value originX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#originY|originY} + * @method getOriginY + * @memberOf fabric.Object.prototype + * @return {String} originY value + */ + + /** + * Sets object's {@link fabric.Object#originY|originY} + * @method setOriginY + * @memberOf fabric.Object.prototype + * @param {String} value originY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#fill|fill} + * @method getFill + * @memberOf fabric.Object.prototype + * @return {String} Fill value + */ + + /** + * Sets object's {@link fabric.Object#fill|fill} + * @method setFill + * @memberOf fabric.Object.prototype + * @param {String} value Fill value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#opacity|opacity} + * @method getOpacity + * @memberOf fabric.Object.prototype + * @return {Number} Opacity value (0-1) + */ + + /** + * Sets object's {@link fabric.Object#opacity|opacity} + * @method setOpacity + * @memberOf fabric.Object.prototype + * @param {Number} value Opacity value (0-1) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#angle|angle} (in degrees) + * @method getAngle + * @memberOf fabric.Object.prototype + * @return {Number} + */ + + /** + * Sets object's {@link fabric.Object#angle|angle} + * @method setAngle + * @memberOf fabric.Object.prototype + * @param {Number} value Angle value (in degrees) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#top|top position} + * @method getTop + * @memberOf fabric.Object.prototype + * @return {Number} Top value (in pixels) + */ + + /** + * Sets object's {@link fabric.Object#top|top position} + * @method setTop + * @memberOf fabric.Object.prototype + * @param {Number} value Top value (in pixels) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#left|left position} + * @method getLeft + * @memberOf fabric.Object.prototype + * @return {Number} Left value (in pixels) + */ + + /** + * Sets object's {@link fabric.Object#left|left position} + * @method setLeft + * @memberOf fabric.Object.prototype + * @param {Number} value Left value (in pixels) + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#scaleX|scaleX} value + * @method getScaleX + * @memberOf fabric.Object.prototype + * @return {Number} scaleX value + */ + + /** + * Sets object's {@link fabric.Object#scaleX|scaleX} value + * @method setScaleX + * @memberOf fabric.Object.prototype + * @param {Number} value scaleX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#scaleY|scaleY} value + * @method getScaleY + * @memberOf fabric.Object.prototype + * @return {Number} scaleY value + */ + + /** + * Sets object's {@link fabric.Object#scaleY|scaleY} value + * @method setScaleY + * @memberOf fabric.Object.prototype + * @param {Number} value scaleY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#flipX|flipX} value + * @method getFlipX + * @memberOf fabric.Object.prototype + * @return {Boolean} flipX value + */ + + /** + * Sets object's {@link fabric.Object#flipX|flipX} value + * @method setFlipX + * @memberOf fabric.Object.prototype + * @param {Boolean} value flipX value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Retrieves object's {@link fabric.Object#flipY|flipY} value + * @method getFlipY + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value + */ + + /** + * Sets object's {@link fabric.Object#flipY|flipY} value + * @method setFlipY + * @memberOf fabric.Object.prototype + * @param {Boolean} value flipY value + * @return {fabric.Object} thisArg + * @chainable + */ + + /** + * Type of an object (rect, circle, path, etc.) + * @type String + * @default + */ + type: 'object', + + /** + * Horizontal origin of transformation of an object (one of "left", "right", "center") + * @type String + * @default + */ + originX: 'left', + + /** + * Vertical origin of transformation of an object (one of "top", "bottom", "center") + * @type String + * @default + */ + originY: 'top', + + /** + * Top position of an object. Note that by default it's relative to object center. You can change this by setting originY={top/center/bottom} + * @type Number + * @default + */ + top: 0, + + /** + * Left position of an object. Note that by default it's relative to object center. You can change this by setting originX={left/center/right} + * @type Number + * @default + */ + left: 0, + + /** + * Object width + * @type Number + * @default + */ + width: 0, + + /** + * Object height + * @type Number + * @default + */ + height: 0, + + /** + * Object scale factor (horizontal) + * @type Number + * @default + */ + scaleX: 1, + + /** + * Object scale factor (vertical) + * @type Number + * @default + */ + scaleY: 1, + + /** + * When true, an object is rendered as flipped horizontally + * @type Boolean + * @default + */ + flipX: false, + + /** + * When true, an object is rendered as flipped vertically + * @type Boolean + * @default + */ + flipY: false, + + /** + * Opacity of an object + * @type Number + * @default + */ + opacity: 1, + + /** + * Angle of rotation of an object (in degrees) + * @type Number + * @default + */ + angle: 0, + + /** + * Size of object's controlling corners (in pixels) + * @type Number + * @default + */ + cornerSize: 12, + + /** + * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) + * @type Boolean + * @default + */ + transparentCorners: true, + + /** + * Default cursor value used when hovering over this object on canvas + * @type String + * @default + */ + hoverCursor: null, + + /** + * Padding between object and its controlling borders (in pixels) + * @type Number + * @default + */ + padding: 0, + + /** + * Color of controlling borders of an object (when it's active) + * @type String + * @default + */ + borderColor: 'rgba(102,153,255,0.75)', + + /** + * Color of controlling corners of an object (when it's active) + * @type String + * @default + */ + cornerColor: 'rgba(102,153,255,0.5)', + + /** + * When true, this object will use center point as the origin of transformation + * when being scaled via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, this object will use center point as the origin of transformation + * when being rotated via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: true, + + /** + * Color of object's fill + * @type String + * @default + */ + fill: 'rgb(0,0,0)', + + /** + * Fill rule used to fill an object + * @type String + * @default + */ + fillRule: 'source-over', + + /** + * Background color of an object. Only works with text objects at the moment. + * @type String + * @default + */ + backgroundColor: '', + + /** + * When defined, an object is rendered via stroke and this property specifies its color + * @type String + * @default + */ + stroke: null, + + /** + * Width of a stroke used to render this object + * @type Number + * @default + */ + strokeWidth: 1, + + /** + * Array specifying dash pattern of an object's stroke (stroke must be defined) + * @type Array + */ + strokeDashArray: null, + + /** + * Line endings style of an object's stroke (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'butt', + + /** + * Corner style of an object's stroke (one of "bevil", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'miter', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke + * @type Number + * @default + */ + strokeMiterLimit: 10, + + /** + * Shadow object representing shadow of this shape + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Opacity of object's controlling borders when object is active and moving + * @type Number + * @default + */ + borderOpacityWhenMoving: 0.4, + + /** + * Scale factor of object's controlling borders + * @type Number + * @default + */ + borderScaleFactor: 1, + + /** + * Transform matrix (similar to SVG's transform matrix) + * @type Array + */ + transformMatrix: null, + + /** + * Minimum allowed scale value of an object + * @type Number + * @default + */ + minScaleLimit: 0.01, + + /** + * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). + * But events still fire on it. + * @type Boolean + * @default + */ + selectable: true, + + /** + * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 + * @type Boolean + * @default + */ + evented: true, + + /** + * When set to `false`, an object is not rendered on canvas + * @type Boolean + * @default + */ + visible: true, + + /** + * When set to `false`, object's controls are not displayed and can not be used to manipulate object + * @type Boolean + * @default + */ + hasControls: true, + + /** + * When set to `false`, object's controlling borders are not rendered + * @type Boolean + * @default + */ + hasBorders: true, + + /** + * When set to `false`, object's controlling rotating point will not be visible or selectable + * @type Boolean + * @default + */ + hasRotatingPoint: true, + + /** + * Offset for object's controlling rotating point (when enabled via `hasRotatingPoint`) + * @type Number + * @default + */ + rotatingPointOffset: 40, + + /** + * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * When `false`, default object's values are not included in its serialization + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Function that determines clipping of an object (context is passed as a first argument) + * Note that context origin is at the object's center point (not left/top corner) + * @type Function + */ + clipTo: null, + + /** + * When `true`, object horizontal movement is locked + * @type Boolean + * @default + */ + lockMovementX: false, + + /** + * When `true`, object vertical movement is locked + * @type Boolean + * @default + */ + lockMovementY: false, + + /** + * When `true`, object rotation is locked + * @type Boolean + * @default + */ + lockRotation: false, + + /** + * When `true`, object horizontal scaling is locked + * @type Boolean + * @default + */ + lockScalingX: false, + + /** + * When `true`, object vertical scaling is locked + * @type Boolean + * @default + */ + lockScalingY: false, + + /** + * When `true`, object non-uniform scaling is locked + * @type Boolean + * @default + */ + lockUniScaling: false, + + /** + * When `true`, object cannot be flipped by scaling into negative values + * @type Boolean + * @default + */ + + lockScalingFlip: false, + /** + * List of properties to consider when checking if state + * of an object is changed (fabric.Object#hasStateChanged) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: ( + 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + + 'stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit ' + + 'angle opacity fill fillRule shadow clipTo visible backgroundColor' + ).split(' '), + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initGradient: function(options) { + if (options.fill && options.fill.colorStops && !(options.fill instanceof fabric.Gradient)) { + this.set('fill', new fabric.Gradient(options.fill)); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initPattern: function(options) { + if (options.fill && options.fill.source && !(options.fill instanceof fabric.Pattern)) { + this.set('fill', new fabric.Pattern(options.fill)); + } + if (options.stroke && options.stroke.source && !(options.stroke instanceof fabric.Pattern)) { + this.set('stroke', new fabric.Pattern(options.stroke)); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initClipping: function(options) { + if (!options.clipTo || typeof options.clipTo !== 'string') { + return; + } + + var functionBody = fabric.util.getFunctionBody(options.clipTo); + if (typeof functionBody !== 'undefined') { + this.clipTo = new Function('ctx', functionBody); + } + }, + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + this._initGradient(options); + this._initPattern(options); + this._initClipping(options); + }, + + /** + * Transforms context when rendering an object + * @param {CanvasRenderingContext2D} ctx Context + * @param {Boolean} fromLeft When true, context is transformed to object's top/left corner. This is used when rendering text on Node + */ + transform: function(ctx, fromLeft) { + if (this.group) { + this.group.transform(ctx, fromLeft); + } + ctx.globalAlpha = this.opacity; + + var center = fromLeft ? this._getLeftTopCoords() : this.getCenterPoint(); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + ctx.scale( + this.scaleX * (this.flipX ? -1 : 1), + this.scaleY * (this.flipY ? -1 : 1) + ); + }, + + /** + * Returns an object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + object = { + type: this.type, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeLineJoin: this.strokeLineJoin, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + 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), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + clipTo: this.clipTo && String(this.clipTo), + backgroundColor: this.backgroundColor + }; + + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /** + * Returns (dataless) object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + // will be overwritten by subclasses + return this.toObject(propertiesToInclude); + }, + + /** + * @private + * @param {Object} object + */ + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; + + stateProperties.forEach(function(prop) { + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + }); + + return object; + }, + + /** + * Returns a string representation of an instance + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Basic getter + * @param {String} property Property name + * @return {Any} value of a property + */ + get: function(property) { + return this[property]; + }, + + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + if (typeof key === 'object') { + this._setObject(key); + } + else { + if (typeof value === 'function' && key !== 'clipTo') { + this._set(key, value(this.get(key))); + } + else { + this._set(key, value); + } + } + return this; + }, + + /** + * @private + * @param {String} key + * @param {Any} value + * @return {fabric.Object} thisArg + */ + _set: function(key, value) { + var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'); + + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === 'scaleX' && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } + else if (key === 'scaleY' && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } + else if (key === 'width' || key === 'height') { + this.minScaleLimit = toFixed(Math.min(0.1, 1/Math.max(this.width, this.height)), 2); + } + else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + + this[key] = value; + + return this; + }, + + /** + * Toggles specified property from `true` to `false` or from `false` to `true` + * @param {String} property Property to toggle + * @return {fabric.Object} thisArg + * @chainable + */ + toggle: function(property) { + var value = this.get(property); + if (typeof value === 'boolean') { + this.set(property, !value); + } + return this; + }, + + /** + * Sets sourcePath of an object + * @param {String} value Value to set sourcePath to + * @return {fabric.Object} thisArg + * @chainable + */ + setSourcePath: function(value) { + this.sourcePath = value; + return this; + }, + + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Boolean} flipY value // TODO + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) { + return this.canvas.viewportTransform; + } + return [1, 0, 0, 1, 0, 0]; + }, + + /** + * Renders an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + render: function(ctx, noTransform) { + // do not render if width/height are zeros or object is not visible + if (this.width === 0 || this.height === 0 || !this.visible) { + return; + } + + ctx.save(); + + //setup fill rule for current object + this._setupFillRule(ctx); + + this._transform(ctx, noTransform); + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + + if (this.group && this.group.type === 'path-group') { + ctx.translate(-this.group.width/2, -this.group.height/2); + var m = this.transformMatrix; + if (m) { + ctx.transform.apply(ctx, m); + } + } + ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + this._render(ctx, noTransform); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + this._restoreFillRule(ctx); + + ctx.restore(); + }, + + _transform: function(ctx, noTransform) { + var m = this.transformMatrix; + + if (m && !this.group) { + ctx.setTransform.apply(ctx, m); + } + if (!noTransform) { + this.transform(ctx); + } + }, + + _setStrokeStyles: function(ctx) { + if (this.stroke) { + ctx.lineWidth = this.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + ctx.strokeStyle = this.stroke.toLive + ? this.stroke.toLive(ctx) + : this.stroke; + } + }, + + _setFillStyles: function(ctx) { + if (this.fill) { + ctx.fillStyle = this.fill.toLive + ? this.fill.toLive(ctx) + : this.fill; + } + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + var vpt = this.getViewportTransform(); + + ctx.save(); + if (this.active && !noTransform) { + var center; + if (this.group) { + center = fabric.util.transformPoint(this.group.getCenterPoint(), vpt); + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.group.angle)); + } + center = fabric.util.transformPoint(this.getCenterPoint(), vpt, null != this.group); + if (this.group) { + center.x *= this.group.scaleX; + center.y *= this.group.scaleY; + } + ctx.translate(center.x, center.y); + ctx.rotate(degreesToRadians(this.angle)); + this.drawBorders(ctx); + this.drawControls(ctx); + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = this.shadow.color; + ctx.shadowBlur = this.shadow.blur; + ctx.shadowOffsetX = this.shadow.offsetX; + ctx.shadowOffsetY = this.shadow.offsetY; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _removeShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderFill: function(ctx) { + if (!this.fill) { + return; + } + + ctx.save(); + if (this.fill.toLive) { + ctx.translate( + -this.width / 2 + this.fill.offsetX || 0, + -this.height / 2 + this.fill.offsetY || 0); + } + if (this.fill.gradientTransform) { + var g = this.fill.gradientTransform; + ctx.transform.apply(ctx, g); + } + if (this.fillRule === 'destination-over') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } + ctx.restore(); + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderStroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + + ctx.save(); + if (this.strokeDashArray) { + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + + if (supportsLineDash) { + ctx.setLineDash(this.strokeDashArray); + this._stroke && this._stroke(ctx); + } + else { + this._renderDashedStroke && this._renderDashedStroke(ctx); + } + ctx.stroke(); + } + else { + if (this.stroke.gradientTransform) { + var g = this.stroke.gradientTransform; + ctx.transform.apply(ctx, g); + } + this._stroke ? this._stroke(ctx) : ctx.stroke(); + } + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Clones an instance + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {fabric.Object} clone of an instance + */ + clone: function(callback, propertiesToInclude) { + if (this.constructor.fromObject) { + return this.constructor.fromObject(this.toObject(propertiesToInclude), callback); + } + return new fabric.Object(this.toObject(propertiesToInclude)); + }, + + /** + * Creates an instance of fabric.Image out of an object + * @param {Function} callback callback, invoked with an instance as a first argument + * @return {fabric.Object} thisArg + */ + cloneAsImage: function(callback) { + var dataUrl = this.toDataURL(); + fabric.util.loadImage(dataUrl, function(img) { + if (callback) { + callback(new fabric.Image(img)); + } + }); + return this; + }, + + /** + * Converts an object into a data-url-like string + * @param {Object} options Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + */ + toDataURL: function(options) { + options || (options = { }); + + var el = fabric.util.createCanvasElement(), + boundingRect = this.getBoundingRect(); + + el.width = boundingRect.width; + el.height = boundingRect.height; + + fabric.util.wrapElement(el, 'div'); + var canvas = new fabric.Canvas(el); + + // to avoid common confusion https://github.com/kangax/fabric.js/issues/806 + if (options.format === 'jpg') { + options.format = 'jpeg'; + } + + if (options.format === 'jpeg') { + canvas.backgroundColor = '#fff'; + } + + var origParams = { + active: this.get('active'), + left: this.getLeft(), + top: this.getTop() + }; + + this.set('active', false); + this.setPositionByOrigin(new fabric.Point(el.width / 2, el.height / 2), 'center', 'center'); + + var originalCanvas = this.canvas; + canvas.add(this); + var data = canvas.toDataURL(options); + + this.set(origParams).setCoords(); + this.canvas = originalCanvas; + + canvas.dispose(); + canvas = null; + + return data; + }, + + /** + * Returns true if specified type is identical to the type of an instance + * @param {String} type Type to check against + * @return {Boolean} + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 0; + }, + + /** + * Returns a JSON representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON + */ + toJSON: function(propertiesToInclude) { + // delegate, not alias + return this.toObject(propertiesToInclude); + }, + + /** + * Sets gradient (fill or stroke) of an object + * Backwards incompatibility note: This method was named "setGradientFill" until v1.1.0 + * @param {String} property Property name 'stroke' or 'fill' + * @param {Object} [options] Options object + * @param {String} [options.type] Type of gradient 'radial' or 'linear' + * @param {Number} [options.x1=0] x-coordinate of start point + * @param {Number} [options.y1=0] y-coordinate of start point + * @param {Number} [options.x2=0] x-coordinate of end point + * @param {Number} [options.y2=0] y-coordinate of end point + * @param {Number} [options.r1=0] Radius of start point (only for radial gradients) + * @param {Number} [options.r2=0] Radius of end point (only for radial gradients) + * @param {Object} [options.colorStops] Color stops object eg. {0: 'ff0000', 1: '000000'} + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/58y8b/|jsFiddle demo} + * @example Set linear gradient + * object.setGradient('fill', { + * type: 'linear', + * x1: -object.width / 2, + * y1: 0, + * x2: object.width / 2, + * y2: 0, + * colorStops: { + * 0: 'red', + * 0.5: '#005555', + * 1: 'rgba(0,0,255,0.5)' + * } + * }); + * canvas.renderAll(); + * @example Set radial gradient + * object.setGradient('fill', { + * type: 'radial', + * x1: 0, + * y1: 0, + * x2: 0, + * y2: 0, + * r1: object.width / 2, + * r2: 10, + * colorStops: { + * 0: 'red', + * 0.5: '#005555', + * 1: 'rgba(0,0,255,0.5)' + * } + * }); + * canvas.renderAll(); + */ + setGradient: function(property, options) { + options || (options = { }); + + var gradient = { colorStops: [] }; + + gradient.type = options.type || (options.r1 || options.r2 ? 'radial' : 'linear'); + gradient.coords = { + x1: options.x1, + y1: options.y1, + x2: options.x2, + y2: options.y2 + }; + + if (options.r1 || options.r2) { + gradient.coords.r1 = options.r1; + gradient.coords.r2 = options.r2; + } + + for (var position in options.colorStops) { + var color = new fabric.Color(options.colorStops[position]); + gradient.colorStops.push({ + offset: position, + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + + return this.set(property, fabric.Gradient.forObject(this, gradient)); + }, + + /** + * Sets pattern fill of an object + * @param {Object} options Options object + * @param {(String|HTMLImageElement)} options.source Pattern source + * @param {String} [options.repeat=repeat] Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) + * @param {Number} [options.offsetX=0] Pattern horizontal offset from object's left/top corner + * @param {Number} [options.offsetY=0] Pattern vertical offset from object's left/top corner + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/QT3pa/|jsFiddle demo} + * @example Set pattern + * fabric.util.loadImage('http://fabricjs.com/assets/escheresque_ste.png', function(img) { + * object.setPatternFill({ + * source: img, + * repeat: 'repeat' + * }); + * canvas.renderAll(); + * }); + */ + setPatternFill: function(options) { + return this.set('fill', new fabric.Pattern(options)); + }, + + /** + * Sets {@link fabric.Object#shadow|shadow} of an object + * @param {Object|String} [options] Options object or string (e.g. "2px 2px 10px rgba(0,0,0,0.2)") + * @param {String} [options.color=rgb(0,0,0)] Shadow color + * @param {Number} [options.blur=0] Shadow blur + * @param {Number} [options.offsetX=0] Shadow horizontal offset + * @param {Number} [options.offsetY=0] Shadow vertical offset + * @return {fabric.Object} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/7gvJG/|jsFiddle demo} + * @example Set shadow with string notation + * object.setShadow('2px 2px 10px rgba(0,0,0,0.2)'); + * canvas.renderAll(); + * @example Set shadow with object notation + * object.setShadow({ + * color: 'red', + * blur: 10, + * offsetX: 20, + * offsetY: 20 + * }); + * canvas.renderAll(); + */ + setShadow: function(options) { + return this.set('shadow', options ? new fabric.Shadow(options) : null); + }, + + /** + * Sets "color" of an instance (alias of `set('fill', …)`) + * @param {String} color Color value + * @return {fabric.Object} thisArg + * @chainable + */ + setColor: function(color) { + this.set('fill', color); + return this; + }, + + /** + * Sets "angle" of an instance + * @param {Number} angle Angle value + * @return {fabric.Object} thisArg + * @chainable + */ + setAngle: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + + /** + * Centers object horizontally on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerH: function () { + this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerV: function () { + this.canvas.centerObjectV(this); + return this; + }, + + /** + * Centers object vertically and horizontally on canvas to which is was added last + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + this.canvas.centerObject(this); + return this; + }, + + /** + * Removes object from canvas to which it was added last + * @return {fabric.Object} thisArg + * @chainable + */ + remove: function() { + this.canvas.remove(this); + return this; + }, + + /** + * Returns coordinates of a pointer relative to an object + * @param {Event} e Event to operate upon + * @param {Object} [pointer] Pointer to operate upon (instead of event) + * @return {Object} Coordinates of a pointer (x, y) + */ + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var objectLeftTop = this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); + return { + x: pointer.x - objectLeftTop.x, + y: pointer.y - objectLeftTop.y + }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specifed using fillRule property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupFillRule: function (ctx) { + if (this.fillRule) { + this._prevFillRule = ctx.globalCompositeOperation; + ctx.globalCompositeOperation = this.fillRule; + } + }, + + /** + * Restores previously saved canvas globalCompositeOperation after obeject rendering + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _restoreFillRule: function (ctx) { + if (this.fillRule && this._prevFillRule) { + ctx.globalCompositeOperation = this._prevFillRule; + } + } + }); + + fabric.util.createAccessors(fabric.Object); + + /** + * Alias for {@link fabric.Object.prototype.setAngle} + * @alias rotate -> setAngle + * @memberof fabric.Object + */ + fabric.Object.prototype.rotate = fabric.Object.prototype.setAngle; + + extend(fabric.Object.prototype, fabric.Observable); + + /** + * Defines the number of fraction digits to use when serializing object values. + * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. + * @static + * @memberof fabric.Object + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; + + /** + * Unique id used internally when creating SVG elements + * @static + * @memberof fabric.Object + * @type Number + */ + fabric.Object.__uid = 0; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Translates the coordinates from origin to center coordinates (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToCenterPoint: function(point, originX, originY) { + var cx = point.x, + cy = point.y, + strokeWidth = this.stroke ? this.strokeWidth : 0; + + if (originX === 'left') { + cx = point.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + cx = point.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + + if (originY === 'top') { + cy = point.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + cy = point.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + + // Apply the reverse rotation to the point (it's already scaled properly) + return fabric.util.rotatePoint(new fabric.Point(cx, cy), point, degreesToRadians(this.angle)); + }, + + /** + * Translates the coordinates from center to origin coordinates (based on the object's dimensions) + * @param {fabric.Point} center The point which corresponds to center of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToOriginPoint: function(center, originX, originY) { + var x = center.x, + y = center.y, + strokeWidth = this.stroke ? this.strokeWidth : 0; + + // Get the point coordinates + if (originX === 'left') { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + if (originY === 'top') { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + + // Apply the rotation to the point (it's already scaled properly) + return fabric.util.rotatePoint(new fabric.Point(x, y), center, degreesToRadians(this.angle)); + }, + + /** + * Returns the real center coordinates of the object + * @return {fabric.Point} + */ + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + + /** + * Returns the coordinates of the object based on center coordinates + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @return {fabric.Point} + */ + // getOriginPoint: function(center) { + // return this.translateToOriginPoint(center, this.originX, this.originY); + // }, + + /** + * Returns the coordinates of the object as if it has a different origin + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + + /** + * Returns the point in local coordinates + * @param {fabric.Point} point The point relative to the global coordinate system + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), + strokeWidth = this.stroke ? this.strokeWidth : 0, + x, y; + + if (originX && originY) { + if (originX === 'left') { + x = center.x - (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else if (originX === 'right') { + x = center.x + (this.getWidth() + strokeWidth * this.scaleX) / 2; + } + else { + x = center.x; + } + + if (originY === 'top') { + y = center.y - (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else if (originY === 'bottom') { + y = center.y + (this.getHeight() + strokeWidth * this.scaleY) / 2; + } + else { + y = center.y; + } + } + else { + x = this.left; + y = this.top; + } + + return fabric.util.rotatePoint(new fabric.Point(point.x, point.y), center, -degreesToRadians(this.angle)) + .subtractEquals(new fabric.Point(x, y)); + }, + + /** + * Returns the point in global coordinates + * @param {fabric.Point} The point relative to the local coordinate system + * @return {fabric.Point} + */ + // toGlobalPoint: function(point) { + // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); + // }, + + /** + * Sets the position of the object taking into consideration the object's origin + * @param {fabric.Point} pos The new position of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {void} + */ + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); + + this.set('left', position.x); + this.set('top', position.y); + }, + + /** + * @param {String} to One of 'left', 'center', 'right' + */ + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), + hypotHalf = this.getWidth() / 2, + xHalf = Math.cos(angle) * hypotHalf, + yHalf = Math.sin(angle) * hypotHalf, + hypotFull = this.getWidth(), + xFull = Math.cos(angle) * hypotFull, + yFull = Math.sin(angle) * hypotFull; + + if (this.originX === 'center' && to === 'left' || + this.originX === 'right' && to === 'center') { + // move half left + this.left -= xHalf; + this.top -= yHalf; + } + else if (this.originX === 'left' && to === 'center' || + this.originX === 'center' && to === 'right') { + // move half right + this.left += xHalf; + this.top += yHalf; + } + else if (this.originX === 'left' && to === 'right') { + // move full right + this.left += xFull; + this.top += yFull; + } + else if (this.originX === 'right' && to === 'left') { + // move full left + this.left -= xFull; + this.top -= yFull; + } + + this.setCoords(); + this.originX = to; + }, + + /** + * Sets the origin/position of the object to it's center point + * @private + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * Resets the origin/position of the object to it's original origin + * @private + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + + /** + * @private + */ + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'center'); + } + }); + +})(); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Object containing coordinates of object's controls + * @type Object + * @default + */ + oCoords: null, + + /** + * Checks if object intersects with an area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @return {Boolean} true if object intersects with an area formed by 2 points + */ + intersectsWithRect: function(pointTL, pointBR) { + 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), + intersection = fabric.Intersection.intersectPolygonRectangle( + [tl, tr, br, bl], + pointTL, + pointBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object intersects with another object + * @param {Object} other Object to test + * @return {Boolean} true if object intersects with another object + */ + 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), + intersection = fabric.Intersection.intersectPolygonPolygon( + [thisCoords.tl, thisCoords.tr, thisCoords.br, thisCoords.bl], + [otherCoords.tl, otherCoords.tr, otherCoords.br, otherCoords.bl] + ); + + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object is fully contained within area of another object + * @param {Object} other Object to test + * @return {Boolean} true if object is fully contained within area of another object + */ + isContainedWithinObject: function(other) { + var boundingRect = other.getBoundingRect(), + point1 = new fabric.Point(boundingRect.left, boundingRect.top), + point2 = new fabric.Point(boundingRect.left + boundingRect.width, boundingRect.top + boundingRect.height); + + return this.isContainedWithinRect(point1, point2); + }, + + /** + * Checks if object is fully contained within area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @return {Boolean} true if object is fully contained within area formed by 2 points + */ + isContainedWithinRect: function(pointTL, pointBR) { + var boundingRect = this.getBoundingRect(); + + return ( + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y + ); + }, + + /** + * Checks if point is inside the object + * @param {fabric.Point} point Point to check against + * @return {Boolean} true if point is inside the object + */ + containsPoint: function(point) { + var lines = this._getImageLines(this.oCoords), + xPoints = this._findCrossPoints(point, lines); + + // if xPoints is odd then point is inside the object + return (xPoints !== 0 && xPoints % 2 === 1); + }, + + /** + * Method that returns an object with the object edges in it, given the coordinates of the corners + * @private + * @param {Object} oCoords Coordinates of the object corners + */ + _getImageLines: function(oCoords) { + 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 + } + }; + }, + + /** + * Helper method to determine how many cross points are between the 4 object edges + * and the horizontal line determined by a point on canvas + * @private + * @param {fabric.Point} point Point to check + * @param {Object} oCoords Coordinates of the object being evaluated + */ + _findCrossPoints: function(point, oCoords) { + var b1, b2, a1, a2, xi, yi, + xcount = 0, + iLine; + + for (var lineKey in oCoords) { + iLine = oCoords[lineKey]; + // optimisation 1: line below point. no cross + if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { + continue; + } + // optimisation 2: line above point. no cross + if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { + xi = iLine.o.x; + yi = point.y; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + + xi = - (a1 - a2) / (b1 - b2); + yi = a1 + b1 * xi; + } + // dont count xi < point.x cases + if (xi >= point.x) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount === 2) { + break; + } + } + return xcount; + }, + + /** + * Returns width of an object's bounding rectangle + * @deprecated since 1.0.4 + * @return {Number} width value + */ + getBoundingRectWidth: function() { + return this.getBoundingRect().width; + }, + + /** + * Returns height of an object's bounding rectangle + * @deprecated since 1.0.4 + * @return {Number} height value + */ + getBoundingRectHeight: function() { + return this.getBoundingRect().height; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * @return {Object} Object with left, top, width, height properties + */ + getBoundingRect: function() { + this.oCoords || this.setCoords(); + + var xCoords = [this.oCoords.tl.x, this.oCoords.tr.x, this.oCoords.br.x, this.oCoords.bl.x], + minX = fabric.util.array.min(xCoords), + maxX = fabric.util.array.max(xCoords), + width = Math.abs(minX - maxX), + + yCoords = [this.oCoords.tl.y, this.oCoords.tr.y, this.oCoords.br.y, this.oCoords.bl.y], + minY = fabric.util.array.min(yCoords), + maxY = fabric.util.array.max(yCoords), + height = Math.abs(minY - maxY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns width of an object + * @return {Number} width value + */ + getWidth: function() { + return this.width * this.scaleX; + }, + + /** + * Returns height of an object + * @return {Number} height value + */ + getHeight: function() { + return this.height * this.scaleY; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @param {Number} value + * @return {Number} + */ + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } + else { + return this.minScaleLimit; + } + } + return value; + }, + + /** + * Scales an object (equally by x and y) + * @param {Number} value Scale factor + * @return {fabric.Object} thisArg + * @chainable + */ + scale: function(value) { + value = this._constrainScale(value); + + if (value < 0) { + this.flipX = !this.flipX; + this.flipY = !this.flipY; + value *= -1; + } + + 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) + * @param {Number} value 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) + * @param {Number} value 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 corner position coordinates based on current angle, width and height + * @return {fabric.Object} thisArg + * @chainable + */ + setCoords: function() { + var strokeWidth = this.strokeWidth > 1 ? this.strokeWidth : 0, + theta = degreesToRadians(this.angle), + vpt = this.getViewportTransform(), + f = function (p) { + return fabric.util.transformPoint(p, vpt); + }, + w = this.width, + h = this.height, + capped = this.strokeLineCap === 'round' || this.strokeLineCap === 'square', + vLine = this.type === 'line' && this.width === 1, + hLine = this.type === 'line' && this.height === 1, + strokeW = (capped && hLine) || this.type !== 'line', + strokeH = (capped && vLine) || this.type !== 'line'; + + if (vLine) { + w = strokeWidth; + } + else if (hLine) { + h = strokeWidth; + } + if (strokeW) { + w += strokeWidth; + } + if (strokeH) { + h += strokeWidth; + } + this.currentWidth = w * this.scaleX; + this.currentHeight = h * this.scaleY; + + // If width is negative, make postive. Fixes path selection issue + if (this.currentWidth < 0) { + this.currentWidth = Math.abs(this.currentWidth); + } + + var _hypotenuse = Math.sqrt( + Math.pow(this.currentWidth / 2, 2) + + Math.pow(this.currentHeight / 2, 2)), + + _angle = Math.atan(isFinite(this.currentHeight / this.currentWidth) ? this.currentHeight / this.currentWidth : 0), + + // offset added for rotate and scale actions + offsetX = Math.cos(_angle + theta) * _hypotenuse, + offsetY = Math.sin(_angle + theta) * _hypotenuse, + sinTh = Math.sin(theta), + cosTh = Math.cos(theta), + coords = this.getCenterPoint(), + wh = new fabric.Point(this.currentWidth, this.currentHeight), + _tl = new fabric.Point(coords.x - offsetX, coords.y - offsetY), + _tr = new fabric.Point(_tl.x + (wh.x * cosTh), _tl.y + (wh.x * sinTh)), + _bl = new fabric.Point(_tl.x - (wh.y * sinTh), _tl.y + (wh.y * cosTh)), + _mt = new fabric.Point(_tl.x + (wh.x/2 * cosTh), _tl.y + (wh.x/2 * sinTh)), + tl = f(_tl), + tr = f(_tr), + br = f(new fabric.Point(_tr.x - (wh.y * sinTh), _tr.y + (wh.y * cosTh))), + bl = f(_bl), + ml = f(new fabric.Point(_tl.x - (wh.y/2 * sinTh), _tl.y + (wh.y/2 * cosTh))), + mt = f(_mt), + mr = f(new fabric.Point(_tr.x - (wh.y/2 * sinTh), _tr.y + (wh.y/2 * cosTh))), + mb = f(new fabric.Point(_bl.x + (wh.x/2 * cosTh), _bl.y + (wh.x/2 * sinTh))), + mtr = f(new fabric.Point(_mt.x, _mt.y)), + + // padding + padX = Math.cos(_angle + theta) * this.padding * Math.sqrt(2), + padY = Math.sin(_angle + theta) * this.padding * Math.sqrt(2); + + tl = tl.add(new fabric.Point(-padX, -padY)); + tr = tr.add(new fabric.Point(padY, -padX)); + br = br.add(new fabric.Point(padX, padY)); + bl = bl.add(new fabric.Point(-padY, padX)); + ml = ml.add(new fabric.Point((-padX - padY) / 2, (-padY + padX) / 2)); + mt = mt.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + mr = mr.add(new fabric.Point((padY + padX) / 2, (padY - padX) / 2)); + mb = mb.add(new fabric.Point((padX - padY) / 2, (padX + padY) / 2)); + mtr = mtr.add(new fabric.Point((padY - padX) / 2, -(padY + padX) / 2)); + + // 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); + + this.oCoords = { + // corners + tl: tl, tr: tr, br: br, bl: bl, + // middle + ml: ml, mt: mt, mr: mr, mb: mb, + // rotating point + mtr: mtr + }; + + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this._setCornerCoords && this._setCornerCoords(); + + return this; + } + }); +})(); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Moves an object to the bottom of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } + else { + this.canvas.sendToBack(this); + } + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } + else { + this.canvas.bringToFront(this); + } + return this; + }, + + /** + * Moves an object down in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } + else { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } + else { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {Number} index New position of object + * @return {fabric.Object} thisArg + * @chainable + */ + moveTo: function(index) { + if (this.group) { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } + else { + this.canvas.moveTo(this, index); + } + return this; + } +}); + + +/* _TO_SVG_START_ */ +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns styles-string for svg-export + * @return {String} + */ + getSvgStyles: function() { + + var fill = this.fill + ? (this.fill.toLive ? 'url(#SVGID_' + this.fill.id + ')' : this.fill) + : 'none', + fillRule = (this.fillRule === 'destination-over' ? 'evenodd' : this.fillRule), + stroke = this.stroke + ? (this.stroke.toLive ? 'url(#SVGID_' + this.stroke.id + ')' : this.stroke) + : 'none', + + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : '', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', + + visibility = this.visible ? '' : ' visibility: hidden;', + filter = this.shadow && this.type !== 'text' ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + + return [ + 'stroke: ', stroke, '; ', + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + 'fill: ', fill, '; ', + 'fill-rule: ', fillRule, '; ', + 'opacity: ', opacity, ';', + filter, + visibility + ].join(''); + }, + + /** + * Returns transform-string for svg-export + * @return {String} + */ + getSvgTransform: function() { + if (this.group) { + return ''; + } + var toFixed = fabric.util.toFixed, + angle = this.getAngle(), + vpt = !this.canvas || this.canvas.svgViewportTransformation ? this.getViewportTransform() : [1, 0, 0, 1, 0, 0], + center = fabric.util.transformPoint(this.getCenterPoint(), vpt), + + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + translatePart = this.type === 'path-group' ? '' : 'translate(' + + toFixed(center.x, NUM_FRACTION_DIGITS) + + ' ' + + toFixed(center.y, NUM_FRACTION_DIGITS) + + ')', + + anglePart = angle !== 0 + ? (' rotate(' + toFixed(angle, NUM_FRACTION_DIGITS) + ')') + : '', + + scalePart = (this.scaleX === 1 && this.scaleY === 1 && vpt[0] === 1 && vpt[3] === 1) + ? '' : + (' scale(' + + toFixed(this.scaleX * vpt[0], NUM_FRACTION_DIGITS) + + ' ' + + toFixed(this.scaleY * vpt[3], NUM_FRACTION_DIGITS) + + ')'), + + addTranslateX = this.type === 'path-group' ? this.width * vpt[0] : 0, + + flipXPart = this.flipX ? ' matrix(-1 0 0 1 ' + addTranslateX + ' 0) ' : '', + + addTranslateY = this.type === 'path-group' ? this.height * vpt[3] : 0, + + flipYPart = this.flipY ? ' matrix(1 0 0 -1 0 ' + addTranslateY + ')' : ''; + + return [ + translatePart, anglePart, scalePart, flipXPart, flipYPart + ].join(''); + }, + + /** + * Returns transform-string for svg-export from the transform matrix of single elements + * @return {String} + */ + getSvgTransformMatrix: function() { + return this.transformMatrix ? ' matrix(' + this.transformMatrix.join(' ') + ')' : ''; + }, + + /** + * @private + */ + _createBaseSVGMarkup: function() { + var markup = [ ]; + + if (this.fill && this.fill.toLive) { + markup.push(this.fill.toSVG(this, false)); + } + if (this.stroke && this.stroke.toLive) { + markup.push(this.stroke.toSVG(this, false)); + } + if (this.shadow) { + markup.push(this.shadow.toSVG(this)); + } + return markup; + } +}); +/* _TO_SVG_END_ */ + + +/* + Depends on `stateProperties` +*/ +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns true if object state (one of its state properties) was changed + * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called + */ + hasStateChanged: function() { + return this.stateProperties.some(function(prop) { + return this.get(prop) !== this.originalState[prop]; + }, this); + }, + + /** + * Saves state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + saveState: function(options) { + this.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + + if (options && options.stateProperties) { + options.stateProperties.forEach(function(prop) { + this.originalState[prop] = this.get(prop); + }, this); + } + + return this; + }, + + /** + * Setups state of an object + * @return {fabric.Object} thisArg + */ + setupState: function() { + this.originalState = { }; + this.saveState(); + + return this; + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Line) { + fabric.warn('fabric.Line is already defined'); + return; + } + + /** + * Line class + * @class fabric.Line + * @extends fabric.Object + * @see {@link fabric.Line#initialize} for constructor definition + */ + fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'line', + + /** + * x value or first line edge + * @type Number + * @default + */ + x1: 0, + + /** + * y value or first line edge + * @type Number + * @default + */ + y1: 0, + + /** + * x value or second line edge + * @type Number + * @default + */ + x2: 0, + + /** + * y value or second line edge + * @type Number + * @default + */ + y2: 0, + + /** + * Constructor + * @param {Array} [points] Array of points + * @param {Object} [options] Options object + * @return {fabric.Line} thisArg + */ + initialize: function(points, options) { + options = options || { }; + + if (!points) { + points = [0, 0, 0, 0]; + } + + this.callSuper('initialize', options); + + this.set('x1', points[0]); + this.set('y1', points[1]); + this.set('x2', points[2]); + this.set('y2', points[3]); + + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Object} [options] Options + */ + _setWidthHeight: function(options) { + options || (options = { }); + + this.width = Math.abs(this.x2 - this.x1) || 1; + this.height = Math.abs(this.y2 - this.y1) || 1; + + this.left = 'left' in options + ? options.left + : this._getLeftToOriginX(); + + this.top = 'top' in options + ? options.top + : this._getTopToOriginY(); + }, + + /** + * @private + * @param {String} key + * @param {Any} value + */ + _set: function(key, value) { + this[key] = value; + if (typeof coordProps[key] !== 'undefined') { + this._setWidthHeight(); + } + return this; + }, + + /** + * @private + * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. + */ + _getLeftToOriginX: makeEdgeToOriginGetter( + { // property names + origin: 'originX', + axis1: 'x1', + axis2: 'x2', + dimension: 'width' + }, + { // possible values of origin + nearest: 'left', + center: 'center', + farthest: 'right' + } + ), + + /** + * @private + * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. + */ + _getTopToOriginY: makeEdgeToOriginGetter( + { // property names + origin: 'originY', + axis1: 'y1', + axis2: 'y2', + dimension: 'height' + }, + { // possible values of origin + nearest: 'top', + center: 'center', + farthest: 'bottom' + } + ), + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + + if (noTransform) { + // Line coords are distances from left-top of canvas to origin of line. + // + // To render line in a path-group, we need to translate them to + // distances from center of path-group to center of line. + var cp = this.getCenterPoint(); + ctx.translate( + cp.x, + cp.y + ); + } + + if (!this.strokeDashArray || this.strokeDashArray && supportsLineDash) { + + // move from center (of virtual box) to its left/top corner + // we can't assume x1, y1 is top left and x2, y2 is bottom right + var xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1; + + ctx.moveTo( + this.width === 1 ? 0 : (xMult * this.width / 2), + this.height === 1 ? 0 : (yMult * this.height / 2)); + + ctx.lineTo( + this.width === 1 ? 0 : (xMult * -1 * this.width / 2), + this.height === 1 ? 0 : (yMult * -1 * 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 = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var + xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1, + x = this.width === 1 ? 0 : xMult * this.width / 2, + y = this.height === 1 ? 0 : yMult * this.height / 2; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, -x, -y, this.strokeDashArray); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @methd toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + x1: this.get('x1'), + y1: this.get('y1'), + x2: this.get('x2'), + y2: this.get('y2') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), addTranslate = ''; + if (!this.group) { + var x = - this.width / 2 - (this.x1 > this.x2 ? this.x2 : this.x1), + y = - this.height / 2 - (this.y1 > this.y2 ? this.y2 : this.y1); + addTranslate = 'translate(' + x + ', ' + y + ') '; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) + * @static + * @memberOf fabric.Line + * @see http://www.w3.org/TR/SVG/shapes.html#LineElement + */ + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); + + /** + * Returns fabric.Line instance from an SVG element + * @static + * @memberOf fabric.Line + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Line} instance of fabric.Line + */ + fabric.Line.fromElement = function(element, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), + points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; + return new fabric.Line(points, extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Line instance from an object representation + * @static + * @memberOf fabric.Line + * @param {Object} object Object to create an instance from + * @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); + }; + + /** + * Produces a function that calculates distance from canvas edge to Line origin. + */ + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, + axis1 = propertyNames.axis1, + axis2 = propertyNames.axis2, + dimension = propertyNames.dimension, + nearest = originValues.nearest, + center = originValues.center, + farthest = originValues.farthest; + + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + + } + +})(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; + } + + /** + * Circle class + * @class fabric.Circle + * @extends fabric.Object + * @see {@link fabric.Circle#initialize} for constructor definition + */ + fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'circle', + + /** + * Radius of this circle + * @type Number + * @default + */ + radius: 0, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Circle} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + this.set('radius', options.radius || 0); + }, + + /** + * @private + * @param {String} key + * @param {Any} value + * @return {fabric.Circle} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + + if (key === 'radius') { + this.setRadius(value); + } + + return this; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + radius: this.get('radius') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0; + if (this.group) { + x = this.left + this.radius; + y = this.top + this.radius; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.arc(noTransform ? this.left + this.radius : 0, noTransform ? this.top + this.radius : 0, this.radius, 0, piBy2, false); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusX: function() { + return this.get('radius') * this.get('scaleX'); + }, + + /** + * Returns vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusY: function() { + return this.get('radius') * this.get('scaleY'); + }, + + /** + * Sets radius of an object (and updates width accordingly) + * @return {Number} + */ + setRadius: function(value) { + this.radius = value; + this.set('width', value * 2).set('height', value * 2); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) + * @static + * @memberOf fabric.Circle + * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement + */ + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); + + /** + * Returns {@link fabric.Circle} instance from an SVG element + * @static + * @memberOf fabric.Circle + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @throws {Error} If value of `r` attribute is missing or invalid + * @return {fabric.Circle} 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 new Error('value of `r` attribute is required and can not be negative'); + } + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + var obj = new fabric.Circle(extend(parsedAttributes, options)); + + obj.left -= obj.radius; + obj.top -= obj.radius; + return obj; + }; + + /** + * @private + */ + function isValidRadius(attributes) { + return (('radius' in attributes) && (attributes.radius > 0)); + } + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Circle} instance from an object representation + * @static + * @memberOf fabric.Circle + * @param {Object} object Object to create an instance from + * @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; + } + + /** + * Triangle class + * @class fabric.Triangle + * @extends fabric.Object + * @return {fabric.Triangle} thisArg + * @see {@link fabric.Triangle#initialize} for constructor definition + */ + fabric.Triangle = fabric.util.createClass(fabric.Object, /** @lends fabric.Triangle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'triangle', + + /** + * Constructor + * @param {Object} [options] 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 + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, -widthBy2, heightBy2, 0, -heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, 0, -heightBy2, widthBy2, heightBy2, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, widthBy2, heightBy2, -widthBy2, heightBy2, this.strokeDashArray); + ctx.closePath(); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), + widthBy2 = this.width / 2, + heightBy2 = this.height / 2, + points = [ + -widthBy2 + ' ' + heightBy2, + '0 ' + -heightBy2, + widthBy2 + ' ' + heightBy2 + ] + .join(','); + + markup.push( + '' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /** + * Returns fabric.Triangle instance from an object representation + * @static + * @memberOf fabric.Triangle + * @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; + } + + /** + * Ellipse class + * @class fabric.Ellipse + * @extends fabric.Object + * @return {fabric.Ellipse} thisArg + * @see {@link fabric.Ellipse#initialize} for constructor definition + */ + fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'ellipse', + + /** + * Horizontal radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical radius + * @type Number + * @default + */ + ry: 0, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Ellipse} 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 + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + rx: this.get('rx'), + ry: this.get('ry') + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = 0, y = 0; + if (this.group) { + x = this.left + this.rx; + y = this.top + this.ry; + } + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _render: function(ctx, noTransform) { + ctx.beginPath(); + ctx.save(); + ctx.transform(1, 0, 0, this.ry/this.rx, 0, 0); + ctx.arc(noTransform ? this.left + this.rx : 0, noTransform ? (this.top + this.ry) * this.rx/this.ry : 0, this.rx, 0, piBy2, false); + ctx.restore(); + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) + * @static + * @memberOf fabric.Ellipse + * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); + + /** + * Returns {@link fabric.Ellipse} instance from an SVG element + * @static + * @memberOf fabric.Ellipse + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromElement = function(element, options) { + options || (options = { }); + + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + var ellipse = new fabric.Ellipse(extend(parsedAttributes, options)); + + ellipse.top -= ellipse.ry; + ellipse.left -= ellipse.rx; + return ellipse; + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Ellipse} instance from an object representation + * @static + * @memberOf fabric.Ellipse + * @param {Object} object Object to create an instance from + * @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 = { }), + extend = fabric.util.object.extend; + + if (fabric.Rect) { + console.warn('fabric.Rect is already defined'); + return; + } + + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push('rx', 'ry', 'x', 'y'); + + /** + * Rectangle class + * @class fabric.Rect + * @extends fabric.Object + * @return {fabric.Rect} thisArg + * @see {@link fabric.Rect#initialize} for constructor definition + */ + fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { + + /** + * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: stateProperties, + + /** + * Type of an object + * @type String + * @default + */ + type: 'rect', + + /** + * Horizontal border radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical border radius + * @type Number + * @default + */ + ry: 0, + + /** + * Used to specify dash pattern for stroke on this object + * @type Array + */ + strokeDashArray: null, + + /** + * Constructor + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + options = options || { }; + + this.callSuper('initialize', options); + this._initRxRy(); + + }, + + /** + * Initializes rx/ry attributes + * @private + */ + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } + else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + + // optimize 1x1 case (used in spray brush) + if (this.width === 1 && this.height === 1) { + ctx.fillRect(0, 0, 1, 1); + return; + } + + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, + ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, + w = this.width, + h = this.height, + x = noTransform ? this.left : -this.width / 2, + y = noTransform ? this.top : -this.height / 2, + isRounded = rx !== 0 || ry !== 0, + k = 1 - 0.5522847498 /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */; + + ctx.beginPath(); + + ctx.moveTo(x + rx, y); + + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + + ctx.closePath(); + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper('toObject', propertiesToInclude), { + rx: this.get('rx') || 0, + ry: this.get('ry') || 0 + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = this._createBaseSVGMarkup(), x = this.left, y = this.top; + if (!this.group) { + x = -this.width / 2; + y = -this.height / 2; + } + markup.push( + '\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) + * @static + * @memberOf fabric.Rect + * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement + */ + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); + + /** + * Returns {@link fabric.Rect} instance from an SVG element + * @static + * @memberOf fabric.Rect + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Rect} Instance of fabric.Rect + */ + fabric.Rect.fromElement = function(element, options) { + if (!element) { + return null; + } + options = options || { }; + + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + + return new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Rect} instance from an object representation + * @static + * @memberOf fabric.Rect + * @param {Object} object Object to create an instance from + * @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; + } + + /** + * Polyline class + * @class fabric.Polyline + * @extends fabric.Object + * @see {@link fabric.Polyline#initialize} for constructor definition + */ + fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polyline', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + /** + * Constructor + * @param {Array} points Array of points (where each point is an object with x and y) + * @param {Object} [options] Options object + * @param {Boolean} [skipOffset] Whether points offsetting should be skipped + * @return {fabric.Polyline} thisArg + * @example + * var poly = new fabric.Polyline([ + * { x: 10, y: 10 }, + * { x: 50, y: 30 }, + * { x: 40, y: 70 }, + * { x: 60, y: 50 }, + * { x: 100, y: 150 }, + * { x: 40, y: 100 } + * ], { + * stroke: 'red', + * left: 100, + * top: 100 + * }); + */ + initialize: function(points, options) { + options = options || { }; + this.set('points', points); + this.callSuper('initialize', options); + this._calcDimensions(); + }, + + /** + * @private + */ + _calcDimensions: function() { + return fabric.Polygon.prototype._calcDimensions.call(this); + }, + + /** + * @private + */ + _applyPointOffset: function() { + return fabric.Polygon.prototype._applyPointOffset.call(this); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + return fabric.Polygon.prototype.toObject.call(this, propertiesToInclude); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var points = [], + markup = this._createBaseSVGMarkup(); + + 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), ' '); + } + + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + + if (this._applyPointOffset) { + if (!(this.group && this.group.type === 'path-group')) { + this._applyPointOffset(); + } + this._applyPointOffset = null; + } + + 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); + } + + this._renderFill(ctx); + this._renderStroke(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var p1, p2; + + ctx.beginPath(); + for (var i = 0, len = this.points.length; i < len; i++) { + p1 = this.points[i]; + p2 = this.points[i + 1] || p1; + fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); + } + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.get('points').length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) + * @static + * @memberOf fabric.Polyline + * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns fabric.Polyline instance from an SVG element + * @static + * @memberOf fabric.Polyline + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Polyline} 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); + + if (points === null) { + return null; + } + + return new fabric.Polyline(points, fabric.util.object.extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polyline instance from an object representation + * @static + * @memberOf fabric.Polyline + * @param {Object} object Object to create an instance from + * @return {fabric.Polyline} Instance of fabric.Polyline + */ + fabric.Polyline.fromObject = function(object) { + var points = object.points; + return new fabric.Polyline(points, object, true); + }; + +})(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; + } + + /** + * Polygon class + * @class fabric.Polygon + * @extends fabric.Object + * @see {@link fabric.Polygon#initialize} for constructor definition + */ + fabric.Polygon = fabric.util.createClass(fabric.Object, /** @lends fabric.Polygon.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polygon', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + /** + * Constructor + * @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 + */ + _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.left = minX, + this.top = minY; + }, + + /** + * @private + */ + _applyPointOffset: function() { + // change points to offset polygon into a bounding box + // executed one time + this.points.forEach(function(p) { + p.x -= (this.left + this.width / 2); + p.y -= (this.top + this.height / 2); + }, this); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + points: this.points.concat() + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var points = [], + markup = this._createBaseSVGMarkup(); + + 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), ' '); + } + + markup.push( + '\n' + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var point; + ctx.beginPath(); + + if (this._applyPointOffset) { + if (!(this.group && this.group.type === 'path-group')) { + this._applyPointOffset(); + } + this._applyPointOffset = null; + } + + 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); + } + this._renderFill(ctx); + if (this.stroke || this.strokeDashArray) { + ctx.closePath(); + this._renderStroke(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var p1, p2; + + ctx.beginPath(); + for (var i = 0, len = this.points.length; i < len; i++) { + p1 = this.points[i]; + p2 = this.points[i + 1] || this.points[0]; + fabric.util.drawDashedLine(ctx, p1.x, p1.y, p2.x, p2.y, this.strokeDashArray); + } + ctx.closePath(); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.points.length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) + * @static + * @memberOf fabric.Polygon + * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement + */ + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns {@link fabric.Polygon} instance from an SVG element + * @static + * @memberOf fabric.Polygon + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Polygon} Instance of 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); + + if (points === null) { + return null; + } + + return new fabric.Polygon(points, extend(parsedAttributes, options)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polygon instance from an object representation + * @static + * @memberOf fabric.Polygon + * @param {Object} object Object to create an instance from + * @return {fabric.Polygon} Instance of fabric.Polygon + */ + fabric.Polygon.fromObject = function(object) { + return new fabric.Polygon(object.points, object, true); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + min = fabric.util.array.min, + max = fabric.util.array.max, + extend = fabric.util.object.extend, + _toString = Object.prototype.toString, + drawArc = fabric.util.drawArc, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' + }; + + if (fabric.Path) { + fabric.warn('fabric.Path is already defined'); + 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]; + } + + /** + * Path class + * @class fabric.Path + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} + * @see {@link fabric.Path#initialize} for constructor definition + */ + fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path', + + /** + * Array of path points + * @type Array + * @default + */ + path: null, + + /** + * Constructor + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + * @return {fabric.Path} thisArg + */ + initialize: function(path, options) { + options = options || { }; + + this.setOptions(options); + + if (!path) { + throw new 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.path = this._parsePath(); + } + this._initializePath(options); + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initializePath: function (options) { + var isWidthSet = 'width' in options && options.width != null, + isHeightSet = 'height' in options && options.width != null, + isLeftSet = 'left' in options, + isTopSet = 'top' in options, + origLeft = isLeftSet ? this.left : 0, + origTop = isTopSet ? this.top : 0; + + if (!isWidthSet || !isHeightSet) { + extend(this, this._parseDimensions()); + if (isWidthSet) { + this.width = options.width; + } + if (isHeightSet) { + this.height = options.height; + } + } + else { //Set center location relative to given height/width if not specified + if (!isTopSet) { + this.top = this.height / 2; + } + if (!isLeftSet) { + this.left = this.width / 2; + } + } + this.pathOffset = this.pathOffset || + // Save top-left coords as offset + this._calculatePathOffset(origLeft, origTop); + }, + + /** + * @private + * @param {Number} origLeft Original left position + * @param {Number} origTop Original top position + */ + _calculatePathOffset: function (origLeft, origTop) { + return { + x: this.left - origLeft - (this.width / 2), + y: this.top - origTop - (this.height / 2) + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _render: function(ctx, noTransform) { + var current, // current instruction + previous = null, + subpathStartX = 0, + subpathStartY = 0, + 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) + this.pathOffset.x), + t = -((this.height / 2) + this.pathOffset.y); + + if (noTransform) { + 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]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'c': // bezierCurveTo, 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 = controlX ? (2 * x - controlX) : x; + controlY = controlY ? (2 * y - controlY) : y; + + 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': + x = subpathStartX; + y = subpathStartY; + ctx.closePath(); + break; + } + previous = current; + } + }, + + /** + * Renders path on a specified context + * @param {CanvasRenderingContext2D} ctx context to render path on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + render: function(ctx, noTransform) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + if (noTransform) { + ctx.translate(-this.width/2, -this.height/2); + } + 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); + } + this._setStrokeStyles(ctx); + this._setFillStyles(ctx); + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + ctx.beginPath(); + ctx.globalAlpha = this.group ? (ctx.globalAlpha * this.opacity) : this.opacity; + this._render(ctx, noTransform); + this._renderFill(ctx); + this._renderStroke(ctx); + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Returns string representation of an instance + * @return {String} string representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + var o = extend(this.callSuper('toObject', propertiesToInclude), { + path: this.path.map(function(item) { return item.slice() }), + pathOffset: this.pathOffset + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + if (this.transformMatrix) { + o.transformMatrix = this.transformMatrix; + } + return o; + }, + + /** + * Returns dataless object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.path = this.sourcePath; + } + delete o.sourcePath; + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var chunks = [], + markup = this._createBaseSVGMarkup(); + + for (var i = 0, len = this.path.length; i < len; i++) { + chunks.push(this.path[i].join(' ')); + } + var path = chunks.join(' '); + + markup.push( + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns number representation of an instance complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.path.length; + }, + + /** + * @private + */ + _parsePath: function() { + var result = [ ], + coords = [ ], + currentPath, + parsed, + re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/ig, + match, + coordsStr; + + for (var i = 0, coordsParsed, len = this.path.length; i < len; i++) { + currentPath = this.path[i]; + + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + + while ((match = re.exec(coordsStr))) { + coords.push(match[0]); + } + + coordsParsed = [ currentPath.charAt(0) ]; + + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + + var command = coordsParsed[0], + commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; + + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([ command ].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } + else { + result.push(coordsParsed); + } + } + + return result; + }, + + /** + * @private + */ + _parseDimensions: function() { + var aX = [], + aY = [], + previous = { }; + + this.path.forEach(function(item, i) { + this._getCoordsFromCommand(item, i, aX, aY, previous); + }, this); + + var minX = min(aX), + minY = min(aY), + maxX = max(aX), + maxY = max(aY), + deltaX = maxX - minX, + deltaY = maxY - minY, + + o = { + left: this.left + (minX + deltaX / 2), + top: this.top + (minY + deltaY / 2), + width: deltaX, + height: deltaY + }; + + return o; + }, + + _getCoordsFromCommand: function(item, i, aX, aY, previous) { + var isLowerCase = false; + + if (item[0] !== 'H') { + previous.x = (i === 0) ? getX(item) : getX(this.path[i - 1]); + } + if (item[0] !== 'V') { + previous.y = (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; + } + + var xy = this._getXY(item, isLowerCase, previous), + val; + + val = parseInt(xy.x, 10); + if (!isNaN(val)) { + aX.push(val); + } + + val = parseInt(xy.y, 10); + if (!isNaN(val)) { + aY.push(val); + } + }, + + _getXY: function(item, isLowerCase, previous) { + + // 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 + + var x = isLowerCase + ? previous.x + getX(item) + : item[0] === 'V' + ? previous.x + : getX(item), + + y = isLowerCase + ? previous.y + getY(item) + : item[0] === 'H' + ? previous.y + : getY(item); + + return { x: x, y: y }; + } + }); + + /** + * Creates an instance of fabric.Path from an object + * @static + * @memberOf fabric.Path + * @param {Object} object + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + */ + fabric.Path.fromObject = function(object, callback) { + if (typeof object.path === 'string') { + fabric.loadSVGFromURL(object.path, function (elements) { + var path = elements[0], + pathUrl = object.path; + + delete object.path; + + fabric.util.object.extend(path, object); + path.setSourcePath(pathUrl); + + callback(path); + }); + } + else { + callback(new fabric.Path(object.path, object)); + } + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) + * @static + * @memberOf fabric.Path + * @see http://www.w3.org/TR/SVG/paths.html#PathElement + */ + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); + + /** + * Creates an instance of fabric.Path from an SVG element + * @static + * @memberOf fabric.Path + * @param {SVGElement} element to parse + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + * @param {Object} [options] Options object + */ + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + callback && callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.Path + * @type Boolean + * @default + */ + fabric.Path.async = true; + +})(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, + parentToObject = fabric.Object.prototype.toObject; + + if (fabric.PathGroup) { + fabric.warn('fabric.PathGroup is already defined'); + return; + } + + /** + * Path group class + * @class fabric.PathGroup + * @extends fabric.Path + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#path_and_pathgroup} + * @see {@link fabric.PathGroup#initialize} for constructor definition + */ + fabric.PathGroup = fabric.util.createClass(fabric.Path, /** @lends fabric.PathGroup.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path-group', + + /** + * Fill value + * @type String + * @default + */ + fill: '', + + /** + * Constructor + * @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); + + if (options.widthAttr) { + this.scaleX = options.widthAttr / options.width; + } + if (options.heightAttr) { + this.scaleY = options.heightAttr / options.height; + } + + this.setCoords(); + + if (options.sourcePath) { + this.setSourcePath(options.sourcePath); + } + }, + + /** + * Renders this group on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render this instance on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + 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); + + this._setShadow(ctx); + this.clipTo && fabric.util.clipContext(this, ctx); + for (var i = 0, l = this.paths.length; i < l; ++i) { + this.paths[i].render(ctx, true); + } + this.clipTo && ctx.restore(); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * Sets certain property to a certain value + * @param {String} prop + * @param {Any} value + * @return {fabric.PathGroup} thisArg + */ + _set: function(prop, value) { + + if (prop === 'fill' && 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 + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + var o = extend(parentToObject.call(this, propertiesToInclude), { + paths: invoke(this.getObjects(), 'toObject', propertiesToInclude) + }); + if (this.sourcePath) { + o.sourcePath = this.sourcePath; + } + return o; + }, + + /** + * Returns dataless object representation of this path group + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} dataless object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(propertiesToInclude); + if (this.sourcePath) { + o.paths = this.sourcePath; + } + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var objects = this.getObjects(), + translatePart = 'translate(' + this.left + ' ' + this.top + ')', + markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + + for (var i = 0, len = objects.length; i < len; i++) { + markup.push(objects[i].toSVG(reviver)); + } + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns a string representation of this path group + * @return {String} string representation of an object + */ + toString: function() { + return '#'; + }, + + /** + * Returns true if all paths in this group are of same color + * @return {Boolean} true if all paths are of the same color (`fill`) + */ + isSameColor: function() { + var firstPathFill = (this.getObjects()[0].get('fill') || '').toLowerCase(); + return this.getObjects().every(function(path) { + return (path.get('fill') || '').toLowerCase() === firstPathFill; + }); + }, + + /** + * Returns number representation of object's complexity + * @return {Number} complexity + */ + complexity: function() { + return this.paths.reduce(function(total, path) { + return total + ((path && path.complexity) ? path.complexity() : 0); + }, 0); + }, + + /** + * Returns all paths in this path group + * @return {Array} array of path objects included in this path group + */ + getObjects: function() { + return this.paths; + } + }); + + /** + * Creates fabric.PathGroup instance from an object representation + * @static + * @memberOf fabric.PathGroup + * @param {Object} object Object to create an instance from + * @param {Function} callback Callback to invoke when an fabric.PathGroup instance is created + */ + fabric.PathGroup.fromObject = function(object, callback) { + if (typeof object.paths === 'string') { + fabric.loadSVGFromURL(object.paths, function (elements) { + + var pathUrl = object.paths; + delete object.paths; + + var pathGroup = fabric.util.groupSVGElements(elements, object, pathUrl); + + callback(pathGroup); + }); + } + else { + fabric.util.enlivenObjects(object.paths, function(enlivenedObjects) { + delete object.paths; + callback(new fabric.PathGroup(enlivenedObjects, object)); + }); + } + }; + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.PathGroup + * @type Boolean + * @default + */ + fabric.PathGroup.async = true; + +})(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; + + if (fabric.Group) { + return; + } + + // lock-related properties, for use in fabric.Group#get + // to enable locking behavior on group + // when one of its objects has lock-related properties set + var _lockProperties = { + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + lockUniScaling: true + }; + + /** + * Group class + * @class fabric.Group + * @extends fabric.Object + * @mixes fabric.Collection + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3/#groups} + * @see {@link fabric.Group#initialize} for constructor definition + */ + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'group', + + /** + * Constructor + * @param {Object} objects Group objects + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(objects, options) { + options = options || { }; + + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + this.originalState = { }; + this.callSuper('initialize'); + + this._calcBounds(); + this._updateObjectsCoords(); + + if (options) { + extend(this, options); + } + this._setOpacityIfSame(); + + this.setCoords(); + this.saveCoords(); + }, + + /** + * @private + */ + _updateObjectsCoords: function() { + this.forEachObject(this._updateObjectCoords, this); + }, + + /** + * @private + */ + _updateObjectCoords: function(object) { + var objectLeft = object.getLeft(), + objectTop = object.getTop(); + + object.set({ + originalLeft: objectLeft, + originalTop: objectTop, + left: objectLeft - this.left, + top: objectTop - this.top + }); + + object.setCoords(); + + // do not display corners of objects enclosed in a group + object.__origHasControls = object.hasControls; + object.hasControls = false; + }, + + /** + * Returns string represenation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Adds an object to a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + addWithUpdate: function(object) { + this._restoreObjectsState(); + if (object) { + this._objects.push(object); + object.group = this; + } + // since _restoreObjectsState set objects inactive + this.forEachObject(this._setObjectActive, this); + this._calcBounds(); + this._updateObjectsCoords(); + return this; + }, + + /** + * @private + */ + _setObjectActive: function(object) { + object.set('active', true); + object.group = this; + }, + + /** + * Removes an object from a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + removeWithUpdate: function(object) { + this._moveFlippedObject(object); + this._restoreObjectsState(); + + // since _restoreObjectsState set objects inactive + this.forEachObject(this._setObjectActive, this); + + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + + return this; + }, + + /** + * @private + */ + _onObjectAdded: function(object) { + object.group = this; + }, + + /** + * @private + */ + _onObjectRemoved: function(object) { + delete object.group; + object.set('active', false); + }, + + /** + * Properties that are delegated to group objects when reading/writing + * @param {Object} delegatedProperties + */ + delegatedProperties: { + fill: true, + opacity: true, + fontFamily: true, + fontWeight: true, + fontSize: true, + fontStyle: true, + lineHeight: true, + textDecoration: true, + textAlign: true, + backgroundColor: true + }, + + /** + * @private + */ + _set: function(key, value) { + if (key in this.delegatedProperties) { + var i = this._objects.length; + this[key] = value; + while (i--) { + this._objects[i].set(key, value); + } + } + else { + this[key] = value; + } + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + objects: invoke(this._objects, 'toObject', propertiesToInclude) + }); + }, + + /** + * Renders instance on a given context + * @param {CanvasRenderingContext2D} ctx context to render instance on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + this.clipTo && fabric.util.clipContext(this, ctx); + + // the array is now sorted in order of highest first, so start from end + for (var i = 0, len = this._objects.length; i < len; i++) { + this._renderObject(this._objects[i], ctx); + } + + this.clipTo && ctx.restore(); + + ctx.restore(); + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Boolean} [noTransform] When true, context is not transformed + */ + _renderControls: function(ctx, noTransform) { + this.callSuper('_renderControls', ctx, noTransform); + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx); + } + }, + + /** + * @private + */ + _renderObject: function(object, ctx) { + var originalHasRotatingPoint = object.hasRotatingPoint; + + // do not render if object is not visible + if (!object.visible) { + return; + } + + object.hasRotatingPoint = false; + + object.render(ctx); + + object.hasRotatingPoint = originalHasRotatingPoint; + }, + + /** + * Retores original state of each of group objects (original state is that which was before group was created). + * @private + * @return {fabric.Group} thisArg + * @chainable + */ + _restoreObjectsState: function() { + this._objects.forEach(this._restoreObjectState, this); + return this; + }, + + /** + * Moves a flipped object to the position where it's displayed + * @private + * @param {fabric.Object} object + * @return {fabric.Group} thisArg + */ + _moveFlippedObject: function(object) { + var oldOriginX = object.get('originX'), + oldOriginY = object.get('originY'), + center = object.getCenterPoint(); + + object.set({ + originX: 'center', + originY: 'center', + left: center.x, + top: center.y + }); + + this._toggleFlipping(object); + + var newOrigin = object.getPointByOrigin(oldOriginX, oldOriginY); + + object.set({ + originX: oldOriginX, + originY: oldOriginY, + left: newOrigin.x, + top: newOrigin.y + }); + + return this; + }, + + /** + * @private + */ + _toggleFlipping: function(object) { + if (this.flipX) { + object.toggle('flipX'); + object.set('left', -object.get('left')); + object.setAngle(-object.getAngle()); + } + if (this.flipY) { + object.toggle('flipY'); + object.set('top', -object.get('top')); + object.setAngle(-object.getAngle()); + } + }, + + /** + * Restores original state of a specified object in group + * @private + * @param {fabric.Object} object + * @return {fabric.Group} thisArg + */ + _restoreObjectState: function(object) { + this._setObjectPosition(object); + + object.setCoords(); + object.hasControls = object.__origHasControls; + delete object.__origHasControls; + object.set('active', false); + object.setCoords(); + delete object.group; + + return this; + }, + + /** + * @private + */ + _setObjectPosition: function(object) { + var groupLeft = this.getLeft(), + groupTop = this.getTop(), + rotated = this._getRotatedLeftTop(object); + + object.set({ + angle: object.getAngle() + this.getAngle(), + left: groupLeft + rotated.left, + top: groupTop + rotated.top, + scaleX: object.get('scaleX') * this.get('scaleX'), + scaleY: object.get('scaleY') * this.get('scaleY') + }); + }, + + /** + * @private + */ + _getRotatedLeftTop: function(object) { + var groupAngle = this.getAngle() * (Math.PI / 180); + return { + left: (-Math.sin(groupAngle) * object.getTop() * this.get('scaleY') + + Math.cos(groupAngle) * object.getLeft() * this.get('scaleX')), + + top: (Math.cos(groupAngle) * object.getTop() * this.get('scaleY') + + Math.sin(groupAngle) * object.getLeft() * this.get('scaleX')) + }; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + destroy: function() { + this._objects.forEach(this._moveFlippedObject, this); + 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) + * @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 + * @return {fabric.Group} thisArg + * @chainable + */ + setObjectsCoords: function() { + this.forEachObject(function(object) { + object.setCoords(); + }); + return this; + }, + + /** + * @private + */ + _setOpacityIfSame: function() { + var objects = this.getObjects(), + firstValue = objects[0] ? objects[0].get('opacity') : 1, + isSameOpacity = objects.every(function(o) { + return o.get('opacity') === firstValue; + }); + + if (isSameOpacity) { + this.opacity = firstValue; + } + }, + + /** + * @private + */ + _calcBounds: function(onlyWidthHeight) { + var aX = [], + aY = [], + o; + + for (var i = 0, len = this._objects.length; 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); + } + } + + this.set(this._getBounds(aX, aY, onlyWidthHeight)); + }, + + /** + * @private + */ + _getBounds: function(aX, aY, onlyWidthHeight) { + var ivt = fabric.util.invertTransform(this.getViewportTransform()), + minXY = fabric.util.transformPoint(new fabric.Point(min(aX), min(aY)), ivt), + maxXY = fabric.util.transformPoint(new fabric.Point(max(aX), max(aY)), ivt), + obj = { + width: (maxXY.x - minXY.x) || 0, + height: (maxXY.y - minXY.y) || 0 + }; + + if (!onlyWidthHeight) { + obj.left = (minXY.x + maxXY.x) / 2 || 0; + obj.top = (minXY.y + maxXY.y) / 2 || 0; + } + return obj; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [ + //jscs:disable validateIndentation + '\n' + //jscs:enable validateIndentation + ]; + + for (var i = 0, len = this._objects.length; i < len; i++) { + markup.push(this._objects[i].toSVG(reviver)); + } + + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns requested property + * @param {String} prop Property to get + * @return {Any} + */ + get: function(prop) { + if (prop in _lockProperties) { + if (this[prop]) { + return this[prop]; + } + else { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i][prop]) { + return true; + } + } + return false; + } + } + else { + if (prop in this.delegatedProperties) { + return this._objects[0] && this._objects[0].get(prop); + } + return this[prop]; + } + } + }); + + /** + * Returns {@link fabric.Group} instance from an object representation + * @static + * @memberOf fabric.Group + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an group instance is created + * @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)); + }); + }; + + /** + * Indicates that instances of this type are async + * @static + * @memberOf fabric.Group + * @type Boolean + * @default + */ + 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; + } + + /** + * Image class + * @class fabric.Image + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images} + * @see {@link fabric.Image#initialize} for constructor definition + */ + fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'image', + + /** + * crossOrigin value (one of "", "anonymous", "allow-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @type String + * @default + */ + crossOrigin: '', + + /** + * Constructor + * @param {HTMLImageElement | String} element Image element + * @param {Object} [options] Options object + * @return {fabric.Image} thisArg + */ + initialize: function(element, options) { + options || (options = { }); + + this.filters = [ ]; + + this.callSuper('initialize', options); + + this._initElement(element, options); + this._initConfig(options); + + if (options.filters) { + this.filters = options.filters; + this.applyFilters(); + } + }, + + /** + * Returns image element which this instance if based on + * @return {HTMLImageElement} Image element + */ + getElement: function() { + return this._element; + }, + + /** + * Sets image element for this instance to a specified one. + * If filters defined they are applied to new image. + * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. + * @param {HTMLImageElement} element + * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated + * @return {fabric.Image} thisArg + * @chainable + */ + setElement: function(element, callback) { + this._element = element; + this._originalElement = element; + this._initConfig(); + + if (this.filters.length !== 0) { + this.applyFilters(callback); + } + + return this; + }, + + /** + * Sets crossOrigin value (on an instance and corresponding image element) + * @return {fabric.Image} thisArg + * @chainable + */ + setCrossOrigin: function(value) { + this.crossOrigin = value; + this._element.crossOrigin = value; + + return this; + }, + + /** + * Returns original size of an image + * @return {Object} Object with "width" and "height" properties + */ + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.width, + height: element.height + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _stroke: function(ctx) { + ctx.save(); + this._setStrokeStyles(ctx); + ctx.beginPath(); + ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); + ctx.closePath(); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderDashedStroke: function(ctx) { + var x = -this.width / 2, + y = -this.height / 2, + w = this.width, + h = this.height; + + ctx.save(); + this._setStrokeStyles(ctx); + + ctx.beginPath(); + fabric.util.drawDashedLine(ctx, x, y, x + w, y, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y, x + w, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x + w, y + h, x, y + h, this.strokeDashArray); + fabric.util.drawDashedLine(ctx, x, y + h, x, y, this.strokeDashArray); + ctx.closePath(); + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + src: this._originalElement.src || this._originalElement._src, + filters: this.filters.map(function(filterObj) { + return filterObj && filterObj.toObject(); + }), + crossOrigin: this.crossOrigin + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [], x = -this.width / 2, y = -this.height / 2; + if (this.group) { + x = this.left; + y = this.top; + } + markup.push( + '\n', + '\n' + ); + + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + markup.push( + '\n' + ); + this.fill = origFill; + } + + markup.push('\n'); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns source of an image + * @return {String} Source of an image + */ + getSrc: function() { + if (this.getElement()) { + return this.getElement().src || this.getElement()._src; + } + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns a clone of an instance + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + */ + clone: function(callback, propertiesToInclude) { + this.constructor.fromObject(this.toObject(propertiesToInclude), 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 + * @return {fabric.Image} thisArg + * @chainable + */ + applyFilters: function(callback) { + + if (!this._originalElement) { + return; + } + + if (this.filters.length === 0) { + this._element = this._originalElement; + callback && callback(); + return; + } + + var imgEl = this._originalElement, + canvasEl = fabric.util.createCanvasElement(), + replacement = fabric.util.createImage(), + _this = this; + + 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.width = imgEl.width; + replacement.height = imgEl.height; + + if (fabric.isLikelyNode) { + replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); + + // onload doesn't fire in some node versions, so we invoke callback manually + _this._element = replacement; + callback && callback(); + } + else { + replacement.onload = function() { + _this._element = replacement; + callback && callback(); + replacement.onload = canvasEl = imgEl = null; + }; + replacement.src = canvasEl.toDataURL('image/png'); + } + + return this; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx, noTransform) { + this._element && + ctx.drawImage( + this._element, + noTransform ? this.left : -this.width/2, + noTransform ? this.top : -this.height/2, + this.width, + this.height + ); + this._renderStroke(ctx); + }, + + /** + * @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. + * @private + * @param {HTMLImageElement|String} element The element representing the image + */ + _initElement: function(element) { + this.setElement(fabric.util.getById(element)); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initConfig: function(options) { + options || (options = { }); + this.setOptions(options); + this._setWidthHeight(options); + if (this._element && this.crossOrigin) { + this._element.crossOrigin = this.crossOrigin; + } + }, + + /** + * @private + * @param {Object} object Object with filters property + * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created + */ + _initFilters: function(object, callback) { + if (object.filters && object.filters.length) { + fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, 'fabric.Image.filters'); + } + else { + callback && callback(); + } + }, + + /** + * @private + * @param {Object} [options] Object with width/height properties + */ + _setWidthHeight: function(options) { + this.width = 'width' in options + ? options.width + : (this.getElement() + ? this.getElement().width || 0 + : 0); + + this.height = 'height' in options + ? options.height + : (this.getElement() + ? this.getElement().height || 0 + : 0); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return 1; + } + }); + + /** + * Default CSS class name for canvas + * @static + * @type String + * @default + */ + fabric.Image.CSS_CANVAS = 'canvas-img'; + + /** + * Alias for getSrc + * @static + */ + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + + /** + * Creates an instance of fabric.Image from its object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an image instance is created + */ + fabric.Image.fromObject = function(object, callback) { + fabric.util.loadImage(object.src, function(img) { + fabric.Image.prototype._initFilters.call(object, object, function(filters) { + object.filters = filters || [ ]; + var instance = new fabric.Image(img, object); + callback && callback(instance); + }); + }, null, object.crossOrigin); + }; + + /** + * Creates an instance of fabric.Image from an URL string + * @static + * @param {String} url URL to create an image from + * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) + * @param {Object} [imgOptions] Options object + */ + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img) { + callback(new fabric.Image(img, imgOptions)); + }, null, imgOptions && imgOptions.crossOrigin); + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) + * @static + * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} + */ + fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height xlink:href'.split(' ')); + + /** + * Returns {@link fabric.Image} instance from an SVG element + * @static + * @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} Instance of fabric.Image + */ + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + + fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, + extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Indicates that instances of this type are async + * @static + * @type Boolean + * @default + */ + fabric.Image.async = true; + + /** + * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 + * @static + * @type Number + * @default + */ + fabric.Image.pngCompression = 1; + +})(typeof exports !== 'undefined' ? exports : this); + + +/** + * @namespace fabric.Image.filters + * @memberOf fabric.Image + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#image_filters} + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + */ +fabric.Image.filters = fabric.Image.filters || { }; + +/** + * Root filter class from which all filter classes inherit from + * @class fabric.Image.filters.BaseFilter + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'BaseFilter', + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { type: this.type }; + }, + + /** + * Returns a JSON representation of an instance + * @return {Object} JSON + */ + toJSON: function() { + // delegate, not alias + return this.toObject(); + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Brightness filter class + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Brightness({ + * brightness: 200 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Brightness = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Brightness', + + /** + * Constructor + * @memberOf fabric.Image.filters.Brightness.prototype + * @param {Object} [options] Options object + * @param {Number} [options.brightness=0] Value to brighten the image up (0..255) + */ + initialize: function(options) { + options = options || { }; + this.brightness = options.brightness || 0; + }, + + /** + * Applies filter to canvas element + * @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); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + brightness: this.brightness + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness + */ + fabric.Image.filters.Brightness.fromObject = function(object) { + return new fabric.Image.filters.Brightness(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Adapted from html5rocks article + * @class fabric.Image.filters.Convolute + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example Sharpen filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 0, -1, 0, + * -1, 5, -1, + * 0, -1, 0 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Blur filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Emboss filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Emboss filter with opaqueness + * var filter = new fabric.Image.filters.Convolute({ + * opaque: true, + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Convolute = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Convolute', + + /** + * Constructor + * @memberOf fabric.Image.filters.Convolute.prototype + * @param {Object} [options] Options object + * @param {Boolean} [options.opaque=false] Opaque value (true/false) + * @param {Array} [options.matrix] Filter matrix + */ + initialize: function(options) { + options = options || { }; + + this.opaque = options.opaque; + this.matrix = options.matrix || [ + 0, 0, 0, + 0, 1, 0, + 0, 0, 0 + ]; + + var canvasEl = fabric.util.createCanvasElement(); + this.tmpCtx = canvasEl.getContext('2d'); + }, + + /** + * @private + */ + _createImageData: function(w, h) { + return this.tmpCtx.createImageData(w, h); + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + + var weights = this.matrix, + context = canvasEl.getContext('2d'), + pixels = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + + side = Math.round(Math.sqrt(weights.length)), + halfSide = Math.floor(side/2), + src = pixels.data, + sw = pixels.width, + sh = pixels.height, + + // pad output by the convolution matrix + w = sw, + h = sh, + output = this._createImageData(w, h), + + dst = output.data, + + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0; + + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + var sy = y, + sx = x, + dstOff = (y * w + x) * 4, + // calculate the weighed sum of the source image pixels that + // fall under the convolution matrix + r = 0, g = 0, b = 0, a = 0; + + for (var cy = 0; cy < side; cy++) { + for (var cx = 0; cx < side; cx++) { + + var scy = sy + cy - halfSide, + scx = sx + cx - halfSide; + + /* jshint maxdepth:5 */ + if (scy < 0 || scy > sh || scx < 0 || scx > sw) { + continue; + } + + var srcOff = (scy * sw + scx) * 4, + wt = weights[cy * side + cx]; + + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + a += src[srcOff + 3] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = a + alphaFac * (255 - a); + } + } + + context.putImageData(output, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + opaque: this.opaque, + matrix: this.matrix + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute + */ + fabric.Image.filters.Convolute.fromObject = function(object) { + return new fabric.Image.filters.Convolute(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * GradientTransparency filter class + * @class fabric.Image.filters.GradientTransparency + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.GradientTransparency#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.GradientTransparency({ + * threshold: 200 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.GradientTransparency = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.GradientTransparency.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'GradientTransparency', + + /** + * Constructor + * @memberOf fabric.Image.filters.GradientTransparency.prototype + * @param {Object} [options] Options object + * @param {Number} [options.threshold=100] Threshold value + */ + initialize: function(options) { + options = options || { }; + this.threshold = options.threshold || 100; + }, + + /** + * Applies filter to canvas element + * @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); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + threshold: this.threshold + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.GradientTransparency} Instance of fabric.Image.filters.GradientTransparency + */ + fabric.Image.filters.GradientTransparency.fromObject = function(object) { + return new fabric.Image.filters.GradientTransparency(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Grayscale image filter class + * @class fabric.Image.filters.Grayscale + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Grayscale(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Grayscale = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Grayscale', + + /** + * Applies filter to canvas element + * @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, + len = imageData.width * imageData.height * 4, + index = 0, + average; + + while (index < len) { + average = (data[index] + data[index + 1] + data[index + 2]) / 3; + data[index] = average; + data[index + 1] = average; + data[index + 2] = average; + index += 4; + } + + context.putImageData(imageData, 0, 0); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale + */ + fabric.Image.filters.Grayscale.fromObject = function() { + return new fabric.Image.filters.Grayscale(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Invert filter class + * @class fabric.Image.filters.Invert + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Invert(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Invert = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Invert', + + /** + * Applies filter to canvas element + * @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); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert + */ + fabric.Image.filters.Invert.fromObject = function() { + return new fabric.Image.filters.Invert(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Mask filter class + * See http://resources.aleph-1.com/mask/ + * @class fabric.Image.filters.Mask + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition + */ + fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Mask', + + /** + * Constructor + * @memberOf fabric.Image.filters.Mask.prototype + * @param {Object} [options] Options object + * @param {fabric.Image} [options.mask] Mask image object + * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3) + */ + initialize: function(options) { + options = options || { }; + + this.mask = options.mask; + this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + if (!this.mask) { + return; + } + + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + maskEl = this.mask.getElement(), + maskCanvasEl = fabric.util.createCanvasElement(), + channel = this.channel, + i, + iLen = imageData.width * imageData.height * 4; + + maskCanvasEl.width = maskEl.width; + maskCanvasEl.height = maskEl.height; + + maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); + + var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), + maskData = maskImageData.data; + + for (i = 0; i < iLen; i += 4) { + data[i + 3] = maskData[i + channel]; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + mask: this.mask.toObject(), + channel: this.channel + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when a mask filter instance is created + */ + fabric.Image.filters.Mask.fromObject = function(object, callback) { + fabric.util.loadImage(object.mask.src, function(img) { + object.mask = new fabric.Image(img, object.mask); + callback && callback(new fabric.Image.filters.Mask(object)); + }); + }; + + /** + * Indicates that instances of this type are async + * @static + * @type Boolean + * @default + */ + fabric.Image.filters.Mask.async = true; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Noise filter class + * @class fabric.Image.filters.Noise + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Noise({ + * noise: 700 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Noise = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Noise', + + /** + * Constructor + * @memberOf fabric.Image.filters.Noise.prototype + * @param {Object} [options] Options object + * @param {Number} [options.noise=0] Noise value + */ + initialize: function(options) { + options = options || { }; + this.noise = options.noise || 0; + }, + + /** + * Applies filter to canvas element + * @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); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + noise: this.noise + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise + */ + fabric.Image.filters.Noise.fromObject = function(object) { + return new fabric.Image.filters.Noise(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Pixelate filter class + * @class fabric.Image.filters.Pixelate + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Pixelate({ + * blocksize: 8 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Pixelate = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Pixelate', + + /** + * Constructor + * @memberOf fabric.Image.filters.Pixelate.prototype + * @param {Object} [options] Options object + * @param {Number} [options.blocksize=4] Blocksize for pixelate + */ + initialize: function(options) { + options = options || { }; + this.blocksize = options.blocksize || 4; + }, + + /** + * Applies filter to canvas element + * @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.height, + jLen = imageData.width, + index, i, j, r, g, b, a; + + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + + index = (i * 4) * jLen + (j * 4); + + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + + /* + blocksize: 4 + + [1,x,x,x,1] + [x,x,x,x,1] + [x,x,x,x,1] + [x,x,x,x,1] + [1,1,1,1,1] + */ + + for (var _i = i, _ilen = i + this.blocksize; _i < _ilen; _i++) { + for (var _j = j, _jlen = j + this.blocksize; _j < _jlen; _j++) { + index = (_i * 4) * jLen + (_j * 4); + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + blocksize: this.blocksize + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate + */ + fabric.Image.filters.Pixelate.fromObject = function(object) { + return new fabric.Image.filters.Pixelate(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Remove white filter class + * @class fabric.Image.filters.RemoveWhite + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.RemoveWhite#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.RemoveWhite({ + * threshold: 40, + * distance: 140 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.RemoveWhite = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.RemoveWhite.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'RemoveWhite', + + /** + * Constructor + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + * @param {Number} [options.threshold=30] Threshold value + * @param {Number} [options.distance=20] Distance value + */ + initialize: function(options) { + options = options || { }; + this.threshold = options.threshold || 30; + this.distance = options.distance || 20; + }, + + /** + * Applies filter to canvas element + * @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); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + threshold: this.threshold, + distance: this.distance + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.RemoveWhite} Instance of fabric.Image.filters.RemoveWhite + */ + fabric.Image.filters.RemoveWhite.fromObject = function(object) { + return new fabric.Image.filters.RemoveWhite(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Sepia filter class + * @class fabric.Image.filters.Sepia + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Sepia(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Sepia = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Sepia', + + /** + * Applies filter to canvas element + * @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); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Sepia} Instance of fabric.Image.filters.Sepia + */ + fabric.Image.filters.Sepia.fromObject = function() { + return new fabric.Image.filters.Sepia(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + /** + * Sepia2 filter class + * @class fabric.Image.filters.Sepia2 + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Sepia2(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Sepia2 = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Sepia2.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Sepia2', + + /** + * Applies filter to canvas element + * @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); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @return {fabric.Image.filters.Sepia2} Instance of fabric.Image.filters.Sepia2 + */ + fabric.Image.filters.Sepia2.fromObject = function() { + return new fabric.Image.filters.Sepia2(); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Tint filter class + * Adapted from https://github.com/mezzoblue/PaintbrushJS + * @class fabric.Image.filters.Tint + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Tint#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters/|ImageFilters demo} + * @example Tint filter with hex color and opacity + * var filter = new fabric.Image.filters.Tint({ + * color: '#3513B0', + * opacity: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Tint filter with rgba color + * var filter = new fabric.Image.filters.Tint({ + * color: 'rgba(53, 21, 176, 0.5)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Tint = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Tint.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Tint', + + /** + * Constructor + * @memberOf fabric.Image.filters.Tint.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to tint the image with + * @param {Number} [options.opacity] Opacity value that controls the tint effect's transparency (0..1) + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + this.opacity = typeof options.opacity !== 'undefined' + ? options.opacity + : new fabric.Color(this.color).getAlpha(); + }, + + /** + * Applies filter to canvas element + * @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, + tintR, tintG, tintB, + r, g, b, alpha1, + source; + + source = new fabric.Color(this.color).getSource(); + + tintR = source[0] * this.opacity; + tintG = source[1] * this.opacity; + tintB = source[2] * this.opacity; + + alpha1 = 1 - this.opacity; + + for (i = 0; i < iLen; i+=4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + // alpha compositing + data[i] = tintR + r * alpha1; + data[i + 1] = tintG + g * alpha1; + data[i + 2] = tintB + b * alpha1; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color, + opacity: this.opacity + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Tint} Instance of fabric.Image.filters.Tint + */ + fabric.Image.filters.Tint.fromObject = function(object) { + return new fabric.Image.filters.Tint(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Multiply filter class + * Adapted from http://www.laurenscorijn.com/articles/colormath-basics + * @class fabric.Image.filters.Multiply + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example Multiply filter with hex color + * var filter = new fabric.Image.filters.Multiply({ + * color: '#F0F' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + * @example Multiply filter with rgb color + * var filter = new fabric.Image.filters.Multiply({ + * color: 'rgb(53, 21, 176)' + * }); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Multiply = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Multiply.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Multiply', + + /** + * Constructor + * @memberOf fabric.Image.filters.Multiply.prototype + * @param {Object} [options] Options object + * @param {String} [options.color=#000000] Color to multiply the image pixels with + */ + initialize: function(options) { + options = options || { }; + + this.color = options.color || '#000000'; + }, + + /** + * Applies filter to canvas element + * @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, + source; + + source = new fabric.Color(this.color).getSource(); + + for (i = 0; i < iLen; i+=4) { + data[i] *= source[0] / 255; + data[i + 1] *= source[1] / 255; + data[i + 2] *= source[2] / 255; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @return {fabric.Image.filters.Multiply} Instance of fabric.Image.filters.Multiply + */ + fabric.Image.filters.Multiply.fromObject = function(object) { + return new fabric.Image.filters.Multiply(object); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global){ + 'use strict'; + + var fabric = global.fabric; + + /** + * Color Blend filter class + * @class fabric.Image.filter.Blend + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.Blend({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.Blend({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + fabric.Image.filters.Blend = fabric.util.createClass({ + type: 'Blend', + + initialize: function(options){ + options = options || {}; + this.color = options.color || '#000'; + this.image = options.image || false; + this.mode = options.mode || 'multiply'; + this.alpha = options.alpha || 1; + }, + + applyTo: function(canvasEl) { + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + tr, tg, tb, + r, g, b, + source, + isImage = false; + + if (this.image) { + // Blend images + isImage = true; + + var _el = fabric.util.createCanvasElement(); + _el.width = this.image.width; + _el.height = this.image.height; + + var tmpCanvas = new fabric.StaticCanvas(_el); + tmpCanvas.add(this.image); + var context2 = tmpCanvas.getContext('2d'); + source = context2.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height).data; + } + else { + // Blend color + source = new fabric.Color(this.color).getSource(); + + tr = source[0] * this.alpha; + tg = source[1] * this.alpha; + tb = source[2] * this.alpha; + } + + for (var i = 0, len = data.length; i < len; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + if (isImage) { + tr = source[i] * this.alpha; + tg = source[i + 1] * this.alpha; + tb = source[i + 2] * this.alpha; + } + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + break; + case 'screen': + data[i] = 1 - (1 - r) * (1 - tr); + data[i + 1] = 1 - (1 - g) * (1 - tg); + data[i + 2] = 1 - (1 - b) * (1 - tb); + break; + case 'add': + data[i] = Math.min(255, r + tr); + data[i + 1] = Math.min(255, g + tg); + data[i + 2] = Math.min(255, b + tb); + break; + case 'diff': + case 'difference': + data[i] = Math.abs(r - tr); + data[i + 1] = Math.abs(g - tg); + data[i + 2] = Math.abs(b - tb); + break; + case 'subtract': + var _r = r - tr, + _g = g - tg, + _b = b - tb; + + data[i] = (_r < 0) ? 0 : _r; + data[i + 1] = (_g < 0) ? 0 : _g; + data[i + 2] = (_b < 0) ? 0 : _b; + break; + case 'darken': + data[i] = Math.min(r, tr); + data[i + 1] = Math.min(g, tg); + data[i + 2] = Math.min(b, tb); + break; + case 'lighten': + data[i] = Math.max(r, tr); + data[i + 1] = Math.max(g, tg); + data[i + 2] = Math.max(b, tb); + break; + } + } + + context.putImageData(imageData, 0, 0); + } + }); + + fabric.Image.filters.Blend.fromObject = function(object) { + return new fabric.Image.filters.Blend(object); + }; +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + supportsLineDash = fabric.StaticCanvas.supports('setLineDash'); + + if (fabric.Text) { + fabric.warn('fabric.Text is already defined'); + return; + } + + var stateProperties = fabric.Object.prototype.stateProperties.concat(); + stateProperties.push( + 'fontFamily', + 'fontWeight', + 'fontSize', + 'text', + 'textDecoration', + 'textAlign', + 'fontStyle', + 'lineHeight', + 'textBackgroundColor', + 'useNative', + 'path' + ); + + /** + * Text class + * @class fabric.Text + * @extends fabric.Object + * @return {fabric.Text} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#text} + * @see {@link fabric.Text#initialize} for constructor definition + */ + fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { + + /** + * Properties which when set cause object to change dimensions + * @type Object + * @private + */ + _dimensionAffectingProps: { + fontSize: true, + fontWeight: true, + fontFamily: true, + textDecoration: true, + fontStyle: true, + lineHeight: true, + stroke: true, + strokeWidth: true, + text: true + }, + + /** + * @private + */ + _reNewline: /\r?\n/, + + /** + * Retrieves object's fontSize + * @method getFontSize + * @memberOf fabric.Text.prototype + * @return {String} Font size (in pixels) + */ + + /** + * Sets object's fontSize + * @method setFontSize + * @memberOf fabric.Text.prototype + * @param {Number} fontSize Font size (in pixels) + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontWeight + * @method getFontWeight + * @memberOf fabric.Text.prototype + * @return {(String|Number)} Font weight + */ + + /** + * Sets object's fontWeight + * @method setFontWeight + * @memberOf fabric.Text.prototype + * @param {(Number|String)} fontWeight Font weight + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontFamily + * @method getFontFamily + * @memberOf fabric.Text.prototype + * @return {String} Font family + */ + + /** + * Sets object's fontFamily + * @method setFontFamily + * @memberOf fabric.Text.prototype + * @param {String} fontFamily Font family + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's text + * @method getText + * @memberOf fabric.Text.prototype + * @return {String} text + */ + + /** + * Sets object's text + * @method setText + * @memberOf fabric.Text.prototype + * @param {String} text Text + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textDecoration + * @method getTextDecoration + * @memberOf fabric.Text.prototype + * @return {String} Text decoration + */ + + /** + * Sets object's textDecoration + * @method setTextDecoration + * @memberOf fabric.Text.prototype + * @param {String} textDecoration Text decoration + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's fontStyle + * @method getFontStyle + * @memberOf fabric.Text.prototype + * @return {String} Font style + */ + + /** + * Sets object's fontStyle + * @method setFontStyle + * @memberOf fabric.Text.prototype + * @param {String} fontStyle Font style + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's lineHeight + * @method getLineHeight + * @memberOf fabric.Text.prototype + * @return {Number} Line height + */ + + /** + * Sets object's lineHeight + * @method setLineHeight + * @memberOf fabric.Text.prototype + * @param {Number} lineHeight Line height + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textAlign + * @method getTextAlign + * @memberOf fabric.Text.prototype + * @return {String} Text alignment + */ + + /** + * Sets object's textAlign + * @method setTextAlign + * @memberOf fabric.Text.prototype + * @param {String} textAlign Text alignment + * @return {fabric.Text} + * @chainable + */ + + /** + * Retrieves object's textBackgroundColor + * @method getTextBackgroundColor + * @memberOf fabric.Text.prototype + * @return {String} Text background color + */ + + /** + * Sets object's textBackgroundColor + * @method setTextBackgroundColor + * @memberOf fabric.Text.prototype + * @param {String} textBackgroundColor Text background color + * @return {fabric.Text} + * @chainable + */ + + /** + * Type of an object + * @type String + * @default + */ + type: 'text', + + /** + * Font size (in pixels) + * @type Number + * @default + */ + fontSize: 40, + + /** + * Font weight (e.g. bold, normal, 400, 600, 800) + * @type {(Number|String)} + * @default + */ + fontWeight: 'normal', + + /** + * Font family + * @type String + * @default + */ + fontFamily: 'Times New Roman', + + /** + * Text decoration Possible values: "", "underline", "overline" or "line-through". + * @type String + * @default + */ + textDecoration: '', + + /** + * Text alignment. Possible values: "left", "center", or "right". + * @type String + * @default + */ + textAlign: 'left', + + /** + * Font style . Possible values: "", "normal", "italic" or "oblique". + * @type String + * @default + */ + fontStyle: '', + + /** + * Line height + * @type Number + * @default + */ + lineHeight: 1.3, + + /** + * Background color of text lines + * @type String + * @default + */ + textBackgroundColor: '', + + /** + * URL of a font file, when using Cufon + * @type String | null + * @default + */ + path: null, + + /** + * Indicates whether canvas native text methods should be used to render text (otherwise, Cufon is used) + * @type Boolean + * @default + */ + useNative: true, + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: stateProperties, + + /** + * When defined, an object is rendered via stroke and this property specifies its color. + * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 + * @type String + * @default + */ + stroke: null, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + initialize: function(text, options) { + options = options || { }; + + this.text = text; + this.__skipDimension = true; + this.setOptions(options); + this.__skipDimension = false; + this._initDimensions(); + }, + + /** + * Renders text object on offscreen canvas, so that it would get dimensions + * @private + */ + _initDimensions: function() { + if (this.__skipDimension) { + return; + } + var canvasEl = fabric.util.createCanvasElement(); + this._render(canvasEl.getContext('2d')); + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of text object + */ + toString: function() { + return '#'; + }, + + /** + * @private + * @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 + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderViaNative: function(ctx) { + var textLines = this.text.split(this._reNewline); + + this._setTextStyles(ctx); + + this.width = this._getTextWidth(ctx, textLines); + this.height = this._getTextHeight(ctx, textLines); + + this.clipTo && fabric.util.clipContext(this, ctx); + + this._renderTextBackground(ctx, textLines); + this._translateForTextAlign(ctx); + this._renderText(ctx, textLines); + + if (this.textAlign !== 'left' && this.textAlign !== 'justify') { + ctx.restore(); + } + + this._renderTextDecoration(ctx, textLines); + this.clipTo && ctx.restore(); + + this._setBoundaries(ctx, textLines); + this._totalLineHeight = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderText: function(ctx, textLines) { + ctx.save(); + this._setShadow(ctx); + this._setupFillRule(ctx); + this._renderTextFill(ctx, textLines); + this._renderTextStroke(ctx, textLines); + this._restoreFillRule(ctx); + this._removeShadow(ctx); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _translateForTextAlign: function(ctx) { + if (this.textAlign !== 'left' && this.textAlign !== 'justify') { + ctx.save(); + ctx.translate(this.textAlign === 'center' ? (this.width / 2) : this.width, 0); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _setBoundaries: function(ctx, textLines) { + this._boundaries = [ ]; + + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); + + this._boundaries.push({ + height: this.fontSize * this.lineHeight, + width: lineWidth, + left: lineLeftOffset + }); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setTextStyles: function(ctx) { + this._setFillStyles(ctx); + this._setStrokeStyles(ctx); + ctx.textBaseline = 'alphabetic'; + if (!this.skipTextAlign) { + ctx.textAlign = this.textAlign; + } + ctx.font = this._getFontDeclaration(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Height of fabric.Text object + */ + _getTextHeight: function(ctx, textLines) { + return this.fontSize * textLines.length * this.lineHeight; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + * @return {Number} Maximum width of fabric.Text object + */ + _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 + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} chars Chars to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + */ + _renderChars: function(method, ctx, chars, left, top) { + ctx[method](chars, left, top); + }, + + /** + * @private + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + * @param {Number} lineIndex Index of a line in a text + */ + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + // lift the line by quarter of fontSize + top -= this.fontSize / 4; + + // short-circuit + if (this.textAlign !== 'justify') { + this._renderChars(method, ctx, line, left, top, lineIndex); + return; + } + + var lineWidth = ctx.measureText(line).width, + totalWidth = this.width; + + if (totalWidth > lineWidth) { + // stretch the line + var words = line.split(/\s+/), + wordsWidth = ctx.measureText(line.replace(/\s+/g, '')).width, + widthDiff = totalWidth - wordsWidth, + numSpaces = words.length - 1, + spaceWidth = widthDiff / numSpaces, + leftOffset = 0; + + for (var i = 0, len = words.length; i < len; i++) { + this._renderChars(method, ctx, words[i], left + leftOffset, top, lineIndex); + leftOffset += ctx.measureText(words[i]).width + spaceWidth; + } + } + else { + this._renderChars(method, ctx, line, left, top, lineIndex); + } + }, + + /** + * @private + * @return {Number} Left offset + */ + _getLeftOffset: function() { + if (fabric.isLikelyNode) { + return 0; + } + return -this.width / 2; + }, + + /** + * @private + * @return {Number} Top offset + */ + _getTopOffset: function() { + return -this.height / 2; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextFill: function(ctx, textLines) { + if (!this.fill && !this._skipFillStrokeCheck) { + return; + } + + this._boundaries = [ ]; + var lineHeights = 0; + + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + + this._renderTextLine( + 'fillText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + lineHeights, + i + ); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextStroke: function(ctx, textLines) { + if ((!this.stroke || this.strokeWidth === 0) && !this._skipFillStrokeCheck) { + return; + } + + var lineHeights = 0; + + ctx.save(); + if (this.strokeDashArray) { + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & this.strokeDashArray.length) { + this.strokeDashArray.push.apply(this.strokeDashArray, this.strokeDashArray); + } + supportsLineDash && ctx.setLineDash(this.strokeDashArray); + } + + ctx.beginPath(); + for (var i = 0, len = textLines.length; i < len; i++) { + var heightOfLine = this._getHeightOfLine(ctx, i, textLines); + lineHeights += heightOfLine; + + this._renderTextLine( + 'strokeText', + ctx, + textLines[i], + this._getLeftOffset(), + this._getTopOffset() + lineHeights, + i + ); + } + ctx.closePath(); + ctx.restore(); + }, + + _getHeightOfLine: function() { + return this.fontSize * this.lineHeight; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextBackground: function(ctx, textLines) { + this._renderTextBoxBackground(ctx); + this._renderTextLinesBackground(ctx, textLines); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextBoxBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + + ctx.save(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + this._getLeftOffset(), + this._getTopOffset(), + this.width, + this.height + ); + + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextLinesBackground: function(ctx, textLines) { + if (!this.textBackgroundColor) { + return; + } + + ctx.save(); + ctx.fillStyle = this.textBackgroundColor; + + for (var i = 0, len = textLines.length; i < len; i++) { + + if (textLines[i] !== '') { + + var lineWidth = this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + this._getLeftOffset() + lineLeftOffset, + this._getTopOffset() + (i * this.fontSize * this.lineHeight), + lineWidth, + this.fontSize * this.lineHeight + ); + } + } + ctx.restore(); + }, + + /** + * @private + * @param {Number} lineWidth Width of text line + * @return {Number} Line left offset + */ + _getLineLeftOffset: function(lineWidth) { + if (this.textAlign === 'center') { + return (this.width - lineWidth) / 2; + } + if (this.textAlign === 'right') { + return this.width - lineWidth; + } + return 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text line + * @return {Number} Line width + */ + _getLineWidth: function(ctx, line) { + return this.textAlign === 'justify' + ? this.width + : ctx.measureText(line).width; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} textLines Array of all text lines + */ + _renderTextDecoration: function(ctx, textLines) { + if (!this.textDecoration) { + return; + } + + // var halfOfVerticalBox = this.originY === 'top' ? 0 : this._getTextHeight(ctx, textLines) / 2; + var halfOfVerticalBox = this._getTextHeight(ctx, textLines) / 2, + _this = this; + + /** @ignore */ + function renderLinesAtOffset(offset) { + for (var i = 0, len = textLines.length; i < len; i++) { + + var lineWidth = _this._getLineWidth(ctx, textLines[i]), + lineLeftOffset = _this._getLineLeftOffset(lineWidth); + + ctx.fillRect( + _this._getLeftOffset() + lineLeftOffset, + ~~((offset + (i * _this._getHeightOfLine(ctx, i, textLines))) - halfOfVerticalBox), + lineWidth, + 1); + } + } + + if (this.textDecoration.indexOf('underline') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight); + } + if (this.textDecoration.indexOf('line-through') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize / 2); + } + if (this.textDecoration.indexOf('overline') > -1) { + renderLinesAtOffset(this.fontSize * this.lineHeight - this.fontSize); + } + }, + + /** + * @private + */ + _getFontDeclaration: function() { + return [ + // node-canvas needs "weight style", while browsers need "style weight" + (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), + (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), + this.fontSize + 'px', + (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) + ].join(' '); + }, + + /** + * Renders text instance on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx, noTransform) { + // do not render if object is not visible + if (!this.visible) { + return; + } + + ctx.save(); + this._transform(ctx, noTransform); + + var m = this.transformMatrix, + isInPathGroup = this.group && this.group.type === 'path-group'; + + if (isInPathGroup) { + ctx.translate(-this.group.width/2, -this.group.height/2); + } + if (m) { + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + if (isInPathGroup) { + ctx.translate(this.left, this.top); + } + this._render(ctx); + ctx.restore(); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var object = extend(this.callSuper('toObject', propertiesToInclude), { + text: this.text, + fontSize: this.fontSize, + fontWeight: this.fontWeight, + fontFamily: this.fontFamily, + fontStyle: this.fontStyle, + lineHeight: this.lineHeight, + textDecoration: this.textDecoration, + textAlign: this.textAlign, + path: this.path, + textBackgroundColor: this.textBackgroundColor, + useNative: this.useNative + }); + if (!this.includeDefaultValues) { + this._removeDefaultValues(object); + } + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var markup = [ ], + textLines = this.text.split(this._reNewline), + offsets = this._getSVGLeftTopOffsets(textLines), + textAndBg = this._getSVGTextAndBg(offsets.lineTop, offsets.textLeft, textLines), + shadowSpans = this._getSVGShadows(offsets.lineTop, textLines); + + // move top offset by an ascent + offsets.textTop += (this._fontAscent ? ((this._fontAscent / 5) * this.lineHeight) : 0); + + this._wrapSVGTextAndBg(markup, textAndBg, shadowSpans, offsets); + + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + + /** + * @private + */ + _getSVGLeftTopOffsets: function(textLines) { + var lineTop = this.useNative + ? this.fontSize * this.lineHeight + : (-this._fontAscent - ((this._fontAscent / 5) * this.lineHeight)), + + textLeft = -(this.width/2), + textTop = this.useNative + ? this.fontSize - 1 + : (this.height/2) - (textLines.length * this.fontSize) - this._totalLineHeight; + + return { + textLeft: textLeft + (this.group && this.group.type === 'path-group' ? this.left : 0), + textTop: textTop + (this.group && this.group.type === 'path-group' ? this.top : 0), + lineTop: lineTop + }; + }, + + /** + * @private + */ + _wrapSVGTextAndBg: function(markup, textAndBg, shadowSpans, offsets) { + markup.push( + '\n', + textAndBg.textBgRects.join(''), + '', + shadowSpans.join(''), + textAndBg.textSpans.join(''), + '\n', + '\n' + ); + }, + + /** + * @private + * @param {Number} lineHeight + * @param {Array} textLines Array of all text lines + * @return {Array} + */ + _getSVGShadows: function(lineHeight, textLines) { + var shadowSpans = [], + i, len, + lineTopOffsetMultiplier = 1; + + if (!this.shadow || !this._boundaries) { + return shadowSpans; + } + + for (i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== '') { + var lineLeftOffset = (this._boundaries && this._boundaries[i]) ? this._boundaries[i].left : 0; + shadowSpans.push( + '', + fabric.util.string.escapeXml(textLines[i]), + ''); + 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; + }, + + /** + * @private + * @param {Number} lineHeight + * @param {Number} textLeftOffset Text left offset + * @param {Array} textLines Array of all text lines + * @return {Object} + */ + _getSVGTextAndBg: function(lineHeight, textLeftOffset, textLines) { + var textSpans = [ ], + textBgRects = [ ], + lineTopOffsetMultiplier = 1; + + // bounding-box background + this._setSVGBg(textBgRects); + + // text and text-background + for (var i = 0, len = textLines.length; i < len; i++) { + if (textLines[i] !== '') { + this._setSVGTextLineText(textLines[i], i, textSpans, lineHeight, lineTopOffsetMultiplier, textBgRects); + 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.textBackgroundColor || !this._boundaries) { + continue; + } + + this._setSVGTextLineBg(textBgRects, i, textLeftOffset, lineHeight); + } + + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + _setSVGTextLineText: function(textLine, i, textSpans, lineHeight, lineTopOffsetMultiplier) { + var lineLeftOffset = (this._boundaries && this._boundaries[i]) + ? toFixed(this._boundaries[i].left, 2) + : 0; + + textSpans.push( + ' elements since setting opacity + // on containing one doesn't work in Illustrator + this._getFillAttributes(this.fill), '>', + fabric.util.string.escapeXml(textLine), + '' + ); + }, + + _setSVGTextLineBg: function(textBgRects, i, textLeftOffset, lineHeight) { + textBgRects.push( + '\n'); + }, + + _setSVGBg: function(textBgRects) { + if (this.backgroundColor && this._boundaries) { + textBgRects.push( + ''); + } + }, + + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @private + * @param {Any} value + * @return {String} + */ + _getFillAttributes: function(value) { + var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + /* _TO_SVG_END_ */ + + /** + * Sets specified property to a specified value + * @param {String} key + * @param {Any} value + * @return {fabric.Text} thisArg + * @chainable + */ + _set: function(key, value) { + if (key === 'fontFamily' && this.path) { + this.path = this.path.replace(/(.*?)([^\/]*)(\.font\.js)/, '$1' + value + '$3'); + } + this.callSuper('_set', key, value); + + if (key in this._dimensionAffectingProps) { + this._initDimensions(); + this.setCoords(); + } + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) + * @static + * @memberOf fabric.Text + * @see: http://www.w3.org/TR/SVG/text.html#TextElement + */ + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( + 'x y dx dy font-family font-style font-weight font-size text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + + /** + * Returns fabric.Text instance from an SVG element (not yet implemented) + * @static + * @memberOf fabric.Text + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @return {fabric.Text} Instance of fabric.Text + */ + fabric.Text.fromElement = function(element, options) { + if (!element) { + return null; + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES); + options = fabric.util.object.extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes); + + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + if (!options.originX) { + options.originX = 'left'; + } + + var text = new fabric.Text(element.textContent, options), + /* + Adjust positioning: + x/y attributes in SVG correspond to the bottom-left corner of text bounding box + top/left properties in Fabric correspond to center point of text bounding box + */ + offX = 0; + + if (text.originX === 'left') { + offX = text.getWidth() / 2; + } + if (text.originX === 'right') { + offX = -text.getWidth() / 2; + } + text.set({ + left: text.getLeft() + offX, + top: text.getTop() - text.getHeight() / 2 + }); + + return text; + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Text instance from an object representation + * @static + * @memberOf fabric.Text + * @param {Object} object Object to create an instance from + * @return {fabric.Text} Instance of fabric.Text + */ + fabric.Text.fromObject = function(object) { + return new fabric.Text(object.text, clone(object)); + }; + + fabric.util.createAccessors(fabric.Text); + +})(typeof exports !== 'undefined' ? exports : this); + + +}).call({}, window, document, html2canvas); \ No newline at end of file diff --git a/hipstamap/dist/html2canvas.svg.min.js b/hipstamap/dist/html2canvas.svg.min.js new file mode 100644 index 0000000..9297c0b --- /dev/null +++ b/hipstamap/dist/html2canvas.svg.min.js @@ -0,0 +1,12 @@ +/* + html2canvas 0.5.0-alpha1 + Copyright (c) 2015 Niklas von Hertzen + + Released under MIT License +*/ +(function(window,document,exports,undefined){var fabric=fabric||{version:"1.4.11"};"undefined"!=typeof exports&&(exports.fabric=fabric),"undefined"!=typeof document&&"undefined"!=typeof window?(fabric.document=document,fabric.window=window):(fabric.document=require("jsdom").jsdom(""),fabric.window=fabric.document.createWindow()),fabric.isTouchSupported="ontouchstart"in fabric.document.documentElement,fabric.isLikelyNode="undefined"!=typeof Buffer&&"undefined"==typeof window,fabric.SHARED_ATTRIBUTES=["display","transform","fill","fill-opacity","fill-rule","opacity","stroke","stroke-dasharray","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke-width"],fabric.DPI=96;var Cufon=function(){function a(a){var b=this.face=a.face;this.glyphs=a.glyphs,this.w=a.w,this.baseSize=parseInt(b["units-per-em"],10),this.family=b["font-family"].toLowerCase(),this.weight=b["font-weight"],this.style=b["font-style"]||"normal",this.viewBox=function(){var a=b.bbox.split(/\s+/),c={minX:parseInt(a[0],10),minY:parseInt(a[1],10),maxX:parseInt(a[2],10),maxY:parseInt(a[3],10)};return c.width=c.maxX-c.minX,c.height=c.maxY-c.minY,c.toString=function(){return[this.minX,this.minY,this.width,this.height].join(" ")},c}(),this.ascent=-parseInt(b.ascent,10),this.descent=-parseInt(b.descent,10),this.height=-this.ascent+this.descent}function b(){var a={},b={oblique:"italic",italic:"oblique"};this.add=function(b){(a[b.style]||(a[b.style]={}))[b.weight]=b},this.get=function(c,d){var e=a[c]||a[b[c]]||a.normal||a.italic||a.oblique;if(!e)return null;if(d={normal:400,bold:700}[d]||parseInt(d,10),e[d])return e[d];var f,g,h={1:1,99:0}[d%100],i=[];h===undefined&&(h=d>400),500==d&&(d=400);for(var j in e)j=parseInt(j,10),(!f||f>j)&&(f=j),(!g||j>g)&&(g=j),i.push(j);return f>d&&(d=f),d>g&&(d=g),i.sort(function(a,b){return(h?a>d&&b>d?b>a:a>b:d>a&&d>b?a>b:b>a)?-1:1}),e[i[0]]}}function c(){function a(a,b){return a.contains?a.contains(b):16&a.compareDocumentPosition(b)}function b(b){var c=b.relatedTarget;c&&!a(this,c)&&d(this)}function c(){d(this)}function d(a){setTimeout(function(){n.replace(a,r.get(a).options,!0)},10)}this.attach=function(a){a.onmouseenter===undefined?(f(a,"mouseover",b),f(a,"mouseout",b)):(f(a,"mouseenter",c),f(a,"mouseleave",c))}}function d(){function a(a){return a.cufid||(a.cufid=++c)}var b={},c=0;this.get=function(c){var d=a(c);return b[d]||(b[d]={})}}function e(a){var b={},c={};this.get=function(c){return b[c]!=undefined?b[c]:a[c]},this.getSize=function(a,b){return c[a]||(c[a]=new p.Size(this.get(a),b))},this.extend=function(a){for(var c in a)b[c]=a[c];return this}}function f(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,function(){return c.call(a,fabric.window.event)})}function g(a,b){var c=r.get(a);return c.options?a:(b.hover&&b.hoverables[a.nodeName.toLowerCase()]&&s.attach(a),c.options=b,a)}function h(a){var b={};return function(c){return b.hasOwnProperty(c)||(b[c]=a.apply(null,arguments)),b[c]}}function i(a,b){b||(b=p.getStyle(a));for(var c,d=p.quotedList(b.get("fontFamily").toLowerCase()),e=0,f=d.length;f>e;++e)if(c=d[e],v[c])return v[c].get(b.get("fontStyle"),b.get("fontWeight"));return null}function j(a){return fabric.document.getElementsByTagName(a)}function k(){for(var a,b={},c=0,d=arguments.length;d>c;++c)for(a in arguments[c])b[a]=arguments[c][a];return b}function l(a,b,c,d,e,f){var g=d.separate;if("none"==g)return u[d.engine].apply(null,arguments);var h,i=fabric.document.createDocumentFragment(),j=b.split(x[g]),k="words"==g;k&&q&&(/^\s/.test(b)&&j.unshift(""),/\s$/.test(b)&&j.push(""));for(var l=0,m=j.length;m>l;++l)h=u[d.engine](a,k?p.textAlign(j[l],c,l,m):j[l],c,d,e,f,m-1>l),h&&i.appendChild(h);return i}function m(a,b){for(var c,d,e,f,h=g(a,b).firstChild;h;h=e){if(e=h.nextSibling,f=!1,1==h.nodeType){if(!h.firstChild)continue;if(!/cufon/.test(h.className)){arguments.callee(h,b);continue}f=!0}if(d||(d=p.getStyle(a).extend(b)),c||(c=i(a,d)),c)if(f)u[b.engine](c,null,d,b,h,a);else{var j=h.data;if("undefined"!=typeof G_vmlCanvasManager&&(j=j.replace(/\r/g,"\n")),""!==j){var k=l(c,j,d,b,h,a);k?h.parentNode.replaceChild(k,h):h.parentNode.removeChild(h)}}}}var n=function(){return n.replace.apply(null,arguments)},o=n.DOM={ready:function(){var a=!1,b={loaded:1,complete:1},c=[],d=function(){if(!a){a=!0;for(var b;b=c.shift();b());}};return fabric.document.addEventListener&&(fabric.document.addEventListener("DOMContentLoaded",d,!1),fabric.window.addEventListener("pageshow",d,!1)),!fabric.window.opera&&fabric.document.readyState&&function(){b[fabric.document.readyState]?d():setTimeout(arguments.callee,10)}(),fabric.document.readyState&&fabric.document.createStyleSheet&&function(){try{fabric.document.body.doScroll("left"),d()}catch(a){setTimeout(arguments.callee,1)}}(),f(fabric.window,"load",d),function(b){arguments.length?a?b():c.push(b):d()}}()},p=n.CSS={Size:function(a,b){this.value=parseFloat(a),this.unit=String(a).match(/[a-z%]*$/)[0]||"px",this.convert=function(a){return a/b*this.value},this.convertFrom=function(a){return a/this.value*b},this.toString=function(){return this.value+this.unit}},getStyle:function(a){return new e(a.style)},quotedList:h(function(a){for(var b,c=[],d=/\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g;b=d.exec(a);)c.push(b[3]||b[1]);return c}),ready:function(){var a=!1,b=[],c=function(){a=!0;for(var c;c=b.shift();c());},d=Object.prototype.propertyIsEnumerable?j("style"):{length:0},e=j("link");return o.ready(function(){for(var a,b=0,f=0,g=e.length;a=e[f],g>f;++f)a.disabled||"stylesheet"!=a.rel.toLowerCase()||++b;fabric.document.styleSheets.length>=d.length+b?c():setTimeout(arguments.callee,10)}),function(c){a?c():b.push(c)}}(),supports:function(a,b){var c=fabric.document.createElement("span").style;return c[a]===undefined?!1:(c[a]=b,c[a]===b)},textAlign:function(a,b,c,d){return"right"==b.get("textAlign")?c>0&&(a=" "+a):d-1>c&&(a+=" "),a},textDecoration:function(a,b){b||(b=this.getStyle(a));for(var c={underline:null,overline:null,"line-through":null},d=a;d.parentNode&&1==d.parentNode.nodeType;){var e=!0;for(var f in c)c[f]||(-1!=b.get("textDecoration").indexOf(f)&&(c[f]=b.get("color")),e=!1);if(e)break;b=this.getStyle(d=d.parentNode)}return c},textShadow:h(function(a){if("none"==a)return null;for(var b,c=[],d={},e=0,f=/(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/gi;b=f.exec(a);)","==b[0]?(c.push(d),d={},e=0):b[1]?d.color=b[1]:d[["offX","offY","blur"][e++]]=b[2];return c.push(d),c}),color:h(function(a){var b={};return b.color=a.replace(/^rgba\((.*?),\s*([\d.]+)\)/,function(a,c,d){return b.opacity=parseFloat(d),"rgb("+c+")"}),b}),textTransform:function(a,b){return a[{uppercase:"toUpperCase",lowercase:"toLowerCase"}[b.get("textTransform")]||"toString"]()}},q=0==" ".split(/\s+/).length,r=new d,s=new c,t=[],u={},v={},w={engine:null,hover:!1,hoverables:{a:!0},printable:!0,selector:fabric.window.Sizzle||fabric.window.jQuery&&function(a){return jQuery(a)}||fabric.window.dojo&&dojo.query||fabric.window.$$&&function(a){return $$(a)}||fabric.window.$&&function(a){return $(a)}||fabric.document.querySelectorAll&&function(a){return fabric.document.querySelectorAll(a)}||j,separate:"words",textShadow:"none"},x={words:/\s+/,characters:""};return n.now=function(){return o.ready(),n},n.refresh=function(){for(var a=t.splice(0,t.length),b=0,c=a.length;c>b;++b)n.replace.apply(null,a[b]);return n},n.registerEngine=function(a,b){return b?(u[a]=b,n.set("engine",a)):n},n.registerFont=function(c){var d=new a(c),e=d.family;return v[e]||(v[e]=new b),v[e].add(d),n.set("fontFamily",'"'+e+'"')},n.replace=function(a,b,c){return b=k(w,b),b.engine?("string"==typeof b.textShadow&&b.textShadow&&(b.textShadow=p.textShadow(b.textShadow)),c||t.push(arguments),(a.nodeType||"string"==typeof a)&&(a=[a]),p.ready(function(){for(var c=0,d=a.length;d>c;++c){var e=a[c];"string"==typeof e?n.replace(b.selector(e),b,!0):m(e,b)}}),n):n},n.replaceElement=function(a,b){return b=k(w,b),"string"==typeof b.textShadow&&b.textShadow&&(b.textShadow=p.textShadow(b.textShadow)),m(a,b)},n.engines=u,n.fonts=v,n.getOptions=function(){return k(w)},n.set=function(a,b){return w[a]=b,n},n}();Cufon.registerEngine("canvas",function(){function a(a,b){var c,d=0,e=0,f=[],g=/([mrvxe])([^a-z]*)/g;a:for(var h=0;c=g.exec(a);++h){var i=c[2].split(",");switch(c[1]){case"v":f[h]={m:"bezierCurveTo",a:[d+~~i[0],e+~~i[1],d+~~i[2],e+~~i[3],d+=~~i[4],e+=~~i[5]]};break;case"r":f[h]={m:"lineTo",a:[d+=~~i[0],e+=~~i[1]]};break;case"m":f[h]={m:"moveTo",a:[d=~~i[0],e=~~i[1]]};break;case"x":f[h]={m:"closePath",a:[]};break;case"e":break a}b[f[h].m].apply(b,f[h].a)}return f}function b(a,b){for(var c=0,d=a.length;d>c;++c){var e=a[c];b[e.m].apply(b,e.a)}}var c=Cufon.CSS.supports("display","inline-block"),d=!c&&("BackCompat"==fabric.document.compatMode||/frameset|transitional/i.test(fabric.document.doctype.publicId)),e=fabric.document.createElement("style");e.type="text/css";var f=fabric.document.createTextNode(".cufon-canvas{text-indent:0}@media screen,projection{.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle"+(d?"":";font-size:1px;line-height:1px")+"}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}"+(c?".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{e.appendChild(f)}catch(g){e.setAttribute("type","text/css"),e.styleSheet.cssText=f.data}return fabric.document.getElementsByTagName("head")[0].appendChild(e),function(d,e,f,g,h){function i(){T.save();var a=0,b=0,c=[{left:0}];g.backgroundColor&&(T.save(),T.fillStyle=g.backgroundColor,T.translate(0,d.ascent),T.fillRect(0,0,A+10,(-d.ascent+d.descent)*D),T.restore()),"right"===g.textAlign?(T.translate(G[b],0),c[0].left=G[b]*U):"center"===g.textAlign&&(T.translate(G[b]/2,0),c[0].left=G[b]/2*U);for(var e=0,f=z.length;f>e;++e)if("\n"!==z[e]){var h=d.glyphs[z[e]]||d.missingGlyph;if(h){var i=Number(h.w||d.w)+n;g.textBackgroundColor&&(T.save(),T.fillStyle=g.textBackgroundColor,T.translate(0,d.ascent),T.fillRect(0,0,i+10,-d.ascent+d.descent),T.restore()),T.translate(i,0),a+=i,e==f-1&&(c[c.length-1].width=a*U,c[c.length-1].height=(-d.ascent+d.descent)*U)}}else{b++;var j=-d.ascent-d.ascent/5*g.lineHeight,k=c[c.length-1],l={left:0};k.width=a*U,k.height=(-d.ascent+d.descent)*U,"right"===g.textAlign?(T.translate(-A,j),T.translate(G[b],0),l.left=G[b]*U):"center"===g.textAlign?(T.translate(-a-G[b-1]/2,j),T.translate(G[b]/2,0),l.left=G[b]/2*U):T.translate(-a,j),c.push(l),a=0}T.restore(),Cufon.textOptions.boundaries=c}function j(c){T.fillStyle=c||Cufon.textOptions.color||f.get("color");var e=0,h=0;"right"===g.textAlign?T.translate(G[h],0):"center"===g.textAlign&&T.translate(G[h]/2,0);for(var i=0,j=z.length;j>i;++i)if("\n"!==z[i]){var k=d.glyphs[z[i]]||d.missingGlyph;if(k){var l=Number(k.w||d.w)+n;W&&(T.save(),T.strokeStyle=T.fillStyle,T.lineWidth+=T.lineWidth,T.beginPath(),W.underline&&(T.moveTo(0,-d.face["underline-position"]+.5),T.lineTo(l,-d.face["underline-position"]+.5)),W.overline&&(T.moveTo(0,d.ascent+.5),T.lineTo(l,d.ascent+.5)),W["line-through"]&&(T.moveTo(0,-d.descent+.5),T.lineTo(l,-d.descent+.5)),T.stroke(),T.restore()),X&&(T.save(),T.transform(1,0,-.25,1,0,0)),T.beginPath(),k.d&&(k.code?b(k.code,T):k.code=a("m"+k.d,T)),T.fill(),g.strokeStyle&&(T.closePath(),T.save(),T.lineWidth=g.strokeWidth,T.strokeStyle=g.strokeStyle,T.stroke(),T.restore()),X&&T.restore(),T.translate(l,0),e+=l}}else{h++;var m=-d.ascent-d.ascent/5*g.lineHeight;"right"===g.textAlign?(T.translate(-A,m),T.translate(G[h],0)):"center"===g.textAlign?(T.translate(-e-G[h-1]/2,m),T.translate(G[h]/2,0)):T.translate(-e,m),e=0}}var k=null===e,l=d.viewBox,m=f.getSize("fontSize",d.baseSize),n=f.get("letterSpacing");n="normal"==n?0:m.convertFrom(parseInt(n,10));var o=0,p=0,q=0,r=0,s=g.textShadow,t=[];if(Cufon.textOptions.shadowOffsets=[],Cufon.textOptions.shadows=null,s){Cufon.textOptions.shadows=s;for(var u=0,v=s.length;v>u;++u){var w=s[u],x=m.convertFrom(parseFloat(w.offX)),y=m.convertFrom(parseFloat(w.offY));t[u]=[x,y]}}for(var z=Cufon.CSS.textTransform(k?h.alt:e,f).split(""),A=0,B=null,C=0,D=1,E=[],u=0,v=z.length;v>u;++u)if("\n"!==z[u]){var F=d.glyphs[z[u]]||d.missingGlyph;F&&(A+=B=Number(F.w||d.w)+n)}else D++,A>C&&(C=A),E.push(A),A=0;E.push(A),A=Math.max(C,A);for(var G=[],u=E.length;u--;)G[u]=A-E[u];if(null===B)return null;p+=l.width-B,r+=l.minX;var H,I;if(k)H=h,I=h.firstChild;else if(H=fabric.document.createElement("span"),H.className="cufon cufon-canvas",H.alt=e,I=fabric.document.createElement("canvas"),H.appendChild(I),g.printable){var J=fabric.document.createElement("span");J.className="cufon-alt",J.appendChild(fabric.document.createTextNode(e)),H.appendChild(J)}var K=H.style,L=I.style||{},M=m.convert(l.height-o+q),N=Math.ceil(M),O=N/M;I.width=Math.ceil(m.convert(A+p-r)*O),I.height=N,o+=l.minY,L.top=Math.round(m.convert(o-d.ascent))+"px",L.left=Math.round(m.convert(r))+"px";var P=Math.ceil(m.convert(A*O)),Q=P+"px",R=m.convert(d.height),S=(g.lineHeight-1)*m.convert(-d.ascent/5)*(D-1);Cufon.textOptions.width=P,Cufon.textOptions.height=R*D+S,Cufon.textOptions.lines=D,Cufon.textOptions.totalLineHeight=S,c?(K.width=Q,K.height=R+"px"):(K.paddingLeft=Q,K.paddingBottom=R-1+"px");var T=Cufon.textOptions.context||I.getContext("2d"),U=N/l.height;Cufon.textOptions.fontAscent=d.ascent*U,Cufon.textOptions.boundaries=null;for(var V=Cufon.textOptions.shadowOffsets,u=t.length;u--;)V[u]=[t[u][0]*U,t[u][1]*U];T.save(),T.scale(U,U),T.translate(-r-1/U*I.width/2+(Cufon.fonts[d.family].offsetLeft||0),-o-Cufon.textOptions.height/U/2+(Cufon.fonts[d.family].offsetTop||0)),T.lineWidth=d.face["underline-thickness"],T.save();var W=Cufon.getTextDecoration(g),X="italic"===g.fontStyle;if(T.save(),i(),s)for(var u=0,v=s.length;v>u;++u){var w=s[u];T.save(),T.translate.apply(T,t[u]),j(w.color),T.restore()}return j(),T.restore(),T.restore(),T.restore(),H}}()),Cufon.registerEngine("vml",function(){function a(a,c){return b(a,/(?:em|ex|%)$/i.test(c)?"1em":c)}function b(a,b){if(/px$/i.test(b))return parseFloat(b);var c=a.style.left,d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left,a.style.left=b;var e=a.style.pixelLeft;return a.style.left=c,a.runtimeStyle.left=d,e}if(fabric.document.namespaces){var c=fabric.document.createElement("canvas");if(!(c&&c.getContext&&c.getContext.apply)){null==fabric.document.namespaces.cvml&&fabric.document.namespaces.add("cvml","urn:schemas-microsoft-com:vml");var d=fabric.document.createElement("cvml:shape");if(d.style.behavior="url(#default#VML)",d.coordsize)return d=null,fabric.document.write(''),function(c,d,e,f,g,h,i){var j=null===d;j&&(d=g.alt);var k=c.viewBox,l=e.computedFontSize||(e.computedFontSize=new Cufon.CSS.Size(a(h,e.get("fontSize"))+"px",c.baseSize)),m=e.computedLSpacing;m==undefined&&(m=e.get("letterSpacing"),e.computedLSpacing=m="normal"==m?0:~~l.convertFrom(b(h,m)));var n,o;if(j)n=g,o=g.firstChild;else{if(n=fabric.document.createElement("span"),n.className="cufon cufon-vml",n.alt=d,o=fabric.document.createElement("span"),o.className="cufon-vml-canvas",n.appendChild(o),f.printable){var p=fabric.document.createElement("span");p.className="cufon-alt",p.appendChild(fabric.document.createTextNode(d)),n.appendChild(p)}i||n.appendChild(fabric.document.createElement("cvml:shape"))}var q=n.style,r=o.style,s=l.convert(k.height),t=Math.ceil(s),u=t/s,v=k.minX,w=k.minY;r.height=t,r.top=Math.round(l.convert(w-c.ascent)),r.left=Math.round(l.convert(v)),q.height=l.convert(c.height)+"px";for(var x,y,z=(Cufon.getTextDecoration(f),e.get("color")),A=Cufon.CSS.textTransform(d,e).split(""),B=0,C=0,D=null,E=f.textShadow,F=0,G=0,H=A.length;H>F;++F)x=c.glyphs[A[F]]||c.missingGlyph,x&&(B+=D=~~(x.w||c.w)+m);if(null===D)return null;var I,J=-v+B+(k.width-D),K=l.convert(J*u),L=Math.round(K),M=J+","+k.height,N="r"+M+"nsnf";for(F=0;H>F;++F)if(x=c.glyphs[A[F]]||c.missingGlyph){j?(y=o.childNodes[G],y.firstChild&&y.removeChild(y.firstChild)):(y=fabric.document.createElement("cvml:shape"),o.appendChild(y)),y.stroked="f",y.coordsize=M,y.coordorigin=I=v-C+","+w,y.path=(x.d?"m"+x.d+"xe":"")+"m"+I+N,y.fillcolor=z;var O=y.style;if(O.width=L,O.height=t,E){var P,Q=E[0],R=E[1],S=Cufon.CSS.color(Q.color),T=fabric.document.createElement("cvml:shadow");T.on="t",T.color=S.color,T.offset=Q.offX+","+Q.offY,R&&(P=Cufon.CSS.color(R.color),T.type="double",T.color2=P.color,T.offset2=R.offX+","+R.offY),T.opacity=S.opacity||P&&P.opacity||1,y.appendChild(T)}C+=~~(x.w||c.w)+m,++G}return q.width=Math.max(Math.ceil(l.convert(B*u)),0),n}}}}()),Cufon.getTextDecoration=function(a){return{underline:"underline"===a.textDecoration,overline:"overline"===a.textDecoration,"line-through":"line-through"===a.textDecoration}},"undefined"!=typeof exports&&(exports.Cufon=Cufon),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){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(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\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,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(){function a(a,b){this.__eventListeners[a]&&(b?fabric.util.removeFromArray(this.__eventListeners[a],b):this.__eventListeners[a].length=0)}function b(a,b){if(this.__eventListeners||(this.__eventListeners={}),1===arguments.length)for(var c in a)this.on(c,a[c]);else this.__eventListeners[a]||(this.__eventListeners[a]=[]),this.__eventListeners[a].push(b);return this}function c(b,c){if(this.__eventListeners){if(0===arguments.length)this.__eventListeners={};else if(1===arguments.length&&"object"==typeof arguments[0])for(var d in b)a.call(this,d,b[d]);else a.call(this,b,c);return this}}function d(a,b){if(this.__eventListeners){var c=this.__eventListeners[a];if(c){for(var d=0,e=c.length;e>d;d++)c[d].call(this,b||{});return this}}}fabric.Observable={observe:b,stopObserving:c,fire:d,on:b,off:c,trigger:d}}(),fabric.Collection={add:function(){this._objects.push.apply(this._objects,arguments);for(var a=0,b=arguments.length;b>a;a++)this._onObjectAdded(arguments[a]);return this.renderOnAddRemove&&this.renderAll(),this},insertAt:function(a,b,c){var d=this.getObjects();return c?d[b]=a:d.splice(b,0,a),this._onObjectAdded(a),this.renderOnAddRemove&&this.renderAll(),this},remove:function(){for(var a,b=this.getObjects(),c=0,d=arguments.length;d>c;c++)a=b.indexOf(arguments[c]),-1!==a&&(b.splice(a,1),this._onObjectRemoved(arguments[c]));return this.renderOnAddRemove&&this.renderAll(),this},forEachObject:function(a,b){for(var c=this.getObjects(),d=c.length;d--;)a.call(b,c[d],d,c);return this},getObjects:function(a){return"undefined"==typeof a?this._objects:this._objects.filter(function(b){return b.type===a})},item:function(a){return this.getObjects()[a]},isEmpty:function(){return 0===this.getObjects().length},size:function(){return this.getObjects().length},contains:function(a){return this.getObjects().indexOf(a)>-1},complexity:function(){return this.getObjects().reduce(function(a,b){return a+=b.complexity?b.complexity():0},0)}},function(a){var b=Math.sqrt,c=Math.atan2,d=Math.PI/180;fabric.util={removeFromArray:function(a,b){var c=a.indexOf(b);return-1!==c&&a.splice(c,1),a},getRandomInt:function(a,b){return Math.floor(Math.random()*(b-a+1))+a},degreesToRadians:function(a){return a*d},radiansToDegrees:function(a){return a/d},rotatePoint:function(a,b,c){var d=Math.sin(c),e=Math.cos(c);a.subtractEquals(b);var f=a.x*e-a.y*d,g=a.x*d+a.y*e;return new fabric.Point(f,g).addEquals(b)},transformPoint:function(a,b,c){return c?new fabric.Point(b[0]*a.x+b[1]*a.y,b[2]*a.x+b[3]*a.y):new fabric.Point(b[0]*a.x+b[1]*a.y+b[4],b[2]*a.x+b[3]*a.y+b[5])},invertTransform:function(a){var b=a.slice(),c=1/(a[0]*a[3]-a[1]*a[2]);b=[c*a[3],-c*a[1],-c*a[2],c*a[0],0,0];var d=fabric.util.transformPoint({x:a[4],y:a[5]},b);return b[4]=-d.x,b[5]=-d.y,b},toFixed:function(a,b){return parseFloat(Number(a).toFixed(b))},parseUnit:function(a){var b=/\D{0,2}$/.exec(a),c=parseFloat(a);switch(b[0]){case"mm":return c*fabric.DPI/25.4;case"cm":return c*fabric.DPI/2.54;case"in":return c*fabric.DPI;case"pt":return c*fabric.DPI/72;case"pc":return c*fabric.DPI/72*12;default:return c}},falseFunction:function(){return!1},getKlass:function(a,b){return a=fabric.util.string.camelize(a.charAt(0).toUpperCase()+a.slice(1)),fabric.util.resolveNamespace(b)[a]},resolveNamespace:function(b){if(!b)return fabric;for(var c=b.split("."),d=c.length,e=a||fabric.window,f=0;d>f;++f)e=e[c[f]];return e},loadImage:function(a,b,c,d){if(!a)return void(b&&b.call(c,a));var e=fabric.util.createImage();e.onload=function(){b&&b.call(c,e),e=e.onload=e.onerror=null},e.onerror=function(){fabric.log("Error loading "+e.src),b&&b.call(c,null,!0),e=e.onload=e.onerror=null},0!==a.indexOf("data")&&"undefined"!=typeof d&&(e.crossOrigin=d),e.src=a},enlivenObjects:function(a,b,c,d){function e(){++g===h&&b&&b(f)}a=a||[];var f=[],g=0,h=a.length;return h?void a.forEach(function(a,b){if(!a||!a.type)return void e();var g=fabric.util.getKlass(a.type,c);g.async?g.fromObject(a,function(c,g){g||(f[b]=c,d&&d(a,f[b])),e()}):(f[b]=g.fromObject(a),d&&d(a,f[b]),e())}):void(b&&b(f))},groupSVGElements:function(a,b,c){var d;return d=new fabric.PathGroup(a,b),"undefined"!=typeof c&&d.setSourcePath(c),d},populateWithProperties:function(a,b,c){if(c&&"[object Array]"===Object.prototype.toString.call(c))for(var d=0,e=c.length;e>d;d++)c[d]in a&&(b[c[d]]=a[c[d]])},drawDashedLine:function(a,d,e,f,g,h){var i=f-d,j=g-e,k=b(i*i+j*j),l=c(j,i),m=h.length,n=0,o=!0;for(a.save(),a.translate(d,e),a.moveTo(0,0),a.rotate(l),d=0;k>d;)d+=h[n++%m],d>k&&(d=k),a[o?"lineTo":"moveTo"](d,0),o=!o;a.restore()},createCanvasElement:function(a){return a||(a=fabric.document.createElement("canvas")),a.getContext||"undefined"==typeof G_vmlCanvasManager||G_vmlCanvasManager.initElement(a),a},createImage:function(){return fabric.isLikelyNode?new(require("canvas").Image):fabric.document.createElement("img")},createAccessors:function(a){for(var b=a.prototype,c=b.stateProperties.length;c--;){var d=b.stateProperties[c],e=d.charAt(0).toUpperCase()+d.slice(1),f="set"+e,g="get"+e;b[g]||(b[g]=function(a){return new Function('return this.get("'+a+'")')}(d)),b[f]||(b[f]=function(a){return new Function("value",'return this.set("'+a+'", value)')}(d))}},clipContext:function(a,b){b.save(),b.beginPath(),a.clipTo(b),b.clip()},multiplyTransformMatrices:function(a,b){for(var c=[[a[0],a[2],a[4]],[a[1],a[3],a[5]],[0,0,1]],d=[[b[0],b[2],b[4]],[b[1],b[3],b[5]],[0,0,1]],e=[],f=0;3>f;f++){e[f]=[];for(var g=0;3>g;g++){for(var h=0,i=0;3>i;i++)h+=c[f][i]*d[i][g];e[f][g]=h}}return[e[0][0],e[1][0],e[0][1],e[1][1],e[0][2],e[1][2]]},getFunctionBody:function(a){return(String(a).match(/function[^{]*\{([\s\S]*)\}/)||{})[1]},isTransparent:function(a,b,c,d){d>0&&(b>d?b-=d:b=0,c>d?c-=d:c=0);for(var e=!0,f=a.getImageData(b,c,2*d||1,2*d||1),g=3,h=f.data.length;h>g;g+=4){var i=f.data[g];if(e=0>=i,e===!1)break}return f=null,e}}}("undefined"!=typeof exports?exports:this),function(){function a(a,e,g,h,i,j,k){var l=f.call(arguments);if(d[l])return d[l];var m=Math.PI,n=k*(m/180),o=Math.sin(n),p=Math.cos(n),q=0,r=0;g=Math.abs(g),h=Math.abs(h);var s=-p*a-o*e,t=-p*e+o*a,u=g*g,v=h*h,w=t*t,x=s*s,y=4*u*v-u*w-v*x,z=0;if(0>y){var A=Math.sqrt(1-.25*y/(u*v));g*=A,h*=A}else z=(i===j?-.5:.5)*Math.sqrt(y/(u*w+v*x));var B=z*g*t/h,C=-z*h*s/g,D=p*B-o*C+a/2,E=o*B+p*C+e/2,F=c(1,0,(s-B)/g,(t-C)/h),G=c((s-B)/g,(t-C)/h,(-s-B)/g,(-t-C)/h);0===j&&G>0?G-=2*m:1===j&&0>G&&(G+=2*m);for(var H=Math.ceil(Math.abs(G/(.5*m))),I=[],J=G/H,K=8/3*Math.sin(J/4)*Math.sin(J/4)/Math.sin(J/2),L=F+J,M=0;H>M;M++)I[M]=b(F,L,p,o,g,h,D,E,K,q,r),q=I[M][4],r=I[M][5],F+=J,L+=J;return d[l]=I,I}function b(a,b,c,d,g,h,i,j,k,l,m){var n=f.call(arguments);if(e[n])return e[n];var o=Math.cos(a),p=Math.sin(a),q=Math.cos(b),r=Math.sin(b),s=c*g*q-d*h*r+i,t=d*g*q+c*h*r+j,u=l+k*(-c*g*p-d*h*o),v=m+k*(-d*g*p+c*h*o),w=s+k*(c*g*r+d*h*q),x=t+k*(d*g*r-c*h*q);return e[n]=[u,v,w,x,s,t],e[n]}function c(a,b,c,d){var e=Math.atan2(b,a),f=Math.atan2(d,c);return f>=e?f-e:2*Math.PI-(e-f)}var d={},e={},f=Array.prototype.join;fabric.util.drawArc=function(b,c,d,e){for(var f=e[0],g=e[1],h=e[2],i=e[3],j=e[4],k=e[5],l=e[6],m=[[],[],[],[]],n=a(k-c,l-d,f,g,i,j,h),o=0,p=n.length;p>o;o++)m[o][0]=n[o][0]+c,m[o][1]=n[o][1]+d,m[o][2]=n[o][2]+c,m[o][3]=n[o][3]+d,m[o][4]=n[o][4]+c,m[o][5]=n[o][5]+d,b.bezierCurveTo.apply(b,m[o])}}(),function(){function a(a,b){for(var c=e.call(arguments,2),d=[],f=0,g=a.length;g>f;f++)d[f]=c.length?a[f][b].apply(a[f],c):a[f][b].call(a[f]);return d}function b(a,b){return d(a,b,function(a,b){return a>=b})}function c(a,b){return d(a,b,function(a,b){return b>a})}function d(a,b,c){if(a&&0!==a.length){var d=a.length-1,e=b?a[d][b]:a[d];if(b)for(;d--;)c(a[d][b],e)&&(e=a[d][b]);else for(;d--;)c(a[d],e)&&(e=a[d]);return e}}var e=Array.prototype.slice;fabric.util.array={invoke:a,min:c,max:b}}(),function(){function a(a,b){for(var c in b)a[c]=b[c];return a}function b(b){return a({},b)}fabric.util.object={extend:a,clone:b}}(),function(){function a(a){return a.replace(/-+(.)?/g,function(a,b){return b?b.toUpperCase():""})}function b(a,b){return a.charAt(0).toUpperCase()+(b?a.slice(1):a.slice(1).toLowerCase())}function c(a){return a.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}fabric.util.string={camelize:a,capitalize:b,escapeXml:c}}(),function(){function a(){}function b(a){var b=this.constructor.superclass.prototype[a];return arguments.length>1?b.apply(this,d.call(arguments,1)):b.call(this)}function c(){function c(){this.initialize.apply(this,arguments)}var f=null,h=d.call(arguments,0);"function"==typeof h[0]&&(f=h.shift()),c.superclass=f,c.subclasses=[],f&&(a.prototype=f.prototype,c.prototype=new a,f.subclasses.push(c));for(var i=0,j=h.length;j>i;i++)g(c,h[i],f);return c.prototype.initialize||(c.prototype.initialize=e),c.prototype.constructor=c,c.prototype.callSuper=b,c}var d=Array.prototype.slice,e=function(){},f=function(){for(var a in{toString:1})if("toString"===a)return!1;return!0}(),g=function(a,b,c){for(var d in b)a.prototype[d]=d in a.prototype&&"function"==typeof a.prototype[d]&&(b[d]+"").indexOf("callSuper")>-1?function(a){return function(){var d=this.constructor.superclass;this.constructor.superclass=c;var e=b[a].apply(this,arguments);return this.constructor.superclass=d,"initialize"!==a?e:void 0}}(d):b[d],f&&(b.toString!==Object.prototype.toString&&(a.prototype.toString=b.toString),b.valueOf!==Object.prototype.valueOf&&(a.prototype.valueOf=b.valueOf))};fabric.util.createClass=c}(),function(){function a(a){var b,c,d=Array.prototype.slice.call(arguments,1),e=d.length;for(c=0;e>c;c++)if(b=typeof a[d[c]],!/^(?:function|object|unknown)$/.test(b))return!1;return!0}function b(a,b){return{handler:b,wrappedHandler:c(a,b)}}function c(a,b){return function(c){b.call(g(a),c||fabric.window.event)}}function d(a,b){return function(c){if(p[a]&&p[a][b])for(var d=p[a][b],e=0,f=d.length;f>e;e++)d[e].call(this,c||fabric.window.event)}}function e(a,b){a||(a=fabric.window.event);var c=a.target||(typeof a.srcElement!==i?a.srcElement:null),d=fabric.util.getScrollLeftTop(c,b);return{x:q(a)+d.left,y:r(a)+d.top}}function f(a,b,c){var d="touchend"===a.type?"changedTouches":"touches";return a[d]&&a[d][0]?a[d][0][b]-(a[d][0][b]-a[d][0][c])||a[c]:a[c]}var g,h,i="unknown",j=function(){var a=0;return function(b){return b.__uniqueID||(b.__uniqueID="uniqueID__"+a++)}}();!function(){var a={};g=function(b){return a[b]},h=function(b,c){a[b]=c}}();var k,l,m=a(fabric.document.documentElement,"addEventListener","removeEventListener")&&a(fabric.window,"addEventListener","removeEventListener"),n=a(fabric.document.documentElement,"attachEvent","detachEvent")&&a(fabric.window,"attachEvent","detachEvent"),o={},p={};m?(k=function(a,b,c){a.addEventListener(b,c,!1)},l=function(a,b,c){a.removeEventListener(b,c,!1)}):n?(k=function(a,c,d){var e=j(a);h(e,a),o[e]||(o[e]={}),o[e][c]||(o[e][c]=[]);var f=b(e,d);o[e][c].push(f),a.attachEvent("on"+c,f.wrappedHandler)},l=function(a,b,c){var d,e=j(a);if(o[e]&&o[e][b])for(var f=0,g=o[e][b].length;g>f;f++)d=o[e][b][f],d&&d.handler===c&&(a.detachEvent("on"+b,d.wrappedHandler),o[e][b][f]=null)}):(k=function(a,b,c){var e=j(a);if(p[e]||(p[e]={}),!p[e][b]){p[e][b]=[];var f=a["on"+b];f&&p[e][b].push(f),a["on"+b]=d(e,b)}p[e][b].push(c)},l=function(a,b,c){var d=j(a);if(p[d]&&p[d][b])for(var e=p[d][b],f=0,g=e.length;g>f;f++)e[f]===c&&e.splice(f,1)}),fabric.util.addListener=k,fabric.util.removeListener=l;var q=function(a){return typeof a.clientX!==i?a.clientX:0},r=function(a){return typeof a.clientY!==i?a.clientY:0};fabric.isTouchSupported&&(q=function(a){return f(a,"pageX","clientX")},r=function(a){return f(a,"pageY","clientY") +}),fabric.util.getPointer=e,fabric.util.object.extend(fabric.util,fabric.Observable)}(),function(){function a(a,b){var c=a.style;if(!c)return a;if("string"==typeof b)return a.style.cssText+=";"+b,b.indexOf("opacity")>-1?f(a,b.match(/opacity:\s*(\d?\.?\d*)/)[1]):a;for(var d in b)if("opacity"===d)f(a,b[d]);else{var e="float"===d||"cssFloat"===d?"undefined"==typeof c.styleFloat?"cssFloat":"styleFloat":d;c[e]=b[d]}return a}var b=fabric.document.createElement("div"),c="string"==typeof b.style.opacity,d="string"==typeof b.style.filter,e=/alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/,f=function(a){return a};c?f=function(a,b){return a.style.opacity=b,a}:d&&(f=function(a,b){var c=a.style;return a.currentStyle&&!a.currentStyle.hasLayout&&(c.zoom=1),e.test(c.filter)?(b=b>=.9999?"":"alpha(opacity="+100*b+")",c.filter=c.filter.replace(e,b)):c.filter+=" alpha(opacity="+100*b+")",a}),fabric.util.setStyle=a}(),function(){function a(a){return"string"==typeof a?fabric.document.getElementById(a):a}function b(a,b){var c=fabric.document.createElement(a);for(var d in b)"class"===d?c.className=b[d]:"for"===d?c.htmlFor=b[d]:c.setAttribute(d,b[d]);return c}function c(a,b){a&&-1===(" "+a.className+" ").indexOf(" "+b+" ")&&(a.className+=(a.className?" ":"")+b)}function d(a,c,d){return"string"==typeof c&&(c=b(c,d)),a.parentNode&&a.parentNode.replaceChild(c,a),c.appendChild(a),c}function e(a,b){var c,d,e=0,f=0,g=fabric.document.documentElement,h=fabric.document.body||{scrollLeft:0,scrollTop:0};for(d=a;a&&a.parentNode&&!c;)a=a.parentNode,a!==fabric.document&&"fixed"===fabric.util.getElementStyle(a,"position")&&(c=a),a!==fabric.document&&d!==b&&"absolute"===fabric.util.getElementStyle(a,"position")?(e=0,f=0):a===fabric.document?(e=h.scrollLeft||g.scrollLeft||0,f=h.scrollTop||g.scrollTop||0):(e+=a.scrollLeft||0,f+=a.scrollTop||0);return{left:e,top:f}}function f(a){var b,c,d=a&&a.ownerDocument,e={left:0,top:0},f={left:0,top:0},g={borderLeftWidth:"left",borderTopWidth:"top",paddingLeft:"left",paddingTop:"top"};if(!d)return{left:0,top:0};for(var h in g)f[g[h]]+=parseInt(k(a,h),10)||0;return b=d.documentElement,"undefined"!=typeof a.getBoundingClientRect&&(e=a.getBoundingClientRect()),c=fabric.util.getScrollLeftTop(a,null),{left:e.left+c.left-(b.clientLeft||0)+f.left,top:e.top+c.top-(b.clientTop||0)+f.top}}var g,h=Array.prototype.slice,i=function(a){return h.call(a,0)};try{g=i(fabric.document.childNodes)instanceof Array}catch(j){}g||(i=function(a){for(var b=new Array(a.length),c=a.length;c--;)b[c]=a[c];return b});var k;k=fabric.document.defaultView&&fabric.document.defaultView.getComputedStyle?function(a,b){return fabric.document.defaultView.getComputedStyle(a,null)[b]}:function(a,b){var c=a.style[b];return!c&&a.currentStyle&&(c=a.currentStyle[b]),c},function(){function a(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=fabric.util.falseFunction),d?a.style[d]="none":"string"==typeof a.unselectable&&(a.unselectable="on"),a}function b(a){return"undefined"!=typeof a.onselectstart&&(a.onselectstart=null),d?a.style[d]="":"string"==typeof a.unselectable&&(a.unselectable=""),a}var c=fabric.document.documentElement.style,d="userSelect"in c?"userSelect":"MozUserSelect"in c?"MozUserSelect":"WebkitUserSelect"in c?"WebkitUserSelect":"KhtmlUserSelect"in c?"KhtmlUserSelect":"";fabric.util.makeElementUnselectable=a,fabric.util.makeElementSelectable=b}(),function(){function a(a,b){var c=fabric.document.getElementsByTagName("head")[0],d=fabric.document.createElement("script"),e=!0;d.onload=d.onreadystatechange=function(a){if(e){if("string"==typeof this.readyState&&"loaded"!==this.readyState&&"complete"!==this.readyState)return;e=!1,b(a||fabric.window.event),d=d.onload=d.onreadystatechange=null}},d.src=a,c.appendChild(d)}fabric.util.getScript=a}(),fabric.util.getById=a,fabric.util.toArray=i,fabric.util.makeElement=b,fabric.util.addClass=c,fabric.util.wrapElement=d,fabric.util.getScrollLeftTop=e,fabric.util.getElementOffset=f,fabric.util.getElementStyle=k}(),function(){function a(a,b){return a+(/\?/.test(a)?"&":"?")+b}function b(){}function c(c,e){e||(e={});var f,g=e.method?e.method.toUpperCase():"GET",h=e.onComplete||function(){},i=d();return i.onreadystatechange=function(){4===i.readyState&&(h(i),i.onreadystatechange=b)},"GET"===g&&(f=null,"string"==typeof e.parameters&&(c=a(c,e.parameters))),i.open(g,c,!0),("POST"===g||"PUT"===g)&&i.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),i.send(f),i}var d=function(){for(var a=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP")},function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0")},function(){return new XMLHttpRequest}],b=a.length;b--;)try{var c=a[b]();if(c)return a[b]}catch(d){}}();fabric.util.request=c}(),fabric.log=function(){},fabric.warn=function(){},"undefined"!=typeof console&&["log","warn"].forEach(function(a){"undefined"!=typeof console[a]&&console[a].apply&&(fabric[a]=function(){return console[a].apply(console,arguments)})}),function(a){"use strict";function b(a){return a in w?w[a]:a}function c(a,b,c){var d,e="[object Array]"===Object.prototype.toString.call(b);return"fill"!==a&&"stroke"!==a||"none"!==b?"fillRule"===a?b="evenodd"===b?"destination-over":b:"strokeDashArray"===a?b=b.replace(/,/g," ").split(/\s+/).map(function(a){return parseInt(a)}):"transformMatrix"===a?b=c&&c.transformMatrix?v(c.transformMatrix,p.parseTransformAttribute(b)):p.parseTransformAttribute(b):"visible"===a?(b="none"===b||"hidden"===b?!1:!0,c&&c.visible===!1&&(b=!1)):"originX"===a?b="start"===b?"left":"end"===b?"right":"center":d=e?b.map(u):u(b):b="",!e&&isNaN(d)?b:d}function d(a){for(var b in x)if(a[b]&&"undefined"!=typeof a[x[b]]&&0!==a[b].indexOf("url(")){var c=new p.Color(a[b]);a[b]=c.setAlpha(t(c.getAlpha()*a[x[b]],2)).toRgba()}return a}function e(a,b){var c=a.match(/(normal|italic)?\s*(normal|small-caps)?\s*(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\s*(\d+)px(?:\/(normal|[\d\.]+))?\s+(.*)/);if(c){var d=c[1],e=c[3],f=c[4],g=c[5],h=c[6];d&&(b.fontStyle=d),e&&(b.fontWeight=isNaN(parseFloat(e))?e:parseFloat(e)),f&&(b.fontSize=parseFloat(f)),h&&(b.fontFamily=h),g&&(b.lineHeight="normal"===g?1:g)}}function f(a,d){var f,g;a.replace(/;$/,"").split(";").forEach(function(a){var h=a.split(":");f=b(h[0].trim().toLowerCase()),g=c(f,h[1].trim()),"font"===f?e(g,d):d[f]=g})}function g(a,d){var f,g;for(var h in a)"undefined"!=typeof a[h]&&(f=b(h.toLowerCase()),g=c(f,a[h]),"font"===f?e(g,d):d[f]=g)}function h(a){var b={};for(var c in p.cssRules)if(i(a,c.split(" ")))for(var d in p.cssRules[c])b[d]=p.cssRules[c][d];return b}function i(a,b){var c,d=!0;return c=k(a,b.pop()),c&&b.length&&(d=j(a,b)),c&&d&&0===b.length}function j(a,b){for(var c,d=!0;a.parentNode&&1===a.parentNode.nodeType&&b.length;)d&&(c=b.pop()),a=a.parentNode,d=k(a,c);return 0===b.length}function k(a,b){var c,d=a.nodeName,e=a.getAttribute("class"),f=a.getAttribute("id");if(c=new RegExp("^"+d,"i"),b=b.replace(c,""),f&&b.length&&(c=new RegExp("#"+f+"(?![a-zA-Z\\-]+)","i"),b=b.replace(c,"")),e&&b.length){e=e.split(" ");for(var g=e.length;g--;)c=new RegExp("\\."+e[g]+"(?![a-zA-Z\\-]+)","i"),b=b.replace(c,"")}return 0===b.length}function l(a){for(var b=a.getElementsByTagName("use");b.length;){for(var c,d=b[0],e=d.getAttribute("xlink:href").substr(1),f=d.getAttribute("x")||0,g=d.getAttribute("y")||0,h=a.getElementById(e).cloneNode(!0),i=(d.getAttribute("transform")||"")+" translate("+f+", "+g+")",j=0,k=d.attributes,l=k.length;l>j;j++){var m=k.item(j);"x"!==m.nodeName&&"y"!==m.nodeName&&"xlink:href"!==m.nodeName&&("transform"===m.nodeName?i=i+" "+m.nodeValue:h.setAttribute(m.nodeName,m.nodeValue))}h.setAttribute("transform",i),h.removeAttribute("id"),c=d.parentNode,c.replaceChild(h,d)}}function m(a,b){if(b[3]=b[0]=b[0]>b[3]?b[3]:b[0],1!==b[0]||1!==b[3]||0!==b[4]||0!==b[5]){for(var c=a.ownerDocument.createElement("g");null!=a.firstChild;)c.appendChild(a.firstChild);c.setAttribute("transform","matrix("+b[0]+" "+b[1]+" "+b[2]+" "+b[3]+" "+b[4]+" "+b[5]+")"),a.appendChild(c)}}function n(a){var b=a.objects,c=a.options;return b=b.map(function(a){return p[r(a.type)].fromObject(a)}),{objects:b,options:c}}function o(a,b,c){b[c]&&b[c].toSVG&&a.push('','')}var p=a.fabric||(a.fabric={}),q=p.util.object.extend,r=p.util.string.capitalize,s=p.util.object.clone,t=p.util.toFixed,u=p.util.parseUnit,v=p.util.multiplyTransformMatrices,w={cx:"left",x:"left",r:"radius",cy:"top",y:"top",display:"visible",visibility:"visible",transform:"transformMatrix","fill-opacity":"fillOpacity","fill-rule":"fillRule","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","stroke-dasharray":"strokeDashArray","stroke-linecap":"strokeLineCap","stroke-linejoin":"strokeLineJoin","stroke-miterlimit":"strokeMiterLimit","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","text-decoration":"textDecoration","text-anchor":"originX"},x={stroke:"strokeOpacity",fill:"fillOpacity"};p.parseTransformAttribute=function(){function a(a,b){var c=b[0];a[0]=Math.cos(c),a[1]=Math.sin(c),a[2]=-Math.sin(c),a[3]=Math.cos(c)}function b(a,b){var c=b[0],d=2===b.length?b[1]:b[0];a[0]=c,a[3]=d}function c(a,b){a[2]=b[0]}function d(a,b){a[1]=b[0]}function e(a,b){a[4]=b[0],2===b.length&&(a[5]=b[1])}var f=[1,0,0,1,0,0],g="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",h="(?:\\s+,?\\s*|,\\s*)",i="(?:(skewX)\\s*\\(\\s*("+g+")\\s*\\))",j="(?:(skewY)\\s*\\(\\s*("+g+")\\s*\\))",k="(?:(rotate)\\s*\\(\\s*("+g+")(?:"+h+"("+g+")"+h+"("+g+"))?\\s*\\))",l="(?:(scale)\\s*\\(\\s*("+g+")(?:"+h+"("+g+"))?\\s*\\))",m="(?:(translate)\\s*\\(\\s*("+g+")(?:"+h+"("+g+"))?\\s*\\))",n="(?:(matrix)\\s*\\(\\s*("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")"+h+"("+g+")\\s*\\))",o="(?:"+n+"|"+m+"|"+l+"|"+k+"|"+i+"|"+j+")",q="(?:"+o+"(?:"+h+o+")*)",r="^\\s*(?:"+q+"?)\\s*$",s=new RegExp(r),t=new RegExp(o,"g");return function(g){var h=f.concat(),i=[];if(!g||g&&!s.test(g))return h;g.replace(t,function(g){var j=new RegExp(o).exec(g).filter(function(a){return""!==a&&null!=a}),k=j[1],l=j.slice(2).map(parseFloat);switch(k){case"translate":e(h,l);break;case"rotate":l[0]=p.util.degreesToRadians(l[0]),a(h,l);break;case"scale":b(h,l);break;case"skewX":c(h,l);break;case"skewY":d(h,l);break;case"matrix":h=l}i.push(h.concat()),h=f.concat()});for(var j=i[0];i.length>1;)i.shift(),j=p.util.multiplyTransformMatrices(j,i[0]);return j}}(),p.parseSVGDocument=function(){function a(a,b){for(;a&&(a=a.parentNode);)if(b.test(a.nodeName))return!0;return!1}var b=/^(path|circle|polygon|polyline|ellipse|rect|line|image|text)$/,c="(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:e[-+]?\\d+)?)",d=new RegExp("^\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*,?\\s*("+c+"+)\\s*$");return function(c,e,f){if(c){var g=new Date;l(c);var h,i,j=c.getAttribute("viewBox"),k=u(c.getAttribute("width")||"100%"),n=u(c.getAttribute("height")||"100%");if(j&&(j=j.match(d))){var o=parseFloat(j[1]),q=parseFloat(j[2]),r=1,t=1;h=parseFloat(j[3]),i=parseFloat(j[4]),k&&k!==h&&(r=k/h),n&&n!==i&&(t=n/i),m(c,[r,0,0,t,r*-o,t*-q])}var v=p.util.toArray(c.getElementsByTagName("*"));if(0===v.length&&p.isLikelyNode){v=c.selectNodes('//*[name(.)!="svg"]');for(var w=[],x=0,y=v.length;y>x;x++)w[x]=v[x];v=w}var z=v.filter(function(c){return b.test(c.tagName)&&!a(c,/^(?:pattern|defs)$/)});if(!z||z&&!z.length)return void(e&&e([],{}));var A={width:k?k:h,height:n?n:i,widthAttr:k,heightAttr:n};p.gradientDefs=p.getGradientDefs(c),p.cssRules=p.getCSSRules(c),p.parseElements(z,function(a){p.documentParsingTime=new Date-g,e&&e(a,A)},s(A),f)}}}();var y={has:function(a,b){b(!1)},get:function(){},set:function(){}};q(p,{getGradientDefs:function(a){var b,c,d,e,f=a.getElementsByTagName("linearGradient"),g=a.getElementsByTagName("radialGradient"),h=0,i=[],j={},k={};for(i.length=f.length+g.length,c=f.length;c--;)i[h++]=f[c];for(c=g.length;c--;)i[h++]=g[c];for(;h--;)b=i[h],e=b.getAttribute("xlink:href"),d=b.getAttribute("id"),e&&(k[d]=e.substr(1)),j[d]=b;for(d in k){var l=j[k[d]].cloneNode(!0);for(b=j[d];l.firstChild;)b.appendChild(l.firstChild)}return j},parseAttributes:function(a,e){if(a){var f,g={};a.parentNode&&/^symbol|[g|a]$/i.test(a.parentNode.nodeName)&&(g=p.parseAttributes(a.parentNode,e));var i=e.reduce(function(d,e){return f=a.getAttribute(e),f&&(e=b(e),f=c(e,f,g),d[e]=f),d},{});return i=q(i,q(h(a),p.parseStyleAttribute(a))),d(q(g,i))}},parseElements:function(a,b,c,d){new p.ElementsParser(a,b,c,d).parse()},parseStyleAttribute:function(a){var b={},c=a.getAttribute("style");return c?("string"==typeof c?f(c,b):g(c,b),b):b},parsePointsAttribute:function(a){if(!a)return null;a=a.replace(/,/g," ").trim(),a=a.split(/\s+/);var b,c,d=[];for(b=0,c=a.length;c>b;b+=2)d.push({x:parseFloat(a[b]),y:parseFloat(a[b+1])});return d},getCSSRules:function(a){for(var d,e=a.getElementsByTagName("style"),f={},g=0,h=e.length;h>g;g++){var i=e[0].textContent;i=i.replace(/\/\*[\s\S]*?\*\//g,""),d=i.match(/[^{]*\{[\s\S]*?\}/g),d=d.map(function(a){return a.trim()}),d.forEach(function(a){for(var d=a.match(/([\s\S]*?)\s*\{([^}]*)\}/),e={},g=d[2].trim(),h=g.replace(/;$/,"").split(/\s*;\s*/),i=0,j=h.length;j>i;i++){var k=h[i].split(/\s*:\s*/),l=b(k[0]),m=c(l,k[1],k[0]);e[l]=m}a=d[1],a.split(",").forEach(function(a){f[a.trim()]=p.util.object.clone(e)})})}return f},loadSVGFromURL:function(a,b,c){function d(d){var e=d.responseXML;e&&!e.documentElement&&p.window.ActiveXObject&&d.responseText&&(e=new ActiveXObject("Microsoft.XMLDOM"),e.async="false",e.loadXML(d.responseText.replace(//i,""))),e&&e.documentElement&&p.parseSVGDocument(e.documentElement,function(c,d){y.set(a,{objects:p.util.array.invoke(c,"toObject"),options:d}),b(c,d)},c)}a=a.replace(/^\n\s*/,"").trim(),y.has(a,function(c){c?y.get(a,function(a){var c=n(a);b(c.objects,c.options)}):new p.util.request(a,{method:"get",onComplete:d})})},loadSVGFromString:function(a,b,c){a=a.trim();var d;if("undefined"!=typeof DOMParser){var e=new DOMParser;e&&e.parseFromString&&(d=e.parseFromString(a,"text/xml"))}else p.window.ActiveXObject&&(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(a.replace(//i,"")));p.parseSVGDocument(d.documentElement,function(a,c){b(a,c)},c)},createSVGFontFacesMarkup:function(a){for(var b="",c=0,d=a.length;d>c;c++)"text"===a[c].type&&a[c].path&&(b+=["@font-face {","font-family: ",a[c].fontFamily,"; ","src: url('",a[c].path,"')","}"].join(""));return b&&(b=['"].join("")),b},createSVGRefElementsMarkup:function(a){var b=[];return o(b,a,"backgroundColor"),o(b,a,"overlayColor"),b.join("")}})}("undefined"!=typeof exports?exports:this),fabric.ElementsParser=function(a,b,c,d){this.elements=a,this.callback=b,this.options=c,this.reviver=d},fabric.ElementsParser.prototype.parse=function(){this.instances=new Array(this.elements.length),this.numElements=this.elements.length,this.createObjects()},fabric.ElementsParser.prototype.createObjects=function(){for(var a=0,b=this.elements.length;b>a;a++)!function(a,b){setTimeout(function(){a.createObject(a.elements[b],b)},0)}(this,a)},fabric.ElementsParser.prototype.createObject=function(a,b){var c=fabric[fabric.util.string.capitalize(a.tagName)];if(c&&c.fromElement)try{this._createObject(c,a,b)}catch(d){fabric.log(d)}else this.checkIfDone()},fabric.ElementsParser.prototype._createObject=function(a,b,c){if(a.async)a.fromElement(b,this.createCallback(c,b),this.options);else{var d=a.fromElement(b,this.options);this.resolveGradient(d,"fill"),this.resolveGradient(d,"stroke"),this.reviver&&this.reviver(b,d),this.instances[c]=d,this.checkIfDone()}},fabric.ElementsParser.prototype.createCallback=function(a,b){var c=this;return function(d){c.resolveGradient(d,"fill"),c.resolveGradient(d,"stroke"),c.reviver&&c.reviver(b,d),c.instances[a]=d,c.checkIfDone()}},fabric.ElementsParser.prototype.resolveGradient=function(a,b){var c=a.get(b);if(/^url\(/.test(c)){var d=c.slice(5,c.length-1);fabric.gradientDefs[d]&&a.set(b,fabric.Gradient.fromElement(fabric.gradientDefs[d],a))}},fabric.ElementsParser.prototype.checkIfDone=function(){0===--this.numElements&&(this.instances=this.instances.filter(function(a){return null!=a}),this.callback(this.instances))},function(a){"use strict";function b(a,b){this.x=a,this.y=b}var c=a.fabric||(a.fabric={});return c.Point?void c.warn("fabric.Point is already defined"):(c.Point=b,void(b.prototype={constructor:b,add:function(a){return new b(this.x+a.x,this.y+a.y)},addEquals:function(a){return this.x+=a.x,this.y+=a.y,this},scalarAdd:function(a){return new b(this.x+a,this.y+a)},scalarAddEquals:function(a){return this.x+=a,this.y+=a,this},subtract:function(a){return new b(this.x-a.x,this.y-a.y)},subtractEquals:function(a){return this.x-=a.x,this.y-=a.y,this},scalarSubtract:function(a){return new b(this.x-a,this.y-a)},scalarSubtractEquals:function(a){return this.x-=a,this.y-=a,this},multiply:function(a){return new b(this.x*a,this.y*a)},multiplyEquals:function(a){return this.x*=a,this.y*=a,this},divide:function(a){return new b(this.x/a,this.y/a)},divideEquals:function(a){return this.x/=a,this.y/=a,this},eq:function(a){return this.x===a.x&&this.y===a.y},lt:function(a){return this.xa.x&&this.y>a.y},gte:function(a){return this.x>=a.x&&this.y>=a.y},lerp:function(a,c){return new b(this.x+(a.x-this.x)*c,this.y+(a.y-this.y)*c)},distanceFrom:function(a){var b=this.x-a.x,c=this.y-a.y;return Math.sqrt(b*b+c*c)},midPointFrom:function(a){return new b(this.x+(a.x-this.x)/2,this.y+(a.y-this.y)/2)},min:function(a){return new b(Math.min(this.x,a.x),Math.min(this.y,a.y))},max:function(a){return new b(Math.max(this.x,a.x),Math.max(this.y,a.y))},toString:function(){return this.x+","+this.y},setXY:function(a,b){this.x=a,this.y=b},setFromPoint:function(a){this.x=a.x,this.y=a.y},swap:function(a){var b=this.x,c=this.y;this.x=a.x,this.y=a.y,a.x=b,a.y=c}}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){this.status=a,this.points=[]}var c=a.fabric||(a.fabric={});return c.Intersection?void c.warn("fabric.Intersection is already defined"):(c.Intersection=b,c.Intersection.prototype={appendPoint:function(a){this.points.push(a)},appendPoints:function(a){this.points=this.points.concat(a)}},c.Intersection.intersectLineLine=function(a,d,e,f){var g,h=(f.x-e.x)*(a.y-e.y)-(f.y-e.y)*(a.x-e.x),i=(d.x-a.x)*(a.y-e.y)-(d.y-a.y)*(a.x-e.x),j=(f.y-e.y)*(d.x-a.x)-(f.x-e.x)*(d.y-a.y);if(0!==j){var k=h/j,l=i/j;k>=0&&1>=k&&l>=0&&1>=l?(g=new b("Intersection"),g.points.push(new c.Point(a.x+k*(d.x-a.x),a.y+k*(d.y-a.y)))):g=new b}else g=new b(0===h||0===i?"Coincident":"Parallel");return g},c.Intersection.intersectLinePolygon=function(a,c,d){for(var e=new b,f=d.length,g=0;f>g;g++){var h=d[g],i=d[(g+1)%f],j=b.intersectLineLine(a,c,h,i);e.appendPoints(j.points)}return e.points.length>0&&(e.status="Intersection"),e},c.Intersection.intersectPolygonPolygon=function(a,c){for(var d=new b,e=a.length,f=0;e>f;f++){var g=a[f],h=a[(f+1)%e],i=b.intersectLinePolygon(g,h,c);d.appendPoints(i.points)}return d.points.length>0&&(d.status="Intersection"),d},void(c.Intersection.intersectPolygonRectangle=function(a,d,e){var f=d.min(e),g=d.max(e),h=new c.Point(g.x,f.y),i=new c.Point(f.x,g.y),j=b.intersectLinePolygon(f,h,a),k=b.intersectLinePolygon(h,g,a),l=b.intersectLinePolygon(g,i,a),m=b.intersectLinePolygon(i,f,a),n=new b;return n.appendPoints(j.points),n.appendPoints(k.points),n.appendPoints(l.points),n.appendPoints(m.points),n.points.length>0&&(n.status="Intersection"),n}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){a?this._tryParsingColor(a):this.setSource([0,0,0,1])}function c(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}var d=a.fabric||(a.fabric={});return d.Color?void d.warn("fabric.Color is already defined."):(d.Color=b,d.Color.prototype={_tryParsingColor:function(a){var c;return a in b.colorNameMap&&(a=b.colorNameMap[a]),"transparent"===a?void this.setSource([255,255,255,0]):(c=b.sourceFromHex(a),c||(c=b.sourceFromRgb(a)),c||(c=b.sourceFromHsl(a)),void(c&&this.setSource(c)))},_rgbToHsl:function(a,b,c){a/=255,b/=255,c/=255;var e,f,g,h=d.util.array.max([a,b,c]),i=d.util.array.min([a,b,c]);if(g=(h+i)/2,h===i)e=f=0;else{var j=h-i;switch(f=g>.5?j/(2-h-i):j/(h+i),h){case a:e=(b-c)/j+(c>b?6:0);break;case b:e=(c-a)/j+2;break;case c:e=(a-b)/j+4}e/=6}return[Math.round(360*e),Math.round(100*f),Math.round(100*g)]},getSource:function(){return this._source},setSource:function(a){this._source=a},toRgb:function(){var a=this.getSource();return"rgb("+a[0]+","+a[1]+","+a[2]+")"},toRgba:function(){var a=this.getSource();return"rgba("+a[0]+","+a[1]+","+a[2]+","+a[3]+")"},toHsl:function(){var a=this.getSource(),b=this._rgbToHsl(a[0],a[1],a[2]);return"hsl("+b[0]+","+b[1]+"%,"+b[2]+"%)"},toHsla:function(){var a=this.getSource(),b=this._rgbToHsl(a[0],a[1],a[2]);return"hsla("+b[0]+","+b[1]+"%,"+b[2]+"%,"+a[3]+")"},toHex:function(){var a,b,c,d=this.getSource();return a=d[0].toString(16),a=1===a.length?"0"+a:a,b=d[1].toString(16),b=1===b.length?"0"+b:b,c=d[2].toString(16),c=1===c.length?"0"+c:c,a.toUpperCase()+b.toUpperCase()+c.toUpperCase()},getAlpha:function(){return this.getSource()[3]},setAlpha:function(a){var b=this.getSource();return b[3]=a,this.setSource(b),this},toGrayscale:function(){var a=this.getSource(),b=parseInt((.3*a[0]+.59*a[1]+.11*a[2]).toFixed(0),10),c=a[3];return this.setSource([b,b,b,c]),this},toBlackWhite:function(a){var b=this.getSource(),c=(.3*b[0]+.59*b[1]+.11*b[2]).toFixed(0),d=b[3];return a=a||127,c=Number(c)h;h++)c.push(Math.round(f[h]*(1-e)+g[h]*e));return c[3]=d,this.setSource(c),this}},d.Color.reRGBa=/^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/,d.Color.reHSLa=/^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/,d.Color.reHex=/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i,d.Color.colorNameMap={aqua:"#00FFFF",black:"#000000",blue:"#0000FF",fuchsia:"#FF00FF",gray:"#808080",green:"#008000",lime:"#00FF00",maroon:"#800000",navy:"#000080",olive:"#808000",orange:"#FFA500",purple:"#800080",red:"#FF0000",silver:"#C0C0C0",teal:"#008080",white:"#FFFFFF",yellow:"#FFFF00"},d.Color.fromRgb=function(a){return b.fromSource(b.sourceFromRgb(a))},d.Color.sourceFromRgb=function(a){var c=a.match(b.reRGBa);if(c){var d=parseInt(c[1],10)/(/%$/.test(c[1])?100:1)*(/%$/.test(c[1])?255:1),e=parseInt(c[2],10)/(/%$/.test(c[2])?100:1)*(/%$/.test(c[2])?255:1),f=parseInt(c[3],10)/(/%$/.test(c[3])?100:1)*(/%$/.test(c[3])?255:1);return[parseInt(d,10),parseInt(e,10),parseInt(f,10),c[4]?parseFloat(c[4]):1]}},d.Color.fromRgba=b.fromRgb,d.Color.fromHsl=function(a){return b.fromSource(b.sourceFromHsl(a))},d.Color.sourceFromHsl=function(a){var d=a.match(b.reHSLa);if(d){var e,f,g,h=(parseFloat(d[1])%360+360)%360/360,i=parseFloat(d[2])/(/%$/.test(d[2])?100:1),j=parseFloat(d[3])/(/%$/.test(d[3])?100:1);if(0===i)e=f=g=j;else{var k=.5>=j?j*(i+1):j+i-j*i,l=2*j-k;e=c(l,k,h+1/3),f=c(l,k,h),g=c(l,k,h-1/3)}return[Math.round(255*e),Math.round(255*f),Math.round(255*g),d[4]?parseFloat(d[4]):1]}},d.Color.fromHsla=b.fromHsl,d.Color.fromHex=function(a){return b.fromSource(b.sourceFromHex(a))},d.Color.sourceFromHex=function(a){if(a.match(b.reHex)){var c=a.slice(a.indexOf("#")+1),d=3===c.length,e=d?c.charAt(0)+c.charAt(0):c.substring(0,2),f=d?c.charAt(1)+c.charAt(1):c.substring(2,4),g=d?c.charAt(2)+c.charAt(2):c.substring(4,6);return[parseInt(e,16),parseInt(f,16),parseInt(g,16),1]}},void(d.Color.fromSource=function(a){var c=new b;return c.setSource(a),c}))}("undefined"!=typeof exports?exports:this),function(){function a(a){var b,c,d,e=a.getAttribute("style"),f=a.getAttribute("offset");if(f=parseFloat(f)/(/%$/.test(f)?100:1),f=0>f?0:f>1?1:f,e){var g=e.split(/\s*;\s*/);""===g[g.length-1]&&g.pop();for(var h=g.length;h--;){var i=g[h].split(/\s*:\s*/),j=i[0].trim(),k=i[1].trim();"stop-color"===j?b=k:"stop-opacity"===j&&(d=k)}}return b||(b=a.getAttribute("stop-color")||"rgb(0,0,0)"),d||(d=a.getAttribute("stop-opacity")),b=new fabric.Color(b),c=b.getAlpha(),d=isNaN(parseFloat(d))?1:parseFloat(d),d*=c,{offset:f,color:b.toRgb(),opacity:d}}function b(a){return{x1:a.getAttribute("x1")||0,y1:a.getAttribute("y1")||0,x2:a.getAttribute("x2")||"100%",y2:a.getAttribute("y2")||0}}function c(a){return{x1:a.getAttribute("fx")||a.getAttribute("cx")||"50%",y1:a.getAttribute("fy")||a.getAttribute("cy")||"50%",r1:0,x2:a.getAttribute("cx")||"50%",y2:a.getAttribute("cy")||"50%",r2:a.getAttribute("r")||"50%"}}function d(a,b,c){var d,e=0,f=1,g="";for(var h in b)d=parseFloat(b[h],10),f="string"==typeof b[h]&&/^\d+%$/.test(b[h])?.01:1,"x1"===h||"x2"===h||"r2"===h?(f*="objectBoundingBox"===c?a.width:1,e="objectBoundingBox"===c?a.left||0:0):("y1"===h||"y2"===h)&&(f*="objectBoundingBox"===c?a.height:1,e="objectBoundingBox"===c?a.top||0:0),b[h]=d*f+e;if("ellipse"===a.type&&null!==b.r2&&"objectBoundingBox"===c&&a.rx!==a.ry){var i=a.ry/a.rx;g=" scale(1, "+i+")",b.y1&&(b.y1/=i),b.y2&&(b.y2/=i)}return g}fabric.Gradient=fabric.util.createClass({offsetX:0,offsetY:0,initialize:function(a){a||(a={});var b={};this.id=fabric.Object.__uid++,this.type=a.type||"linear",b={x1:a.coords.x1||0,y1:a.coords.y1||0,x2:a.coords.x2||0,y2:a.coords.y2||0},"radial"===this.type&&(b.r1=a.coords.r1||0,b.r2=a.coords.r2||0),this.coords=b,this.colorStops=a.colorStops.slice(),a.gradientTransform&&(this.gradientTransform=a.gradientTransform),this.offsetX=a.offsetX||this.offsetX,this.offsetY=a.offsetY||this.offsetY},addColorStop:function(a){for(var b in a){var c=new fabric.Color(a[b]);this.colorStops.push({offset:b,color:c.toRgb(),opacity:c.getAlpha()})}return this},toObject:function(){return{type:this.type,coords:this.coords,colorStops:this.colorStops,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(a){var b,c,d=fabric.util.object.clone(this.coords);if(this.colorStops.sort(function(a,b){return a.offset-b.offset}),!a.group||"path-group"!==a.group.type)for(var e in d)"x1"===e||"x2"===e||"r2"===e?d[e]+=this.offsetX-a.width/2:("y1"===e||"y2"===e)&&(d[e]+=this.offsetY-a.height/2);c='id="SVGID_'+this.id+'" gradientUnits="userSpaceOnUse"',this.gradientTransform&&(c+=' gradientTransform="matrix('+this.gradientTransform.join(" ")+')" '),"linear"===this.type?b=["\n']:"radial"===this.type&&(b=["\n']);for(var f=0;f\n');return b.push("linear"===this.type?"\n":"\n"),b.join("")},toLive:function(a){var b;if(this.type){"linear"===this.type?b=a.createLinearGradient(this.coords.x1,this.coords.y1,this.coords.x2,this.coords.y2):"radial"===this.type&&(b=a.createRadialGradient(this.coords.x1,this.coords.y1,this.coords.r1,this.coords.x2,this.coords.y2,this.coords.r2));for(var c=0,d=this.colorStops.length;d>c;c++){var e=this.colorStops[c].color,f=this.colorStops[c].opacity,g=this.colorStops[c].offset;"undefined"!=typeof f&&(e=new fabric.Color(e).setAlpha(f).toRgba()),b.addColorStop(parseFloat(g),e)}return b}}}),fabric.util.object.extend(fabric.Gradient,{fromElement:function(e,f){var g,h=e.getElementsByTagName("stop"),i="linearGradient"===e.nodeName?"linear":"radial",j=e.getAttribute("gradientUnits")||"objectBoundingBox",k=e.getAttribute("gradientTransform"),l=[],m={};"linear"===i?m=b(e):"radial"===i&&(m=c(e));for(var n=h.length;n--;)l.push(a(h[n]));g=d(f,m,j);var o=new fabric.Gradient({type:i,coords:m,colorStops:l,offsetX:-f.left,offsetY:-f.top});return(k||""!==g)&&(o.gradientTransform=fabric.parseTransformAttribute((k||"")+g)),o},forObject:function(a,b){return b||(b={}),d(a,b.coords,"userSpaceOnUse"),new fabric.Gradient(b)}})}(),fabric.Pattern=fabric.util.createClass({repeat:"repeat",offsetX:0,offsetY:0,initialize:function(a){if(a||(a={}),this.id=fabric.Object.__uid++,a.source)if("string"==typeof a.source)if("undefined"!=typeof fabric.util.getFunctionBody(a.source))this.source=new Function(fabric.util.getFunctionBody(a.source));else{var b=this;this.source=fabric.util.createImage(),fabric.util.loadImage(a.source,function(a){b.source=a})}else this.source=a.source;a.repeat&&(this.repeat=a.repeat),a.offsetX&&(this.offsetX=a.offsetX),a.offsetY&&(this.offsetY=a.offsetY)},toObject:function(){var a;return"function"==typeof this.source?a=String(this.source):"string"==typeof this.source.src&&(a=this.source.src),{source:a,repeat:this.repeat,offsetX:this.offsetX,offsetY:this.offsetY}},toSVG:function(a){var b="function"==typeof this.source?this.source():this.source,c=b.width/a.getWidth(),d=b.height/a.getHeight(),e="";return b.src?e=b.src:b.toDataURL&&(e=b.toDataURL()),''},toLive:function(a){var b="function"==typeof this.source?this.source():this.source;if(!b)return"";if("undefined"!=typeof b.src){if(!b.complete)return"";if(0===b.naturalWidth||0===b.naturalHeight)return""}return a.createPattern(b,this.repeat)}}),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Shadow?void b.warn("fabric.Shadow is already defined."):(b.Shadow=b.util.createClass({color:"rgb(0,0,0)",blur:0,offsetX:0,offsetY:0,affectStroke:!1,includeDefaultValues:!0,initialize:function(a){"string"==typeof a&&(a=this._parseShadow(a));for(var c in a)this[c]=a[c];this.id=b.Object.__uid++},_parseShadow:function(a){var c=a.trim(),d=b.Shadow.reOffsetsAndBlur.exec(c)||[],e=c.replace(b.Shadow.reOffsetsAndBlur,"")||"rgb(0,0,0)";return{color:e.trim(),offsetX:parseInt(d[1],10)||0,offsetY:parseInt(d[2],10)||0,blur:parseInt(d[3],10)||0}},toString:function(){return[this.offsetX,this.offsetY,this.blur,this.color].join("px ")},toSVG:function(a){var b="SourceAlpha";return!a||a.fill!==this.color&&a.stroke!==this.color||(b="SourceGraphic"),''},toObject:function(){if(this.includeDefaultValues)return{color:this.color,blur:this.blur,offsetX:this.offsetX,offsetY:this.offsetY};var a={},c=b.Shadow.prototype;return this.color!==c.color&&(a.color=this.color),this.blur!==c.blur&&(a.blur=this.blur),this.offsetX!==c.offsetX&&(a.offsetX=this.offsetX),this.offsetY!==c.offsetY&&(a.offsetY=this.offsetY),a}}),void(b.Shadow.reOffsetsAndBlur=/(?:\s|^)(-?\d+(?:px)?(?:\s?|$))?(-?\d+(?:px)?(?:\s?|$))?(\d+(?:px)?)?(?:\s?|$)(?:$|\s)/))}("undefined"!=typeof exports?exports:this),function(){"use strict";if(fabric.StaticCanvas)return void fabric.warn("fabric.StaticCanvas is already defined.");var a=fabric.util.object.extend,b=fabric.util.getElementOffset,c=fabric.util.removeFromArray,d=new Error("Could not initialize `canvas` element");fabric.StaticCanvas=fabric.util.createClass({initialize:function(a,b){b||(b={}),this._initStatic(a,b),fabric.StaticCanvas.activeInstance=this +},backgroundColor:"",backgroundImage:null,overlayColor:"",overlayImage:null,includeDefaultValues:!0,stateful:!0,renderOnAddRemove:!0,clipTo:null,controlsAboveOverlay:!1,allowTouchScrolling:!1,imageSmoothingEnabled:!0,viewportTransform:[1,0,0,1,0,0],onBeforeScaleRotate:function(){},_initStatic:function(a,b){this._objects=[],this._createLowerCanvas(a),this._initOptions(b),this._setImageSmoothing(),b.overlayImage&&this.setOverlayImage(b.overlayImage,this.renderAll.bind(this)),b.backgroundImage&&this.setBackgroundImage(b.backgroundImage,this.renderAll.bind(this)),b.backgroundColor&&this.setBackgroundColor(b.backgroundColor,this.renderAll.bind(this)),b.overlayColor&&this.setOverlayColor(b.overlayColor,this.renderAll.bind(this)),this.calcOffset()},calcOffset:function(){return this._offset=b(this.lowerCanvasEl),this},setOverlayImage:function(a,b,c){return this.__setBgOverlayImage("overlayImage",a,b,c)},setBackgroundImage:function(a,b,c){return this.__setBgOverlayImage("backgroundImage",a,b,c)},setOverlayColor:function(a,b){return this.__setBgOverlayColor("overlayColor",a,b)},setBackgroundColor:function(a,b){return this.__setBgOverlayColor("backgroundColor",a,b)},_setImageSmoothing:function(){var a=this.getContext();a.imageSmoothingEnabled=this.imageSmoothingEnabled,a.webkitImageSmoothingEnabled=this.imageSmoothingEnabled,a.mozImageSmoothingEnabled=this.imageSmoothingEnabled,a.msImageSmoothingEnabled=this.imageSmoothingEnabled,a.oImageSmoothingEnabled=this.imageSmoothingEnabled},__setBgOverlayImage:function(a,b,c,d){return"string"==typeof b?fabric.util.loadImage(b,function(b){this[a]=new fabric.Image(b,d),c&&c()},this):(this[a]=b,c&&c()),this},__setBgOverlayColor:function(a,b,c){if(b&&b.source){var d=this;fabric.util.loadImage(b.source,function(e){d[a]=new fabric.Pattern({source:e,repeat:b.repeat,offsetX:b.offsetX,offsetY:b.offsetY}),c&&c()})}else this[a]=b,c&&c();return this},_createCanvasElement:function(){var a=fabric.document.createElement("canvas");if(a.style||(a.style={}),!a)throw d;return this._initCanvasElement(a),a},_initCanvasElement:function(a){if(fabric.util.createCanvasElement(a),"undefined"==typeof a.getContext)throw d},_initOptions:function(a){for(var b in a)this[b]=a[b];this.width=this.width||parseInt(this.lowerCanvasEl.width,10)||0,this.height=this.height||parseInt(this.lowerCanvasEl.height,10)||0,this.lowerCanvasEl.style&&(this.lowerCanvasEl.width=this.width,this.lowerCanvasEl.height=this.height,this.lowerCanvasEl.style.width=this.width+"px",this.lowerCanvasEl.style.height=this.height+"px",this.viewportTransform=this.viewportTransform.slice())},_createLowerCanvas:function(a){this.lowerCanvasEl=fabric.util.getById(a)||this._createCanvasElement(),this._initCanvasElement(this.lowerCanvasEl),fabric.util.addClass(this.lowerCanvasEl,"lower-canvas"),this.interactive&&this._applyCanvasStyle(this.lowerCanvasEl),this.contextContainer=this.lowerCanvasEl.getContext("2d")},getWidth:function(){return this.width},getHeight:function(){return this.height},setWidth:function(a,b){return this.setDimensions({width:a},b)},setHeight:function(a,b){return this.setDimensions({height:a},b)},setDimensions:function(a,b){var c;b=b||{};for(var d in a)c=a[d],b.cssOnly||(this._setBackstoreDimension(d,a[d]),c+="px"),b.backstoreOnly||this._setCssDimension(d,c);return b.cssOnly||this.renderAll(),this.calcOffset(),this},_setBackstoreDimension:function(a,b){return this.lowerCanvasEl[a]=b,this.upperCanvasEl&&(this.upperCanvasEl[a]=b),this.cacheCanvasEl&&(this.cacheCanvasEl[a]=b),this[a]=b,this},_setCssDimension:function(a,b){return this.lowerCanvasEl.style[a]=b,this.upperCanvasEl&&(this.upperCanvasEl.style[a]=b),this.wrapperEl&&(this.wrapperEl.style[a]=b),this},getZoom:function(){return Math.sqrt(this.viewportTransform[0]*this.viewportTransform[3])},setViewportTransform:function(a){this.viewportTransform=a,this.renderAll();for(var b=0,c=this._objects.length;c>b;b++)this._objects[b].setCoords();return this},zoomToPoint:function(a,b){var c=a;a=fabric.util.transformPoint(a,fabric.util.invertTransform(this.viewportTransform)),this.viewportTransform[0]=b,this.viewportTransform[3]=b;var d=fabric.util.transformPoint(a,this.viewportTransform);this.viewportTransform[4]+=c.x-d.x,this.viewportTransform[5]+=c.y-d.y,this.renderAll();for(var e=0,f=this._objects.length;f>e;e++)this._objects[e].setCoords();return this},setZoom:function(a){return this.zoomToPoint(new fabric.Point(0,0),a),this},absolutePan:function(a){this.viewportTransform[4]=-a.x,this.viewportTransform[5]=-a.y,this.renderAll();for(var b=0,c=this._objects.length;c>b;b++)this._objects[b].setCoords();return this},relativePan:function(a){return this.absolutePan(new fabric.Point(-a.x-this.viewportTransform[4],-a.y-this.viewportTransform[5]))},getElement:function(){return this.lowerCanvasEl},getActiveObject:function(){return null},getActiveGroup:function(){return null},_draw:function(a,b){if(b){a.save();var c=this.viewportTransform;a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),b.render(a),a.restore(),this.controlsAboveOverlay||b._renderControls(a)}},_onObjectAdded:function(a){this.stateful&&a.setupState(),a.canvas=this,a.setCoords(),this.fire("object:added",{target:a}),a.fire("added")},_onObjectRemoved:function(a){this.getActiveObject()===a&&(this.fire("before:selection:cleared",{target:a}),this._discardActiveObject(),this.fire("selection:cleared")),this.fire("object:removed",{target:a}),a.fire("removed")},clearContext:function(a){return a.clearRect(0,0,this.width,this.height),this},getContext:function(){return this.contextContainer},clear:function(){return this._objects.length=0,this.discardActiveGroup&&this.discardActiveGroup(),this.discardActiveObject&&this.discardActiveObject(),this.clearContext(this.contextContainer),this.contextTop&&this.clearContext(this.contextTop),this.fire("canvas:cleared"),this.renderAll(),this},renderAll:function(a){var b=this[a===!0&&this.interactive?"contextTop":"contextContainer"],c=this.getActiveGroup();return this.contextTop&&this.selection&&!this._groupSelector&&this.clearContext(this.contextTop),a||this.clearContext(b),this.fire("before:render"),this.clipTo&&fabric.util.clipContext(this,b),this._renderBackground(b),this._renderObjects(b,c),this._renderActiveGroup(b,c),this.clipTo&&b.restore(),this._renderOverlay(b),this.controlsAboveOverlay&&this.interactive&&this.drawControls(b),this.fire("after:render"),this},_renderObjects:function(a,b){var c,d;if(b)for(c=0,d=this._objects.length;d>c;++c)this._objects[c]&&!b.contains(this._objects[c])&&this._draw(a,this._objects[c]);else for(c=0,d=this._objects.length;d>c;++c)this._draw(a,this._objects[c])},_renderActiveGroup:function(a,b){if(b){var c=[];this.forEachObject(function(a){b.contains(a)&&c.push(a)}),b._set("objects",c),this._draw(a,b)}},_renderBackground:function(a){this.backgroundColor&&(a.fillStyle=this.backgroundColor.toLive?this.backgroundColor.toLive(a):this.backgroundColor,a.fillRect(this.backgroundColor.offsetX||0,this.backgroundColor.offsetY||0,this.width,this.height)),this.backgroundImage&&this._draw(a,this.backgroundImage)},_renderOverlay:function(a){this.overlayColor&&(a.fillStyle=this.overlayColor.toLive?this.overlayColor.toLive(a):this.overlayColor,a.fillRect(this.overlayColor.offsetX||0,this.overlayColor.offsetY||0,this.width,this.height)),this.overlayImage&&this._draw(a,this.overlayImage)},renderTop:function(){var a=this.contextTop||this.contextContainer;this.clearContext(a),this.selection&&this._groupSelector&&this._drawSelection();var b=this.getActiveGroup();return b&&b.render(a),this._renderOverlay(a),this.fire("after:render"),this},getCenter:function(){return{top:this.getHeight()/2,left:this.getWidth()/2}},centerObjectH:function(a){return this._centerObject(a,new fabric.Point(this.getCenter().left,a.getCenterPoint().y)),this.renderAll(),this},centerObjectV:function(a){return this._centerObject(a,new fabric.Point(a.getCenterPoint().x,this.getCenter().top)),this.renderAll(),this},centerObject:function(a){var b=this.getCenter();return this._centerObject(a,new fabric.Point(b.left,b.top)),this.renderAll(),this},_centerObject:function(a,b){return a.setPositionByOrigin(b,"center","center"),this},toDatalessJSON:function(a){return this.toDatalessObject(a)},toObject:function(a){return this._toObjectMethod("toObject",a)},toDatalessObject:function(a){return this._toObjectMethod("toDatalessObject",a)},_toObjectMethod:function(b,c){var d=this.getActiveGroup();d&&this.discardActiveGroup();var e={objects:this._toObjects(b,c)};return a(e,this.__serializeBgOverlay()),fabric.util.populateWithProperties(this,e,c),d&&(this.setActiveGroup(new fabric.Group(d.getObjects(),{originX:"center",originY:"center"})),d.forEachObject(function(a){a.set("active",!0)}),this._currentTransform&&(this._currentTransform.target=this.getActiveGroup())),e},_toObjects:function(a,b){return this.getObjects().map(function(c){return this._toObject(c,a,b)},this)},_toObject:function(a,b,c){var d;this.includeDefaultValues||(d=a.includeDefaultValues,a.includeDefaultValues=!1);var e=a[b](c);return this.includeDefaultValues||(a.includeDefaultValues=d),e},__serializeBgOverlay:function(){var a={background:this.backgroundColor&&this.backgroundColor.toObject?this.backgroundColor.toObject():this.backgroundColor};return this.overlayColor&&(a.overlay=this.overlayColor.toObject?this.overlayColor.toObject():this.overlayColor),this.backgroundImage&&(a.backgroundImage=this.backgroundImage.toObject()),this.overlayImage&&(a.overlayImage=this.overlayImage.toObject()),a},svgViewportTransformation:!0,toSVG:function(a,b){a||(a={});var c=[];return this._setSVGPreamble(c,a),this._setSVGHeader(c,a),this._setSVGBgOverlayColor(c,"backgroundColor"),this._setSVGBgOverlayImage(c,"backgroundImage"),this._setSVGObjects(c,b),this._setSVGBgOverlayColor(c,"overlayColor"),this._setSVGBgOverlayImage(c,"overlayImage"),c.push(""),c.join("")},_setSVGPreamble:function(a,b){b.suppressPreamble||a.push('','\n')},_setSVGHeader:function(a,b){var c,d,e;b.viewBox?(c=b.viewBox.width,d=b.viewBox.height):(c=this.width,d=this.height,this.svgViewportTransformation||(e=this.viewportTransform,c/=e[0],d/=e[3])),a.push("',"Created with Fabric.js ",fabric.version,"","",fabric.createSVGFontFacesMarkup(this.getObjects()),fabric.createSVGRefElementsMarkup(this),"")},_setSVGObjects:function(a,b){var c=this.getActiveGroup();c&&this.discardActiveGroup();for(var d=0,e=this.getObjects(),f=e.length;f>d;d++)a.push(e[d].toSVG(b));c&&(this.setActiveGroup(new fabric.Group(c.getObjects())),c.forEachObject(function(a){a.set("active",!0)}))},_setSVGBgOverlayImage:function(a,b){this[b]&&this[b].toSVG&&a.push(this[b].toSVG())},_setSVGBgOverlayColor:function(a,b){this[b]&&this[b].source?a.push('"):this[b]&&"overlayColor"===b&&a.push('")},sendToBack:function(a){return c(this._objects,a),this._objects.unshift(a),this.renderAll&&this.renderAll()},bringToFront:function(a){return c(this._objects,a),this._objects.push(a),this.renderAll&&this.renderAll()},sendBackwards:function(a,b){var d=this._objects.indexOf(a);if(0!==d){var e=this._findNewLowerIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this},_findNewLowerIndex:function(a,b,c){var d;if(c){d=b;for(var e=b-1;e>=0;--e){var f=a.intersectsWithObject(this._objects[e])||a.isContainedWithinObject(this._objects[e])||this._objects[e].isContainedWithinObject(a);if(f){d=e;break}}}else d=b-1;return d},bringForward:function(a,b){var d=this._objects.indexOf(a);if(d!==this._objects.length-1){var e=this._findNewUpperIndex(a,d,b);c(this._objects,a),this._objects.splice(e,0,a),this.renderAll&&this.renderAll()}return this},_findNewUpperIndex:function(a,b,c){var d;if(c){d=b;for(var e=b+1;e"}}),a(fabric.StaticCanvas.prototype,fabric.Observable),a(fabric.StaticCanvas.prototype,fabric.Collection),a(fabric.StaticCanvas.prototype,fabric.DataURLExporter),a(fabric.StaticCanvas,{EMPTY_JSON:'{"objects": [], "background": "white"}',supports:function(a){var b=fabric.util.createCanvasElement();if(!b||!b.getContext)return null;var c=b.getContext("2d");if(!c)return null;switch(a){case"getImageData":return"undefined"!=typeof c.getImageData;case"setLineDash":return"undefined"!=typeof c.setLineDash;case"toDataURL":return"undefined"!=typeof b.toDataURL;case"toDataURLWithQuality":try{return b.toDataURL("image/jpeg",0),!0}catch(d){}return!1;default:return null}}}),fabric.StaticCanvas.prototype.toJSON=fabric.StaticCanvas.prototype.toObject}(),fabric.BaseBrush=fabric.util.createClass({color:"rgb(0, 0, 0)",width:1,shadow:null,strokeLineCap:"round",strokeLineJoin:"round",setShadow:function(a){return this.shadow=new fabric.Shadow(a),this},_setBrushStyles:function(){var a=this.canvas.contextTop;a.strokeStyle=this.color,a.lineWidth=this.width,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin},_setShadow:function(){if(this.shadow){var a=this.canvas.contextTop;a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY}},_resetShadow:function(){var a=this.canvas.contextTop;a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0}}),function(){var a=fabric.util.array.min,b=fabric.util.array.max;fabric.PencilBrush=fabric.util.createClass(fabric.BaseBrush,{initialize:function(a){this.canvas=a,this._points=[]},onMouseDown:function(a){this._prepareForDrawing(a),this._captureDrawingPath(a),this._render()},onMouseMove:function(a){this._captureDrawingPath(a),this.canvas.clearContext(this.canvas.contextTop),this._render()},onMouseUp:function(){this._finalizeAndAddPath()},_prepareForDrawing:function(a){var b=new fabric.Point(a.x,a.y);this._reset(),this._addPoint(b),this.canvas.contextTop.moveTo(b.x,b.y)},_addPoint:function(a){this._points.push(a)},_reset:function(){this._points.length=0,this._setBrushStyles(),this._setShadow()},_captureDrawingPath:function(a){var b=new fabric.Point(a.x,a.y);this._addPoint(b)},_render:function(){var a=this.canvas.contextTop,b=this.canvas.viewportTransform,c=this._points[0],d=this._points[1];a.save(),a.transform(b[0],b[1],b[2],b[3],b[4],b[5]),a.beginPath(),2===this._points.length&&c.x===d.x&&c.y===d.y&&(c.x-=.5,d.x+=.5),a.moveTo(c.x,c.y);for(var e=1,f=this._points.length;f>e;e++){var g=c.midPointFrom(d);a.quadraticCurveTo(c.x,c.y,g.x,g.y),c=this._points[e],d=this._points[e+1]}a.lineTo(c.x,c.y),a.stroke(),a.restore()},_getSVGPathData:function(){return this.box=this.getPathBoundingBox(this._points),this.convertPointsToSVGPath(this._points,this.box.minX,this.box.minY)},getPathBoundingBox:function(c){for(var d=[],e=[],f=c[0],g=c[1],h=f,i=1,j=c.length;j>i;i++){var k=f.midPointFrom(g);d.push(h.x),d.push(k.x),e.push(h.y),e.push(k.y),f=c[i],g=c[i+1],h=k}return d.push(f.x),e.push(f.y),{minX:a(d),minY:a(e),maxX:b(d),maxY:b(e)}},convertPointsToSVGPath:function(a,b,c){var d=[],e=new fabric.Point(a[0].x-b,a[0].y-c),f=new fabric.Point(a[1].x-b,a[1].y-c);d.push("M ",a[0].x-b," ",a[0].y-c," ");for(var g=1,h=a.length;h>g;g++){var i=e.midPointFrom(f);d.push("Q ",e.x," ",e.y," ",i.x," ",i.y," "),e=new fabric.Point(a[g].x-b,a[g].y-c),g+1c;c++){var e=this.points[c],f=new fabric.Circle({radius:e.radius,left:e.x,top:e.y,originX:"center",originY:"center",fill:e.fill});this.shadow&&f.setShadow(this.shadow),b.push(f)}var g=new fabric.Group(b,{originX:"center",originY:"center"});g.canvas=this.canvas,this.canvas.add(g),this.canvas.fire("path:created",{path:g}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=a,this.canvas.renderAll()},addPoint:function(a){var b=new fabric.Point(a.x,a.y),c=fabric.util.getRandomInt(Math.max(0,this.width-20),this.width+20)/2,d=new fabric.Color(this.color).setAlpha(fabric.util.getRandomInt(0,100)/100).toRgba();return b.radius=c,b.fill=d,this.points.push(b),b}}),fabric.SprayBrush=fabric.util.createClass(fabric.BaseBrush,{width:10,density:20,dotWidth:1,dotWidthVariance:1,randomOpacity:!1,optimizeOverlapping:!0,initialize:function(a){this.canvas=a,this.sprayChunks=[]},onMouseDown:function(a){this.sprayChunks.length=0,this.canvas.clearContext(this.canvas.contextTop),this._setShadow(),this.addSprayChunk(a),this.render()},onMouseMove:function(a){this.addSprayChunk(a),this.render()},onMouseUp:function(){var a=this.canvas.renderOnAddRemove;this.canvas.renderOnAddRemove=!1;for(var b=[],c=0,d=this.sprayChunks.length;d>c;c++)for(var e=this.sprayChunks[c],f=0,g=e.length;g>f;f++){var h=new fabric.Rect({width:e[f].width,height:e[f].width,left:e[f].x+1,top:e[f].y+1,originX:"center",originY:"center",fill:this.color});this.shadow&&h.setShadow(this.shadow),b.push(h)}this.optimizeOverlapping&&(b=this._getOptimizedRects(b));var i=new fabric.Group(b,{originX:"center",originY:"center"});i.canvas=this.canvas,this.canvas.add(i),this.canvas.fire("path:created",{path:i}),this.canvas.clearContext(this.canvas.contextTop),this._resetShadow(),this.canvas.renderOnAddRemove=a,this.canvas.renderAll()},_getOptimizedRects:function(a){for(var b,c={},d=0,e=a.length;e>d;d++)b=a[d].left+""+a[d].top,c[b]||(c[b]=a[d]);var f=[];for(b in c)f.push(c[b]);return f},render:function(){var a=this.canvas.contextTop;a.fillStyle=this.color;var b=this.canvas.viewportTransform;a.save(),a.transform(b[0],b[1],b[2],b[3],b[4],b[5]);for(var c=0,d=this.sprayChunkPoints.length;d>c;c++){var e=this.sprayChunkPoints[c];"undefined"!=typeof e.opacity&&(a.globalAlpha=e.opacity),a.fillRect(e.x,e.y,e.width,e.width)}a.restore()},addSprayChunk:function(a){this.sprayChunkPoints=[];for(var b,c,d,e=this.width/2,f=0;f1&&this.setWidth(g).setHeight(h),k.scale(d,d),c.left&&(c.left*=d),c.top&&(c.top*=d),c.width?c.width*=d:1>d&&(c.width=g),c.height?c.height*=d:1>d&&(c.height=h),j?this._tempRemoveBordersControlsFromGroup(j):i&&this.deactivateAll&&this.deactivateAll(),this.renderAll(!0);var l=this.__toDataURL(a,b,c);return this.width=e,this.height=f,k.scale(1/d,1/d),this.setWidth(e).setHeight(f),j?this._restoreBordersControlsOnGroup(j):i&&this.setActiveObject&&this.setActiveObject(i),this.contextTop&&this.clearContext(this.contextTop),this.renderAll(),l},toDataURLWithMultiplier:function(a,b,c){return this.toDataURL({format:a,multiplier:b,quality:c})},_tempRemoveBordersControlsFromGroup:function(a){a.origHasControls=a.hasControls,a.origBorderColor=a.borderColor,a.hasControls=!0,a.borderColor="rgba(0,0,0,0)",a.forEachObject(function(a){a.origBorderColor=a.borderColor,a.borderColor="rgba(0,0,0,0)"})},_restoreBordersControlsOnGroup:function(a){a.hideControls=a.origHideControls,a.borderColor=a.origBorderColor,a.forEachObject(function(a){a.borderColor=a.origBorderColor,delete a.origBorderColor})}}),fabric.util.object.extend(fabric.StaticCanvas.prototype,{loadFromDatalessJSON:function(a,b,c){return this.loadFromJSON(a,b,c)},loadFromJSON:function(a,b,c){if(a){var d="string"==typeof a?JSON.parse(a):a;this.clear();var e=this;return this._enlivenObjects(d.objects,function(){e._setBgOverlay(d,b)},c),this}},_setBgOverlay:function(a,b){var c=this,d={backgroundColor:!1,overlayColor:!1,backgroundImage:!1,overlayImage:!1};if(!(a.backgroundImage||a.overlayImage||a.background||a.overlay))return void(b&&b());var e=function(){d.backgroundImage&&d.overlayImage&&d.backgroundColor&&d.overlayColor&&(c.renderAll(),b&&b())};this.__setBgOverlay("backgroundImage",a.backgroundImage,d,e),this.__setBgOverlay("overlayImage",a.overlayImage,d,e),this.__setBgOverlay("backgroundColor",a.background,d,e),this.__setBgOverlay("overlayColor",a.overlay,d,e),e()},__setBgOverlay:function(a,b,c,d){var e=this;return b?void("backgroundImage"===a||"overlayImage"===a?fabric.Image.fromObject(b,function(b){e[a]=b,c[a]=!0,d&&d()}):this["set"+fabric.util.string.capitalize(a,!0)](b,function(){c[a]=!0,d&&d()})):void(c[a]=!0)},_enlivenObjects:function(a,b,c){var d=this;if(!a||0===a.length)return void(b&&b());var e=this.renderOnAddRemove;this.renderOnAddRemove=!1,fabric.util.enlivenObjects(a,function(a){a.forEach(function(a,b){d.insertAt(a,b,!0)}),d.renderOnAddRemove=e,b&&b()},null,c)},_toDataURL:function(a,b){this.clone(function(c){b(c.toDataURL(a))})},_toDataURLWithMultiplier:function(a,b,c){this.clone(function(d){c(d.toDataURLWithMultiplier(a,b))})},clone:function(a,b){var c=JSON.stringify(this.toJSON(b));this.cloneWithoutData(function(b){b.loadFromJSON(c,function(){a&&a(b)})})},cloneWithoutData:function(a){var b=fabric.document.createElement("canvas");b.width=this.getWidth(),b.height=this.getHeight();var c=new fabric.Canvas(b);c.clipTo=this.clipTo,this.backgroundImage?(c.setBackgroundImage(this.backgroundImage.src,function(){c.renderAll(),a&&a(c)}),c.backgroundImageOpacity=this.backgroundImageOpacity,c.backgroundImageStretch=this.backgroundImageStretch):a&&a(c)}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.toFixed,e=b.util.string.capitalize,f=b.util.degreesToRadians,g=b.StaticCanvas.supports("setLineDash");b.Object||(b.Object=b.util.createClass({type:"object",originX:"left",originY:"top",top:0,left:0,width:0,height:0,scaleX:1,scaleY:1,flipX:!1,flipY:!1,opacity:1,angle:0,cornerSize:12,transparentCorners:!0,hoverCursor:null,padding:0,borderColor:"rgba(102,153,255,0.75)",cornerColor:"rgba(102,153,255,0.5)",centeredScaling:!1,centeredRotation:!0,fill:"rgb(0,0,0)",fillRule:"source-over",backgroundColor:"",stroke:null,strokeWidth:1,strokeDashArray:null,strokeLineCap:"butt",strokeLineJoin:"miter",strokeMiterLimit:10,shadow:null,borderOpacityWhenMoving:.4,borderScaleFactor:1,transformMatrix:null,minScaleLimit:.01,selectable:!0,evented:!0,visible:!0,hasControls:!0,hasBorders:!0,hasRotatingPoint:!0,rotatingPointOffset:40,perPixelTargetFind:!1,includeDefaultValues:!0,clipTo:null,lockMovementX:!1,lockMovementY:!1,lockRotation:!1,lockScalingX:!1,lockScalingY:!1,lockUniScaling:!1,lockScalingFlip:!1,stateProperties:"top left width height scaleX scaleY flipX flipY originX originY transformMatrix stroke strokeWidth strokeDashArray strokeLineCap strokeLineJoin strokeMiterLimit angle opacity fill fillRule shadow clipTo visible backgroundColor".split(" "),initialize:function(a){a&&this.setOptions(a)},_initGradient:function(a){!a.fill||!a.fill.colorStops||a.fill instanceof b.Gradient||this.set("fill",new b.Gradient(a.fill))},_initPattern:function(a){!a.fill||!a.fill.source||a.fill instanceof b.Pattern||this.set("fill",new b.Pattern(a.fill)),!a.stroke||!a.stroke.source||a.stroke instanceof b.Pattern||this.set("stroke",new b.Pattern(a.stroke))},_initClipping:function(a){if(a.clipTo&&"string"==typeof a.clipTo){var c=b.util.getFunctionBody(a.clipTo);"undefined"!=typeof c&&(this.clipTo=new Function("ctx",c))}},setOptions:function(a){for(var b in a)this.set(b,a[b]);this._initGradient(a),this._initPattern(a),this._initClipping(a)},transform:function(a,b){this.group&&this.group.transform(a,b),a.globalAlpha=this.opacity;var c=b?this._getLeftTopCoords():this.getCenterPoint();a.translate(c.x,c.y),a.rotate(f(this.angle)),a.scale(this.scaleX*(this.flipX?-1:1),this.scaleY*(this.flipY?-1:1))},toObject:function(a){var c=b.Object.NUM_FRACTION_DIGITS,e={type:this.type,originX:this.originX,originY:this.originY,left:d(this.left,c),top:d(this.top,c),width:d(this.width,c),height:d(this.height,c),fill:this.fill&&this.fill.toObject?this.fill.toObject():this.fill,stroke:this.stroke&&this.stroke.toObject?this.stroke.toObject():this.stroke,strokeWidth:d(this.strokeWidth,c),strokeDashArray:this.strokeDashArray,strokeLineCap:this.strokeLineCap,strokeLineJoin:this.strokeLineJoin,strokeMiterLimit:d(this.strokeMiterLimit,c),scaleX:d(this.scaleX,c),scaleY:d(this.scaleY,c),angle:d(this.getAngle(),c),flipX:this.flipX,flipY:this.flipY,opacity:d(this.opacity,c),shadow:this.shadow&&this.shadow.toObject?this.shadow.toObject():this.shadow,visible:this.visible,clipTo:this.clipTo&&String(this.clipTo),backgroundColor:this.backgroundColor};return this.includeDefaultValues||(e=this._removeDefaultValues(e)),b.util.populateWithProperties(this,e,a),e},toDatalessObject:function(a){return this.toObject(a)},_removeDefaultValues:function(a){var c=b.util.getKlass(a.type).prototype,d=c.stateProperties;return d.forEach(function(b){a[b]===c[b]&&delete a[b]}),a},toString:function(){return"#"},get:function(a){return this[a]},_setObject:function(a){for(var b in a)this._set(b,a[b])},set:function(a,b){return"object"==typeof a?this._setObject(a):"function"==typeof b&&"clipTo"!==a?this._set(a,b(this.get(a))):this._set(a,b),this},_set:function(a,c){var e="scaleX"===a||"scaleY"===a;return e&&(c=this._constrainScale(c)),"scaleX"===a&&0>c?(this.flipX=!this.flipX,c*=-1):"scaleY"===a&&0>c?(this.flipY=!this.flipY,c*=-1):"width"===a||"height"===a?this.minScaleLimit=d(Math.min(.1,1/Math.max(this.width,this.height)),2):"shadow"!==a||!c||c instanceof b.Shadow||(c=new b.Shadow(c)),this[a]=c,this},toggle:function(a){var b=this.get(a);return"boolean"==typeof b&&this.set(a,!b),this},setSourcePath:function(a){return this.sourcePath=a,this},getViewportTransform:function(){return this.canvas&&this.canvas.viewportTransform?this.canvas.viewportTransform:[1,0,0,1,0,0]},render:function(a,c){if(0!==this.width&&0!==this.height&&this.visible){if(a.save(),this._setupFillRule(a),this._transform(a,c),this._setStrokeStyles(a),this._setFillStyles(a),this.group&&"path-group"===this.group.type){a.translate(-this.group.width/2,-this.group.height/2);var d=this.transformMatrix;d&&a.transform.apply(a,d)}a.globalAlpha=this.group?a.globalAlpha*this.opacity:this.opacity,this._setShadow(a),this.clipTo&&b.util.clipContext(this,a),this._render(a,c),this.clipTo&&a.restore(),this._removeShadow(a),this._restoreFillRule(a),a.restore()}},_transform:function(a,b){var c=this.transformMatrix;c&&!this.group&&a.setTransform.apply(a,c),b||this.transform(a)},_setStrokeStyles:function(a){this.stroke&&(a.lineWidth=this.strokeWidth,a.lineCap=this.strokeLineCap,a.lineJoin=this.strokeLineJoin,a.miterLimit=this.strokeMiterLimit,a.strokeStyle=this.stroke.toLive?this.stroke.toLive(a):this.stroke)},_setFillStyles:function(a){this.fill&&(a.fillStyle=this.fill.toLive?this.fill.toLive(a):this.fill)},_renderControls:function(a,c){var d=this.getViewportTransform();if(a.save(),this.active&&!c){var e;this.group&&(e=b.util.transformPoint(this.group.getCenterPoint(),d),a.translate(e.x,e.y),a.rotate(f(this.group.angle))),e=b.util.transformPoint(this.getCenterPoint(),d,null!=this.group),this.group&&(e.x*=this.group.scaleX,e.y*=this.group.scaleY),a.translate(e.x,e.y),a.rotate(f(this.angle)),this.drawBorders(a),this.drawControls(a)}a.restore()},_setShadow:function(a){this.shadow&&(a.shadowColor=this.shadow.color,a.shadowBlur=this.shadow.blur,a.shadowOffsetX=this.shadow.offsetX,a.shadowOffsetY=this.shadow.offsetY) +},_removeShadow:function(a){this.shadow&&(a.shadowColor="",a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0)},_renderFill:function(a){if(this.fill){if(a.save(),this.fill.toLive&&a.translate(-this.width/2+this.fill.offsetX||0,-this.height/2+this.fill.offsetY||0),this.fill.gradientTransform){var b=this.fill.gradientTransform;a.transform.apply(a,b)}"destination-over"===this.fillRule?a.fill("evenodd"):a.fill(),a.restore(),this.shadow&&!this.shadow.affectStroke&&this._removeShadow(a)}},_renderStroke:function(a){if(this.stroke&&0!==this.strokeWidth){if(a.save(),this.strokeDashArray)1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),g?(a.setLineDash(this.strokeDashArray),this._stroke&&this._stroke(a)):this._renderDashedStroke&&this._renderDashedStroke(a),a.stroke();else{if(this.stroke.gradientTransform){var b=this.stroke.gradientTransform;a.transform.apply(a,b)}this._stroke?this._stroke(a):a.stroke()}this._removeShadow(a),a.restore()}},clone:function(a,c){return this.constructor.fromObject?this.constructor.fromObject(this.toObject(c),a):new b.Object(this.toObject(c))},cloneAsImage:function(a){var c=this.toDataURL();return b.util.loadImage(c,function(c){a&&a(new b.Image(c))}),this},toDataURL:function(a){a||(a={});var c=b.util.createCanvasElement(),d=this.getBoundingRect();c.width=d.width,c.height=d.height,b.util.wrapElement(c,"div");var e=new b.Canvas(c);"jpg"===a.format&&(a.format="jpeg"),"jpeg"===a.format&&(e.backgroundColor="#fff");var f={active:this.get("active"),left:this.getLeft(),top:this.getTop()};this.set("active",!1),this.setPositionByOrigin(new b.Point(c.width/2,c.height/2),"center","center");var g=this.canvas;e.add(this);var h=e.toDataURL(a);return this.set(f).setCoords(),this.canvas=g,e.dispose(),e=null,h},isType:function(a){return this.type===a},complexity:function(){return 0},toJSON:function(a){return this.toObject(a)},setGradient:function(a,c){c||(c={});var d={colorStops:[]};d.type=c.type||(c.r1||c.r2?"radial":"linear"),d.coords={x1:c.x1,y1:c.y1,x2:c.x2,y2:c.y2},(c.r1||c.r2)&&(d.coords.r1=c.r1,d.coords.r2=c.r2);for(var e in c.colorStops){var f=new b.Color(c.colorStops[e]);d.colorStops.push({offset:e,color:f.toRgb(),opacity:f.getAlpha()})}return this.set(a,b.Gradient.forObject(this,d))},setPatternFill:function(a){return this.set("fill",new b.Pattern(a))},setShadow:function(a){return this.set("shadow",a?new b.Shadow(a):null)},setColor:function(a){return this.set("fill",a),this},setAngle:function(a){var b=("center"!==this.originX||"center"!==this.originY)&&this.centeredRotation;return b&&this._setOriginToCenter(),this.set("angle",a),b&&this._resetOrigin(),this},centerH:function(){return this.canvas.centerObjectH(this),this},centerV:function(){return this.canvas.centerObjectV(this),this},center:function(){return this.canvas.centerObject(this),this},remove:function(){return this.canvas.remove(this),this},getLocalPointer:function(a,b){b=b||this.canvas.getPointer(a);var c=this.translateToOriginPoint(this.getCenterPoint(),"left","top");return{x:b.x-c.x,y:b.y-c.y}},_setupFillRule:function(a){this.fillRule&&(this._prevFillRule=a.globalCompositeOperation,a.globalCompositeOperation=this.fillRule)},_restoreFillRule:function(a){this.fillRule&&this._prevFillRule&&(a.globalCompositeOperation=this._prevFillRule)}}),b.util.createAccessors(b.Object),b.Object.prototype.rotate=b.Object.prototype.setAngle,c(b.Object.prototype,b.Observable),b.Object.NUM_FRACTION_DIGITS=2,b.Object.__uid=0)}("undefined"!=typeof exports?exports:this),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{translateToCenterPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x+(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x-(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y+(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y-(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},translateToOriginPoint:function(b,c,d){var e=b.x,f=b.y,g=this.stroke?this.strokeWidth:0;return"left"===c?e=b.x-(this.getWidth()+g*this.scaleX)/2:"right"===c&&(e=b.x+(this.getWidth()+g*this.scaleX)/2),"top"===d?f=b.y-(this.getHeight()+g*this.scaleY)/2:"bottom"===d&&(f=b.y+(this.getHeight()+g*this.scaleY)/2),fabric.util.rotatePoint(new fabric.Point(e,f),b,a(this.angle))},getCenterPoint:function(){var a=new fabric.Point(this.left,this.top);return this.translateToCenterPoint(a,this.originX,this.originY)},getPointByOrigin:function(a,b){var c=this.getCenterPoint();return this.translateToOriginPoint(c,a,b)},toLocalPoint:function(b,c,d){var e,f,g=this.getCenterPoint(),h=this.stroke?this.strokeWidth:0;return c&&d?(e="left"===c?g.x-(this.getWidth()+h*this.scaleX)/2:"right"===c?g.x+(this.getWidth()+h*this.scaleX)/2:g.x,f="top"===d?g.y-(this.getHeight()+h*this.scaleY)/2:"bottom"===d?g.y+(this.getHeight()+h*this.scaleY)/2:g.y):(e=this.left,f=this.top),fabric.util.rotatePoint(new fabric.Point(b.x,b.y),g,-a(this.angle)).subtractEquals(new fabric.Point(e,f))},setPositionByOrigin:function(a,b,c){var d=this.translateToCenterPoint(a,b,c),e=this.translateToOriginPoint(d,this.originX,this.originY);this.set("left",e.x),this.set("top",e.y)},adjustPosition:function(b){var c=a(this.angle),d=this.getWidth()/2,e=Math.cos(c)*d,f=Math.sin(c)*d,g=this.getWidth(),h=Math.cos(c)*g,i=Math.sin(c)*g;"center"===this.originX&&"left"===b||"right"===this.originX&&"center"===b?(this.left-=e,this.top-=f):"left"===this.originX&&"center"===b||"center"===this.originX&&"right"===b?(this.left+=e,this.top+=f):"left"===this.originX&&"right"===b?(this.left+=h,this.top+=i):"right"===this.originX&&"left"===b&&(this.left-=h,this.top-=i),this.setCoords(),this.originX=b},_setOriginToCenter:function(){this._originalOriginX=this.originX,this._originalOriginY=this.originY;var a=this.getCenterPoint();this.originX="center",this.originY="center",this.left=a.x,this.top=a.y},_resetOrigin:function(){var a=this.translateToOriginPoint(this.getCenterPoint(),this._originalOriginX,this._originalOriginY);this.originX=this._originalOriginX,this.originY=this._originalOriginY,this.left=a.x,this.top=a.y,this._originalOriginX=null,this._originalOriginY=null},_getLeftTopCoords:function(){return this.translateToOriginPoint(this.getCenterPoint(),"left","center")}})}(),function(){var a=fabric.util.degreesToRadians;fabric.util.object.extend(fabric.Object.prototype,{oCoords:null,intersectsWithRect:function(a,b){var c=this.oCoords,d=new fabric.Point(c.tl.x,c.tl.y),e=new fabric.Point(c.tr.x,c.tr.y),f=new fabric.Point(c.bl.x,c.bl.y),g=new fabric.Point(c.br.x,c.br.y),h=fabric.Intersection.intersectPolygonRectangle([d,e,g,f],a,b);return"Intersection"===h.status},intersectsWithObject:function(a){function b(a){return{tl:new fabric.Point(a.tl.x,a.tl.y),tr:new fabric.Point(a.tr.x,a.tr.y),bl:new fabric.Point(a.bl.x,a.bl.y),br:new fabric.Point(a.br.x,a.br.y)}}var c=b(this.oCoords),d=b(a.oCoords),e=fabric.Intersection.intersectPolygonPolygon([c.tl,c.tr,c.br,c.bl],[d.tl,d.tr,d.br,d.bl]);return"Intersection"===e.status},isContainedWithinObject:function(a){var b=a.getBoundingRect(),c=new fabric.Point(b.left,b.top),d=new fabric.Point(b.left+b.width,b.top+b.height);return this.isContainedWithinRect(c,d)},isContainedWithinRect:function(a,b){var c=this.getBoundingRect();return c.left>=a.x&&c.left+c.width<=b.x&&c.top>=a.y&&c.top+c.height<=b.y},containsPoint:function(a){var b=this._getImageLines(this.oCoords),c=this._findCrossPoints(a,b);return 0!==c&&c%2===1},_getImageLines:function(a){return{topline:{o:a.tl,d:a.tr},rightline:{o:a.tr,d:a.br},bottomline:{o:a.br,d:a.bl},leftline:{o:a.bl,d:a.tl}}},_findCrossPoints:function(a,b){var c,d,e,f,g,h,i,j=0;for(var k in b)if(i=b[k],!(i.o.y=a.y&&i.d.y>=a.y||(i.o.x===i.d.x&&i.o.x>=a.x?(g=i.o.x,h=a.y):(c=0,d=(i.d.y-i.o.y)/(i.d.x-i.o.x),e=a.y-c*a.x,f=i.o.y-d*i.o.x,g=-(e-f)/(c-d),h=e+c*g),g>=a.x&&(j+=1),2!==j)))break;return j},getBoundingRectWidth:function(){return this.getBoundingRect().width},getBoundingRectHeight:function(){return this.getBoundingRect().height},getBoundingRect:function(){this.oCoords||this.setCoords();var a=[this.oCoords.tl.x,this.oCoords.tr.x,this.oCoords.br.x,this.oCoords.bl.x],b=fabric.util.array.min(a),c=fabric.util.array.max(a),d=Math.abs(b-c),e=[this.oCoords.tl.y,this.oCoords.tr.y,this.oCoords.br.y,this.oCoords.bl.y],f=fabric.util.array.min(e),g=fabric.util.array.max(e),h=Math.abs(f-g);return{left:b,top:f,width:d,height:h}},getWidth:function(){return this.width*this.scaleX},getHeight:function(){return this.height*this.scaleY},_constrainScale:function(a){return Math.abs(a)a?-this.minScaleLimit:this.minScaleLimit:a},scale:function(a){return a=this._constrainScale(a),0>a&&(this.flipX=!this.flipX,this.flipY=!this.flipY,a*=-1),this.scaleX=a,this.scaleY=a,this.setCoords(),this},scaleToWidth:function(a){var b=this.getBoundingRectWidth()/this.getWidth();return this.scale(a/this.width/b)},scaleToHeight:function(a){var b=this.getBoundingRectHeight()/this.getHeight();return this.scale(a/this.height/b)},setCoords:function(){var b=this.strokeWidth>1?this.strokeWidth:0,c=a(this.angle),d=this.getViewportTransform(),e=function(a){return fabric.util.transformPoint(a,d)},f=this.width,g=this.height,h="round"===this.strokeLineCap||"square"===this.strokeLineCap,i="line"===this.type&&1===this.width,j="line"===this.type&&1===this.height,k=h&&j||"line"!==this.type,l=h&&i||"line"!==this.type;i?f=b:j&&(g=b),k&&(f+=b),l&&(g+=b),this.currentWidth=f*this.scaleX,this.currentHeight=g*this.scaleY,this.currentWidth<0&&(this.currentWidth=Math.abs(this.currentWidth));var m=Math.sqrt(Math.pow(this.currentWidth/2,2)+Math.pow(this.currentHeight/2,2)),n=Math.atan(isFinite(this.currentHeight/this.currentWidth)?this.currentHeight/this.currentWidth:0),o=Math.cos(n+c)*m,p=Math.sin(n+c)*m,q=Math.sin(c),r=Math.cos(c),s=this.getCenterPoint(),t=new fabric.Point(this.currentWidth,this.currentHeight),u=new fabric.Point(s.x-o,s.y-p),v=new fabric.Point(u.x+t.x*r,u.y+t.x*q),w=new fabric.Point(u.x-t.y*q,u.y+t.y*r),x=new fabric.Point(u.x+t.x/2*r,u.y+t.x/2*q),y=e(u),z=e(v),A=e(new fabric.Point(v.x-t.y*q,v.y+t.y*r)),B=e(w),C=e(new fabric.Point(u.x-t.y/2*q,u.y+t.y/2*r)),D=e(x),E=e(new fabric.Point(v.x-t.y/2*q,v.y+t.y/2*r)),F=e(new fabric.Point(w.x+t.x/2*r,w.y+t.x/2*q)),G=e(new fabric.Point(x.x,x.y)),H=Math.cos(n+c)*this.padding*Math.sqrt(2),I=Math.sin(n+c)*this.padding*Math.sqrt(2);return y=y.add(new fabric.Point(-H,-I)),z=z.add(new fabric.Point(I,-H)),A=A.add(new fabric.Point(H,I)),B=B.add(new fabric.Point(-I,H)),C=C.add(new fabric.Point((-H-I)/2,(-I+H)/2)),D=D.add(new fabric.Point((I-H)/2,-(I+H)/2)),E=E.add(new fabric.Point((I+H)/2,(I-H)/2)),F=F.add(new fabric.Point((H-I)/2,(H+I)/2)),G=G.add(new fabric.Point((I-H)/2,-(I+H)/2)),this.oCoords={tl:y,tr:z,br:A,bl:B,ml:C,mt:D,mr:E,mb:F,mtr:G},this._setCornerCoords&&this._setCornerCoords(),this}})}(),fabric.util.object.extend(fabric.Object.prototype,{sendToBack:function(){return this.group?fabric.StaticCanvas.prototype.sendToBack.call(this.group,this):this.canvas.sendToBack(this),this},bringToFront:function(){return this.group?fabric.StaticCanvas.prototype.bringToFront.call(this.group,this):this.canvas.bringToFront(this),this},sendBackwards:function(a){return this.group?fabric.StaticCanvas.prototype.sendBackwards.call(this.group,this,a):this.canvas.sendBackwards(this,a),this},bringForward:function(a){return this.group?fabric.StaticCanvas.prototype.bringForward.call(this.group,this,a):this.canvas.bringForward(this,a),this},moveTo:function(a){return this.group?fabric.StaticCanvas.prototype.moveTo.call(this.group,this,a):this.canvas.moveTo(this,a),this}}),fabric.util.object.extend(fabric.Object.prototype,{getSvgStyles:function(){var a=this.fill?this.fill.toLive?"url(#SVGID_"+this.fill.id+")":this.fill:"none",b="destination-over"===this.fillRule?"evenodd":this.fillRule,c=this.stroke?this.stroke.toLive?"url(#SVGID_"+this.stroke.id+")":this.stroke:"none",d=this.strokeWidth?this.strokeWidth:"0",e=this.strokeDashArray?this.strokeDashArray.join(" "):"",f=this.strokeLineCap?this.strokeLineCap:"butt",g=this.strokeLineJoin?this.strokeLineJoin:"miter",h=this.strokeMiterLimit?this.strokeMiterLimit:"4",i="undefined"!=typeof this.opacity?this.opacity:"1",j=this.visible?"":" visibility: hidden;",k=this.shadow&&"text"!==this.type?"filter: url(#SVGID_"+this.shadow.id+");":"";return["stroke: ",c,"; ","stroke-width: ",d,"; ","stroke-dasharray: ",e,"; ","stroke-linecap: ",f,"; ","stroke-linejoin: ",g,"; ","stroke-miterlimit: ",h,"; ","fill: ",a,"; ","fill-rule: ",b,"; ","opacity: ",i,";",k,j].join("")},getSvgTransform:function(){if(this.group)return"";var a=fabric.util.toFixed,b=this.getAngle(),c=!this.canvas||this.canvas.svgViewportTransformation?this.getViewportTransform():[1,0,0,1,0,0],d=fabric.util.transformPoint(this.getCenterPoint(),c),e=fabric.Object.NUM_FRACTION_DIGITS,f="path-group"===this.type?"":"translate("+a(d.x,e)+" "+a(d.y,e)+")",g=0!==b?" rotate("+a(b,e)+")":"",h=1===this.scaleX&&1===this.scaleY&&1===c[0]&&1===c[3]?"":" scale("+a(this.scaleX*c[0],e)+" "+a(this.scaleY*c[3],e)+")",i="path-group"===this.type?this.width*c[0]:0,j=this.flipX?" matrix(-1 0 0 1 "+i+" 0) ":"",k="path-group"===this.type?this.height*c[3]:0,l=this.flipY?" matrix(1 0 0 -1 0 "+k+")":"";return[f,g,h,j,l].join("")},getSvgTransformMatrix:function(){return this.transformMatrix?" matrix("+this.transformMatrix.join(" ")+")":""},_createBaseSVGMarkup:function(){var a=[];return this.fill&&this.fill.toLive&&a.push(this.fill.toSVG(this,!1)),this.stroke&&this.stroke.toLive&&a.push(this.stroke.toSVG(this,!1)),this.shadow&&a.push(this.shadow.toSVG(this)),a}}),fabric.util.object.extend(fabric.Object.prototype,{hasStateChanged:function(){return this.stateProperties.some(function(a){return this.get(a)!==this.originalState[a]},this)},saveState:function(a){return this.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),a&&a.stateProperties&&a.stateProperties.forEach(function(a){this.originalState[a]=this.get(a)},this),this},setupState:function(){return this.originalState={},this.saveState(),this}}),function(a){"use strict";function b(a,b){var c=a.origin,d=a.axis1,e=a.axis2,f=a.dimension,g=b.nearest,h=b.center,i=b.farthest;return function(){switch(this.get(c)){case g:return Math.min(this.get(d),this.get(e));case h:return Math.min(this.get(d),this.get(e))+.5*this.get(f);case i:return Math.max(this.get(d),this.get(e))}}}var c=a.fabric||(a.fabric={}),d=c.util.object.extend,e={x1:1,x2:1,y1:1,y2:1},f=c.StaticCanvas.supports("setLineDash");return c.Line?void c.warn("fabric.Line is already defined"):(c.Line=c.util.createClass(c.Object,{type:"line",x1:0,y1:0,x2:0,y2:0,initialize:function(a,b){b=b||{},a||(a=[0,0,0,0]),this.callSuper("initialize",b),this.set("x1",a[0]),this.set("y1",a[1]),this.set("x2",a[2]),this.set("y2",a[3]),this._setWidthHeight(b)},_setWidthHeight:function(a){a||(a={}),this.width=Math.abs(this.x2-this.x1)||1,this.height=Math.abs(this.y2-this.y1)||1,this.left="left"in a?a.left:this._getLeftToOriginX(),this.top="top"in a?a.top:this._getTopToOriginY()},_set:function(a,b){return this[a]=b,"undefined"!=typeof e[a]&&this._setWidthHeight(),this},_getLeftToOriginX:b({origin:"originX",axis1:"x1",axis2:"x2",dimension:"width"},{nearest:"left",center:"center",farthest:"right"}),_getTopToOriginY:b({origin:"originY",axis1:"y1",axis2:"y2",dimension:"height"},{nearest:"top",center:"center",farthest:"bottom"}),_render:function(a,b){if(a.beginPath(),b){var c=this.getCenterPoint();a.translate(c.x,c.y)}if(!this.strokeDashArray||this.strokeDashArray&&f){var d=this.x1<=this.x2?-1:1,e=this.y1<=this.y2?-1:1;a.moveTo(1===this.width?0:d*this.width/2,1===this.height?0:e*this.height/2),a.lineTo(1===this.width?0:-1*d*this.width/2,1===this.height?0:-1*e*this.height/2)}a.lineWidth=this.strokeWidth;var g=a.strokeStyle;a.strokeStyle=this.stroke||a.fillStyle,this.stroke&&this._renderStroke(a),a.strokeStyle=g},_renderDashedStroke:function(a){var b=this.x1<=this.x2?-1:1,d=this.y1<=this.y2?-1:1,e=1===this.width?0:b*this.width/2,f=1===this.height?0:d*this.height/2;a.beginPath(),c.util.drawDashedLine(a,e,f,-e,-f,this.strokeDashArray),a.closePath()},toObject:function(a){return d(this.callSuper("toObject",a),{x1:this.get("x1"),y1:this.get("y1"),x2:this.get("x2"),y2:this.get("y2")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c="";if(!this.group){var d=-this.width/2-(this.x1>this.x2?this.x2:this.x1),e=-this.height/2-(this.y1>this.y2?this.y2:this.y1);c="translate("+d+", "+e+") "}return b.push("\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),c.Line.ATTRIBUTE_NAMES=c.SHARED_ATTRIBUTES.concat("x1 y1 x2 y2".split(" ")),c.Line.fromElement=function(a,b){var e=c.parseAttributes(a,c.Line.ATTRIBUTE_NAMES),f=[e.x1||0,e.y1||0,e.x2||0,e.y2||0];return new c.Line(f,d(e,b))},void(c.Line.fromObject=function(a){var b=[a.x1,a.y1,a.x2,a.y2];return new c.Line(b,a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){return"radius"in a&&a.radius>0}var c=a.fabric||(a.fabric={}),d=2*Math.PI,e=c.util.object.extend;return c.Circle?void c.warn("fabric.Circle is already defined."):(c.Circle=c.util.createClass(c.Object,{type:"circle",radius:0,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("radius",a.radius||0)},_set:function(a,b){return this.callSuper("_set",a,b),"radius"===a&&this.setRadius(b),this},toObject:function(a){return e(this.callSuper("toObject",a),{radius:this.get("radius")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=0,d=0;return this.group&&(c=this.left+this.radius,d=this.top+this.radius),b.push("\n'),a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.arc(b?this.left+this.radius:0,b?this.top+this.radius:0,this.radius,0,d,!1),this._renderFill(a),this._renderStroke(a)},getRadiusX:function(){return this.get("radius")*this.get("scaleX")},getRadiusY:function(){return this.get("radius")*this.get("scaleY")},setRadius:function(a){this.radius=a,this.set("width",2*a).set("height",2*a)},complexity:function(){return 1}}),c.Circle.ATTRIBUTE_NAMES=c.SHARED_ATTRIBUTES.concat("cx cy r".split(" ")),c.Circle.fromElement=function(a,d){d||(d={});var f=c.parseAttributes(a,c.Circle.ATTRIBUTE_NAMES);if(!b(f))throw new Error("value of `r` attribute is required and can not be negative");f.left=f.left||0,f.top=f.top||0;var g=new c.Circle(e(f,d));return g.left-=g.radius,g.top-=g.radius,g},void(c.Circle.fromObject=function(a){return new c.Circle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});return b.Triangle?void b.warn("fabric.Triangle is already defined"):(b.Triangle=b.util.createClass(b.Object,{type:"triangle",initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("width",a.width||100).set("height",a.height||100)},_render:function(a){var b=this.width/2,c=this.height/2;a.beginPath(),a.moveTo(-b,c),a.lineTo(0,-c),a.lineTo(b,c),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=this.width/2,d=this.height/2;a.beginPath(),b.util.drawDashedLine(a,-c,d,0,-d,this.strokeDashArray),b.util.drawDashedLine(a,0,-d,c,d,this.strokeDashArray),b.util.drawDashedLine(a,c,d,-c,d,this.strokeDashArray),a.closePath()},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.width/2,d=this.height/2,e=[-c+" "+d,"0 "+-d,c+" "+d].join(",");return b.push("'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),void(b.Triangle.fromObject=function(a){return new b.Triangle(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=2*Math.PI,d=b.util.object.extend;return b.Ellipse?void b.warn("fabric.Ellipse is already defined."):(b.Ellipse=b.util.createClass(b.Object,{type:"ellipse",rx:0,ry:0,initialize:function(a){a=a||{},this.callSuper("initialize",a),this.set("rx",a.rx||0),this.set("ry",a.ry||0),this.set("width",2*this.get("rx")),this.set("height",2*this.get("ry"))},toObject:function(a){return d(this.callSuper("toObject",a),{rx:this.get("rx"),ry:this.get("ry")})},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=0,d=0;return this.group&&(c=this.left+this.rx,d=this.top+this.ry),b.push("\n'),a?a(b.join("")):b.join("")},_render:function(a,b){a.beginPath(),a.save(),a.transform(1,0,0,this.ry/this.rx,0,0),a.arc(b?this.left+this.rx:0,b?(this.top+this.ry)*this.rx/this.ry:0,this.rx,0,c,!1),a.restore(),this._renderFill(a),this._renderStroke(a)},complexity:function(){return 1}}),b.Ellipse.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("cx cy rx ry".split(" ")),b.Ellipse.fromElement=function(a,c){c||(c={});var e=b.parseAttributes(a,b.Ellipse.ATTRIBUTE_NAMES);e.left=e.left||0,e.top=e.top||0;var f=new b.Ellipse(d(e,c));return f.top-=f.ry,f.left-=f.rx,f},void(b.Ellipse.fromObject=function(a){return new b.Ellipse(a)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;if(b.Rect)return void console.warn("fabric.Rect is already defined");var d=b.Object.prototype.stateProperties.concat();d.push("rx","ry","x","y"),b.Rect=b.util.createClass(b.Object,{stateProperties:d,type:"rect",rx:0,ry:0,strokeDashArray:null,initialize:function(a){a=a||{},this.callSuper("initialize",a),this._initRxRy()},_initRxRy:function(){this.rx&&!this.ry?this.ry=this.rx:this.ry&&!this.rx&&(this.rx=this.ry)},_render:function(a,b){if(1===this.width&&1===this.height)return void a.fillRect(0,0,1,1);var c=this.rx?Math.min(this.rx,this.width/2):0,d=this.ry?Math.min(this.ry,this.height/2):0,e=this.width,f=this.height,g=b?this.left:-this.width/2,h=b?this.top:-this.height/2,i=0!==c||0!==d,j=.4477152502;a.beginPath(),a.moveTo(g+c,h),a.lineTo(g+e-c,h),i&&a.bezierCurveTo(g+e-j*c,h,g+e,h+j*d,g+e,h+d),a.lineTo(g+e,h+f-d),i&&a.bezierCurveTo(g+e,h+f-j*d,g+e-j*c,h+f,g+e-c,h+f),a.lineTo(g+c,h+f),i&&a.bezierCurveTo(g+j*c,h+f,g,h+f-j*d,g,h+f-d),a.lineTo(g,h+d),i&&a.bezierCurveTo(g,h+j*d,g+j*c,h,g+c,h),a.closePath(),this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c=-this.width/2,d=-this.height/2,e=this.width,f=this.height;a.beginPath(),b.util.drawDashedLine(a,c,d,c+e,d,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d,c+e,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c+e,d+f,c,d+f,this.strokeDashArray),b.util.drawDashedLine(a,c,d+f,c,d,this.strokeDashArray),a.closePath()},toObject:function(a){var b=c(this.callSuper("toObject",a),{rx:this.get("rx")||0,ry:this.get("ry")||0});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=this._createBaseSVGMarkup(),c=this.left,d=this.top;return this.group||(c=-this.width/2,d=-this.height/2),b.push("\n'),a?a(b.join("")):b.join("")},complexity:function(){return 1}}),b.Rect.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y rx ry width height".split(" ")),b.Rect.fromElement=function(a,d){if(!a)return null;d=d||{};var e=b.parseAttributes(a,b.Rect.ATTRIBUTE_NAMES);return e.left=e.left||0,e.top=e.top||0,new b.Rect(c(d?b.util.object.clone(d):{},e))},b.Rect.fromObject=function(a){return new b.Rect(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.toFixed;return b.Polyline?void b.warn("fabric.Polyline is already defined"):(b.Polyline=b.util.createClass(b.Object,{type:"polyline",points:null,initialize:function(a,b){b=b||{},this.set("points",a),this.callSuper("initialize",b),this._calcDimensions()},_calcDimensions:function(){return b.Polygon.prototype._calcDimensions.call(this)},_applyPointOffset:function(){return b.Polygon.prototype._applyPointOffset.call(this)},toObject:function(a){return b.Polygon.prototype.toObject.call(this,a)},toSVG:function(a){for(var b=[],d=this._createBaseSVGMarkup(),e=0,f=this.points.length;f>e;e++)b.push(c(this.points[e].x,2),",",c(this.points[e].y,2)," ");return d.push("\n'),a?a(d.join("")):d.join("")},_render:function(a){var b;a.beginPath(),this._applyPointOffset&&(this.group&&"path-group"===this.group.type||this._applyPointOffset(),this._applyPointOffset=null),a.moveTo(this.points[0].x,this.points[0].y);for(var c=0,d=this.points.length;d>c;c++)b=this.points[c],a.lineTo(b.x,b.y);this._renderFill(a),this._renderStroke(a)},_renderDashedStroke:function(a){var c,d;a.beginPath();for(var e=0,f=this.points.length;f>e;e++)c=this.points[e],d=this.points[e+1]||c,b.util.drawDashedLine(a,c.x,c.y,d.x,d.y,this.strokeDashArray)},complexity:function(){return this.get("points").length}}),b.Polyline.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat(),b.Polyline.fromElement=function(a,c){if(!a)return null;c||(c={});var d=b.parsePointsAttribute(a.getAttribute("points")),e=b.parseAttributes(a,b.Polyline.ATTRIBUTE_NAMES);return null===d?null:new b.Polyline(d,b.util.object.extend(e,c))},void(b.Polyline.fromObject=function(a){var c=a.points;return new b.Polyline(c,a,!0)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.min,e=b.util.array.max,f=b.util.toFixed;return b.Polygon?void b.warn("fabric.Polygon is already defined"):(b.Polygon=b.util.createClass(b.Object,{type:"polygon",points:null,initialize:function(a,b){b=b||{},this.points=a,this.callSuper("initialize",b),this._calcDimensions()},_calcDimensions:function(){var a=this.points,b=d(a,"x"),c=d(a,"y"),f=e(a,"x"),g=e(a,"y");this.width=f-b||1,this.height=g-c||1,this.left=b,this.top=c},_applyPointOffset:function(){this.points.forEach(function(a){a.x-=this.left+this.width/2,a.y-=this.top+this.height/2},this)},toObject:function(a){return c(this.callSuper("toObject",a),{points:this.points.concat()})},toSVG:function(a){for(var b=[],c=this._createBaseSVGMarkup(),d=0,e=this.points.length;e>d;d++)b.push(f(this.points[d].x,2),",",f(this.points[d].y,2)," ");return c.push("\n'),a?a(c.join("")):c.join("")},_render:function(a){var b;a.beginPath(),this._applyPointOffset&&(this.group&&"path-group"===this.group.type||this._applyPointOffset(),this._applyPointOffset=null),a.moveTo(this.points[0].x,this.points[0].y);for(var c=0,d=this.points.length;d>c;c++)b=this.points[c],a.lineTo(b.x,b.y);this._renderFill(a),(this.stroke||this.strokeDashArray)&&(a.closePath(),this._renderStroke(a))},_renderDashedStroke:function(a){var c,d;a.beginPath();for(var e=0,f=this.points.length;f>e;e++)c=this.points[e],d=this.points[e+1]||this.points[0],b.util.drawDashedLine(a,c.x,c.y,d.x,d.y,this.strokeDashArray);a.closePath()},complexity:function(){return this.points.length}}),b.Polygon.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat(),b.Polygon.fromElement=function(a,d){if(!a)return null;d||(d={});var e=b.parsePointsAttribute(a.getAttribute("points")),f=b.parseAttributes(a,b.Polygon.ATTRIBUTE_NAMES);return null===e?null:new b.Polygon(e,c(f,d))},void(b.Polygon.fromObject=function(a){return new b.Polygon(a.points,a,!0)}))}("undefined"!=typeof exports?exports:this),function(a){"use strict";function b(a){return"H"===a[0]?a[1]:a[a.length-2]}function c(a){return"V"===a[0]?a[1]:a[a.length-1]}var d=a.fabric||(a.fabric={}),e=d.util.array.min,f=d.util.array.max,g=d.util.object.extend,h=Object.prototype.toString,i=d.util.drawArc,j={m:2,l:2,h:1,v:1,c:6,s:4,q:4,t:2,a:7},k={m:"l",M:"L"};return d.Path?void d.warn("fabric.Path is already defined"):(d.Path=d.util.createClass(d.Object,{type:"path",path:null,initialize:function(a,b){if(b=b||{},this.setOptions(b),!a)throw new Error("`path` argument is required");var c="[object Array]"===h.call(a);this.path=c?a:a.match&&a.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi),this.path&&(c||(this.path=this._parsePath()),this._initializePath(b),b.sourcePath&&this.setSourcePath(b.sourcePath))},_initializePath:function(a){var b="width"in a&&null!=a.width,c="height"in a&&null!=a.width,d="left"in a,e="top"in a,f=d?this.left:0,h=e?this.top:0;b&&c?(e||(this.top=this.height/2),d||(this.left=this.width/2)):(g(this,this._parseDimensions()),b&&(this.width=a.width),c&&(this.height=a.height)),this.pathOffset=this.pathOffset||this._calculatePathOffset(f,h)},_calculatePathOffset:function(a,b){return{x:this.left-a-this.width/2,y:this.top-b-this.height/2}},_render:function(a,b){var c,d,e,f,g,h=null,j=0,k=0,l=0,m=0,n=0,o=0,p=-(this.width/2+this.pathOffset.x),q=-(this.height/2+this.pathOffset.y);b&&(p+=this.width/2,q+=this.height/2);for(var r=0,s=this.path.length;s>r;++r){switch(c=this.path[r],c[0]){case"l":l+=c[1],m+=c[2],a.lineTo(l+p,m+q);break;case"L":l=c[1],m=c[2],a.lineTo(l+p,m+q);break;case"h":l+=c[1],a.lineTo(l+p,m+q);break;case"H":l=c[1],a.lineTo(l+p,m+q);break;case"v":m+=c[1],a.lineTo(l+p,m+q);break;case"V":m=c[1],a.lineTo(l+p,m+q);break;case"m":l+=c[1],m+=c[2],j=l,k=m,a.moveTo(l+p,m+q);break;case"M":l=c[1],m=c[2],j=l,k=m,a.moveTo(l+p,m+q);break;case"c":d=l+c[5],e=m+c[6],n=l+c[3],o=m+c[4],a.bezierCurveTo(l+c[1]+p,m+c[2]+q,n+p,o+q,d+p,e+q),l=d,m=e;break;case"C":l=c[5],m=c[6],n=c[3],o=c[4],a.bezierCurveTo(c[1]+p,c[2]+q,n+p,o+q,l+p,m+q);break;case"s":d=l+c[3],e=m+c[4],n=n?2*l-n:l,o=o?2*m-o:m,a.bezierCurveTo(n+p,o+q,l+c[1]+p,m+c[2]+q,d+p,e+q),n=l+c[1],o=m+c[2],l=d,m=e;break;case"S":d=c[3],e=c[4],n=2*l-n,o=2*m-o,a.bezierCurveTo(n+p,o+q,c[1]+p,c[2]+q,d+p,e+q),l=d,m=e,n=c[1],o=c[2];break;case"q":d=l+c[3],e=m+c[4],n=l+c[1],o=m+c[2],a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e;break;case"Q":d=c[3],e=c[4],a.quadraticCurveTo(c[1]+p,c[2]+q,d+p,e+q),l=d,m=e,n=c[1],o=c[2];break;case"t":d=l+c[1],e=m+c[2],null===h[0].match(/[QqTt]/)?(n=l,o=m):"t"===h[0]?(n=2*l-f,o=2*m-g):"q"===h[0]&&(n=2*l-n,o=2*m-o),f=n,g=o,a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e,n=l+c[1],o=m+c[2];break;case"T":d=c[1],e=c[2],n=2*l-n,o=2*m-o,a.quadraticCurveTo(n+p,o+q,d+p,e+q),l=d,m=e;break;case"a":i(a,l+p,m+q,[c[1],c[2],c[3],c[4],c[5],c[6]+l+p,c[7]+m+q]),l+=c[6],m+=c[7];break;case"A":i(a,l+p,m+q,[c[1],c[2],c[3],c[4],c[5],c[6]+p,c[7]+q]),l=c[6],m=c[7];break;case"z":case"Z":l=j,m=k,a.closePath()}h=c}},render:function(a,b){if(this.visible){a.save(),b&&a.translate(-this.width/2,-this.height/2);var c=this.transformMatrix;c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),b||this.transform(a),this._setStrokeStyles(a),this._setFillStyles(a),this._setShadow(a),this.clipTo&&d.util.clipContext(this,a),a.beginPath(),a.globalAlpha=this.group?a.globalAlpha*this.opacity:this.opacity,this._render(a,b),this._renderFill(a),this._renderStroke(a),this.clipTo&&a.restore(),this._removeShadow(a),a.restore()}},toString:function(){return"#"},toObject:function(a){var b=g(this.callSuper("toObject",a),{path:this.path.map(function(a){return a.slice()}),pathOffset:this.pathOffset});return this.sourcePath&&(b.sourcePath=this.sourcePath),this.transformMatrix&&(b.transformMatrix=this.transformMatrix),b},toDatalessObject:function(a){var b=this.toObject(a);return this.sourcePath&&(b.path=this.sourcePath),delete b.sourcePath,b},toSVG:function(a){for(var b=[],c=this._createBaseSVGMarkup(),d=0,e=this.path.length;e>d;d++)b.push(this.path[d].join(" ")); +var f=b.join(" ");return c.push("\n"),a?a(c.join("")):c.join("")},complexity:function(){return this.path.length},_parsePath:function(){for(var a,b,c,d,e,f=[],g=[],h=/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi,i=0,l=this.path.length;l>i;i++){for(a=this.path[i],d=a.slice(1).trim(),g.length=0;c=h.exec(d);)g.push(c[0]);e=[a.charAt(0)];for(var m=0,n=g.length;n>m;m++)b=parseFloat(g[m]),isNaN(b)||e.push(b);var o=e[0],p=j[o.toLowerCase()],q=k[o]||o;if(e.length-1>p)for(var r=1,s=e.length;s>r;r+=p)f.push([o].concat(e.slice(r,r+p))),o=q;else f.push(e)}return f},_parseDimensions:function(){var a=[],b=[],c={};this.path.forEach(function(d,e){this._getCoordsFromCommand(d,e,a,b,c)},this);var d=e(a),g=e(b),h=f(a),i=f(b),j=h-d,k=i-g,l={left:this.left+(d+j/2),top:this.top+(g+k/2),width:j,height:k};return l},_getCoordsFromCommand:function(a,d,e,f,g){var h=!1;"H"!==a[0]&&(g.x=b(0===d?a:this.path[d-1])),"V"!==a[0]&&(g.y=c(0===d?a:this.path[d-1])),a[0]===a[0].toLowerCase()&&(h=!0);var i,j=this._getXY(a,h,g);i=parseInt(j.x,10),isNaN(i)||e.push(i),i=parseInt(j.y,10),isNaN(i)||f.push(i)},_getXY:function(a,d,e){var f=d?e.x+b(a):"V"===a[0]?e.x:b(a),g=d?e.y+c(a):"H"===a[0]?e.y:c(a);return{x:f,y:g}}}),d.Path.fromObject=function(a,b){"string"==typeof a.path?d.loadSVGFromURL(a.path,function(c){var e=c[0],f=a.path;delete a.path,d.util.object.extend(e,a),e.setSourcePath(f),b(e)}):b(new d.Path(a.path,a))},d.Path.ATTRIBUTE_NAMES=d.SHARED_ATTRIBUTES.concat(["d"]),d.Path.fromElement=function(a,b,c){var e=d.parseAttributes(a,d.Path.ATTRIBUTE_NAMES);b&&b(new d.Path(e.d,g(e,c)))},void(d.Path.async=!0))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.invoke,e=b.Object.prototype.toObject;return b.PathGroup?void b.warn("fabric.PathGroup is already defined"):(b.PathGroup=b.util.createClass(b.Path,{type:"path-group",fill:"",initialize:function(a,b){b=b||{},this.paths=a||[];for(var c=this.paths.length;c--;)this.paths[c].group=this;this.setOptions(b),b.widthAttr&&(this.scaleX=b.widthAttr/b.width),b.heightAttr&&(this.scaleY=b.heightAttr/b.height),this.setCoords(),b.sourcePath&&this.setSourcePath(b.sourcePath)},render:function(a){if(this.visible){a.save();var c=this.transformMatrix;c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),this.transform(a),this._setShadow(a),this.clipTo&&b.util.clipContext(this,a);for(var d=0,e=this.paths.length;e>d;++d)this.paths[d].render(a,!0);this.clipTo&&a.restore(),this._removeShadow(a),a.restore()}},_set:function(a,b){if("fill"===a&&b&&this.isSameColor())for(var c=this.paths.length;c--;)this.paths[c]._set(a,b);return this.callSuper("_set",a,b)},toObject:function(a){var b=c(e.call(this,a),{paths:d(this.getObjects(),"toObject",a)});return this.sourcePath&&(b.sourcePath=this.sourcePath),b},toDatalessObject:function(a){var b=this.toObject(a);return this.sourcePath&&(b.paths=this.sourcePath),b},toSVG:function(a){for(var b=this.getObjects(),c="translate("+this.left+" "+this.top+")",d=["\n"],e=0,f=b.length;f>e;e++)d.push(b[e].toSVG(a));return d.push("\n"),a?a(d.join("")):d.join("")},toString:function(){return"#"},isSameColor:function(){var a=(this.getObjects()[0].get("fill")||"").toLowerCase();return this.getObjects().every(function(b){return(b.get("fill")||"").toLowerCase()===a})},complexity:function(){return this.paths.reduce(function(a,b){return a+(b&&b.complexity?b.complexity():0)},0)},getObjects:function(){return this.paths}}),b.PathGroup.fromObject=function(a,c){"string"==typeof a.paths?b.loadSVGFromURL(a.paths,function(d){var e=a.paths;delete a.paths;var f=b.util.groupSVGElements(d,a,e);c(f)}):b.util.enlivenObjects(a.paths,function(d){delete a.paths,c(new b.PathGroup(d,a))})},void(b.PathGroup.async=!0))}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.array.min,e=b.util.array.max,f=b.util.array.invoke;if(!b.Group){var g={lockMovementX:!0,lockMovementY:!0,lockRotation:!0,lockScalingX:!0,lockScalingY:!0,lockUniScaling:!0};b.Group=b.util.createClass(b.Object,b.Collection,{type:"group",initialize:function(a,b){b=b||{},this._objects=a||[];for(var d=this._objects.length;d--;)this._objects[d].group=this;this.originalState={},this.callSuper("initialize"),this._calcBounds(),this._updateObjectsCoords(),b&&c(this,b),this._setOpacityIfSame(),this.setCoords(),this.saveCoords()},_updateObjectsCoords:function(){this.forEachObject(this._updateObjectCoords,this)},_updateObjectCoords:function(a){var b=a.getLeft(),c=a.getTop();a.set({originalLeft:b,originalTop:c,left:b-this.left,top:c-this.top}),a.setCoords(),a.__origHasControls=a.hasControls,a.hasControls=!1},toString:function(){return"#"},addWithUpdate:function(a){return this._restoreObjectsState(),a&&(this._objects.push(a),a.group=this),this.forEachObject(this._setObjectActive,this),this._calcBounds(),this._updateObjectsCoords(),this},_setObjectActive:function(a){a.set("active",!0),a.group=this},removeWithUpdate:function(a){return this._moveFlippedObject(a),this._restoreObjectsState(),this.forEachObject(this._setObjectActive,this),this.remove(a),this._calcBounds(),this._updateObjectsCoords(),this},_onObjectAdded:function(a){a.group=this},_onObjectRemoved:function(a){delete a.group,a.set("active",!1)},delegatedProperties:{fill:!0,opacity:!0,fontFamily:!0,fontWeight:!0,fontSize:!0,fontStyle:!0,lineHeight:!0,textDecoration:!0,textAlign:!0,backgroundColor:!0},_set:function(a,b){if(a in this.delegatedProperties){var c=this._objects.length;for(this[a]=b;c--;)this._objects[c].set(a,b)}else this[a]=b},toObject:function(a){return c(this.callSuper("toObject",a),{objects:f(this._objects,"toObject",a)})},render:function(a){if(this.visible){a.save(),this.clipTo&&b.util.clipContext(this,a);for(var c=0,d=this._objects.length;d>c;c++)this._renderObject(this._objects[c],a);this.clipTo&&a.restore(),a.restore()}},_renderControls:function(a,b){this.callSuper("_renderControls",a,b);for(var c=0,d=this._objects.length;d>c;c++)this._objects[c]._renderControls(a)},_renderObject:function(a,b){var c=a.hasRotatingPoint;a.visible&&(a.hasRotatingPoint=!1,a.render(b),a.hasRotatingPoint=c)},_restoreObjectsState:function(){return this._objects.forEach(this._restoreObjectState,this),this},_moveFlippedObject:function(a){var b=a.get("originX"),c=a.get("originY"),d=a.getCenterPoint();a.set({originX:"center",originY:"center",left:d.x,top:d.y}),this._toggleFlipping(a);var e=a.getPointByOrigin(b,c);return a.set({originX:b,originY:c,left:e.x,top:e.y}),this},_toggleFlipping:function(a){this.flipX&&(a.toggle("flipX"),a.set("left",-a.get("left")),a.setAngle(-a.getAngle())),this.flipY&&(a.toggle("flipY"),a.set("top",-a.get("top")),a.setAngle(-a.getAngle()))},_restoreObjectState:function(a){return this._setObjectPosition(a),a.setCoords(),a.hasControls=a.__origHasControls,delete a.__origHasControls,a.set("active",!1),a.setCoords(),delete a.group,this},_setObjectPosition:function(a){var b=this.getLeft(),c=this.getTop(),d=this._getRotatedLeftTop(a);a.set({angle:a.getAngle()+this.getAngle(),left:b+d.left,top:c+d.top,scaleX:a.get("scaleX")*this.get("scaleX"),scaleY:a.get("scaleY")*this.get("scaleY")})},_getRotatedLeftTop:function(a){var b=this.getAngle()*(Math.PI/180);return{left:-Math.sin(b)*a.getTop()*this.get("scaleY")+Math.cos(b)*a.getLeft()*this.get("scaleX"),top:Math.cos(b)*a.getTop()*this.get("scaleY")+Math.sin(b)*a.getLeft()*this.get("scaleX")}},destroy:function(){return this._objects.forEach(this._moveFlippedObject,this),this._restoreObjectsState()},saveCoords:function(){return this._originalLeft=this.get("left"),this._originalTop=this.get("top"),this},hasMoved:function(){return this._originalLeft!==this.get("left")||this._originalTop!==this.get("top")},setObjectsCoords:function(){return this.forEachObject(function(a){a.setCoords()}),this},_setOpacityIfSame:function(){var a=this.getObjects(),b=a[0]?a[0].get("opacity"):1,c=a.every(function(a){return a.get("opacity")===b});c&&(this.opacity=b)},_calcBounds:function(a){for(var b,c=[],d=[],e=0,f=this._objects.length;f>e;++e){b=this._objects[e],b.setCoords();for(var g in b.oCoords)c.push(b.oCoords[g].x),d.push(b.oCoords[g].y)}this.set(this._getBounds(c,d,a))},_getBounds:function(a,c,f){var g=b.util.invertTransform(this.getViewportTransform()),h=b.util.transformPoint(new b.Point(d(a),d(c)),g),i=b.util.transformPoint(new b.Point(e(a),e(c)),g),j={width:i.x-h.x||0,height:i.y-h.y||0};return f||(j.left=(h.x+i.x)/2||0,j.top=(h.y+i.y)/2||0),j},toSVG:function(a){for(var b=["\n'],c=0,d=this._objects.length;d>c;c++)b.push(this._objects[c].toSVG(a));return b.push("\n"),a?a(b.join("")):b.join("")},get:function(a){if(a in g){if(this[a])return this[a];for(var b=0,c=this._objects.length;c>b;b++)if(this._objects[b][a])return!0;return!1}return a in this.delegatedProperties?this._objects[0]&&this._objects[0].get(a):this[a]}}),b.Group.fromObject=function(a,c){b.util.enlivenObjects(a.objects,function(d){delete a.objects,c&&c(new b.Group(d,a))})},b.Group.async=!0}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=fabric.util.object.extend;return a.fabric||(a.fabric={}),a.fabric.Image?void fabric.warn("fabric.Image is already defined."):(fabric.Image=fabric.util.createClass(fabric.Object,{type:"image",crossOrigin:"",initialize:function(a,b){b||(b={}),this.filters=[],this.callSuper("initialize",b),this._initElement(a,b),this._initConfig(b),b.filters&&(this.filters=b.filters,this.applyFilters())},getElement:function(){return this._element},setElement:function(a,b){return this._element=a,this._originalElement=a,this._initConfig(),0!==this.filters.length&&this.applyFilters(b),this},setCrossOrigin:function(a){return this.crossOrigin=a,this._element.crossOrigin=a,this},getOriginalSize:function(){var a=this.getElement();return{width:a.width,height:a.height}},_stroke:function(a){a.save(),this._setStrokeStyles(a),a.beginPath(),a.strokeRect(-this.width/2,-this.height/2,this.width,this.height),a.closePath(),a.restore()},_renderDashedStroke:function(a){var b=-this.width/2,c=-this.height/2,d=this.width,e=this.height;a.save(),this._setStrokeStyles(a),a.beginPath(),fabric.util.drawDashedLine(a,b,c,b+d,c,this.strokeDashArray),fabric.util.drawDashedLine(a,b+d,c,b+d,c+e,this.strokeDashArray),fabric.util.drawDashedLine(a,b+d,c+e,b,c+e,this.strokeDashArray),fabric.util.drawDashedLine(a,b,c+e,b,c,this.strokeDashArray),a.closePath(),a.restore()},toObject:function(a){return b(this.callSuper("toObject",a),{src:this._originalElement.src||this._originalElement._src,filters:this.filters.map(function(a){return a&&a.toObject()}),crossOrigin:this.crossOrigin})},toSVG:function(a){var b=[],c=-this.width/2,d=-this.height/2;if(this.group&&(c=this.left,d=this.top),b.push('\n','\n"),this.stroke||this.strokeDashArray){var e=this.fill;this.fill=null,b.push("\n'),this.fill=e}return b.push("\n"),a?a(b.join("")):b.join("")},getSrc:function(){return this.getElement()?this.getElement().src||this.getElement()._src:void 0},toString:function(){return'#'},clone:function(a,b){this.constructor.fromObject(this.toObject(b),a)},applyFilters:function(a){if(this._originalElement){if(0===this.filters.length)return this._element=this._originalElement,void(a&&a());var b=this._originalElement,c=fabric.util.createCanvasElement(),d=fabric.util.createImage(),e=this;return c.width=b.width,c.height=b.height,c.getContext("2d").drawImage(b,0,0,b.width,b.height),this.filters.forEach(function(a){a&&a.applyTo(c)}),d.width=b.width,d.height=b.height,fabric.isLikelyNode?(d.src=c.toBuffer(undefined,fabric.Image.pngCompression),e._element=d,a&&a()):(d.onload=function(){e._element=d,a&&a(),d.onload=c=b=null},d.src=c.toDataURL("image/png")),this}},_render:function(a,b){this._element&&a.drawImage(this._element,b?this.left:-this.width/2,b?this.top:-this.height/2,this.width,this.height),this._renderStroke(a)},_resetWidthHeight:function(){var a=this.getElement();this.set("width",a.width),this.set("height",a.height)},_initElement:function(a){this.setElement(fabric.util.getById(a)),fabric.util.addClass(this.getElement(),fabric.Image.CSS_CANVAS)},_initConfig:function(a){a||(a={}),this.setOptions(a),this._setWidthHeight(a),this._element&&this.crossOrigin&&(this._element.crossOrigin=this.crossOrigin)},_initFilters:function(a,b){a.filters&&a.filters.length?fabric.util.enlivenObjects(a.filters,function(a){b&&b(a)},"fabric.Image.filters"):b&&b()},_setWidthHeight:function(a){this.width="width"in a?a.width:this.getElement()?this.getElement().width||0:0,this.height="height"in a?a.height:this.getElement()?this.getElement().height||0:0},complexity:function(){return 1}}),fabric.Image.CSS_CANVAS="canvas-img",fabric.Image.prototype.getSvgSrc=fabric.Image.prototype.getSrc,fabric.Image.fromObject=function(a,b){fabric.util.loadImage(a.src,function(c){fabric.Image.prototype._initFilters.call(a,a,function(d){a.filters=d||[];var e=new fabric.Image(c,a);b&&b(e)})},null,a.crossOrigin)},fabric.Image.fromURL=function(a,b,c){fabric.util.loadImage(a,function(a){b(new fabric.Image(a,c))},null,c&&c.crossOrigin)},fabric.Image.ATTRIBUTE_NAMES=fabric.SHARED_ATTRIBUTES.concat("x y width height xlink:href".split(" ")),fabric.Image.fromElement=function(a,c,d){var e=fabric.parseAttributes(a,fabric.Image.ATTRIBUTE_NAMES);fabric.Image.fromURL(e["xlink:href"],c,b(d?fabric.util.object.clone(d):{},e))},fabric.Image.async=!0,void(fabric.Image.pngCompression=1))}("undefined"!=typeof exports?exports:this),fabric.Image.filters=fabric.Image.filters||{},fabric.Image.filters.BaseFilter=fabric.util.createClass({type:"BaseFilter",toObject:function(){return{type:this.type}},toJSON:function(){return this.toObject()}}),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Brightness=b.util.createClass(b.Image.filters.BaseFilter,{type:"Brightness",initialize:function(a){a=a||{},this.brightness=a.brightness||0},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.brightness,f=0,g=d.length;g>f;f+=4)d[f]+=e,d[f+1]+=e,d[f+2]+=e;b.putImageData(c,0,0)},toObject:function(){return c(this.callSuper("toObject"),{brightness:this.brightness})}}),b.Image.filters.Brightness.fromObject=function(a){return new b.Image.filters.Brightness(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Convolute=b.util.createClass(b.Image.filters.BaseFilter,{type:"Convolute",initialize:function(a){a=a||{},this.opaque=a.opaque,this.matrix=a.matrix||[0,0,0,0,1,0,0,0,0];var c=b.util.createCanvasElement();this.tmpCtx=c.getContext("2d")},_createImageData:function(a,b){return this.tmpCtx.createImageData(a,b)},applyTo:function(a){for(var b=this.matrix,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=Math.round(Math.sqrt(b.length)),f=Math.floor(e/2),g=d.data,h=d.width,i=d.height,j=h,k=i,l=this._createImageData(j,k),m=l.data,n=this.opaque?1:0,o=0;k>o;o++)for(var p=0;j>p;p++){for(var q=o,r=p,s=4*(o*j+p),t=0,u=0,v=0,w=0,x=0;e>x;x++)for(var y=0;e>y;y++){var z=q+x-f,A=r+y-f;if(!(0>z||z>i||0>A||A>h)){var B=4*(z*h+A),C=b[x*e+y];t+=g[B]*C,u+=g[B+1]*C,v+=g[B+2]*C,w+=g[B+3]*C}}m[s]=t,m[s+1]=u,m[s+2]=v,m[s+3]=w+n*(255-w)}c.putImageData(l,0,0)},toObject:function(){return c(this.callSuper("toObject"),{opaque:this.opaque,matrix:this.matrix})}}),b.Image.filters.Convolute.fromObject=function(a){return new b.Image.filters.Convolute(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.GradientTransparency=b.util.createClass(b.Image.filters.BaseFilter,{type:"GradientTransparency",initialize:function(a){a=a||{},this.threshold=a.threshold||100},applyTo:function(a){for(var b=a.getContext("2d"),c=b.getImageData(0,0,a.width,a.height),d=c.data,e=this.threshold,f=d.length,g=0,h=d.length;h>g;g+=4)d[g+3]=e+255*(f-g)/f;b.putImageData(c,0,0)},toObject:function(){return c(this.callSuper("toObject"),{threshold:this.threshold})}}),b.Image.filters.GradientTransparency.fromObject=function(a){return new b.Image.filters.GradientTransparency(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Grayscale=b.util.createClass(b.Image.filters.BaseFilter,{type:"Grayscale",applyTo:function(a){for(var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=d.width*d.height*4,g=0;f>g;)b=(e[g]+e[g+1]+e[g+2])/3,e[g]=b,e[g+1]=b,e[g+2]=b,g+=4;c.putImageData(d,0,0)}}),b.Image.filters.Grayscale.fromObject=function(){return new b.Image.filters.Grayscale}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Invert=b.util.createClass(b.Image.filters.BaseFilter,{type:"Invert",applyTo:function(a){var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=e.length;for(b=0;f>b;b+=4)e[b]=255-e[b],e[b+1]=255-e[b+1],e[b+2]=255-e[b+2];c.putImageData(d,0,0)}}),b.Image.filters.Invert.fromObject=function(){return new b.Image.filters.Invert}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Mask=b.util.createClass(b.Image.filters.BaseFilter,{type:"Mask",initialize:function(a){a=a||{},this.mask=a.mask,this.channel=[0,1,2,3].indexOf(a.channel)>-1?a.channel:0},applyTo:function(a){if(this.mask){var c,d=a.getContext("2d"),e=d.getImageData(0,0,a.width,a.height),f=e.data,g=this.mask.getElement(),h=b.util.createCanvasElement(),i=this.channel,j=e.width*e.height*4;h.width=g.width,h.height=g.height,h.getContext("2d").drawImage(g,0,0,g.width,g.height);var k=h.getContext("2d").getImageData(0,0,g.width,g.height),l=k.data;for(c=0;j>c;c+=4)f[c+3]=l[c+i];d.putImageData(e,0,0)}},toObject:function(){return c(this.callSuper("toObject"),{mask:this.mask.toObject(),channel:this.channel})}}),b.Image.filters.Mask.fromObject=function(a,c){b.util.loadImage(a.mask.src,function(d){a.mask=new b.Image(d,a.mask),c&&c(new b.Image.filters.Mask(a))})},b.Image.filters.Mask.async=!0}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Noise=b.util.createClass(b.Image.filters.BaseFilter,{type:"Noise",initialize:function(a){a=a||{},this.noise=a.noise||0},applyTo:function(a){for(var b,c=a.getContext("2d"),d=c.getImageData(0,0,a.width,a.height),e=d.data,f=this.noise,g=0,h=e.length;h>g;g+=4)b=(.5-Math.random())*f,e[g]+=b,e[g+1]+=b,e[g+2]+=b;c.putImageData(d,0,0)},toObject:function(){return c(this.callSuper("toObject"),{noise:this.noise})}}),b.Image.filters.Noise.fromObject=function(a){return new b.Image.filters.Noise(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Pixelate=b.util.createClass(b.Image.filters.BaseFilter,{type:"Pixelate",initialize:function(a){a=a||{},this.blocksize=a.blocksize||4},applyTo:function(a){var b,c,d,e,f,g,h,i=a.getContext("2d"),j=i.getImageData(0,0,a.width,a.height),k=j.data,l=j.height,m=j.width;for(c=0;l>c;c+=this.blocksize)for(d=0;m>d;d+=this.blocksize){b=4*c*m+4*d,e=k[b],f=k[b+1],g=k[b+2],h=k[b+3];for(var n=c,o=c+this.blocksize;o>n;n++)for(var p=d,q=d+this.blocksize;q>p;p++)b=4*n*m+4*p,k[b]=e,k[b+1]=f,k[b+2]=g,k[b+3]=h}i.putImageData(j,0,0)},toObject:function(){return c(this.callSuper("toObject"),{blocksize:this.blocksize})}}),b.Image.filters.Pixelate.fromObject=function(a){return new b.Image.filters.Pixelate(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.RemoveWhite=b.util.createClass(b.Image.filters.BaseFilter,{type:"RemoveWhite",initialize:function(a){a=a||{},this.threshold=a.threshold||30,this.distance=a.distance||20},applyTo:function(a){for(var b,c,d,e=a.getContext("2d"),f=e.getImageData(0,0,a.width,a.height),g=f.data,h=this.threshold,i=this.distance,j=255-h,k=Math.abs,l=0,m=g.length;m>l;l+=4)b=g[l],c=g[l+1],d=g[l+2],b>j&&c>j&&d>j&&k(b-c)b;b+=4)c=.3*f[b]+.59*f[b+1]+.11*f[b+2],f[b]=c+100,f[b+1]=c+50,f[b+2]=c+255;d.putImageData(e,0,0)}}),b.Image.filters.Sepia.fromObject=function(){return new b.Image.filters.Sepia}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={});b.Image.filters.Sepia2=b.util.createClass(b.Image.filters.BaseFilter,{type:"Sepia2",applyTo:function(a){var b,c,d,e,f=a.getContext("2d"),g=f.getImageData(0,0,a.width,a.height),h=g.data,i=h.length;for(b=0;i>b;b+=4)c=h[b],d=h[b+1],e=h[b+2],h[b]=(.393*c+.769*d+.189*e)/1.351,h[b+1]=(.349*c+.686*d+.168*e)/1.203,h[b+2]=(.272*c+.534*d+.131*e)/2.14;f.putImageData(g,0,0)}}),b.Image.filters.Sepia2.fromObject=function(){return new b.Image.filters.Sepia2}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Tint=b.util.createClass(b.Image.filters.BaseFilter,{type:"Tint",initialize:function(a){a=a||{},this.color=a.color||"#000000",this.opacity="undefined"!=typeof a.opacity?a.opacity:new b.Color(this.color).getAlpha()},applyTo:function(a){var c,d,e,f,g,h,i,j,k,l=a.getContext("2d"),m=l.getImageData(0,0,a.width,a.height),n=m.data,o=n.length;for(k=new b.Color(this.color).getSource(),d=k[0]*this.opacity,e=k[1]*this.opacity,f=k[2]*this.opacity,j=1-this.opacity,c=0;o>c;c+=4)g=n[c],h=n[c+1],i=n[c+2],n[c]=d+g*j,n[c+1]=e+h*j,n[c+2]=f+i*j;l.putImageData(m,0,0)},toObject:function(){return c(this.callSuper("toObject"),{color:this.color,opacity:this.opacity})}}),b.Image.filters.Tint.fromObject=function(a){return new b.Image.filters.Tint(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend;b.Image.filters.Multiply=b.util.createClass(b.Image.filters.BaseFilter,{type:"Multiply",initialize:function(a){a=a||{},this.color=a.color||"#000000"},applyTo:function(a){var c,d,e=a.getContext("2d"),f=e.getImageData(0,0,a.width,a.height),g=f.data,h=g.length;for(d=new b.Color(this.color).getSource(),c=0;h>c;c+=4)g[c]*=d[0]/255,g[c+1]*=d[1]/255,g[c+2]*=d[2]/255;e.putImageData(f,0,0)},toObject:function(){return c(this.callSuper("toObject"),{color:this.color})}}),b.Image.filters.Multiply.fromObject=function(a){return new b.Image.filters.Multiply(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric;b.Image.filters.Blend=b.util.createClass({type:"Blend",initialize:function(a){a=a||{},this.color=a.color||"#000",this.image=a.image||!1,this.mode=a.mode||"multiply",this.alpha=a.alpha||1},applyTo:function(a){var c,d,e,f,g,h,i,j=a.getContext("2d"),k=j.getImageData(0,0,a.width,a.height),l=k.data,m=!1;if(this.image){m=!0;var n=b.util.createCanvasElement();n.width=this.image.width,n.height=this.image.height;var o=new b.StaticCanvas(n);o.add(this.image);var p=o.getContext("2d");i=p.getImageData(0,0,o.width,o.height).data}else i=new b.Color(this.color).getSource(),c=i[0]*this.alpha,d=i[1]*this.alpha,e=i[2]*this.alpha;for(var q=0,r=l.length;r>q;q+=4)switch(f=l[q],g=l[q+1],h=l[q+2],m&&(c=i[q]*this.alpha,d=i[q+1]*this.alpha,e=i[q+2]*this.alpha),this.mode){case"multiply":l[q]=f*c/255,l[q+1]=g*d/255,l[q+2]=h*e/255;break;case"screen":l[q]=1-(1-f)*(1-c),l[q+1]=1-(1-g)*(1-d),l[q+2]=1-(1-h)*(1-e);break;case"add":l[q]=Math.min(255,f+c),l[q+1]=Math.min(255,g+d),l[q+2]=Math.min(255,h+e);break;case"diff":case"difference":l[q]=Math.abs(f-c),l[q+1]=Math.abs(g-d),l[q+2]=Math.abs(h-e);break;case"subtract":var s=f-c,t=g-d,u=h-e;l[q]=0>s?0:s,l[q+1]=0>t?0:t,l[q+2]=0>u?0:u;break;case"darken":l[q]=Math.min(f,c),l[q+1]=Math.min(g,d),l[q+2]=Math.min(h,e);break;case"lighten":l[q]=Math.max(f,c),l[q+1]=Math.max(g,d),l[q+2]=Math.max(h,e)}j.putImageData(k,0,0)}}),b.Image.filters.Blend.fromObject=function(a){return new b.Image.filters.Blend(a)}}("undefined"!=typeof exports?exports:this),function(a){"use strict";var b=a.fabric||(a.fabric={}),c=b.util.object.extend,d=b.util.object.clone,e=b.util.toFixed,f=b.StaticCanvas.supports("setLineDash");if(b.Text)return void b.warn("fabric.Text is already defined");var g=b.Object.prototype.stateProperties.concat();g.push("fontFamily","fontWeight","fontSize","text","textDecoration","textAlign","fontStyle","lineHeight","textBackgroundColor","useNative","path"),b.Text=b.util.createClass(b.Object,{_dimensionAffectingProps:{fontSize:!0,fontWeight:!0,fontFamily:!0,textDecoration:!0,fontStyle:!0,lineHeight:!0,stroke:!0,strokeWidth:!0,text:!0},_reNewline:/\r?\n/,type:"text",fontSize:40,fontWeight:"normal",fontFamily:"Times New Roman",textDecoration:"",textAlign:"left",fontStyle:"",lineHeight:1.3,textBackgroundColor:"",path:null,useNative:!0,stateProperties:g,stroke:null,shadow:null,initialize:function(a,b){b=b||{},this.text=a,this.__skipDimension=!0,this.setOptions(b),this.__skipDimension=!1,this._initDimensions()},_initDimensions:function(){if(!this.__skipDimension){var a=b.util.createCanvasElement();this._render(a.getContext("2d"))}},toString:function(){return"#'},_render:function(a){"undefined"==typeof Cufon||this.useNative===!0?this._renderViaNative(a):this._renderViaCufon(a)},_renderViaNative:function(a){var c=this.text.split(this._reNewline);this._setTextStyles(a),this.width=this._getTextWidth(a,c),this.height=this._getTextHeight(a,c),this.clipTo&&b.util.clipContext(this,a),this._renderTextBackground(a,c),this._translateForTextAlign(a),this._renderText(a,c),"left"!==this.textAlign&&"justify"!==this.textAlign&&a.restore(),this._renderTextDecoration(a,c),this.clipTo&&a.restore(),this._setBoundaries(a,c),this._totalLineHeight=0},_renderText:function(a,b){a.save(),this._setShadow(a),this._setupFillRule(a),this._renderTextFill(a,b),this._renderTextStroke(a,b),this._restoreFillRule(a),this._removeShadow(a),a.restore()},_translateForTextAlign:function(a){"left"!==this.textAlign&&"justify"!==this.textAlign&&(a.save(),a.translate("center"===this.textAlign?this.width/2:this.width,0))},_setBoundaries:function(a,b){this._boundaries=[];for(var c=0,d=b.length;d>c;c++){var e=this._getLineWidth(a,b[c]),f=this._getLineLeftOffset(e);this._boundaries.push({height:this.fontSize*this.lineHeight,width:e,left:f})}},_setTextStyles:function(a){this._setFillStyles(a),this._setStrokeStyles(a),a.textBaseline="alphabetic",this.skipTextAlign||(a.textAlign=this.textAlign),a.font=this._getFontDeclaration()},_getTextHeight:function(a,b){return this.fontSize*b.length*this.lineHeight},_getTextWidth:function(a,b){for(var c=a.measureText(b[0]||"|").width,d=1,e=b.length;e>d;d++){var f=a.measureText(b[d]).width;f>c&&(c=f)}return c},_renderChars:function(a,b,c,d,e){b[a](c,d,e)},_renderTextLine:function(a,b,c,d,e,f){if(e-=this.fontSize/4,"justify"!==this.textAlign)return void this._renderChars(a,b,c,d,e,f);var g=b.measureText(c).width,h=this.width;if(h>g)for(var i=c.split(/\s+/),j=b.measureText(c.replace(/\s+/g,"")).width,k=h-j,l=i.length-1,m=k/l,n=0,o=0,p=i.length;p>o;o++)this._renderChars(a,b,i[o],d+n,e,f),n+=b.measureText(i[o]).width+m;else this._renderChars(a,b,c,d,e,f)},_getLeftOffset:function(){return b.isLikelyNode?0:-this.width/2},_getTopOffset:function(){return-this.height/2},_renderTextFill:function(a,b){if(this.fill||this._skipFillStrokeCheck){this._boundaries=[];for(var c=0,d=0,e=b.length;e>d;d++){var f=this._getHeightOfLine(a,d,b);c+=f,this._renderTextLine("fillText",a,b[d],this._getLeftOffset(),this._getTopOffset()+c,d)}}},_renderTextStroke:function(a,b){if(this.stroke&&0!==this.strokeWidth||this._skipFillStrokeCheck){var c=0;a.save(),this.strokeDashArray&&(1&this.strokeDashArray.length&&this.strokeDashArray.push.apply(this.strokeDashArray,this.strokeDashArray),f&&a.setLineDash(this.strokeDashArray)),a.beginPath();for(var d=0,e=b.length;e>d;d++){var g=this._getHeightOfLine(a,d,b);c+=g,this._renderTextLine("strokeText",a,b[d],this._getLeftOffset(),this._getTopOffset()+c,d)}a.closePath(),a.restore()}},_getHeightOfLine:function(){return this.fontSize*this.lineHeight},_renderTextBackground:function(a,b){this._renderTextBoxBackground(a),this._renderTextLinesBackground(a,b)},_renderTextBoxBackground:function(a){this.backgroundColor&&(a.save(),a.fillStyle=this.backgroundColor,a.fillRect(this._getLeftOffset(),this._getTopOffset(),this.width,this.height),a.restore())},_renderTextLinesBackground:function(a,b){if(this.textBackgroundColor){a.save(),a.fillStyle=this.textBackgroundColor;for(var c=0,d=b.length;d>c;c++)if(""!==b[c]){var e=this._getLineWidth(a,b[c]),f=this._getLineLeftOffset(e);a.fillRect(this._getLeftOffset()+f,this._getTopOffset()+c*this.fontSize*this.lineHeight,e,this.fontSize*this.lineHeight)}a.restore()}},_getLineLeftOffset:function(a){return"center"===this.textAlign?(this.width-a)/2:"right"===this.textAlign?this.width-a:0},_getLineWidth:function(a,b){return"justify"===this.textAlign?this.width:a.measureText(b).width},_renderTextDecoration:function(a,b){function c(c){for(var f=0,g=b.length;g>f;f++){var h=e._getLineWidth(a,b[f]),i=e._getLineLeftOffset(h);a.fillRect(e._getLeftOffset()+i,~~(c+f*e._getHeightOfLine(a,f,b)-d),h,1)}}if(this.textDecoration){var d=this._getTextHeight(a,b)/2,e=this;this.textDecoration.indexOf("underline")>-1&&c(this.fontSize*this.lineHeight),this.textDecoration.indexOf("line-through")>-1&&c(this.fontSize*this.lineHeight-this.fontSize/2),this.textDecoration.indexOf("overline")>-1&&c(this.fontSize*this.lineHeight-this.fontSize)}},_getFontDeclaration:function(){return[b.isLikelyNode?this.fontWeight:this.fontStyle,b.isLikelyNode?this.fontStyle:this.fontWeight,this.fontSize+"px",b.isLikelyNode?'"'+this.fontFamily+'"':this.fontFamily].join(" ")},render:function(a,b){if(this.visible){a.save(),this._transform(a,b);var c=this.transformMatrix,d=this.group&&"path-group"===this.group.type;d&&a.translate(-this.group.width/2,-this.group.height/2),c&&a.transform(c[0],c[1],c[2],c[3],c[4],c[5]),d&&a.translate(this.left,this.top),this._render(a),a.restore()}},toObject:function(a){var b=c(this.callSuper("toObject",a),{text:this.text,fontSize:this.fontSize,fontWeight:this.fontWeight,fontFamily:this.fontFamily,fontStyle:this.fontStyle,lineHeight:this.lineHeight,textDecoration:this.textDecoration,textAlign:this.textAlign,path:this.path,textBackgroundColor:this.textBackgroundColor,useNative:this.useNative});return this.includeDefaultValues||this._removeDefaultValues(b),b},toSVG:function(a){var b=[],c=this.text.split(this._reNewline),d=this._getSVGLeftTopOffsets(c),e=this._getSVGTextAndBg(d.lineTop,d.textLeft,c),f=this._getSVGShadows(d.lineTop,c);return d.textTop+=this._fontAscent?this._fontAscent/5*this.lineHeight:0,this._wrapSVGTextAndBg(b,e,f,d),a?a(b.join("")):b.join("") +},_getSVGLeftTopOffsets:function(a){var b=this.useNative?this.fontSize*this.lineHeight:-this._fontAscent-this._fontAscent/5*this.lineHeight,c=-(this.width/2),d=this.useNative?this.fontSize-1:this.height/2-a.length*this.fontSize-this._totalLineHeight;return{textLeft:c+(this.group&&"path-group"===this.group.type?this.left:0),textTop:d+(this.group&&"path-group"===this.group.type?this.top:0),lineTop:b}},_wrapSVGTextAndBg:function(a,b,c,d){a.push('\n',b.textBgRects.join(""),"',c.join(""),b.textSpans.join(""),"\n","\n")},_getSVGShadows:function(a,c){var d,f,g=[],h=1;if(!this.shadow||!this._boundaries)return g;for(d=0,f=c.length;f>d;d++)if(""!==c[d]){var i=this._boundaries&&this._boundaries[d]?this._boundaries[d].left:0;g.push('",b.util.string.escapeXml(c[d]),""),h=1}else h++;return g},_getSVGTextAndBg:function(a,b,c){var d=[],e=[],f=1;this._setSVGBg(e);for(var g=0,h=c.length;h>g;g++)""!==c[g]?(this._setSVGTextLineText(c[g],g,d,a,f,e),f=1):f++,this.textBackgroundColor&&this._boundaries&&this._setSVGTextLineBg(e,g,b,a);return{textSpans:d,textBgRects:e}},_setSVGTextLineText:function(a,c,d,f,g){var h=this._boundaries&&this._boundaries[c]?e(this._boundaries[c].left,2):0;d.push('",b.util.string.escapeXml(a),"")},_setSVGTextLineBg:function(a,b,c,d){a.push("\n')},_setSVGBg:function(a){this.backgroundColor&&this._boundaries&&a.push("')},_getFillAttributes:function(a){var c=a&&"string"==typeof a?new b.Color(a):"";return c&&c.getSource()&&1!==c.getAlpha()?'opacity="'+c.getAlpha()+'" fill="'+c.setAlpha(1).toRgb()+'"':'fill="'+a+'"'},_set:function(a,b){"fontFamily"===a&&this.path&&(this.path=this.path.replace(/(.*?)([^\/]*)(\.font\.js)/,"$1"+b+"$3")),this.callSuper("_set",a,b),a in this._dimensionAffectingProps&&(this._initDimensions(),this.setCoords())},complexity:function(){return 1}}),b.Text.ATTRIBUTE_NAMES=b.SHARED_ATTRIBUTES.concat("x y dx dy font-family font-style font-weight font-size text-decoration text-anchor".split(" ")),b.Text.DEFAULT_SVG_FONT_SIZE=16,b.Text.fromElement=function(a,c){if(!a)return null;var d=b.parseAttributes(a,b.Text.ATTRIBUTE_NAMES);c=b.util.object.extend(c?b.util.object.clone(c):{},d),"dx"in d&&(c.left+=d.dx),"dy"in d&&(c.top+=d.dy),"fontSize"in c||(c.fontSize=b.Text.DEFAULT_SVG_FONT_SIZE),c.originX||(c.originX="left");var e=new b.Text(a.textContent,c),f=0;return"left"===e.originX&&(f=e.getWidth()/2),"right"===e.originX&&(f=-e.getWidth()/2),e.set({left:e.getLeft()+f,top:e.getTop()-e.getHeight()/2}),e},b.Text.fromObject=function(a){return new b.Text(a.text,d(a))},b.util.createAccessors(b.Text)}("undefined"!=typeof exports?exports:this)}).call({},window,document,html2canvas); \ No newline at end of file diff --git a/hipstamap/images/carousel-sprite.png b/hipstamap/images/carousel-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6a4481e16442e44a3d42c88db80aeb0c0298b708 GIT binary patch literal 220576 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 z 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
    t%ALWM>~T- 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 literal 0 HcmV?d00001 diff --git a/hipstamap/images/settings.png b/hipstamap/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..82962a55a5617129f800655daf33d589350d2548 GIT binary patch literal 1612 zcmeAS@N?(olHy`uVBq!ia0vp^CLqkg1SGwrdiDS*$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y2=9ZF3nBND}m`vf$KFlz^NCM6mkoIHoK%2WtOF;xE1B+DuBIgm5JLeE;!AD z>P^Av7Bg3zdi8;h(Fa8=QbfaqfT;(>geO}d2cGa#^MI+n2$-yMOjCe)jOl=9ekQ^0vKy z|HC`2r|0n~9nZb>O=#xt7azYf7Tjrezu%O6jzL*4vf$B|-wuT)slKw?C%Edx{hKnU zR?uToimdX-wL%Lnp4)d^@uIlA>gx5U)eoHK@`*cOadg$a`_W2vUTiDaUPvamFq<;n ztTvBkG7Y*}ctUS63&Y*=XA{;o$%vaSE`9W+ddhAdj)pdo(744#vreB6Srqn?wI^H4 znr}wHCnFKf^2nGaJO;W!_S=Q>HRKZVm3{UkiA|csEf-m5CsrF*$vjs3%6i?7S6Wdwxyx_v(+xY2#BX}? z^u4`Ldal+UaN4-?vsmdV%LGYo9)kqMc5T1L#+GMBPp&a+usW#4s`I>|^Za>lnR+X= z2Nns!%=K2xZc1{jr>d&uAFWGoeEI$?b9q$gp9wMz>?aZ+5%6aF;PVLK%2bOBH zL`jNlo6BL5ch7`5;i!WC=}zsq4QYuL$6T_#6cLH;;oSIt1Vs)y=DoSO{RhO zcfM$e+`@D>H>q4=_2XBkuD;T`vLgDUHq+lO(+?qEzArR=*$@+>_xbZAg-fYRLgNoy zJF;+b{>EMWH?A;?#eOPUaHIam3+r_^R91RA&3eqS?ZL+#ozF_O*FCdXIoIQrHD89t zx~};u3~PRLzJDh0+9IpeRZ+K2%X;1VSnm2p9~A~Er<(5hTh=X{eBRK*St!ED`PP;L z0(ZCYRvR&i*-qHQ>9b~n3S(w`;Qnlma(RQGhB)_a3^(eJO-HJmG`zO-+^K zg)U~j4O@jbY_yCx=5=9~TgXeUz_W!Xp4u5CPP}q-QRNmspK4%|T-+yJdS}IBH(7UY c27v^I^G=TMZl3Ge2P)q^UHx3vIVCg!02l>b5C8xG literal 0 HcmV?d00001 diff --git a/hipstamap/index.html b/hipstamap/index.html new file mode 100644 index 0000000..95d1045 --- /dev/null +++ b/hipstamap/index.html @@ -0,0 +1,123 @@ + + + + + Esri UK Hipstamap + + + + + + + + + + + + + + + + + + +
    + +
    + +

    Style Generator

    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    + + Reset + +
    + +

    CSS output

    [id*="_container"] {
    -webkit-filter: grayscale(0) sepia(0) saturate(1) hue-rotate(0deg) invert(0) opacity(1) brightness(1) contrast(1);
    }
    + +
    + +
    + + + + + +
      +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    • +
    + +
    + + + + + + + diff --git a/hipstamap/js/common.js b/hipstamap/js/common.js new file mode 100644 index 0000000..1746c64 --- /dev/null +++ b/hipstamap/js/common.js @@ -0,0 +1,107 @@ +$(function() { + + var div = $(".slider"); + //Setup each of the sliders with properties and functions + div.each(function () { + $(this).slider({ + value: 0, + min: $(this).data("minval"), + max: $(this).data("maxval"), + step: $(this).data("stepval"), + value: $(this).data("editvalue"), + + //When the user moves the slider (during adjustment): + slide: function (event, ui) { + $(this).data("editvalue", ui.value); + $(this).parent().find('label > span').html(ui.value); + var ss = ''; + //Check to see whether the slider is the hue rotate or not as this has a different format + $('.slider-holder').each(function(){ + if($(this).attr('id') === 'hue-rotate') { + ss += " " + $(this).attr('id') + "(" + $(this).find('.slider').data('editvalue')+"deg)"; + } else { + ss += " " + $(this).attr('id') + "(" + $(this).find('.slider').data('editvalue')+")"; + } + }); + //console.log(ss); + $('[id*="_container"]').css("-webkit-filter",ss); + // Make the relevant CSS available to the user + $('#output').html('

    CSS output

    [id*="_container"] {
    -webkit-filter: \n'+ ss +';
    }
    '); + }, + + //When the user lets go of the slider (final position): + stop: function (event, ui) { + $(this).data("editvalue", ui.value); + var ss = ''; + $('.slider-holder').each(function(){ + if($(this).attr('id') === 'hue-rotate') { + ss += " " + $(this).attr('id') + "(" + $(this).find('.slider').data('editvalue')+"deg)"; + } else { + ss += " " + $(this).attr('id') + "(" + $(this).find('.slider').data('editvalue')+")"; + } + }); + //console.log(ss); + $('[id*="_container"]').css("-webkit-filter",ss); + $('#output').html('

    CSS output

    [id*="_container"] {
    -webkit-filter: \n'+ ss +';
    }
    '); + } + }); + }); + + // Custom filter menu + $('#adv-menu').sidr({ + side: 'right', + onOpen: function() { + $('#switcher-list').fadeOut(); + $('body').removeClass(); + }, + onClose: function() { + $('#switcher-list').fadeIn(); + reset(); + } + }); + + $('#switcher-list a').click(function(e){ + e.preventDefault(); + var title = $(this).attr("title"); + $('#switcher-list a').removeClass("active"); + $(this).addClass("active"); + $('body').removeClass().addClass(title); + }); + + $('#reset').click(function(e){ + e.preventDefault(); + reset(); + }); + + // reset all of the UI and data values to their defaults + function reset() { + $('#map_container').css("-webkit-filter", ""); + $("#grayscale .slider").slider('value',0); + $("#grayscale .slider").data('editvalue',0); + $("#grayscale label span").html("0"); + $("#sepia .slider").slider('value',0); + $("#sepia .slider").data('editvalue',0); + $("#sepia label span").html("0"); + $("#saturate .slider").slider('value',1); + $("#saturate .slider").data('editvalue',1); + $("#saturate label span").html("1"); + $("#hue-rotate .slider").slider('value',0); + $("#hue-rotate .slider").data('editvalue',0); + $("#hue-rotate label span").html("0"); + $("#invert .slider").slider('value',0); + $("#invert .slider").data('editvalue',0); + $("#invert label span").html("0"); + $("#opacity .slider").slider('value',1); + $("#opacity .slider").data('editvalue',1); + $("#brightness label span").html("1"); + $("#brightness .slider").slider('value',1); + $("#brightness .slider").data('editvalue',1); + $("#brightness label span").html("1"); + $("#contrast .slider").slider('value',1); + $("#contrast .slider").data('editvalue',1); + $("#contrast label span").html("1"); + + $('#output').html('

    CSS output

    [id*="_container"] {
    -webkit-filter: grayscale(0) sepia(0) saturate(1) hue-rotate(0deg) invert(0) opacity(1) brightness(1) contrast(1);
    }
    '); + } + +}); \ No newline at end of file diff --git a/hipstamap/js/jquery.sidr.min.js b/hipstamap/js/jquery.sidr.min.js new file mode 100644 index 0000000..c0e3deb --- /dev/null +++ b/hipstamap/js/jquery.sidr.min.js @@ -0,0 +1,4 @@ +/*! Sidr - v1.2.1 - 2013-11-06 + * https://github.com/artberri/sidr + * Copyright (c) 2013 Alberto Varela; Licensed MIT */ +(function(e){var t=!1,i=!1,n={isUrl:function(e){var t=RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$","i");return t.test(e)?!0:!1},loadContent:function(e,t){e.html(t)},addPrefix:function(e){var t=e.attr("id"),i=e.attr("class");"string"==typeof t&&""!==t&&e.attr("id",t.replace(/([A-Za-z0-9_.\-]+)/g,"sidr-id-$1")),"string"==typeof i&&""!==i&&"sidr-inner"!==i&&e.attr("class",i.replace(/([A-Za-z0-9_.\-]+)/g,"sidr-class-$1")),e.removeAttr("style")},execute:function(n,s,a){"function"==typeof s?(a=s,s="sidr"):s||(s="sidr");var r,d,l,c=e("#"+s),u=e(c.data("body")),f=e("html"),p=c.outerWidth(!0),g=c.data("speed"),h=c.data("side"),m=c.data("displace"),v=c.data("onOpen"),y=c.data("onClose"),x="sidr"===s?"sidr-open":"sidr-open "+s+"-open";if("open"===n||"toggle"===n&&!c.is(":visible")){if(c.is(":visible")||t)return;if(i!==!1)return o.close(i,function(){o.open(s)}),void 0;t=!0,"left"===h?(r={left:p+"px"},d={left:"0px"}):(r={right:p+"px"},d={right:"0px"}),u.is("body")&&(l=f.scrollTop(),f.css("overflow-x","hidden").scrollTop(l)),m?u.addClass("sidr-animating").css({width:u.width(),position:"absolute"}).animate(r,g,function(){e(this).addClass(x)}):setTimeout(function(){e(this).addClass(x)},g),c.css("display","block").animate(d,g,function(){t=!1,i=s,"function"==typeof a&&a(s),u.removeClass("sidr-animating")}),v()}else{if(!c.is(":visible")||t)return;t=!0,"left"===h?(r={left:0},d={left:"-"+p+"px"}):(r={right:0},d={right:"-"+p+"px"}),u.is("body")&&(l=f.scrollTop(),f.removeAttr("style").scrollTop(l)),u.addClass("sidr-animating").animate(r,g).removeClass(x),c.animate(d,g,function(){c.removeAttr("style").hide(),u.removeAttr("style"),e("html").removeAttr("style"),t=!1,i=!1,"function"==typeof a&&a(s),u.removeClass("sidr-animating")}),y()}}},o={open:function(e,t){n.execute("open",e,t)},close:function(e,t){n.execute("close",e,t)},toggle:function(e,t){n.execute("toggle",e,t)},toogle:function(e,t){n.execute("toggle",e,t)}};e.sidr=function(t){return o[t]?o[t].apply(this,Array.prototype.slice.call(arguments,1)):"function"!=typeof t&&"string"!=typeof t&&t?(e.error("Method "+t+" does not exist on jQuery.sidr"),void 0):o.toggle.apply(this,arguments)},e.fn.sidr=function(t){var i=e.extend({name:"sidr",speed:200,side:"left",source:null,renaming:!0,body:"body",displace:!0,onOpen:function(){},onClose:function(){}},t),s=i.name,a=e("#"+s);if(0===a.length&&(a=e("
    ").attr("id",s).appendTo(e("body"))),a.addClass("sidr").addClass(i.side).data({speed:i.speed,side:i.side,body:i.body,displace:i.displace,onOpen:i.onOpen,onClose:i.onClose}),"function"==typeof i.source){var r=i.source(s);n.loadContent(a,r)}else if("string"==typeof i.source&&n.isUrl(i.source))e.get(i.source,function(e){n.loadContent(a,e)});else if("string"==typeof i.source){var d="",l=i.source.split(",");if(e.each(l,function(t,i){d+='
    '+e(i).html()+"
    "}),i.renaming){var c=e("
    ").html(d);c.find("*").each(function(t,i){var o=e(i);n.addPrefix(o)}),d=c.html()}n.loadContent(a,d)}else null!==i.source&&e.error("Invalid Sidr Source");return this.each(function(){var t=e(this),i=t.data("sidr");i||(t.data("sidr",s),"ontouchstart"in document.documentElement?(t.bind("touchstart",function(e){e.originalEvent.touches[0],this.touched=e.timeStamp}),t.bind("touchend",function(e){var t=Math.abs(e.timeStamp-this.touched);200>t&&(e.preventDefault(),o.toggle(s))})):t.click(function(e){e.preventDefault(),o.toggle(s)}))})}})(jQuery); \ No newline at end of file From f5c6e5103bac0ee4fb79e93f0077f9eabd8ab789 Mon Sep 17 00:00:00 2001 From: Richard Mumford Date: Fri, 22 Jun 2018 14:42:50 +0100 Subject: [PATCH 3/8] Updated hipstamap demo --- hipstamap/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hipstamap/index.html b/hipstamap/index.html index 95d1045..7367468 100644 --- a/hipstamap/index.html +++ b/hipstamap/index.html @@ -4,15 +4,15 @@ Esri UK Hipstamap - + - + - + + + + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/directly/js/main.js b/directly/js/main.js new file mode 100644 index 0000000..5123041 --- /dev/null +++ b/directly/js/main.js @@ -0,0 +1,168 @@ + require([ + "esri/Map", + "esri/views/MapView", + "esri/layers/FeatureLayer", + "esri/core/watchUtils", + "esri/geometry/support/webMercatorUtils", + "esri/tasks/support/Query", + "esri/Graphic", + "dojo/domReady!" + ], function(Map, MapView, FeatureLayer, watchUtils, webMercatorUtils, Query, Graphic ) { + + var map = new Map({ + basemap: "streets-navigation-vector" + }); + + var view = new MapView({ + container: "viewDiv", + map: map, + zoom: 10, + center: [-5,55], + constraints: { + rotationEnabled: false, //Rotation too complex for prototype... + minZoom: 10, + maxZoom: 10 + } + }); + + view.ui.remove("zoom"); + + + //Global vars + var feats; + var thePoints; + var board = document.getElementById('contentContainer'); + var w = window; + var d = document; + var e = d.documentElement; + var g = d.getElementsByTagName('body')[0]; + var request = new XMLHttpRequest(); + var x = w.innerWidth || e.clientWidth || g.clientWidth; + var y = w.innerHeight|| e.clientHeight|| g.clientHeight; + + function addFeatureService(){ + var portsRenderer = { + type: "simple", // autocasts as new SimpleRenderer() + symbol: { + type: "picture-marker", // autocasts as new PictureMarkerSymbol() + url: "images/portIcon.png", + width: "64px", + height: "64px", + yOffset: "32px" + } + }; + + var ukPorts = new FeatureLayer({ + url: "https://services.arcgis.com/EKJFXol2kjsIRba0/arcgis/rest/services/UK_Ports_Locations/FeatureServer/0", + renderer: portsRenderer + }); + + map.add(ukPorts); + queryFeatureService(ukPorts) + + }; + + function queryFeatureService(featureService){ + var query = featureService.createQuery(); + featureService.queryFeatures(query) + .then(function(response){ + feats = response + for(var i = 0; i < feats.features.length; i++){ + var Outerpoint = document.createElement("div"); + Outerpoint.className = 'point'; + board.appendChild(Outerpoint); + } + + thePoints = document.querySelectorAll(".point"); + calcDegree(); // once loaded, re-arrange points. + }); + } + + view.when(function() { + addFeatureService(); + watchUtils.when(view, "extent", calcDegree); + }); + + function calcDistance(i,lat1,lon1,lat2,lon2){ + var distance = Math.sqrt(Math.pow((lon2-lon1),2) + Math.pow((lat2-lat1),2)) + + //if it's visible - value hard coded for now. Next step would be to calculate it with scale. + if(distance<50000){ + document.getElementsByClassName('point')[i].style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + } else{ + if(distance<100000 && distance>=50000){ + document.getElementsByClassName('point')[i].classList.add('close'); + }else{ + document.getElementsByClassName('point')[i].classList.remove('close'); + } + document.getElementsByClassName('point')[i].style.backgroundColor = "rgba(40, 121, 242, 0.5)"; + + } + return distance + } + + // This function works out the degree/angle the point is from the center of the view. + function calcDegree() { + for (var i = 0; i < feats.features.length; i++) { + + var thePoint = thePoints[i]; + + var pointA = { + x: feats.features[i].geometry.x, + y: feats.features[i].geometry.y + }; + + var pointB = { + x: view.center.x, + y: view.center.y + }; + + //This changes the scale of the points + var sizer = calcDistance(i,feats.features[i].geometry.y,feats.features[i].geometry.x,view.center.y,view.center.x); + + var angleDeg = (Math.atan2(pointB.x - pointA.x, pointB.y - pointA.y) * 180 / Math.PI) + 180; + var circleOffset = 25; // Padding in pixels + var borderOffset = 40; + var hypot = Math.sqrt(Math.pow(y, 2) + Math.pow(x, 2)); + var sinOfAngleA = Math.asin(y / hypot) * 180 / Math.PI; + var sinOfAngleB = Math.asin(x / hypot) * 180 / Math.PI; + + var pixels = "px"; + var distanceFromTop + var distanceFromLeft + + //TOPRIGHT + if(0 < angleDeg && angleDeg < sinOfAngleB) { + distanceFromTop = 0 + pixels; + distanceFromLeft = ((angleDeg - 0) / (sinOfAngleB - 0) * (x / 2)) + (x/2) - borderOffset + pixels; + } + + //RIGHT + else if(angleDeg > sinOfAngleB && angleDeg < sinOfAngleB + (2 * sinOfAngleA)) { + distanceFromLeft = x -borderOffset + pixels; + distanceFromTop = ((angleDeg - sinOfAngleB) / ((sinOfAngleB + (2 * sinOfAngleA)) - sinOfAngleB) * y) + pixels; + } + + //BOTTOM + else if(angleDeg > sinOfAngleB + (2 * sinOfAngleA) && angleDeg < (3 * sinOfAngleB) + (2 * sinOfAngleA)) { + distanceFromTop = y - borderOffset + pixels; + distanceFromLeft = x -(angleDeg - (sinOfAngleB + (2 * sinOfAngleA))) / (((3 * sinOfAngleB) + (2 * sinOfAngleA)) - (sinOfAngleB + (2 * sinOfAngleA))) * x + pixels; + } + + //LEFT + else if(angleDeg > (3 * sinOfAngleB) + (2 * sinOfAngleA) && angleDeg < (3 * sinOfAngleB) + (4 * sinOfAngleA)) { + distanceFromLeft = 0 + pixels; + distanceFromTop = y - (angleDeg - ((3 * sinOfAngleB) + (2 * sinOfAngleA))) / (((3 * sinOfAngleB) + (4 * sinOfAngleA)) - ((3 * sinOfAngleB) + (2 * sinOfAngleA))) * y - circleOffset + pixels; + } + + //TOP LEFT + else if(angleDeg > (3 * sinOfAngleB) + (4 * sinOfAngleA) && angleDeg < 360) { + distanceFromTop = 0 + pixels; + distanceFromLeft = (angleDeg - ((3 * sinOfAngleB) + (4 * sinOfAngleA))) / (360 - ((3 * sinOfAngleB) + (4 * sinOfAngleA))) * (x / 2) + pixels; + } + + thePoint.style.top = distanceFromTop; + thePoint.style.left = distanceFromLeft; + } + } + }); \ No newline at end of file diff --git a/img/placeholder_directly_350x150.png b/img/placeholder_directly_350x150.png new file mode 100644 index 0000000000000000000000000000000000000000..4d551e33953c1bda0aaefe3374432a6a810d0442 GIT binary patch literal 30794 zcmb5V1z1$=)-b#o1{g|aq(dBrlRR$>TUJ{3+CHyLX?dxZcGYs~;vEz1B$OCc+Sgg9K(PZ&kO z+1kq-?&s{};wkJWhWLxFFzWeFF$V(v7l@am7((h#gK#|+HMoqchc%p!ouAE;iK+65IE!3MB!p_Uf zO_+nj*VmWbmxtZe!?`F-RVeRSa?O|z+0;l~~Q=VS3J}4IdDeV6OS=Pte#p`c$E6cxe-Ml@V z{!){bC5N?>wKEFL6D1Yb-+a2+xq7*J+PVH0CH!6ef2hLC-sZo^_NV6m0JZkB|1Y3_ zYW@aw6PEF?HurM%&~kNk`j_vh{fjrajLaWj;bZ56GwIs9Sh@OoKKi4pe_~l9&AqI} z5P!UhjT7Zc0$N3`X$_WSx za&gJ>3rTayNOK~2WOz8acsK=R5pXL@VH;NuXLFS2**lxtT64I$*dpNn=0wKT$<+g; zN|dU3{9e-fUv+aD`gd)WI}TgiF2I>Y}8Y+?KV z!S4Umu+)Fo@IP=-7XI7j|Ce>7|0^!X-^?i3d!pjR|DX9$)7t%?PfqqI-cWf%*xd3T z8uc_s@r?Lq;s3<2kdU>2kN_tyo3%MFDmh#53bG0E*$A<53-DM9{xKkrCGWrReeJAK zmixa#j7ON89~EN%Xx7u!#>>~-!&=G~W$FKA2>%1q($3t))*99Ka3KDO`6o&L{}<+e z7j^$=@NZ%MTPFD5O7tJu=zkQ2Ys*X>;3u z>*3#Uf5E7lJEPp%%HG4;(#y&JKjZMP>OZvpqxz3N|6<}FBsu;~?Eh%=FOdJ}DF20p z>V5w_{@v}PUj809Si7Js=7Aa(d}t4$0RYz|MWmFL-|PJw`)9fOuNa=@c>ZiZICHM$ ziV(@PN>$wKm>i!Jo0ypR;leh0A{jX*X_<_PDO%$s6Xp!*gMjeRZ#N_;6n_!i)Kpqy z*3mdtmbpB$R{rXx>#MD+tE2r>*~Vj;mh7sjW6*fyFj)XWMi*)I9FJZ90m1#u6w{!? zcjvi*KZU;f-@6?QJ>h;Fl2m z`u)fZ3!u2LSTbomT=fzp*M`971xaY0LUk^QOPRXsW)+Ud^`8{sVLjX(Rj`zNj`c7B za@8|{nIkK%VgY@mk0zCG1?*cd_q0rId$%a;bKMlCu-3W7NvU zJ%TTu2ctcBq(LBZ&}U5B6_hrlyIr8tCfWb{!0QzhIdzZ(6luJ}Wt4$+p7mt7NSnqr-wA&*CK=L4fp^ z$d=blyhltKH}2xpvx=HrVG!{kR@yM>ZU6e6f^dNk3LDdSANxr8@7%ub*TVDZh{!kz zn2Se8siDxKGIAh3Pu_gj+;&Pr2SPkC-BWumJV;@UTgcGG%A^c_6(QZCIm)KhIl-k4L*hJV<3i5qO0mS-{n!z8}9wUI4!T&Qz=5SoEiEZUoe z9_lY5K0nCNm#t2OjRhmwSSgE2PG9MAi;LbJ`w(3 zGheNKU~V-o?yr+7mwwwOSC;MtTWMuXl`G3&->Rat*1@0y7c2RI43(QjX2@(BjR_+B z)yiyFu~4}E>NHb8Z}B=Vy+7p(0LAk;S3@4ls})~gf^MqhmoK)AMBXk zbAxnSJs(YmQ~OFOq)0PJ_lJS9td2oJ5(j`Tm7?C`+PbHXBp;SM3AG@48ft-7|)Hn$XS* zf}O{B5rpcKzGFdYN%Gp#tGUGlu&c-gMQ}!{R=m6-Rhb?d6Cf#xh8i}TR3Ka@Sg%tjB>TAS~ zlxLn#(o+-P;BVez!`!lA0dmMsSTC+6Q%jhCUr~`D$8kVAQYMXKvVFf2JgJeN=G9!Q zpFh`^h7=j7Ez%8CbXi#(FrzzF5o2@cm6bc0+k--j2cLI&#=?|s4ZJthHd41?(9>ZqYkoi3R&41aB7cKvVeq~c3(|5~#z64DI zTPT{BG;66EFE=nXD7$?aWBMcXAkYPiddm`!62;s6iN|4XxkaRT3L-0pMsu->EIAlv#HF4#`;T-A_)ZTqP>Q#`1+zA3%h zs5Q4}xKX7r7KoK>5?RP-Io;{{NPu%SN9js6JVW$lnA@3DoF=r&Zd0oF6OgBNA4kWo zR}NBB>&GKmq9~B=ZwaaFB;{nvB13~jsR4QSY_%$x85pAgDF+_3o;;Sf8O^sg!O!3O zh`I6`4t<$wkJO+sV#`wf_C@YVz7s!vW8u?S#iD5vA{a1gF!=;JMXP(>uBG-_=i4xM zE4M=u$lj>cnj|BBFHSCWXr{}r)424F7e)51TWO)3MD<)IPoGo!@Vq`ma7zlnj*BG% zh51S$CE;OV)xH~aFBQy+XR$`I_RAY+kTQm{iWKGDbeA-`SCV%)VUbbLC=5Wn^~3w* z=I6b+M*u?aV;)lb;PhR# zf+YlGW`L|3DY^s+A+sDDMa8qWt4HXM@ii<5>`_zRz0YSqtcDbq|2;&bOj#2zM%J zT&~w-aB)50dZ0=meZ>iJ{X|ObZwA;oN}N!DHHUlJ$|GRd!^l*@5LRC;aDzr%{ztdw zw(+Y~g3#Z?0?W@56Bj#zujC~z$MR)jZof!eF!rqeI@_NQ{e=}ey6ttlLU6lsl*aNx zWr8}J%uEh~fCCif!sWt!jb5L&U}ZQM;E*JB#WL-ANp`c1FWBRh(uQ2`6$M?jJGnE7w3`U0H&bq6XfBh zthwew<`tyH_@N&^)HJls9S}S%9CH(-xF_GE&SmgHh>S$c+iImCoDZE6pPBq#fPrIu zJCh4OnidnWotG^6vshll$oTq~_hm{fM7gc)UKdB&2n51$|8#dc)8E2kZLZO=L^X#w zeyuMNHEvF*vXS9qkhZ*B&v?VBZFu$NbMEI$`SG!iqM0}AJnrGZAiqzWPb9!jR$`Mc0z+o93z^hZ8=Q|Gm! zP-tyi@b=1**v;dI$xVj!^~bgyf}W>yDq{X8Q^mOY77K2Z-dmT2YlUy_EL_Zs7s*9J zSJyrbg~L9XhI+GatxtUIsH~~w?Bs+fauAq?T#taH$+9cwuYN$Q|LpMbqckx}p687nOjSu57*f0PM)?4f3f$3lA*d6cN~+y^Y5MxH zqb^|-@w;9!KoOWe>y3?)j=eGC{Xm@$9xKQOQA}SGpcybe`i7=V0+o_2nA`JQ1NEp` zsxbt|s+Xzfcx2#)rvqbG~rj|dBcj^#<{=|5-- z*_p9?4RB4?j1oX_un9NnS%cSJ`n4a?f)L6oD#KsD-Ys@s`MNGN=fB)Jzfva}9{F?K z_PJj7ot&I(45pL2-b^{hoE0&gpNfY%OL-}$A5B>hCr8`^@hG0^R5(7b$U9>cs^oaw z={+9mrCrs}4u0PxI+Y6tF+n>BEjx}47f#SYZT?yblGXDMva^1&bI@FwR8Nz?`BBZT z(_<3KNk++P+Ua(7awVac+q!vV&b4#;m0iy;QRS!5;p+Kt8jEKC64Q&ai|AyY`R#a$ zGx-+lz?-F&!Q`xc)j5adX1B9Z7RH%Z`4k)7k1N_oKILwIGG^0Z+4|lv=ESd~+uClz z@m1dSwRoHY!R)I91tR+|3Gi++rD`R|##CM%14kg}5{QZgljiYbA*5zLbg?qTgC3U> zhQ%VYh_*lRQVjUp%9yod1-TAY^6u}$av0Vd7VY@5Z_@66biJ44bg$oOZ#2WY{)I+p zab@{Hp{n7!u}zRm%2av~2=>ky1WM%+_QRFzfvOkr6>!q$af1O&gx-9hlL-&h{W!~& zB8}&%yqt#*-rEa>_)#8ie7ISah}Vx_w>MY?Oy$*W)QwA6RoZ6n1hj@0_I;|2uAl0^ z|IkB++%;0aAiA$*AT#SE`oUPQ)nrqy{-Tf4^^^Pch{yrZRi;_86mqpMaor;k^gU6) z#@4o^qy!Bf<+e9dTdS(+zD*a;u?ge0i%u=8xt*atLUQTQyj7czhU;N&L_?ziC>;b5A$|Bv{(RdfQjZKe zF4wxeOA`5R%Hi8Vz;2f2Rkwicj$qKQjqWN(KoY5*3QDzn6dnszAgu(`$;_)39z!S= z*7f8GU*GKPtrEJz0bCBYk@>oy{Tza%&VaSEs@;m?riKDK#lAja_tc=%$?1^~AI<#s z`-TatscNRg_t;{ej~7ZwNPo?fls7ragMhW2IFs;Uab2{|QI&zo=Qj8lQ5e`q>e`rU z3~~?9^X#oA-6U}uU#*~Hv7j;O!SCOriVBxoaQfhnKaw|1Hh|zrjH~leUoU2<*PG5} z*c2A*0-*NB{dR5m-Mf!P%vRL2&UT$(#8iCO@6fOg?%Vg~{ z`Q?Vor->g!ayg*;=98kZcztK5S8L;zS!ujO!vNl}4kNc5OL1Du$=Kb&r$fst z2E*_8#=Mjh@~WqpA22*uPOhdqev_wW2?eos*92X?RRa%ilf6nf+DxmME)*=Qyh}8_ z3#pzqHTg*`5wMctDwTrsqn=Jqddp}-36o8j9YiE!tI*IlLq>;`B$@R)ZN7c8;@{&f zP-|jqUb#=gy!D!(z~VuF2NgwChCeTn$c|4IKH?Ju?X-^u=B3giNol@O$kay{p{8G5 zRb{j!;ErBs0~1Aj41l!sjq1x(#s)z^U3jdZup(Z1{hDa0Wo1jr>y^Aan~>mgLEQ$^!~N@6Nu^U#33O6Zjgn+@JYAZZFk>Kme;=wJJp$kpGpB2+e{^1?>NM z(@XTl-EpV7*ip0?JJDF;Z1kIulkI^49G*MWxb5~y$kB`M{mIKM-n+jKzuSJzy~$a= z$dR}#lrYfO$EHzMR_-_-*?!SaAaPU^B}hj6Dp7;tW^_k~YWw{=>V*0>dcF0js(I@a zjt^e%hrnhG><$L*LmzOXV&hVT8RzT;3^lKwQU#0s&@wts@)KrvC7PuiUi!$p{CS*4 zf%=3qqum|$GYF#dhoR0dJkcg`j|i$YBu#wEED5xq<*c% z_xWgbhWh|LO&9`%cQ5WW6P=`PKKC;)4V{uIe?XQq2VMR9SprO{`#3b<=+Z_Ukklv+ zd4xjwO=6f+pw4;zHU{E7o3n27g^i^UXe$Exh>kZN=(-H+^0TFEJmhqMfP@d^bubM1 z3;3ofV1jNNuW`eKVUoqMkrQ-sk?Qec)gLAt?@SUpCq?m`iS7$;<)jbb7Rb?ktxUt& zsL7O#(h_o|k&|%X8;4S_NYUo+^HuQBD__-4%SVbMtIT3@2m}ld&7tet?9jOh|QkF-1ePY zw$HXM3?L+PY|43Fq9%GoHJtGH(x^!K8X7+u3`DHq#PE?u8}6mMbl5!7`D#sY#q1_+GpG^qp~P)Jqv~rrUerR2PpfgrD9fxDFR*t{j`}_%cFP){Z z>p&qR9%?ES@EpoN9wm^_7q9u9A&B#R-Asj=?aI^d`*>0jT~E>(15_VH-MDW=1uPkT zsn4AG`8a;q3QbziJB74cWDJ0YK`yT3yPZfwyv!4aoSw(6q^#x#)?8eje(k&{54}@U zn>h$93{NhaQu7J1V=XLbsYnK`Su;hIY1;Vth8lli}UHw}oAJz!DY^*FX~wrt_0mB20<( z+loR@tbz1CPh<&@AWYlho$-%hH zs+2vFxL1)x)7M&Pya>DNsSU~Wfkh7J1Q@w~8q|eWMaY4pi)77c9aLlQ>dy!7_MUtXr$^9-$x1RRDNJ@ifFjuK z)I1DSN)U4Qa4J+sgvq3oq34?v1IbBVn^{gi@9tjuDK8BgZUj1-7Pga7B+xC~g9bwe zg)Ix2W0;9da?fMYuvb;S?|(~?*cV60xD>F(Swu~})WDd!~OqgsqPADgM)*QjHw3*QLH zef{|ozfwo2^}|m8{W|SjGHIA_bB^A*|`X+LqIqi&oGdybk z_07bD+`PSn7JOQ|_=)y#k#=?YqKh^RU|_ZbqmfPcbZZ%t3gsuZeZIMaN`D+k$l?}4 zw}%9JQ9m+M%Up}4)1=Y9cCd77nSI0g3XSe_5qlN`lJ08FqgxBk`5>Kbf3xDmCsVcG zI@nw(b28*`Hhr0u&6~00EXe6rt1uK$9hbK4#V z0W~JDSv1%#aRs8l^c4AB?&BsEjXQlIuCF^)VrOK}a|(S6EqoSbJD7l*5MV#g}f_p|F7g zVr?>FRG&c{$1$H7Us$U8<0Saom`lH>;pz43DsIdGK1z6Zgo>-yu%46BYS2=P%RJod z`LaXxyXM~pW8avZ_xpxO=FyC*Guj?Per<-ZKQJ=PKPAYeLIw5R{J}EUOG=%mXgtIW zg%0lqx@BbAV>wq!r86mQ zPIXc0*+E$r`k!RDfI3m=(ZzJVr>DVgX8VMGR*3&*#k6XN@RUpJ=geyM%1WPPD&b$- zwzg+K=dZ@(AJV&9JHCn^4J=+^L*w+ec!y-|jT@c7$3Z1;3vwu<9 z8sIxQjt!^z^w}HDHOeUF8iQKdP|_kzolJv_>wY;T%JF;C8ICM84;C5MP_vh33;;Gu zp=O<#?W>z1Xb>d<@Ut3$kX&u_tZKhb65IFp>L3hf)RJv4a>_lSJqz3nN-S`WJ>q7Y z322#!%glSFhKH0<-4VFj4xI1^2=yRl;ESCB?inWX%_Ry21%_u;&J#to^5gj(*}iS$ z1Rs>7e>W|k#xSnm-Ndoq5Ozc$2bV*ZL;Ze-8)Uu^CybcPvU~L2j+5H zOWgC9U6{L0xjywHj@G@E_?Ez7NGNF-q7k#EwC84gbGo&X{{(?{@ zU+-;oOip-7ZOb^86gWop)VqYjJGp89!`QErjN~!XFWJufs=SvWnklD*D?L4{pLp-; zzn+!|e6!RS0s9@O{mwr4t$GyOY58$t*yP#uX{j~!njqkpS3OY1rM*8^h&h)bZHCvj z2wO8==_>Be6nw&D^d-B3Uw97talNg44-Bz1P{86i5+cZR+FUbp6_MG zCwB0OR5ZGe1RfYxZLWW#1E#4iTb3RTjz7Gf^SwLy8ZtmhRDAU_vd~6J z(^V~_Xn#;DOSaC$p#0_02toXP?V=Yt%VmZ#i(5p>Mc5e+75M?wsPC8QJC|c?(QCmU zo@){63HHl(yUW6>cbg&vm39FIgv@47+CA0vEJ)I+NOEWl*W<6dbb8_bVn6uJho9Z6 zl{0wp3QA)Bv))+akSGZz-R zkX@;_krpauJV|Ci-bkz8pgt`tKQX3i+_Nu!d+2|2p%PGpm15_=1;0Cek*tnk`TT40 zT>8=5kZ-(Or;E07?_?%dOGzzf5Z;J9gtq35gWs;e(RtA$ZYEIh!$ySM`0?@ML{@!% z9&4A9n#XWVnN&IVtSX_YS(lam?Fo*1EbFflunpY7CEDzFKk1UIGFFrjw zVunRS|IBf%NM5~)E1hsjzI+VB6M1xxrRPgSp8;LC7ZC@0HKOa2UrF72CgZ}zM}q@l z$-J>)`?+_%ZqOonDV-I9`p6nH0X? zL0ss0!yA|Vg>`9=z_&$?XI^4f?{0*738cS8`5CQYCzaHCKYxi?8w-udi#LLQ{XzbK zg;Z;UOEqg`Uhjhsk3U2ciZ+mEv6fT*Y3%c{_L-vV0(XwXJrifVtUefk?(p)JkEb?; z!Z}Xv$R}M{s_d82^Md{Z@Q)0!%n{r$0vO}fFp^Oqf(I2+E;4~3g>#L{EP z{css+@XrQH{YvLQZ~K&S8cOzs88TTJHd%hU{#zD>28l1*EwQ8S+k=Q;Nwme-88WZP z?ujR!7|5>4{G3Ay*rtbEl6LCp=}~O-rW96Rk9A&ukHk8Y&w8m*IB<7aT;2Hl|70O>*1CmLI0^ToK$5+@0L<@bP&cZ{lxq3N2y6;K$$J z+y=RS2)sHAvb{U3?i30=y`IMJc@%9IluXWc-TUyi3F~aXc&gbu*-vIZHDQh#ItpyT zDRC?v6&hY=Ea@}TjsMDGFdQ?ne*=s+Ce8On1CmtRbo|{z-6Mg!Ad7ujS`ZnI(^VC+ z_mH-s3mT>tk=Gq_0BiDH1YS)Rx6y<8DGM!UpJT``RW{q18lAL%5OmO1hDa)II&&l- zY?i)!-e(}gaNYa((<~Mn5+XVxKd6s5e>33+6u_Cc?b?! zKZYSrQOWK}``M16>+9Sc)1wigt(_KZ!d}u&@xUJ|A04M%J5EOeC2qD&l~taFp0_TO zKf@ti-)}N`(;0jfxa`lty)(X1(Rt^3n@g}7ikct`xvwE<6C`fdNr!D5Jt}ERad*is z2GT0-E)QI_YbR`(f{~n5K8($})+^56RWkU?Q>W;gYp~nKq|?%F*krS-)&%?z^p02#^6V%t&lYeU8Ci1R%bqeYOhYXnoh@Oh z<$hpNa%3^5z9%2~5t@-xuc@gy!4udi41yr#YILeFcgL3h4^$>83|j(Rw$Bpf3Tw}QXR|-799eGYWztdTFncv2@!OFCFqZm$^*VGWtnY5k zlr=cWvSi8&DF83a(9lm0^uJU4eI-(H6qSWZs^VVk(1DhC(gyjwanI ziN)lCHn*>(ixZFcD)G(q==fHGC8aI#;!m(Xe3`Qr|4{@(e`P)SS>vEQt8(=eQ$scPp1!8TGP(ADlxL zB>qiNhpU(DQ)^dcBLhMzjpr?0SI5C{PY&J9)xq!TP3i|_OVmg&h7sEIZrF20-n-!@ z-+&#ZJdczzw72-lOIYHkvknYoON<`PV&C%Pg=^m1y_jVVX&16{5Wv~A86(3~0UAqc z7|YM-WtBS0#KQeBWtFH|wa!x|Y0rbbLcW>QrOd?*6QBvcP4HP8whG_D!vtYX+a=@H zTliR~yybEzMk=t-rh?+#AVWiQ3Q$|;v7xmXUEAe^l6ely0|)iOpP$iMb*OeTpraQa z&jY`0*DMe7`W*o9sUPJaD7_!RiQ{Dv!9goi*nKa?A1u{230#zxyzp`59al#xI?n9A zHNY!NOJm>G(+cwW$fiW8tU<_u-s5pEOr;dSc$xKsk1KB{WM$r7DjTji&cJ1tbEARDPE*-J9mL$0G zuOT#j{pidd!4P_ZWbtCG=0!q~moe)2OKxeHcEBk)l%`B6-ra!& zZYZaUT!1j>rqQd>-zPNuP`cw6zHh8Jv?~bY=IkLwU)wNm5g@JN*76Anf-eOSl|JaIh zhiyDUg*`D#%$52>95h@{#_$RXl06)iwgHTzh0DQ~hmE-jSQ__}Q~isoSi~CMBE>aE67R%cN6WOeBb` zh+Jft~7>Wiq&Z~^d^h#|542ZeQ>j6K(h&3h#xP(<6(&cj&G8M+RS>17-@ww7~l zfBZ&^-@1{tv&?kc^-3mM#ai70T>=801YA#7s&2OYywvTaBA)b>2y*2l(|24Jy-`wcd3f`? zkS?yC^=f5tVsYt*>CKP+KSLiKt8WkQX1|Wp!BP%F&&3LVT{II=&9uTnDPx7Vd3;=! z&h%>$)du8?nhuS@@Q9jq>kZM8ln-1dq?akl2Et298rCK5jAZJ%E7$D@El7zAdqTF* zgVvwRAteQyCbnk|WUDLVw0wE*akcc=Y5kHIeli*@U63e^?ChFv8H}~*@TMDkJaQ$t z?pb38BLHth`KUl!36Rymc~B7)ug>I3o=AL-c-F0mxErap$jSqTh7%@!O!1=2CR%Q4 z#!rdarImOBjg`ZUA|_p)`$lFX0u5hvd*0Yjx@8mWyLy&}%h-7r_*RH3@OH4A8MRN} zGgizYVPgE613r5^=ZmzuT#q5{D|?D-`X+O(eoNqPr@B{>o9do?AJLkvgA=DB41QaaqnDJ!&n3{n;FVT4A-$gPTpYenty}Fulx{ zm5HppgiLM5F|EgamNT3-S@@6%FcoW+EPknT;${A7+WgIle;S!K7k$_c4j#yjF z(Q5{vDWtKyy(#aAshTe910^KG3L1!CaMx<}Fez4f7RN9-nr?ptH6act690n{3bq!MrqI$RKgx*M@M^Iit9g*{WCd1n{-PGO;RR0v)Qk- z08MAAmPz)${3QnJv~N72Fb`R$paa`Xvjs--2NGP#a+-lHf{xS35N=|oftQ`$XN1^i zzvq`JPhS~Y67CJiFfKD=E*`eW%$L>O^kxQQZ91=IsD8E5O5OC35E76LRP0c?pBAh>rrF6O!}|FiZrZy2W_E9JQ_}W z={V|g`#uwX9TsHGNP1;j(x;6o*7Da)%4B>UKeM#O%1&hF=fo(@ma+psR~v^N<9daJ z+XOZUc6Cc_bB(1^%uJ--JP;QeW-Pe~CqU!#?u_ehifDi8UHZClt9?|d$R4#urLT`LudB4&CI5^v$x3scw*{Z{&f}ezTLTvV`Fh}uh%12 z<3Y;6?{x<%FsEAzR)*?qL)LuCq>ANmG-%B{Aw5EmZB@W&wE`6hs4((K#&G4aW z)(xw9=$aLHoDLcZfTsB$q*UhRlL`#e^4r!@rq-PQs?wTrNP6Jx0sHux9Qf zLq~+iV#Px|`uiz+#8Sj~ez5A`V04PiJ{jdo;AaM7hwJM(;v;Cll3L!62|zZGq1`CC zlcfpGv>I1js^*_2`slDK?|YWA9XYZDypMNX-EOMQG7Ocn)b3>^(s7iB%~k9CN;YPEN1H=t#Brcd`=o75i- zy=HGs7{L#l%`9aS8(1{(JB=U=GK1Y)lXjz4d5IdpQOPRku{g#9)lIYOT_P?NkSJ)| zN@}93?zTmmD7`HjtQ4>uCCR;H% zn)T=h*>lbnGNg0D=7@Q0|^@!lS z>JE5i-`k7x5w|7Jk)Ib=>s33}12pGxIMd^7T^ddrniP9pp2~9WhZK?UuAz_USo(u| z=#u-xM=wZt7JVD}9C^+I#uz-cg-^23h{WZZ4{5NLgYf3A6RtvXtH*n`?Vf0{&8++J z|8A4Gl?@fmcdMIq-4!u99o~JcV)y(iB;;Q67SeHD1?^$mIYe@o&%?WOnU6wFMd!=o zU2@dvVtDVAz822+6S;S;t#~i?E?g1(-}O=ZLQr6Hv$m$iVIiXN>Z8?@#wtImniVgt zw?yIx$;-?W!S3pH26c+lnJo|P0ivCbh2om9P< zqAL7l^4n7H?e=kIyS7=09Z^hU$paajJ_PrSppqxzWaY$xAkJrU^z_c9I1nnMOxhWb zZ;HH=Wz{Wfzg?aOwAmYe4Q&=NR$>CLfYY70P3Z^u-(@&lo_Uf{orghj-i8ZU_AR{| zf9q4WrWf5sX8F8?Vhe@ewC;+)kR4*Ch35oK5Tv)xZWY~|d7 zSVgO&r9=hpMLC$X(i@(_9mL;yOG<*uzY`_j%tV+WSKH49P#@oDZ;zI_Tbh1(8dwmI zwh%m?++Y6@Ge^KSxc;-tEB)REYo>c>yxo%sO23Mv-eTZOH;XkB87qtiBG zpad}HI7+Z4Gb$SJ)coc%2oTQu5(|)_0+Tw6nidbTzklX^x`jLUW*Gym)YrB#Xk^Sw z(8URP4X<4wO&EUKTKO73V!$i4(&&Sj(5r6Yd5);(>DoZF68hsB{~}H$eBf=jOx)Kb zVH1{PZkXwtYE5(zPV}cVkJ6Sg2?$`nbVwT{=SR;7-F5QVug$~dK*+^2SX*zf5;Q1BD}D?}c( z6Y0+@mJ<+mwC3<_KJS6>#3>i9o=&jq>jiUbhbYm~CoP18DT|b(-``RQ_~ge6@DphY zCy)8hTIATmG}KGwD)F5H8A;=q(2_h5@g*2+A!hEvft_ zI;znXa%2M(8DKDrDnRBN4H;fQAM!o(iT*kGXbdDY8V=j@KCgEjvw zOc-&Tec`TjjUmTvY#2^N%-C!8Eg}NA-js?uzIPGH$83C0(swNdV zDF~M;IvQO#@p0%@c<|BSG!b{QnKUSK`7`azbCD2Xvud4AUcowt_Kc0_Y#1;B>cj~t?$NX6B*5|d)tV5k-Xd*G*7L;4?;p}+T`Sb9Y*h*ZI`Gjs@9&LI5dW#7 znzbXDnqJwb(zuqIKV8ii$dQfOh=y&SJ|2x*ZNrHYR`OPu&_fa_QF|@#LW-;QY z^GuEej$W&v1I!TBst|KGt4TUvRT<-W14fY5KM&#VaE< z)U0=BBplt9@9leBuZx)mJEWIt3WuzpLh^G7@l=vRG1>oXHPrMawZfgpmCnH+8Hx341rS?HQBWv681QMKrW<$ zt(%Zg(Iqm${A_CjENJ`6KDV=dr(SRET1{E&aB8BWLFQT#UIYIK7T`X@fr>Up}-BbXxMMzRE(28?;{sh5hI*~gS=oo}g$)ui}KxkcTBn9(&mLy2|nh~4Uz zQp<&_oQ1Bre)3i3yqXG*=~f!-yNY8fV~_5VJtL8=of~^P zcmBlN#1|i8HQ6tPN-f4g^~z89uCQJ#99q_B!6a$!>I52{Xd8cubiECTx>N<56X5BL zgnnBJT;CLU({_Aj_;`?I=_+mVr(VhNZL4dr|K{G>kw?j5$knib_{GVwp{HkwsmR5q zqkOM~GI1N}T5b_-5-aNLY()8B*Y^A-tCQSt_wUoV-h@J(Y1!q6rikaL&-p@BML!x8 zIw$0=6GTA>2A*4QpNg}&4elxf1_Pv0SpJU$X_KS3zD8pJfVrAFStw?iX(IyA+sDIJ!2U5wM|EskL-QEmehx*|FiteF9Q|E-2N zueqR`swT{y)iQzQZ4&yJRue*WOeuna$ekFw$^WOLvyO_Y>%#aQ7+~lbNyz~L>Fx$; zK>_LR4gqQD?nXk8kZur=?rx=9TDnu*3)|$0u&AMlwbM~{J=XYPbXKkw}#H?XV zkIyYg?5dUBnEU-h)IsTjlG`HVWHWJGc}Q!+N210aNouH+c-|T6a^*Vj7<)xuQmDb^ zNEfZQUMu`Ja3B;lek48mmv7!7qSk>X6z40w|O8KZ&*7n(7*`>fCB>fJe4oh zy-FAK8-@y6Jts6iPxX8HonKirR#3i?6oin=hv^etwU!WI*{&}-8Vgp&ng@BN=Y6ji z_P)CP>puSPd?K#nA?U7YNhM$UXlU7)OxhcVqQ&d%pg+MqYWacHYV#b?oJ7p)W_?GB z8=rAR3%ylXZB;RQo%zUF%p_eVp9{eNp&C5r3DN3K`R?be+ojiMwhjCw2V#~Hy*TRl z@@6PTXIjGtm2`2jHFt5mcN8AmljrtXuKllXurzia8XaTe-k0&~X196mrQju-<&GBJ z99LDLx%a>GeaL~1;|fuJN?kiD*Bf1WA~c2jSyoRT&IXGKUSz;KpbEIe~)pTwb^|FneJ!;8Gic(T!m63~EN#iuzClXa8z z*8CMX9D9qP*DGfA4{|&b3LN5zk^zq~GV4MGR-xRtZ!M{{;41&#d7CfKU*z**9=|_V zylck5n{x*<&tK4~Q{{Ts=|D`hwxZR3_wZx~gHsMHI(27R@NzV4$po?6MFu6grwULFR_U9S;c5#2>dyAau zMFq9!?|s`bY6J5BxMwAD_1H)V%1js5y{1~V3(1c(^wk>mR_#cR+js*SoQg8rQk$zV z?sLVHbNLLf2XJXT$88!$c|-L};gVJO@6vy9uq;3jna=M#4CG&>sk!^PtggG6=>j`D zDfr`gY4`Avsj)@Q8(LhShW$}Y`tbTGCQzAJ_r3EIGKb?{wj6Hc@`Y->+LfS`b4J+! z+4lTFk(Idloa`-fYHKqX{{9zo21>XQqqA9<2+yioY^3^H+N=XjW%OcFWh1dQ!KZzD zX&q$$fb}==c7YeZtNNpbIPt2gIC=dZ?Se%0H=~+y`*IJGXC}P(;l+87&@bGJR202G zvtC;BDv2`7rsUI;;x{;Zwxo!jaYF*b5@AT-&TPKel7m-F^@dFi{~SE+K;aQWi}vZ= zzRGrq%B4A?C-3nCSa&MWO|cb@K0(BThHVj6|JaU;M;E@zzx0dldxK4Jev60EM`HIj zMopi0e)G%I-B7Ny+;rYe_)>R=t=+~l$XrSH#3 z`{E%#B-bQ^B`IJI#sO3tt;sf)j&h}x<14%sg5BD%YoHRWznhD{`ZZg$a`6g@q4V?A zefl@@rLVjW46F&E^y{|h|W4tz#J_L0Yx3s45W=h$V|JM2G^H6`| z_whQ4GLwF%t9w=RUG~Gqgl-o4PPL`o{om|iu7N06qJ*mIHJ4fHb!j!o&LD)8%Na4 zrInoZ7&=^vuuf)9?dO3VnKV<>^A~vKP5H?{Ik8D9jw$n3 z4rb@X+cy(%~I&Ims zI9Sxq@?yda(8TrF*o%0uQ;O;_k)SZeBIwI)ZBCS^I?Z3#Tiq#j7Uf*7ds}>l|4dH) zo}@(tVY=`obBs4leTHN**%@_C z0#0UifZj8T>ahAt$6I|a=NOLqWj$Fl{m}GU3`U>a=AzqLUBqGM)tB%wsZjEkB@r zH4)Je+x#Spq#vM!PGe8s<4d#-m=;dQ`ToYnzwWUzmBq<^nlTIfvW1ad*&vY1-YWkZ z|Gh!S?b}`cX^%lHzrqADm=&$E{^KW4Cyq$dwYC*~owU^!zo*3zk=JVzg*b}uCvJaA ztL(XJ(%n#uhWXf5SpUeX$S}p@tI}_}?z5~UJ{G&yvHX1AnP5ufdsigucj`F4@I}zz zj#?3!fnL)@%iKo1;n{cNRb8)&T66UWT!;L{4)bL*H6O0S>BMR{?v}cJLKA{_fw)w> zZQ(EtTHhIywhy|d4FSmHoC($FJ(a&*(#e*GTiJLOPC2QVC6>P&kSCK~xoaBit&k!7 z=JCqGHMPo+*!w8vc-W-8n9D~z@HejiwK}OW zas9J1%H#2>~jFhYab4Lbt3jqUolFL!E|1qs8$5jD~Ny*L8*vtf6%IKINr{zmc4)zsj@sCZ9# z2%nZe3M35~IYG4-D>^46L{3Y2KJOQVW;CFlj)oB#5Rc~ks3n4%ead zQE@Xk7yzV|Z%Q~&`O6DfB!P-9&q)mY08F6vI%sNLWLY=hA@R>$CaTg(U4mh?4K6~j z$w4Z$G$w&|V)#r^%K8WJ{dR)k-Lz_JTk<*LxpH^G6=k-czNfLL^y_5Xv1D1KBftR6 zB$&Lj2d#g@Na1=nD7@VkA8)?deLgo)4}DJD%yy^ATpdZRvKw)aTzk=Ra+iV1$^Vmt z;EOPC_tt~|WscM`*EtCQhwMmrca3=3&ZPeyd%DU~sm?o6G2w#)u8zyGP6(Ody&5AF z0q$_IKhPOaWoJrw`ucU_xH`@ zmDJ3-bbRTC=%&*KkpZ?f^1b!`=A{|vDBzbh4aTxMZw^g}*j`MGaKq7ce?O3PEnb3Y zHc7Q3(4J8IZ|t&qRW5@oCQsbYXXP9R{b6UeV+JqPK6u6mpVC=MXMSzFw|t#*OgH2~ z6|&5W1B4oUkpwGqrUc}?^FLV((f|B#rl*Mf0#dx{855$;^-K6xt|ces*RXelY=LQm z^$QWn>8({Rq=))rNB`o67aFw51f6RxCNd{+&ymzDgd*u#`gYFI9thAhju`)yi( zuFzG8A$4z_QQC_&Kt{6KXpa&-iouZfFsg_+Zq(*8Cu;dgInmDz2fdgF#W>h> z1e;{74wx1Jf1FT^U2k>2}Q~SH9H*1^v&e0!kXyOXl zlf(lMySQ`RMHbjk|Md4b3KwvQSk1Keio>$^NgLu&SKf8W`xHcmZ}U zYN*@_r-qy@o@P%<$&d#5G);VETr+F44tEe738Cc1@rFXq<4H=JV-RWRC{OR9exNh( zX^ElhXOzA$ScoW83I$MhElHbkKy;eJdY`3!`Q+pmB?l0|U~p9@tor$3nQJO`F0_+i zX(%nYVKxe2C{X4y+HT_Ft*Nao6=71-I}pc!2NE^&+fWUk*|8I-vmD)mIH`DMeVO8m zPc2+r@wwq1xeL??S__+d%_ytena4c(Z-21AGHaQMMhF2u&h+J(=-_oAXQwgRcCIw< zQ@yXVR7JgkVZAgBF9irB4NhCY!)=L5h=@=2z!9a-m!g5ZOWW5n<##wttArRo#1Pmv z=9RQb{%tNcyHb-jYMJjF2`gGv&Iwdjc@8{#3%n>kN@|NgLiyGCe%|-kx+Xm|Qfrqz zQsrf!RX(JqP=zWlqF%*4mKY?7C=;#&24!gQLH~5bz6`84{2u}2 z;xMo6B=e|1&wpY58 zVXF=|YmAP;N51=?QHJWmHP+(F95|I$^yLd9rjEeREPH_h{EF-0_o5G_a|i8i`}GI| z>f^>PCEA~)Dhf(cN+I{-jVixq{hiZYN}s322ve@43R0JmZ@zAYT(hFiTXL_IM4z@? zfPYUnqCY)tukWuibq@Fp{3^tVogs)Ydq_x#$cLnFzSrjYQ)%hx<0K3Jj+P#7PFg%& zjuBo-p`RviZzOWD+C=$m*cP;oG~OR=Xp-n&nfuVlQE5(h5H~G>@+SpR*WL9-xxo|% z+=fD)!m;^{t6q;IY#kX3LXVohkKTsa0!s(jVLc#fcxR5->1vXyv`Vy0K8?K2m`nmu zSy|a4y(%|Jj3mTdJJsBrNmpI{v~uo&*>}lwI9Ho$AwW}Nq z=71k#_^Bw>zWe~k4Y7QxSdXtrpd7(QS>Ee|vfWemS%!2W6k0m3RSMv7Q)A{2%7Z$1 z2nduGcn%#Wyn9p>P@zGD%4A~XRPdFe zFP3aznF=dRK5_8834ORuK%e%_^@F#`=>$D)7q!m8*%`XqjF4m2(;m^c<)1tc{iIdz zqvKLi9>4gVCw-`Dj! z+R?&me?aWc%E{@j2Y0n*{F|_+$=Yc4M906KY1@I!vHsCfMTqW61oxR_ddeTo?+=5B zVwPJ@X{G5^G59Lte$}$}>}vXHUB_Qh;3Q_2ni>eM9}=g&-OG=|IU&u=Aj6$_H-bR` zfIKQb8J&sJ$Zz>Tn8}b5R3pFM06Fz0sZb=Uz8a@-OgXdY8_{ZL9*0ga@>>X*?4m%N zoKBK!!2XkRI6brb0Q|r$v~l>byLj6iu{4PMv_Do^(+{6tRIy@*qQKe_SvFHsQ%ax9 zsjBDus^^7oj|&LM!2JAt(Kp}wKVg(^Eh_~D1^m1NI7;soqeb5Qu9hDwu#l)!7oYa_ zDcRPX-DjIi`7132Jy6B$*D1l>Iu1J3al-?{_7>UM6Up^tQ;Ey>5l-)#Zw6{VxOLBoCn~+{E*60d3(Mv!za5$BeDJMr$zSNZ z=pr0?3yawFcz*I}GdQ{NJ-J~uY!@cO85kIl&7bP+?X~>oa~(waZ@ozD(K^-q?c2BC z-w8iQt-`=v*-<70B*Tu{mY%eZ`o#6nb%FVi=GvA}nLT1s*!zsqT;!_8r5FAS2&xv{ zPhFrr$}-MdDJWb@1%VY+QJ)z~dYz)m#l_{=ez${wtMnl3rprH_Og%l*)8BvPSHbVQ z^>}@owZ$pS2bCMYmr1|+eCc3YP_VL7%7_s3=c0D%mZa?5uz{QcpEq}**La1h^h(E!aN&6g>kF_DYvAe7LSIXe6#UH+m@EBtykCH-|-oazJ74kUAtcjr|uqAU;+!f z>|B_JS!UB=$=`Nj>{()fi)sHjCdVLxFK_Oe#b_&uWc9`~zg+Cpovv#8gG>?&-C*a4 zVW?nr=VWG~8$*ax2pWMGl|Nk*w@nr$?^WgDKh|v8<8u-%n%Mk^nw^5TyWuP;QIY)M zKc6TQD}4;~4kkhHNx4sD3m)Nx2R+^)T%DcL=)30T}$~@~%uErNbu=#Q;9@}ZYMV2ah>UCrb7e~@VLc?ag(ETliGq7bO)))$!FU_o2y9xD0+hIg_noC={D_k1zvpg+Iu&Apu0KPI?7WCj-*wqKyN2-bZ~cf zu{!;O;XU`gKdznRBXg&Jl)6a>V&5leG7(%L=Kj%tqQpJVcQ>B*SYRMDS$9)t9s(%s@%@y5hRD-&+Ye5Rq&`HTAI! z%)$H0eymnEH|wx+)_+u$_Z~S03Epw2#3E+l-Estn{HXV9Xx8uBlH{pM2t30n^C3Gk0{>TS9mc!^MjmXYqY{B1l%E zYSC(nj)`?A%Yo|vg_B^_zN!%S9p52Di~WZ2L;JyK<@zJ58+7sGgnVxWUignJx?~>k zN6ylAu7=Bt<79#>uevYFMhNKPbVgMGB(>xuuc5)z#r+aH=@0|Hn8k7$`Wtd6kpdd06^kYLK?fW z_Avfea27k^c~)WHJU(MJxnGENx-@NI$IirRvESZQ|+kCh& z`Sq6YUZVQjJE;(qOs#&)*UN<9STj#qp^=FI>DUqs4DwF_92x^b1>% zwXnu+S~GbNfouNYc$^N&oK9;6CSUhXzpF%N{8xv|{%9pNaR5~rZiq!ExJW(RWGp1C z0G0p{OvHZt{%CTPBs6ulL27>p$60U*qegzt0XoA0b_rb`V{WxFtpR;5JKmQO-s7+= zORO1?TF5$z!H~>r(nrf9PCH=EdFb5Q(RoUy zQ+N84Y-^-vle-1NMRo{%Os{9R-=tbs|Bmh#(5)F%g)%C;*CnP%C;{)PAs(o;WB*~G1 z5nbhV@Am69c{V8CH>>N$Z>lR2R>{GVlE%bt?gT9!jCNCbGOr9-bmpSU>6owEIKimq z)TOERhs5p#O1wcRCL8WTOhhtAA&kTL9V-UpeAi7VB*zl=zG9S$%o=A^(Pt}t7ibmF zyW`@75<#Dv7aor16RCK?z&dz$xOb#de;yHS3R+Tcw#rY%IdRpXGx(|x_`|iTDfnih zn*H_v*@z?i13Tp}FnCVbf$?5xeO3_%JD8dh-=cotF&bvH>@24K7%6yiy1mI@?NL&k z{*7ZhS@eSb%MpEHa&9D#fQbh@k{*OI5}}~P9LS77Cem{_4c#Jr>H6|Ad_9ddR)>TN zGF{)WOy8A0Dyxj2YvN+u0YeEhz>}%XP{m<+I3P`8=@-_wW&8n?7w% zj^R4zZ6Y%0Jm)2xiV}=d)T1WC^!L%;Fp)SJSzeSto$XfD?aV%u4ja_hj=5oykW~vA0S8Mn!WjBZI@);i7P!Hbj4lalgAF@(#KcE6Ru@ zMT2<8e_oEzeqMqBu|@e02LlGoP{bMzRA0-~|6)w^9`!u%(>DiB%tCyfMVrr|ZImC(C$pgtuXd^b%yRmh)vktM!-jJdG)O{}RgWyI!SPx(NCGp51m%@GN(fTk+zqNh z_x2SB3{1V%=HyEMlIO>lFFcA&{>KZLLkQu4I1VAf<&=)!>9E1;)ZOkA;3qv@A#K$*(BfLoG)si44@S zjjF@GC6gSpa5NRRdr7ymieA(5KM-Z`)m09cZXvQIR6KVwpKu&bOx0LFLT|Md;b=VH zI^IE_{OT{g2Gs}S_iF7}z>a>Xe3_yBkjK>Yc&GB3x7|;-xD{!E=7PtUVm3vFPIpi7Ke@od6r<$ zvl^HPy8)R{NEfDH)q}xA%3noQigz-&oKT*}LI*V`3-$)1&7x#V696Q~{v#O$K;yd!$nNI)3|at<#B3^XM~NsNDbFqGFnB(UUR!gAM) zE2cbUm(hwn_)nO5TE zux}+etdyaMJpQ^M(88-)^*&YUBkN{b%-97b*ju0}cFt%H^Y|A5oEKDh zP8hVOPz=kT%L^a&jisnmiFU{}sYS|35d>1*ejU9+y352mMUN27O(FhEKQP0?A?D^6 z)Zvj5ojAkqm^}Z?2E=RXpZ6Il9=VOJ_Wk~|UD56&i5QK>S@fXZDSEGN@!O$NQ*T)K zts=*iZLG`=r_>Szn_!rhMlwJpOW!E?_83(g$u8XpjbNAw5#$Ba!>Ls@8fwWp{W)m} z;!PsThWpPjZ^$R(59yfQ&n`XWm4X1P4Id{b-}%Zk9J)RP>-9iBEGNbU20nEd3pka0 zne13dJAFt)2ve3xpe9@{zfq*;5VaGmGM5lf6R2vv*mxO$1(&R@IUAGV93JT|8b6W%ozt|(GY0L;MKtMsVOy(u&fd$?Kf9$y_tFmI?bK z9J+jz%^7J2BPq?2%8cO)7biV_1T~pP;k%$dU0uUenU}C2aOPU(oE0abBs}eAO zILb#02=B#QHD${tgEY;3yBYXzw)AtJLEl13*{dZ?5}^4s9_LI%FDVJ}Wa7~wr7pGa za^pqF^%htFnL+gY3TnRb9PgotY;_-_CWk&73Hhc3zuYp3x2);Eyc!3u0tb&Onv=<}<-bKNX@pewvbi*MF1lZytQI4?!hpFcA>! zI-xipC<=Y$xVpz|tG9^)>&z}j_=jWBG|zYHYtKHuZqSflB+@kaxE^Cp6AUj_M%-zK zWW>~s6_+fDx?XidJ=n|j;4;$R6U65qIu&3NGjeM91*7L?ZW1s=bR?eodlpgbJR!7` zOI@+Wpof(ru=As>z-Yzku)p`K8iVpMxi{Zyes@L?9%berCn>-XX$@rmZP=5h7#E+$ z3CS#^TT+Ga@j;BQMgtq=xyp^ub=04wU zb#<6toN-<3OVuJjPcM5K{`@l&_<=%=T{TKS>g_V}>YuD18FuLdN(K!?psR%Y-^p`x zmfbSX27v;_T`Y$K~6ISDPI_Xwi{*wwF`3SyP07}AC~wKQ2= zi*(D0>}+n_ilR>KUPGy4&ucGr7y^~KUl0tGKn|HH(LpvFNs>YwsT>`H`-l3O)lm;l z)~x^Tv>)W&|MA^>Us-lghPzDhR{s#|qfd%xQ)RnLFP=DT8aq>eC^Pl1Zx{%KXz(5q zg>#MKwNJClX)sGI?TGem8!6;VAZ0Q_1oOuE)gURe1i7gA+nS!D_TuWVJ z(x|vr<6kuIGX$FUcBG%?g^capBoPRV=pag$DzfMM5NmtD^6*ag&y=ALaCwz-#DEVh zAO7O&xz6JWHL9yyM=?okaPo0veg)_akwX51P(LZ@K|!!L5b3`|y(B}Rr9UtYIu0u_nut~a)^pl`BlMHHo;%!`Mee0f6<3X{^3@tWWsWj zf?mcg=mFux5)?ydYkh_<^fE9F4Sc$NdQm@dC5CB*ElO8vrIhD|9~TQFhj${7NOgbA z)Mhe9I6_|iJ^d(n|DDgNCGxDvW3E1Py={W&1c{Tr7t8?=RfN*1PJHUeh-%c3t{IH_ zntn`s+};$m8{L{HCf0&{DB_(qK? z6npOmH;htWYsByxv~{kbe@P*Q>1d_F)5f^4&GIekut);%Bwe6 z5)*kY&_yJA*;2>=BJ|eNl7F@QJ9qP8y2htPnrf*pz09{rMr^08 zsetOq@R|PJ!hpab%JDnuyRl%uTcH{M45AB*qk3OQyP6v9VOR2C=O)tUU9q7Z??h!Q z>qhNQe<%5$+1gKzjn!A*BEP%R`5XwMrEhF7PHqG(-@ds zgBeJeGf!PH>Q{f&AA&ab3(~M4WWs-myX>df51QZz`Q+{@uWPMaA#%jUxaMY$qiuc@ zv3cLNuXr3F*Kp3u{}%6Pt^8P_kjTtru!anmqnIv@ipputA^LYuS4GPPI@Itn@$=M2 z2Xg75Z=!AWN?2YH;F?&@5q)Su7X_ctvMaQ0!eor3b5z2&GW+;r4TwQ!IAADLIvfH~ zx-|JgCC&aJx8Ml#SFJCj-q)2%yw*p@r)?=e*&96`6`k^8l}R8T%9pIW$%F3I6|;lQ z6jYCgDK4p7emAEH1CQi@tIbdVS|-?_TKFC->{fS0Nf)*C!|f*@lym`hAZOYM3YCOQ z_F~%x0_;oP8cp(2YC3q-b*DPAA}|041WQp}^#+u6Fu4+t0%&*xTd8*r-I`r8-? zb;!Cr1NHehRf5JMT2%@T8ULYTcFFY`0-)4!7^i)gT2nwoIqALU%Z%F|l6oSL=PYDH z5=w~Pi`mx;Q3bOgVf0P5F)43*I^Fmkv46?E^zNTfhIPI+mq#Wl=%}QQQv2~n}cU)W_l&xfQ2u9{@r_5-^k+(a! zRL)VhU)`5oU^0EF)bl%8;X@FO57PgNJXrSkjBAIjd9Igq|S+7L}yilsU{oP3_? z!Rd#C{4)|D{t|AA(vwK>btK@Gi_nvIp|q$))ZWcJ9z|_l9e)7-d*$#KAJ_*W@r-Lz zyoKHm{oK?bKxz*MqaS|=ZVg`vhe<%RnI<#B_k8oK?;t|Bp!}KX>8%u|IZKN|@3z&7 zOcy>!#DtZZ*p$(>;|>vdj?ZJ#th~DKnGk(G-+V9H;bc4ReS;Xo!c_1FFyEG8M!nq~ zH*9rsxn8U%WsIE2>bSfkvWy%~F~7Pn{CECqn-np?JhN$RtV629;>Sz5zk^P@o2Cyp zN;V-9JiT_AK0J%uVKVeYen8P)Yp) zCvvaNeT{X5nsgH-#}TJ{>dB2pLzr{@?tzw;cwKJ%GkKLw(G^AgE`08b7yDg zUm7#LTV@cUi6Hht@((^O#5h&QYsV&y|1WWGS#9IAo1Ld5vDkK|IbeyMePxm%acH}0?k!s(M(|eIlzx^xQ>t{p(pf)2V3`lcQ?YIyv2a_!dlpM6saQ6S_oIc(|o zq5{A4AV$)f(eJ%O9>Iv%ZGA04lLeBB8T2GZl@@kUj+m4U&kKc{1TWOOiS0uzc zNUsoy`)zA4u%Kk;8PhHq53yNGt*P@nls+oq5!K^JiGC(qM_m8 z;i@#u{^7RW2u>o3|c=QijzK}7RIOjKldiLMBfgz zxQMPOpJktoxpsnn?`-5bv(iMOOA~7a16XG@95)?z677wD?<(UFyD1JC(j0j)EFRw+ z*6Pi!r>E+Pf*>n%uNupclk7fU89I&8v%e)_TC}Oll0Ll#wbv>>I!8HuwEFt|xb{)V ziU^UDs<^<)pm8)8jB+I_87$`K)nNdjAd5=R$$IkgB0<3J>E_`wQhGftch?9Y@77cZ-Ps%?oVVq_!@^>2jw<_22%!Vt%Y z(w~vc%MsiCMGWk>*1gUjqt_6mRgt~Mi+AD5j*;=cPD#FA>W2C%<8NOjzS;bTKpd>I zzmjv`^I3oQMubMRL&ek6G)0E+;8m+%_?lSj%Hr5j{%m%`i-BSDp(s4tMJl*l-+ulv z#j|}`!(wr(-oDE=4KM%9YDMd3;eWT$A~&C!9Z7GWkuh|i$E1e)SP{%mKwkQtRHdX* G;Qs*Z1+WMap Labs is on GitHub
    -
    +
    +
    + + + +
    +

    directly

    +

    JavaScript

    +
    +

    + Visualise data beyond the map. +

    +
    +
    + -
    -
    - - - -
    -

    Simon

    -

    JavaScript

    -
    -

    - A geo twist on a classic memory game. -

    -
    -
    +
    @@ -188,20 +175,7 @@

    JavaScript

    -
    -
    - - - -
    -

    Pin the Tail on the Map

    -

    JavaScript

    -
    -

    - How well do you know the UK? Play pin the tail on the map to find out! -

    -
    -
    +
    @@ -235,35 +209,8 @@

    JavaScript

    -
    -
    - - - -
    -

    Travel Like Royalty

    -

    JavaScript

    -
    -

    - Explore Queen Elizabeth II's 265 overseas visits in her record-breaking reign. -

    -
    -
    -
    -
    - - - -
    -

    On My Way

    -

    JavaScript

    -
    -

    - Let your friends know you are on your way! -

    -
    -
    +
    From f7a0d05a69fb99a4d32e46368fb33c695d21480b Mon Sep 17 00:00:00 2001 From: Ben Flanagan Date: Fri, 15 Nov 2019 15:33:55 +0000 Subject: [PATCH 7/8] nov 2019 update --- index.html | 64 +++++------------------------------------------------- 1 file changed, 6 insertions(+), 58 deletions(-) diff --git a/index.html b/index.html index 61526e7..7fa0bcd 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - +u @@ -33,7 +33,7 @@

    Map Labs is on GitHub

    We're excited to show you a showcase of recent projects and apps from the Esri UK Map Labs team.

    - Browse on GitHub + Browse on GitHub

    Need an ArcGIS subscription?  Start developing today for free. @@ -115,34 +115,8 @@

    JavaScript

    -
    -
    - - - -
    -

    PictureFillSymbolPro

    -

    JavaScript

    -
    -

    - Module that gives you more control when texturing features in the ArcGIS API for JavaScript 4.4+ -

    -
    -
    -
    -
    - - - -
    -

    geohash-helper

    -

    JavaScript

    -
    -

    - Generate and display geohash grids with the ArcGIS API for JavaScript version 4.4+ -

    -
    -
    + +
    @@ -177,20 +151,7 @@

    JavaScript

    -
    -
    - - - -
    -

    Map Racer 3000

    -

    JavaScript

    -
    -

    - An addictive retro racing game built on ArcGIS. -

    -
    -
    +
    @@ -212,20 +173,7 @@

    JavaScript

    -
    -
    - - - -
    -

    Name That Landmark

    -

    JavaScript

    -
    -

    - How well do you know famous landmarks of the world? -

    -
    -
    +
    From 3cb59a66cfe1cac928f28435269a9732999231a5 Mon Sep 17 00:00:00 2001 From: John Foster Date: Thu, 15 Aug 2024 17:10:39 -0700 Subject: [PATCH 8/8] Update esri links --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 7fa0bcd..d70ccee 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@

    Map Labs is on GitHub

    Browse on GitHub

    - Need an ArcGIS subscription?  Start developing today for free. + Need an ArcGIS subscription?  Start developing today for free.