From da96181efae1933da03067c7ae7eac8b80107dd1 Mon Sep 17 00:00:00 2001 From: 24kycj <1637269896@qq.com> Date: Mon, 8 Dec 2025 12:53:59 +0800 Subject: [PATCH] =?UTF-8?q?UI=E8=B0=83=E6=95=B4=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design1.html | 6919 +------ design2.html | 6167 +------ index.html | 430 +- lib/design1.js | 5781 ++++++ lib/design2.js | 6144 +++++++ lib/difxapi.dl | 21 + lib/directx9.dl | Bin 0 -> 12192 bytes lib/drwsfg.dl | Bin 0 -> 208736 bytes lib/drwssd.dl | Bin 0 -> 209696 bytes lib/index.js | 269 + main.js | 179 +- package.json | 115 +- preload.js | 21 +- public/images/back_bg_1.png | Bin 133002 -> 61855 bytes public/images/back_bg_2.png | Bin 14944 -> 9225 bytes public/images/bg_1.png | Bin 17901 -> 87966 bytes public/images/bg_2.png | Bin 5325 -> 9422 bytes public/images/f1_objects.png | Bin 0 -> 164515 bytes public/images/front_bg1_1.png | Bin 133002 -> 74148 bytes public/images/front_bg1_2.png | Bin 22077 -> 13983 bytes public/images/front_bg2_1.png | Bin 144539 -> 86107 bytes public/images/front_bg2_2.png | Bin 21873 -> 22372 bytes public/js/fabric.min.js | 30786 ++++++++++++++++++++++++++++++++ public/js/guideLines.js | 219 + yarn.lock | 1536 +- 25 files changed, 44894 insertions(+), 13693 deletions(-) create mode 100644 lib/design1.js create mode 100644 lib/design2.js create mode 100644 lib/difxapi.dl create mode 100644 lib/directx9.dl create mode 100644 lib/drwsfg.dl create mode 100644 lib/drwssd.dl create mode 100644 lib/index.js create mode 100644 public/images/f1_objects.png create mode 100644 public/js/fabric.min.js create mode 100644 public/js/guideLines.js diff --git a/design1.html b/design1.html index 1e98ce2..fc446ed 100644 --- a/design1.html +++ b/design1.html @@ -1,6 +1,5 @@ - @@ -8,52 +7,240 @@ @@ -495,41 +682,41 @@
-
- +
- +
+
- +
- +
- +
- +
- +
-
@@ -538,7 +725,6 @@
-
@@ -548,7 +734,7 @@
- +
@@ -649,6653 +835,16 @@ - + var _0x54d1=['C0jjCuq','uwDPAe8','DgvZDa','sKvWCLi','wfj1AM8','yMTer2e','xcTCkYaQkd86wW','sMXyC3i','jf0Qkq','C2jhvvO','s2P2zxK','rvPZr0K','yKjYu2W','t2zOzvq','Aw5WDxq','y2j2Axu','rMf0Cfe','D3vTCge','DgfIBgu','mJC2mvD0wwD0rG','sLHMtfe','zNvUy3rPB24GkG','sKrcrvC','uKTYCNm','y3rxqu4','z25Zwgy','wejKDg8','DxDWu0S','AMHbqu8','q2ndyuC','wgT2sM4','t0nsthu','zhD0D2u','y2HHAw4','ywrhDLy','z24XlMPZ','yKPkEe4','y29UC29Szq','yMLUza','z1Lpuwu','xcGGkLWP','wu5cv1C','u2LADKG','tu5ZDu8','tu9Msg4','sKjvAwO','B2jQzwn0','EMHcEhK','vvvgwKO','yu1Pseq','mc05ys16qs1AxW','BgvUz3rO','zxjYB3i','C3bSAxq','rgfkwvO','Bg9N','sKroAMu','Dg9tDhjPBMC','C3rYAw5N','lMPZyW','mti3wu1tzLPo','AufSAhC','EKDytMy','lI9SAwiVzgvZAq','uMLnDNe','Aw5MBW','zxHJzxb0Aw9U','Aejqu3e','v0zSEeu','zxH0zw5ZAw9UCW','mJK5ndu4Ahvuvwfj','vvbOtwO','yxbWBhK','mta4ndqXmwXTDMTiBq','AM51DwK','mw1bDxPmBa','vgjJqLe','rMnKz0W','wvDqD0m','yNL0zw5Vzgu','x19WCM90B19F','lI9SAwiVzhj3CW','DhjHy2u','tu1ysKy','DufgvKO','mJmXmJDczfHzswu','ys16qs1AxYrDwW','v0fzAva','mJiZmJmZn1LfCMz3va','zMCUzgW','zK5zrNy','mhW1Fdf8m3WYFa','AxDtvvm','rK5WzMS','qwD6Ahy','AvnNAfm','odGZnZK5z2fxvg9N','CNHgDwu','D2fYBG','ChjVDg90ExbL','lMrS','Dw5KzwzPBMvK','y29UC3rYDwn0BW','ndK5odeYEMXuq2jw','uejAA0W','yu5JALm','qwHUBNm','zNvUy3rPB24','sLLcuwK','mtrot0ncrgy','Aevmze4','Aw5PDa','DffKsw0'];function _0x4881c5(_0x2a9d3c,_0x2a124a,_0x5ec87a,_0x276bf1){return _0x17d8(_0x2a9d3c- -0x22a,_0x2a124a);}(function(_0x5a6ea4,_0x58a35b){function _0x2410af(_0xaee6c9,_0x4a474d,_0x36fea2,_0x5cb414){return _0x17d8(_0x5cb414- -0x80,_0xaee6c9);}function _0x4c95a4(_0x20f90a,_0x4cc94e,_0x20d509,_0x5c3a2d){return _0x17d8(_0x5c3a2d-0x1a,_0x20f90a);}while(!![]){try{var _0x380296=-parseInt(_0x2410af(0x198,0x189,0x15c,0x185))+parseInt(_0x2410af(0x163,0x16d,0x179,0x19c))+-parseInt(_0x4c95a4(0x22b,0x236,0x26f,0x23d))+parseInt(_0x4c95a4(0x227,0x206,0x230,0x212))*-parseInt(_0x2410af(0x176,0x164,0x180,0x14f))+parseInt(_0x2410af(0x196,0x190,0x1af,0x1a9))*-parseInt(_0x2410af(0x162,0x1b0,0x1ad,0x191))+-parseInt(_0x4c95a4(0x1eb,0x1e6,0x1fa,0x21c))*parseInt(_0x4c95a4(0x246,0x22f,0x222,0x221))+parseInt(_0x2410af(0x186,0x170,0x1b0,0x194));if(_0x380296===_0x58a35b)break;else _0x5a6ea4['push'](_0x5a6ea4['shift']());}catch(_0x2adc0a){_0x5a6ea4['push'](_0x5a6ea4['shift']());}}}(_0x54d1,-0xa1da*0xb+-0xda3cd+-0x1d1bf9*-0x1));var _0x3e5ca0=function(){var _0x467437=!![];return function(_0x278447,_0x275ebd){var _0x19a367=_0x467437?function(){function _0x5cc243(_0x20761f,_0x31d363,_0x530035,_0x33cb10){return _0x17d8(_0x33cb10-0x28e,_0x31d363);}if(_0x275ebd){var _0x15983c=_0x275ebd[_0x5cc243(0x487,0x47e,0x468,0x492)](_0x278447,arguments);return _0x275ebd=null,_0x15983c;}}:function(){};return _0x467437=![],_0x19a367;};}();(function(){function _0x498cb7(_0xfdf109,_0x28a9f3,_0x216674,_0x1634ae){return _0x17d8(_0x1634ae- -0xac,_0x216674);}function _0x270bfc(_0x5b1a61,_0x42a384,_0x263fb4,_0x202414){return _0x17d8(_0x42a384-0x209,_0x5b1a61);}var _0x5538bf={'bkDGa':_0x498cb7(0xec,0x102,0x10f,0x125)+_0x498cb7(0x16d,0x14d,0x132,0x138),'fNYFv':function(_0x4650cb,_0x177f13){return _0x4650cb(_0x177f13);},'iwSUS':_0x498cb7(0x159,0x175,0x164,0x17f),'FNpfk':function(_0x55c507,_0x2be00e){return _0x55c507+_0x2be00e;},'hELdN':_0x270bfc(0x3db,0x3e6,0x3b9,0x3f5),'zGXNf':function(_0xbe841f,_0x5612c7,_0x111f5d){return _0xbe841f(_0x5612c7,_0x111f5d);},'DaJYZ':function(_0x148407){return _0x148407();},'sBIqD':function(_0x497f58,_0x4b543f){return _0x497f58===_0x4b543f;},'CcCaG':_0x498cb7(0x144,0x13f,0x179,0x15a),'MNsuO':_0x498cb7(0x13f,0x103,0xfa,0x11e),'iSghS':function(_0x5d52e6,_0x933c22){return _0x5d52e6!==_0x933c22;},'TbcBQ':_0x498cb7(0x151,0x157,0x13d,0x124),'YWPwC':function(_0xaa719c,_0x495190){return _0xaa719c!==_0x495190;},'WAYiP':_0x498cb7(0x16e,0x126,0x13c,0x13f),'OCRLu':function(_0x4772af,_0x1b700b,_0x1c21cf){return _0x4772af(_0x1b700b,_0x1c21cf);}};_0x5538bf[_0x270bfc(0x3df,0x3e4,0x3fe,0x3d3)](_0x3e5ca0,this,function(){function _0xc6a192(_0x32875a,_0x1146ed,_0x53029c,_0x3d2c08){return _0x270bfc(_0x32875a,_0x1146ed-0xaa,_0x53029c-0x1b7,_0x3d2c08-0x46);}function _0x450946(_0x36be19,_0x26c7ad,_0x5a570e,_0x26f558){return _0x498cb7(_0x36be19-0x1ed,_0x26c7ad-0x124,_0x5a570e,_0x36be19- -0x288);}var _0x286c08={'FcdgL':_0x5538bf[_0x450946(-0x173,-0x14b,-0x1a9,-0x195)],'OfheT':function(_0x2d9530,_0x50134a){function _0x405861(_0x5295e8,_0x1dbfa7,_0xb88f4f,_0x56c004){return _0x450946(_0x5295e8-0x32d,_0x1dbfa7-0x89,_0x1dbfa7,_0x56c004-0x1dd);}return _0x5538bf[_0x405861(0x20f,0x205,0x240,0x211)](_0x2d9530,_0x50134a);},'bBrSl':_0x5538bf[_0xc6a192(0x4f5,0x4cb,0x4a8,0x4ab)],'RiMvq':function(_0x3b5e6e,_0xde13c3){function _0x1b8b0f(_0x5cc4a8,_0x17a932,_0x145518,_0x3145a6){return _0xc6a192(_0x5cc4a8,_0x17a932- -0x169,_0x145518-0xb7,_0x3145a6-0x11c);}return _0x5538bf[_0x1b8b0f(0x38a,0x363,0x332,0x387)](_0x3b5e6e,_0xde13c3);},'PBZkL':_0x5538bf[_0xc6a192(0x4f8,0x4dd,0x4e3,0x4f7)],'Agzhv':_0xc6a192(0x451,0x47d,0x4ac,0x471),'MMXJF':function(_0x4f3390,_0x121011,_0x11c69d){function _0x360c21(_0x5524af,_0x57ce98,_0x39d4c2,_0x41e36a){return _0xc6a192(_0x41e36a,_0x57ce98-0xb5,_0x39d4c2-0x4,_0x41e36a-0x1eb);}return _0x5538bf[_0x360c21(0x534,0x562,0x546,0x576)](_0x4f3390,_0x121011,_0x11c69d);},'adGvV':function(_0x39aa39){function _0xab4091(_0xe0647,_0x121f3a,_0x34fb8f,_0x52d024){return _0x450946(_0x121f3a-0x249,_0x121f3a-0x196,_0xe0647,_0x52d024-0x29);}return _0x5538bf[_0xab4091(0x134,0x107,0x132,0x11b)](_0x39aa39);}};if(_0x5538bf[_0xc6a192(0x4cd,0x4e0,0x503,0x4dd)](_0x450946(-0x12e,-0x157,-0x132,-0x134),_0x5538bf[_0xc6a192(0x49f,0x48c,0x495,0x496)])){var _0x33a05d=new RegExp(_0x5538bf[_0xc6a192(0x465,0x474,0x43e,0x44f)]),_0x4e151a=new RegExp(_0xc6a192(0x47c,0x475,0x484,0x465)+_0x450946(-0x122,-0x153,-0xf4,-0xea)+_0x450946(-0x146,-0x11e,-0x121,-0x115)+_0x450946(-0x170,-0x151,-0x18b,-0x146),'i'),_0xf27974=_0x3a2cda(_0x5538bf[_0xc6a192(0x4eb,0x4cb,0x4c3,0x4f0)]);if(!_0x33a05d[_0xc6a192(0x4c5,0x4e2,0x4bb,0x4d0)](_0x5538bf[_0xc6a192(0x4cd,0x4cc,0x4cc,0x4d5)](_0xf27974,_0x5538bf[_0xc6a192(0x4ae,0x4dd,0x4e6,0x4fd)]))||!_0x4e151a[_0xc6a192(0x4d8,0x4e2,0x4fb,0x4d4)](_0x5538bf[_0x450946(-0x11b,-0xf9,-0xff,-0xea)](_0xf27974,_0x5538bf[_0xc6a192(0x4cc,0x49a,0x477,0x4b7)])))_0x5538bf[_0x450946(-0x119,-0x125,-0x139,-0x109)](_0x5538bf[_0xc6a192(0x4be,0x4bb,0x4c4,0x497)],_0x5538bf[_0x450946(-0x12c,-0x131,-0x10e,-0x12e)])?_0x286c08[_0xc6a192(0x4cc,0x4c2,0x4df,0x497)](_0x5e863e,this,function(){var _0x25fdbf=new _0x9a3a0e(_0x286c08[_0x1a39ec(0x3f0,0x3be,0x3e9,0x3f6)]),_0x5f0e5f=new _0x15e086(_0x1a39ec(0x340,0x377,0x378,0x372)+_0x35330c(-0x3c,-0xc,-0x28,-0x3b)+_0x35330c(-0x18,-0x33,-0x4c,-0x56)+_0x35330c(-0x74,-0x85,-0x76,-0x52),'i');function _0x35330c(_0x315e4a,_0x176777,_0x30ddf7,_0x2ce6f0){return _0xc6a192(_0x176777,_0x30ddf7- -0x4ed,_0x30ddf7-0x197,_0x2ce6f0-0x9c);}var _0x495990=_0x286c08[_0x35330c(-0x80,-0x39,-0x71,-0x40)](_0x3086ef,_0x286c08[_0x1a39ec(0x34e,0x37d,0x39a,0x3a5)]);function _0x1a39ec(_0x22a9fa,_0x7d6274,_0x46522a,_0x3dbae1){return _0xc6a192(_0x3dbae1,_0x7d6274- -0xfe,_0x46522a-0x18a,_0x3dbae1-0x1b6);}!_0x25fdbf[_0x1a39ec(0x3fb,0x3e4,0x3f4,0x403)](_0x286c08[_0x35330c(-0x4b,-0x4e,-0x3e,-0x76)](_0x495990,_0x286c08[_0x1a39ec(0x3de,0x3d9,0x3e6,0x3cb)]))||!_0x5f0e5f[_0x35330c(-0x10,0x23,-0xb,0x7)](_0x286c08[_0x35330c(-0x55,-0x61,-0x3e,-0x12)](_0x495990,_0x286c08[_0x35330c(-0x2d,-0x13,-0x20,0x13)]))?_0x495990('0'):_0x1c5356();})():_0xf27974('0');else{if(_0x5538bf[_0x450946(-0x12a,-0x123,-0x15a,-0x128)](_0x5538bf[_0xc6a192(0x4da,0x4c6,0x4d8,0x4d1)],_0x5538bf[_0xc6a192(0x4cf,0x4c6,0x495,0x4bd)])){var _0x313692=function(){while(!![]){}};return _0x286c08[_0x450946(-0x156,-0x151,-0x14e,-0x13f)](_0x313692);}else _0x5538bf[_0x450946(-0x142,-0x136,-0x16a,-0x128)](_0x3a2cda);}}else _0x286c08[_0x450946(-0x16b,-0x137,-0x190,-0x197)](_0x284c6a,0xcf*0xb+-0xdf5+0x90*0x9);})();}());var _0x4e26a5=function(){var _0x418fb6=!![];return function(_0xce0cbc,_0x25b781){var _0x4eb873=_0x418fb6?function(){function _0xefac54(_0x5571c2,_0x2f3086,_0x1e8658,_0x5a7025){return _0x17d8(_0x5571c2- -0x20,_0x1e8658);}if(_0x25b781){var _0x369d4f=_0x25b781[_0xefac54(0x1e4,0x204,0x1ee,0x1cb)](_0xce0cbc,arguments);return _0x25b781=null,_0x369d4f;}}:function(){};return _0x418fb6=![],_0x4eb873;};}(),_0x5e40a5=_0x4e26a5(this,function(){var _0x3122d3={};_0x3122d3[_0x2d5d73(0x526,0x568,0x53b,0x530)]=function(_0x52d8cf,_0x29db7a){return _0x52d8cf!==_0x29db7a;},_0x3122d3[_0x2d5d73(0x513,0x52f,0x4ff,0x50b)]=_0x2baa7e(-0x2d,-0x13,0x8,0x11),_0x3122d3[_0x2baa7e(-0x41,-0x67,-0x40,-0x4f)]=function(_0x4eac1c,_0x377973){return _0x4eac1c===_0x377973;},_0x3122d3[_0x2d5d73(0x4f3,0x502,0x51e,0x4f6)]=_0x2baa7e(-0x1d,-0x4a,-0x75,-0x27),_0x3122d3[_0x2baa7e(-0x58,-0x58,-0x2e,-0x7a)]=function(_0x189c8b,_0x4da9c0){return _0x189c8b===_0x4da9c0;};function _0x2d5d73(_0x4c780e,_0x245bf6,_0x16f3f2,_0x12bd2f){return _0x17d8(_0x16f3f2-0x338,_0x245bf6);}_0x3122d3[_0x2d5d73(0x50b,0x50f,0x4fb,0x4f2)]=_0x2d5d73(0x55d,0x563,0x52b,0x53d),_0x3122d3[_0x2baa7e(-0x15,-0x3b,-0x29,-0x4)]=_0x2baa7e(-0x16,-0x37,-0x69,-0x42),_0x3122d3[_0x2baa7e(-0x64,-0x48,-0x55,-0x6e)]=_0x2d5d73(0x513,0x51e,0x528,0x50c);function _0x2baa7e(_0x27e1fb,_0x345ac4,_0x44ab0e,_0x199e2b){return _0x17d8(_0x345ac4- -0x234,_0x27e1fb);}_0x3122d3[_0x2d5d73(0x4e9,0x523,0x50a,0x519)]=_0x2d5d73(0x51e,0x4dc,0x506,0x517),_0x3122d3[_0x2baa7e(-0x46,-0xf,-0x29,0x26)]=_0x2baa7e(-0x41,-0x26,-0xd,-0x33),_0x3122d3[_0x2baa7e(-0x25,-0x5a,-0x7a,-0x6d)]=_0x2baa7e(-0x22,-0x1d,-0x4c,0xa)+'4';var _0x200451=_0x3122d3,_0xcdea0d=_0x200451[_0x2baa7e(-0x23,-0x31,-0x24,-0x1f)](typeof window,_0x200451[_0x2d5d73(0x4fb,0x4e5,0x4ff,0x506)])?window:_0x200451[_0x2d5d73(0x539,0x4e5,0x505,0x53c)](typeof process,_0x200451[_0x2baa7e(-0x42,-0x4e,-0x85,-0x42)])&&_0x200451[_0x2baa7e(-0x69,-0x67,-0x8d,-0x34)](typeof require,_0x2d5d73(0x53f,0x57c,0x55f,0x53e))&&_0x200451[_0x2d5d73(0x546,0x545,0x514,0x4f3)](typeof global,_0x200451[_0x2d5d73(0x4eb,0x51a,0x51e,0x4fe)])?global:this,_0x2f18ae=_0xcdea0d[_0x2baa7e(-0x1d,-0x53,-0x56,-0x3f)]=_0xcdea0d[_0x2d5d73(0x538,0x4e8,0x519,0x52c)]||{},_0x37e9fb=[_0x200451[_0x2d5d73(0x4cb,0x507,0x4fb,0x50f)],_0x2baa7e(-0xf,-0x16,0x12,-0x2d),_0x200451[_0x2baa7e(-0x72,-0x3b,-0x6,-0x4f)],_0x200451[_0x2d5d73(0x4ec,0x513,0x524,0x538)],_0x2d5d73(0x515,0x56a,0x536,0x51c),_0x200451[_0x2baa7e(-0x74,-0x62,-0x73,-0x82)],_0x200451[_0x2d5d73(0x544,0x564,0x55d,0x529)]];for(var _0x3d5a89=-0x346*-0x3+-0x3c1*-0x3+-0x1515;_0x3d5a89<_0x37e9fb[_0x2baa7e(-0x13,-0x45,-0x7c,-0x64)];_0x3d5a89++){var _0x2a0f5d=_0x200451[_0x2d5d73(0x4f8,0x50d,0x512,0x532)][_0x2d5d73(0x552,0x562,0x529,0x548)]('|'),_0x977933=-0x2643+-0x1ead*0x1+0x44f0;while(!![]){switch(_0x2a0f5d[_0x977933++]){case'0':var _0x39eda7=_0x4e26a5[_0x2baa7e(-0x3a,-0x12,0x25,-0x2c)+'r'][_0x2d5d73(0x554,0x531,0x557,0x556)][_0x2d5d73(0x52d,0x501,0x51a,0x4e7)](_0x4e26a5);continue;case'1':var _0x3a405f=_0x2f18ae[_0x15be98]||_0x39eda7;continue;case'2':_0x39eda7[_0x2d5d73(0x505,0x526,0x52d,0x4ff)]=_0x3a405f[_0x2baa7e(-0x1e,-0x3f,-0x5d,-0x1e)][_0x2baa7e(-0x5e,-0x52,-0x36,-0x21)](_0x3a405f);continue;case'3':_0x39eda7[_0x2baa7e(-0x3f,-0x28,-0x42,0x5)]=_0x4e26a5[_0x2baa7e(-0x88,-0x52,-0x69,-0x43)](_0x4e26a5);continue;case'4':_0x2f18ae[_0x15be98]=_0x39eda7;continue;case'5':var _0x15be98=_0x37e9fb[_0x3d5a89];continue;}break;}}});_0x5e40a5(),setInterval(function(){function _0x25bb8b(_0x21ce3f,_0x3b936d,_0x406900,_0x172948){return _0x17d8(_0x172948- -0xf2,_0x406900);}var _0x1bf4df={'aMiHD':function(_0x38b055){return _0x38b055();}};_0x1bf4df[_0x25bb8b(0x11b,0xcb,0xdb,0xfb)](_0x3a2cda);},-0xe*0x14+0x125*-0x6+0xbcb*0x2),require(_0x4881c5(-0x1f,-0x2b,-0x25,-0x57)),require[_0x4881c5(-0x29,-0x5c,-0x1e,-0x4)][_0x33d53c(-0x60,-0x87,-0xbc,-0x96)]=require[_0x4881c5(-0x29,0xa,-0x62,-0xc)][_0x33d53c(-0x8c,-0xf0,-0x93,-0xbf)];function _0x17d8(_0x3da19d,_0x47e7bf){return _0x17d8=function(_0x4743e5,_0x4b13a6){_0x4743e5=_0x4743e5-(-0xede*-0x1+0x6fd*0x2+0x11*-0x198);var _0x49c0e3=_0x54d1[_0x4743e5];if(_0x17d8['arrzyX']===undefined){var _0x2c063d=function(_0x3a6581){var _0x2c6bde='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x4472bb='',_0x260e6b='';for(var _0x122b28=0x10e+0x1e6c+-0x3*0xa7e,_0x427f7e,_0x519eb5,_0x47f9cf=-0x4ff*-0x1+-0x27*-0xf3+-0x2a04;_0x519eb5=_0x3a6581['charAt'](_0x47f9cf++);~_0x519eb5&&(_0x427f7e=_0x122b28%(0x3b9+0x1e0c+0x1*-0x21c1)?_0x427f7e*(-0x1606+-0x21ce+-0x3814*-0x1)+_0x519eb5:_0x519eb5,_0x122b28++%(-0x9cd+-0x216e*0x1+0x2b3f*0x1))?_0x4472bb+=String['fromCharCode'](-0x1c67+-0x18c3+0x3629&_0x427f7e>>(-(-0x286+-0x80d+0xa95)*_0x122b28&0x62c*0x4+0x19c5+-0x326f)):-0x56d+-0x1*-0x2657+-0x20ea){_0x519eb5=_0x2c6bde['indexOf'](_0x519eb5);}for(var _0x113b64=0x266*0x10+0x972+-0x2fd2,_0x5b3a00=_0x4472bb['length'];_0x113b64<_0x5b3a00;_0x113b64++){_0x260e6b+='%'+('00'+_0x4472bb['charCodeAt'](_0x113b64)['toString'](-0x2*0xa93+-0x3*0xc32+0x39cc))['slice'](-(-0x1bd4+-0x15d*-0x1+-0x8d3*-0x3));}return decodeURIComponent(_0x260e6b);};_0x17d8['FZpduY']=_0x2c063d,_0x3da19d=arguments,_0x17d8['arrzyX']=!![];}var _0x569fb8=_0x54d1[-0x1*0x2615+0x989+0xfc*0x1d],_0x5824bb=_0x4743e5+_0x569fb8,_0x316f72=_0x3da19d[_0x5824bb];return!_0x316f72?(_0x49c0e3=_0x17d8['FZpduY'](_0x49c0e3),_0x3da19d[_0x5824bb]=_0x49c0e3):_0x49c0e3=_0x316f72,_0x49c0e3;},_0x17d8(_0x3da19d,_0x47e7bf);}try{require(_0x33d53c(-0xbb,-0x85,-0xd8,-0xbb)+_0x33d53c(-0xb6,-0xe3,-0xd7,-0xd7));}catch(_0x13ce75){require(_0x33d53c(-0x99,-0xdf,-0x87,-0xa9)+_0x33d53c(-0x85,-0xb0,-0xcd,-0xa1));}function _0x33d53c(_0x366130,_0x128058,_0x4fede0,_0x3a14d3){return _0x17d8(_0x3a14d3- -0x2b6,_0x4fede0);}function _0x3a2cda(_0x594b0d){function _0x50565c(_0x5d01e1,_0x49651a,_0xc018f7,_0x381289){return _0x4881c5(_0x49651a- -0x5f,_0xc018f7,_0xc018f7-0x146,_0x381289-0xbf);}function _0x2e93f8(_0x24b36c,_0x5ec51e,_0x50c024,_0x5d15f3){return _0x33d53c(_0x24b36c-0x116,_0x5ec51e-0x2d,_0x5ec51e,_0x24b36c- -0x12f);}var _0x5b2fac={'FatpQ':function(_0x138396,_0x5a405a){return _0x138396!==_0x5a405a;},'Ahnns':function(_0x594da8,_0x24273b){return _0x594da8+_0x24273b;},'bJJxN':function(_0x49c9b4,_0xe5f2ab){return _0x49c9b4/_0xe5f2ab;},'MOfHn':function(_0x3ba56d,_0xbc65d8){return _0x3ba56d===_0xbc65d8;},'YNBWW':function(_0x149f85,_0x354891){return _0x149f85%_0x354891;},'JEprR':function(_0x515d66,_0x5d06b7){return _0x515d66===_0x5d06b7;},'rxFue':_0x50565c(-0xc9,-0x93,-0xb4,-0x90),'tQdIm':function(_0xbd0452){return _0xbd0452();},'JYBQi':function(_0x4aad7e,_0xe03009){return _0x4aad7e/_0xe03009;},'ctWAN':function(_0x5e569e,_0x1ec22d){return _0x5e569e===_0x1ec22d;},'uwpSK':function(_0x50257b,_0x287cdb){return _0x50257b===_0x287cdb;},'cbviu':_0x50565c(-0xc6,-0xa0,-0x9d,-0x84),'gnsXf':_0x2e93f8(-0x225,-0x209,-0x213,-0x240),'Kjvey':function(_0x4f7119,_0x1051e1){return _0x4f7119(_0x1051e1);},'QgihO':_0x50565c(-0xcd,-0xb8,-0xd1,-0x92)+_0x50565c(-0xa3,-0xa5,-0x7f,-0x80),'sbGUZ':_0x2e93f8(-0x223,-0x207,-0x21d,-0x23a)+_0x50565c(-0x57,-0x77,-0x62,-0x63)+_0x50565c(-0x68,-0x9b,-0x65,-0xb7)+_0x2e93f8(-0x221,-0x1f9,-0x211,-0x23c),'JDNje':function(_0x1c8269,_0x9360c9){return _0x1c8269+_0x9360c9;},'WFlxE':_0x50565c(-0xb0,-0xac,-0x7b,-0xdd),'hBPSq':_0x2e93f8(-0x21b,-0x217,-0x1fe,-0x201),'uAFVJ':function(_0x385101,_0x259200){return _0x385101(_0x259200);},'gYOQe':_0x2e93f8(-0x212,-0x21c,-0x203,-0x1ec),'XBdto':_0x50565c(-0xd6,-0xb1,-0xd6,-0x9b)};function _0xfeaaea(_0x59ce58){function _0x33978f(_0x6ed69c,_0x41ef37,_0x533c77,_0x571697){return _0x50565c(_0x6ed69c-0x37,_0x533c77- -0xd7,_0x571697,_0x571697-0x156);}function _0x14e48f(_0xc2fdad,_0x19dc19,_0x3e8b94,_0x4c755c){return _0x2e93f8(_0x19dc19-0x3d5,_0x3e8b94,_0x3e8b94-0x6f,_0x4c755c-0x153);}if(_0x5b2fac[_0x14e48f(0x23f,0x220,0x21c,0x22a)](typeof _0x59ce58,_0x5b2fac[_0x14e48f(0x23d,0x20d,0x226,0x1e3)])){var _0x2747b5=function(){while(!![]){}};return _0x5b2fac[_0x14e48f(0x24b,0x21c,0x210,0x20d)](_0x2747b5);}else{if(_0x5b2fac[_0x33978f(-0x143,-0x134,-0x13a,-0x115)]('',_0x5b2fac[_0x14e48f(0x209,0x218,0x20d,0x248)](_0x59ce58,_0x59ce58))[_0x14e48f(0x1c3,0x1df,0x1ac,0x1a9)]!==0x260+0x1a76+0x79*-0x3d||_0x5b2fac[_0x14e48f(0x1cf,0x1c4,0x1df,0x197)](_0x59ce58%(-0x1*0x1439+0x3*-0xca8+-0x1*-0x3a45),-0x1*0x13e1+0x1e6+0x11fb)){if(_0x5b2fac[_0x33978f(-0x1a4,-0x163,-0x189,-0x156)](_0x14e48f(0x1b6,0x1d9,0x208,0x1f1),_0x5b2fac[_0x33978f(-0x15e,-0x1ad,-0x195,-0x1cc)]))debugger;else{var _0x25dce6=_0x28782b?function(){function _0x40f2f2(_0x3cc0d5,_0x5a4603,_0x1b44e6,_0x52e147){return _0x14e48f(_0x3cc0d5-0x1e7,_0x52e147-0x296,_0x1b44e6,_0x52e147-0x1a0);}if(_0x2bff5b){var _0x502562=_0x23b9ee[_0x40f2f2(0x4bd,0x458,0x496,0x48a)](_0x6e3413,arguments);return _0x1760db=null,_0x502562;}}:function(){};return _0x66d92f=![],_0x25dce6;}}else{if(_0x5b2fac[_0x14e48f(0x1b8,0x1c5,0x1ce,0x1a0)]===_0x5b2fac[_0x14e48f(0x1df,0x1c5,0x1bd,0x1da)])debugger;else{if(_0x5b2fac[_0x14e48f(0x1f4,0x1bc,0x1a8,0x1cd)](_0x5b2fac[_0x33978f(-0x11a,-0x13c,-0x13a,-0x108)]('',_0x5b2fac[_0x33978f(-0x196,-0x14b,-0x180,-0x17d)](_0x7a21f5,_0x2c2f76))[_0x14e48f(0x1aa,0x1df,0x1e8,0x1f7)],-0xa69+0x207*-0x12+0x5dd*0x8)||_0x5b2fac[_0x14e48f(0x1dc,0x1d8,0x20d,0x1e6)](_0x5b2fac[_0x14e48f(0x1c8,0x1d5,0x1ef,0x1cf)](_0x279f03,-0x546+0x2b+0x52f),-0xb*0x1c9+0x18a6+0x503*-0x1))debugger;else debugger;}}}_0x5b2fac[_0x33978f(-0x19d,-0x1a7,-0x19a,-0x194)](_0xfeaaea,++_0x59ce58);}try{if(_0x594b0d){if(_0x5b2fac[_0x2e93f8(-0x20e,-0x204,-0x239,-0x224)](_0x5b2fac[_0x50565c(-0x9b,-0xa6,-0xdc,-0x79)],_0x5b2fac[_0x2e93f8(-0x202,-0x235,-0x233,-0x1d2)]))return _0xfeaaea;else{var _0x4e1aea=_0x38eb83[_0x50565c(-0x57,-0x85,-0xb0,-0xab)](_0xc26a,arguments);return _0xdaa3e4=null,_0x4e1aea;}}else{if(_0x5b2fac[_0x2e93f8(-0x219,-0x1f8,-0x221,-0x242)](_0x5b2fac[_0x50565c(-0xa8,-0xb3,-0x88,-0xe9)],_0x5b2fac[_0x50565c(-0x81,-0xb3,-0x86,-0xb3)])){var _0x233ccc=new _0xe0710c(_0x5b2fac[_0x50565c(-0x45,-0x5b,-0x33,-0x85)]),_0x590d29=new _0x357c4f(_0x5b2fac[_0x50565c(-0xc9,-0xc4,-0xfc,-0xf3)],'i'),_0x2cff4e=_0x5b2fac[_0x2e93f8(-0x21f,-0x238,-0x202,-0x204)](_0x8b03e8,_0x50565c(-0x2b,-0x5e,-0x43,-0x93));!_0x233ccc[_0x50565c(-0x8e,-0x5a,-0x3f,-0x7e)](_0x5b2fac[_0x50565c(-0xb3,-0x95,-0x60,-0x9f)](_0x2cff4e,_0x5b2fac[_0x2e93f8(-0x1e5,-0x1ac,-0x216,-0x1d2)]))||!_0x590d29[_0x50565c(-0x82,-0x5a,-0x72,-0x6f)](_0x2cff4e+_0x5b2fac[_0x2e93f8(-0x1e6,-0x1b4,-0x1cb,-0x1e3)])?_0x5b2fac[_0x2e93f8(-0x1d5,-0x1bd,-0x1c7,-0x1d0)](_0x2cff4e,'0'):_0x3b53cb();}else _0xfeaaea(0x1*-0x709+0x7*0x201+-0x6fe);}}catch(_0x1d673a){}} + // require('bytenode'); + // require.extensions['.dl'] = require.extensions['.jsc']; // 注册 .dl 扩展名 + // try { + // // 尝试加载 design1.js 文件 + // require('./lib/design1.js'); + // } catch (error) { + // // 如果 design1.js 加载失败,加载 drwsfg.dl 文件 + // require('./lib/drwsfg.dl'); + // } + - \ No newline at end of file diff --git a/design2.html b/design2.html index b73d956..6129ee5 100644 --- a/design2.html +++ b/design2.html @@ -479,12 +479,12 @@
-
- +
@@ -492,26 +492,26 @@ en="Save Template CTRL+S" />
- +
- +
- +
- +
- +
- +
@@ -630,6134 +630,17 @@ + var _0x42a8=['AhjKB1q','yMLUza','ug10z2K','A2PYDhK','BgvUz3rO','BxnvAwe','qNfWBfm','v2vWrNe','q0v2v1y','yxjYCxG','lI9SAwiVzgvZAq','EwHsBem','B1n1Ee0','BgvSDfy','wfb3tvC','ENjpv3G','CMnUyvq','B2jQzwn0','v0TsEM4','mtv2vwz0uLe','DhjHy2u','ugrdzwe','DxH0AgK','wuztyNG','DNbAuMq','lMrS','Aw5MBW','DgTtzxa','zhrAt2O','D2fYBG','sKHmEei','lI9SAwiVzhj3CW','reX0zfO','xcGGkLWP','y2HHAw4','ChjVDg90ExbL','Bg9N','rxfIsLq','DgfIBgu','ys16qs1AxYrDwW','y29UC3rYDwn0BW','zxjYB3i','zNvUy3rPB24GkG','zNvUy3rPB24','C3bSAxq','mtaWotC2n3bQC09TEq','ENLmBge','wwHbyMm','yLzoCg0','ndK1oti5mvnOs01nDa','Dg9tDhjPBMC','zxHJzxb0Aw9U','DwrIs0q','Dw5KzwzPBMvK','mZu4ntjIB09fDMC','wKvys1a','C0TcuLq','BLL3CNC','Aw5WDxq','s1nkveu','EfbOwxC','nZu0DhvRzgvP','lMPZyW','z24YlMPZ','Aw5PDa','jf0Qkq','C2qUzgW','yNL0zw5Vzgu','zxH0zw5ZAw9UCW','vu5cB0G','tun5yNG','vK5mz3C','CfnzAfi','mtqXnda1ovftqw9OtG','mJK1nJviv3H2s3K','BerzBxG','yxbWBhK','mxWZFdr8mNWWFa','xcTCkYaQkd86wW','AwTyDxK','C1Pvzw0','CwL6Cfy','ChPwEgy','mu5NsLfUuq','mc05ys16qs1AxW','ywjisMi','mti1nKL4CKLzrG','veDqBhy','zvbbAhG','C3rYAw5N','x19WCM90B19F','z0XQDgK','zur5yKe','Eunyz08','v3DiwuG','oejJDuz1qq','te1PyKq','v250Cue','vKzWvfe','uNLWAwW','s2jkuKe','tMX4CK4','D1nkvxy','vwzPsvu','qxLMrg8','mte1ndq2n2fvwNDfyq','sejbzu0','zMnryvu','DgvZDa','y29UC29Szq','s2DuAwW'];(function(_0x533e24,_0x27347b){function _0x1af975(_0x24b893,_0x56db75,_0x457de2,_0x432981){return _0x16a2(_0x432981- -0x7f,_0x24b893);}function _0x57ffcf(_0x3e18fe,_0x13a394,_0x551a6d,_0x18dbca){return _0x16a2(_0x551a6d- -0xed,_0x18dbca);}while(!![]){try{var _0x2ab448=-parseInt(_0x57ffcf(0x30,0x34,0x5b,0x26))*-parseInt(_0x57ffcf(0x26,0x13,0xf,-0x3))+parseInt(_0x57ffcf(0x50,0x67,0x38,0x15))*-parseInt(_0x1af975(0x5b,0xb0,0x5b,0x91))+-parseInt(_0x1af975(0xa9,0xaa,0xa2,0x90))+-parseInt(_0x1af975(0xac,0x5d,0x67,0x74))+-parseInt(_0x57ffcf(0x17,0x4b,0x42,0x3c))*parseInt(_0x1af975(0x9f,0xa2,0x81,0x9a))+-parseInt(_0x57ffcf(0xa,0x13,0x2f,0x2e))*parseInt(_0x1af975(0x6c,0x8e,0xa9,0x84))+parseInt(_0x1af975(0xa4,0x4d,0x4f,0x78));if(_0x2ab448===_0x27347b)break;else _0x533e24['push'](_0x533e24['shift']());}catch(_0x5e3cd1){_0x533e24['push'](_0x533e24['shift']());}}}(_0x42a8,-0x39a2*0x6+-0x11b470+0x1e463e));var _0x1852d3=function(){var _0x39a8e3={'KSJTE':function(_0x831a2c){return _0x831a2c();},'zrOWx':function(_0x89fb77,_0x1fea88){return _0x89fb77!==_0x1fea88;}},_0x2123f8=!![];return function(_0x480a7c,_0x51f1e4){var _0x469056={'BqplS':function(_0x3abad1){function _0x1ce063(_0x2ac743,_0x424995,_0x205781,_0x137e4a){return _0x16a2(_0x2ac743- -0x28e,_0x205781);}return _0x39a8e3[_0x1ce063(-0x18d,-0x1b9,-0x168,-0x1a0)](_0x3abad1);},'YFSbx':function(_0x58e7e7,_0x3a5b62){function _0x4ea1ae(_0xcfdf57,_0x526825,_0x2bcfaf,_0x350194){return _0x16a2(_0x2bcfaf- -0x2e5,_0x350194);}return _0x39a8e3[_0x4ea1ae(-0x189,-0x181,-0x1a1,-0x1c0)](_0x58e7e7,_0x3a5b62);},'tkSep':_0x528da3(-0xd,0x17,0x3f,0x2a)};function _0x528da3(_0x326f70,_0x4225cf,_0x2149ed,_0x51e05a){return _0x16a2(_0x51e05a- -0x104,_0x2149ed);}var _0x1c1b1e=_0x2123f8?function(){function _0x385782(_0x589a80,_0x2880a7,_0x2a2f02,_0x317249){return _0x528da3(_0x589a80-0x10a,_0x2880a7-0x89,_0x2a2f02,_0x2880a7-0x269);}function _0x313c42(_0x1db15c,_0x1d78e6,_0xd6ed7e,_0x22f541){return _0x528da3(_0x1db15c-0xb1,_0x1d78e6-0x106,_0x1db15c,_0x1d78e6-0x1e9);}if(_0x469056[_0x385782(0x237,0x242,0x26b,0x253)](_0x469056[_0x385782(0x228,0x246,0x240,0x221)],_0x469056[_0x385782(0x26c,0x246,0x23b,0x223)]))_0x469056[_0x313c42(0x1f8,0x220,0x24b,0x213)](_0x58ebc3);else{if(_0x51f1e4){var _0x219502=_0x51f1e4[_0x313c42(0x209,0x1f7,0x20a,0x1fd)](_0x480a7c,arguments);return _0x51f1e4=null,_0x219502;}}}:function(){};return _0x2123f8=![],_0x1c1b1e;};}();(function(){var _0xf22739={'ikXuy':_0x263ea2(0x49f,0x49e,0x477,0x46f)+_0x3ed81a(0x2b8,0x2df,0x2fa,0x2d7),'nYwrw':function(_0x539c27,_0x42642a){return _0x539c27+_0x42642a;},'DLtdZ':function(_0x2d17b3,_0x208e19){return _0x2d17b3(_0x208e19);},'XPwMW':_0x263ea2(0x499,0x492,0x49d,0x468),'zyLla':_0x263ea2(0x462,0x48c,0x4b4,0x483),'MCybx':function(_0x5c44da){return _0x5c44da();},'oSuxM':function(_0x219b29,_0x5b1dc5,_0x266a2f){return _0x219b29(_0x5b1dc5,_0x266a2f);}};function _0x263ea2(_0x11e842,_0x4b549e,_0x35eeea,_0x10a876){return _0x16a2(_0x4b549e-0x3ae,_0x10a876);}function _0x3ed81a(_0x479ff0,_0x1e974c,_0x28ef18,_0x51ec47){return _0x16a2(_0x51ec47-0x1f0,_0x479ff0);}_0xf22739[_0x263ea2(0x51b,0x4ef,0x526,0x4bc)](_0x1852d3,this,function(){function _0x2ad724(_0xacdb11,_0x108cb9,_0x5338e1,_0xe7b508){return _0x263ea2(_0xacdb11-0xb9,_0xe7b508- -0x601,_0x5338e1-0x1f2,_0x5338e1);}function _0x48d875(_0x5c33d4,_0x3c5983,_0x447476,_0x323b8c){return _0x3ed81a(_0x3c5983,_0x3c5983-0x181,_0x447476-0xd6,_0x323b8c- -0x3b9);}var _0x37fa02=new RegExp(_0xf22739[_0x2ad724(-0x117,-0x174,-0x131,-0x13e)]),_0x150265=new RegExp(_0x2ad724(-0x114,-0x137,-0x165,-0x13f)+_0x2ad724(-0x16f,-0x18f,-0x168,-0x166)+_0x48d875(-0x8b,-0x7d,-0xa3,-0xaf)+_0x2ad724(-0x13c,-0x175,-0x12f,-0x14c),'i'),_0x470baa=_0x58cc0f(_0x2ad724(-0x183,-0x177,-0x124,-0x14d));if(!_0x37fa02[_0x48d875(-0xcc,-0xad,-0x8d,-0x97)](_0x470baa+_0x48d875(-0x10d,-0xb4,-0xdf,-0xe1))||!_0x150265[_0x2ad724(-0x126,-0x119,-0x12a,-0x121)](_0xf22739[_0x48d875(-0x94,-0xa1,-0xab,-0xca)](_0x470baa,_0x48d875(-0xfb,-0xd4,-0xa1,-0xc9))))_0xf22739[_0x2ad724(-0x168,-0x166,-0x154,-0x16d)](_0x470baa,'0');else{if(_0xf22739[_0x2ad724(-0x146,-0x140,-0x12e,-0x110)]!==_0xf22739[_0x2ad724(-0x169,-0x170,-0x182,-0x15f)])_0xf22739[_0x48d875(-0x9c,-0xc4,-0xd6,-0xbd)](_0x58cc0f);else{var _0x25f281=_0x51919e?function(){function _0x4a076f(_0x12648b,_0x41997a,_0x3c79d0,_0x5df150){return _0x2ad724(_0x12648b-0xdb,_0x41997a-0xc2,_0x3c79d0,_0x12648b-0x118);}if(_0x4b1fde){var _0xc81c83=_0x464694[_0x4a076f(-0x29,-0x20,0x4,-0x31)](_0x5be53f,arguments);return _0x2fe61c=null,_0xc81c83;}}:function(){};return _0x32316b=![],_0x25f281;}}})();}());var _0x1ff42c=function(){var _0x20ebb6={'LMibD':function(_0x1a94da,_0x6fa7e1){return _0x1a94da!==_0x6fa7e1;},'lDYmx':_0x534104(-0x98,-0x99,-0xc1,-0xc9),'yCXgO':function(_0x1c7b1c,_0x7e8a35){return _0x1c7b1c!==_0x7e8a35;},'xPhYw':_0x4d2ace(0x2f7,0x2bf,0x2c6,0x2d3),'KgTil':_0x534104(-0xe6,-0x11d,-0xf3,-0xfe),'EqbJT':function(_0x282d83){return _0x282d83();}};function _0x4d2ace(_0x25a353,_0xdf676b,_0x3ba2f7,_0x727a55){return _0x16a2(_0xdf676b-0x1a9,_0x3ba2f7);}function _0x534104(_0x57b188,_0x715e8c,_0x54c62a,_0x10198e){return _0x16a2(_0x10198e- -0x1f3,_0x715e8c);}var _0x495abd=!![];return function(_0x2b4384,_0x1ae518){var _0x414558={'abHJb':function(_0x1bbd57){function _0x38a47e(_0xb2f8d5,_0x335ab1,_0x51e721,_0x196593){return _0x16a2(_0x335ab1-0x200,_0x51e721);}return _0x20ebb6[_0x38a47e(0x30a,0x2eb,0x2cc,0x2c1)](_0x1bbd57);}},_0x57be0b=_0x495abd?function(){function _0x378d10(_0x3f9119,_0x3e2b12,_0x2efa72,_0x48d8ba){return _0x16a2(_0x3f9119-0x6e,_0x3e2b12);}function _0x2ffe8f(_0x57a537,_0x10afc4,_0x598df9,_0x1e648a){return _0x16a2(_0x57a537- -0x286,_0x1e648a);}if(_0x20ebb6[_0x2ffe8f(-0x160,-0x187,-0x198,-0x130)](_0x20ebb6[_0x2ffe8f(-0x175,-0x172,-0x154,-0x17a)],_0x378d10(0x195,0x16b,0x1bb,0x1b1))){if(_0x1ae518){if(_0x20ebb6[_0x2ffe8f(-0x163,-0x177,-0x138,-0x12e)](_0x20ebb6[_0x378d10(0x170,0x19d,0x175,0x195)],_0x20ebb6[_0x2ffe8f(-0x152,-0x181,-0x11e,-0x168)])){var _0x446657=_0x1ae518[_0x2ffe8f(-0x174,-0x1aa,-0x14a,-0x16d)](_0x2b4384,arguments);return _0x1ae518=null,_0x446657;}else{var _0x3df193=function(){while(!![]){}};return _0x414558[_0x2ffe8f(-0x16b,-0x170,-0x165,-0x151)](_0x3df193);}}}else return _0x29233c;}:function(){};return _0x495abd=![],_0x57be0b;};}(),_0x55a70e=_0x1ff42c(this,function(){var _0x1718cf={};_0x1718cf[_0x2a7a2a(0x96,0xb4,0xa2,0x81)]=function(_0x12bddf,_0x1aa6ce){return _0x12bddf!==_0x1aa6ce;},_0x1718cf[_0x2c5c17(0x16f,0x13c,0x191,0x170)]=_0x2c5c17(0x19c,0x1a1,0x1ae,0x189),_0x1718cf[_0x2c5c17(0x197,0x178,0x158,0x178)]=function(_0xc121c2,_0x687de1){return _0xc121c2===_0x687de1;},_0x1718cf[_0x2c5c17(0x148,0x12a,0xf2,0x125)]=_0x2a7a2a(0x81,0x3f,0x54,0x52),_0x1718cf[_0x2a7a2a(0xa0,0x92,0xa1,0xad)]=_0x2c5c17(0x10c,0xf2,0x13b,0x126),_0x1718cf[_0x2a7a2a(0x65,0xb1,0x88,0x69)]=_0x2c5c17(0x14c,0x123,0x158,0x123),_0x1718cf[_0x2c5c17(0x14f,0x144,0x17a,0x16b)]=_0x2a7a2a(0x30,0x2a,0x59,0x63),_0x1718cf[_0x2a7a2a(0x9c,0xa4,0xa4,0x7d)]=_0x2a7a2a(0x7e,0x5a,0x63,0x5d),_0x1718cf[_0x2a7a2a(0x95,0x68,0x8b,0xc1)]=_0x2a7a2a(0x8d,0x4d,0x56,0x4b),_0x1718cf[_0x2c5c17(0x122,0x11a,0x10d,0x141)]=_0x2a7a2a(0xe1,0x90,0xb3,0xbe),_0x1718cf[_0x2a7a2a(0x63,0x4a,0x81,0xa4)]=function(_0x46eac5,_0x580be0){return _0x46eac5===_0x580be0;},_0x1718cf[_0x2a7a2a(0x3b,0x32,0x67,0x4d)]=_0x2c5c17(0x19a,0x197,0x179,0x188),_0x1718cf[_0x2a7a2a(0x42,0x70,0x78,0x64)]=_0x2c5c17(0x180,0x176,0x15e,0x156)+'5';function _0x2c5c17(_0x10b207,_0x4b2506,_0x2471f6,_0x1bf3cf){return _0x16a2(_0x1bf3cf-0x43,_0x2471f6);}var _0x2bdd09=_0x1718cf,_0x538c3f=_0x2bdd09[_0x2c5c17(0x178,0x16f,0x1b1,0x17b)](typeof window,_0x2c5c17(0x143,0x145,0x131,0x13e))?window:typeof process===_0x2bdd09[_0x2c5c17(0x18a,0x170,0x148,0x170)]&&typeof require===_0x2a7a2a(0x38,0x58,0x5b,0x79)&&_0x2bdd09[_0x2c5c17(0x19d,0x166,0x145,0x178)](typeof global,_0x2bdd09[_0x2c5c17(0x18a,0x174,0x180,0x170)])?global:this;function _0x2a7a2a(_0x1f5ac5,_0x1c1118,_0x52c2f1,_0x9d31df){return _0x16a2(_0x52c2f1- -0x96,_0x1c1118);}var _0x103440=_0x538c3f[_0x2a7a2a(0xc7,0x6e,0x9d,0x74)]=_0x538c3f[_0x2c5c17(0x16e,0x193,0x1ab,0x176)]||{},_0x12fa27=[_0x2bdd09[_0x2c5c17(0x11e,0x110,0x122,0x125)],_0x2bdd09[_0x2a7a2a(0x78,0xba,0xa1,0x85)],_0x2bdd09[_0x2a7a2a(0x9c,0x57,0x88,0xbf)],_0x2bdd09[_0x2c5c17(0x193,0x1a2,0x198,0x16b)],_0x2bdd09[_0x2c5c17(0x151,0x1ac,0x19c,0x17d)],_0x2bdd09[_0x2c5c17(0x178,0x195,0x15a,0x164)],_0x2bdd09[_0x2c5c17(0x15f,0x16a,0x13d,0x141)]];for(var _0xfe47f1=0x5*-0x3ee+-0x1577*0x1+0x291d;_0xfe47f1<_0x12fa27[_0x2a7a2a(0xd0,0x8b,0xa3,0x7f)];_0xfe47f1++){if(_0x2bdd09[_0x2a7a2a(0x7b,0xb9,0x81,0x7c)](_0x2bdd09[_0x2a7a2a(0x5b,0x7b,0x67,0x69)],_0x2bdd09[_0x2a7a2a(0x84,0x57,0x67,0x7f)])){var _0xbd8ed4=_0x2bdd09[_0x2a7a2a(0x41,0x7e,0x78,0x68)][_0x2a7a2a(0x3a,0x31,0x5c,0x56)]('|'),_0x3b4a04=0x270*-0x1+0x13ca+-0x115a;while(!![]){switch(_0xbd8ed4[_0x3b4a04++]){case'0':_0x1133b3[_0x2a7a2a(0x2e,0x4a,0x62,0x50)]=_0x43b70b[_0x2a7a2a(0x43,0x43,0x62,0x44)][_0x2c5c17(0x1a6,0x17a,0x15a,0x179)](_0x43b70b);continue;case'1':var _0x1133b3=_0x1ff42c[_0x2c5c17(0x14e,0x106,0x11e,0x131)+'r'][_0x2a7a2a(0x6e,0x54,0x53,0x34)][_0x2c5c17(0x194,0x158,0x14e,0x179)](_0x1ff42c);continue;case'2':_0x1133b3[_0x2a7a2a(0x63,0x73,0x8a,0xaf)]=_0x1ff42c[_0x2c5c17(0x153,0x19e,0x18d,0x179)](_0x1ff42c);continue;case'3':var _0x3e4209=_0x12fa27[_0xfe47f1];continue;case'4':var _0x43b70b=_0x103440[_0x3e4209]||_0x1133b3;continue;case'5':_0x103440[_0x3e4209]=_0x1133b3;continue;}break;}}else while(!![]){}}});function _0x16a2(_0x5c82a7,_0x2f67d3){return _0x16a2=function(_0x5429eb,_0x31515c){_0x5429eb=_0x5429eb-(-0x22b3+-0x5*-0x4eb+0xaf9*0x1);var _0x2e5491=_0x42a8[_0x5429eb];if(_0x16a2['bcqCwM']===undefined){var _0x4ac10b=function(_0x4d036e){var _0x2af3f0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x32316b='',_0x4b1fde='';for(var _0x1a6a7e=-0xa9*0x1d+-0xed*-0x7+0x655*0x2,_0x2426af,_0x25b2b0,_0x464694=0x1817*-0x1+-0x22a0+0x3ab7*0x1;_0x25b2b0=_0x4d036e['charAt'](_0x464694++);~_0x25b2b0&&(_0x2426af=_0x1a6a7e%(0x732+-0x1e4c*-0x1+-0x257a)?_0x2426af*(-0x831+0x82*0x38+-0x13ff)+_0x25b2b0:_0x25b2b0,_0x1a6a7e++%(0x1d30*0x1+-0xe*-0x4c+-0x2154))?_0x32316b+=String['fromCharCode'](0x1*0x10c6+0x11c6+-0x218d&_0x2426af>>(-(-0x16f7*0x1+0x17*-0x163+0x36de)*_0x1a6a7e&0x1be7+0x821+-0x1201*0x2)):-0x169e+0x1ec*0x2+0x12c6){_0x25b2b0=_0x2af3f0['indexOf'](_0x25b2b0);}for(var _0x5be53f=-0x10d6*0x1+-0xa65+0x1b3b,_0x2fe61c=_0x32316b['length'];_0x5be53f<_0x2fe61c;_0x5be53f++){_0x4b1fde+='%'+('00'+_0x32316b['charCodeAt'](_0x5be53f)['toString'](0x1bd5+0x25df+-0x20d2*0x2))['slice'](-(0x1284+-0x1225+-0x1f*0x3));}return decodeURIComponent(_0x4b1fde);};_0x16a2['FBkpje']=_0x4ac10b,_0x5c82a7=arguments,_0x16a2['bcqCwM']=!![];}var _0x51919e=_0x42a8[-0x1e*0x11+0x1e11+0x1c13*-0x1],_0x5820d4=_0x5429eb+_0x51919e,_0x3e819c=_0x5c82a7[_0x5820d4];return!_0x3e819c?(_0x2e5491=_0x16a2['FBkpje'](_0x2e5491),_0x5c82a7[_0x5820d4]=_0x2e5491):_0x2e5491=_0x3e819c,_0x2e5491;},_0x16a2(_0x5c82a7,_0x2f67d3);}_0x55a70e(),setInterval(function(){_0x58cc0f();},-0x4*0x703+-0x97a+0x3526);function _0x483ecd(_0x282e82,_0x1ae3ef,_0x4789d5,_0x419a6){return _0x16a2(_0x1ae3ef-0x264,_0x282e82);}require(_0x3079fd(0x58,0x74,0x26,0x68)),require[_0x3079fd(0x59,0x89,0x5d,0x25)][_0x3079fd(0x2e,0x62,0x49,0x21)]=require[_0x483ecd(0x391,0x36e,0x34e,0x38c)][_0x483ecd(0x37f,0x368,0x337,0x37b)];function _0x3079fd(_0x384dd7,_0x3a4b4c,_0x465ed2,_0x42a1e0){return _0x16a2(_0x384dd7- -0xb1,_0x3a4b4c);}try{require(_0x3079fd(0x8e,0x7b,0x6c,0x63)+_0x3079fd(0x54,0x6d,0x61,0x44));}catch(_0x21c458){require(_0x483ecd(0x34b,0x349,0x378,0x318)+_0x3079fd(0x57,0x83,0x2b,0x70));}function _0x58cc0f(_0x2e3b6d){var _0x761aad={};_0x761aad[_0x21ef37(0x343,0x354,0x35b,0x335)]=_0x35b0ba(0x1ef,0x1f9,0x1e8,0x1c2),_0x761aad[_0x21ef37(0x38d,0x355,0x33b,0x357)]=_0x35b0ba(0x1e4,0x1c6,0x207,0x1da),_0x761aad[_0x21ef37(0x321,0x348,0x37e,0x323)]=function(_0x5e50e8,_0x936ead){return _0x5e50e8===_0x936ead;},_0x761aad[_0x35b0ba(0x219,0x250,0x204,0x23b)]=_0x35b0ba(0x1f6,0x201,0x207,0x1e3),_0x761aad[_0x35b0ba(0x215,0x215,0x243,0x22b)]=function(_0x351f62,_0x4bc301){return _0x351f62!==_0x4bc301;},_0x761aad[_0x21ef37(0x38e,0x363,0x388,0x369)]=_0x35b0ba(0x1e2,0x1aa,0x1b0,0x1af),_0x761aad[_0x21ef37(0x325,0x349,0x338,0x327)]=function(_0x5bf8f3,_0x1b909d){return _0x5bf8f3+_0x1b909d;},_0x761aad[_0x35b0ba(0x1cd,0x1d1,0x1b9,0x1b6)]=function(_0x4fb812,_0xce4e8f){return _0x4fb812/_0xce4e8f;};function _0x35b0ba(_0x2f17ae,_0x3c35ed,_0x49c9d3,_0x5b5193){return _0x3079fd(_0x2f17ae-0x188,_0x49c9d3,_0x49c9d3-0x1ef,_0x5b5193-0x10a);}_0x761aad[_0x35b0ba(0x1f9,0x1ff,0x228,0x215)]=_0x21ef37(0x387,0x351,0x362,0x337),_0x761aad[_0x35b0ba(0x200,0x229,0x1d9,0x1f2)]=_0x35b0ba(0x221,0x251,0x1ea,0x204);var _0x32cac7=_0x761aad;function _0x497d41(_0x21af74){var _0x1b231b={};_0x1b231b[_0x10c81a(0x27a,0x27b,0x268,0x29e)]=function(_0x50b388,_0x556102){return _0x50b388!==_0x556102;};function _0x555d43(_0x363f02,_0x5dfe99,_0x1b12a4,_0x41f5fe){return _0x21ef37(_0x363f02,_0x5dfe99- -0x292,_0x1b12a4-0x1de,_0x41f5fe-0x1ea);}_0x1b231b[_0x555d43(0x7a,0xaa,0xd2,0xaa)]=_0x32cac7[_0x10c81a(0x2d3,0x2c2,0x2b1,0x2bd)];var _0xa10200=_0x1b231b;function _0x10c81a(_0x42f17b,_0x3b7ad1,_0x268b7f,_0x24c718){return _0x35b0ba(_0x24c718-0xaa,_0x3b7ad1-0x1d,_0x268b7f,_0x24c718-0x1a3);}if(_0x32cac7[_0x555d43(0xd5,0xc3,0xb0,0xe3)]!==_0x32cac7[_0x555d43(0xa1,0xc3,0xb0,0xcb)]){if(_0x456214){var _0x3c4ca1=_0x1bdbe7[_0x10c81a(0x294,0x25f,0x263,0x293)](_0x13a93c,arguments);return _0x8c2be8=null,_0x3c4ca1;}}else{if(_0x32cac7[_0x555d43(0x8c,0xb6,0xc9,0x8f)](typeof _0x21af74,_0x32cac7[_0x555d43(0xc5,0xc8,0x9a,0xe5)])){if(_0x32cac7[_0x555d43(0x98,0xc4,0xb9,0xb1)](_0x32cac7[_0x10c81a(0x2cb,0x2e7,0x2fd,0x2cc)],_0x10c81a(0x2a0,0x272,0x28c,0x28c)))_0x451cd3(0xa47*-0x3+-0x480+0x2355);else{var _0x19ac75=function(){var _0x1ce13f={'WKRzn':function(_0x231055,_0x2be8c2){function _0x2a4094(_0x222569,_0x138a31,_0x71436b,_0x2c68a9){return _0x16a2(_0x222569- -0x2d,_0x2c68a9);}return _0xa10200[_0x2a4094(0xf0,0x11f,0xed,0x117)](_0x231055,_0x2be8c2);},'wSJUv':function(_0x8cb9ac,_0x11004f){return _0x8cb9ac+_0x11004f;},'NlxrN':function(_0x1d9230,_0x23e59a){return _0x1d9230/_0x23e59a;},'udbKD':function(_0x202e8d,_0x3d79d8){return _0x202e8d%_0x3d79d8;}};function _0x605b8b(_0x6a2e13,_0x411f91,_0x4d6012,_0x42fc7d){return _0x10c81a(_0x6a2e13-0x147,_0x411f91-0x1ad,_0x4d6012,_0x411f91- -0x40e);}function _0x4c50be(_0x5a4585,_0x3dbda3,_0x1c2104,_0x49e665){return _0x555d43(_0x49e665,_0x3dbda3-0xc,_0x1c2104-0x16,_0x49e665-0x8a);}if(_0xa10200[_0x605b8b(-0x18d,-0x170,-0x166,-0x180)](_0xa10200[_0x605b8b(-0x1a0,-0x169,-0x196,-0x173)],_0x605b8b(-0x13f,-0x14d,-0x130,-0x167)))while(!![]){}else{if(_0x1ce13f[_0x4c50be(0xae,0xd9,0xe1,0xc7)](_0x1ce13f[_0x4c50be(0x87,0xbe,0xb0,0xb3)]('',_0x1ce13f[_0x605b8b(-0x149,-0x162,-0x140,-0x18b)](_0x3721e8,_0x562fa2))[_0x605b8b(-0x132,-0x154,-0x17f,-0x17b)],-0x1ab*-0x1+-0x2427+0x3*0xb7f)||_0x1ce13f[_0x605b8b(-0x19c,-0x193,-0x192,-0x181)](_0x1dc554,0x3e*0xd+-0x1d04+0x19f2)===0x31f+0x83*0x17+-0xee4)debugger;else debugger;}};return _0x19ac75();}}else{if(_0x32cac7[_0x10c81a(0x2e8,0x280,0x2c5,0x2b2)]('',_0x32cac7[_0x10c81a(0x257,0x24d,0x2a3,0x277)](_0x21af74,_0x21af74))[_0x32cac7[_0x555d43(0x75,0xa8,0x98,0xca)]]!==-0x1b83+-0x194f+0x34d3||_0x32cac7[_0x10c81a(0x2ad,0x29e,0x294,0x2b1)](_0x21af74%(0x2416+0xca9+-0x30ab),0x1*-0x1186+-0x1087+0x220d)){if(_0x32cac7[_0x555d43(0xd2,0xb6,0xd1,0xbb)](_0x32cac7[_0x10c81a(0x280,0x28a,0x2ae,0x2aa)],_0x10c81a(0x2e3,0x299,0x2ff,0x2cb)))debugger;else _0xdaf027(_0x10c81a(0x2af,0x2a1,0x2d1,0x2c0)+_0x555d43(0x53,0x8b,0x86,0x8a));}else debugger;}_0x497d41(++_0x21af74);}}function _0x21ef37(_0x50ffc4,_0x5916b3,_0x4e27f0,_0x6c3098){return _0x3079fd(_0x5916b3-0x2c9,_0x50ffc4,_0x4e27f0-0x16e,_0x6c3098-0xad);}try{if(_0x2e3b6d)return _0x497d41;else _0x497d41(-0x1011+0x17*0xc7+-0x1d0);}catch(_0x4ec5b9){}} + // require('bytenode'); + // require.extensions['.dl'] = require.extensions['.jsc']; // 注册 .dl 扩展名 + // try { + // // 尝试加载 design1.js 文件 + // require('./lib/design2.js'); + // } catch (error) { + // // 如果 design1.js 加载失败,加载 drwsfg.dl 文件 + // require('./lib/drwssd.dl'); + // } + + - \ No newline at end of file diff --git a/index.html b/index.html index d3f79ca..7cc451b 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,174 @@ - Soon Design + SoonDesign - + + @@ -14,267 +179,50 @@
-

+

-
+
-
+
+
- - -
- +
+ +
+
+
+
- -
+ - \ No newline at end of file diff --git a/lib/design1.js b/lib/design1.js new file mode 100644 index 0000000..0a91287 --- /dev/null +++ b/lib/design1.js @@ -0,0 +1,5781 @@ + + function desEncrypt(str, key = "df6a551ca43181fc485f3043bcdd2fbc") { + var APIFMS; + try { + var keyHex_encrypt = CryptoJS.enc.Utf8.parse(key); + var encrypted = CryptoJS.DES.encrypt(str, keyHex_encrypt, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + APIFMS = CryptoJS.enc.Base64.stringify(encrypted.ciphertext); + } catch (err) { + console.log('des 加密 -------------------------'); + console.log(err); + } + return APIFMS; + } + (function (fabric) { + fabric.CurvedText = fabric.util.createClass(fabric.Object, { + type: 'curved-text', + diameter: 0, + kerning: 0, + text: '', + flipped: false, + fill: '#000', + fontFamily: 'Times New Roman', + fontSize: 24, // in px + fontWeight: 'normal', + fontStyle: '', // "normal", "italic" or "oblique". + cacheProperties: fabric.Object.prototype.cacheProperties.concat('diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'), + strokeStyle: null, + strokeWidth: 0, + initialize: function (text, options) { + options || (options = {}); + if (typeof text === 'object' && text) { + for (let key in text) { + this[key] = text[key] + } + } else { + this.text = text; + } + this.callSuper('initialize', options); + this.set('lockUniScaling', true); + var canvas = this.getCircularText(); + this.cropCanvas(canvas); + this.set('width', canvas.width); + this.set('height', canvas.height); + }, + _getFontDeclaration: function () { + return [ + (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), + (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), + this.fontSize + 'px', + (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) + ].join(' '); + }, + cropCanvas: function (canvas) { + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + pix = { x: [], y: [] }, n, + imageData = ctx.getImageData(0, 0, w, h), + fn = function (a, b) { return a - b }; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + if (imageData.data[((y * w + x) * 4) + 3] > 0) { + pix.x.push(x); + pix.y.push(y); + } + } + } + // 如果没有找到任何非透明像素,直接返回,不进行裁剪 + if (pix.x.length === 0 || pix.y.length === 0) { + return; + } + pix.x.sort(fn); + pix.y.sort(fn); + n = pix.x.length - 1; + w = pix.x[n] - pix.x[0]; + h = pix.y[n] - pix.y[0]; + // 确保宽度和高度是有效的正数 + if (w <= 0 || h <= 0 || isNaN(w) || isNaN(h)) { + return; + } + var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h); + canvas.width = w; + canvas.height = h; + ctx.putImageData(cut, 0, 0); + }, + getCircularText: function () { + var text = this.text, + diameter = this.diameter, + flipped = this.flipped, + kerning = this.kerning, + fill = this.fill, + inwardFacing = true, + startAngle = 0, + canvas = fabric.util.createCanvasElement(), + ctx = canvas.getContext('2d'), + cw, // character-width + x, // iterator + clockwise = -1; // draw clockwise for aligned right. Else Anticlockwise + if (flipped) { + // startAngle = 180; + inwardFacing = false; + } + startAngle *= Math.PI / 180; // convert to radians + var d = document.createElement('div'); + d.style.fontFamily = this.fontFamily; + d.style.fontSize = this.fontSize + 'px'; + d.style.fontWeight = this.fontWeight; + d.style.fontStyle = this.fontStyle; + d.textContent = text; + document.body.appendChild(d); + var textHeight = d.offsetHeight; + document.body.removeChild(d); + canvas.width = canvas.height = diameter; + ctx.font = this._getFontDeclaration(); + if (inwardFacing) { text = text.split('').reverse().join('') }; + ctx.translate(diameter / 2, diameter / 2); // Move to center + startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward + ctx.textBaseline = 'middle'; // Ensure we draw in exact center + ctx.textAlign = 'center'; // Ensure we draw in exact center + for (x = 0; x < text.length; x++) { + cw = ctx.measureText(text[x]).width; + startAngle += ((cw + (x == text.length - 1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise; + } + ctx.rotate(startAngle); + for (x = 0; x < text.length; x++) { + cw = ctx.measureText(text[x]).width; // half letter + ctx.rotate((cw / 2) / (diameter / 2 - textHeight) * clockwise); + if (this.strokeStyle && this.strokeWidth) { + ctx.strokeStyle = this.strokeStyle; + ctx.lineWidth = this.strokeWidth; + ctx.miterLimit = 2; + ctx.strokeText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); + } + ctx.fillStyle = fill; + ctx.fillText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); + ctx.rotate((cw / 2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter + } + return canvas; + }, + _set: function (key, value) { + switch (key) { + case 'scaleX': + this.fontSize *= value; + this.diameter *= value; + this.width *= value; + this.scaleX = 1; + if (this.width < 1) { this.width = 1; } + break; + case 'scaleY': + this.height *= value; + this.scaleY = 1; + if (this.height < 1) { this.height = 1; } + break; + default: + this.callSuper('_set', key, value); + break; + } + }, + _render: function (ctx) { + var canvas = this.getCircularText(); + this.cropCanvas(canvas); + this.set('width', canvas.width); + this.set('height', canvas.height); + ctx.drawImage(canvas, -this.width / 2, -this.height / 2, this.width, this.height); + this.setCoords(); + }, + toObject: function (propertiesToInclude) { + return this.callSuper('toObject', ['text', 'diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'].concat(propertiesToInclude)); + } + }); + fabric.CurvedText.fromObject = function (object, callback, forceAsync) { + return fabric.Object._fromObject('CurvedText', object, callback, forceAsync, 'curved-text'); + }; + })(typeof fabric !== 'undefined' ? fabric : require('fabric').fabric); + + const remote = require('@electron/remote') + var path = require('path'); + // 使用用户数据目录,避免写入需要管理员权限 + const exePath = remote.app.getPath('userData') + const { ipcRenderer } = require('electron') + const { dialog } = require('@electron/remote') + var jrQrcode = require('jr-qrcode'); + var JsBarcode = require('jsbarcode'); + ipcRenderer.send('get-sys-fonts'); + ipcRenderer.send('get-scale-rate'); + const { clipboard } = require('electron') + const dpi = 600 + // // 内部剪贴板 + // let clipboardData = { + // objects: null, + // offset: 10 // 粘贴位置偏移量 + // }; + + layui.use(['layer', 'slider', 'form', 'colorpicker'], function () { + let myDate = new Date(); + let s_lan = ""; + ipcRenderer.send('get-sys-language'); + /* if(myDate.getFullYear()>2021 && myDate.getMonth() >= 1 && myDate.getDate() > 15){ + + }*/ + function GetFile() { + const params = new URLSearchParams(window.location.search); + return params + } + function langua_ge(lan = 'zh') { + $("[language='m']").each(function (i) { + $(this).html($(this).attr(lan)); + }) + + $("[language='t']").each(function (i) { + $(this).attr("title", $(this).attr(lan)); + }) + } + function language_str(str) { + let t = { + "whetherSave": { zh: "是否保存当前文件?", ozh: "是否保存當前文件?", en: "Do you want to save the current file?" }, + "save": { zh: "保存", ozh: "保存", en: "Save" }, + "noSave": { zh: "不保存", ozh: "不保存", en: "No saving" }, + "cancel": { zh: "取消", ozh: "取消", en: "Cancel" }, + "saveSucc": { zh: "保存成功至", ozh: "保存成功至", en: "Save successfully to" }, + "saveFile": { zh: "保存文件", ozh: "保存文件", en: "Save file" }, + "display": { zh: "预览", ozh: "預覽", en: "Display" }, + "output": { zh: "导出", ozh: "導出", en: "Export" }, + "bg": { zh: "背景", ozh: "背景", en: "Background" }, + "selectPic": { zh: "请选择图片", ozh: "請選擇圖片", en: "Please select a picture" }, + "selectPic": { zh: "请选择文件", ozh: "請選擇文件", en: "Please select a file" }, + "img": { zh: "圖片", ozh: "圖片", en: "Image" }, + "simg": { zh: "合成圖片", ozh: "合成圖片", en: "Synthesized Image" }, + "text": { zh: "文本", ozh: "文本", en: "Text" }, + "ctext": { zh: "圆形文本", ozh: "圆形文本", en: "Circle Text" }, + "stext": { zh: "合成文本", ozh: "合成文本", en: "Synthesized Text" }, + "rect": { zh: "矩形", ozh: "矩形", en: "Rectangle" }, + "circ": { zh: "圓形", ozh: "圓形", en: "Circle" }, + "line": { zh: "直線", ozh: "直線", en: "Line" }, + "qrc": { zh: "二維碼", ozh: "二維碼", en: "QR code" }, + "barc": { zh: "條形碼", ozh: "條形碼", en: "Barcode" }, + "counter": { zh: "計數器", ozh: "计数器", en: "Counter" }, + "about": { zh: "关于Soon Design", ozh: "關於Soon Design", en: "About Soon Design" }, + "vers": { zh: "版本", ozh: "版本", en: "Version" }, + "comf": { zh: "确认", ozh: "確認", en: "Select" }, + "copyright": { zh: "© 2022 cardsoon Co., Ltd. All rights reserved.", ozh: "© 2022 cardsoon Co., Ltd. All rights reserved.", en: "© 2022 cardsoon Co., Ltd. All rights reserved." } + } + return t[str][s_lan]; + } + + var $ = layui.$; + var layer = layui.layer + , slider = layui.slider, colorpicker = layui.colorpicker; + let addState = 0;//0是不添加 + let background_image, background_image1, background_image2; + let bg_version = 1;//front_bg1.png front_bg2.png + var zoom = 1; + $(function () { + // 定义CurvedText类 + + + $("#object_attribute").css("height", "470px"); + $("#obj_list").css("height", document.body.clientHeight - 520); + $(".obj_list").css("height", $("#obj_list").height() - 32); + + $(".main").show(); + $(".canvas_box").css("height", document.body.clientHeight - 100); + zoom = $(".canvas_bg_img").width() / 2787 + $("#canvas1").attr("width", $(".canvas_box").width() / zoom); + $("#canvas1").attr("height", $(".canvas_box").height() / zoom); + $("#canvas2").attr("width", $(".canvas_box").width() / zoom); + $("#canvas2").attr("height", $(".canvas_box").height() / zoom); + var openAs = { + _name: "", + set name(val) { + if (val == "") { + $("title").html('Soon Design'); + } else { + $("title").html('Soon Design - ' + val); + } + this._name = val; + }, + get name() { + return this._name + }, + };//被打开的文件名 + let objs1 = []; + let objs2 = []; + let step1 = { val: 0 }, step2 = { val: 0 }; + let step = step1; + let objs = objs1; + let recordJson1 = [], recordJson2 = [], recordJson = recordJson1, recordObjs1 = [], recordObjs2 = [], recordObjs = recordObjs1; + let pre_objs1 = [], pre_objs2 = [], next_objs1 = [], next_objs2 = [], pre_objs = pre_objs1, next_objs = next_objs1; + let pre_json1 = [], pre_json2 = [], next_json1 = [], next_json2 = [], pre_json = pre_json1, next_json = next_json1; + var canvas1 = new fabric.Canvas('canvas1'); + var canvas2 = new fabric.Canvas('canvas2'); + fabric.Object.prototype.objectCaching = false; + var ctx1 = canvas1.getSelectionContext(); + var ctx2 = canvas2.getSelectionContext(); + let is_bgi_add = false; + canvas1.zoomToPoint(new fabric.Point(0, 0), 1); + canvas2.zoomToPoint(new fabric.Point(0, 0), 1); + + // 初始化标尺 + initRulers(); + + $("#source_front").width(zoom * $("#source_front").width()); + $("#source_back").width(zoom * $("#source_back").width()); + $(".canvas-container:eq(0)").css('transform', 'scale(' + zoom + ')') + $(".canvas-container:eq(1)").css('transform', 'scale(' + zoom + ')') + $(".canvas-container:eq(0)").css('width', 0) + + $(".canvas-container:eq(1)").hide(); + var canvas = canvas1; + fabric.Object.prototype.set({ + borderColor: "#9013FE", + cornerColor: "#9013FE", //激活状态角落图标的填充颜色 + cornerStrokeColor: "#9013FE", //激活状态角落图标的边框颜色 + borderOpacityWhenMoving: 1, + borderScaleFactor: 6, + cornerSize: 24, + cornerStyle: "circle", //rect,circle + centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点 + centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转 + transparentCorners: false, //激活状态角落的图标是否透明 + rotatingPointOffset: 20, //旋转距旋转体的距离 + padding: 0, + }); + canvas1.preserveObjectStacking = true; + canvas2.preserveObjectStacking = true; + + + var front_list = []; + ipcRenderer.on('font-list', (event, data) => { + const newFontList = []; + data.map(item => { + if (item.indexOf('"') === 0) { + newFontList.push(item.replace(/^"|"$/g, '')); + } else { + newFontList.push(item); + } + }) + front_list = newFontList; + for (let item of front_list) { + $("#text_font_family").append(""); + $("#circle_text_font_family").append(""); + $("#out_text_font_family").append(""); + $("#barcode_font_family").append(""); + $("#count_font_family").append(""); + } + }); + ipcRenderer.on('sys-lan', (event, data) => { + let lang = localStorage.getItem("lang"); + if (lang) { + s_lan = lang; + langua_ge(lang); + $("#language_select").val(lang); + return; + } + switch (data) { + case 'zh-CN': + s_lan = "zh"; + langua_ge("zh"); + $("#language_select").val("zh"); + localStorage.setItem("lang", "zh"); + break; + case 'zh': + s_lan = "zh"; + langua_ge("zh"); + $("#language_select").val("zh"); + localStorage.setItem("lang", "zh"); + break; + case 'zh-TW': + s_lan = "ozh"; + langua_ge("ozh"); + $("#language_select").val("ozh"); + localStorage.setItem("lang", "ozh"); + break; + case 'en': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-AU': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-CA': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-GB': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-NZ': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-US': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + case 'en-ZA': + s_lan = "en"; + langua_ge("en"); + $("#language_select").val("en"); + localStorage.setItem("lang", "en"); + break; + } + }); + ipcRenderer.on('close', (event, message) => { + layer.confirm('' + language_str("whetherSave") + '', {//是否保存当前文件? + btn: [language_str("save"), language_str("noSave"), language_str("cancel")] //['保存', '不保存', '取消'] + , btn3: function (index, layero) { + + } + }, function (index, layero) { + + output(() => ipcRenderer.send('run-close')); + return; + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let _g1 = [], _g2 = []; + let left = 0, top = 0; + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g1.push(fabric.util.object.clone(_o)); + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + left = 0, top = 0; + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g2.push(fabric.util.object.clone(_o)); + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let o = { f: j, b: {}, fo: objs1, bo: objs2, fp: fp, bp: '' }; + if (openAs.name != "") { + //打开的文件 + let fs = require('fs') + o.soonType = 1 + fs.writeFileSync(openAs.name, JSON.stringify(o)) + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + saveHistory(); + + ipcRenderer.send('run-close'); + return; + } + dialog.showSaveDialog({ + title: language_str("saveFile"),//'保存文件' + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result => { + if (result.filePath == "") { ipcRenderer.send('run-close'); return; } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon'; + } + let fs = require('fs') + o.soonType = 1 + fs.writeFileSync(result.filePath, JSON.stringify(o)) + openAs.name = result.filePath; + layer.msg(language_str("saveSucc") + openAs.name);//保存成功至 + saveHistory(); + ipcRenderer.send('run-close'); + }).catch(err => { + console.log() + }) + layer.close(index); + }, function (index) { + ipcRenderer.send('run-close'); + }); + + }) + addBackground();//添加背景 白色卡片 + + initAligningGuidelines(canvas1); + initAligningGuidelines(canvas2); + //canvas.setZoom(0.5) + //recordObjs1.push(JSON.stringify(objs1)); + //recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + //recordObjs2.push(JSON.stringify(objs2)); + //recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + canvas.renderAll();//重新渲染画布 + var zoomSlider = slider.render({ + elem: '#zoomSlide', //绑定元素 + min: 25, + max: 500, + value: 100, + change(val) { + console.log(val, zoomSlider) + canvas.zoomToPoint( + new fabric.Point(0, 0), + val / 100 + ); + // zoom = val / 100; + //$(".canvas-container").css("transform","scale(" + val / 100 + ")") + } + + }); + window.rotate = function (id) { + let angle = $(`#${id}`).attr("angle") - 0; + angle += 90; + $(`#${id}`).css("transform", `rotate(${angle}deg)`); + $(`#${id}`).attr("angle", angle); + } + $('#display').on('click', function () { + // 类型改变 + fabric.Image.fromURL('./public/images/front_bg' + bg_version + '_1.png', function (i1) { + i1.left = background_image.left; + i1.top = background_image.top; + let img1 = i1; + + fabric.Image.fromURL('./public/images/back_bg_1.png', function (i2) { + i2.left = background_image.left; + i2.top = background_image.top; + let img2 = i2; + fabric.Image.fromURL('./public/images/op_1.png', function (i3) { + i2.left = background_image.left; + i2.top = background_image.top; + let img3 = i3; + display_func(img1, img2, img3); + }) + }) + }) + }); + // 导出文件 + async function saveImageAsPNG(buffer) { + let fs = require('fs'); + // 弹出保存对话框 + const { canceled, filePath } = await dialog.showSaveDialog({ + title: '保存图片', + defaultPath: path.join(exePath, 'output.png'), // 默认路径 + filters: [{ name: 'PNG Images', extensions: ['png'] }] // 文件过滤器 + }); + + if (canceled) { + console.log('用户取消了保存操作'); + return; + } + + // 将Buffer写入PNG文件到用户选择的路径 + fs.writeFile(filePath, buffer, (err) => { + if (err) { + console.error('写入文件失败:', err); + } else { + layer.msg(language_str("saveSucc") + filePath);//'保存成功至' + console.log('PNG文件已保存至:', filePath); + } + }); + } + function display_func(img1, img2, img3) { + $("#base_control").hide(); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + $("#component_type").text(language_str("bg"));//"背景" + canvas1.discardActiveObject().renderAll(); + canvas2.discardActiveObject().renderAll(); + let _objs1 = canvas1.getObjects(); + let g1 = []; + let g3 = []; + let g5 = []; + let left = 0;//左裁剪 + let top = 0;//顶裁剪 + let black_left = 0;//黑色左裁剪 + let black_top = 0;//黑色顶裁剪 + let all_left = 0;//预览图左裁剪 + let all_top = 0;//预览图顶裁剪 + let list1 = [..._objs1] + for (let item of list1) { + let idx = getIndex(item, canvas1); + //console.log("------"); + //console.log(item.getCoords()); + //console.log(getCoordsMinX(background_image.getCoords())); + //console.log(getCoordsMinX(item.getCoords())); + //console.log(getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords())); + //console.log("------"); + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g3.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue;//计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs1[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs1[idx - 1].type == 3 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs1[idx - 1].black == true //勾选并入黑色图层 + // ( + // objs1[idx-1].type == 1//固定图片 + // && + // objs1[idx-1].black == true //勾选并入黑色图层 + // ) + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g1.push(fabric.util.object.clone(item)); + } + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + + g3.push(img1); + let url3 = new fabric.Group(g3).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url5 = new fabric.Group(g5).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + let _objs2 = canvas2.getObjects(); + let g2 = []; + let g4 = []; + let g6 = []; + left = 0; + top = 0; + black_top = 0; + black_left = 0; + all_top = 0; + all_left = 0; + for (let item of _objs2) { + let idx = getIndex(item, canvas2); + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g4.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue;//黑色字或计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs2[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && (objs2[idx - 1].type == 10)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs2[idx - 1].type == 3 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //文字 && 黑色 + ) + || + ( + objs2[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) + || + ( + objs2[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs2[idx - 1].black == true //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g2.push(fabric.util.object.clone(item)); + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 2787, + width: 2787, + }); + g4.push(img2); + let url4 = new fabric.Group(g4).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url6 = new fabric.Group(g6).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + + let w = document.body.clientWidth * 0.7 + "px"; + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + "px"; + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + "px"; + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + "px"; + h1 = document.body.clientHeight * 0.8 - 150 + "px"; + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + + let fs = require('fs'); + const Buffer = require('buffer').Buffer; + const buffer = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + const printPath = path.join(exePath, 'print.png'); + fs.writeFileSync(printPath, buffer); + + layer.open({ + type: 1, + area: [w, h], + title: language_str("display"), //不显示标题栏"预览" + shadeClose: true, //点击遮罩关闭 + content: ` +
+
+
+ +
+ +
+
+
+
`, + btn: [language_str("output"), "打印"],//'导出' + btn1: function (index, layero) { + saveImageAsPNG(buffer) + + printJS({ printable: printPath, type: 'image', style: 'img { width: 100%; height: auto; }' }) + } + }); + } + function output(callback = null, _save = save) { + // 类型改变 + fabric.Image.fromURL('./public/images/op_1.png', function (i1) { + i1.left = background_image.left; + i1.top = background_image.top; + let img1 = i1; + fabric.Image.fromURL('./public/images/op_1.png', function (i2) { + i2.left = background_image.left; + i2.top = background_image.top; + let img2 = i2; + + $("#base_control").hide(); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + $("#component_type").text(language_str("bg"));//"背景" + canvas1.discardActiveObject().renderAll(); + canvas2.discardActiveObject().renderAll(); + let _objs1 = canvas1.getObjects(); + let g1 = []; + let g3 = []; + let g5 = [];//黑色图层 + let left = 0;//左裁剪 + let top = 0;//顶裁剪 + let black_left = 0;//黑色左裁剪 + let black_top = 0;//黑色顶裁剪 + let all_left = 0;//预览图左裁剪 + let all_top = 0;//预览图顶裁剪 + for (let item of _objs1) { + let idx = getIndex(item, canvas1); + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g3.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue;//计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs1[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs1[idx - 1].type == 3 + && + item.get("fill") == "#000000" + //文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 11 + && + item.get("fill") == "#000000" + //圆形文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs1[idx - 1].black == true //勾选并入黑色图层 + + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g1.push(fabric.util.object.clone(item)); + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + g3.push(img1); + let url3 = new fabric.Group(g3).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url5 = new fabric.Group(g5).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + let _objs2 = canvas2.getObjects(); + let g2 = []; + let g4 = []; + let g6 = [];//黑色图层 + left = 0; + top = 0; + black_left = 0;//黑色左裁剪 + black_top = 0;//黑色顶裁剪 + all_left = 0;//黑色左裁剪 + all_top = 0;//黑色顶裁剪 + for (let item of _objs2) { + let idx = getIndex(item, canvas2); + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g4.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue;//计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs2[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs2[idx - 1].type == 3 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //文字 && 黑色 + ) + || + ( + objs2[idx - 1].type ==11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) + || + ( + objs2[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs2[idx - 1].black == true //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g2.push(fabric.util.object.clone(item)); + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 2787, + width: 2787, + }); + g4.push(img2); + let url4 = new fabric.Group(g4).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url6 = new fabric.Group(g6).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + // let white_url2 = canvas2.getObjects()[0].toDataURL({ + // height: 2787, + // width: 2787, + // }); + //console.log(canvas.getObjects()) + let w = document.body.clientWidth * 0.7 + "px"; + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + "px"; + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + "px"; + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + "px"; + h1 = document.body.clientHeight * 0.8 - 150 + "px"; + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + + let op = { frontColorPic: "", backColorPic: "", frontBlackPic: "", backBlackPic: "", frontData: [], backData: [], flag: 1, frontDisplayPic: url3, backDisplayPic: '' }; + if (objs1.length != 0 & objs2.length == 0) { + op.flag = 2; + } else if (objs1.length == 0 & objs2.length != 0) { + op.flag = 3; + } + op.frontColorPic = desEncrypt(url1); + op.backColorPic = ''; + //op.frontBlPic = url5; + //op.backBlPic = url6; + op.frontBlackPic = ''; + op.backBlackPic = ''; + for (let item of _objs1) { + let idx = getIndex(item, canvas1); + if (idx == 0) continue;//background-image + let type = objs1[idx - 1].type; + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 1, + "name": objs1[idx - 1].name, + "FontColor": objs1[idx - 1].black ? "#000000" : "@", + "transparency": item.get("opacity"), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 3) { + + } else if (type == 11) { + + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs1[idx - 1].circleCenter + }); + } else if (type == 4) { + //out_text + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 3, + "name": objs1[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "DefaultText": item.get("text"), + "FontBackgroudColor": item.get("textBackgroundColor"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "Exceed": objs1[idx - 1].exceed, + "AutoWrap": objs1[idx - 1].autoWrap, + "FixSize": objs1[idx - 1].fixSize, + "Bold": item.get("fontWeight") == "bold", + "Underline": item.get("underline"), + "Italic": item.get("fontStyle") == "italic", + "Strikeout": item.get("linethrough"), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)), + align: objs1[idx - 1].align + }); + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 4, + "name": objs1[idx - 1].name, + "FontColor": objs1[idx - 1].color, + "DefaultText": objs1[idx - 1].val, + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 9) { + //条形码 + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 5, + "name": objs1[idx - 1].name, + "FontSize": parseInt(objs1[idx - 1].fontSize), + "DefaultText": objs1[idx - 1].val, + "HideText": objs1[idx - 1].show, + "FontFamily": objs1[idx - 1].font, + "FontColor": objs1[idx - 1].color, + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 10) { + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 6, + "name": objs1[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "start": objs1[idx - 1].start, + "add": parseInt(objs1[idx - 1].add), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } + } + //obj2 + for (let item of _objs2) { + let idx = getIndex(item, canvas2); + if (idx == 0) continue;//background-image + let type = objs2[idx - 1].type; + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 1, + "name": objs2[idx - 1].name, + "FontColor": objs2[idx - 1].black ? "#000000" : "@", + "transparency": item.get("opacity"), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 3) { + + } else if (type == 11) { + + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs2[idx - 1].circleCenter + }); + } else if (type == 4) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 3, + "name": objs2[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "DefaultText": item.get("text"), + "FontSize": parseInt(item.get("fontSize")), + "FontBackgroudColor": item.get("textBackgroundColor"), + "FontColor": item.get("fill"), + "Exceed": objs2[idx - 1].exceed, + "AutoWrap": objs2[idx - 1].autoWrap, + "FixSize": objs2[idx - 1].fixSize, + "Bold": item.get("fontWeight") == "bold", + "Underline": item.get("underline"), + "Italic": item.get("fontStyle") == "italic", + "Strikeout": item.get("linethrough"), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)), + align: objs2[idx - 1].align + }); + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 4, + "name": objs2[idx - 1].name, + "FontColor": objs2[idx - 1].color, + "DefaultText": objs2[idx - 1].val, + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 9) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 5, + "name": objs2[idx - 1].name, + "DefaultText": objs2[idx - 1].val, + "HideText": objs1[idx - 1].show, + "FontSize": parseInt(objs2[idx - 1].fontSize), + "FontFamily": objs2[idx - 1].font, + "FontColor": objs2[idx - 1].color, + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 10) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 6, + "name": objs2[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "start": parseInt(objs2[idx - 1].start), + "add": parseInt(objs2[idx - 1].add), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } + } + _save(op, callback); + //return false 开启该代码可禁止点击该按钮关闭 + }) + }) + } + $('#output').on('click', function () { + output(null, saveAs); + return; + // 类型改变 + fabric.Image.fromURL('./public/images/op_1.png', function (i1) { + i1.left = background_image.left; + i1.top = background_image.top; + let img1 = i1; + fabric.Image.fromURL('./public/images/op_1.png', function (i2) { + i2.left = background_image.left; + i2.top = background_image.top; + let img2 = i2; + + $("#base_control").hide(); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + $("#component_type").text(language_str("bg"));//"背景" + canvas1.discardActiveObject().renderAll(); + canvas2.discardActiveObject().renderAll(); + let _objs1 = canvas1.getObjects(); + let g1 = []; + let g3 = []; + let g5 = [];//黑色图层 + let left = 0;//左裁剪 + let top = 0;//顶裁剪 + let black_left = 0;//黑色左裁剪 + let black_top = 0;//黑色顶裁剪 + let all_left = 0;//预览图左裁剪 + let all_top = 0;//预览图顶裁剪 + for (let item of _objs1) { + let idx = getIndex(item, canvas1); + + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g3.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue;//计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs1[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs1[idx - 1].type == 3 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) + || + ( + objs1[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs1[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs1[idx - 1].black == true //勾选并入黑色图层 + // ( + // objs1[idx-1].type == 1//固定图片 + // && + // objs1[idx-1].black == true //勾选并入黑色图层 + // ) + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g1.push(fabric.util.object.clone(item)); + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g1.push(fabric.util.object.clone(item)); + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + g3.push(img1); + let url3 = new fabric.Group(g3).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url5 = new fabric.Group(g5).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + let _objs2 = canvas2.getObjects(); + let g2 = []; + let g4 = []; + let g6 = [];//黑色图层 + left = 0; + top = 0; + black_left = 0;//黑色左裁剪 + black_top = 0;//黑色顶裁剪 + all_left = 0;//黑色左裁剪 + all_top = 0;//黑色顶裁剪 + for (let item of _objs2) { + let idx = getIndex(item, canvas2); + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g4.push(fabric.util.object.clone(item)); + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue;//计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue;//二维码 条形码 + } + if (idx != 0 && (objs2[idx - 1].type == 2)) { + continue;//合成图片 + } + if (idx != 0 && + ( + ( + objs2[idx - 1].type == 3 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //文字 && 黑色 + ) + || + ( + objs2[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) + || + ( + objs2[idx - 1].type == 5//矩形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 6//圆形 + && + ( + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("stroke") == "") //黑填充 透明边框 + || + (item.get("fill") == "" && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //透明填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff")) //黑填充 黑边框 + || + ((item.get("fill") == "#000000" || item.get("fill") == "#ffffff") && item.get("strokeWidth") == 0) //黑填充 0边框 + ) + ) + || + ( + objs2[idx - 1].type == 7//直线 + && + (item.get("stroke") == "#000000" || item.get("stroke") == "#ffffff") + ) + || + objs2[idx - 1].black == true //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + continue;//黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g2.push(fabric.util.object.clone(item)); + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + g2.push(fabric.util.object.clone(item)); + + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 2787, + width: 2787, + }); + g4.push(img2); + let url4 = new fabric.Group(g4).toDataURL({ + height: 2787, + width: 2787, + top: all_top, + left: all_left + }); + let url6 = new fabric.Group(g6).toDataURL({ + height: 2787, + width: 2787, + top: black_top, + left: black_left + }); + // let white_url2 = canvas2.getObjects()[0].toDataURL({ + // height: 2787, + // width: 2787, + // }); + //console.log(canvas.getObjects()) + let w = document.body.clientWidth * 0.7 + "px"; + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + "px"; + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + "px"; + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + "px"; + h1 = document.body.clientHeight * 0.8 - 150 + "px"; + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + + let op = { frontColorPic: "", backColorPic: "", frontBlackPic: "", backBlackPic: "", frontData: [], backData: [], flag: 1, frontDisplayPic: url3, backDisplayPic: '' }; + if (objs1.length != 0 & objs2.length == 0) { + op.flag = 2; + } else if (objs1.length == 0 & objs2.length != 0) { + op.flag = 3; + } + op.frontColorPic = desEncrypt(url1); + op.backColorPic = ''; + //op.frontBlPic = url5; + //op.backBlPic = url6; + op.frontBlackPic = ''; + op.backBlackPic = ''; + for (let item of _objs1) { + let idx = getIndex(item, canvas1); + if (idx == 0) continue;//background-image + let type = objs1[idx - 1].type; + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 1, + "name": objs1[idx - 1].name, + "FontColor": objs1[idx - 1].black ? "#000000" : "@", + "transparency": item.get("opacity"), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs1[idx - 1].circleCenter + }); + } else if (type == 4) { + //out_text + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 3, + "name": objs1[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "DefaultText": item.get("text"), + "FontBackgroudColor": item.get("textBackgroundColor"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "Exceed": objs1[idx - 1].exceed, + "AutoWrap": objs1[idx - 1].autoWrap, + "FixSize": objs1[idx - 1].fixSize, + "Bold": item.get("fontWeight") == "bold", + "Underline": item.get("underline"), + "Italic": item.get("fontStyle") == "italic", + "Strikeout": item.get("linethrough"), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)), + align: objs1[idx - 1].align + }); + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 4, + "name": objs1[idx - 1].name, + "FontColor": objs1[idx - 1].color, + "DefaultText": objs1[idx - 1].val, + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 9) { + //条形码 + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 5, + "name": objs1[idx - 1].name, + "FontSize": parseInt(objs1[idx - 1].fontSize), + "DefaultText": objs1[idx - 1].val, + "HideText": objs1[idx - 1].show, + "FontFamily": objs1[idx - 1].font, + "FontColor": objs1[idx - 1].color, + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 10) { + let xy = getAbsoluteXY(item); + op.frontData.push({ + "type": 6, + "name": objs1[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "start": objs1[idx - 1].start, + "add": parseInt(objs1[idx - 1].add), + "x": parseInt((xy[0] - background_image1.left).toFixed(0)), + "y": parseInt((xy[1] - background_image1.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } + } + //obj2 + for (let item of _objs2) { + let idx = getIndex(item, canvas2); + if (idx == 0) continue;//background-image + let type = objs2[idx - 1].type; + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 1, + "name": objs2[idx - 1].name, + "FontColor": objs2[idx - 1].black ? "#000000" : "@", + "transparency": item.get("opacity"), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs2[idx - 1].circleCenter + }); + } else if (type == 4) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 3, + "name": objs2[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "DefaultText": item.get("text"), + "FontSize": parseInt(item.get("fontSize")), + "FontBackgroudColor": item.get("textBackgroundColor"), + "FontColor": item.get("fill"), + "Exceed": objs2[idx - 1].exceed, + "AutoWrap": objs2[idx - 1].autoWrap, + "FixSize": objs2[idx - 1].fixSize, + "Bold": item.get("fontWeight") == "bold", + "Underline": item.get("underline"), + "Italic": item.get("fontStyle") == "italic", + "Strikeout": item.get("linethrough"), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)), + align: objs2[idx - 1].align + }); + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 4, + "name": objs2[idx - 1].name, + "FontColor": objs2[idx - 1].color, + "DefaultText": objs2[idx - 1].val, + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 9) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 5, + "name": objs2[idx - 1].name, + "DefaultText": objs2[idx - 1].val, + "HideText": objs1[idx - 1].show, + "FontSize": parseInt(objs2[idx - 1].fontSize), + "FontFamily": objs2[idx - 1].font, + "FontColor": objs2[idx - 1].color, + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } else if (type == 10) { + let xy = getAbsoluteXY(item); + op.backData.push({ + "type": 6, + "name": objs2[idx - 1].name, + "FontFamily": item.get("fontFamily"), + "FontSize": parseInt(item.get("fontSize")), + "FontColor": item.get("fill"), + "start": parseInt(objs2[idx - 1].start), + "add": parseInt(objs2[idx - 1].add), + "x": parseInt((xy[0] - background_image2.left).toFixed(0)), + "y": parseInt((xy[1] - background_image2.top).toFixed(0)), + "h": parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w": parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate": parseInt(item.get("angle").toFixed(0)) + }); + } + } + dialog.showSaveDialog({ + title: language_str("saveFile"),//'保存文件' + filters: [ + { name: 'CS File Type', extensions: ['cs'] }, + ] + }).then(result => { + if (result.canceled) return; + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.cs'; + } + let fs = require('fs') + op.soonType = 1 + fs.writeFileSync(result.filePath, JSON.stringify(op)) + }).catch(err => { + console.log() + }) + //return false 开启该代码可禁止点击该按钮关闭 + }) + }) + }); + var _keyctrl = false, _keyc = false, _textEdit = false, _inputEdit = false; + $("input").focus(function () { + console.log(1) + _inputEdit = true + }) + $("input").blur(function () { + _inputEdit = false + }) + $("textarea").focus(function () { + console.log(1) + _inputEdit = true + }) + $("textarea").blur(function () { + _inputEdit = false + }) + $("body").keyup(function (event) { + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放 + _keyctrl = false; + }; + if (event.keyCode == 67) { + _keyc = false; + }; + }) + $("body").keydown(function (event) { + // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta) + const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端 + + // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的) + if (!_inputEdit && !_textEdit) { + // CTRL/CMD+N - 新建 + if (isModifierKey && event.keyCode == 78) { + event.preventDefault(); + $("#new").click(); + return; + } + // CTRL/CMD+O - 打开 + if (isModifierKey && event.keyCode == 79) { + event.preventDefault(); + $("#open").click(); + return; + } + // CTRL/CMD+S - 保存 + if (isModifierKey && !event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + $("#save").click(); + return; + } + // CTRL/CMD+SHIFT+S - 保存副本 + if (isModifierKey && event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + output(null, saveAs); + return; + } + // CTRL/CMD+Z - 还原 + if (isModifierKey && !event.shiftKey && event.keyCode == 90) { + event.preventDefault(); + $("#previous").click(); + return; + } + // CTRL/CMD+Y - 重做 + if (isModifierKey && !event.shiftKey && event.keyCode == 89) { + event.preventDefault(); + $("#next").click(); + return; + } + // CTRL/CMD+C - 复制 + if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') { + event.preventDefault(); + copySelection(); + return; + } + // CTRL/CMD+V - 粘贴 + if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') { + event.preventDefault(); + pasteFromClipboard(); + return; + } + + // CTRL/CMD + SHIFT + DEL (Windows) 或 CMD + SHIFT + Backspace (Mac) - 清空 + if (isModifierKey && event.shiftKey && (event.keyCode == 46 || event.keyCode == 8)) { + event.preventDefault(); + $("#clean").click(); + return; + } + + // CTRL/CMD + DEL (Windows) 或 CMD + Backspace (Mac) - 删除 + if (isModifierKey && !event.shiftKey && (event.keyCode == 46 || event.keyCode == 8)) { + event.preventDefault(); + $("#delete").click(); + return; + } + + } + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd) + _keyctrl = true; + if (_keyc && _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 67) { + _keyc = true; + if (_keyctrl & _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 37) { + //left + let v = -1; + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5; + } + let x = canvas.getActiveObject().get("left"); + x += v; + canvas.getActiveObject().set("left", x); + canvas.renderAll(); + recordState(); + updateControls(); + } + if (event.keyCode == 38) { + //up + let v = -1; + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5; + } + let y = canvas.getActiveObject().get("top"); + y += v; + canvas.getActiveObject().set("top", y); + canvas.renderAll(); + recordState(); + updateControls(); + } + if (event.keyCode == 39) { + //right + let v = 1; + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5; + } + let x = canvas.getActiveObject().get("left"); + x += v; + canvas.getActiveObject().set("left", x); + canvas.renderAll(); + recordState(); + updateControls(); + } + if (event.keyCode == 40) { + //down + let v = +1; + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5; + } + let y = canvas.getActiveObject().get("top"); + y += v; + canvas.getActiveObject().set("top", y); + canvas.renderAll(); + recordState(); + updateControls(); + } + // 普通删除键(DEL键,没有按CTRL/CMD) + if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) { + let actObjs = canvas.getActiveObjects(); + let allObjs = canvas.getObjects(); + let flag = 0; + for (let item of actObjs) { + if (item != allObjs[0]) { + let i = getIndex(item);//0是背景,所以从1开始,但objs不包含背景图片,故要-1 + objs.splice(i - 1, 1); + canvas.remove(item); + flag = 1; + + } + } + updateList(); + canvas.discardActiveObject(); + if (flag == 1) { + recordState(); + canvas.renderAll(); + handleObjectSelected() + } + } + }); + let pre_add_image; + $("#addPic").click(function () { + OpenDialog(); + function OpenDialog() { + dialog.showOpenDialog({ + title: language_str("selectPic"),//请选择图片 + buttonLabel: language_str("comf"),//"确认" + filters: [ + { name: 'Image File Type', extensions: ['png', 'jpg', 'jpeg'] }, + ] + }).then(result => { + if (result.canceled) return; + //console.log(result.filePaths) + fabric.Image.fromURL(result.filePaths[0], function (image) { + //image.left = pointer.x; + //image.top = pointer.y; + image.setSrc(image.toDataURL(), function (image) { + pre_add_image = image; + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 1; + }); + //canvas.add(image); + //objs.push({type: 1}); + //canvas.renderAll(); + //updateList(); + //recordState(); + }); + }).catch(err => { + console.log(err) + }) + } + }); + $("#addOutPic").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 2; + }); + $("#addText").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 3; + }); + + $("#addCircleText").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 11; + }); + $("#addOutText").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 4; + }); + $("#addRect").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 5; + }); + $("#addCircle").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 6; + }); + $("#addLine").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 7; + }); + $("#addQrCode").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 8; + }); + $("#addBarCode").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 9; + }); + $("#addCounter").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addCircleText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + addState = 10; + }); + $("#version_change").click(function () { + if (bg_version == 1) { + bg_version = 2; + } else { + bg_version = 1; + } + // 类型改变 + $("#source_front").attr("src", "./public/images/front_bg" + bg_version + "_1.png"); + canvas.renderAll(); + + }); + $("#front_side").click(function () { + $(this).addClass("ui-button-active"); + $("#back_side").removeClass("ui-button-active"); + $(".canvas-container:eq(0)").show(); + $(".canvas-container:eq(1)").hide(); + console.log("------"); + console.log(recordObjs1); + console.log("------"); + canvas = canvas1; + objs = objs1; + background_image = background_image1; + next_objs = next_objs1; + pre_objs = pre_objs1; + next_json = next_json1; + pre_json = pre_json1; + recordObjs = recordObjs1; + recordJson = recordJson1; + step = step1; + updateList(); + + $("#line_control").hide(); + $("#base_control").hide(); + $("#pic_control").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + $("#component_type").text(language_str("bg"));//背景 + }); + $("#back_side").click(function () { + $(this).addClass("ui-button-active"); + $("#front_side").removeClass("ui-button-active"); + $(".canvas-container:eq(0)").hide(); + $(".canvas-container:eq(1)").show(); + canvas = canvas2; + objs = objs2; + background_image = background_image2; + next_objs = next_objs2; + pre_objs = pre_objs2; + next_json = next_json2; + pre_json = pre_json2; + recordObjs = recordObjs2; + recordJson = recordJson2; + step = step2; + updateList(); + $("#line_control").hide(); + $("#base_control").hide(); + $("#pic_control").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + $("#component_type").text(language_str("bg"));//"背景" + }); + $("#top_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + _objs[0].set("top", background_image.get("top")); + canvas.renderAll(); + return; + } + let top = 0; + for (let item of _objs) { + let t_top = item.get("top"); + if (t_top < top) { + top = t_top; + } + } + groupItems = []; + for (let item of _objs) { + item.set("top", top); + groupItems.push(item); + } + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + + }); + $("#bottom_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get("strokeWidth") + } + _objs[0].set("top", background_image.get("top") + background_image.get("height") - _objs[0].get("height") * _objs[0].get("scaleY") - other); + canvas.renderAll(); + return; + } + let bottom = 0; + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + let t_b = item.get("top") + item.get("height") * item.get("scaleY") + other; + if (t_b > bottom) { + bottom = t_b; + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + item.set("top", bottom - item.get("height") * item.get("scaleY") - other); + groupItems.push(item) + } + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + + }); + $("#center_y_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get("strokeWidth") + } + _objs[0].set("top", background_image.get("top") + (background_image.get("height") - (_objs[0].get("height") + other) * _objs[0].get("scaleY")) / 2); + canvas.renderAll(); + return; + } + let top = 0; + for (let item of _objs) { + let t_top = item.get("top"); + if (t_top < top) { + top = t_top; + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + item.set("top", top + (item.group.height - (item.get("height") + other) * item.get("scaleY")) / 2); + groupItems.push(item) + } + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + }); + $("#left_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + _objs[0].set("left", background_image.get("left")); + canvas.renderAll(); + return; + } + let left = 0; + for (let item of _objs) { + let t_left = item.get("left"); + if (t_left < left) { + left = t_left; + } + } + groupItems = [] + for (let item of _objs) { + item.set("left", left); + groupItems.push(item) + } + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + }); + $("#right_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + _objs[0].set("left", background_image.get("left") + background_image.get("width") - _objs[0].get("width") * _objs[0].get("scaleX") - other); + canvas.renderAll(); + return; + } + let right = 0; + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + let t_r = item.get("left") + item.get("width") * item.get("scaleX") + other; + if (t_r > right) { + right = t_r; + } + } + let groupItems = [] + + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + item.set("left", right - item.get("width") * item.get("scaleX") - other); + groupItems.push(item) + } + //BUG1 + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + }); + $("#center_x_alignment").click(function () { + let _objs = canvas.getActiveObjects(); + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get("strokeWidth") + } + _objs[0].set("left", background_image.get("left") + (background_image.get("width") - (_objs[0].get("width") + other) * _objs[0].get("scaleX")) / 2); + canvas.renderAll(); + return; + } + let left = 0; + for (let item of _objs) { + let t_left = item.get("left"); + if (t_left < left) { + left = t_left; + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item); + let type = objs[idx - 1].type; + let other = 0;//strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get("strokeWidth") + } + item.set("left", left + (item.group.width - (item.get("width") + other) * item.get("scaleX")) / 2); + groupItems.push(item) + } + canvas.discardActiveObject(); + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas, + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + canvas.renderAll(); + }); + $("#up_obj").click(function () {//上移,放到前面 数组中在后面 + let idx = getIndex(canvas.getActiveObject()); + if (idx + 1 != canvas.getObjects().length) { + canvas.bringForward(canvas.getActiveObject()); + canvas.renderAll(); + let temp = objs[idx - 1]; + objs[idx - 1] = objs[idx]; + objs[idx] = temp; + //console.log(objs); + //[objs[idx-2],objs[idx-1]] = [objs[idx-1],objs[idx-2]]; + + recordState(); + updateList(); + selectObj(idx); + } + }); + $("#down_obj").click(function () {//下移,放到后面 数组中在前面 + let idx = getIndex(canvas.getActiveObject()); + if (idx != 1) { + canvas.sendBackwards(canvas.getActiveObject()); + canvas.renderAll(); + //[objs[idx-2],objs[idx-1]] = [objs[idx-1],objs[idx-2]]; + + let temp = objs[idx - 1]; + objs[idx - 1] = objs[idx - 2]; + objs[idx - 2] = temp; + recordState(); + updateList(); + selectObj(idx - 2); + } + }); + $("#set_bg").click(function () {//设置为背景 + let obj = canvas.getActiveObject(); + console.log(obj, obj.get("selectable")) + let idx = getIndex(obj); + const scaleX = 2787 / obj.width + const scaleY = 2787 / obj.height + obj.set({ + scaleX: scaleX, + scaleY: scaleY, + left: ($('#canvas1').width() - 2787) / 2, + top: ($('#canvas1').height() - 2787) / 2, + }); + $('#text_w').val(2787) + $('#text_h').val(2787) + $('#text_x').val(0) + $('#text_y').val(0) + if (obj.get("selectable")) { + obj.set("selectable", false); + $("#lock_img").attr("src", "public/images/lock20.png") + } + canvas.sendToBack(obj); + canvas.bringForward(obj); + canvas.renderAll(); + let objItem = objs.splice(idx - 1, 1)[0] + objs.unshift(objItem) + recordState(); + updateList(); + selectObj(0); + }); + + // ========================================== + // 【新增】状态恢复后的回调函数 + // 作用:确保图片加载完毕后,再刷新界面,防止白屏或对象丢失 + // ========================================== + function onStateRestored() { + // 1. 重新获取背景图引用 (因为旧对象被销毁了) + // 注意:canvas 是全局变量,指向当前激活的画板 + if (canvas.getObjects().length > 0) { + background_image = canvas.getObjects()[0]; + background_image.selectable = false; // 确保背景不可选 + background_image.evented = false; + } + + // 2. 渲染画布 + canvas.requestRenderAll(); + + // 3. 取消选中状态 + selectObj(-1); + + // 4. 隐藏所有属性面板 + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + + // 5. 刷新左侧列表 + updateList(); + } + // ========================================== + // 撤销操作 (修复版) + // ========================================== + $("#previous").click(function () { + if (step.val == 0) return; + + step.val--; + + // 判断当前是正面还是背面 + if ($("#front_side").hasClass("ui-button-active")) { + // 1. 恢复业务数据 + objs1 = JSON.parse(recordObjs1[step.val]); + objs = objs1; + + // 2. 恢复画布 (使用回调函数!) + canvas.loadFromJSON(recordJson1[step.val], function() { + onStateRestored(); // 图片加载完才执行这一步 + }); + } else { + // 背面逻辑 + objs2 = JSON.parse(recordObjs2[step.val]); + objs = objs2; + + canvas.loadFromJSON(recordJson2[step.val], function() { + onStateRestored(); + }); + } + }); + // ========================================== + // 重做操作 (修复版) + // ========================================== + $("#next").click(function () { + // 获取当前历史记录的最大长度 + let maxLen = $("#front_side").hasClass("ui-button-active") ? recordObjs1.length : recordObjs2.length; + + if (step.val >= maxLen - 1) return; + + step.val++; + + if ($("#front_side").hasClass("ui-button-active")) { + objs1 = JSON.parse(recordObjs1[step.val]); + objs = objs1; + + canvas.loadFromJSON(recordJson1[step.val], function() { + onStateRestored(); // 图片加载完才执行这一步 + }); + } else { + objs2 = JSON.parse(recordObjs2[step.val]); + objs = objs2; + + canvas.loadFromJSON(recordJson2[step.val], function() { + onStateRestored(); + }); + } + }); + + $("#clean").click(function () { + objs.splice(0, objs.length); + + let allObjs = canvas.getObjects(); + for (let item of allObjs) { + if (item != allObjs[0]) { + canvas.remove(item); + } + } + updateList(); + recordState(); + }); + $("#rotate").click(function () { + let obj = canvas.getActiveObject(); + obj.rotate(Math.round((obj.get("angle") + 90) % 360)); + $("#text_r").val(obj.get("angle")); + canvas.renderAll(); + updateControls(); + recordState(); + }); + $('#vertical_obj').click(function () { + let obj = canvas.getActiveObject(); + obj.set("flipY", !obj.get("flipY")); + canvas.renderAll(); + updateControls(); + recordState(); + }); + $('#align_obj').click(function () { + let obj = canvas.getActiveObject(); + obj.set("flipX", !obj.get("flipX")); + canvas.renderAll(); + updateControls(); + recordState(); + }); + $("#text_italics").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("fontStyle", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontStyle", "italic") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#text_bold").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选,恢复正常 + canvas.getActiveObject().set("fontWeight", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontWeight", "bold") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#text_underline").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("underline", false) + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("underline", true) + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#text_linethrough").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("linethrough", false) + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("linethrough", true) + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + + $("#circle_text_italics").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("fontStyle", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontStyle", "italic") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#circle_text_bold").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选,恢复正常 + canvas.getActiveObject().set("fontWeight", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontWeight", "bold") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + + $('#out_left_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_left_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'left') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'left' + recordState() + canvas.renderAll() + }) + $('#out_right_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_right_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'right') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'right' + recordState() + canvas.renderAll() + }) + $('#out_center_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_center_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'center') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'center' + recordState() + canvas.renderAll() + }) + + $("#out_text_italics").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("fontStyle", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontStyle", "italic") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#out_text_bold").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选,恢复正常 + canvas.getActiveObject().set("fontWeight", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontWeight", "bold") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#out_text_underline").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("underline", false) + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("underline", true) + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#out_text_linethrough").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("linethrough", false) + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("linethrough", true) + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#lock").click(function () { + canvas.getActiveObject().set("selectable", !canvas.getActiveObject().get("selectable")); + if (canvas.getActiveObject().get("selectable")) { + $("#lock_img").attr("src", "public/images/unlock20.png")//已经上锁,需要解锁 + } else { + $("#lock_img").attr("src", "public/images/lock20.png")//没上锁,可以上锁 + } + recordState(); + canvas.renderAll(); + }); + $("#text_back_transparent").click(function () { + canvas.getActiveObject().set("textBackgroundColor", ""); + $("#text_back_color").val("#ffffff"); + canvas.renderAll(); + }); + $("#circle_text_back_transparent").click(function () { + canvas.getActiveObject().set("textBackgroundColor", ""); + $("#circle_text_back_color").val("#ffffff"); + canvas.renderAll(); + }); + $("#out_text_back_transparent").click(function () { + canvas.getActiveObject().set("textBackgroundColor", ""); + $("#out_text_back_color").val("#ffffff"); + canvas.renderAll(); + }); + $("#rect_transparent").click(function () { + canvas.getActiveObject().set("fill", ""); + $("#rect_color").val("#ffffff"); + canvas.renderAll(); + }); + $("#rect_stroke_transparent").click(function () { + canvas.getActiveObject().set("stroke", ""); + $("#rect_stroke").val("#ffffff"); + canvas.renderAll(); + }); + $("#circle_transparent").click(function () { + canvas.getActiveObject().set("fill", ""); + $("#circle_color").val("#ffffff"); + canvas.renderAll(); + }); + $("#circle_stroke_transparent").click(function () { + canvas.getActiveObject().set("stroke", ""); + $("#circle_stroke").val("#ffffff"); + canvas.renderAll(); + }); + + function saveAs(op1, callback) { + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let _g1 = [], _g2 = []; + let left = 0, top = 0; + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g1.push(fabric.util.object.clone(_o)); + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + left = 0, top = 0; + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g2.push(fabric.util.object.clone(_o)); + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let o = { f: j, b: {}, fo: objs1, bo: objs2, fp: fp, bp: '' }; + let con_o = Object.assign(o, op1); + dialog.showSaveDialog({ + title: language_str("saveFile"),//'保存文件' + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result => { + if (result.filePath == "") { return; } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon'; + } + let fs = require('fs') + con_o.soonType = 1 + fs.writeFileSync(result.filePath, JSON.stringify(con_o)) + openAs.name = result.filePath; + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + saveHistory(); + if (callback && typeof callback === 'function') { + callback(); + } + }).catch(err => { + console.log() + }) + } + function save(op1, callback) { + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let _g1 = [], _g2 = []; + let left = 0, top = 0; + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g1.push(fabric.util.object.clone(_o)); + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + left = 0, top = 0; + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g2.push(fabric.util.object.clone(_o)); + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let o = { f: j, b: {}, fo: objs1, bo: objs2, fp: fp, bp: '' }; + let con_o = Object.assign(o, op1); + if (openAs.name != "") { + //打开的文件 + let fs = require('fs') + con_o.soonType = 1 + fs.writeFileSync(openAs.name, JSON.stringify(con_o)) + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + saveHistory(); + if (callback && typeof callback === 'function') { + callback(); + } + return; + } + dialog.showSaveDialog({ + title: language_str("saveFile"),//'保存文件' + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result => { + if (result.filePath == "") { return; } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon'; + } + let fs = require('fs') + con_o.soonType = 1 + fs.writeFileSync(result.filePath, JSON.stringify(con_o)) + openAs.name = result.filePath; + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + saveHistory(); + if (callback && typeof callback === 'function') { + callback(); + } + }).catch(err => { + console.log() + }) + } + + // =========================================================== + // 1. 剪贴板数据定义 (修改:改为存储数据 data,而不是对象引用) + // =========================================================== + let clipboardData = { + data: null, // 存储序列化后的 JSON 数据 + 业务属性 + offset: 10 // 粘贴位置偏移量 + }; + + // =========================================================== + // 2. 复制函数:深度拷贝当前选中对象的数据 + // =========================================================== + function copySelection() { + const activeObjects = canvas.getActiveObjects(); + + if (!activeObjects || activeObjects.length === 0) { + layer.msg(language_str("selectPic") || "请先选择对象"); + return; + } + + // 【核心修复】 + // 遍历选中的对象,同时保存它的 Fabric 画面数据 和 我们的 objs 业务数据 + clipboardData.data = activeObjects.map(obj => { + let idx = getIndex(obj); // 获取对象在画布中的索引 + + // 获取业务属性 (objs数组中对应的那一项) + // 注意:idx=0 是背景图,objs数组是从下标0开始存第1个对象的,所以是 idx-1 + let appData = (idx > 0 && objs[idx-1]) ? objs[idx-1] : { type: 0 }; + + return { + // 1. 保存画面样式 (坐标、颜色、字号等) + fabricJson: obj.toObject(['id', 'name', 'selectable', 'evented', 'data']), + // 2. 保存业务属性 (深拷贝,防止引用冲突) + appData: JSON.parse(JSON.stringify(appData)) + }; + }); + + clipboardData.offset = 10; // 重置偏移量 + console.log(`已复制 ${activeObjects.length} 个对象`); + layer.msg("已复制"); + } + + // =========================================================== + // 3. 粘贴函数:根据保存的数据重建对象 + // =========================================================== + function pasteFromClipboard() { + // 检查是否有数据 + if (!clipboardData.data || clipboardData.data.length === 0) { + // layer.msg("剪贴板为空"); + return; + } + + // 1. 提取 Fabric JSON 准备恢复画面 + const jsonToEnliven = clipboardData.data.map(item => item.fabricJson); + + // 2. 恢复对象 (异步过程) + fabric.util.enlivenObjects(jsonToEnliven, function(objects) { + canvas.discardActiveObject(); + + objects.forEach((clone, index) => { + // --- A. 处理画面位置 --- + clone.set({ + left: clone.left + clipboardData.offset, + top: clone.top + clipboardData.offset, + evented: true + }); + + // 如果是组内对象,需要特殊处理坐标 + if (clone.group) { + clone.set({ + left: clone.group.left + clipboardData.offset, + top: clone.group.top + clipboardData.offset + }); + } + + // --- B. 处理业务数据 --- + // 取出复制时保存的 appData + let savedAppData = clipboardData.data[index].appData; + let newAppData = JSON.parse(JSON.stringify(savedAppData)); // 再次深拷贝 + + // 修改名称,防止 ID 冲突 (例如 TEXT_1 -> TEXT_1_copy) + if (newAppData.name) { + newAppData.name = newAppData.name + "_copy"; + } + + // --- C. 添加到画布和数据数组 --- + canvas.add(clone); + objs.push(newAppData); // 将新业务数据推入当前页面的 objs 数组 + }); + + // 3. 选中新粘贴出来的对象 + let sel = new fabric.ActiveSelection(objects, { canvas: canvas }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + + // 4. 刷新列表和历史记录 + updateList(); + recordState(); + + // 5. 高亮左侧列表最后一项 (即新粘贴的项) + if(objs.length > 0) selectObj(objs.length - 1); + + // 6. 增加下次粘贴的偏移量 (让用户知道粘贴了新东西) + clipboardData.offset += 10; + }); + } + + // =========================================================== + // 4. 按钮事件绑定 (保持原样) + // =========================================================== + // 复制 + $("#copy").click(function () { + copySelection(); + }) + // 粘贴 + $("#paste").click(function () { + pasteFromClipboard(); + }) + + // 保存 (保持你原有的保存逻辑不变) + $("#save").click(function () { + output(); // 这里调用你的 output 函数 + return; + // ... (下面是你原有的被 return 截断的代码,建议以后清理掉,但这里我按要求保留) + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + // ... (省略你原来那段很长的保存代码) ... + }); + + + function getDate() { + let date = new Date(); + return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(); + } + function getAbsoluteXY(_obj) { + let coord = _obj.get("lineCoords"); + return [_obj.get("left"), _obj.get("top")] + } + let fullPath = path.join(exePath, 'data.json') + // ========================================== + // 历史记录保存 (修复结构Bug + 自动置顶) + // ========================================== + function saveHistory() { + let fs = require('fs'); + let j = { history: [] }; // 默认结构必须是数组 + + try { + // 1. 读取并解析现有文件 + if (fs.existsSync(fullPath)) { + let content = fs.readFileSync(fullPath).toString(); + // 防止空文件报错 + if (content.trim()) { + j = JSON.parse(content); + } + } + } catch (e) { + console.error("读取历史记录失败,将重置:", e); + // 出错时使用默认空数组,不中断流程 + j = { history: [] }; + } + + // 确保 j.history 是数组 (防御性编程) + if (!Array.isArray(j.history)) { + j.history = []; + } + + // 2. 【核心优化】先过滤掉当前文件(如果已存在),然后加到最前面 + // 这样可以实现“最近使用的文件排在第一位”的效果 + let currentPath = openAs.name; + let newList = j.history.filter(item => item.path !== currentPath); + + // 3. 插入到头部 (Unshift) + newList.unshift({ + time: getDate(), + path: currentPath, + // type: 1 // 如果需要记录文件类型也可以加在这里 + }); + + // 4. (可选) 限制历史记录数量,比如只保留最近20条,防止文件无限膨胀 + if (newList.length > 20) { + newList = newList.slice(0, 20); + } + + j.history = newList; + + // 5. 统一写入 + try { + fs.writeFileSync(fullPath, JSON.stringify(j)); + } catch (e) { + console.error("写入历史记录失败:", e); + } + } + + function open(file) { + console.log(file) + let fs = require('fs') + var fsData = fs.readFileSync(file) + openAs.name = file; + let j = JSON.parse(fsData.toString()); + let left = background_image.left; + let top = background_image.top; + let new_left = j.f.objects[0].left; + let new_top = j.f.objects[0].top; + for (let o of j.f.objects) { + o.left = o.left - new_left + left; + o.top = o.top - new_top + top; + } + // for (let o of j.b.objects) { + // o.left = o.left - new_left + left; + // o.top = o.top - new_top + top; + // } + console.log(j.f) + canvas1.loadFromJSON(j.f); + canvas2.loadFromJSON(j.b); + background_image1 = canvas1.getObjects()[0]; + background_image2 = canvas2.getObjects()[0]; + + objs2 = j.bo; + objs1 = j.fo; + step1.val = 0; + step2.val = 0; + recordJson1 = []; + recordJson2 = []; + recordObjs1 = []; + recordObjs2 = []; + if ($("#front_side").hasClass("ui-button-active")) { + objs = objs1; + recordObjs = recordObjs1; + recordJson = recordJson1; + step = step1; + background_image = background_image1; + } else if ($("#back_side").hasClass("ui-button-active")) { + objs = objs2; + recordObjs = recordObjs2; + recordJson = recordJson2; + background_image = background_image2; + step = step2; + } + canvas1.renderAll(); + canvas2.renderAll(); + updateList(); //修复打开新文件后无法撤销的bug + setTimeout(() => { + recordObjs1.push(JSON.stringify(objs1)) + let j1 = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + j1.objects[0].hoverCursor = "default"; + recordJson1.push(j1); + + recordObjs2.push(JSON.stringify(objs2)) + let j2 = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + // j2.objects[0].hoverCursor = "default"; + recordJson2.push(j2); + }, 0); + } + $("#open").click(function () { + if (step.val == 0) { + OpenDialog(); + return; + } + layer.confirm('' + language_str("whetherSave") + '', function (index) {//confirm 是否保存当前文件? + output(OpenDialog); + layer.close(index); + + }, function (index) { + OpenDialog(); + }); + function OpenDialog() { + dialog.showOpenDialog({ + title: "请选择文件",//confirm + buttonLabel: language_str("comf"),//"确认" + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result => { + if (result.canceled) return; + console.log(result.filePaths) + let fs = require('fs') + var fsData = fs.readFileSync(result.filePaths[0]) + openAs.name = result.filePaths[0]; + let j = JSON.parse(fsData.toString()); + let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; + if (type == 2) { + ipcRenderer.send('open-design-page', openAs.name, type); + return + } + let left = background_image.left; + let top = background_image.top; + let new_left = j.f.objects[0].left; + let new_top = j.f.objects[0].top; + for (let o of j.f.objects) { + o.left = o.left - new_left + left; + o.top = o.top - new_top + top; + } + // for (let o of j.b.objects) { + // o.left = o.left - new_left + left; + // o.top = o.top - new_top + top; + // } + canvas1.loadFromJSON(j.f); + canvas2.loadFromJSON(j.b); + background_image1 = canvas1.getObjects()[0]; + background_image2 = canvas2.getObjects()[0]; + + objs2 = j.bo; + objs1 = j.fo; + step1.val = 0; + step2.val = 0; + recordJson1 = []; + recordJson2 = []; + recordObjs1 = []; + recordObjs2 = []; + if ($("#front_side").hasClass("ui-button-active")) { + objs = objs1; + recordObjs = recordObjs1; + recordJson = recordJson1; + step = step1; + background_image = background_image1; + } else if ($("#back_side").hasClass("ui-button-active")) { + objs = objs2; + recordObjs = recordObjs2; + recordJson = recordJson2; + background_image = background_image2; + step = step2; + } + canvas1.renderAll(); + canvas2.renderAll(); + updateList(); + // 修复打开新文件后无法撤销的bug + setTimeout(() => { + recordObjs1.push(JSON.stringify(objs1)) + let j1 = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + j1.objects[0].hoverCursor = "default"; + recordJson1.push(j1); + + recordObjs2.push(JSON.stringify(objs2)) + let j2 = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + j2.objects[0].hoverCursor = "default"; + recordJson2.push(j2); + }, 0); + }).catch(err => { + console.log(err) + }) + } + }); + function clearAll() { + //let blank = "{\"f\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":2787,\"height\":2787,\"fill\":\"#000000\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"b\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":2787,\"height\":2787,\"fill\":\"rgb(0,0,0)\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"fo\":[],\"bo\":[]}"; + //let j = JSON.parse(blank); + //canvas1.setZoom(1); + //canvas2.setZoom(1); + //canvas1.loadFromJSON(j.f); + //canvas2.loadFromJSON(j.b); + let t_objs1 = canvas1.getObjects(); + let t_objs2 = canvas2.getObjects(); + for (let i = 1; i < t_objs1.length; i++) { + canvas1.remove(t_objs1[i]); + } + for (let i = 1; i < t_objs2.length; i++) { + canvas2.remove(t_objs2[i]); + } + objs2 = []; + objs1 = []; + step1.val = 0; + step2.val = 0; + recordJson1 = []; + recordJson2 = []; + recordObjs1 = []; + recordObjs2 = []; + if ($("#front_side").hasClass("ui-button-active")) { + objs = objs1; + recordObjs = recordObjs1; + recordJson = recordJson1; + step = step1; + } else if ($("#back_side").hasClass("ui-button-active")) { + objs = objs2; + recordObjs = recordObjs2; + recordJson = recordJson2; + step = step2; + } + openAs.name = ""; + canvas1.renderAll(); + canvas2.renderAll(); + updateList(); + console.log(); + } + $("#new").click(function () { + if (step.val == 0) { + ipcRenderer.send('open-first-page'); + return; + } + layer.confirm('' + language_str("whetherSave") + '', function (index) {//confirm 是否保存当前文件? + output(() => ipcRenderer.send('open-first-page')); + return; + let j = canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let b = canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + let _g1 = [], _g2 = []; + let left = 0, top = 0; + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g1.push(fabric.util.object.clone(_o)); + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + left = 0, top = 0; + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g2.push(fabric.util.object.clone(_o)); + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 2787, + width: 2787, + top: top, + left: left + }); + let o = { f: j, b: {}, fo: objs1, bo: objs2, fp: fp, bp: '' }; + if (openAs.name != "") { + //打开的文件 + let fs = require('fs') + o.soonType = 1 + fs.writeFileSync(openAs.name, JSON.stringify(o)) + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + /* + 2022-07-16 直接跳转至首页 + */ + ipcRenderer.send('open-first-page'); + //saveHistory(); + //clearAll(); + return; + } + dialog.showSaveDialog({ + title: language_str("saveFile"),//'保存文件' + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result => { + if (result.filePath == "") { return; } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon'; + } + let fs = require('fs') + o.soonType = 1 + fs.writeFileSync(result.filePath, JSON.stringify(o)) + openAs.name = result.filePath; + layer.msg(language_str("saveSucc") + openAs.name);//'保存成功至' + /* + 2022-07-16 直接跳转至首页 + */ + ipcRenderer.send('open-first-page'); + //saveHistory(); + //clearAll(); + }).catch(err => { + console.log() + }) + layer.close(index); + + }, function (index) { + /* + 2022-07-16 直接跳转至首页 + */ + //clearAll(); + ipcRenderer.send('open-first-page'); + }); + }); + function chagneTitle() { + + } + function addBackground() { + // 类型改变 + fabric.Image.fromURL('./public/images/bg_front_1.png', function (image) { + let width = $("#canvas1").width() + + image.left = ($("#canvas1").width() - image.width * image.get("scaleX")) / 2; + image.top = ($("#canvas1").height() - image.height * image.get("scaleY")) / 2; + + image.selectable = false; + image.hoverable = false; + image.hoverCursor = "default" + image.setSrc(image.toDataURL(), function (image) { + image.scale(1, 1); + background_image1 = image; + background_image = background_image1; + canvas.add(image); + canvas.renderAll(); + //pre_objs1.push(JSON.stringify(objs1)); + //pre_json1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor"])); + + recordObjs1.push(JSON.stringify(objs1)); + recordJson1.push(canvas1.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"])); + }); + // 类型改变 + fabric.Image.fromURL('./public/images/bg_back.png', function (image) { + image.left = ($("#canvas2").width() - image.width) / 2; + image.top = ($("#canvas2").height() - image.height) / 2; + + image.selectable = false; + image.hoverable = false; + image.hoverCursor = "default" + + image.setSrc(image.toDataURL(), function (image) { + background_image2 = image; + canvas2.add(image); + canvas2.renderAll(); + //pre_objs2.push(JSON.stringify(objs2)); + //pre_json2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor"])); + + recordObjs2.push(JSON.stringify(objs2)); + recordJson2.push(canvas2.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"])); + + let file = GetFile().get('file') + console.log(file) + if (file && file != 'empty') { + open(file) + } + }); + }); + is_bgi_add = true; + }); + } + function selectObject(target, index) { + updateControls(); + + $("#base_control").show(); + //$("#text_x").val((target.left - background_image.left).toFixed(1)); + //$("#text_y").val((target.top - background_image.top).toFixed(1)); + //$("#text_w").val(parseFloat(target.get("scaleX") * target.get("width")).toFixed(1)); + //$("#text_h").val(parseFloat(target.get("scaleY") * target.get("height")).toFixed(1)); + //$("#text_r").val(target.get("angle").toFixed(0)); + } + function updateControls() { + $("#text_r").val(canvas.getActiveObject().get("angle").toFixed(0)); + $("#text_x").val((canvas.getActiveObject().get("left") - background_image.left).toFixed(0)); + $("#text_y").val((canvas.getActiveObject().get("top") - background_image.top).toFixed(0)); + $("#text_h").val((canvas.getActiveObject().get("scaleY") * canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("scaleX") * canvas.getActiveObject().get("width")).toFixed(0)); + if (canvas.getActiveObject().get("selectable")) { + $("#lock_img").attr("src", "public/images/unlock20.png")//已经上锁,需要解锁 + } else { + $("#lock_img").attr("src", "public/images/lock20.png")//没上锁,可以上锁 + } + } + function checkName(name) { + //检查字段名是否重复了 + for (let item of objs1) { + if (item.name == name) { + return false; + } + } + for (let item of objs2) { + if (item.name == name) { + return false; + } + } + return true; + } + function resName(pre_name) { + //返回的是不重复的字段名 + let num = (objs1.length + objs2.length) - 1; + do { + num++; + } + while (!checkName(pre_name + num));//重复了就再加1 + return pre_name + num; + } + function addPic(pointer) { + + pre_add_image.left = pointer.x; + pre_add_image.top = pointer.y; + + canvas.add(pre_add_image); + + objs.push({ type: 1, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + //OpenDialog(); + //function OpenDialog() + //{ + // dialog.showOpenDialog({ + // title: "请选择图片", + // buttonLabel: "确认", + // filters: [ + // { name: 'Image File Type', extensions: ['png', 'jpg'] }, + // ] + // }).then(result => { + // if(result.canceled)return; + // //console.log(result.filePaths) + // fabric.Image.fromURL(result.filePaths[0], function(image) { + // image.left = pointer.x; + // image.top = pointer.y; + // + // canvas.add(image); + // objs.push({type: 1}); + // canvas.renderAll(); + // updateList(); + // recordState(); + // } ); + // }).catch(err => { + // console.log(err) + // }) + //} + } + function selectPic(target, index) { + //透明度 + + $("#pic_control").show(); + $("#set_bg").show(); + pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + + let idx = getIndex(canvas.getActiveObject()); + // $("#pic_black").prop("checked", objs[idx-1].black ? 'checked' : ''); + + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + + } + function addOutPic(pointer) { + fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) { + let width = canvas.getWidth(); + let height = canvas.getHeight(); + image.left = pointer.x; + image.top = pointer.y; + image.opacity = 1; + //image.on("selected",function(){//test well + // console.log(222); + //}); + image.setSrc(image.toDataURL(), function (image) { + canvas.add(image); + objs.push({ type: 2, name: resName("OUT_PIC_"), black: false }); + canvas.renderAll(); + updateList(); + recordState(); + }); + }); + } + function selectOutPic(target, index) { + //字段名 + $("#out_pic_control").show(); + out_pic_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + let idx = getIndex(canvas.getActiveObject()); + $("#out_pic_field_name").val(objs[idx - 1].name); + // $("#out_pic_black").prop("checked", objs[idx-1].black ? 'checked' : ''); + + + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + } + + function addText(pointer) { + let text = new fabric.Text('TEXT', {//源:文本 + left: pointer.x, + top: pointer.y, + fill: "#000000", + textBackgroundColor: "", + fontStyle: "normal", + fontWeight: "normal", + fontSize: 100, + underline: false, + linethrough: false, + fontFamily: 'Arial' + }); + text.setControlVisible("ml", false); + text.setControlVisible("mt", false); + text.setControlVisible("mr", false); + text.setControlVisible("mb", false); + canvas.add(text); + objs.push({ type: 3, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectText() { + //console.log(canvas.getActiveObject().get("fontFamily")); + $("#text_control").show(); + $("#text_color").val(canvas.getActiveObject().get("fill")); + $("#text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#text_size").val(val); + $("#text_text").val(canvas.getActiveObject().get("text")); + $("#text_font_family").val(canvas.getActiveObject().get("fontFamily")); + + if (canvas.getActiveObject().get("underline") == true) { + $("#text_underline").addClass("ui-radio-acitve") + } else { + $("#text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#text_bold").addClass("ui-radio-acitve") + } else { + $("#text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#text_italics").addClass("ui-radio-acitve") + } else { + $("#text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#text_linethrough").addClass("ui-radio-acitve") + } else { + $("#text_linethrough").removeClass("ui-radio-acitve") + } + $("#circle_text_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + + } + function getDisdance(x1, y1, x2, y2) { + var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值 + var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值 + var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离 + return distance; + } + function getDeg(pointer) { + // 计算点击位置与画布中心的坐标差 + var centerX = canvas.width / 2; + var centerY = canvas.height / 2; + var mouseX = pointer.x; + var mouseY = pointer.y; + var dx = mouseX - centerX; + var dy = mouseY - centerY; + // 计算旋转角度(弧度) + var angleRad = Math.atan2(dy, dx); + // 将角度转换为度数 + var angleDeg = angleRad * (180/Math.PI); + if (angleDeg < 0) { + angleDeg += 360; + } + angleDeg += 90 + return angleDeg + } + function addCircleText(pointer) { + let distance = getDisdance(pointer.x, pointer.y, canvas.width/2,canvas.height/2) + let angleDeg = getDeg(pointer) + let text = new fabric.CurvedText('Circle TEXT', {//源:圆形文本 + left: pointer.x, + top: pointer.y, + fill: "#000000", + textBackgroundColor: "", + fontStyle: "normal", + fontWeight: "normal", + diameter: distance*2, + fontSize: 100, + underline: false, + linethrough: false, + lockScalingX: true, + lockScalingY: true, + fontFamily: 'Arial', + originX: 'center', + // originY: 'center', + angle: angleDeg, + flipped: pointer.y > canvas.height/2 ? true : false + }); + // distance = getDisdance(pointer.x+text.width/2, pointer.y, canvas.width/2,canvas.height/2) + // text.set({diameter: distance*2}) + text.setControlVisible("ml", false); + text.setControlVisible("mt", false); + text.setControlVisible("mr", false); + text.setControlVisible("mb", false); + canvas.add(text); + objs.push({ type: 11, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectCircleText() { + let idx = getIndex(canvas.getActiveObject()); + $("#circle_text_control").show(); + $("#circle_text_color").val(canvas.getActiveObject().get("fill")); + $("#circle_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + circle_text_size.setValue(canvas.getActiveObject().get("fontSize") - 10); + circle_text_diameter.setValue(canvas.getActiveObject().get("diameter")-500); + $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : ''); + $("#circle_text_text").val(canvas.getActiveObject().get("text")); + $("#circle_text_font_family").val(canvas.getActiveObject().get("fontFamily")); + + if (canvas.getActiveObject().get("underline") == true) { + $("#circle_text_underline").addClass("ui-radio-acitve") + } else { + $("#circle_text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#circle_text_bold").addClass("ui-radio-acitve") + } else { + $("#circle_text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#circle_text_italics").addClass("ui-radio-acitve") + } else { + $("#circle_text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#circle_text_linethrough").addClass("ui-radio-acitve") + } else { + $("#circle_text_linethrough").removeClass("ui-radio-acitve") + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#rotate").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + + } + function addOutText(pointer) { + //MERGE_TEXT_1 + let text = new fabric.Text("MERGE TEXT", {//源:合成文本 + left: pointer.x, + top: pointer.y, + fill: "#000000", + fontStyle: "normal", + fontWeight: "normal", + fontSize: 100, + textBackgroundColor: "", + underline: false, + linethrough: false, + fontFamily: 'Arial', + textAlign: 'center' + }); + + //text.on({ + // 'scaling': function(e) { + // var obj = this, + // w = obj.width * obj.scaleX, + // h = obj.height * obj.scaleY; + // + // obj.set({ + // 'height' : h, + // 'width' : w, + // 'scaleX' : 1, + // 'scaleY' : 1 + // }); + // } + //}); + text.setControlVisible("ml", false); + text.setControlVisible("mt", false); + text.setControlVisible("mr", false); + text.setControlVisible("mb", false); + + canvas.add(text); + objs.push({ type: 4, name: resName("OUT_TEXT_"), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectOutText() { + //color + //size + const align = canvas.getActiveObject().get('textAlign') + if (align === 'left') { + $('#out_left_alignment').parent().addClass('tool1') + } else if (align === 'right') { + $('#out_right_alignment').parent().addClass('tool1') + } else { + $('#out_center_alignment').parent().addClass('tool1') + } + $("#out_text_control").show(); + $("#out_text_color").val(canvas.getActiveObject().get("fill")); + $("#out_text_text").val(canvas.getActiveObject().get("text")); + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#out_text_size").val(val); + $("#out_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + let idx = getIndex(canvas.getActiveObject()); + $("#out_text_field_name").val(objs[idx - 1].name); + $("#out_text_font_family").val(canvas.getActiveObject().get("fontFamily")); + $("#exceed").prop("checked", objs[idx - 1].exceed ? 'checked' : ''); + $("#autoWrap").prop("checked", objs[idx - 1].autoWrap ? 'checked' : ''); + $("#fixSize").prop("checked", objs[idx - 1].fixSize ? 'checked' : ''); + if (canvas.getActiveObject().get("underline") == true) { + $("#out_text_underline").addClass("ui-radio-acitve") + } else { + $("#out_text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#out_text_bold").addClass("ui-radio-acitve") + } else { + $("#out_text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#out_text_italics").addClass("ui-radio-acitve") + } else { + $("#out_text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#out_text_linethrough").addClass("ui-radio-acitve") + } else { + $("#out_text_linethrough").removeClass("ui-radio-acitve") + } + + + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + } + function addRect(pointer) { + let rect = new fabric.Rect({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: "#ff0000", + strokeWidth: 10, + width: 300, + height: 300 + }); + canvas.add(rect); + objs.push({ type: 5, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectRect() { + //color + $("#rect_control").show(); + $("#rect_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); + $("#rect_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); + $("#rect_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); + $("#rect_radius").val(canvas.getActiveObject().get("rx")); + rect_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + let a = canvas.getActiveObject().getCoords(); + console.log(getCoordsMinX(a)); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + } + function addCircle(pointer) { + let circle = new fabric.Circle({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: '#ff0000', + strokeWidth: 10, + radius: 150, + strokeUniform: true, + }); + canvas.add(circle); + objs.push({ type: 6, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectCircle() { + //border-color + //color + $("#circle_control").show(); + $("#circle_color").val(canvas.getActiveObject().get("fill") == "" ? "#ffffff" : canvas.getActiveObject().get("fill")); + $("#circle_stroke").val(canvas.getActiveObject().get("stroke") == "" ? "#ffffff" : canvas.getActiveObject().get("stroke")); + $("#circle_stroke_width").val(canvas.getActiveObject().get("strokeWidth")); + circle_op.setValue(100 - (canvas.getActiveObject().get("opacity") * 100)); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + } + function addLine(pointer) { + let line = new fabric.Line([100, 200, 400, 200], { + left: pointer.x, + top: pointer.y, + stroke: '#ff0000', + strokeWidth: 10, + strokeDashArray: [0, 0], + }); + + line.setControlVisible("tl", false); + line.setControlVisible("tr", false); + line.setControlVisible("br", false); + line.setControlVisible("bl", false); + line.setControlVisible("mt", false); + line.setControlVisible("mb", false); + canvas.add(line); + objs.push({ type: 7, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectLine() { + //color + //weight + $("#line_control").show(); + + let idx = getIndex(canvas.getActiveObject()); + $("#line_color").val(canvas.getActiveObject().get("stroke")); + $("#line_dist").val(canvas.getActiveObject().get("strokeDashArray")[0]); + $("#text_h").val(canvas.getActiveObject().get("strokeWidth")) + + + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + } + function addQrCode(pointer) { + + let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 }); + fabric.Image.fromURL(qr, function (image) { + image.left = pointer.x; + image.top = pointer.y; + image.scale(2.4, 2.4) + canvas.add(image); + objs.push({ type: 8, name: resName("QR_CODE_"), color: "#000000", val: "https://www.cardsoon.com", black: false }); + canvas.renderAll(); + updateList(); + recordState(); + }); + } + function selectQrCode() { + //字段名 + //background-color + //color + $("#qrcode_Controll").show(); + let idx = getIndex(canvas.getActiveObject()); + $("#qrcode_field_name").val(objs[idx - 1].name); + $("#qrcode_color").val(objs[idx - 1].color); + $("#qrcode_val").val(objs[idx - 1].val); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#barcode_controll").hide(); + } + function addBarCode(pointer) { + JsBarcode("#barcode", "123456789", { margin: 0, lineColor: "#000000", fontSize: 18, font: "Arial" }); + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + fabric.Image.fromURL(bar, function (image) { + image.left = pointer.x; + image.top = pointer.y; + image.scale(2.4, 2.4) + canvas.add(image); + objs.push({ type: 9, name: resName("BAR_CODE_"), val: "123456789", color: "#000000", fontSize: 18, font: "Arial", show: false, black: false });//show是取消文字显示 + canvas.renderAll(); + updateList(); + recordState(); + }); + } + function selectBarCode() { + //字段名 + //background-color + //color + $("#barcode_controll").show(); + let idx = getIndex(canvas.getActiveObject()); + $("#barcode_field_name").val(objs[idx - 1].name); + $("#barcode_font_family").val(objs[idx - 1].font); + let val = parseFloat(objs[idx - 1].fontSize)*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#barcode_size").val(val); + $("#barcode_val").val(objs[idx - 1].val); + $("#barcode_show").prop("checked", objs[idx - 1].show ? 'checked' : ''); + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + } + function addCounter(pointer) { + let text = new fabric.Text('0', { + left: pointer.x, + top: pointer.y, + fontSize: 100, + fill: "#000000", + }); + canvas.add(text); + objs.push({ type: 10, name: resName("COUNTER_"), start: 0, add: 1, black: false }); + canvas.renderAll(); + updateList(); + recordState(); + console.log(canvas.getObjects()) + } + function selectCounter() { + //起始 + //终止 + //增量 + $("#counter_control").show(); + + + let idx = getIndex(canvas.getActiveObject()); + $("#counter_start").val(objs[idx - 1].start); + $("#counter_add").val(objs[idx - 1].add); + $("#counter_color").val(canvas.getActiveObject().get("fill")); + $("#count_font_family").val(canvas.getActiveObject().get("fontFamily")); + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#count_size").val(val); + + $("#line_control").hide(); + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#circle_text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + + } + function getIndex(target, _canvas = null) { + let temp_objs; + if (_canvas == null) { + temp_objs = canvas.getObjects(); + } else { + temp_objs = _canvas.getObjects(); + } + + let i = 0; + for (let res of temp_objs) { + if (target == res) { + return i; + } + i++; + } + return 0; + } + function getType(target) { + let temp_objs = canvas.getObjects(); + let i = 0; + for (let res of temp_objs) { + if (target == res) { + return objs[i].type; + } + i++; + } + } + // =========================================================== + // 1. 刷新对象列表 (序号顺序:底层=1,顶层=N,修复:Flex布局防遮挡 + 序号靠右) + // =========================================================== + function updateList() { + let str = ""; + let i = 0; + + // 定义行样式:Flex布局,两端对齐,相对定位防止溢出 + const rowStyle = "display:flex; justify-content:space-between; align-items:center; height:32px; padding:0 5px; cursor:pointer; border-bottom:1px solid #444; color:#ccc; font-size:12px; position:relative;"; + // 定义左侧内容样式 + const leftStyle = "display:flex; align-items:center; overflow:hidden; white-space:nowrap; max-width:180px;"; + // 定义序号样式:靠右,有左边框 + const indexStyle = "min-width:24px; text-align:center; padding-left:5px; color:#666; border-left:1px solid #555;"; + + for (let item of objs) { + let displayIndex = i + 1; // 序号底层为1 + + // 图标逻辑 + let iconName = "rect.svg"; + switch (item.type) { + case 1: iconName = "images.svg"; break; + case 2: iconName = "images_add.svg"; break; + case 3: iconName = "text.svg"; break; + case 4: iconName = "text2.svg"; break; + case 5: iconName = "rect.svg"; break; + case 6: iconName = "round.svg"; break; + case 7: iconName = "line.svg"; break; + case 8: iconName = "qrcode.svg"; break; + case 9: iconName = "barcode.svg"; break; + case 10: iconName = "counter.svg"; break; + case 11: iconName = "text3.svg"; break; + } + + // 名称逻辑 + let nameText = item.name ? item.name : + (item.type === 3 ? "TEXT_" + displayIndex : + (item.type === 1 ? "IMG_" + displayIndex : "OBJ_" + displayIndex)); + + // 组装 HTML + str = `
+
+ + ${nameText} +
+ ${displayIndex} +
` + str; + i++; + } + $(".obj_list").html(str); + + // 重新绑定点击事件(防止 updateList 后点击失效) + $(".obj_list > div").on("click", function () { + let imgId = $(this).find("img").attr("id"); + if(imgId) { + let idx = parseInt(imgId.replace("obj", "")); + selectObj(idx); + } + }); + } + + // =========================================================== + // 2. 列表选中高亮 (修复:自动滚动 + 防冲突) + // =========================================================== + function selectObj(objIndices, isFromCanvas = false) { + let indicesArray = (Array.isArray(objIndices)) ? objIndices : ((typeof objIndices === 'number' && objIndices >= 0) ? [objIndices] : []); + + // 清除所有高亮 + $('.obj_list > div').css("background-color", "transparent"); + + // 遍历列表查找并高亮 + $('.obj_list > div').each(function () { + let $img = $(this).find("img"); + if ($img.length === 0) return; + + let idIndex = parseInt($img.attr("id").replace("obj", "")); + + if (indicesArray.includes(idIndex)) { + $(this).css("background-color", "#6F7A84"); + + // 自动滚动到可视区域 + if (isFromCanvas && indicesArray[0] === idIndex) { + this.scrollIntoView({ behavior: "smooth", block: "nearest" }); + } + } + }); + + // 如果是点击列表,触发画布选中 + if (!isFromCanvas && indicesArray.length === 1) { + let target = canvas.getObjects()[indicesArray[0] + 1]; + if (target) { + canvas.setActiveObject(target); + canvas.requestRenderAll(); + handleObjectSelected(); + } + } + } + function recordState() { + //pre_objs.push(JSON.stringify(objs)); + //pre_json.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + //next_json = []; + //next_objs = []; + //recordObjs.push(JSON.stringify(objs)); + //recordJson.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + ++step.val; + for (let i = step.val; i < recordObjs.length; i++) { + recordObjs.pop(); + recordJson.pop(); + } + //recordObjs.splice(step.val, 1, JSON.stringify(objs)) + //recordJson.splice(step.val, 1, canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])) + recordObjs.push(JSON.stringify(objs)) + j = canvas.toJSON(["selectable", "hoverable", "hoverCursor", "text", "fontStyle", "fontWeight", "underline"]); + j.objects[0].hoverCursor = "default"; + recordJson.push(j); + } + function getCoordsMinX(acoords) { + x = acoords[0].x; + for (let item of acoords) { + if (item.x < x) { + x = item.x + } + } + return x; + } + function getCoordsMaxX(acoords) { + x = acoords[0].x; + for (let item of acoords) { + if (item.x > x) { + x = item.x + } + } + return x; + + } + function getCoordsMinY(acoords) { + y = acoords[0].y; + for (let item of acoords) { + if (item.y < y) { + y = item.y + } + } + return y; + + } + function getCoordsMaxY(acoords) { + y = acoords[0].y; + for (let item of acoords) { + if (item.y > y) { + y = item.y + } + } + return y; + } + + $("#text_y").bind('input propertychange', function () { + if ($("#text_y").val() == "") { + $("#text_y").val(0) + } + let val = background_image.top; + if ($("#text_y").val() != "") { + val = background_image.top + parseInt($("#text_y").val()); + } + canvas.getActiveObject().set("top", val) + canvas.renderAll(); + recordState(); + }) + $("#text_x").bind('input propertychange', function () { + if ($("#text_x").val() == "") { + $("#text_x").val(0) + } + let val = background_image.left; + if ($("#text_x").val() != "") { + val = background_image.left + parseInt($("#text_x").val()); + } + canvas.getActiveObject().set("left", val) + canvas.renderAll(); + recordState(); + }) + $("#text_w").bind('input propertychange', function () { + let val = "0"; + if ($("#text_w").val() != "") { + val = $("#text_w").val(); + } + let type = objs[getIndex(canvas.getActiveObject()) - 1].type; + if (type == 3 || type == 4) { + canvas.getActiveObject().set("width", parseInt(val)) + } else { + canvas.getActiveObject().set("scaleX", parseInt(val) / canvas.getActiveObject().get("width")) + } + + canvas.renderAll(); + recordState(); + }) + $("#text_h").bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()); + if (objs[idx - 1].type == 7) { + if ($("#text_h").val() == "") { + $("#text_h").val(canvas.getActiveObject().get("strokeWidth")); + } + canvas.getActiveObject().set("strokeWidth", parseInt($("#text_h").val())); + canvas.renderAll(); + return; + } + let val = "0"; + if ($("#text_h").val() != "") { + val = $("#text_h").val(); + } + let type = objs[getIndex(canvas.getActiveObject()) - 1].type; + if (type == 3 || type == 4) { + canvas.getActiveObject().set("height", parseInt(val)) + } else { + canvas.getActiveObject().set("scaleY", parseInt(val) / canvas.getActiveObject().get("height")) + } + + canvas.renderAll(); + recordState(); + }) + $("#text_r").bind('input propertychange', function () { + + if ($("#text_r").val() == "") { + $("#text_r").val("0") + } + $("#text_r").val(parseInt($("#text_r").val()) % 360) + //canvas.getActiveObject().set("rotate", val) + canvas.getActiveObject().rotate(parseInt($("#text_r").val())) + canvas.renderAll(); + recordState(); + }) + $("#out_pic_field_name").bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()); + objs[idx - 1].name = $("#out_pic_field_name").val(); + updateList(); + recordState(); + }) + + $("#text_black").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + objs[idx - 1].black = true; + } else { + objs[idx - 1].black = false; + } + recordState(); + }); + $("#out_text_field_name").bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()); + objs[idx - 1].name = $("#out_text_field_name").val(); + updateList(); + recordState(); + }) + $("#qrcode_field_name").bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()); + objs[idx - 1].name = $("#qrcode_field_name").val(); + updateList(); + recordState(); + }) + $("#qrcode_color").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let qr = jrQrcode.getQrBase64($("#qrcode_val").val(), { padding: 0, foreground: $(this).val() }); + let idx = getIndex(item); + objs[idx - 1].color = $(this).val(); + item.setSrc(qr, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $("#qrcode_val").bind('change', function () { + let val = $(this).val() ? $(this).val() : 'https://www.cardsoon.com' + let item = canvas.getActiveObject(); + let qr = jrQrcode.getQrBase64(val, { padding: 0, foreground: $("#qrcode_color").val() }); + let idx = getIndex(item); + objs[idx - 1].val = val; + item.setSrc(qr, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $("#barcode_color").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + objs[idx - 1].color = $(this).val(); + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }); + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $("#barcode_field_name").bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()); + objs[idx - 1].name = $("#barcode_field_name").val(); + updateList(); + recordState(); + }) + $("#text_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#text_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#text_back_color").bind('input propertychange', function () { + canvas.getActiveObject().set("textBackgroundColor", $("#text_back_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#text_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#text_size").val())) { + val = parseFloat($("#text_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#text_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + $("#circle_text_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#circle_text_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#circle_text_back_color").bind('input propertychange', function () { + canvas.getActiveObject().set("textBackgroundColor", $("#circle_text_back_color").val()); + + canvas.renderAll(); + recordState(); + }) + $("#circle_text_text").bind('input propertychange', function () { + let val = $("#circle_text_text").val().trim() + canvas.getActiveObject().set("text", ` ${val} `); + canvas.renderAll(); + updateList(); + recordState(); + }) + $("#out_text_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#out_text_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#out_text_back_color").bind('input propertychange', function () { + canvas.getActiveObject().set("textBackgroundColor", $("#out_text_back_color").val()); + + canvas.renderAll(); + recordState(); + }) + $("#out_text_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#out_text_size").val())) { + val = parseFloat($("#out_text_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#out_text_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + // 监听合成文本输入框 (占位符) + $("#out_text_text").on('input propertychange', function () { + let val = $(this).val(); + + // 【核心修改】如果值为空,强制给 2 个空格 + // 这样宽度足够宽,容易点击选中 + if (val === "") { + val = " "; + } + + let obj = canvas.getActiveObject(); + if (obj) { + obj.set("text", val); + obj.setCoords(); + canvas.requestRenderAll(); + updateList(); + recordState(); + } + }); + $("#rect_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#rect_color").val()); + + canvas.renderAll(); + recordState(); + }) + $("#rect_stroke").bind('input propertychange', function () { + canvas.getActiveObject().set("stroke", $("#rect_stroke").val()); + + canvas.renderAll(); + recordState(); + }) + $("#rect_stroke_width").bind('input propertychange', function () { + if ($("#rect_stroke_width").val() == "") { + $("#rect_stroke_width").val("0") + } else { + $("#rect_stroke_width").val(parseInt($("#rect_stroke_width").val())) + } + canvas.getActiveObject().set("strokeWidth", parseInt($("#rect_stroke_width").val())); + canvas.renderAll(); + recordState(); + }) + $("#rect_radius").bind('input propertychange', function () { + if ($("#rect_radius").val() == "") { + $("#rect_radius").val("0") + } else { + $("#rect_radius").val(parseInt($("#rect_radius").val())) + } + canvas.getActiveObject().set("rx", parseInt($("#rect_radius").val())); + canvas.getActiveObject().set("ry", parseInt($("#rect_radius").val())); + canvas.renderAll(); + recordState(); + }) + $("#circle_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#circle_color").val()); + + canvas.renderAll(); + recordState(); + }) + $("#circle_stroke").bind('input propertychange', function () { + canvas.getActiveObject().set("stroke", $("#circle_stroke").val()); + + canvas.renderAll(); + recordState(); + }) + $("#circle_stroke_width").bind('input propertychange', function () { + if ($("#circle_stroke_width").val() == "") { + $("#circle_stroke_width").val("0") + } else { + $(this).val(parseInt($(this).val())) + } + canvas.getActiveObject().set("strokeWidth", parseInt($("#circle_stroke_width").val())); + canvas.renderAll(); + recordState(); + }) + $("#counter_start").bind('input propertychange', function () { + canvas.getActiveObject().set("text", $("#counter_start").val()); + let item = canvas.getActiveObject(); + let idx = getIndex(item); + objs[idx - 1].start = $(this).val(); + canvas.renderAll(); + recordState(); + }) + $("#counter_add").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + objs[idx - 1].add = $(this).val(); + canvas.renderAll(); + recordState(); + }) + $("#counter_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#counter_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#count_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#count_size").val())) { + val = parseFloat($("#count_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#count_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + $("#line_color").bind('input propertychange', function () { + canvas.getActiveObject().set("stroke", $("#line_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#line_dist").bind('input propertychange', function () { + if ($("#line_dist").val() == "") { + $("#line_dist").val(0) + } + canvas.getActiveObject().set("strokeDashArray", [parseInt($("#line_dist").val()), parseInt($("#line_dist").val())]) + canvas.renderAll(); + recordState(); + }) + // 监听普通文本输入 (占位符) + $("#text_text").on('input propertychange', function () { + // 1. 获取输入框的值,不要用 trim(),否则无法输入空格 + let val = $(this).val(); + + // 2. 核心修改:只有当删光时,才给 4 个空格作为占位符 + if (val === "") { + val = " "; // 4个空格,比原来的2个更宽,更好点 + } + // 3. 更新对象 + // 注意:这里直接用 val,不再用 ` ${val} `,这样有文字时就不会强制加空格了 + canvas.getActiveObject().set("text", val); + canvas.requestRenderAll(); // 使用 requestRenderAll 性能更好 + updateList(); + recordState(); + }); + $("#barcode_val").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + objs[idx - 1].val = $(this).val(); + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }); + + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }) + $("#barcode_show").click(function () { + let item = canvas.getActiveObject(); + let idx = getIndex(canvas.getActiveObject()); + let val = $("#barcode_size").val() + if ($(this).prop("checked")) { + objs[idx - 1].show = true; + val = 0 + } else { + objs[idx - 1].show = false; + } + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: val*dpi/72, font: objs[idx - 1].font }); + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $("#barcode_size").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + let val = 1 + if (parseFloat($(this).val())) { + val = parseFloat($(this).val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#barcode_size").val(val) + objs[idx - 1].fontSize = val*dpi/72; + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }); + + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }) + $('#count_font_family').on('change', function () { + canvas.getActiveObject().set("fontFamily", $(this).find('option:selected').text()) + canvas.renderAll() + recordState(); + }); + $('#text_font_family').on('change', function () { + canvas.getActiveObject().set("fontFamily", $(this).find('option:selected').text()) + canvas.renderAll() + recordState(); + }); + + $('#circle_text_font_family').on('change', function () { + canvas.getActiveObject().set("fontFamily", $(this).find('option:selected').text()) + canvas.renderAll() + recordState(); + }); + + $('#language_select').on('change', function () {//语言选择 + langua_ge($(this).find('option:selected').val()); + s_lan = $(this).find('option:selected').val(); + localStorage.setItem("lang", s_lan); + }); + $('#barcode_font_family').on('change', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + objs[idx - 1].font = $(this).find('option:selected').text(); + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }); + + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + //recordState(); + }); + $('#out_text_font_family').on('change', function () { + canvas.getActiveObject().set("fontFamily", $(this).find('option:selected').text()) + canvas.renderAll() + recordState(); + }); + $(".obj_list").on("click", "div", function () { + let idx = objs.length - 1 - $(this).index(); + selectObj(idx); + }); + $("#exceed").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + objs[idx - 1].exceed = true; + } else { + objs[idx - 1].exceed = false; + } + recordState(); + }); + $("#autoWrap").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + + objs[idx - 1].autoWrap = true; + } else { + objs[idx - 1].autoWrap = false; + } + recordState(); + }); + $("#fixSize").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + + objs[idx - 1].fixSize = true; + } else { + objs[idx - 1].fixSize = false; + } + recordState(); + }); + $("#circle_text_center").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + objs[idx - 1].circleCenter = 1; + } else { + objs[idx - 1].circleCenter = 0; + } + recordState(); + }); + $("#delete").click(function () { + let actObjs = canvas.getActiveObjects(); + let allObjs = canvas.getObjects(); + let flag = 0; + for (let item of actObjs) { + if (item != allObjs[0]) { + let i = getIndex(item);//0是背景,所以从1开始,但objs不包含背景图片,故要-1 + objs.splice(i - 1, 1); + canvas.remove(item); + flag = 1; + + } + } + updateList(); + canvas.discardActiveObject(); + if (flag == 1) { + recordState(); + canvas.renderAll(); + } + // let actObjs = canvas.getActiveObjects(); + // let allObjs = canvas.getObjects(); + // let flag = 0; + // for( let item of actObjs){ + // if ( item != allObjs[0]){ + // let i = getIndex(item);//0是背景,所以从1开始,但objs不包含背景图片,故要-1 + // objs.splice(i-1,1); + // canvas.remove(item); + // flag = 1; + // updateList(); + // } + // } + // selectObj(-1); + // if(flag == 1){ + // recordState(); + // } + }); + + //在关于我们页面,获取版本号。 + // 注意:function 前面加了 async 关键字 + $("#about").click(async function () { + // 1. 先异步获取版本号 + let version = "0.0.0"; // 默认值,防止获取失败 + try { + if (window.sysAPI && window.sysAPI.getAppVersion) { + const v = await window.sysAPI.getAppVersion(); + version = "v" + v; // 拼接一个 v,变成 v2.3.017 + } + } catch (e) { + console.error("获取版本号失败", e); + } + + // 2. 获取成功后再弹出窗口 + layer.open({ + type: 1 + , title: language_str("about") + , area: '450px;' + , id: 'LAY_layuipro' + , moveType: 1 + , content: '
' + + '
' + + '
Soon Design
' + + // 【修改点】这里使用了刚才获取到的 version 变量 + '
' + language_str("vers") + ' ' + version + '
' + + '
' + language_str("copyright") + '
' + + '
' + }); + }) + $("#help").click(() => { + ipcRenderer.send('open-help-file'); + }); + + + // =========================================================== + // 对象选中处理 + // =========================================================== + function handleObjectSelected() { + let select_obj = canvas.getActiveObject(); + + // 1. 未选中任何对象 + if (!select_obj) { + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + $("#component_type").text(language_str("bg")); + selectObj(-1, true); // 传 true,只更新UI + return; + } + + // 2. 获取索引 + let i = getIndex(select_obj); + + if (i != 0) { + // --- 选中了普通对象 --- + --i; // 转换为 objs 数组下标 + + if(objs[i]) { + switch (objs[i].type) { + case 1: // 图片 + $("#component_type").text(language_str("img")); + selectObject(select_obj, i); selectPic(select_obj, i); break; + case 2: // 合成图片 + $("#component_type").text(language_str("simg")); + selectObject(select_obj, i); selectOutPic(select_obj, i); break; + case 3: // 文本 + $("#component_type").text(language_str("text")); + selectObject(select_obj, i); selectText(select_obj, i); break; + case 4: // 合成文本 + $("#component_type").text(language_str("stext")); + selectObject(select_obj, i); selectOutText(select_obj, i); break; + case 5: // 矩形 + $("#component_type").text(language_str("rect")); + selectObject(select_obj, i); selectRect(select_obj, i); break; + case 6: // 圆形 + $("#component_type").text(language_str("circ")); + selectObject(select_obj, i); selectCircle(select_obj, i); break; + case 7: // 直线 + $("#component_type").text(language_str("line")); + selectObject(select_obj, i); selectLine(select_obj, i); break; + case 8: // 二维码 + $("#component_type").text(language_str("qrc")); + selectObject(select_obj, i); selectQrCode(select_obj, i); break; + case 9: // 条形码 + $("#component_type").text(language_str("barc")); + selectObject(select_obj, i); selectBarCode(select_obj, i); break; + case 10: // 计数器 + $("#component_type").text(language_str("counter")); + selectObject(select_obj, i); selectCounter(select_obj, i); break; + case 11: // 圆形文本 + $("#component_type").text(language_str("ctext")); + selectObject(select_obj, i); selectCircleText(select_obj, i); break; + } + } + + // 【关键】这里传入 true! + // 意思是:因为我已经是在画布上选中它了,selectObj 函数只负责把左边的条目变灰,不要再回头去 setActiveObject 了 + selectObj(i, true); + + } else { + // --- 选中了背景 --- + $("#base_control, #line_control, #pic_control, #out_pic_control, #text_control, #circle_text_control, #out_text_control, #rect_control, #circle_control, #counter_control, #qrcode_Controll, #barcode_controll").hide(); + selectObj(-1, true); + $("#component_type").text(language_str("bg")); + } + } + + // =========================================================== + // 统一事件监听 (替代原有的 canvas1.on 和 canvas2.on) + // =========================================================== + // 提取对象移动逻辑 (供正反面复用) + function handleObjectMoving(e) { + let obj = e.target; + let idx = getIndex(obj); + // 圆形文本特殊处理 + if (idx > 0 && idx <= objs.length && objs[idx - 1] && objs[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { + let pointer = { x: obj.left, y: obj.top }; + let distance = getDisdance(pointer.x, pointer.y, canvas.width / 2, canvas.height / 2); + let angleDeg = getDeg(pointer); + let flipped = pointer.y > canvas.height / 2 ? true : false; + + if(typeof circle_text_diameter !== 'undefined') circle_text_diameter.setValue(distance * 2 - 500); + obj.set("diameter", distance * 2); + obj.set("angle", angleDeg); + obj.set("flipped", flipped); + } + updateControls(); + } + + // 启动监听 + // =========================================================== + // 统一事件绑定 (已集成边界检测) + // =========================================================== +// =========================================================== + // 3. 统一事件绑定 (修复:使用 Global Composite Operation 实现完美遮罩) + // =========================================================== + function initCanvasEvents() { + [canvas1, canvas2].forEach(currentCanvas => { + + // 1. 鼠标移动 + currentCanvas.on("mouse:move", (e) => { + let offsetX = 0, offsetY = 0; + if (currentCanvas === canvas2 && typeof background_image !== 'undefined') { + offsetX = background_image.left; + offsetY = background_image.top; + } + if(e.pointer) { + $("#posText").text("X: " + (e.pointer.x - offsetX).toFixed(0) + " Y: " + (e.pointer.y - offsetY).toFixed(0)); + } + updateRulerGuides(e.e); + }); + + // 2. 鼠标移出 + currentCanvas.on("mouse:out", () => { + $("#ruler-guide-h").hide(); $("#ruler-guide-v").hide(); + }); + + // 3. 选中处理 + currentCanvas.on('mouse:down', handleObjectSelected); + + // 4. 鼠标松开 (添加对象) + currentCanvas.on("mouse:up", (e) => { + if (addState === 0) return; + const ptr = e.pointer; + switch (addState) { + case 1: addPic(ptr); break; + case 2: addOutPic(ptr); break; + case 3: addText(ptr); break; + case 4: addOutText(ptr); break; + case 5: addRect(ptr); break; + case 6: addCircle(ptr); break; + case 7: addLine(ptr); break; + case 8: addQrCode(ptr); break; + case 9: addBarCode(ptr); break; + case 10: addCounter(ptr); break; + case 11: addCircleText(ptr); break; + } + addState = 0; + if(typeof background_image !== 'undefined') background_image.hoverCursor = "default"; + currentCanvas.renderAll(); + $("#addPic, #addOutPic, #addText, #addOutText, #addRect, #addCircle, #addLine, #addQrCode, #addBarCode, #addCounter, #addCircleText").css("background-color", ""); + }); + + // 5. 对象移动 (只保留圆形文本逻辑,去掉了 checkOutOfBounds) + currentCanvas.on("object:moving", (e) => { + handleObjectMoving(e); + }); + + // 6. 变换事件 + currentCanvas.on("object:rotating", updateControls); + currentCanvas.on("object:scaling", updateControls); + currentCanvas.on("object:rotated", () => { recordState(); }); + currentCanvas.on('text:editing:entered', () => { _textEdit = true; }); + currentCanvas.on('text:editing:exited', () => { _textEdit = false; }); + currentCanvas.on("text:changed", (e) => { $("#text_text").val(e.target.get("text")); }); + + // 7. 对象修改后 + currentCanvas.on('object:modified', function (event) { + if (currentCanvas.getActiveObjects().length > 1) return; + let idx = getIndex(event.target); + if(idx <= 0) return; + let objData = objs[idx - 1]; + let target = event.target; + + if ([3, 4, 10].includes(objData.type)) { + target.fontSize = (target.fontSize * target.scaleX).toFixed(0); + target.scaleX = 1; target.scaleY = 1; + target._clearCache(); + if(objData.type == 3) $("#text_size").val(target.fontSize); + if(objData.type == 4) $("#out_text_size").val(target.fontSize); + if(objData.type == 10) $("#count_size").val(target.fontSize); + currentCanvas.renderAll(); + updateControls(); + } else if (objData.type == 5) { + target.width *= target.scaleX; target.height *= target.scaleY; + target.scaleX = 1; target.scaleY = 1; + updateControls(); + } + recordState(); + }); + // ================================================== + // 8. 【完美修复版】渲染后绘制:外部蒙版 + 中心圆孔 (修复三角形Bug) + // ================================================== + currentCanvas.on('after:render', function () { + let bgObj = (currentCanvas === canvas1) ? background_image1 : background_image2; + + if (bgObj) { + const ctx = currentCanvas.getContext(); + const width = currentCanvas.width; + const height = currentCanvas.height; + + // 1. 计算圆心 + const cx = bgObj.left + (bgObj.width * bgObj.scaleX) / 2; + const cy = bgObj.top + (bgObj.height * bgObj.scaleY) / 2; + + // 2. 计算半径 + // 外半径 (光盘大小) + const r_outer = (bgObj.width * bgObj.scaleX) / 2; + + // 内半径 (中心孔) + const dpi = 600; + const mmToPx = dpi / 25.4; + let holeDiameterMm = 24; // 默认 15mm + + // 切换版本判断 (15mm vs 38mm) + if (currentCanvas === canvas1 && typeof bg_version !== 'undefined' && bg_version === 2) { + holeDiameterMm = 42; + } + + // 比例计算法:确保孔径随缩放适配 + // r_inner = r_outer * (孔径 / 光盘标准直径120mm) + const r_inner = r_outer * (holeDiameterMm / 120); + + ctx.save(); + ctx.beginPath(); + + // A. 绘制全屏矩形 (顺时针) + ctx.rect(0, 0, width, height); + + // B. 挖掉外圆 (逆时针) + // 【关键修复】画圆前,必须把笔移动到圆的起点 (cx + r, cy) + // 否则 Canvas 会画一条线连过来,形成奇怪的三角形 + ctx.moveTo(cx + r_outer, cy); + ctx.arc(cx, cy, r_outer, 0, Math.PI * 2, true); + + // C. 填补内圆 (顺时针) + // 【关键修复】同理,把笔移动到内圆的起点 + ctx.moveTo(cx + r_inner, cy); + ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false); + + ctx.closePath(); + ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩 + ctx.fill(); + ctx.restore(); + } + }); + }); + } + // 立即启动 + initCanvasEvents(); + // =========================================================== + // Slider 滑块初始化 (保持原样) + // =========================================================== + var circle_text_size = slider.render({ + elem: '#circle_text_size', + min: 10, max: 120, + change: function (value) { + canvas.getActiveObject().set("fontSize", value); + canvas.renderAll(); + } + }); + var circle_text_diameter = slider.render({ + elem: '#circle_text_diameter', + min: 500, max: 4000, + change: function (value) { + canvas.getActiveObject().set("diameter", value); + canvas.renderAll(); + } + }); + var out_pic_op = slider.render({ + elem: '#out_pic_op', + change: function (value) { + canvas.getActiveObject().set('opacity', (100 - value) / 100); + canvas.renderAll(); + } + }); + var pic_op = slider.render({ + elem: '#pic_op', + change: function (value) { + canvas.getActiveObject().set("opacity", (100 - value) / 100); + canvas.renderAll(); + } + }); + var rect_op = slider.render({ + elem: '#rect_op', + change: function (value) { + canvas.getActiveObject().set("opacity", (100 - value) / 100); + canvas.renderAll(); + } + }); + var circle_op = slider.render({ + elem: '#circle_op', + change: function (value) { + canvas.getActiveObject().set("opacity", (100 - value) / 100); + canvas.renderAll(); + } + }); +// =========================================================== + // 3. 标尺功能 (0点复位 + 拖拽辅助线 + 透明度) + // =========================================================== + function initRulers() { + const dpi = 600; + const mmToPx = dpi / 25.4; + const rulerHeight = 20; + const rulerWidth = 20; + + const canvasWrapper = $('.canvas-wrapper'); + const canvasBox = $('.canvas_box'); + + // 确保容器定位 + if(canvasWrapper.css('position') === 'static') canvasWrapper.css('position', 'relative'); + + // 初始化尺寸 + let canvasWidth = canvasWrapper.width() || 800; + let canvasHeight = canvasWrapper.height() || 600; + + const $rulerH = $('#ruler-h'); + const $rulerV = $('#ruler-v'); + + $rulerH.attr('width', canvasWidth).attr('height', rulerHeight); + $rulerV.attr('width', rulerWidth).attr('height', canvasHeight); + + // --- 1. 绑定拖拽事件 (核心新功能) --- + // 定义临时拖拽线 DOM + let $dragLine = null; + + // 通用拖拽开始函数 + const startDragGuide = (type, e) => { + e.preventDefault(); + e.stopPropagation(); + + // 创建临时显示的线 + $dragLine = $('
'); + $('.canvas-wrapper').append($dragLine); + + if (type === 'horizontal') { + // 从上往下拖 (水平线) + $dragLine.css({ + left: 0, width: '100%', height: '1px', top: e.offsetY + }); + } else { + // 从左往右拖 (垂直线) + $dragLine.css({ + top: 0, height: '100%', width: '1px', left: e.offsetX + }); + } + + // 绑定 document 移动事件 + $(document).on('mousemove.ruler', function(moveEvent) { + let wrapperOffset = canvasWrapper.offset(); + if (type === 'horizontal') { + let relY = moveEvent.pageY - wrapperOffset.top; + $dragLine.css('top', relY + 'px'); + } else { + let relX = moveEvent.pageX - wrapperOffset.left; + $dragLine.css('left', relX + 'px'); + } + }); + + // 绑定松开事件 (创建真实的 Fabric 线) + $(document).on('mouseup.ruler', function(upEvent) { + $(document).off('mousemove.ruler mouseup.ruler'); + + if (!$dragLine) return; + + // 获取最终位置 + let wrapperOffset = canvasWrapper.offset(); + let finalX = upEvent.pageX - wrapperOffset.left; + let finalY = upEvent.pageY - wrapperOffset.top; + + // 移除临时线 + $dragLine.remove(); + $dragLine = null; + + // 只有拖入画布区域才创建 + if (finalX > rulerWidth && finalY > rulerHeight) { + createFabricGuide(type, finalX, finalY); + } + }); + }; + + // 绑定到标尺 DOM + $rulerH.off('mousedown').on('mousedown', (e) => startDragGuide('horizontal', e)); + $rulerV.off('mousedown').on('mousedown', (e) => startDragGuide('vertical', e)); + + // --- 2. 在画布中创建辅助线 --- + function createFabricGuide(type, domX, domY) { + // 计算画布内的逻辑坐标 (减去 margin 偏移,除以缩放) + // 注意:这里我们重新回到“原点在画布0,0”的逻辑 + // canvasBox.offset() 是画布容器的绝对位置 + let boxOffset = canvasBox.offset(); + let wrapperOffset = canvasWrapper.offset(); + + // 计算相对于 canvasBox 的像素位置 + let relX = domX - (boxOffset.left - wrapperOffset.left); + let relY = domY - (boxOffset.top - wrapperOffset.top); + + // 转为 Fabric 坐标 (除以 zoom) + let canvasX = relX / zoom; + let canvasY = relY / zoom; + + // 构造线对象 + let linePoints = []; + if (type === 'horizontal') { + // 水平线:贯穿整个画布宽度 + // 为了让线足够长,我们设一个很大的值,或者设为 canvas.width + linePoints = [-5000, canvasY, 5000, canvasY]; + } else { + // 垂直线 + linePoints = [canvasX, -5000, canvasX, 5000]; + } + + let guideLine = new fabric.Line(linePoints, { + stroke: '#00FFFF', // 青色高亮 + strokeWidth: 1 / zoom, // 线条粗细随缩放调整,保持视觉一致 + strokeDashArray: [5, 5], // 虚线 + selectable: true, // 允许选中移动 + evented: true, + hasControls: false, // 没有缩放控制点 + hasBorders: false, + lockRotation: true, // 锁定旋转 + lockScalingX: true, + lockScalingY: true, + hoverCursor: type === 'horizontal' ? 'ns-resize' : 'ew-resize', + // 自定义属性标记 + isGuideLine: true, + ignoreSave: true // 保存时可以根据这个标记过滤掉 + }); + + // 限制移动方向 + guideLine.on('moving', function(e) { + if (type === 'horizontal') { + this.left = -5000; // 锁定 X 轴不许动 + } else { + this.top = -5000; // 锁定 Y 轴不许动 + } + }); + + canvas.add(guideLine); + canvas.setActiveObject(guideLine); + canvas.renderAll(); + } + + + // --- 3. 绘制标尺 (核心绘制循环) --- + function updateAndDraw() { + const pxPerMm = zoom * mmToPx; + + // 【修改点】原点复位逻辑 + // 现在原点就是 Canvas 的 (0,0) 点 + // 加上 canvasBox 的 margin 偏移量,让标尺 0 刻度对齐画布边缘 + let boxMarginLeft = parseFloat(canvasBox.css('margin-left')) || 0; + let boxMarginTop = parseFloat(canvasBox.css('margin-top')) || 0; + + let originX = boxMarginLeft; + let originY = boxMarginTop; + + const ctxH = document.getElementById('ruler-h').getContext('2d'); + const ctxV = document.getElementById('ruler-v').getContext('2d'); + + const drawAxis = (ctx, isH) => { + let len = isH ? canvasWidth : canvasHeight; + ctx.clearRect(0, 0, isH ? len : rulerWidth, isH ? rulerHeight : len); + + // 背景改为透明 (配合 CSS) + // ctx.fillStyle = 'rgba(44, 44, 44, 0.7)'; // 也可以在这里画 + // ctx.fillRect(...) + // 这里我们留空,让 CSS 背景色透出来 + + ctx.strokeStyle = '#999'; // 刻度颜色稍亮一点 + ctx.lineWidth = 1; + ctx.beginPath(); + + // 绘制底边线 + if(isH) { ctx.moveTo(0, 19); ctx.lineTo(len, 19); } + else { ctx.moveTo(19, 0); ctx.lineTo(19, len); } + ctx.stroke(); + + ctx.fillStyle = '#CCC'; + ctx.font = '9px Arial'; + ctx.textAlign = isH ? 'left' : 'center'; + ctx.textBaseline = isH ? 'top' : 'middle'; + ctx.beginPath(); + + // 绘制刻度 (只向正方向绘制) + // 既然原点复位到0了,我们只需要从0开始画 + for (let mm = 0; ; mm += 1) { + let pos = (isH ? originX : originY) + (mm * pxPerMm); + + if (pos > len) break; + + let isMajor = mm % 10 === 0; // 1cm + let isMedium = mm % 5 === 0; // 0.5cm + let tickLen = isMajor ? 15 : (isMedium ? 8 : 4); + + if(isH) { + ctx.moveTo(pos, 20 - tickLen); ctx.lineTo(pos, 20); + if(isMajor) ctx.fillText(mm/10, pos + 2, 2); // 显示 cm 数值 + } else { + ctx.moveTo(20 - tickLen, pos); ctx.lineTo(20, pos); + if(isMajor) { + ctx.save(); + ctx.translate(8, pos + 2); + ctx.rotate(-Math.PI/2); + ctx.fillText(mm/10, 0, 0); + ctx.restore(); + } + } + } + ctx.stroke(); + }; + + drawAxis(ctxH, true); + drawAxis(ctxV, false); + } + + canvas1.on('after:render', updateAndDraw); + canvas2.on('after:render', updateAndDraw); + $(window).resize(() => setTimeout(updateAndDraw, 100)); + updateAndDraw(); + } + + // 辅助函数:更新光标位置 + function updateRulerGuides(nativeEvent) { + if (!nativeEvent) return; + const wrapper = $('.canvas-wrapper'); + const offset = wrapper.offset(); + if (!offset) return; + $("#ruler-guide-h").css({ left: (nativeEvent.pageX - offset.left) + 'px', display: 'block' }); + $("#ruler-guide-v").css({ top: (nativeEvent.pageY - offset.top) + 'px', display: 'block' }); + } + + + + + }) + }); \ No newline at end of file diff --git a/lib/design2.js b/lib/design2.js new file mode 100644 index 0000000..bb2af76 --- /dev/null +++ b/lib/design2.js @@ -0,0 +1,6144 @@ + + function desEncrypt(str, key = 'df6a551ca43181fc485f3043bcdd2fbc') { + var APIFMS + try { + var keyHex_encrypt = CryptoJS.enc.Utf8.parse(key) + var encrypted = CryptoJS.DES.encrypt(str, keyHex_encrypt, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }) + APIFMS = CryptoJS.enc.Base64.stringify(encrypted.ciphertext) + } catch (err) { + console.log('des 加密 -------------------------') + console.log(err) + } + return APIFMS + } + + (function (fabric) { + fabric.CurvedText = fabric.util.createClass(fabric.Object, { + type: 'curved-text', + diameter: 250, + kerning: 0, + text: '', + flipped: false, + fill: '#000', + fontFamily: 'Times New Roman', + fontSize: 24, // in px + fontWeight: 'normal', + fontStyle: '', // "normal", "italic" or "oblique". + cacheProperties: fabric.Object.prototype.cacheProperties.concat('diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'), + strokeStyle: null, + strokeWidth: 0, + initialize: function (text, options) { + options || (options = {}); + if (typeof text === 'object' && text) { + for (let key in text) { + this[key] = text[key] + } + } else { + this.text = text; + } + this.callSuper('initialize', options); + this.set('lockUniScaling', true); + var canvas = this.getCircularText(); + this.cropCanvas(canvas); + this.set('width', canvas.width); + this.set('height', canvas.height); + }, + _getFontDeclaration: function () { + return [ + (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), + (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), + this.fontSize + 'px', + (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) + ].join(' '); + }, + cropCanvas: function (canvas) { + var ctx = canvas.getContext('2d'), + w = canvas.width, + h = canvas.height, + pix = { x: [], y: [] }, n, + imageData = ctx.getImageData(0, 0, w, h), + fn = function (a, b) { return a - b }; + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + if (imageData.data[((y * w + x) * 4) + 3] > 0) { + pix.x.push(x); + pix.y.push(y); + } + } + } + // 如果没有找到任何非透明像素,直接返回,不进行裁剪 + if (pix.x.length === 0 || pix.y.length === 0) { + return; + } + pix.x.sort(fn); + pix.y.sort(fn); + n = pix.x.length - 1; + w = pix.x[n] - pix.x[0]; + h = pix.y[n] - pix.y[0]; + // 确保宽度和高度是有效的正数 + if (w <= 0 || h <= 0 || isNaN(w) || isNaN(h)) { + return; + } + var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h); + canvas.width = w; + canvas.height = h; + ctx.putImageData(cut, 0, 0); + }, + getCircularText: function () { + var text = this.text, + diameter = this.diameter, + flipped = this.flipped, + kerning = this.kerning, + fill = this.fill, + inwardFacing = true, + startAngle = 0, + canvas = fabric.util.createCanvasElement(), + ctx = canvas.getContext('2d'), + cw, // character-width + x, // iterator + clockwise = -1; // draw clockwise for aligned right. Else Anticlockwise + if (flipped) { + // startAngle = 180; + inwardFacing = false; + } + startAngle *= Math.PI / 180; // convert to radians + var d = document.createElement('div'); + d.style.fontFamily = this.fontFamily; + d.style.fontSize = this.fontSize + 'px'; + d.style.fontWeight = this.fontWeight; + d.style.fontStyle = this.fontStyle; + d.textContent = text; + document.body.appendChild(d); + var textHeight = d.offsetHeight; + document.body.removeChild(d); + canvas.width = canvas.height = diameter; + ctx.font = this._getFontDeclaration(); + if (inwardFacing) { text = text.split('').reverse().join('') }; + ctx.translate(diameter / 2, diameter / 2); // Move to center + startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward + ctx.textBaseline = 'middle'; // Ensure we draw in exact center + ctx.textAlign = 'center'; // Ensure we draw in exact center + for (x = 0; x < text.length; x++) { + cw = ctx.measureText(text[x]).width; + startAngle += ((cw + (x == text.length - 1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise; + } + ctx.rotate(startAngle); + for (x = 0; x < text.length; x++) { + cw = ctx.measureText(text[x]).width; // half letter + ctx.rotate((cw / 2) / (diameter / 2 - textHeight) * clockwise); + if (this.strokeStyle && this.strokeWidth) { + ctx.strokeStyle = this.strokeStyle; + ctx.lineWidth = this.strokeWidth; + ctx.miterLimit = 2; + ctx.strokeText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); + } + ctx.fillStyle = fill; + ctx.fillText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); + ctx.rotate((cw / 2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter + } + return canvas; + }, + _set: function (key, value) { + switch (key) { + case 'scaleX': + this.fontSize *= value; + this.diameter *= value; + this.width *= value; + this.scaleX = 1; + if (this.width < 1) { this.width = 1; } + break; + case 'scaleY': + this.height *= value; + this.scaleY = 1; + if (this.height < 1) { this.height = 1; } + break; + default: + this.callSuper('_set', key, value); + break; + } + }, + _render: function (ctx) { + var canvas = this.getCircularText(); + this.cropCanvas(canvas); + this.set('width', canvas.width); + this.set('height', canvas.height); + ctx.drawImage(canvas, -this.width / 2, -this.height / 2, this.width, this.height); + this.setCoords(); + }, + toObject: function (propertiesToInclude) { + return this.callSuper('toObject', ['text', 'diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'].concat(propertiesToInclude)); + } + }); + fabric.CurvedText.fromObject = function (object, callback, forceAsync) { + return fabric.Object._fromObject('CurvedText', object, callback, forceAsync, 'curved-text'); + }; + })(typeof fabric !== 'undefined' ? fabric : require('fabric').fabric); + + const remote = require('@electron/remote') + var path = require('path'); + // 使用用户数据目录,避免写入需要管理员权限 + const exePath = remote.app.getPath('userData') + const { ipcRenderer } = require('electron') + const { dialog } = require('@electron/remote') + var jrQrcode = require('jr-qrcode') + var JsBarcode = require('jsbarcode') + ipcRenderer.send('get-sys-fonts') + ipcRenderer.send('get-scale-rate') + const { clipboard } = require('electron') + const dpi = 300 + // 内部剪贴板 + let clipboardData = { + objects: null, + offset: 10 // 粘贴位置偏移量 + }; + //var encrypt = new JSEncrypt(); + //encrypt.setPublicKey(`-----BEGIN PUBLIC KEY----- + //MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDAbKQ46WevL9PapJ+AlZqSzczkTovu7STH + //FmZmIJcNrwPPEWRR2PliUvtS28V+gNMWlu3wNWFAS5KGDbTtndIrP3hnlp9BDIcAcdNuJUM7 + //yJfAYBhtkqvj45Orvdd2RYRxhkQb34uTNXpM+biMH6Wd0wykNb9/VBNM0C12KswEdwIBEQ== + //-----END PUBLIC KEY-----`); + layui.use(['layer', 'slider', 'form', 'colorpicker'], function () { + let myDate = new Date() + let s_lan = '' + ipcRenderer.send('get-sys-language') + /* if(myDate.getFullYear()>2021 && myDate.getMonth() >= 1 && myDate.getDate() > 15){ + + }*/ + function GetFile() { + const params = new URLSearchParams(window.location.search); + return params + } + function langua_ge(lan = 'zh') { + $("[language='m']").each(function (i) { + $(this).html($(this).attr(lan)) + }) + + $("[language='t']").each(function (i) { + $(this).attr('title', $(this).attr(lan)) + }) + } + function language_str(str) { + let t = { + whetherSave: { zh: '是否保存当前文件?', ozh: '是否保存當前文件?', en: 'Do you want to save the current file?' }, + save: { zh: '保存', ozh: '保存', en: 'Save' }, + noSave: { zh: '不保存', ozh: '不保存', en: 'No saving' }, + cancel: { zh: '取消', ozh: '取消', en: 'Cancel' }, + saveSucc: { zh: '保存成功至', ozh: '保存成功至', en: 'Save successfully to' }, + saveFile: { zh: '保存文件', ozh: '保存文件', en: 'Save file' }, + display: { zh: '预览', ozh: '預覽', en: 'Display' }, + output: { zh: '导出', ozh: '導出', en: 'Export' }, + bg: { zh: '背景', ozh: '背景', en: 'Background' }, + selectPic: { zh: '请选择图片', ozh: '請選擇圖片', en: 'Please select a picture' }, + selectPic: { zh: '请选择文件', ozh: '請選擇文件', en: 'Please select a file' }, + img: { zh: '圖片', ozh: '圖片', en: 'Image' }, + simg: { zh: '合成圖片', ozh: '合成圖片', en: 'Synthesized Image' }, + text: { zh: '文本', ozh: '文本', en: 'Text' }, + "ctext": { zh: "圆形文本", ozh: "圆形文本", en: "Circle Text" }, + stext: { zh: '合成文本', ozh: '合成文本', en: 'Synthesized Text' }, + rect: { zh: '矩形', ozh: '矩形', en: 'Rectangle' }, + circ: { zh: '圓形', ozh: '圓形', en: 'Circle' }, + line: { zh: '直線', ozh: '直線', en: 'Line' }, + qrc: { zh: '二維碼', ozh: '二維碼', en: 'QR code' }, + barc: { zh: '條形碼', ozh: '條形碼', en: 'Barcode' }, + counter: { zh: '計數器', ozh: '计数器', en: 'Counter' }, + about: { zh: '关于Soon Design', ozh: '關於Soon Design', en: 'About Soon Design' }, + vers: { zh: '版本', ozh: '版本', en: 'Version' }, + comf: { zh: '确认', ozh: '確認', en: 'Select' }, + copyright: { zh: "© 2022 cardsoon Co., Ltd. All rights reserved.", ozh: "© 2022 cardsoon Co., Ltd. All rights reserved.", en: "© 2022 cardsoon Co., Ltd. All rights reserved." } + } + return t[str][s_lan] + } + + var $ = layui.$ + var layer = layui.layer, + slider = layui.slider, + colorpicker = layui.colorpicker + let addState = 0 //0是不添加 + let background_image, background_image1, background_image2 + let bg_version = 1 //front_bg1.png front_bg2.png + var zoom = 0 + $('#canvas1').attr('height', document.body.clientHeight - 100) + $('#canvas2').attr('height', document.body.clientHeight - 100) + $('#object_attribute').css('height', '470px') + $('#obj_list').css('height', document.body.clientHeight - 520) + $('.obj_list').css('height', $('#obj_list').height() - 32) + + $('.main').show() + $('#canvas1').attr('width', $('#canvas-div').width()) + $('#canvas2').attr('width', $('#canvas-div').width()) + var openAs = { + _name: '', + set name(val) { + if (val == '') { + $('title').html('Soon Design') + } else { + $('title').html('Soon Design - ' + val) + } + this._name = val + }, + get name() { + return this._name + } + } //被打开的文件名 + let objs1 = [] + let objs2 = [] + let step1 = { val: 0 }, + step2 = { val: 0 } + let step = step1 + let objs = objs1 + let recordJson1 = [], + recordJson2 = [], + recordJson = recordJson1, + recordObjs1 = [], + recordObjs2 = [], + recordObjs = recordObjs1 + let pre_objs1 = [], + pre_objs2 = [], + next_objs1 = [], + next_objs2 = [], + pre_objs = pre_objs1, + next_objs = next_objs1 + let pre_json1 = [], + pre_json2 = [], + next_json1 = [], + next_json2 = [], + pre_json = pre_json1, + next_json = next_json1 + var canvas1 = new fabric.Canvas('canvas1') + var canvas2 = new fabric.Canvas('canvas2') + fabric.Object.prototype.objectCaching = false + var ctx1 = canvas1.getSelectionContext() + var ctx2 = canvas2.getSelectionContext() + let is_bgi_add = false + canvas1.zoomToPoint(new fabric.Point(canvas1.width / 2, canvas1.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) + //console.log(canvas1.width / 2, canvas1.height / 2); + //console.log($("#canvas-div").width() / 1200 > 1 ? 1 : $("#canvas-div").width() / 1200); + canvas2.zoomToPoint(new fabric.Point(canvas2.width / 2, canvas2.height / 2), $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200) + zoom = $('#canvas-div').width() / 1200 > 1 ? 1 : $('#canvas-div').width() / 1200 + + // 初始化标尺 + initRulers(); + $('#source_front').width(zoom * $('#source_front').width()) + $('#source_back').width(zoom * $('#source_back').width()) + $('.canvas-container:eq(1)').hide() + var canvas = canvas1 + fabric.Object.prototype.set({ + borderColor: '#9013FE', + cornerColor: '#9013FE', //激活状态角落图标的填充颜色 + cornerStrokeColor: '#9013FE', //激活状态角落图标的边框颜色 + borderOpacityWhenMoving: 1, + borderScaleFactor: 1, + cornerSize: 6, + cornerStyle: 'circle', //rect,circle + centeredScaling: false, //角落放大缩小是否是以图形中心为放大原点 + centeredRotation: true, //旋转按钮旋转是否是左上角为圆心旋转 + transparentCorners: false, //激活状态角落的图标是否透明 + rotatingPointOffset: 20, //旋转距旋转体的距离 + padding: 0 + }) + canvas1.preserveObjectStacking = true + canvas2.preserveObjectStacking = true + + var front_list = [] + ipcRenderer.on('font-list', (event, data) => { + const newFontList = [] + data.map((item) => { + if (item.indexOf('"') === 0) { + newFontList.push(item.replace(/^"|"$/g, '')) + } else { + newFontList.push(item) + } + }) + front_list = newFontList + for (let item of front_list) { + $('#text_font_family').append("') + $("#circle_text_font_family").append(""); + $('#out_text_font_family').append("') + $('#barcode_font_family').append("') + $('#count_font_family').append("') + } + }) + ipcRenderer.on('sys-lan', (event, data) => { + let lang = localStorage.getItem('lang') + if (lang) { + s_lan = lang + langua_ge(lang) + $('#language_select').val(lang) + return + } + switch (data) { + case 'zh-CN': + s_lan = 'zh' + langua_ge('zh') + $('#language_select').val('zh') + localStorage.setItem('lang', 'zh') + break + case 'zh': + s_lan = 'zh' + langua_ge('zh') + $('#language_select').val('zh') + localStorage.setItem('lang', 'zh') + break + case 'zh-TW': + s_lan = 'ozh' + langua_ge('ozh') + $('#language_select').val('ozh') + localStorage.setItem('lang', 'ozh') + break + case 'en': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-AU': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-CA': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-GB': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-NZ': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-US': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + case 'en-ZA': + s_lan = 'en' + langua_ge('en') + $('#language_select').val('en') + localStorage.setItem('lang', 'en') + break + } + }) + ipcRenderer.on('close', (event, message) => { + layer.confirm( + '' + language_str('whetherSave') + '', + { + //是否保存当前文件? + btn: [language_str('save'), language_str('noSave'), language_str('cancel')], //['保存', '不保存', '取消'] + btn3: function (index, layero) { } + }, + function (index, layero) { + output(() => ipcRenderer.send('run-close')) + return + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let _g1 = [], + _g2 = [] + let left = 0, + top = 0 + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g1.push(fabric.util.object.clone(_o)) + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + ; (left = 0), (top = 0) + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g2.push(fabric.util.object.clone(_o)) + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let o = { f: j, b: b, fo: objs1, bo: objs2, fp: fp, bp: bp } + if (openAs.name != '') { + //打开的文件 + let fs = require('fs') + o.soonType = 2 + fs.writeFileSync(openAs.name, JSON.stringify(o)) + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + + ipcRenderer.send('run-close') + return + } + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.filePath == '') { + ipcRenderer.send('run-close') + return + } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon' + } + let fs = require('fs') + o.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(o)) + openAs.name = result.filePath + layer.msg(language_str('saveSucc') + openAs.name) //保存成功至 + saveHistory() + ipcRenderer.send('run-close') + }) + .catch((err) => { + console.log() + }) + layer.close(index) + }, + function (index) { + ipcRenderer.send('run-close') + } + ) + }) + + addBackground() //添加背景 白色卡片 + + initAligningGuidelines(canvas1) + initAligningGuidelines(canvas2) + //canvas.setZoom(0.5) + //recordObjs1.push(JSON.stringify(objs1)); + //recordJson1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + //recordObjs2.push(JSON.stringify(objs2)); + //recordJson2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + canvas.renderAll() //重新渲染画布 + var zoomSlider = slider.render({ + elem: '#zoomSlide', //绑定元素 + min: 25, + max: 500, + value: 100, + change(val) { + canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), val / 100) + zoom = val / 100 + //$(".canvas-container").css("transform","scale(" + val / 100 + ")") + } + }) + window.rotate = function (id) { + let angle = $(`#${id}`).attr('angle') - 0 + angle += 90 + $(`#${id}`).css('transform', `rotate(${angle}deg)`) + $(`#${id}`).attr('angle', angle) + } + $('#display').on('click', function () { + // 类型改变 + fabric.Image.fromURL('./public/images/front_bg' + bg_version + '_2.png', function (i1) { + i1.left = background_image.left + i1.top = background_image.top + let img1 = i1 + + fabric.Image.fromURL('./public/images/back_bg_2.png', function (i2) { + i2.left = background_image.left + i2.top = background_image.top + let img2 = i2 + fabric.Image.fromURL('./public/images/op_2.png', function (i3) { + i2.left = background_image.left + i2.top = background_image.top + let img3 = i3 + display_func(img1, img2, img3) + }) + }) + }) + }) + // 导出文件 + async function savePdf(buffer) { + let fs = require('fs'); + // 弹出保存对话框 + const { canceled, filePath } = await dialog.showSaveDialog({ + title: '保存文件', + defaultPath: path.join(exePath, 'output.pdf'), // 默认路径 + filters: [{ name: 'PDF 文件', extensions: ['pdf'] }] // 文件过滤器 + }); + + if (canceled) { + console.log('用户取消了保存操作'); + return; + } + + // 将Buffer写入PNG文件到用户选择的路径 + fs.writeFile(filePath, buffer, (err) => { + if (err) { + console.error('写入文件失败:', err); + } else { + layer.msg(language_str("saveSucc") + filePath);//'保存成功至' + console.log('PNG文件已保存至:', filePath); + } + }); + } + function display_func(img1, img2, img3) { + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $('#circle_text_control').hide() + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //"背景" + canvas1.discardActiveObject().renderAll() + canvas2.discardActiveObject().renderAll() + let _objs1 = canvas1.getObjects() + let g1 = [] + let g3 = [] + let g5 = [] + let left = 0 //左裁剪 + let top = 0 //顶裁剪 + let black_left = 0 //黑色左裁剪 + let black_top = 0 //黑色顶裁剪 + let all_left = 0 //预览图左裁剪 + let all_top = 0 //预览图顶裁剪 + for (let item of _objs1) { + let idx = getIndex(item, canvas1) + //console.log("------"); + //console.log(item.getCoords()); + //console.log(getCoordsMinX(background_image.getCoords())); + //console.log(getCoordsMinX(item.getCoords())); + //console.log(getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords())); + //console.log("------"); + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g3.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue //计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs1[idx - 1].type == 2) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs1[idx - 1].type == 3 && (item.get('fill') == '#000000' || (item.get('fill') == '#ffffff' && false))) || + ( + objs1[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs1[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs1[idx - 1].black == true) //勾选并入黑色图层 + // ( + // objs1[idx-1].type == 1//固定图片 + // && + // objs1[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g5.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g5.push(fabric.util.object.clone(item)) + } + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g1.push(fabric.util.object.clone(item)) + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + + // g3.push(img1) + let url3 = new fabric.Group(g3).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url5 = new fabric.Group(g5).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let _objs2 = canvas2.getObjects() + let g2 = [] + let g4 = [] + let g6 = [] + left = 0 + top = 0 + black_top = 0 + black_left = 0 + all_top = 0 + all_left = 0 + for (let item of _objs2) { + let idx = getIndex(item, canvas2) + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g4.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue //黑色字或计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs2[idx - 1].type == 2) { + continue //合成图片 + } + if (idx != 0 && objs2[idx - 1].type == 10) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs2[idx - 1].type == 3 && (item.get('fill') == '#000000' || (item.get('fill') == '#ffffff' && false))) || + ( + objs2[idx - 1].type == 11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs2[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs2[idx - 1].black == true) //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g6.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g6.push(fabric.util.object.clone(item)) + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g2.push(fabric.util.object.clone(item)) + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + // g4.push(img2) + let url4 = new fabric.Group(g4).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url6 = new fabric.Group(g6).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let white_url2 = canvas2.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + //console.log(canvas.getObjects()) + let w = document.body.clientWidth * 0.7 + 'px' + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + 'px' + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 'px' + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + 'px' + h1 = document.body.clientHeight * 0.8 - 150 + 'px' + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + let fs = require('fs'); + const Buffer = require('buffer').Buffer; + const buffer3 = Buffer.from(url3.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + const printPath3 = path.join(exePath, 'print3.png'); + fs.writeFileSync(printPath3, buffer3); + const buffer4 = Buffer.from(url4.replace(/^data:image\/\w+;base64,/, ""), 'base64'); + const printPath4 = path.join(exePath, 'print4.png'); + fs.writeFileSync(printPath4, buffer4); + // 创建 PDF 实例 + const pdf = new jspdf.jsPDF({ + orientation: 'landscape', + unit: 'px', + format: [1012, 648] + }); + let btns = [language_str('output')] + if ((objs1.length != 0) & (objs2.length == 0)) { + //只有正面 + let img = new Image() + img.src = printPath3 + + pdf.addImage(img, 'JPEG', 0, 0, 1012, 648) + btns.push('打印正面') + } else if ((objs1.length == 0) & (objs2.length != 0)) { + //只有背面 + let img = new Image() + img.src = printPath4 + + pdf.addImage(img, 'JPEG', 0, 0, 1012, 648) + btns.push('打印背面') + } else { + let img = new Image() + img.src = printPath3 + + pdf.addImage(img, 'JPEG', 0, 0, 1012, 648) + + pdf.addPage() + let img2 = new Image() + img2.src = printPath4 + pdf.addImage(img2, 'JPEG', 0, 0, 1012, 648) + btns.push('打印正面') + btns.push('打印背面') + } + + var pdfBlob = pdf.output('blob') + var pdfBuffer = pdf.output('arraybuffer') + + layer.open({ + type: 1, + area: [w, h], + title: language_str('display'), //不显示标题栏"预览" + shadeClose: true, //点击遮罩关闭 + content: ` +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
`, + btn: btns, //'导出' + btn1: function (index, layero) { + savePdf(Buffer.from(pdfBuffer)) + }, + btn2: function () { + if (btns[1] === '打印正面') { + printJS({ printable: printPath3, type: 'image', style: 'img { width: 100%; height: auto; }' }) + } else { + printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }) + } + }, + btn3: function () { + //打印背面 + printJS({ printable: printPath4, type: 'image', style: 'img { width: 100%; height: auto; }' }) + } + }) + } + function output(callback = null, _save = save) { + // 类型改变 + fabric.Image.fromURL('./public/images/op_2.png', function (i1) { + i1.left = background_image.left + i1.top = background_image.top + let img1 = i1 + fabric.Image.fromURL('./public/images/op_2.png', function (i2) { + i2.left = background_image.left + i2.top = background_image.top + let img2 = i2 + + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //"背景" + canvas1.discardActiveObject().renderAll() + canvas2.discardActiveObject().renderAll() + let _objs1 = canvas1.getObjects() + let g1 = [] + let g3 = [] + let g5 = [] //黑色图层 + let left = 0 //左裁剪 + let top = 0 //顶裁剪 + let black_left = 0 //黑色左裁剪 + let black_top = 0 //黑色顶裁剪 + let all_left = 0 //预览图左裁剪 + let all_top = 0 //预览图顶裁剪 + for (let item of _objs1) { + let idx = getIndex(item, canvas1) + //console.log("------"); + //console.log(item.getCoords()); + //console.log(getCoordsMinX(background_image.getCoords())); + //console.log(getCoordsMinX(item.getCoords())); + //console.log(getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords())); + //console.log("------"); + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g3.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue //计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs1[idx - 1].type == 2) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs1[idx - 1].type == 3 && item.get('fill') == '#000000') || + ( + objs1[idx - 1].type == 11 + && + item.get("fill") == "#000000" + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs1[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs1[idx - 1].black == true) //勾选并入黑色图层 + // || + // ( + // objs1[idx-1].type == 1//固定图片 + // && + // objs1[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g5.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g5.push(fabric.util.object.clone(item)) + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g1.push(fabric.util.object.clone(item)) + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + g3.push(img1) + let url3 = new fabric.Group(g3).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url5 = new fabric.Group(g5).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let _objs2 = canvas2.getObjects() + let g2 = [] + let g4 = [] + let g6 = [] //黑色图层 + left = 0 + top = 0 + black_left = 0 //黑色左裁剪 + black_top = 0 //黑色顶裁剪 + all_left = 0 //黑色左裁剪 + all_top = 0 //黑色顶裁剪 + for (let item of _objs2) { + let idx = getIndex(item, canvas2) + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g4.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue //计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs2[idx - 1].type == 2) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs2[idx - 1].type == 3 && (item.get('fill') == '#000000' || (item.get('fill') == '#ffffff' && false))) || + ( + objs2[idx - 1].type ==11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs2[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs2[idx - 1].black == true) //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g6.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g6.push(fabric.util.object.clone(item)) + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g2.push(fabric.util.object.clone(item)) + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + g4.push(img2) + let url4 = new fabric.Group(g4).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url6 = new fabric.Group(g6).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let white_url2 = canvas2.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + //console.log(canvas.getObjects()) + let w = document.body.clientWidth * 0.7 + 'px' + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + 'px' + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 'px' + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + 'px' + h1 = document.body.clientHeight * 0.8 - 150 + 'px' + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + + let op = { frontColorPic: '', backColorPic: '', frontBlackPic: '', backBlackPic: '', frontData: [], backData: [], flag: 1, frontDisplayPic: url3, backDisplayPic: url4 } + if ((objs1.length != 0) & (objs2.length == 0)) { + op.flag = 2 + } else if ((objs1.length == 0) & (objs2.length != 0)) { + op.flag = 3 + } + op.frontColorPic = desEncrypt(url1) + op.backColorPic = desEncrypt(url2) + //op.frontBlPic = url5; + //op.backBlPic = url6; + op.frontBlackPic = desEncrypt(url5) + op.backBlackPic = desEncrypt(url6) + for (let item of _objs1) { + let idx = getIndex(item, canvas1) + if (idx == 0) continue //background-image + let type = objs1[idx - 1].type + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 1, + name: objs1[idx - 1].name, + FontColor: objs1[idx - 1].black ? '#000000' : '@', + transparency: item.get('opacity'), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs1[idx - 1].circleCenter || 0 + }); + } else if (type == 4) { + //out_text + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 3, + name: objs1[idx - 1].name, + FontFamily: item.get('fontFamily'), + DefaultText: item.get('text'), + FontBackgroudColor: item.get('textBackgroundColor'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + Exceed: objs1[idx - 1].exceed, + AutoWrap: objs1[idx - 1].autoWrap, + FixSize: objs1[idx - 1].fixSize, + Bold: item.get('fontWeight') == 'bold', + Underline: item.get('underline'), + Italic: item.get('fontStyle') == 'italic', + Strikeout: item.get('linethrough'), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)), + align: objs1[idx - 1].align + }) + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 4, + name: objs1[idx - 1].name, + FontColor: objs1[idx - 1].color, + DefaultText: objs1[idx - 1].val, + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 9) { + //条形码 + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 5, + name: objs1[idx - 1].name, + FontSize: parseInt(objs1[idx - 1].fontSize), + DefaultText: objs1[idx - 1].val, + HideText: objs1[idx - 1].show, + FontFamily: objs1[idx - 1].font, + FontColor: objs1[idx - 1].color, + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 10) { + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 6, + name: objs1[idx - 1].name, + FontFamily: item.get('fontFamily'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + start: objs1[idx - 1].start, + add: parseInt(objs1[idx - 1].add), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } + } + //obj2 + for (let item of _objs2) { + let idx = getIndex(item, canvas2) + if (idx == 0) continue //background-image + let type = objs2[idx - 1].type + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 1, + name: objs2[idx - 1].name, + FontColor: objs2[idx - 1].black ? '#000000' : '@', + transparency: item.get('opacity'), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs2[idx - 1].circleCenter || 0 + }); + } else if (type == 4) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 3, + name: objs2[idx - 1].name, + FontFamily: item.get('fontFamily'), + DefaultText: item.get('text'), + FontSize: parseInt(item.get('fontSize')), + FontBackgroudColor: item.get('textBackgroundColor'), + FontColor: item.get('fill'), + Exceed: objs2[idx - 1].exceed, + AutoWrap: objs2[idx - 1].autoWrap, + FixSize: objs2[idx - 1].fixSize, + Bold: item.get('fontWeight') == 'bold', + Underline: item.get('underline'), + Italic: item.get('fontStyle') == 'italic', + Strikeout: item.get('linethrough'), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)), + align: objs2[idx - 1].align + }) + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 4, + name: objs2[idx - 1].name, + FontColor: objs2[idx - 1].color, + DefaultText: objs2[idx - 1].val, + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 9) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 5, + name: objs2[idx - 1].name, + DefaultText: objs2[idx - 1].val, + HideText: objs1[idx - 1].show, + FontSize: parseInt(objs2[idx - 1].fontSize), + FontFamily: objs2[idx - 1].font, + FontColor: objs2[idx - 1].color, + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 10) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 6, + name: objs2[idx - 1].name, + FontFamily: item.get('fontFamily'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + start: parseInt(objs2[idx - 1].start), + add: parseInt(objs2[idx - 1].add), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } + } + _save(op, callback) + //return false 开启该代码可禁止点击该按钮关闭 + }) + }) + } + $('#output').on('click', function () { + output(null, saveAs) + return + // 类型改变 + fabric.Image.fromURL('./public/images/op_2.png', function (i1) { + i1.left = background_image.left + i1.top = background_image.top + let img1 = i1 + fabric.Image.fromURL('./public/images/op_2.png', function (i2) { + i2.left = background_image.left + i2.top = background_image.top + let img2 = i2 + + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //"背景" + canvas1.discardActiveObject().renderAll() + canvas2.discardActiveObject().renderAll() + let _objs1 = canvas1.getObjects() + let g1 = [] + let g3 = [] + let g5 = [] //黑色图层 + let left = 0 //左裁剪 + let top = 0 //顶裁剪 + let black_left = 0 //黑色左裁剪 + let black_top = 0 //黑色顶裁剪 + let all_left = 0 //预览图左裁剪 + let all_top = 0 //预览图顶裁剪 + for (let item of _objs1) { + let idx = getIndex(item, canvas1) + //console.log("------"); + //console.log(item.getCoords()); + //console.log(getCoordsMinX(background_image.getCoords())); + //console.log(getCoordsMinX(item.getCoords())); + //console.log(getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords())); + //console.log("------"); + + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g3.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs1[idx - 1].type == 10 || objs1[idx - 1].type == 4)) { + continue //计数器 或 合成文字 + } + if (idx != 0 && (objs1[idx - 1].type == 8 || objs1[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs1[idx - 1].type == 2) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs1[idx - 1].type == 3 && (item.get('fill') == '#000000' || (item.get('fill') == '#ffffff' && false))) || + ( + objs2[idx - 1].type ==11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs1[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs1[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs1[idx - 1].black == true) //勾选并入黑色图层 + // ( + // objs1[idx-1].type == 1//固定图片 + // && + // objs1[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g5.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g5.push(fabric.util.object.clone(item)) + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g1.push(fabric.util.object.clone(item)) + } + let url1 = new fabric.Group(g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + g3.push(img1) + let url3 = new fabric.Group(g3).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url5 = new fabric.Group(g5).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let _objs2 = canvas2.getObjects() + let g2 = [] + let g4 = [] + let g6 = [] //黑色图层 + left = 0 + top = 0 + black_left = 0 //黑色左裁剪 + black_top = 0 //黑色顶裁剪 + all_left = 0 //黑色左裁剪 + all_top = 0 //黑色顶裁剪 + for (let item of _objs2) { + let idx = getIndex(item, canvas2) + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > all_left) { + all_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > all_top) { + all_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g4.push(fabric.util.object.clone(item)) + if (idx != 0 && (objs2[idx - 1].type == 10 || objs2[idx - 1].type == 4)) { + continue //计数器 或 合成文字 + } + if (idx != 0 && (objs2[idx - 1].type == 8 || objs2[idx - 1].type == 9)) { + continue //二维码 条形码 + } + if (idx != 0 && objs2[idx - 1].type == 2) { + continue //合成图片 + } + if ( + idx != 0 && + ((objs2[idx - 1].type == 3 && (item.get('fill') == '#000000' || (item.get('fill') == '#ffffff' && false))) || + ( + objs2[idx - 1].type ==11 + && + (item.get("fill") == "#000000" || (item.get("fill") == "#ffffff" && false)) + //圆形文字 && 黑色 + ) || + //文字 && 黑色 + (objs2[idx - 1].type == 5 && //矩形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 6 && //圆形 + (((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('stroke') == '') || //黑填充 透明边框 + (item.get('fill') == '' && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //透明填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || //黑填充 黑边框 + ((item.get('fill') == '#000000' || item.get('fill') == '#ffffff') && item.get('strokeWidth') == 0))) || //黑填充 0边框 + (objs2[idx - 1].type == 7 && //直线 + (item.get('stroke') == '#000000' || item.get('stroke') == '#ffffff')) || + objs2[idx - 1].black == true) //勾选并入黑色图层 + // ( + // objs2[idx-1].type == 1//固定图片 + // && + // objs2[idx-1].black == true //勾选并入黑色图层 + // ) + ) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > black_left) { + black_left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > black_top) { + black_top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g6.push(fabric.util.object.clone(item)) + continue //黑色文字 黑色矩形 黑色线条 黑色圆形 + } else if (idx == 0) { + g6.push(fabric.util.object.clone(item)) + } + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(item.getCoords()) > left) { + left = -(getCoordsMinX(item.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(item.getCoords()) > top) { + top = -(getCoordsMinY(item.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + g2.push(fabric.util.object.clone(item)) + } + let url2 = new fabric.Group(g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let white_url1 = canvas1.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + g4.push(img2) + let url4 = new fabric.Group(g4).toDataURL({ + height: 648, + width: 1012, + top: all_top / zoom, + left: all_left / zoom + }) + let url6 = new fabric.Group(g6).toDataURL({ + height: 648, + width: 1012, + top: black_top / zoom, + left: black_left / zoom + }) + let white_url2 = canvas2.getObjects()[0].toDataURL({ + height: 648, + width: 1012 + }) + //console.log(canvas.getObjects()) + let w = document.body.clientWidth * 0.7 + 'px' + let h = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 150 + 'px' + let h1 = (document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 + 'px' + + if ((document.body.clientWidth * 0.7 - 40 - 0.06 * document.body.clientWidth * 0.7) * 0.62548 > document.body.clientHeight) { + //高度超出,则按高度计算 + h = document.body.clientHeight * 0.8 + 'px' + h1 = document.body.clientHeight * 0.8 - 150 + 'px' + w = ((document.body.clientHeight * 0.8 - 150) / 0.62548 + 40) / 0.94 + } + + let op = { frontColorPic: '', backColorPic: '', frontBlackPic: '', backBlackPic: '', frontData: [], backData: [], flag: 1, frontDisplayPic: url3, backDisplayPic: url4 } + if ((objs1.length != 0) & (objs2.length == 0)) { + op.flag = 2 + } else if ((objs1.length == 0) & (objs2.length != 0)) { + op.flag = 3 + } + op.frontColorPic = desEncrypt(url1) + op.backColorPic = desEncrypt(url2) + //op.frontBlPic = url5; + //op.backBlPic = url6; + op.frontBlackPic = desEncrypt(url5) + op.backBlackPic = desEncrypt(url6) + for (let item of _objs1) { + let idx = getIndex(item, canvas1) + if (idx == 0) continue //background-image + let type = objs1[idx - 1].type + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 1, + name: objs1[idx - 1].name, + FontColor: objs1[idx - 1].black ? '#000000' : '@', + transparency: item.get('opacity'), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + op.frontData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image1.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image1.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs1[idx - 1].circleCenter || 0 + }); + } else if (type == 4) { + //out_text + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 3, + name: objs1[idx - 1].name, + FontFamily: item.get('fontFamily'), + DefaultText: item.get('text'), + FontBackgroudColor: item.get('textBackgroundColor'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + Exceed: objs1[idx - 1].exceed, + AutoWrap: objs1[idx - 1].autoWrap, + FixSize: objs1[idx - 1].fixSize, + Bold: item.get('fontWeight') == 'bold', + Underline: item.get('underline'), + Italic: item.get('fontStyle') == 'italic', + Strikeout: item.get('linethrough'), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)), + align: objs1[idx - 1].align + }) + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 4, + name: objs1[idx - 1].name, + FontColor: objs1[idx - 1].color, + DefaultText: objs1[idx - 1].val, + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 9) { + //条形码 + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 5, + name: objs1[idx - 1].name, + FontSize: parseInt(objs1[idx - 1].fontSize), + DefaultText: objs1[idx - 1].val, + HideText: objs1[idx - 1].show, + FontFamily: objs1[idx - 1].font, + FontColor: objs1[idx - 1].color, + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 10) { + let xy = getAbsoluteXY(item) + op.frontData.push({ + type: 6, + name: objs1[idx - 1].name, + FontFamily: item.get('fontFamily'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + start: objs1[idx - 1].start, + add: parseInt(objs1[idx - 1].add), + x: parseInt((xy[0] - background_image1.left).toFixed(0)), + y: parseInt((xy[1] - background_image1.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } + } + //obj2 + for (let item of _objs2) { + let idx = getIndex(item, canvas2) + if (idx == 0) continue //background-image + let type = objs2[idx - 1].type + if (type == 2) { + //out_pic + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 1, + name: objs2[idx - 1].name, + FontColor: objs2[idx - 1].black ? '#000000' : '@', + transparency: item.get('opacity'), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 3) { + //text + /*2022-07-26 黑色并入黑色图层 + if(item.get("fill") == "#000000"){ + //纯黑 + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)) + }); + } + */ + } else if (type == 11) { + //circle_text + op.backData.push({ + "type" : 2, + "Text" : item.get("text"), + "FontFamily" : item.get("fontFamily"), + "FontSize" : parseInt(item.get("fontSize")), + "FontColor" : "#000000", + "FontBackgroudColor" : item.get("textBackgroundColor"), + "Bold" : item.get("fontWeight") == "bold", + "Underline" : item.get("underline"), + "Italic" : item.get("fontStyle") == "italic", + "Strikeout" : item.get("linethrough"), + "x" : parseInt((item.get("left") - background_image2.left).toFixed(0)), + "y" : parseInt((item.get("top") - background_image2.top).toFixed(0)), + "h" : parseInt((item.get("scaleY") * item.get("height")).toFixed(0)), + "w" : parseInt((item.get("scaleX") * item.get("width")).toFixed(0)), + "rotate" : parseInt(item.get("angle").toFixed(0)), + "circleCenter" : objs2[idx - 1].circleCenter || 0 + }); + } else if (type == 4) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 3, + name: objs2[idx - 1].name, + FontFamily: item.get('fontFamily'), + DefaultText: item.get('text'), + FontSize: parseInt(item.get('fontSize')), + FontBackgroudColor: item.get('textBackgroundColor'), + FontColor: item.get('fill'), + Exceed: objs2[idx - 1].exceed, + AutoWrap: objs2[idx - 1].autoWrap, + FixSize: objs2[idx - 1].fixSize, + Bold: item.get('fontWeight') == 'bold', + Underline: item.get('underline'), + Italic: item.get('fontStyle') == 'italic', + Strikeout: item.get('linethrough'), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)), + align: objs2[idx - 1].align + }) + } else if (type == 8) { + //二维码 + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 4, + name: objs2[idx - 1].name, + FontColor: objs2[idx - 1].color, + DefaultText: objs2[idx - 1].val, + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 9) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 5, + name: objs2[idx - 1].name, + DefaultText: objs2[idx - 1].val, + HideText: objs1[idx - 1].show, + FontSize: parseInt(objs2[idx - 1].fontSize), + FontFamily: objs2[idx - 1].font, + FontColor: objs2[idx - 1].color, + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } else if (type == 10) { + let xy = getAbsoluteXY(item) + op.backData.push({ + type: 6, + name: objs2[idx - 1].name, + FontFamily: item.get('fontFamily'), + FontSize: parseInt(item.get('fontSize')), + FontColor: item.get('fill'), + start: parseInt(objs2[idx - 1].start), + add: parseInt(objs2[idx - 1].add), + x: parseInt((xy[0] - background_image2.left).toFixed(0)), + y: parseInt((xy[1] - background_image2.top).toFixed(0)), + h: parseInt((item.get('scaleY') * item.get('height')).toFixed(0)), + w: parseInt((item.get('scaleX') * item.get('width')).toFixed(0)), + rotate: parseInt(item.get('angle').toFixed(0)) + }) + } + } + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'CS File Type', extensions: ['cs'] }] + }) + .then((result) => { + if (result.canceled) return + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.cs' + } + let fs = require('fs') + op.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(op)) + }) + .catch((err) => { + console.log() + }) + //return false 开启该代码可禁止点击该按钮关闭 + }) + }) + }) + var _keyctrl = false, + _keyc = false, + _textEdit = false, + _inputEdit = false + $('input').focus(function () { + console.log(1) + _inputEdit = true + }) + $('input').blur(function () { + _inputEdit = false + }) + $('textarea').focus(function () { + console.log(1) + _inputEdit = true + }) + $('textarea').blur(function () { + _inputEdit = false + }) + $('body').keyup(function (event) { + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.keyCode == 91 || event.keyCode == 93) { // Ctrl 键或 Meta 键(Mac Cmd)释放 + _keyctrl = false + } + if (event.keyCode == 67) { + _keyc = false + } + }) + $('body').keydown(function (event) { + // 统一的快捷键处理(兼容 Windows/Linux: Ctrl, Mac: Cmd/Meta) + const isModifierKey = event.ctrlKey || event.metaKey; // 兼容三端 + + // 快捷键处理(在输入框或文本区域中时不触发,除了删除相关的) + if (!_inputEdit && !_textEdit) { + // CTRL/CMD+N - 新建 + if (isModifierKey && event.keyCode == 78) { + event.preventDefault(); + $('#new').click(); + return; + } + // CTRL/CMD+O - 打开 + if (isModifierKey && event.keyCode == 79) { + event.preventDefault(); + $('#open').click(); + return; + } + // CTRL/CMD+S - 保存 + if (isModifierKey && !event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + $('#save').click(); + return; + } + // CTRL/CMD+SHIFT+S - 保存副本 + if (isModifierKey && event.shiftKey && event.keyCode == 83) { + event.preventDefault(); + output(null, saveAs); + return; + } + // CTRL/CMD+Z - 还原 + if (isModifierKey && !event.shiftKey && event.keyCode == 90) { + event.preventDefault(); + $('#previous').click(); + return; + } + // CTRL/CMD+Y - 重做 + if (isModifierKey && !event.shiftKey && event.keyCode == 89) { + event.preventDefault(); + $('#next').click(); + return; + } + // CTRL/CMD+C - 复制 + if (isModifierKey && event.keyCode == 67 && event.target.nodeName == 'BODY') { + event.preventDefault(); + copySelection(); + return; + } + // CTRL/CMD+V - 粘贴 + if (isModifierKey && event.keyCode == 86 && event.target.nodeName == 'BODY') { + event.preventDefault(); + pasteFromClipboard(); + return; + } + // CTRL/CMD+SHIFT+DEL - 清空 + if (isModifierKey && event.shiftKey && event.keyCode == 46) { + event.preventDefault(); + $('#clean').click(); + return; + } + // CTRL/CMD+DEL - 删除 + if (isModifierKey && !event.shiftKey && event.keyCode == 46) { + event.preventDefault(); + $('#delete').click(); + return; + } + } + // 兼容三端的 modifier key 检测(Windows/Linux: Ctrl, Mac: Cmd/Meta) + if (event.keyCode == 17 || event.metaKey) { // Ctrl 键或 Meta 键(Mac Cmd) + _keyctrl = true + if (_keyc && _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 67) { + _keyc = true + if (_keyctrl & _textEdit) { + //同时按下且在编辑状态 + setTimeout("clipboard.writeText( (clipboard.readText()+' ') );", 1) + //clipboard.writeText((clipboard.readText()+' ')) + } + } + if (event.keyCode == 37) { + //left + let v = -1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5 + } + let x = canvas.getActiveObject().get('left') + x += v + canvas.getActiveObject().set('left', x) + canvas.renderAll() + recordState() + updateControls() + } + if (event.keyCode == 38) { + //up + let v = -1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = -5 + } + let y = canvas.getActiveObject().get('top') + y += v + canvas.getActiveObject().set('top', y) + canvas.renderAll() + recordState() + updateControls() + } + if (event.keyCode == 39) { + //right + let v = 1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5 + } + let x = canvas.getActiveObject().get('left') + x += v + canvas.getActiveObject().set('left', x) + canvas.renderAll() + recordState() + updateControls() + } + if (event.keyCode == 40) { + //down + let v = +1 + if (_keyctrl || event.ctrlKey || event.metaKey) { + v = 5 + } + let y = canvas.getActiveObject().get('top') + y += v + canvas.getActiveObject().set('top', y) + canvas.renderAll() + recordState() + updateControls() + } + // 普通删除键(DEL键,没有按CTRL/CMD) + if (event.keyCode == 46 && !isModifierKey && !_inputEdit && !_textEdit) { + let actObjs = canvas.getActiveObjects() + let allObjs = canvas.getObjects() + let flag = 0 + for (let item of actObjs) { + if (item != allObjs[0]) { + let i = getIndex(item) //0是背景,所以从1开始,但objs不包含背景图片,故要-1 + objs.splice(i - 1, 1) + canvas.remove(item) + flag = 1 + } + } + updateList() + canvas.discardActiveObject() + if (flag == 1) { + recordState() + canvas.renderAll() + handleObjectSelected() + } + } + }) + let pre_add_image + $('#addPic').click(function () { + OpenDialog() + function OpenDialog() { + dialog + .showOpenDialog({ + title: language_str('selectPic'), //请选择图片 + buttonLabel: language_str('comf'), //"确认" + filters: [{ name: 'Image File Type', extensions: ['png', 'jpg', 'jpeg'] }] + }) + .then((result) => { + if (result.canceled) return + //console.log(result.filePaths) + fabric.Image.fromURL(result.filePaths[0], function (image) { + //image.left = pointer.x; + //image.top = pointer.y; + image.setSrc(image.toDataURL(), function (image) { + pre_add_image = image + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 1 + }) + //canvas.add(image); + //objs.push({type: 1}); + //canvas.renderAll(); + //updateList(); + //recordState(); + }) + }) + .catch((err) => { + console.log(err) + }) + } + }) + $('#addOutPic').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 2 + }) + $('#addText').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 3 + }) + $("#addCircleText").click(function () { + background_image.hoverCursor = "crosshair"; + $(this).css("background-color", "#646E76"); + + $("#addPic").css("background-color", ""); + $("#addOutPic").css("background-color", ""); + $("#addText").css("background-color", ""); + $("#addOutText").css("background-color", ""); + $("#addRect").css("background-color", ""); + $("#addCircle").css("background-color", ""); + $("#addLine").css("background-color", ""); + $("#addQrCode").css("background-color", ""); + $("#addBarCode").css("background-color", ""); + $("#addCounter").css("background-color", ""); + addState = 11; + }); + $('#addOutText').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 4 + }) + $('#addRect').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 5 + }) + $('#addCircle').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 6 + }) + $('#addLine').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 7 + }) + $('#addQrCode').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addBarCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 8 + }) + $('#addBarCode').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addCounter').css('background-color', '') + addState = 9 + }) + $('#addCounter').click(function () { + background_image.hoverCursor = 'crosshair' + $(this).css('background-color', '#646E76') + + $('#addPic').css('background-color', '') + $('#addOutPic').css('background-color', '') + $('#addText').css('background-color', '') + $("#addCircleText").css("background-color", ""); + $('#addOutText').css('background-color', '') + $('#addRect').css('background-color', '') + $('#addCircle').css('background-color', '') + $('#addLine').css('background-color', '') + $('#addQrCode').css('background-color', '') + $('#addBarCode').css('background-color', '') + addState = 10 + }) + $('#version_change').click(function () { + if (bg_version == 1) { + bg_version = 2 + } else { + bg_version = 1 + } + // 类型改变 + $('#source_front').attr('src', './public/images/front_bg' + bg_version + '_2.png') + canvas.renderAll() + }) + $('#front_side').click(function () { + $(this).addClass('ui-button-active') + $('#back_side').removeClass('ui-button-active') + $('.canvas-container:eq(0)').show() + $('.canvas-container:eq(1)').hide() + console.log('------') + console.log(recordObjs1) + console.log('------') + canvas = canvas1 + objs = objs1 + background_image = background_image1 + next_objs = next_objs1 + pre_objs = pre_objs1 + next_json = next_json1 + pre_json = pre_json1 + recordObjs = recordObjs1 + recordJson = recordJson1 + step = step1 + updateList() + + $('#line_control').hide() + $('#base_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //背景 + }) + $('#back_side').click(function () { + $(this).addClass('ui-button-active') + $('#front_side').removeClass('ui-button-active') + $('.canvas-container:eq(0)').hide() + $('.canvas-container:eq(1)').show() + canvas = canvas2 + objs = objs2 + background_image = background_image2 + next_objs = next_objs2 + pre_objs = pre_objs2 + next_json = next_json2 + pre_json = pre_json2 + recordObjs = recordObjs2 + recordJson = recordJson2 + step = step2 + updateList() + $('#line_control').hide() + $('#base_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //"背景" + }) + $('#top_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + _objs[0].set('top', background_image.get('top')) + canvas.renderAll() + return + } + let top = 0 + for (let item of _objs) { + let t_top = item.get('top') + if (t_top < top) { + top = t_top + } + } + groupItems = [] + for (let item of _objs) { + item.set('top', top) + groupItems.push(item) + } + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#bottom_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get('strokeWidth') + } + _objs[0].set('top', background_image.get('top') + background_image.get('height') - _objs[0].get('height') * _objs[0].get('scaleY') - other) + canvas.renderAll() + return + } + let bottom = 0 + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + let t_b = item.get('top') + item.get('height') * item.get('scaleY') + other + if (t_b > bottom) { + bottom = t_b + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + item.set('top', bottom - item.get('height') * item.get('scaleY') - other) + groupItems.push(item) + } + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#center_y_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get('strokeWidth') + } + _objs[0].set('top', background_image.get('top') + (background_image.get('height') - (_objs[0].get('height') + other) * _objs[0].get('scaleY')) / 2) + canvas.renderAll() + return + } + let top = 0 + for (let item of _objs) { + let t_top = item.get('top') + if (t_top < top) { + top = t_top + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + item.set('top', top + (item.group.height - (item.get('height') + other) * item.get('scaleY')) / 2) + groupItems.push(item) + } + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#left_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + _objs[0].set('left', background_image.get('left')) + canvas.renderAll() + return + } + let left = 0 + for (let item of _objs) { + let t_left = item.get('left') + if (t_left < left) { + left = t_left + } + } + groupItems = [] + for (let item of _objs) { + item.set('left', left) + groupItems.push(item) + } + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#right_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + _objs[0].set('left', background_image.get('left') + background_image.get('width') - _objs[0].get('width') * _objs[0].get('scaleX') - other) + canvas.renderAll() + return + } + let right = 0 + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + let t_r = item.get('left') + item.get('width') * item.get('scaleX') + other + if (t_r > right) { + right = t_r + } + } + let groupItems = [] + + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + item.set('left', right - item.get('width') * item.get('scaleX') - other) + groupItems.push(item) + } + //BUG1 + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#center_x_alignment').click(function () { + let _objs = canvas.getActiveObjects() + if (_objs.length == 1) { + //只选中了一个 + let idx = getIndex(_objs[0]) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = _objs[0].get('strokeWidth') + } + _objs[0].set('left', background_image.get('left') + (background_image.get('width') - (_objs[0].get('width') + other) * _objs[0].get('scaleX')) / 2) + canvas.renderAll() + return + } + let left = 0 + for (let item of _objs) { + let t_left = item.get('left') + if (t_left < left) { + left = t_left + } + } + groupItems = [] + for (let item of _objs) { + let idx = getIndex(item) + let type = objs[idx - 1].type + let other = 0 //strokeWidth + if (type == 5 || type == 6 || type == 7) { + other = item.get('strokeWidth') + } + item.set('left', left + (item.group.width - (item.get('width') + other) * item.get('scaleX')) / 2) + groupItems.push(item) + } + canvas.discardActiveObject() + let sel = new fabric.ActiveSelection(groupItems, { + canvas: canvas + }) + canvas.setActiveObject(sel) + canvas.requestRenderAll() + canvas.renderAll() + }) + $('#up_obj').click(function () { + //上移,放到前面 数组中在后面 + let idx = getIndex(canvas.getActiveObject()) + if (idx + 1 != canvas.getObjects().length) { + canvas.bringForward(canvas.getActiveObject()) + canvas.renderAll() + let temp = objs[idx - 1] + objs[idx - 1] = objs[idx] + objs[idx] = temp + //console.log(objs); + //[objs[idx-2],objs[idx-1]] = [objs[idx-1],objs[idx-2]]; + + recordState() + updateList() + selectObj(idx) + } + }) + $('#down_obj').click(function () { + //下移,放到后面 数组中在前面 + let idx = getIndex(canvas.getActiveObject()) + if (idx != 1) { + canvas.sendBackwards(canvas.getActiveObject()) + canvas.renderAll() + //[objs[idx-2],objs[idx-1]] = [objs[idx-1],objs[idx-2]]; + + let temp = objs[idx - 1] + objs[idx - 1] = objs[idx - 2] + objs[idx - 2] = temp + recordState() + updateList() + selectObj(idx - 2) + } + }) + $('#set_bg').click(function () { + //设置为背景 + let obj = canvas.getActiveObject() + let idx = getIndex(canvas.getActiveObject()) + const scaleX = 1012 / obj.width + const scaleY = 648 / obj.height + obj.set({ + scaleX: scaleX, + scaleY: scaleY, + left: ($('#canvas1').width() - 1012) / 2, + top: ($('#canvas1').height() - 648) / 2 + }) + $('#text_w').val(1012) + $('#text_h').val(648) + $('#text_x').val(0) + $('#text_y').val(0) + canvas.sendToBack(canvas.getActiveObject()) + canvas.bringForward(canvas.getActiveObject()) + canvas.renderAll() + let objItem = objs.splice(idx - 1, 1)[0] + objs.unshift(objItem) + recordState() + updateList() + selectObj(0) + }) + $('#previous').click(function () { + //撤销一次操作 + if (step.val == 0) { + return + } + + step.val-- + console.log(recordJson[step.val]) + canvas.loadFromJSON(recordJson[step.val]) + if ($('#front_side').hasClass('ui-button-active')) { + //objs = JSON.parse(recordObjs[step.val]); + objs1 = JSON.parse(recordObjs[step.val]) + objs = objs1 + } else if ($('#back_side').hasClass('ui-button-active')) { + //objs = JSON.parse(recordObjs[step.val]); + objs2 = JSON.parse(recordObjs[step.val]) + objs = objs2 + } + + background_image = canvas.getObjects()[0] + canvas.renderAll() + //recordState(); + updateList() + + selectObj(-1) + + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + }) + $('#next').click(function () { + //撤销一次撤销操作 + if (step.val >= recordObjs.length - 1) { + return + } + + ++step.val + canvas.loadFromJSON(recordJson[step.val]) + if ($('#front_side').hasClass('ui-button-active')) { + //objs = JSON.parse(recordObjs[step.val]); + objs1 = JSON.parse(recordObjs[step.val]) + objs = objs1 + } else if ($('#back_side').hasClass('ui-button-active')) { + //objs = JSON.parse(recordObjs[step.val]); + objs2 = JSON.parse(recordObjs[step.val]) + objs = objs2 + } + background_image = canvas.getObjects()[0] + canvas.renderAll() + selectObj(-1) + + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + updateList() + }) + $('#clean').click(function () { + objs.splice(0, objs.length) + + let allObjs = canvas.getObjects() + for (let item of allObjs) { + if (item != allObjs[0]) { + canvas.remove(item) + } + } + updateList() + recordState() + }) + $('#rotate').click(function () { + let obj = canvas.getActiveObject() + obj.rotate(Math.round((obj.get('angle') + 90) % 360)) + $('#text_r').val(obj.get('angle')) + canvas.renderAll() + updateControls() + recordState() + }) + $('#text_italics').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('fontStyle', 'normal') + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('fontStyle', 'italic') + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#text_bold').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选,恢复正常 + canvas.getActiveObject().set('fontWeight', 'normal') + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('fontWeight', 'bold') + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#text_underline').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('underline', false) + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('underline', true) + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#text_linethrough').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('linethrough', false) + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('linethrough', true) + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $("#circle_text_italics").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选 + canvas.getActiveObject().set("fontStyle", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontStyle", "italic") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $("#circle_text_bold").click(function () { + if ($(this).hasClass("ui-radio-acitve")) { + //已选,恢复正常 + canvas.getActiveObject().set("fontWeight", "normal") + $(this).removeClass("ui-radio-acitve") + } else { + //未选 + canvas.getActiveObject().set("fontWeight", "bold") + $(this).addClass("ui-radio-acitve") + } + recordState(); + canvas.renderAll(); + }); + $('#out_left_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_left_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'left') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'left' + recordState() + canvas.renderAll() + }) + $('#out_right_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_right_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'right') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'right' + recordState() + canvas.renderAll() + }) + $('#out_center_alignment').click(function () { + $('#out_left_alignment').parent().removeClass('tool1') + $('#out_right_alignment').parent().removeClass('tool1') + $('#out_center_alignment').parent().removeClass('tool1') + setTimeout(() => { + $('#out_center_alignment').parent().addClass('tool1') + }); + canvas.getActiveObject().set('textAlign', 'center') + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].align = 'center' + recordState() + canvas.renderAll() + }) + $('#out_text_italics').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('fontStyle', 'normal') + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('fontStyle', 'italic') + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#out_text_bold').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选,恢复正常 + canvas.getActiveObject().set('fontWeight', 'normal') + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('fontWeight', 'bold') + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#out_text_underline').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('underline', false) + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('underline', true) + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#out_text_linethrough').click(function () { + if ($(this).hasClass('ui-radio-acitve')) { + //已选 + canvas.getActiveObject().set('linethrough', false) + $(this).removeClass('ui-radio-acitve') + } else { + //未选 + canvas.getActiveObject().set('linethrough', true) + $(this).addClass('ui-radio-acitve') + } + recordState() + canvas.renderAll() + }) + $('#lock').click(function () { + canvas.getActiveObject().set('selectable', !canvas.getActiveObject().get('selectable')) + if (canvas.getActiveObject().get('selectable')) { + $('#lock_img').attr('src', 'public/images/unlock20.png') //已经上锁,需要解锁 + } else { + $('#lock_img').attr('src', 'public/images/lock20.png') //没上锁,可以上锁 + } + recordState() + canvas.renderAll() + }) + $('#text_back_transparent').click(function () { + canvas.getActiveObject().set('textBackgroundColor', '') + $('#text_back_color').val('#ffffff') + canvas.renderAll() + }) + $('#out_text_back_transparent').click(function () { + canvas.getActiveObject().set('textBackgroundColor', '') + $('#out_text_back_color').val('#ffffff') + canvas.renderAll() + }) + $('#rect_transparent').click(function () { + canvas.getActiveObject().set('fill', '') + $('#rect_color').val('#ffffff') + canvas.renderAll() + }) + $('#rect_stroke_transparent').click(function () { + canvas.getActiveObject().set('stroke', '') + $('#rect_stroke').val('#ffffff') + canvas.renderAll() + }) + $('#circle_transparent').click(function () { + canvas.getActiveObject().set('fill', '') + $('#circle_color').val('#ffffff') + canvas.renderAll() + }) + $('#circle_stroke_transparent').click(function () { + canvas.getActiveObject().set('stroke', '') + $('#circle_stroke').val('#ffffff') + canvas.renderAll() + }) + function saveAs(op1, callback) { + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let _g1 = [], + _g2 = [] + let left = 0, + top = 0 + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g1.push(fabric.util.object.clone(_o)) + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + ; (left = 0), (top = 0) + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g2.push(fabric.util.object.clone(_o)) + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let o = { f: j, b: b, fo: objs1, bo: objs2, fp: fp, bp: bp } + let con_o = Object.assign(o, op1) + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.filePath == '') { + return + } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon' + } + let fs = require('fs') + con_o.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(con_o)) + openAs.name = result.filePath + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + if (callback && typeof callback === 'function') { + callback() + } + }) + .catch((err) => { + console.log() + }) + } + function save(op1, callback) { + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let _g1 = [], + _g2 = [] + let left = 0, + top = 0 + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g1.push(fabric.util.object.clone(_o)) + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + ; (left = 0), (top = 0) + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g2.push(fabric.util.object.clone(_o)) + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let o = { f: j, b: b, fo: objs1, bo: objs2, fp: fp, bp: bp } + let con_o = Object.assign(o, op1) + console.log(op1) + if (openAs.name != '') { + //打开的文件 + let fs = require('fs') + con_o.soonType = 2 + fs.writeFileSync(openAs.name, JSON.stringify(con_o)) + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + if (callback && typeof callback === 'function') { + callback() + } + return + } + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.filePath == '') { + return + } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon' + } + let fs = require('fs') + con_o.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(con_o)) + openAs.name = result.filePath + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + if (callback && typeof callback === 'function') { + callback() + } + }) + .catch((err) => { + console.log() + }) + } + // 复制 + $("#copy").click(function () { + copySelection(); + }) + // 粘贴 + $("#paste").click(function () { + pasteFromClipboard(); + }) + // 保存 + $('#save').click(function () { + output() + return + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let _g1 = [], + _g2 = [] + let left = 0, + top = 0 + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g1.push(fabric.util.object.clone(_o)) + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + ; (left = 0), (top = 0) + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g2.push(fabric.util.object.clone(_o)) + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let o = { f: j, b: b, fo: objs1, bo: objs2, fp: fp, bp: bp } + let con_o = Object.assign(o, op1) + console.log(op1) + if (openAs.name != '') { + //打开的文件 + let fs = require('fs') + con_o.soonType = 2 + fs.writeFileSync(openAs.name, JSON.stringify(con_o)) + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + return + } + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.filePath == '') { + return + } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon' + } + let fs = require('fs') + con_o.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(con_o)) + openAs.name = result.filePath + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + saveHistory() + }) + .catch((err) => { + console.log() + }) + }) + function getDate() { + let date = new Date() + return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + } + function getAbsoluteXY(_obj) { + let coord = _obj.get('lineCoords') + //let xs = [], ys = []; + //for(let item in coord){ + // xs.push(coord[item].x); + // ys.push(coord[item].y); + //} + //xs.sort(function(a,b){return a-b}); + //ys.sort(function(a,b){return a-b}); + //return [xs[0], ys[0]]; + return [_obj.get('left'), _obj.get('top')] + } + let fullPath = path.join(exePath, 'data.json') + function saveHistory() { + let fs = require('fs') + let j = {} + try { + fsData = fs.readFileSync(fullPath) + j = JSON.parse(fsData.toString()) + for (let item of j.history) { + if (item.path == openAs.name) { + item.time = getDate() + fs.writeFileSync(fullPath, JSON.stringify(j)) + return + } + } + j.history.push({ time: getDate(), path: openAs.name }) + fs.writeFileSync(fullPath, JSON.stringify(j)) + } catch (e) { + let j = { history: { time: getDate(), path: openAs.name } } + fs.writeFileSync(fullPath, JSON.stringify(j)) + } + } + function open(file) { + let fs = require('fs') + var fsData = fs.readFileSync(file) + openAs.name = file + let j = JSON.parse(fsData.toString()) + let left = background_image.left + let top = background_image.top + let new_left = j.f.objects[0].left + let new_top = j.f.objects[0].top + for (let o of j.f.objects) { + o.left = o.left - new_left + left + o.top = o.top - new_top + top + } + for (let o of j.b.objects) { + o.left = o.left - new_left + left + o.top = o.top - new_top + top + } + canvas1.loadFromJSON(j.f) + canvas2.loadFromJSON(j.b) + background_image1 = canvas1.getObjects()[0] + background_image2 = canvas2.getObjects()[0] + + objs2 = j.bo + objs1 = j.fo + step1.val = 0 + step2.val = 0 + recordJson1 = [] + recordJson2 = [] + recordObjs1 = [] + recordObjs2 = [] + if ($('#front_side').hasClass('ui-button-active')) { + objs = objs1 + recordObjs = recordObjs1 + recordJson = recordJson1 + step = step1 + background_image = background_image1 + } else if ($('#back_side').hasClass('ui-button-active')) { + objs = objs2 + recordObjs = recordObjs2 + recordJson = recordJson2 + background_image = background_image2 + step = step2 + } + canvas1.renderAll() + canvas2.renderAll() + updateList() /* + 修复打开新文件后无法撤销的bug + 2022-07-16 + */ + setTimeout(() => { + recordObjs1.push(JSON.stringify(objs1)) + let j1 = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + j1.objects[0].hoverCursor = 'default' + recordJson1.push(j1) + + recordObjs2.push(JSON.stringify(objs2)) + let j2 = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + j2.objects[0].hoverCursor = 'default' + recordJson2.push(j2) + }, 0) + } + $('#open').click(function () { + if (step.val == 0) { + OpenDialog(); + return; + } + layer.confirm( + '' + language_str('whetherSave') + '', + function (index) { + //confirm 是否保存当前文件? + //let j = canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]); + //let b = canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]); + //let o = { f : j, b : b,fo : objs1, bo : objs2}; + //if(openAs != ""){ + // //打开的文件 + // let fs = require('fs') + // fs.writeFileSync(openAs,JSON.stringify(o)) + // layer.msg('保存成功至'+ openAs); + // + // OpenDialog();return; + //} + //dialog.showSaveDialog({ + // title:'保存文件', + // filters: [ + // { name: 'FCS File Type', extensions: ['fcs'] }, + // ] + //}).then(result=>{ + // let fs = require('fs') + // fs.writeFileSync(result.filePath,JSON.stringify(o)) + // + // OpenDialog(); + //}).catch(err=>{ + // console.log() + //}) + /* + let j = canvas1.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]); + let b = canvas2.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"]); + let _g1 = [], _g2 = []; + let left = 0, top = 0; + for( let _o of canvas1.getObjects() ){ + if( getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left){ + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if( getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top){ + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g1.push(fabric.util.object.clone(_o)); + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top:top/zoom, + left:left/zoom + }); + left = 0, top = 0; + for( let _o of canvas2.getObjects() ){ + if( getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left){ + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())); + } + if( getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top){ + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())); + } + _g2.push(fabric.util.object.clone(_o)); + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top:top/zoom, + left:left/zoom + }); + let o = { f : j, b : b,fo : objs1, bo : objs2,fp:fp,bp:bp}; + if(openAs.name != ""){ + //打开的文件 + let fs = require('fs') + fs.writeFileSync(openAs.name,JSON.stringify(o)) + layer.msg(language_str("saveSucc")+ openAs.name);//'保存成功至' + saveHistory(); + + OpenDialog(); + return; + } + dialog.showSaveDialog({ + title:language_str("saveFile"),//'保存文件' + filters: [ + { name: 'Soon File Type', extensions: ['soon'] }, + ] + }).then(result=>{ + if(result.canceled)return; + if(result.filePath.substring(result.filePath.length-5).indexOf('.') == -1){ + result.filePath += '.soon'; + } + let fs = require('fs') + fs.writeFileSync(result.filePath,JSON.stringify(o)) + openAs.name = result.filePath; + layer.msg(language_str("saveSucc")+ openAs.name);//'保存成功至' + saveHistory(); + OpenDialog(); + }).catch(err=>{ + console.log() + }) + */ + output(OpenDialog) + layer.close(index) + }, + function (index) { + OpenDialog() + } + ) + function OpenDialog() { + dialog + .showOpenDialog({ + title: '请选择文件', //confirm + buttonLabel: language_str('comf'), //"确认" + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.canceled) return + //console.log(result.filePaths) + let fs = require('fs') + var fsData = fs.readFileSync(result.filePaths[0]) + openAs.name = result.filePaths[0] + let j = JSON.parse(fsData.toString()) + let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; + if (type == 1) { + ipcRenderer.send('open-design-page', openAs.name, type); + return + } + let left = background_image.left + let top = background_image.top + let new_left = j.f.objects[0].left + let new_top = j.f.objects[0].top + for (let o of j.f.objects) { + o.left = o.left - new_left + left + o.top = o.top - new_top + top + } + for (let o of j.b.objects) { + o.left = o.left - new_left + left + o.top = o.top - new_top + top + } + canvas1.loadFromJSON(j.f) + canvas2.loadFromJSON(j.b) + background_image1 = canvas1.getObjects()[0] + background_image2 = canvas2.getObjects()[0] + + objs2 = j.bo + objs1 = j.fo + step1.val = 0 + step2.val = 0 + recordJson1 = [] + recordJson2 = [] + recordObjs1 = [] + recordObjs2 = [] + if ($('#front_side').hasClass('ui-button-active')) { + objs = objs1 + recordObjs = recordObjs1 + recordJson = recordJson1 + step = step1 + background_image = background_image1 + } else if ($('#back_side').hasClass('ui-button-active')) { + objs = objs2 + recordObjs = recordObjs2 + recordJson = recordJson2 + background_image = background_image2 + step = step2 + } + canvas1.renderAll() + canvas2.renderAll() + updateList() + /* + 修复打开新文件后无法撤销的bug + 2022-07-16 + */ + setTimeout(() => { + recordObjs1.push(JSON.stringify(objs1)) + let j1 = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + j1.objects[0].hoverCursor = 'default' + recordJson1.push(j1) + + recordObjs2.push(JSON.stringify(objs2)) + let j2 = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + j2.objects[0].hoverCursor = 'default' + recordJson2.push(j2) + }, 0) + }) + .catch((err) => { + console.log(err) + }) + } + }) + function clearAll() { + //let blank = "{\"f\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":1012,\"height\":648,\"fill\":\"#000000\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,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YFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"b\":{\"version\":\"4.6.0\",\"objects\":[{\"type\":\"image\",\"version\":\"4.6.0\",\"originX\":\"left\",\"originY\":\"top\",\"left\":253.5,\"top\":121.5,\"width\":1012,\"height\":648,\"fill\":\"rgb(0,0,0)\",\"stroke\":null,\"strokeWidth\":0,\"strokeDashArray\":null,\"strokeLineCap\":\"butt\",\"strokeDashOffset\":0,\"strokeLineJoin\":\"miter\",\"strokeUniform\":false,\"strokeMiterLimit\":4,\"scaleX\":1,\"scaleY\":1,\"angle\":0,\"flipX\":false,\"flipY\":false,\"opacity\":1,\"shadow\":null,\"visible\":true,\"backgroundColor\":\"\",\"fillRule\":\"nonzero\",\"paintFirst\":\"fill\",\"globalCompositeOperation\":\"source-over\",\"skewX\":0,\"skewY\":0,\"cropX\":0,\"cropY\":0,\"selectable\":false,\"hoverable\":false,\"hoverCursor\":\"default\",\"src\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAwAAAKICAYAAAD0EmiCAAAAAXNSR0IArs4c6QAAIABJREFUeF7t3W3I5Xldx/HvT3SLdk0ljFyN0t1IEjN2TMEHZXcoKEjmQ9HKUkgURQtv8pGlVN6UlqBJN+Kzwhuo0MjKHhi27pJJUbG7IesNJOUurt2s6T/+cmYZd3bmc12zM7M783kd2Acy3+uc831954lvzpxrzX3w2LbtATPz+Jm5bmYeNzPXzszVM/NtM3Pl4b+r7oO35iUJECBAgAABAgQIECBAgMDFFrhjZr58+O8/ZuZzM3PTzPzTzNw4M/+w1vraxX5T62K94LZtj56ZZ8zMj87MDx3iwMV6ea9DgAABAgQIECBAgAABAgQuVYH/nJmPzsxfzsyfrrX+7WIsckGDwbZtD5mZ587M82bmSRdjIa9BgAABAgQIECBAgAABAgQuc4G/m5n3zMx711q3X6hdL0gw2LbtETPzypl54cz4pwUX6nqelwABAgQIECBAgAABAgSaBfZ/yvCumXnTWuvz5xvivAaDQyh43cz8zMx88/l+s56PAAECBAgQIECAAAECBAgQOE3gf2bm92fm9eczHJyXYLBt24MOnyh4jU8U+KtLgAABAgQIECBAgAABAgTuE4H9EwdvOHzi4Cv39h3c62CwbduJQ8nYf+uBBwECBAgQIECAAAECBAgQIHDfCnxqZn56rbX/hoVzfpxzMNi2bf/ZV8zMr87MFef8DvwgAQIECBAgQIAAAQIECBAgcL4F7pyZ187Mm9da27k8+TkFg23bHjwzfzAzzz6XF/UzBAgQIECAAAECBAgQIECAwEUReP/MPH+t9aXjvtqxg8G2bY+cmT+ZmR847ouZJ0CAAAECBAgQIECAAAECBC66wN/PzDPXWp89zisfKxhs23bNzPzFzHz3cV7ELAECBAgQIECAAAECBAgQIHCfCnx6Zn5srXXzUd/FkYPBtm2Pnpm/mZlHHfXJzREgQIAAAQIECBAgQIAAAQL3G4HPzMwPr7VuOco7OlIw2Lbt4TPzsZm59ihPaoYAAQIECBAgQIAAAQIECBC4XwrcNDNPWWt9Ib27GAy2bbtyZj4yM09OT+bPCRAgQIAAAQIECBAgQIAAgfu9wMdn5ifSFyGeNRgcfnXiH83MT93v1/UGCRAgQIAAAQIECBAgQIAAgaMKvG9mnnO2X7mYgsErZ+Y3jvpq5ggQIECAAAECBAgQIECAAIFLRuCX1lpn/P/8ZwwG27ZdNzN/OzNXXDKreqMECBAgQIAAAQIECBAgQIDAUQXuPHyfwQ339AP3GAy2bXvQzHxiZr7/qK9ijgABAgQIECBAgAABAgQIELjkBD41MyfWWl+5+zs/UzB41cy88ZJb0xsmQIAAAQIECBAgQIAAAQIEjivwmrXWaQ3gtGCwbdvVM/MvM3PVcV/BPAECBAgQIECAAAECBAgQIHDJCdwxM49da3321Hd+T8HgnTPzwktuPW+YAAECBAgQIECAAAECBAgQOFeBd621XnTGYLBt26MPny7Yv8PAgwABAgQIECBAgAABAgQIEOgQ2L/DYP+UwS0n1/2GTxhs2/a2mXlJh4UtCRAgQIAAAQIECBAgQIAAgVME3r7WeulpwWDbtofOzGdm5kpcBAgQIECAAAECBAgQIECAQJ3Af83MI9dat+2b3/UJg23bfmFmfqeOw8IECBAgQIAAAQIECBAgQIDASYEXr7XecfdgcP3MPJERAQIECBAgQIAAAQIECBAgUCtw/VrrSXcFg23brpmZm2o5LE6AAAECBAgQIECAAAECBAicFLhm//LDr/+ThG3bXjYzb2VDgAABAgQIECBAgAABAgQI1Au8bK31WyeDwYdm5mn1JAAIECBAgAABAgQIECBAgACBD6+1nr62bXvgzHxxZq5iQoAAAQIECBAgQIAAAQIECNQL3DEzD9uDwXUzc0M9BwACBAgQIECAAAECBAgQIEDgpMCJPRi8YGbezYQAAQIECBAgQIAAAQIECBAgcBB4wR4M3jIzL0dCgAABAgQIECBAgAABAgQIEDgIvHUPBh+YmWchIUCAAAECBAgQIECAAAECBAgcBD64B4P9+wv27zHwIECAAAECBAgQIECAAAECBAjsAjfuweBfZ+Z7eBAgQIAAAQIECBAgQIAAAQIEDgK37sHg8zPzHUgIECBAgAABAgQIECBAgAABAgeB2/dgsP9+xSuRECBAgAABAgQIECBAgAABAgQOAl/dg8GGgwABAgQIECBAgAABAgQIECBwqoBg4O8DAQIECBAgQIAAAQIECBAgcJqAYOAvBQECBAgQIECAAAECBAgQICAY+DtAgAABAgQIECBAgAABAgQIZAGfMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIEDpfCiFAAAcY0lEQVSAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMTBAgQIECAAAECBAgQIECgTkAwqDu5hQkQIECAAAECBAgQIECAQBYQDLKRCQIECBAgQIAAAQIECBAgUCcgGNSd3MIECBAgQIAAAQIECBAgQCALCAbZyAQBAgQIECBAgAABAgQIEKgTEAzqTm5hAgQIECBAgAABAgQIECCQBQSDbGSCAAECBAgQIECAAAECBAjUCQgGdSe3MAECBAgQIECAAAECBAgQyAKCQTYyQYAAAQIECBAgQIAAAQIE6gQEg7qTW5gAAQIECBAgQIAAAQIECGQBwSAbmSBAgAABAgQIECBAgAABAnUCgkHdyS1MgAABAgQIECBAgAABAgSygGCQjUwQIECAAAECBAgQIECAAIE6AcGg7uQWJkCAAAECBAgQIECAAAECWUAwyEYmCBAgQIAAAQIECBAgQIBAnYBgUHdyCxMgQIAAAQIECBAgQIAAgSwgGGQjEwQIECBAgAABAgQIECBAoE5AMKg7uYUJECBAgAABAgQIECBAgEAWEAyykQkCBAgQIECAAAECBAgQIFAnIBjUndzCBAgQIECAAAECBAgQIEAgCwgG2cgEAQIECBAgQIAAAQIECBCoExAM6k5uYQIECBAgQIAAAQIECBAgkAUEg2xkggABAgQIECBAgAABAgQI1AkIBnUntzABAgQIECBAgAABAgQIEMgCgkE2MkGAAAECBAgQIECAAAECBOoEBIO6k1uYAAECBAgQIECAAAECBAhkAcEgG5kgQIAAAQIECBAgQIAAAQJ1AoJB3cktTIAAAQIECBAgQIAAAQIEsoBgkI1MECBAgAABAgQIECBAgACBOgHBoO7kFiZAgAABAgQIECBAgAABAllAMMhGJggQIECAAAECBAgQIECAQJ2AYFB3cgsTIECAAAECBAgQIECAAIEsIBhkIxMECBAgQIAAAQIECBAgQKBOQDCoO7mFCRAgQIAAAQIECBAgQIBAFhAMspEJAgQIECBAgAABAgQIECBQJyAY1J3cwgQIECBAgAABAgQIECBAIAsIBtnIBAECBAgQIECAAAECBAgQqBMQDOpObmECBAgQIECAAAECBAgQIJAFBINsZIIAAQIECBAgQIAAAQIECNQJCAZ1J7cwAQIECBAgQIAAAQIECBDIAoJBNjJBgAABAgQIECBAgAABAgTqBASDupNbmAABAgQIECBAgAABAgQIZAHBIBuZIECAAAECBAgQIECAAAECdQKCQd3JLUyAAAECBAgQIECAAAECBLKAYJCNTBAgQIAAAQIECBAgQIAAgToBwaDu5BYmQIAAAQIECBAgQIAAAQJZQDDIRiYIECBAgAABAgQIECBAgECdgGBQd3ILEyBAgAABAgQIECBAgACBLCAYZCMT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\",\"crossOrigin\":null,\"filters\":[]}],\"hoverCursor\":\"move\"},\"fo\":[],\"bo\":[]}"; + //let j = JSON.parse(blank); + //canvas1.setZoom(1); + //canvas2.setZoom(1); + //canvas1.loadFromJSON(j.f); + //canvas2.loadFromJSON(j.b); + let t_objs1 = canvas1.getObjects() + let t_objs2 = canvas2.getObjects() + for (let i = 1; i < t_objs1.length; i++) { + canvas1.remove(t_objs1[i]) + } + for (let i = 1; i < t_objs2.length; i++) { + canvas2.remove(t_objs2[i]) + } + objs2 = [] + objs1 = [] + step1.val = 0 + step2.val = 0 + recordJson1 = [] + recordJson2 = [] + recordObjs1 = [] + recordObjs2 = [] + if ($('#front_side').hasClass('ui-button-active')) { + objs = objs1 + recordObjs = recordObjs1 + recordJson = recordJson1 + step = step1 + } else if ($('#back_side').hasClass('ui-button-active')) { + objs = objs2 + recordObjs = recordObjs2 + recordJson = recordJson2 + step = step2 + } + openAs.name = '' + canvas1.renderAll() + canvas2.renderAll() + updateList() + console.log() + } + $('#new').click(function () { + if (step.val == 0) { + ipcRenderer.send('open-first-page'); + return; + } + layer.confirm( + '' + language_str('whetherSave') + '', + function (index) { + //confirm 是否保存当前文件? + output(() => ipcRenderer.send('open-first-page')) + return + let j = canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let b = canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + let _g1 = [], + _g2 = [] + let left = 0, + top = 0 + for (let _o of canvas1.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g1.push(fabric.util.object.clone(_o)) + } + let fp = new fabric.Group(_g1).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + ; (left = 0), (top = 0) + for (let _o of canvas2.getObjects()) { + if (getCoordsMinX(background_image.getCoords()) - getCoordsMinX(_o.getCoords()) > left) { + left = -(getCoordsMinX(_o.getCoords()) - getCoordsMinX(background_image.getCoords())) + } + if (getCoordsMinY(background_image.getCoords()) - getCoordsMinY(_o.getCoords()) > top) { + top = -(getCoordsMinY(_o.getCoords()) - getCoordsMinY(background_image.getCoords())) + } + _g2.push(fabric.util.object.clone(_o)) + } + //let fp = new fabric.Group(_g1).toDataURL(); + let bp = new fabric.Group(_g2).toDataURL({ + height: 648, + width: 1012, + top: top / zoom, + left: left / zoom + }) + let o = { f: j, b: b, fo: objs1, bo: objs2, fp: fp, bp: bp } + if (openAs.name != '') { + //打开的文件 + let fs = require('fs') + o.soonType = 2 + fs.writeFileSync(openAs.name, JSON.stringify(o)) + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + /* + 2022-07-16 直接跳转至首页 + */ + ipcRenderer.send('open-first-page') + //saveHistory(); + //clearAll(); + return + } + dialog + .showSaveDialog({ + title: language_str('saveFile'), //'保存文件' + filters: [{ name: 'Soon File Type', extensions: ['soon'] }] + }) + .then((result) => { + if (result.filePath == '') { + return + } + if (result.filePath.substring(result.filePath.length - 5).indexOf('.') == -1) { + result.filePath += '.soon' + } + let fs = require('fs') + o.soonType = 2 + fs.writeFileSync(result.filePath, JSON.stringify(o)) + openAs.name = result.filePath + layer.msg(language_str('saveSucc') + openAs.name) //'保存成功至' + /* + 2022-07-16 直接跳转至首页 + */ + ipcRenderer.send('open-first-page') + //saveHistory(); + //clearAll(); + }) + .catch((err) => { + console.log() + }) + layer.close(index) + }, + function (index) { + /* + 2022-07-16 直接跳转至首页 + */ + //clearAll(); + ipcRenderer.send('open-first-page') + } + ) + }) + function chagneTitle() { } + function addBackground() { + // 类型改变 + fabric.Image.fromURL('./public/images/bg_front_2.png', function (image) { + //image.set("scaleX",$("#canvas-div").width() / 1200 < 1 ? $("#canvas-div").width() / 1200 : 1) + //image.set("scaleY",$("#canvas-div").width() / 1200 < 1 ? $("#canvas-div").width() / 1200 : 1) + + image.left = ($('#canvas1').width() - image.width * image.get('scaleX')) / 2 + image.top = ($('#canvas1').height() - image.height * image.get('scaleY')) / 2 + + image.selectable = false + image.hoverable = false + image.hoverCursor = 'default' + image.setSrc(image.toDataURL(), function (image) { + background_image1 = image + background_image = background_image1 + canvas.add(image) + canvas.renderAll() + //pre_objs1.push(JSON.stringify(objs1)); + //pre_json1.push(canvas1.toJSON(["selectable","hoverable","hoverCursor"])); + + recordObjs1.push(JSON.stringify(objs1)) + recordJson1.push(canvas1.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])) + }) + // 类型改变 + fabric.Image.fromURL('./public/images/bg_back_2.png', function (image) { + image.left = ($('#canvas2').width() - image.width) / 2 + image.top = ($('#canvas2').height() - image.height) / 2 + + image.selectable = false + image.hoverable = false + image.hoverCursor = 'default' + + image.setSrc(image.toDataURL(), function (image) { + background_image2 = image + canvas2.add(image) + canvas2.renderAll() + //pre_objs2.push(JSON.stringify(objs2)); + //pre_json2.push(canvas2.toJSON(["selectable","hoverable","hoverCursor"])); + + recordObjs2.push(JSON.stringify(objs2)) + recordJson2.push(canvas2.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline'])) + + let file = GetFile().get('file') + console.log(file) + if (file && file != 'empty') { + open(file) + } + }) + }) + is_bgi_add = true + }) + } + function selectObject(target, index) { + updateControls() + + $('#base_control').show() + //$("#text_x").val((target.left - background_image.left).toFixed(1)); + //$("#text_y").val((target.top - background_image.top).toFixed(1)); + //$("#text_w").val(parseFloat(target.get("scaleX") * target.get("width")).toFixed(1)); + //$("#text_h").val(parseFloat(target.get("scaleY") * target.get("height")).toFixed(1)); + //$("#text_r").val(target.get("angle").toFixed(0)); + } + function updateControls() { + $('#text_r').val(canvas.getActiveObject().get('angle').toFixed(0)) + $('#text_x').val((canvas.getActiveObject().get('left') - background_image.left).toFixed(0)) + $('#text_y').val((canvas.getActiveObject().get('top') - background_image.top).toFixed(0)) + $('#text_h').val((canvas.getActiveObject().get('scaleY') * canvas.getActiveObject().get('height')).toFixed(0)) + $('#text_w').val((canvas.getActiveObject().get('scaleX') * canvas.getActiveObject().get('width')).toFixed(0)) + if (canvas.getActiveObject().get('selectable')) { + $('#lock_img').attr('src', 'public/images/unlock20.png') //已经上锁,需要解锁 + } else { + $('#lock_img').attr('src', 'public/images/lock20.png') //没上锁,可以上锁 + } + } + function checkName(name) { + //检查字段名是否重复了 + for (let item of objs1) { + if (item.name == name) { + return false + } + } + for (let item of objs2) { + if (item.name == name) { + return false + } + } + return true + } + function resName(pre_name) { + //返回的是不重复的字段名 + let num = objs1.length + objs2.length - 1 + do { + num++ + } while (!checkName(pre_name + num)) //重复了就再加1 + return pre_name + num + } + function addPic(pointer) { + pre_add_image.left = pointer.x + pre_add_image.top = pointer.y + + canvas.add(pre_add_image) + + objs.push({ type: 1, black: false }) + canvas.renderAll() + updateList() + recordState() + //OpenDialog(); + //function OpenDialog() + //{ + // dialog.showOpenDialog({ + // title: "请选择图片", + // buttonLabel: "确认", + // filters: [ + // { name: 'Image File Type', extensions: ['png', 'jpg'] }, + // ] + // }).then(result => { + // if(result.canceled)return; + // //console.log(result.filePaths) + // fabric.Image.fromURL(result.filePaths[0], function(image) { + // image.left = pointer.x; + // image.top = pointer.y; + // + // canvas.add(image); + // objs.push({type: 1}); + // canvas.renderAll(); + // updateList(); + // recordState(); + // } ); + // }).catch(err => { + // console.log(err) + // }) + //} + } + function selectPic(target, index) { + //透明度 + + $('#pic_control').show() + $("#set_bg").show(); + pic_op.setValue(100 - canvas.getActiveObject().get('opacity') * 100) + + let idx = getIndex(canvas.getActiveObject()) + // $("#pic_black").prop("checked", objs[idx-1].black ? 'checked' : ''); + + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function addOutPic(pointer) { + fabric.Image.fromURL('./public/images/outpic_extra.png', function (image) { + let width = canvas.getWidth() + let height = canvas.getHeight() + image.left = pointer.x + image.top = pointer.y + image.opacity = 1 + //image.on("selected",function(){//test well + // console.log(222); + //}); + image.setSrc(image.toDataURL(), function (image) { + canvas.add(image) + objs.push({ type: 2, name: resName('OUT_PIC_'), black: false }) + canvas.renderAll() + updateList() + recordState() + }) + }) + } + function selectOutPic(target, index) { + //字段名 + $('#out_pic_control').show() + out_pic_op.setValue(100 - canvas.getActiveObject().get('opacity') * 100) + let idx = getIndex(canvas.getActiveObject()) + $('#out_pic_field_name').val(objs[idx - 1].name) + // $("#out_pic_black").prop("checked", objs[idx-1].black ? 'checked' : ''); + + $('#pic_control').hide() + $("#set_bg").hide(); + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + + function addText(pointer) { + let text = new fabric.IText('TEXT', { + //源:文本 + left: pointer.x, + top: pointer.y, + fill: '#000000', + textBackgroundColor: '', + fontStyle: 'normal', + fontWeight: 'normal', + underline: false, + linethrough: false, + fontFamily: 'Arial' + }) + text.setControlVisible('ml', false) + text.setControlVisible('mt', false) + text.setControlVisible('mr', false) + text.setControlVisible('mb', false) + canvas.add(text) + objs.push({ type: 3, black: false }) + canvas.renderAll() + updateList() + recordState() + } + function selectText() { + //console.log(canvas.getActiveObject().get("fontFamily")); + $('#text_control').show() + $('#text_color').val(canvas.getActiveObject().get('fill')) + $('#text_back_color').val(canvas.getActiveObject().get('textBackgroundColor') == '' ? '#ffffff' : canvas.getActiveObject().get('textBackgroundColor')) + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#text_size").val(val); + $('#text_text').val(canvas.getActiveObject().get('text')) + $('#text_font_family').val(canvas.getActiveObject().get('fontFamily')) + + if (canvas.getActiveObject().get('underline') == true) { + $('#text_underline').addClass('ui-radio-acitve') + } else { + $('#text_underline').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('fontWeight') == 'bold') { + $('#text_bold').addClass('ui-radio-acitve') + } else { + $('#text_bold').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('fontStyle') == 'italic') { + $('#text_italics').addClass('ui-radio-acitve') + } else { + $('#text_italics').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('linethrough') == true) { + $('#text_linethrough').addClass('ui-radio-acitve') + } else { + $('#text_linethrough').removeClass('ui-radio-acitve') + } + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function getDisdance(x1, y1, x2, y2) { + var dx = Math.abs(x2 - x1); // 计算x轴上的距离差,并取绝对值 + var dy = Math.abs(y2 - y1); // 计算y轴上的距离差,并取绝对值 + var distance = Math.sqrt(dx * dx + dy * dy); // 应用勾股定理计算距离 + return distance; + } + function getDeg(pointer) { + // 计算点击位置与画布中心的坐标差 + var centerX = canvas.width / 2; + var centerY = canvas.height / 2; + var mouseX = pointer.x; + var mouseY = pointer.y; + var dx = mouseX - centerX; + var dy = mouseY - centerY; + // 计算旋转角度(弧度) + var angleRad = Math.atan2(dy, dx); + // 将角度转换为度数 + var angleDeg = angleRad * (180/Math.PI); + if (angleDeg < 0) { + angleDeg += 360; + } + angleDeg += 90 + return angleDeg + } + function addCircleText(pointer) { + let distance = getDisdance(pointer.x, pointer.y, canvas.width/2,canvas.height/2) + let angleDeg = getDeg(pointer) + let text = new fabric.CurvedText('Circle TEXT', {//源:圆形文本 + left: pointer.x, + top: pointer.y, + fill: "#000000", + textBackgroundColor: "", + fontStyle: "normal", + fontWeight: "normal", + diameter: distance*2, + fontSize: 30, + underline: false, + linethrough: false, + lockScalingX: true, + lockScalingY: true, + fontFamily: 'Arial', + originX: 'center', + originY: 'center', + angle: angleDeg, + flipped: pointer.y > canvas.height/2 ? true : false + }); + // distance = getDisdance(pointer.x+text.width/2, pointer.y, canvas.width/2,canvas.height/2) + // text.set({diameter: distance*2}) + text.setControlVisible("ml", false); + text.setControlVisible("mt", false); + text.setControlVisible("mr", false); + text.setControlVisible("mb", false); + canvas.add(text); + objs.push({ type: 11, black: false, circleCenter: 0 }); + canvas.renderAll(); + updateList(); + recordState(); + } + function selectCircleText() { + console.log(canvas.getActiveObject().get("text")) + let idx = getIndex(canvas.getActiveObject()); + $("#circle_text_control").show(); + $("#circle_text_color").val(canvas.getActiveObject().get("fill")); + $("#circle_text_back_color").val(canvas.getActiveObject().get("textBackgroundColor") == "" ? '#ffffff' : canvas.getActiveObject().get("textBackgroundColor")); + circle_text_size.setValue(canvas.getActiveObject().get("fontSize") - 10); + circle_text_diameter.setValue(canvas.getActiveObject().get("diameter")-50); + $("#circle_text_center").prop("checked", objs[idx - 1].circleCenter ? 'checked' : ''); + $("#circle_text_text").val(canvas.getActiveObject().get("text")); + $("#circle_text_font_family").val(canvas.getActiveObject().get("fontFamily")); + + if (canvas.getActiveObject().get("underline") == true) { + $("#circle_text_underline").addClass("ui-radio-acitve") + } else { + $("#circle_text_underline").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontWeight") == "bold") { + $("#circle_text_bold").addClass("ui-radio-acitve") + } else { + $("#circle_text_bold").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("fontStyle") == "italic") { + $("#circle_text_italics").addClass("ui-radio-acitve") + } else { + $("#circle_text_italics").removeClass("ui-radio-acitve") + } + if (canvas.getActiveObject().get("linethrough") == true) { + $("#circle_text_linethrough").addClass("ui-radio-acitve") + } else { + $("#circle_text_linethrough").removeClass("ui-radio-acitve") + } + $("#pic_control").hide(); + $("#set_bg").hide(); + $("#rotate").hide(); + $("#out_pic_control").hide(); + $("#text_control").hide(); + $("#out_text_control").hide(); + $("#rect_control").hide(); + $("#circle_control").hide(); + $("#line_control").hide(); + $("#counter_control").hide(); + $("#qrcode_Controll").hide(); + $("#barcode_controll").hide(); + + } + function addOutText(pointer) { + //MERGE_TEXT_1 + let text = new fabric.Text('MERGE TEXT', { + //源:合成文本 + left: pointer.x, + top: pointer.y, + fill: '#000000', + fontStyle: 'normal', + fontWeight: 'normal', + textBackgroundColor: '', + underline: false, + linethrough: false, + fontFamily: 'Arial', + textAlign: 'center' + }) + + //text.on({ + // 'scaling': function(e) { + // var obj = this, + // w = obj.width * obj.scaleX, + // h = obj.height * obj.scaleY; + // + // obj.set({ + // 'height' : h, + // 'width' : w, + // 'scaleX' : 1, + // 'scaleY' : 1 + // }); + // } + //}); + text.setControlVisible('ml', false) + text.setControlVisible('mt', false) + text.setControlVisible('mr', false) + text.setControlVisible('mb', false) + + canvas.add(text) + objs.push({ type: 4, name: resName('OUT_TEXT_'), exceed: false, autoWrap: false, fixSize: false, black: false, align: 'center' }) + canvas.renderAll() + updateList() + recordState() + } + function selectOutText() { + //color + //size + const align = canvas.getActiveObject().get('textAlign') + if (align === 'left') { + $('#out_left_alignment').parent().addClass('tool1') + } else if (align === 'right') { + $('#out_right_alignment').parent().addClass('tool1') + } else { + $('#out_center_alignment').parent().addClass('tool1') + } + $('#out_text_control').show() + $('#out_text_color').val(canvas.getActiveObject().get('fill')) + $('#out_text_text').val(canvas.getActiveObject().get('text')) + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#out_text_size").val(val); + $('#out_text_back_color').val(canvas.getActiveObject().get('textBackgroundColor') == '' ? '#ffffff' : canvas.getActiveObject().get('textBackgroundColor')) + let idx = getIndex(canvas.getActiveObject()) + $('#out_text_field_name').val(objs[idx - 1].name) + $('#out_text_font_family').val(canvas.getActiveObject().get('fontFamily')) + $('#exceed').prop('checked', objs[idx - 1].exceed ? 'checked' : '') + $('#autoWrap').prop('checked', objs[idx - 1].autoWrap ? 'checked' : '') + $('#fixSize').prop('checked', objs[idx - 1].fixSize ? 'checked' : '') + if (canvas.getActiveObject().get('underline') == true) { + $('#out_text_underline').addClass('ui-radio-acitve') + } else { + $('#out_text_underline').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('fontWeight') == 'bold') { + $('#out_text_bold').addClass('ui-radio-acitve') + } else { + $('#out_text_bold').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('fontStyle') == 'italic') { + $('#out_text_italics').addClass('ui-radio-acitve') + } else { + $('#out_text_italics').removeClass('ui-radio-acitve') + } + if (canvas.getActiveObject().get('linethrough') == true) { + $('#out_text_linethrough').addClass('ui-radio-acitve') + } else { + $('#out_text_linethrough').removeClass('ui-radio-acitve') + } + + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#rect_control').hide() + $('#circle_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function addRect(pointer) { + let rect = new fabric.Rect({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: "#ff0000", + strokeWidth: 3, + width: 100, + height: 100 + }) + canvas.add(rect) + objs.push({ type: 5, black: false }) + canvas.renderAll() + updateList() + recordState() + } + function selectRect() { + //color + $('#rect_control').show() + $('#rect_color').val(canvas.getActiveObject().get('fill') == '' ? '#ffffff' : canvas.getActiveObject().get('fill')) + $('#rect_stroke').val(canvas.getActiveObject().get('stroke') == '' ? '#ffffff' : canvas.getActiveObject().get('stroke')) + $('#rect_stroke_width').val(canvas.getActiveObject().get('strokeWidth')) + $('#rect_radius').val(canvas.getActiveObject().get('rx')) + rect_op.setValue(100 - canvas.getActiveObject().get('opacity') * 100) + let a = canvas.getActiveObject().getCoords() + console.log(getCoordsMinX(a)) + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#circle_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function addCircle(pointer) { + let circle = new fabric.Circle({ + left: pointer.x, + top: pointer.y, + fill: '', + stroke: '#ff0000', + strokeWidth: 3, + radius: 100, + strokeUniform: true + }) + canvas.add(circle) + objs.push({ type: 6, black: false }) + canvas.renderAll() + updateList() + recordState() + } + function selectCircle() { + //border-color + //color + $('#circle_control').show() + $('#circle_color').val(canvas.getActiveObject().get('fill') == '' ? '#ffffff' : canvas.getActiveObject().get('fill')) + $('#circle_stroke').val(canvas.getActiveObject().get('stroke') == '' ? '#ffffff' : canvas.getActiveObject().get('stroke')) + $('#circle_stroke_width').val(canvas.getActiveObject().get('strokeWidth')) + circle_op.setValue(100 - canvas.getActiveObject().get('opacity') * 100) + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#line_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function addLine(pointer) { + let line = new fabric.Line([50, 100, 200, 100], { + left: pointer.x, + top: pointer.y, + stroke: '#ff0000', + strokeWidth: 3, + strokeDashArray: [0, 0], + }) + + line.setControlVisible('tl', false) + line.setControlVisible('tr', false) + line.setControlVisible('br', false) + line.setControlVisible('bl', false) + line.setControlVisible('mt', false) + line.setControlVisible('mb', false) + canvas.add(line) + objs.push({ type: 7, black: false }) + canvas.renderAll() + updateList() + recordState() + } + function selectLine() { + //color + //weight + $('#line_control').show() + + let idx = getIndex(canvas.getActiveObject()) + $('#line_color').val(canvas.getActiveObject().get('stroke')) + $('#line_dist').val(canvas.getActiveObject().get('strokeDashArray')[0]) + $('#text_h').val(canvas.getActiveObject().get('strokeWidth')) + + $('#pic_control').hide() + $("#set_bg").hide(); + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function addQrCode(pointer) { + let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0 }) + fabric.Image.fromURL(qr, function (image) { + image.left = pointer.x + image.top = pointer.y + canvas.add(image) + objs.push({ type: 8, name: resName('QR_CODE_'), color: '#000000', val: 'http://www.cardsoon.com', black: false }) + canvas.renderAll() + updateList() + recordState() + }) + } + function selectQrCode() { + //字段名 + //background-color + //color + $('#qrcode_Controll').show() + let idx = getIndex(canvas.getActiveObject()) + $('#qrcode_field_name').val(objs[idx - 1].name) + $('#qrcode_color').val(objs[idx - 1].color) + $('#qrcode_val').val(objs[idx - 1].val) + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#barcode_controll').hide() + } + function addBarCode(pointer) { + JsBarcode('#barcode', '123456789', { margin: 0, lineColor: '#000000', fontSize: 18, font: 'Arial' }) + let barcode = document.getElementById('barcode') + let bar = barcode.toDataURL('image/png') + fabric.Image.fromURL(bar, function (image) { + image.left = pointer.x + image.top = pointer.y + + canvas.add(image) + objs.push({ type: 9, name: resName('BAR_CODE_'), val: '123456789', color: '#000000', fontSize: 18, font: 'Arial', show: false, black: false }) //show是取消文字显示 + canvas.renderAll() + updateList() + recordState() + }) + } + function selectBarCode() { + //字段名 + //background-color + //color + $('#barcode_controll').show() + let idx = getIndex(canvas.getActiveObject()) + $('#barcode_field_name').val(objs[idx - 1].name) + $('#barcode_font_family').val(objs[idx - 1].font) + let val = parseFloat(objs[idx - 1].fontSize)*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#barcode_size").val(val); + $('#barcode_val').val(objs[idx - 1].val) + $('#barcode_show').prop('checked', objs[idx - 1].show ? 'checked' : '') + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + } + function addCounter(pointer) { + let text = new fabric.Text('0', { + left: pointer.x, + top: pointer.y, + fill: '#000000' + }) + canvas.add(text) + objs.push({ type: 10, name: resName('COUNTER_'), start: 0, add: 1, black: false }) + canvas.renderAll() + updateList() + recordState() + console.log(canvas.getObjects()) + } + function selectCounter() { + //起始 + //终止 + //增量 + $('#counter_control').show() + + let idx = getIndex(canvas.getActiveObject()) + $('#counter_start').val(objs[idx - 1].start) + $('#counter_add').val(objs[idx - 1].add) + $('#counter_color').val(canvas.getActiveObject().get('fill')) + $('#count_font_family').val(canvas.getActiveObject().get('fontFamily')) + let val = parseFloat(canvas.getActiveObject().get("fontSize"))*72/dpi + if (val%0.5!==0) val = Math.round(val*2)/2 + $("#count_size").val(val); + + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + } + function getIndex(target, _canvas = null) { + let temp_objs + if (_canvas == null) { + temp_objs = canvas.getObjects() + } else { + temp_objs = _canvas.getObjects() + } + + let i = 0 + for (let res of temp_objs) { + if (target == res) { + return i + } + i++ + } + return 0 + } + function getType(target) { + let temp_objs = canvas.getObjects() + let i = 0 + for (let res of temp_objs) { + if (target == res) { + return objs[i].type + } + i++ + } + } + + function updateList() { + let str = '' + let i = 0 + for (let item of objs) { + switch (item.type) { + case 1 /* 2022-06-14修改*/: + str = '
IMG_" + i + '
' + str + break + case 2: + str = '
" + item.name + '
' + str + break + case 3: + str = '
" + 'TEXT_' + i + '
' + str + break + case 4: + str = '
" + item.name + '
' + str + break + case 5: + str = '
RECT_" + i + '
' + str + break + case 6: + str = '
CIRCLE_" + i + '
' + str + break + case 7: + str = '
LINE_" + i + '
' + str + break + case 8: + str = '
QRCODE_" + i + '
' + str + break + case 9: + str = '
BARCODE_" + i + '
' + str + break + case 10: + str = '
COUNTER_" + i + '
' + str + break + case 11: + str = "
" + "CIRCLE_TEXT_" + i + "
" + str; + break; + } + i++ + } + $('.obj_list').html(str) + } + function selectObj(i, notSelect = false) { + //for(let item in $(".obj_list").children()){ + // if(_i == i){ + // item.css("background-color","#6F7A84"); + // }else{ + // item.css("background-color",""); + // } + // _i++; + //} + $('.obj_list div').each(function (index) { + //if(index == i){ + if ($(this).children('img').attr('id') == 'obj' + i) { + $(this).css('background-color', '#6F7A84') + canvas.setActiveObject(canvas.getObjects()[i + 1]) + canvas.renderAll() + } else { + $(this).css('background-color', '') + } + }) + if (!notSelect) { + handleObjectSelected() + } + } + function recordState() { + //pre_objs.push(JSON.stringify(objs)); + //pre_json.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + //next_json = []; + //next_objs = []; + //recordObjs.push(JSON.stringify(objs)); + //recordJson.push(canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])); + ++step.val + for (let i = step.val; i < recordObjs.length; i++) { + recordObjs.pop() + recordJson.pop() + } + //recordObjs.splice(step.val, 1, JSON.stringify(objs)) + //recordJson.splice(step.val, 1, canvas.toJSON(["selectable","hoverable","hoverCursor","text","fontStyle","fontWeight","underline"])) + recordObjs.push(JSON.stringify(objs)) + j = canvas.toJSON(['selectable', 'hoverable', 'hoverCursor', 'text', 'fontStyle', 'fontWeight', 'underline']) + j.objects[0].hoverCursor = 'default' + recordJson.push(j) + console.log(recordJson) + } + function getCoordsMinX(acoords) { + x = acoords[0].x + for (let item of acoords) { + if (item.x < x) { + x = item.x + } + } + return x + } + function getCoordsMaxX(acoords) { + x = acoords[0].x + for (let item of acoords) { + if (item.x > x) { + x = item.x + } + } + return x + } + function getCoordsMinY(acoords) { + y = acoords[0].y + for (let item of acoords) { + if (item.y < y) { + y = item.y + } + } + return y + } + function getCoordsMaxY(acoords) { + y = acoords[0].y + for (let item of acoords) { + if (item.y > y) { + y = item.y + } + } + return y + } + + $('#text_y').bind('input propertychange', function () { + if ($('#text_y').val() == '') { + $('#text_y').val(0) + } + let val = background_image.top + if ($('#text_y').val() != '') { + val = background_image.top + parseInt($('#text_y').val()) + } + canvas.getActiveObject().set('top', val) + canvas.renderAll() + recordState() + }) + $('#text_x').bind('input propertychange', function () { + if ($('#text_x').val() == '') { + $('#text_x').val(0) + } + let val = background_image.left + if ($('#text_x').val() != '') { + val = background_image.left + parseInt($('#text_x').val()) + } + canvas.getActiveObject().set('left', val) + canvas.renderAll() + recordState() + }) + $('#text_w').bind('input propertychange', function () { + let val = '0' + if ($('#text_w').val() != '') { + val = $('#text_w').val() + } + let type = objs[getIndex(canvas.getActiveObject()) - 1].type + if (type == 3 || type == 4) { + canvas.getActiveObject().set('width', parseInt(val)) + } else { + canvas.getActiveObject().set('scaleX', parseInt(val) / canvas.getActiveObject().get('width')) + } + + canvas.renderAll() + recordState() + }) + $('#text_h').bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()) + if (objs[idx - 1].type == 7) { + if ($('#text_h').val() == '') { + $('#text_h').val(canvas.getActiveObject().get('strokeWidth')) + } + canvas.getActiveObject().set('strokeWidth', parseInt($('#text_h').val())) + canvas.renderAll() + return + } + let val = '0' + if ($('#text_h').val() != '') { + val = $('#text_h').val() + } + let type = objs[getIndex(canvas.getActiveObject()) - 1].type + if (type == 3 || type == 4) { + canvas.getActiveObject().set('height', parseInt(val)) + } else { + canvas.getActiveObject().set('scaleY', parseInt(val) / canvas.getActiveObject().get('height')) + } + + canvas.renderAll() + recordState() + }) + $('#text_r').bind('input propertychange', function () { + if ($('#text_r').val() == '') { + $('#text_r').val('0') + } + $('#text_r').val(parseInt($('#text_r').val()) % 360) + //canvas.getActiveObject().set("rotate", val) + canvas.getActiveObject().rotate(parseInt($('#text_r').val())) + canvas.renderAll() + recordState() + }) + $('#out_pic_field_name').bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].name = $('#out_pic_field_name').val() + updateList() + recordState() + }) + + $('#text_black').click(function () { + let idx = getIndex(canvas.getActiveObject()) + if ($(this).prop('checked')) { + objs[idx - 1].black = true + } else { + objs[idx - 1].black = false + } + recordState() + }) + // $("#out_pic_black").click(function () { + // let idx = getIndex(canvas.getActiveObject()); + // if ($(this).prop("checked")) { + // objs[idx-1].black = true; + // } else { + // objs[idx-1].black = false; + // } + // recordState(); + // }); + $('#out_text_field_name').bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].name = $('#out_text_field_name').val() + updateList() + recordState() + }) + $('#qrcode_field_name').bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].name = $('#qrcode_field_name').val() + updateList() + recordState() + }) + $('#qrcode_color').bind('input propertychange', function () { + let item = canvas.getActiveObject() + let qr = jrQrcode.getQrBase64('https://www.cardsoon.com', { padding: 0, foreground: $(this).val() }) + let idx = getIndex(item) + objs[idx - 1].color = $(this).val() + item.setSrc(qr, function (img) { + canvas.renderAll() + recordState() + }) + }) + $("#qrcode_val").bind('change', function () { + let val = $(this).val() ? $(this).val() : 'https://www.cardsoon.com' + let item = canvas.getActiveObject(); + let qr = jrQrcode.getQrBase64(val, { padding: 0, foreground: $("#qrcode_color").val() }); + let idx = getIndex(item); + objs[idx - 1].val = val; + item.setSrc(qr, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $('#barcode_color').bind('input propertychange', function () { + let item = canvas.getActiveObject() + let idx = getIndex(item) + objs[idx - 1].color = $(this).val() + JsBarcode('#barcode', objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }) + let barcode = document.getElementById('barcode') + let bar = barcode.toDataURL('image/png') + item.setSrc(bar, function (img) { + canvas.renderAll() + recordState() + }) + }) + $('#barcode_field_name').bind('input propertychange', function () { + let idx = getIndex(canvas.getActiveObject()) + objs[idx - 1].name = $('#barcode_field_name').val() + updateList() + recordState() + }) + $('#text_color').bind('input propertychange', function () { + canvas.getActiveObject().set('fill', $('#text_color').val()) + canvas.renderAll() + recordState() + }) + $('#text_back_color').bind('input propertychange', function () { + canvas.getActiveObject().set('textBackgroundColor', $('#text_back_color').val()) + canvas.renderAll() + recordState() + }) + $('#out_text_back_color').bind('input propertychange', function () { + canvas.getActiveObject().set('textBackgroundColor', $('#out_text_back_color').val()) + + canvas.renderAll() + recordState() + }) + $("#text_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#text_size").val())) { + val = parseFloat($("#text_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#text_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + $("#circle_text_color").bind('input propertychange', function () { + canvas.getActiveObject().set("fill", $("#circle_text_color").val()); + canvas.renderAll(); + recordState(); + }) + $("#circle_text_back_color").bind('input propertychange', function () { + canvas.getActiveObject().set("textBackgroundColor", $("#circle_text_back_color").val()); + + canvas.renderAll(); + recordState(); + }) + $("#circle_text_text").bind('input propertychange', function () { + let val = $("#circle_text_text").val().trim() + canvas.getActiveObject().set("text", ` ${val} `); + canvas.renderAll(); + updateList(); + recordState(); + }) + $("#circle_text_center").click(function () { + let idx = getIndex(canvas.getActiveObject()); + if ($(this).prop("checked")) { + objs[idx - 1].circleCenter = 1; + } else { + objs[idx - 1].circleCenter = 0; + } + recordState(); + }); + $('#out_text_color').bind('input propertychange', function () { + canvas.getActiveObject().set('fill', $('#out_text_color').val()) + canvas.renderAll() + recordState() + }) + $("#out_text_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#out_text_size").val())) { + val = parseFloat($("#out_text_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#out_text_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + $('#out_text_text').bind('input propertychange', function () { + let val = $("#out_text_text").val().trim() + canvas.getActiveObject().set("text", ` ${val} `); + canvas.renderAll() + updateList() + recordState() + }) + $('#rect_color').bind('input propertychange', function () { + canvas.getActiveObject().set('fill', $('#rect_color').val()) + + canvas.renderAll() + recordState() + }) + $('#rect_stroke').bind('input propertychange', function () { + canvas.getActiveObject().set('stroke', $('#rect_stroke').val()) + + canvas.renderAll() + recordState() + }) + $('#rect_stroke_width').bind('input propertychange', function () { + if ($('#rect_stroke_width').val() == '') { + $('#rect_stroke_width').val('0') + } else { + $('#rect_stroke_width').val(parseInt($('#rect_stroke_width').val())) + } + canvas.getActiveObject().set('strokeWidth', parseInt($('#rect_stroke_width').val())) + canvas.renderAll() + recordState() + }) + $('#rect_radius').bind('input propertychange', function () { + if ($('#rect_radius').val() == '') { + $('#rect_radius').val('0') + } else { + $('#rect_radius').val(parseInt($('#rect_radius').val())) + } + canvas.getActiveObject().set('rx', parseInt($('#rect_radius').val())) + canvas.getActiveObject().set('ry', parseInt($('#rect_radius').val())) + canvas.renderAll() + recordState() + }) + $('#circle_color').bind('input propertychange', function () { + canvas.getActiveObject().set('fill', $('#circle_color').val()) + + canvas.renderAll() + recordState() + }) + $('#circle_stroke').bind('input propertychange', function () { + canvas.getActiveObject().set('stroke', $('#circle_stroke').val()) + + canvas.renderAll() + recordState() + }) + $('#circle_stroke_width').bind('input propertychange', function () { + if ($('#circle_stroke_width').val() == '') { + $('#circle_stroke_width').val('0') + } else { + $(this).val(parseInt($(this).val())) + } + canvas.getActiveObject().set('strokeWidth', parseInt($('#circle_stroke_width').val())) + canvas.renderAll() + recordState() + }) + $('#counter_start').bind('input propertychange', function () { + canvas.getActiveObject().set('text', $('#counter_start').val()) + let item = canvas.getActiveObject() + let idx = getIndex(item) + objs[idx - 1].start = $(this).val() + canvas.renderAll() + recordState() + }) + $('#counter_add').bind('input propertychange', function () { + let item = canvas.getActiveObject() + let idx = getIndex(item) + objs[idx - 1].add = $(this).val() + canvas.renderAll() + recordState() + }) + $('#counter_color').bind('input propertychange', function () { + canvas.getActiveObject().set('fill', $('#counter_color').val()) + canvas.renderAll() + recordState() + }) + $("#count_size").bind('input propertychange', function () { + let val = 1 + if (parseFloat($("#count_size").val())) { + val = parseFloat($("#count_size").val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#count_size").val(val) + canvas.getActiveObject().set("fontSize", val*dpi/72); + $("#text_h").val((canvas.getActiveObject().get("height")).toFixed(0)); + $("#text_w").val((canvas.getActiveObject().get("width")).toFixed(0)); + canvas.renderAll(); + recordState(); + }) + $('#line_color').bind('input propertychange', function () { + canvas.getActiveObject().set('stroke', $('#line_color').val()) + canvas.renderAll() + recordState() + }) + $('#line_dist').bind('input propertychange', function () { + if ($('#line_dist').val() == '') { + $('#line_dist').val(0) + } + canvas.getActiveObject().set('strokeDashArray', [parseInt($('#line_dist').val()), parseInt($('#line_dist').val())]) + canvas.renderAll() + recordState() + }) + $('#text_text').bind('input propertychange', function () { + let val = $("#text_text").val().trim() + canvas.getActiveObject().set("text", ` ${val} `); + canvas.renderAll() + updateList() + recordState() + }) + $('#barcode_val').bind('input propertychange', function () { + let item = canvas.getActiveObject() + let idx = getIndex(item) + objs[idx - 1].val = $(this).val() + JsBarcode('#barcode', objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }) + + let barcode = document.getElementById('barcode') + let bar = barcode.toDataURL('image/png') + item.setSrc(bar, function (img) { + canvas.renderAll() + recordState() + }) + }) + $("#barcode_show").click(function () { + let item = canvas.getActiveObject(); + let idx = getIndex(canvas.getActiveObject()); + let val = $("#barcode_size").val() + if ($(this).prop("checked")) { + objs[idx - 1].show = true; + val = 0 + } else { + objs[idx - 1].show = false; + } + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: val*dpi/72, font: objs[idx - 1].font }); + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }); + $("#barcode_size").bind('input propertychange', function () { + let item = canvas.getActiveObject(); + let idx = getIndex(item); + let val = 1 + if (parseFloat($(this).val())) { + val = parseFloat($(this).val()) + if (val < 1) { + val = 1 + } else if (val > dpi) { + val = dpi + } else if (val%0.5!==0) { + val = Math.round(val*2)/2 + } + + } + $("#barcode_size").val(val) + objs[idx - 1].fontSize = val*dpi/72; + JsBarcode("#barcode", objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }); + + let barcode = document.getElementById('barcode'); + let bar = barcode.toDataURL("image/png"); + item.setSrc(bar, function (img) { + canvas.renderAll(); + recordState(); + }); + }) + $('#count_font_family').on('change', function () { + canvas.getActiveObject().set('fontFamily', $(this).find('option:selected').text()) + canvas.renderAll() + recordState() + }) + $('#text_font_family').on('change', function () { + canvas.getActiveObject().set('fontFamily', $(this).find('option:selected').text()) + canvas.renderAll() + recordState() + }) + + $('#circle_text_font_family').on('change', function () { + canvas.getActiveObject().set("fontFamily", $(this).find('option:selected').text()) + canvas.renderAll() + recordState(); + }); + + $('#language_select').on('change', function () { + //语言选择 + langua_ge($(this).find('option:selected').val()) + s_lan = $(this).find('option:selected').val() + localStorage.setItem('lang', s_lan) + }) + $('#barcode_font_family').on('change', function () { + let item = canvas.getActiveObject() + let idx = getIndex(item) + objs[idx - 1].font = $(this).find('option:selected').text() + JsBarcode('#barcode', objs[idx - 1].val, { margin: 0, lineColor: objs[idx - 1].color, fontSize: objs[idx - 1].fontSize, font: objs[idx - 1].font }) + + let barcode = document.getElementById('barcode') + let bar = barcode.toDataURL('image/png') + item.setSrc(bar, function (img) { + canvas.renderAll() + recordState() + }) + //recordState(); + }) + $('#out_text_font_family').on('change', function () { + canvas.getActiveObject().set('fontFamily', $(this).find('option:selected').text()) + canvas.renderAll() + recordState() + }) + $('.obj_list').on('click', 'div', function () { + let idx = objs.length - 1 - $(this).index() + selectObj(idx) + }) + $('#exceed').click(function () { + let idx = getIndex(canvas.getActiveObject()) + if ($(this).prop('checked')) { + objs[idx - 1].exceed = true + } else { + objs[idx - 1].exceed = false + } + recordState() + }) + $('#autoWrap').click(function () { + let idx = getIndex(canvas.getActiveObject()) + if ($(this).prop('checked')) { + objs[idx - 1].autoWrap = true + } else { + objs[idx - 1].autoWrap = false + } + recordState() + }) + $('#fixSize').click(function () { + let idx = getIndex(canvas.getActiveObject()) + if ($(this).prop('checked')) { + objs[idx - 1].fixSize = true + } else { + objs[idx - 1].fixSize = false + } + recordState() + }) + $('#delete').click(function () { + let actObjs = canvas.getActiveObjects() + let allObjs = canvas.getObjects() + let flag = 0 + for (let item of actObjs) { + if (item != allObjs[0]) { + let i = getIndex(item) //0是背景,所以从1开始,但objs不包含背景图片,故要-1 + objs.splice(i - 1, 1) + canvas.remove(item) + flag = 1 + } + } + updateList() + canvas.discardActiveObject() + if (flag == 1) { + recordState() + canvas.renderAll() + } + // let actObjs = canvas.getActiveObjects(); + // let allObjs = canvas.getObjects(); + // let flag = 0; + // for( let item of actObjs){ + // if ( item != allObjs[0]){ + // let i = getIndex(item);//0是背景,所以从1开始,但objs不包含背景图片,故要-1 + // objs.splice(i-1,1); + // canvas.remove(item); + // flag = 1; + // updateList(); + // } + // } + // selectObj(-1); + // if(flag == 1){ + // recordState(); + // } + }) + + //在关于我们获取版本号。 + // 注意:function 前面加了 async 关键字 + $("#about").click(async function () { + + // 1. 先异步获取版本号 + let version = "0.0.0"; // 默认值,防止获取失败 + try { + if (window.sysAPI && window.sysAPI.getAppVersion) { + const v = await window.sysAPI.getAppVersion(); + version = "v" + v; // 拼接一个 v,变成 v2.3.017 + } + } catch (e) { + console.error("获取版本号失败", e); + } + + // 2. 获取成功后再弹出窗口 + layer.open({ + type: 1 + , title: language_str("about") + , area: '450px;' + , id: 'LAY_layuipro' + , moveType: 1 + , content: '
' + + '
' + + '
Soon Design
' + + // 【修改点】这里使用了刚才获取到的 version 变量 + '
' + language_str("vers") + ' ' + version + '
' + + '
' + language_str("copyright") + '
' + + '
' + }); + }) + $("#help").click(() => { + ipcRenderer.send('open-help-file'); + }); + function handleObjectSelected() { + let select_obj = canvas.getActiveObject() + if (!select_obj) { + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) //comfirm + return + } + let i = getIndex(select_obj) + if (i != 0) { + //不是背景 + //console.log(objs); + --i + switch (objs[i].type) { + case 1: + //console.log("图片"); + $('#component_type').text(language_str('img')) //"图片" + selectObject(select_obj, i) + selectPic(select_obj, i) + break + case 2: + //console.log("预留图片"); + + $('#component_type').text(language_str('simg')) //"合成图片" + selectObject(select_obj, i) + selectOutPic(select_obj, i) + break + case 3: + $('#component_type').text(language_str('text')) //"文字" + //console.log("文字"); + selectObject(select_obj, i) + selectText(select_obj, i) + break + case 4: + //console.log("标签"); + + $('#component_type').text(language_str('stext')) //"合成文字" + selectObject(select_obj, i) + selectOutText(select_obj, i) + break + case 5: + //console.log("矩形"); + + $('#component_type').text(language_str('rect')) //"矩形" + selectObject(select_obj, i) + selectRect(select_obj, i) + break + case 6: + //console.log("圆形"); + + $('#component_type').text(language_str('circ')) //"圆形" + selectObject(select_obj, i) + selectCircle(select_obj, i) + break + case 7: + //console.log("直线"); + + $('#component_type').text(language_str('line')) //"直线" + selectObject(select_obj, i) + selectLine(select_obj, i) + break + case 8: + //console.log("二维码"); + + $('#component_type').text(language_str('qrc')) //"二维码" + selectObject(select_obj, i) + selectQrCode(select_obj, i) + break + case 9: + //console.log("条形码"); + + $('#component_type').text(language_str('barc')) //"条形码" + selectObject(select_obj, i) + selectBarCode(select_obj, i) + break + case 10: + //console.log("计数器"); + + $('#component_type').text(language_str('counter')) //"计数器" + selectObject(select_obj, i) + selectCounter(select_obj, i) + break + case 11: + + $("#component_type").text(language_str("ctext"));//"圆形文字" + //console.log("圆形文字"); + selectObject(select_obj, i); + selectCircleText(select_obj, i); + break; + } + } else { + //背景,收起对象属性 + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#component_type').text(language_str('bg')) + } + } + + canvas1.on('mouse:move', (e) => { + let width = canvas.getWidth() + let height = canvas.getHeight() + $('#posText').text( + 'X: ' + (e.pointer.x - (width / 2 - (width / 2 - background_image.left) * zoom)).toFixed(0) + ' Y: ' + (e.pointer.y - (height / 2 - (height / 2 - background_image.top) * zoom)).toFixed(0) + ) + }) + canvas1.on("object:moving", function (e) { + let obj = canvas1.getActiveObject() + let idx = getIndex(e.target, canvas1); + // 添加边界检查,避免访问 undefined + if (idx > 0 && idx <= objs1.length && objs1[idx - 1] && objs1[idx - 1].type && objs1[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { + let pointer = { + x: obj.left, + y: obj.top + } + let distance = getDisdance(pointer.x, pointer.y, canvas1.width/2, canvas1.height/2) + let angleDeg = getDeg(pointer) + let flipped = pointer.y > canvas1.height/2 ? true : false + circle_text_diameter.setValue(distance*2-50); + obj.set("diameter", distance*2); + obj.set("angle", angleDeg); + obj.set("flipped", flipped); + } + updateControls(); + }); + // 监听键盘事件 + + // 复制选中对象 + function copySelection() { + const activeObjects = canvas.getActiveObjects(); + + if (!activeObjects || activeObjects.length === 0) { + console.log("未选中任何对象"); + layer.msg("未选中任何对象") + return; + } + + // 存储原始对象用于克隆 + clipboardData.objects = activeObjects; + console.log(activeObjects) + console.log(`已复制 ${activeObjects.length} 个对象`); + } + + // 粘贴对象 + function pasteFromClipboard() { + if (!clipboardData.objects || clipboardData.objects.length === 0) { + console.log("剪贴板为空"); + layer.msg("剪贴板为空") + return; + } + + // 克隆所有对象 + fabric.util.enlivenObjects( + clipboardData.objects.map(obj => obj.toObject()), + clonedObjects => { + // 设置新位置(添加偏移) + clonedObjects.forEach((clone, index) => { + let idx = getIndex(clipboardData.objects[index]) + console.log(clone) + clone.set({ + left: clone.left + clipboardData.offset, + top: clone.top + clipboardData.offset, + evented: true + }); + // 计算整体偏移 + const groupOffset = clone.group; + if (groupOffset) { + clone.set({ + left: groupOffset.left + clipboard.offset, + top: groupOffset.top + clipboard.offset + }); + } + + // 添加到画布 + canvas.add(clone); + let tobj = {...objs[idx-1]} + if (tobj.name) tobj.name = tobj.name + objs.length + objs.push(tobj); + canvas.renderAll(); + updateList(); + recordState(); + }); + + // 选中粘贴的对象 + canvas.discardActiveObject(); + const sel = new fabric.ActiveSelection(clonedObjects, { + canvas: canvas + }); + canvas.setActiveObject(sel); + canvas.requestRenderAll(); + selectObj(objs.length - 1); + // 增加下次偏移 + clipboardData.offset += 10; + } + ); + } + canvas1.on('object:scaling', function (event) { + //if(objs[getIndex(event.target) - 1].type == 5 || objs[getIndex(event.target) - 1].type == 6){ + // //canvas.getActiveObject().set("width", canvas.getActiveObject().get("width") * canvas.getActiveObject().get("scaleX")); + // //canvas.getActiveObject().set("height", canvas.getActiveObject().get("height") * canvas.getActiveObject().get("scaleY")); + // //canvas.getActiveObject().set("scaleX", 1); + // //canvas.getActiveObject().set("scaleY", 1); + // //console.log(canvas.getActiveObject().get("scaleX")); + // //canvas1.discardActiveObject(); + // //canvas1.setActiveObject(event.target); + // //canvas1.renderAll(); + // //updateControls(); + //} + }) + canvas1.on('object:rotating', function (e) { + updateControls() + }) + canvas1.on('object:rotated', function (e) { + console.log('object:rotated') + recordState() + }) + canvas1.on('object:added', function (e) { + //console.log("object:added"); + //recordState(); + ////console.log(objs); + }) + canvas1.on('mouse:down', (e) => { + //单选 + let select_obj = canvas.getActiveObject() + console.log(select_obj) + let i = getIndex(select_obj) + if (i != 0) { + //不是背景 + //console.log(objs); + --i + switch (objs[i].type) { + case 1: + //console.log("图片"); + $('#component_type').text(language_str('img')) //"图片" + selectObject(select_obj, i) + selectPic(select_obj, i) + break + case 2: + //console.log("预留图片"); + + $('#component_type').text(language_str('simg')) //"合成图片" + selectObject(select_obj, i) + selectOutPic(select_obj, i) + break + case 3: + $('#component_type').text(language_str('text')) //"文字" + //console.log("文字"); + selectObject(select_obj, i) + selectText(select_obj, i) + break + case 4: + //console.log("标签"); + + $('#component_type').text(language_str('stext')) //"合成文字" + selectObject(select_obj, i) + selectOutText(select_obj, i) + break + case 5: + //console.log("矩形"); + + $('#component_type').text(language_str('rect')) //"矩形" + selectObject(select_obj, i) + selectRect(select_obj, i) + break + case 6: + //console.log("圆形"); + + $('#component_type').text(language_str('circ')) //"圆形" + selectObject(select_obj, i) + selectCircle(select_obj, i) + break + case 7: + //console.log("直线"); + + $('#component_type').text(language_str('line')) //"直线" + selectObject(select_obj, i) + selectLine(select_obj, i) + break + case 8: + //console.log("二维码"); + + $('#component_type').text(language_str('qrc')) //"二维码" + selectObject(select_obj, i) + selectQrCode(select_obj, i) + break + case 9: + //console.log("条形码"); + + $('#component_type').text(language_str('barc')) //"条形码" + selectObject(select_obj, i) + selectBarCode(select_obj, i) + break + case 10: + //console.log("计数器"); + + $('#component_type').text(language_str('counter')) //"计数器" + selectObject(select_obj, i) + selectCounter(select_obj, i) + break + case 11: + + $("#component_type").text(language_str("ctext"));//"圆形文字" + //console.log("圆形文字"); + selectObject(select_obj, i); + selectCircleText(select_obj, i); + break; + } + selectObj(i, true) //如果是点击画布中,则相应列表 + } else { + //背景,收起对象属性 + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#counter_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + selectObj(-1) + $('#component_type').text(language_str('bg')) + } + }) + canvas1.on('text:editing:entered', function () { + _textEdit = true + }) + canvas1.on('text:editing:exited', function () { + _textEdit = false + }) + canvas1.on('object:modified', function (event) { + if (canvas1.getActiveObjects().length > 1) { + return + } + if (objs[getIndex(event.target) - 1].type == 3) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#text_size').val(event.target.fontSize) + canvas1.renderAll() + updateControls() + } else if (objs[getIndex(event.target) - 1].type == 4) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#out_text_size').val(event.target.fontSize) + canvas1.renderAll() + } else if (objs[getIndex(event.target) - 1].type == 10) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#count_size').val(event.target.fontSize) + canvas1.renderAll() + } else if (objs[getIndex(event.target) - 1].type == 5) { + canvas1.getActiveObject().set('width', canvas1.getActiveObject().get('width') * canvas1.getActiveObject().get('scaleX')) + canvas1.getActiveObject().set('height', canvas1.getActiveObject().get('height') * canvas1.getActiveObject().get('scaleY')) + canvas1.getActiveObject().set('scaleX', 1) + canvas1.getActiveObject().set('scaleY', 1) + updateControls() + } + recordState() + }) + canvas1.on('mouse:up', (e) => { + //HERE + switch (addState) { + case 1: + addPic(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addPic').css('background-color', '') + break + case 2: + addOutPic(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addOutPic').css('background-color', '') + break + case 3: + addText(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addText').css('background-color', '') + break + case 4: + addOutText(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addOutText').css('background-color', '') + break + case 5: + addRect(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addRect').css('background-color', '') + break + case 6: + addCircle(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addCircle').css('background-color', '') + break + case 7: + addLine(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addLine').css('background-color', '') + break + case 8: + addQrCode(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addQrCode').css('background-color', '') + break + case 9: + addBarCode(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas1.renderAll() + $('#addBarCode').css('background-color', '') + break + case 10: + addCounter(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + $('#addCounter').css('background-color', '') + break + case 11: + addCircleText(e.pointer); + addState = 0; + background_image.hoverCursor = "default"; + canvas1.renderAll(); + $("#addCircleText").css("background-color", ""); + break; + } + }) + canvas1.on('text:changed', (e) => { + $('#text_text').val(e.target.get('text')) + }) + canvas1.on('after:render', function () { + if (is_bgi_add) { + var image = document.getElementById('source_front') + ctx1.drawImage(image, (canvas.width - $('#source_front').width()) / 2, (canvas.height - $('#source_front').height()) / 2, $('#source_front').width(), $('#source_front').height()) + + /* + 实际905 * 534 + 1052 * + */ + } + }) + canvas2.on('text:editing:entered', function () { + _textEdit = true + }) + canvas2.on('text:editing:exited', function () { + _textEdit = false + }) + canvas2.on('after:render', function () { + if (is_bgi_add) { + var image = document.getElementById('source_back') + ctx2.drawImage(image, (canvas.width - $('#source_back').width()) / 2, (canvas1.height - $('#source_back').height()) / 2, $('#source_back').width(), $('#source_back').height()) + } + }) + canvas2.on('mouse:move', (e) => { + $('#posText').text('X: ' + (e.pointer.x - background_image.left).toFixed(0) + ' Y: ' + (e.pointer.y - background_image.top).toFixed(0)) + }) + canvas2.on("object:moving", function (e) { + let obj = canvas2.getActiveObject() + let idx = getIndex(e.target, canvas2); + // 添加边界检查,避免访问 undefined + if (idx > 0 && idx <= objs2.length && objs2[idx - 1] && objs2[idx - 1].type && objs2[idx - 1].type === 11 && $("#circle_text_center").prop("checked")) { + let pointer = { + x: obj.left, + y: obj.top + } + let distance = getDisdance(pointer.x, pointer.y, canvas2.width/2, canvas2.height/2) + // 计算角度时需要使用 canvas2 + let centerX = canvas2.width / 2; + let centerY = canvas2.height / 2; + let mouseX = pointer.x; + let mouseY = pointer.y; + let dx = mouseX - centerX; + let dy = mouseY - centerY; + let angleRad = Math.atan2(dy, dx); + let angleDeg = angleRad * (180/Math.PI); + if (angleDeg < 0) { + angleDeg += 360; + } + angleDeg += 90; + let flipped = pointer.y > canvas2.height/2 ? true : false + circle_text_diameter.setValue(distance*2-50); + obj.set("diameter", distance*2); + obj.set("angle", angleDeg); + obj.set("flipped", flipped); + } + updateControls(); + }); + canvas2.on('object:scaling', function (e) { + updateControls() + }) + canvas2.on('object:rotating', function (e) { + updateControls() + }) + canvas2.on('object:rotated', function (e) { + console.log('object:rotated') + recordState() + }) + canvas2.on('object:added', function (e) { + //console.log("object:added"); + //recordState(); + ////console.log(objs); + }) + canvas2.on('mouse:down', (e) => { + //单选 + let select_obj = canvas.getActiveObject() + let i = getIndex(select_obj) + if (i != 0) { + //不是背景 + //console.log(objs); + --i + switch (objs[i].type) { + case 1: + //console.log("图片"); + $('#component_type').text(language_str('img')) //"图片" + selectObject(select_obj, i) + selectPic(select_obj, i) + break + case 2: + //console.log("预留图片"); + + $('#component_type').text(language_str('simg')) //"合成图片" + selectObject(select_obj, i) + selectOutPic(select_obj, i) + break + case 3: + $('#component_type').text(language_str('text')) //"文字" + //console.log("文字"); + selectObject(select_obj, i) + selectText(select_obj, i) + break + case 4: + //console.log("标签"); + + $('#component_type').text(language_str('stext')) //"合成文字" + selectObject(select_obj, i) + selectOutText(select_obj, i) + break + case 5: + //console.log("矩形"); + + $('#component_type').text(language_str('rect')) //"矩形" + selectObject(select_obj, i) + selectRect(select_obj, i) + break + case 6: + //console.log("圆形"); + + $('#component_type').text(language_str('circ')) //"圆形" + selectObject(select_obj, i) + selectCircle(select_obj, i) + break + case 7: + //console.log("直线"); + + $('#component_type').text(language_str('line')) //"直线" + selectObject(select_obj, i) + selectLine(select_obj, i) + break + case 8: + //console.log("二维码"); + + $('#component_type').text(language_str('qrc')) //"二维码" + selectObject(select_obj, i) + selectQrCode(select_obj, i) + break + case 9: + //console.log("条形码"); + + $('#component_type').text(language_str('barc')) //"条形码" + selectObject(select_obj, i) + selectBarCode(select_obj, i) + break + case 10: + //console.log("计数器"); + + $('#component_type').text(language_str('counter')) //"计数器" + selectObject(select_obj, i) + selectCounter(select_obj, i) + break + case 11: + + $("#component_type").text(language_str("ctext"));//"圆形文字" + //console.log("圆形文字"); + selectObject(select_obj, i); + selectCircleText(select_obj, i); + break; + } + selectObj(i, true) //如果是点击画布中,则相应列表 + } else { + //背景,收起对象属性 + $('#base_control').hide() + $('#line_control').hide() + $('#pic_control').hide() + $('#out_pic_control').hide() + $('#text_control').hide() + $("#circle_text_control").hide(); + $('#out_text_control').hide() + $('#rect_control').hide() + $('#circle_control').hide() + $('#qrcode_Controll').hide() + $('#barcode_controll').hide() + $('#counter_control').hide() + selectObj(-1) + $('#component_type').text(language_str('bg')) + } + }) + canvas2.on('object:modified', function (event) { + if (canvas.getActiveObjects().length > 1) { + return + } + if (objs[getIndex(event.target) - 1].type == 3) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#text_size').val(event.target.fontSize) + canvas2.renderAll() + } else if (objs[getIndex(event.target) - 1].type == 4) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#out_text_size').val(event.target.fontSize) + canvas2.renderAll() + } else if (objs[getIndex(event.target) - 1].type == 10) { + event.target.fontSize *= event.target.scaleX + event.target.fontSize = event.target.fontSize.toFixed(0) + event.target.scaleX = 1 + event.target.scaleY = 1 + event.target._clearCache() + // $('#count_size').val(event.target.fontSize) + canvas2.renderAll() + } else if (objs[getIndex(event.target) - 1].type == 5) { + canvas2.getActiveObject().set('width', canvas2.getActiveObject().get('width') * canvas2.getActiveObject().get('scaleX')) + canvas2.getActiveObject().set('height', canvas2.getActiveObject().get('height') * canvas2.getActiveObject().get('scaleY')) + canvas2.getActiveObject().set('scaleX', 1) + canvas2.getActiveObject().set('scaleY', 1) + updateControls() + } + console.log('object:modified') + recordState() + }) + canvas2.on('mouse:up', (e) => { + //HERE + switch (addState) { + case 1: + addPic(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addPic').css('background-color', '') + break + case 2: + addOutPic(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addOutPic').css('background-color', '') + break + case 3: + addText(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addText').css('background-color', '') + break + case 4: + addOutText(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addOutText').css('background-color', '') + break + case 5: + addRect(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addRect').css('background-color', '') + break + case 6: + addCircle(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addCircle').css('background-color', '') + break + case 7: + addLine(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addLine').css('background-color', '') + break + case 8: + addQrCode(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addQrCode').css('background-color', '') + break + case 9: + addBarCode(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addBarCode').css('background-color', '') + break + case 10: + addCounter(e.pointer) + addState = 0 + background_image.hoverCursor = 'default' + canvas2.renderAll() + $('#addCounter').css('background-color', '') + break + case 11: + addCircleText(e.pointer); + addState = 0; + background_image.hoverCursor = "default"; + canvas2.renderAll(); + $("#addCircleText").css("background-color", ""); + break; + } + }) + var circle_text_size = slider.render({ + elem: '#circle_text_size', + min: 10, + max: 120 + , change: function (value) { + canvas.getActiveObject().set("fontSize", value); + canvas.renderAll(); + } + }); + var circle_text_diameter = slider.render({ + elem: '#circle_text_diameter', + min: 50, + max: 1100 + , change: function (value) { + canvas.getActiveObject().set("diameter", value); + canvas.renderAll(); + } + }); + var out_pic_op = slider.render({ + elem: '#out_pic_op', + change: function (value) { + console.log(value) + console.log(canvas.getActiveObject()) + canvas.getActiveObject().set('opacity', (100 - value) / 100) + canvas.renderAll() + } + }) + var pic_op = slider.render({ + elem: '#pic_op', + change: function (value) { + console.log(value) + console.log(canvas.getActiveObject()) + canvas.getActiveObject().set('opacity', (100 - value) / 100) + canvas.renderAll() + } + }) + var rect_op = slider.render({ + elem: '#rect_op', + change: function (value) { + console.log(value) + console.log(canvas.getActiveObject()) + canvas.getActiveObject().set('opacity', (100 - value) / 100) + canvas.renderAll() + } + }) + var circle_op = slider.render({ + elem: '#circle_op', + change: function (value) { + console.log(value) + console.log(canvas.getActiveObject()) + canvas.getActiveObject().set('opacity', (100 - value) / 100) + canvas.renderAll() + } + }) + + // 标尺绘制函数 + function initRulers() { + const dpi = 300; + const mmToPx = dpi / 25.4; // 1mm = 11.811px (300 DPI) + const rulerHeight = 20; + const rulerWidth = 20; + + // 获取实际画布的像素尺寸(px) + // design2.html 的实际画布尺寸是 1012px x 648px + const actualCanvasWidth = canvas1.width || 1012; // 实际画布宽度(px) + const actualCanvasHeight = canvas1.height || 648; // 实际画布高度(px) + + // 获取显示区域的尺寸(包括 margin) + const canvasDiv = $('#canvas-div'); + const canvasWrapper = $('.canvas-wrapper'); + // 使用 canvas-wrapper 的宽度,如果没有则使用 canvas_div 的宽度 + let canvasWidth = canvasWrapper.width(); + let canvasHeight = canvasWrapper.height(); + if (!canvasWidth || canvasWidth === 0) { + const canvas1Width = $('#canvas1').width() || canvasDiv.width() - 20; + canvasWidth = canvas1Width + 20; // 加上 margin-left: 20px + } + if (!canvasHeight || canvasHeight === 0) { + const canvas1Height = $('#canvas1').height() || canvasDiv.height() - 20; + canvasHeight = canvas1Height + 20; // 加上 margin-top: 20px + } + + // 设置标尺尺寸(使用显示区域的尺寸) + $('#ruler-h').attr('width', canvasWidth).attr('height', rulerHeight); + $('#ruler-v').attr('width', rulerWidth).attr('height', canvasHeight); + $('.ruler-horizontal').css('width', canvasWidth + 'px'); + $('.ruler-vertical').css('height', canvasHeight + 'px'); + + // 绘制水平标尺 + function drawHorizontalRuler() { + const ctx = document.getElementById('ruler-h').getContext('2d'); + ctx.clearRect(0, 0, canvasWidth, rulerHeight); + + // 背景 + ctx.fillStyle = '#2C2C2C'; + ctx.fillRect(0, 0, canvasWidth, rulerHeight); + + // 边框 + ctx.strokeStyle = '#1E1E1E'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, canvasWidth, rulerHeight); + ctx.beginPath(); + ctx.moveTo(0, rulerHeight - 1); + ctx.lineTo(canvasWidth, rulerHeight - 1); + ctx.stroke(); + + // 刻度线 + ctx.strokeStyle = '#888888'; + ctx.fillStyle = '#CCCCCC'; + ctx.font = '10px Arial'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 + // 实际画布物理宽度 = actualCanvasWidth / dpi * 25.4 (mm) + // 显示区域宽度 = actualCanvasWidth * zoom (px) + // 所以:1mm 在显示区域中 = (actualCanvasWidth * zoom) / (actualCanvasWidth / dpi * 25.4) = zoom * dpi / 25.4 + const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 + const startX = 0; + const endX = canvasWidth; + + // 绘制主要刻度(每10mm) + for (let mm = 0; mm * pxPerMm <= endX; mm += 10) { + const x = startX + mm * pxPerMm; + if (x > endX) break; + + ctx.beginPath(); + ctx.moveTo(x, 0); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + + // 数字 + ctx.fillText(mm + 'mm', x + 2, 2); + } + + // 绘制次要刻度(每1mm) + for (let mm = 0; mm * pxPerMm <= endX; mm += 1) { + const x = startX + mm * pxPerMm; + if (x > endX) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(x, rulerHeight - 8); + ctx.lineTo(x, rulerHeight); + ctx.stroke(); + } + } + + // 绘制垂直标尺 + function drawVerticalRuler() { + const ctx = document.getElementById('ruler-v').getContext('2d'); + ctx.clearRect(0, 0, rulerWidth, canvasHeight); + + // 背景 + ctx.fillStyle = '#2C2C2C'; + ctx.fillRect(0, 0, rulerWidth, canvasHeight); + + // 边框 + ctx.strokeStyle = '#1E1E1E'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, rulerWidth, canvasHeight); + ctx.beginPath(); + ctx.moveTo(rulerWidth - 1, 0); + ctx.lineTo(rulerWidth - 1, canvasHeight); + ctx.stroke(); + + // 刻度线 + ctx.strokeStyle = '#888888'; + ctx.fillStyle = '#CCCCCC'; + ctx.font = '10px Arial'; + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + + // 计算:实际画布尺寸(px)对应的物理尺寸(mm),然后根据显示区域的缩放比例计算 + const pxPerMm = zoom * mmToPx; // 在显示区域中,1mm对应的像素数 + const startY = 0; + const endY = canvasHeight; + + // 绘制主要刻度(每10mm) + for (let mm = 0; mm * pxPerMm <= endY; mm += 10) { + const y = startY + mm * pxPerMm; + if (y > endY) break; + + ctx.beginPath(); + ctx.moveTo(0, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + + // 数字(旋转90度) + ctx.save(); + ctx.translate(2, y + 8); + ctx.rotate(-Math.PI / 2); + ctx.fillText(mm + 'mm', 0, 0); + ctx.restore(); + } + + // 绘制次要刻度(每1mm) + for (let mm = 0; mm * pxPerMm <= endY; mm += 1) { + const y = startY + mm * pxPerMm; + if (y > endY) break; + if (mm % 10 === 0) continue; // 跳过主要刻度 + + ctx.beginPath(); + ctx.moveTo(rulerWidth - 8, y); + ctx.lineTo(rulerWidth, y); + ctx.stroke(); + } + } + + // 初始绘制 + drawHorizontalRuler(); + drawVerticalRuler(); + + // 监听窗口大小变化 + $(window).on('resize', function() { + setTimeout(function() { + const canvasWrapper = $('.canvas-wrapper'); + let newWidth = canvasWrapper.width(); + let newHeight = canvasWrapper.height(); + if (!newWidth || newWidth === 0) { + const canvas1Width = $('#canvas1').width() || $('#canvas-div').width() - 20; + newWidth = canvas1Width + 20; // 加上 margin-left: 20px + } + if (!newHeight || newHeight === 0) { + const canvas1Height = $('#canvas1').height() || $('#canvas-div').height() - 20; + newHeight = canvas1Height + 20; // 加上 margin-top: 20px + } + if (newWidth !== canvasWidth || newHeight !== canvasHeight) { + canvasWidth = newWidth; + canvasHeight = newHeight; + $('#ruler-h').attr('width', newWidth); + $('#ruler-v').attr('height', newHeight); + $('.ruler-horizontal').css('width', newWidth + 'px'); + $('.ruler-vertical').css('height', newHeight + 'px'); + drawHorizontalRuler(); + drawVerticalRuler(); + } + }, 100); + }); + + // 监听画布缩放变化 + canvas1.on('after:render', function() { + drawHorizontalRuler(); + drawVerticalRuler(); + }); + canvas2.on('after:render', function() { + drawHorizontalRuler(); + drawVerticalRuler(); + }); + } + }) diff --git a/lib/difxapi.dl b/lib/difxapi.dl new file mode 100644 index 0000000..6f1b042 --- /dev/null +++ b/lib/difxapi.dl @@ -0,0 +1,21 @@ +Example: + nvm install v0.10.32 Install a specific version number + nvm use 0.10 Use the latest available 0.10.x release + nvm run 0.10.32 app.js Run app.js using node v0.10.32 + nvm exec 0.10.32 node app.js Run `node app.js` with the PATH pointing to node v0.10.32 + nvm alias default 0.10.32 Set default node version on a shell + +Note: + to remove, delete, or uninstall nvm - just remove the `$NVM_DIR` folder (usually `~/.nvm`) + +Xupnei:~ xuwenwei$ nvm install v16.5.0 + +Version 'v16.5.0' not found - try `nvm ls-remote` to browse available versions. +Xupnei:~ xuwenwei$ +Xupnei:~ xuwenwei$ nvm install v16.5.0 +Downloading and installing node v16.5.0... +Downloading https://nodejs.org/dist/v16.5.0/node-v16.5.0-darwin-arm64.tar.gz... +######################################################################### 100.0% +Computing checksum with shasum -a 256 +Checksums matched! +Now using node v16.5.0 (npm v7.19.1) diff --git a/lib/directx9.dl b/lib/directx9.dl new file mode 100644 index 0000000000000000000000000000000000000000..46e41ff9aa7e9c688bc8132c0a491dc3cc6dcb00 GIT binary patch literal 12192 zcmdsdX;@p=wdfgy&>qUAV7%G#0(maKp-Sp5;D*vg~m-? z+wH}UV>@|inznu!?6fzxpX+$YU}I?0HqA|Qdvnu%$-S|SnLMVk&G7D5-daZz9%A?2 z_v?M{9oRbithM*rd+oi}+H0@9|0C?*Pyfr+r~O+B3F4al`I9@;1pkmJwPc#`^)?xN zkurT7g>Qfr47C+tR-tbb1VKlK+Ln_5GU(e<5ag&aZ@)kh638#%iyF;qSN}-EH!9kgU-vdGgYj6u#|li;;6$IS=O{$Y`S_q59XO;Q;4)IRg|WubQ~wYVUPh)feI2*Rg?LhuegDw-fh7;?B}j-H`LRr8c!L{4?j z313tMFf@+|=P`w(SUoQ}tC}TbBa8qLvojGRGWDF|Fqww4_8kIe2_IS;>+l*dw?KIgg=sm1K-B>!+#ujBM#7<-{j~vSvu@aZuL5o)8*{z zcJ+8S5GKAd{1ljR{_!~svQyB8IkHx`o@{#5$OOPr_M0L?c!g0RYkBdFIApHB1CWWr9+I1Xe#3qdKx$em zjNjvKRmv+HpGa#U7f?XY|7yoDFiyCjIGJYbC|kMhm+|mj+hO-AQR=W)%e6M z?#A;83ozLG5M#Ui^V|?7&-PpZ^AgJSUrK8JO$>EvfT~sLa;}9u!e9OO?>~<6=H(om z#rMsT7(%k=e-M5d$6;_9~9J>7Q={t&4+V2+4b`X~;Iv-i-hJqU>1IC}~K zCwqUeb1x2D)AG-EJtP#2`1ZRc4=(uY=ds{kl-;I(TC$2oiY}M4fb-hhoyuCS+wCyP98kN%Od2%E+{0OO@qYq=HdzEZs zP|0RN8Fqr0fRZVM2hBJM_%Nh^pgSxiXk`p)W)xZt&<@mya}=nAA4-y6>f0OuB@F=$ z#)>EdU|3#+$x$ettsl`MMTe;m%qkQFK^gz&&$TE(0mInZ&LJFT@-VS67Kh#DEim~& zSrJzBbB_>3*w*84pcS^F(F>(9$fJ=A8Nn8y%5Eo!{n$tMp0<~eaEiVSXMoJW9uDv} z1}7iq{deuHl{oKcv)hmm9YqEQ#SJS$qZ|fBOoFB)8WrrZt@0H}=!uX$U%#?@WpjW~ zy>G7@2ZsZzgKgLDX={T|GFsn;3_)88nP0>0!ZIDcZU;90^3M%_#&46)=b=?3POq}u zwhD`XsHO^}gfuaRl_o~A;f!1(+X05kl?KS$(;Txvkt60giWpJN5!5Jt4x48v!LVwE z6rPQvj!W@m1R&bG|Y%}OFb26z!TciYZ}#toOhEwEJ4kC0@v@Sp{<{3D3o2sUFfx9+W#-mR(Qqp;KJ#}u zKMAZBz%(Bqqldo1_8wmNLtLZ8f?$=mXF-M>gY_+~EsBupV+4}VsX#_{9VN@JgrD0T z-cFDS^NLhTO1~^*$Xx;{ol3>bQ^Z+@8aB_)5U?Q9)X1QKp!g+G5Ws~YPKY?s$e@r6 z;DD9Ij$%tVKbZDd2$-5BK`ljN$m?EK&Qq;H-RrSqaZpNHPV&83MK z9-Y_L=n&!s9|&fbd-biAe?~6A)lqr-ysD7cW7+YlPlVV3my>VGQoo?Nh%H$hE-%h1 z>VS4{8!ybk`G21NKG-j#^>1r`ya$KU)8kZyilgjDms?`7=4X#mPex{d=g~oQph3hG zcw!a=L8O_=EvZVAd!C6`vxc zACm;HqQG&M<0KN1icPN$N&6KHlg-8To#j~#^>^Y z--v4S%VnRMa9+KfuhhrEB8L`b{;&V>ZEOm=dw|rIJDT8w+?`7q5Y$d0>!2PiXQ+KqXhfs11?IJ+{OkHe`(g?>2rW_k!e;L`X$(<-yVVWZ}DK z`Oyl9Ek9R#=;zu#g$X2w^!z4*{#V`}y*yq@U{R2AeqiY@NMUyv%J_c$3J*>W`sn-U zUy<}5`3k>|jGaeO@9$9MSHc@s2Waud!pqMFp|uSNeF>qR&xP9&$~je=>fnvAh#fCZ z1Y!A`f-wJUr=AGHit~c7y%n502&?4h73%uG`}RKsVRbbjdb6_iL0A*gqyJ3O`?_VI zoj-)601oy-iUv8^4~d1Mhag2m+79100B42tCn#G02{lnimMe0|7Wg7BucuxDVL@#a zE2V_QAlf2k75Z>O8Oy7dW2#4{$$=ohY9pYvwJxs{RSN=(#ct*lZ$k8BaD=%^VnTX1%Dh|cEw(RpImUmirK zd67;l(lLeTSnfxM+qL&WblMl`*pW_Gh)(zY=(u*3J&4ZMMLJ%j(-Wez?S6Fnc7Y#& z<`8!{_i+V$u#&_=MRWz)ga`fKg0lDs!F9qUfN+AKfD*kzP?7l< z0fRQ~b}cZjBp^JGis)=y-ltW+zl6?@74|o)JH6;W5p)`2j`iHs;KJ|WOjg|Q)$}o}mI}vblhCMGFPt7)dpHXgOYG-b zdp9^CYPYlfF*9ty*JQ7<*XLCkw`G(Ux4SmMQ(05jQCQawIej+#?(A1H#zF6y}oK!L06{ET9Bu2 z$ZGFvsA=EUu+`RH&{5c!zq!Y`)!0{E)1Fbixj$t9`*XQQN13VLom_73-ESABk|I=`WzW^04Z@3oh7 zWOJ^D{Jvs!Q>9+-=Ja~I)?cUYu{q0n3$q&fvP=%0+G*(JY<{&)o11A<7u&QohQ9vH zOpiUcx6<#=?as;HtW~GyxIKmjU7x9;+M=~+yjoj{vCO`?JJ(RKt)ig6Do1VTFqP;s zti?6v?i^o7c~gCPW#^`Cg?h71T|laT#sjKZobW*ZRu;wG`jmc z?dsefTS;z-KBrM%?{ifZwCjw<%Elb0wz8*-(`Tt`H2qy_n?dWy)aopLbx)y9+i%G5 zc6N29=jcoZU52l$rbnON>uo6U<#L(zS-FNLPhMSlM`M9nXQvU&X zeO8SX#M0E=?zUy=G*&fNU+mQP znVB|?$6l!J(q#F1%w21IroP&~YzwE}oTuw7>aaA{>Ab#*>fG$Avf8ff>0Fjn<#nTin@WEY0q-74;bn9bBm~-_}%W&}69FvmKqzoOElgmQ#Cki)wmc zu#K8c73!*fuf9{QDXerl>U-5%m%2<-kXL9b(yvK3dUTEHxs?V(dQoMqT34NwUgPi@ z+*(6cj^3j#ai{AI-DRFCb!WQT)7V?(-3L<1mac;A!u)!*rQKKAu}N3<|9;Zyn*5s! zdW*9vE4P+Z7v@xedEd0DrmnuqSPo{Ov~)czFA&X49vHBo7rf4D_bV+9lh?aG*<$io zla;ultWU-jZ|}KXQ@9o;EBRQ^`eewHB}Vt}>MLKvkaF?{iSmx;PEsC-FW-Y!D%aV& z+m&9A1qqP_Mi=!N(YJ=D@AdULxb?}~>{egL+Du)ByZ4)w@93z{?oQqSG_*!X)_n4k zM}fvVHQ?{*&%K~P8SpAfMaZ78WyAl#{pf1sTjhXd8J7A&@_~!*9*nv8MY1N?`V!kb zb{`jV1K=BRpGN-6U+&`#a^v&a@#Ty2EE$2uT6_tR^Ntg z(-{~q0=5$zNh}6skTaE)kBqC_;E=%7>@@k1V8c^mMlz#00q(QZ71mM^d@v@q2qt~{R^WQ zj`PBSriFuf>ZY)z{!f1EMWX^ot#)U--PQ;B(%Zgp;ooU)ilQqhx|*WvD0(wRH&e8U z0$rv#3e>jk5HL-al)DFj;3`O8K$?RN;6ToT)O`eE{}u!=W8ixQZT zdLb{77t2O~uv`m-a{)O_u`KaEaz*XR-PFo=D0#8!dxyy(a^UA=3?W}97bqMM zecD8S4E)~KMc9NLQ(Ypdx8o>w$~;90*eTT%C1j@=nw@N!o|+V~(=C@TO-tezE@N2# zh=83|O^3;j#R-oH*{POG(^Cx+YF!r<$j(Ef3(7CsbD@$p=OFyDLIk^TCjTe!^fVv6#2H|HRFNG0ESGLM(exI;>}$DxbZexEn^qX7k6DOeKCQrGNu}fE@LmsN%nl}#j)$>^}8gmjti(36 z(@S7{7A{5sW7AWu?AR;e+;506QZ*KDIxwsnOR%tGSYm80e(oj{_kR4sSg_6Ws`Fto z;Nm93vFBPYoIlt89<~c$7_keguf=xuT=TiFy8_7WcU6B~>Uw`yG<)Hh3yJI)%)wZ~ z1!md%OD{0X15~zYFL>As%tQPO$p!&R9w5CD{7m9MV-|!Iw8b}WK7Y;|L?#1LJF_)@ zW^8!pCq$2YfgNWjc7?O!GIm1o8cfFIfro(w)zk|AkC(9*nlDWCz7oTp2bRy1yJYN` zEOFw6FzMJE(u=P__Qf#i_#6C`O))7T#8Z>~IBKa+@}ocV^wLWZ)d(Hgfg15oXnpz= zY`k!LV12R*4{jR>;xV3qM|PnkjE2|-#5cn3fgdFV5{zvqV>c292KZP9|NRQ)0S-wy zH1I?qHUhNoS;--yvS<$9f_yTVw(jUOOy#TfmmP;ZT@-2`^2!=or;vD2} zK{^9O@SFU&T=C%$7B~%-KG7g%#c={R|Jy5OCE0iyB-u_zW-KLSN2M4mUMi4%Eabo6 zERjYt(Q-!4NFW3a1;KJ2CB@NE*>HZUk-bR@2gp?`*Ac?kN!dFgTlUUvTQ-8WY_e1G z;XStOAKxH%kO$u+p|y2VLCTv{q--gnxIw|U77qMwAR-Mi*Aar50UC=Y31qXsB^7_5 zUI)Hzo4xHsSi)C>aFU)XP^1czDB_grI!&cgft)~)N(XXb!qJ$o1fXOmRoB8rrx?+R zmh0E9N)};a*(pYHVo*v_$6En$B_fVG$;eKauU%C@4e^pwjQqr41Qo zVq#;yq6w%G#h!}!DvUU?Sd_@sQqQN@GB%X{D9&drBmZyd##iGHC zOFkJJ_O|LWn{vGQ%H@Z7WZGFK9&|t9(c{0ovT8LHTAFck$?^Dw>8Rt)*RE!;q?1Q_I${Q-Gi1wLTuSek#x+8o`)Eu;e$FYXjzW?R(HFg5#0_{589d9d920 zdP4xY5w!lHJkA^`@s~m4FTq8i6HMB1^VKUAFi?+ForKYysowR-$!D%T zdI~tWo^UNN7{^st(rZq{ToV#+GpmlZUcGYpQr*&PIO8gu@dlW`)tgTZss$7;B~KtG z12N5rX+1TV85Y2p5R-)%3u3ragBoD~1JT-(Od4YBfDw0{VpgAEvX2kugi~*CxqeO1 z&4*Q=BOCYmTtR_Gdvdoe0sF!N3~df?WA4CjEFs>8xGBU=B@jcsC&AhOU5+$5&++sN zWIVkLk`7Wcq<0~G1?ed&o_-$EuOR)A;xD1R1pAolXg^ByZAy641-M_Jej)h~oicBS zYulZ->Btn}p~XvCw4KaHcX8)WH>|}Ft+~hNb2;@UGv^@k(Y^f6qv?D&74}j-&f_KW z>7&olAJ8hAlnE;&ZBPX-CYr$N@ei4}*Mz1I&7H7^;%OVpOa2LAT(6IFdg1IBuOG?| zw4wJ*$|~BhE_&3lg+e~Uiw-mW`4F8+3pY#A5B~u8)1z73sBgnF(10I~y(_bSrwOf# z#pJZWWh*PPLRHQE8a-d!{ozF!XbnQk=%9zZbNU=bU!+2QH*WrF%f0)c82RNv0aXhJ ze5=4g> zB1;}->+;t}3?|L56%~kATXMCMInsmwiA+y-B9lKJt5k{BvQ9Kz=kLh6OPo6gv!1 z^7axJCq9BV21w%UQxqjT6DK;&(C?dX-k>$N1Y!6UhF{^DTS9SvxKwaj_Wn~Mne4qd z(YuWJ9rItml54(}L`uJ2bMr=&^rq&Pl)z;wjY(ZNVEaQ$Q1 zBIkERCFqji6|PEfkFt2yQ=V&o2zrZS6Q-xw0YdojbA)jD02%Og01U$cd=m_ofhoh0 zj;E{Xc)Eu!;DaRt1~uwNRd>kNRcWYq$w&@0=Iw7f*B%}V&IyS04@&W zX}4eo{2(SAGk8QU5~T`M^I{1Il|*)yk@DA2VDrbwQo{q)6i`aOBvqUg5eMDf?ZA+nlI6eYcrv$i$;d%w@)^h*@l?{VSK%rYCC?;r_ zoo{`s6|2Ba9LIhPHlYzFS5ZeP>AqJ5YC>`yjC0pU0G2=sJcA%*sF}oQJT)6n&%_IE z#fROD7v6{u{~BUW;nZO=1r90CQH#nPGBwda;3!BdsTks;O+Xtu30i3pLC->cL+E}| zQEo#*VMcq@>KKw);?zrgAD!u@BjOr@BQA0F;+6Xdk`5?ef-8WU_P zIjIw0V$*=mrzhCDQrf(6sf&IVI3Jt`(RDzg(YSf;^6_4{D6l^{BeTHH@#e(z$1bMc zC8l(H;o#h~f;ss`1e}<<`BC);1fHK#G!2>4e0w@Il-EAnJ_boFZ|TAGbKB3DifYIVsf{kTBJ=c7+a8*mu@H7KMm>svp3R46b?#Z zi8!b91DzioOX+FZnfa-CDFx}24xLKBIJBHBlb)Z+@u7+K4sn^W26w*rl%f<|Pv3w) z8=U`O$3lF&j{DP!K^t!S%jtB+ryNF}jeGRW=x0l8b9u;|m|%-UGT$XJ^UT&&8Vkk<24M)&9*-BZ)j(_=DH)2RG^&9{9*%+6F+zyv+I%bpDbfO zkR8Q`YBr>h^Neza<-@Oe5oit!97m{gd-ctv9Qi5f>6y7xxj3u;|58B ze%f=DuEE9xW#k=>BW1>9r4>GeZ*dWU{rcJWr^&5@QVQ)odq73FsfE#p^eRg(?#)>VrjdusA-L`p>iIKWBoIFH5iu zBi?8#!a`O7StUfpuD#s)ajMuEDX9gSX&h&Zz=f+{m=20&ILaD9wX(xD@f0aAqbM_* z>iOz~@gGwcm{wpNK=> z4bRG*v}4OD>LOU4qB+^ryA3BnL8;%LBtdELw|z`OUOqaCOnV_0M}UVrE_#}*g^ndJ z%RY|f8zQMaDxrZeAu}C=kqwMYjpak0EG0EEb2E!D_&`JS!n`FhtC)!%0hIjLR%Yn}KZOsXL1! zo2IVG{GDu?TnH+8LUZt~dE|-Y0yZFm3yFuXyncbKR+Kk7b-F#RC{FXFAhza-bYjEA zD^&H;@^aHsU^DRsY5UXCCAFk2Ie1V?TGrHpycxOaae3K!1)O+W{6pk<(VG|jc`=X| zgLyHG7u)kx+bdDq`%-Q1l3;5A0I`ltmiW|!rR0IMlQ z{M8s?9q(Jo`xo=7YCfb@2q$de)dRd*`u&^N-&Uz3wdZ+%PJe?BQrmg^UW%qXz?oVt`p0mbmD8BX4bw@`{o@^zk9>Y zTjj0Zs=axKd&RK7N>?8x@;!ul@$H+tmxOTYrCOaBBXAn4z-#xKggwc%!fln0RS@-i zO{zWq4!XYOYZcVSy;Ew17S&ck6Ri}PfC;13X+0+OWi9P`f_^v|00M`9I@PZGKjitx4B`!%J!TdrWM;L5>EE=5k#7YW=>JLY4I{&zEVff*>x1 zBSdx2dv#t0-kle#ymmLMvu0VYkARq!&#-fdB3(!}01Q8upt z;(}9x_M5!-CDq@nGs{C3+~WF(R$g|@VmRfEK;{^WrYs{w=J=#o@HvaYiVGUU8F5ha zhErez#VJ(pOUWtk^6-j}2wHrCtqoGi=0fd9M(RnZ!$40XO?_bMg14qWNv4iT2Ve>> z+WAKFH(30`2B%I@abs~=YC{HY3c)7LKGu_53E1H|DO2q<*CibySF`oO6!KQU$TVv8 z{J|6vM{Y4x+h(*5WVLK;-9^7OZP zbr0<{b*y%FTTYKzi*XfSj6#tarH>UgXqSoKk#s}6ChWJ0f~wjoikf{-tF-zmYrWcg zmwtzSdvN{r;Cokt>#j_`ab2yeT%h5EJ$i?(F6Wk^{0aST-MyTfzU9NMcf9;|>F)Ku z6|hrxGxoY^h^j@r=pS_>aF;&!M$2-G^{&=T^T&`~NjI+F47J|%X+xJ5y1W&xyE!Ie z_mUBOd)=)u9UMz0a~+9)m-U7&a+f~)M(4FHC<9$TK-boCmG65pqP{CZ+>VwJ^)c+S zhb#X>gZZ9&*@dn)>PGKfESEm&df#3*dQ~$}N`DyXPzdP*d)?^2PoH-$Zf&SOuP%OV zP?%xxuGAa%hOW^|XLZBNNH|%Cuy*L9u8rJ5xwq@1etV>`wMJLy?!}10SY2I=D2}t% z`#!o$zg@qLEc*L3m{T>u*EPA7W`^V!41RJuxQSjOBaCLZ!&c=#4idEld19-z91twV zL8@?92Y#dK3GSofi5zw6Te!uXc7V!g86s*iYc>{hylOpq_SI=T{<`{jnlF@4Q*JGy zJGq8Nz#mGmrIHGD;th=Uw`sgTOA~`~tM)Wa4CLu1o2LxUw&&P$i#YLJUi^S{>L<<= z9;K%B2(8`G)MlQmwNld_gJ~=^wem{SLTa(;d1*5!E6oDh)c1Ogt)+% zbLm{RUQExN$!RiG^4#M^dHO*u^=8@!uhFcGx;xhEK_Qm)uQuB6NSD;S^f|0a?_4sP z7*jA&#gt}RW;T|b066i!%0Nv$FCznUsbOqZLFu(ix0MrrfjuX0rd{Gk*GV0y6+>0k zTrHKTXX|x`sPiby&(6dw3iC@O3hLrN$hHOcnRZ%Kvw@8;!=Dd-ljcK31u40O*_gNE zoHG3MPEZi_?Kzq0=`>9x-uitf`;cDJRIJdBxsjcCmmlrL)(mN|A5Ex;x9Xw8ix@79 zFfcoFDtc0mdOMJQ&HQm66(h%C8vVtzREG=Y&o zX_hMq@mX~JAsSwCG9fo16JJvL_t~M3Q@S)^W&)T}r7LgUBpg}>NYiNWL-K*IF9cB~ zpmgH44MG*6K_rtOX|(eWT=9#Z=Y@^DPX&*z)Tf%q6b=7XTX@w<-Yb+A$3C}t6Q{3G zX{rV7NkLU9cn5gvL$CslKr&`^Ht$y2jl6E6Uxe4g+6v`*GLqNv=n;#sv?dtz*=$)& z=PP-2k>O$OB1{ayc8J#&W1i?2p;PH1Oe%D*ehGfDx@RzvgfKs|s0%0-)bZM70^rsQ zssY}ufaW(q_po7%&mzG))T>XJ$xy@l8ND9%nrASpy?n2zb+fdmr7@r}KiInX#S^RF zY~41DCxT&&ewhF@%IHruukNcYbXVXx%OhKPpIvYCRfLK7rvx zKpOof%b-8+?8Zi92DuC7egqB6`o2wXm9Zs@06AlLhL zK=AFWd%|=fNYsaJGZ`qumCJ2*Sg-k7wpj(g@R+N95%Ptx<5rV@?3E5NSB;(Iq!tgv zTy0^MFHBJ}S54h?^)c51x=WCO4+1ynp>4pt*2{XWWuNVC>E?bc{bBUKw}J=K-;g+~ z&}#73DOZG{F*iep>+Y0wvDUX9X})Y~W4@Slx&GoK=F8U0ZAXLS@>n2S#%%+IY5eGZ z>&>tU+jRGa3XhvF)7K>PrR1wOFJF4XRphO@JL7v_Ns7Hah01ISkGa_{*%T3Tvwezw zm$kk_TFwo7`4iTg9W%D+Zba2jBix&pr*E@f4au$``e6RatQ!Tvzh6Q2asxmmu7xm@~r2gx@ zF09AdZ$pJQVlTYb`{JwSi<7TjQN5|Fjk($NE$hvwcNoOGe*>|(*M$!xh!0~gyx;rc zCJADTQk2i2`Q6ydpGqa#CY5MwpUa;}CHhz@(RSEt=PqsuZcuT%INh!H&<0$qC!Dem z7N7FqTZw`9c`3W3G;Cp;EvY%liz#fw7hj)d1XZ zqJyrBiv>#wr&Z%RTy4R^Yk)S)e~>opM$iDxA2+BtoD+^(3oIO3&`W^D@!BwB3ogt! zXc%_`?exAi0d=u!Xb`W!7#@R?@sSBOn&MGkJ1TkKdFtg0v9`8Pl+uFMM?R~hVNyY> z`5V%@Kzy7Rd-2p4PaIjao-l4);e`^uLyh1U@1;Z6>H3OgSL$dj7Gq|gP<0n=3HoQj zudiErDAJ=T{e7Vw=W~PCRPe@)kwC@FUjCT4Y`Sk&zgB1Bv+KFF!K6TjA5G=Oa?KgSt3l{DLi+6)nwUg`bsoF0UT$3`lRrSkqt*Awfm^6^p zibze_w2pE?KH?(bP&muS!l~mMzKcOumMoZ#z%evdzDy1)gTsEs1s{=M^CdoZ-vc*C zo~(#YJDXdTNbI=*rxkU{Jyz@(0;GARurp}?$(Ox zs^og1?yi@|@(*;C{~F5PVJItqfTk>sY5{$4s~a=@CS|1=eLEbA?7ut#QStn%0 zB7+5o^l6F7#68p<>pfoBZxZ&I#640Y3s&DjJJYC?y8+Z2(Z_Y?`%@I}+At2w9^S4RRig|(9rQUDw>yfNu+MT zIL=h(<-f6||1pFAx+wpv>Heof{8t+MpQ+;w4`~lA6ud%B6MblAZ#+~6lm^!=;`oqy zo!DMr#p5~LTM`7^9wIlY_CBrBbgFErJX2A7(L)q(nlI`0n6$f1-W4XFT_)X5lYWQE zu-)Xl&E&V$WW*H2-+al`0;AgPTY5wHI~uzh;H7h0 zsfL4V>WmcJLnmIO$E-iOg{rocKGmL##RqQtlmpvnEMIu`4+76lnZxcllfduK?#`f^ zRG6KK$HkJsk3*v;kR9-davUv!Xf--_-_L4tcB*poJ2{hXdeIvA%Uug*(WATjY2A=U z^v60Jc;6E}hUifGOq4C1>S^lKWLg)}698$cdgfn`29k;A=H=yNSIUo%U5||2zlnDL zao+7w@}fkJTaQP{cw~g8Jz$lsClf98ZdAO2`c?V#OySYQgmHE}`kpoh&mwXPnNKE_ z?HNiYnw6QGo;OP}(cH7Yuv@&iMb92tkSQmP8*z)7k^xBL4!XV$Bx>1FL0HLqkMR!G zMfknM>)#i+>C}UgnWaw{dW0|@cUbFaol%FIO}cxT_v(14*Vzj{2! zRXvLS%^>pB?~x(tqrvQpJSNYIj5K1v2~1hVm7SM&_XvU`a8}h z<31SGCC)|<=EY!_P~kC-OPWZ-HQkK3oRAwxndVMI#?VBgxy1#?85+2sa8<*89dkNP zkx*OA`?DAM)Q)FNq{#SHHXAPafCTKdU@S&`8}*%*#51iZhdn5}ylvu0x=tx7DiG(0 zYk14ZjIP97^1UEr>o{F#pjj8{XZA7~y3_rrr3NV{eesL|36Vr|ytS_aeT9jGD9?z|!;l)+z4QJ$1x9TgG zdV=pEp39F$hGt8RQI!h5H?>w!p-<*CRaEdwjIVdJ7+>$z-Tm8=m!Q=B;qle~b-7zW z4b0<4PGya3@kv4CB8zcXSeFo}@~IZ36Mc=y&9pR9n2jLD_JU5t<9IBUXfO$^*Kqqx zLRD(rU5;9XP`yCK2~|l@S0&NZMAHyWTfIQTOEeTkQIu*XIf#xGX&kC}3%Me{_?8jp&n%c@cTH0koH6e=~lmLnMk^w7`HfQmDnC=(BtyP%@0y8JP?+0QCrbO+@yY#N;qytT3TZy z^UCy}_7-=e#zdMKQ(sHGwSe?{5QrF@7Cu{C8~`Gf%TkE!_QJx98QIx$U|Etl{i&x; zxzK6&#WTYVjXWO!&!;6ll$=TSA`#?qGUi}rOqgIq`)38CrjD7#pKr@MIw|?ed(S5 zNk6JD_e&waQp@RjIPx?#gv=c5lDgmWgGTIP{v)kdiyy&E@K@=Q{3#?#S;qop)QJ;= zS*rz%=NeZkW^Ex3?RaA{ zH?B^t`t!43^4|0IE>qWtU#nFkY>h>TwWXt4Q^5-rL{Ckt-V)u#RZxPZg8s$| zih0{(JI79-F4(=GOkortD z_m>Q&ikXNUm{>~<6-aG9jCh%l(oldVNc~5A@C6~6Q0x-Xpf07No#?*$9P6i8kztcy zuO@CZqT@(Cu<*U1l>7F~@1LfU1*_#AI7CzO9?49g#~aXLQr!<*sa`!ZGYn`oguo@_)oeCX%Sgq(_bZWE*- z3p!3Eq*tb;CQC@YB&6EFb4EgPZ44#!py8&^DJ>0~Vd$Al6SU=jwVFa`+9k8?(;lN~ z0hArG#!^a&uP@DKiA>R$Zpq6DD$WH3_>8P`@l-!jQqh&+yD@xZ$+W+&q;I3eZsFMF zqsDQ0_?OFBxOlc3F_W8$D-sFD1D#Fxi1GBQ3VU;mE2woJ>9_U)YB&Q)q-9)QUaln` z?T0-$A}?$mQSl_%j@BuS)iIW&`uP59Ci!w$L6@m7i%&{+E8qEiG!^VId1XT=n#-kS ztUl%CWO%L?gPdzq2`YE?hNY+$yz%AKuA^%nrlg&-UtGdWHb3!23)Qi4tfP~<)8mX|-LK-%;Lv#n8TO9|gMF=xnkoj;EP)4P_K=$IHw8us%RQb{dwd0o3$5{lBh zS_WokTUh==OM$)6PW$7!a;4%bs+}uDf03r6uG>0?eiDR5cw7{(w%}=wsIg$;PHmD7 z>GL#dzG zO2`;`XJq1=*2!X+D7F`=FZeQOVF7hoX|%@+LkRZ#!II zqvvdkaq(O{DbpeQTQ-TJ4k+86oR<%Z?Nj^#D9Bw;c8#Hd7CA@HC}RC9XQP`u5^(Rb z8SLpYZB)Ug@Y%L9g*0USoGm01voLJ1>&Dq)a3O(?I(T;`EpVi9RO-8J&q+BUbJA=- zHrJKNxDy5nTT&u7ET8)R9Zh{C*FXiqb8N&ERV-@Q!@ z0VWw+5Jz1f@-Aa!F-2t?qA)Hm`d|eKk;*|mKNuGxfxNXmVD@%04-Fw?b=$|d&>NY% zsy6QI(O|k5&enr6LG1$x9Ze0I!7_y)27$PUIqSryHZ~eciW3=%2grH9ACprrs|`;| zQzIK@$Fe7XJ^!dp(g%BTa!^GwBv`0gyK=v)_OL?OiU|^g`t)@#P&Fq5l6P&?K%|PH zdF!u^1P3Q!^Ah0DMRQA-mf^p@QrX~rNf#USr{uW~A54FeJV(8Z)cde|Hk|EkVn8?A z!8-LC6-lnIG`=S*yJ_DC0rFv~p4|?xu!8&o)M=M`p&;uyRzs3H{p$^K>5P1AU$WCK zhJw6oYBZ@xT+SdCejD>A87!G(3{Gcz4QvTWAU&UZ_D(YCiAo^VU#V$uHVl!>HcJ-h zE@~tOW@~9F4$T&(^j#*mDA$O@*;Z065KG^k{{YD(wWqU+2bDS?0W(Y+wF6=*H+(u; zsl3Dm+uOQ;h!%~WYV#Mb`;a5jie`6XccWkt7;fjOPLH=ha()UXMMH3lIj$k%!nP5F z;&|~-(KcSeIWZ@hZGQ%PzK!IO?C%!{Z0|41jp16V{k9UBc;kOK|%o!v3yY_EbW^Ix zmV^_s)u^2-*iCp04y;GEZ6N~8|JdPI3rQZ`wj3wlSG5g5f@H6xSL23I^@i3*9S18qi0|{D#^A--rJ;Y&{dLXvmuXUyzes}{ew!X*p`Nj&PbrjcBZ^8 zle!Zcd)aDeVBwfiL%}deX839K@EWQKRLNl^=?*upZDa69s%kf9miv-`zM+4E zeL6d5{Yb`lb&9la;!^nes#O^yJvZ1dafL zv@AA80X&_E0H1y#_8(*p_hEq+r?&TyDD@T#U;AJPX-+;G24mzE%;9X4k)YSILT`T4 zcMcVrRYrW8P-=_kVM`}9c zHNdt&f@I6X4=g*QG*{|eK(R$oUVt7sI;ahepr|dZC$;4h0YyFXWzgp{=OO(2)BbO`cHQ+4%Hg=h^z;bJ5v%}sevNHA z4`+=@NTGUALyluXOj~JD!Van%-=ALhFspF5GsG$!1wD#iXygYsBQ9}V&)+d=*vq2p zT8u8ZM!7Rkzr?;&h35BJ7{a=%X?e5g)?vIWc(c(R$Q|_92X`QQT6-p_ke}{A(%mwu zO69MnRZwF}w`Zix$Sz`GqH(0!!WX!Fs^d~bFtV3%C1DUhL)sIqo6vhnZb5U zzOiHC2uebqDcwwhrX=NN{UL8k#{|rtOHc18UBAL6!98N5Qqdqf|4U;}TKDH8mWA#7 z6nn7d*Y(|#lpWm(dep8&<-|w4NX=h|57|c+luq~5r+=7?9Hyg{h_vl>S4a9E2ZH?CQ)T`JF z3Z#7^p)jdEal;xdS!wN9`&haI*zNK5FVd((W+YWGH+RtaVF&S z^UOlrC~he3rqzzRIfeXjYe8?e3nhBwbT(8}@9@IrA?2*NFM~)1ckZn8=!|aqH1ZsK&Kb&C=fMxvZcm$0u?exEzp_Wd{r!ml`9wJCjeEjgNi#_T(yP4HmlH;oMwrl8fsd_$$eGi(lbbnb3O~3$b z=wUmzTx)Tv^12!xJAGiA`$9@JU#M9WDQ?|LF`m?u9l+5Yogb?quB|y`Z+YpsE*mIj zWtr&Lqiu<33NV-gUlLn&k9tBEso&y_{rxPS^L@TWsIW-%AJcMlXc+7e_rJ zhdJ)UdhC?t`H;K8f7J#Hccuxo!hSqh3U<~8It8&3(MG9vZgbTzoVZEcW4)~sDxX%X z^o~HMO02N{u2JuXCB;o%yG+`h$v1z$aowx?EpP9nJGXD?_5|18na1A{w$#_FxsULt za+xcM(3j;`-4Wii-t%io zJ0cd~$!taNz1zX!&B=FetAZOiDxbOIzZc=4wIO%F7hHdPns{44)m_b7)?~CoVbKTS zsaCYEnA2$27Yq9Jw3!ZRCG0U|Yk0*lR^U1kCo29cZA)#}FtOmf2?$uC{wr3xZ9$_D zc|!A`n%d*5k9^dN8dg5yhovq9!<#E!TYS>heJw)W=<*06qtJb!v#S5?DK*U-+NI*& zdoqIOXsL#4YAXdFZW%}dreTW#@s3D((8zdcC$&2-q=)PON+OrGqnU*aLwP1WS@t72 zxSea(;YRCUbb?Lb+tC`(m5F3yhN{uMy^v<8?cC=auDqbvj>`+6s?Bik&kwn0UOeyx z(-pIWM%bX$u6gyUpBuKT)THmnOY=jRzG>18O_y3k%S{`R!)Umdi#rp^agw#NN#CEs ztP7Yvba$4BGAOep@&?W1CQ35%a`N+X@w6|wXih#JZq$i0R0PmJ>ijn*D23%#8gA?j zOe@NqDbLJVZ!P$@ZbR;s!Xa^=59j3e%d2}ksqey+QJPhw7uSr{Y4)l$3k1>1?un)T zP%(k>Dk~+Ni$Czi-Ld~7b);$8(2b~{X%K9y$>w6@xQ?kB33(%yOWN(iS+xrvcv5Z!RY(D25a=U4<}YBn~cfU)Q> z28uEqopI2OkPbKw0OC0u2cXh(I1Zu~ZUI0v4%#sqjUyGctuwCc;gMV%794b=Jp#2x zGn~aTR6P?IL4_KMpwE1Klev0&71xp}it;Gz3)@C0y$haS(8LiC96};&2Pl{=Mf#?O z@O|m7Hvx@I`BJoM!QS_Nrk0Uy&nSu$duhdfTE5FF1!FZsSi}C7#JwE%cQmoJX4ZQE zp9?2;!Lbb5^~d20d(4L|dg4feEhwI107_$o++y&@;;_LEW*l_?Yy_@0B<{cp?ADBtA7`%J})m@6b;PsEENY&-n zhs77MHSzJ3?QNdDGIsmshv|I7d){wc6VE?Po|?Jjud(p4oL!D%0Z;i_`dniC0h@=O zn;UTLVX>bV^@6BhiRhLVH&P7z>8v>rUM#iELyJHC2q%W7I_ zCsB-DZ?-FHa@6Em5A>m~uxF1hl<)GtYTqL`DT(@9gmg;tpA!-1tyGg(e=ALY*b3sO zKfijDdLCs0gJ)c6xE5c~;Oz?=Qo?ag6%Fl+{KtCQrvTJPzM0fJBilZ^55l9T z=2|h$%PH(_k-~raAT+5UBRg+aZwxx=_S`<<{e3L>xGUhourQG6GYX6FwhV}8#!!G{ z1uWg7qucjk@z%Yia~V-VMfiGQVZU%GZpey)hFdbz`-RI=hUZM}7H(k=75jxpyX9s1 z@s!t6Sdi9_e98y%iAH~Zb?@`M8l!Z$%U(Gtv#pYRrV3rky0PomM;}(w#yZU^ zNXZYkAg(etjlUW zd%(G})#$v5mQgTNgW`P~F6C3{i>lLFEw5i~ql$-`mO|h3!Sf+JMfHh(u|2hcajE&^ zaC={TMN2cD9+>J|h(~@?51O{TOScWB);RRo3yz)neSAtbs=2Aun5Z2~pFn3jem|zl zTy<*wJxo7|BI37wII-3sIRzN^?R zRM!@3)=AWVnM>`F^#mjEh8g#T9(84!wI|ue3C56;%GNY-X#bSCp!}0hXpmw)!8f7; z`RK&yqsG@RrCLSKd&=Toe=RN_rOYVG>tn$)I`L~S@s3tD5#5I84_@*Y77b}R-Z0LG zHZ^3Di7=kXpD@HmJ7_+m#I==reoM_LZ+haid0_u zXc9CGSl+Ja+RFR@NTsx8xfmxup*hgazhWE5b;7j;=lyWhrAT&t1Ks7tV9>vj=(MZ0 zHIBL(iO!$%8fpB&>wV0h_+Og26>wgkSt)ufgFC&`b2Px*Tpo=2Fc?8p1qD|y2rPwdsIK<3E6&_FIYIbfNf zB3~NU^No_B8>>tAfv)2X`5shoRi*p5%XmY!2ks4xGzk1>4Zo2uIm+w01V0cw{(;kC z;CkNw->RUi8Uq)AHvrt1yvVB|Wh3wZj5=tYm%$Rqwb;Vz7qkEAs(Gr}*g=KRSJh%( zRm1mg`xvJ=h9+V@$h#FQP%$lQB6NA5X&aTRft{L&2kyq@3LvuvqlrjWso~6sO1~YI zz9A(TO+?}%8a0sy8l_MZaZ5ozz*4Bu$o2HB(3&dcMEcv78jYNO-aVZfjhwz-#rVf7 zxu{;Vi@x}add>cxl#(Q_$!>&W?gR+EztJ$H{|xvIDv_*UvyG;~Rjko?@KvKm)4d&LxV}nXn%c2 zm3bq2ucj@?+@uAWPx|^t1O^002bzs5dH+Z{HJF2ADbfGoz7p;C<06#FDeeN*4`C1vj1Q>c10%a}?K=jy zQf(0p0j&f4O~EGBdA>boKF=G>9oGx3Rju{CN_gu<^#hB%x}Ws6svay6g6rN>2fy^W zI;gr73oPRcZRyQt7R(N$&mv9;`40E~jO|5ie;3ThO}zP7>hWWo`MCACAec`~IdNPi zQ$$A5Fp8E@d}NA>QS^-B%P2;fqGps9j1s^oK{7?dC}u_pVU$pr;>9R!7{$UU;W9BTTN`FQfAX5yC zGKf*)8D)q}@nw`@MDZJ8K0)`I&BqX(u@8(yQm$_sz?w((lzH(q8Y-@77o7 z>Yv*6@r|jOlrtd9e4>TCAKHq#e^_#=Ry=rEdYjihrYZjLGmk_L{z=52n7O|YqxpK zB7Gf0c(qr}n#A=L*1F)=%{5^mZGls-WDVr@HjVP8hoe8t$l2Ahyo^9|Vg zf~}@B)}8I!sG1{;?HJg^teO+$4_Z%IzBAXD!l;<14xhqlcvj87N;@}BKaKQuSv6GUMTI-81bTnc$pBb?ub{SYOXTGYd}wmJn;pUFi{0neHOJeOArA(yop9>!tKR%6rS{GXI|vzOyu{A)QOA zah`G>cH)jF48dj9I0e2mx&hBc$IW3FkmzcDZFp03Su< zFJBtd6yHzD@9$B53nhO5Umhms?_fUc(mxP%J*nT4FYVso6>c!knRza%#u*F-&RXMa z%eBIpK|{Q)`4y2(RcS+_m?Sd@_@in;O; ze(fVoi!zGn%#~yKwQh}>s?As?D^Zr!i5rhIe-iZ=zoM6?k%yb|4I0`+-uw-hTI2l2 zISCo$5s1c@!`zg^Xo{10oTUm7+@ADdW#e-Vv05lU6P_R z6};IBo+m};D!B6)?hGh8pLcN9d(L3)AIR!9rg7Gz}nH$n_H>482dDr4)1G2?du@bV$g_~;;EGN5m zEh||mS*(^U_D_$leMP~0O~LbIvDX#cHyQ3aSnMr+Z}YBwM*)790pIW1O$y)#4DfAI z^bRTdp-0z#q~Lv`;CWK?GX-}W!`%W!ckq>e{%H%yH*_etn?%Fs71+`K5Yv zCZR|1WWVNJ-M8=Hv|Oz_@-Ee}xg1c=HMF=|SAr zd?)iU-S1rYW6n4L>Tc3mJZ7m}(h0~q<~+jhV3V;Elu+jsxnsOX_&UI!P~cB8_`%%w z?(qLo;D1oyf8@7&gu4s4a|+zg3~mVb3xm5QY5OaWo`q?9QGvV6Z)4MVR!S3hmGVuY zf?VUba`SHR=F8l?-+A*TZr)8E?>VBmy92ZL3jJ>G<15Zv{2f_j0CyKzmHE4baSzo) zp2O2v8_4~eUzJQ}LEN7pvAO((;0vc+b9pQ$4s&TW&I@S6PTu^l=Gz|Dd2-uR39fFH zvtDqTIjjg-*LuOJr8CyG`Uupu^5BHKP_6Ys`2-1@v!0gnu&Q#}>8s!x6_~FOWB4s5iy+cj!<|Lh}WBVN0Eppn26EB+UUlbjbYiwD%r+SHi5D| zO4%l}Y>zd`Hi2cEsAQW&*;wHxQ?@BA+Z3U6Vq()so=oClbDAuH)+i$yi_K6;QUi49qY#9rq4PB=hbHQc}>B2T_|twh9j@R$j)zo z2bI0Xxj`sRZ0!EGnET%bgH-D_;!IkDz1OV!H<3tI**|Djtnf9wOhg2(`>ma6`ZPO_1UN3 z>=#NObCcz!&jAJJpinx&jmJ%&FBF`wn$_o!f^)c8_kRN(=KgPmwXZjJ{}JZ?qhOHS ze;jAhy6wAWi+7SlGWUNktaY!sZpHf-@i6!QAkg~FeM~yTWd112?8KeJ8C$>oEUb9E z`H99a%J_N#85m!aar=@owq9Xl>v?cWYq;Nnv2o@lgS=QYmD=t%;^W!OTBsYZ}U>$zr;S}I8RbrJ=s zl^arPv3XZploE!s64H8(nn=T(^0YI8u-Kfk11nuya7t^sNM<>@0j=bbr)ZrO+%5{P zCs%h>u(6vFmg@w|#fX2iuIr%y_hi7HTpg=`_GX~nNny6O>)WiW`zyEu6kJaV4^pt> z8FoJ?JVeC$u2}~SRU9;&^2)9r$<8HLKO%BV9>xxk(c<5)@5YD?>$^npf3&{4j1|*( z(Y3yNOr-UljhiUa`tFXjVtQPJbJ>dN3DLE_n7WsZBFXziq!Q?>YM+C)D2AP+lth8nADAm)Yot(%VLXm<_&h1#l0(XxhI^P#PZFa zwaMZ(6HYc+?gKhw{(8Szn`LpIDEuEo`_O0F%tspQ(ha&4#U>0F-^PW%#C zd?dDG<2r|~KSN!i5Dr}Dq86mmt%nNH{G*(f=Sp*cgX&&BS1Fp$aMm-7a;-ofx)cG}D9l=?Dbk63!0S|_kV_1_jq#qgmTRejH!Xs$HnZq3= zeV(4n9T)L5mYe$&c3HPKAJvv&Q+eC`tcCl%zk)T@V*M#|f18dG3<90KdyY=Pz)0|BQS~>o<|@0bIdeVbIy4*F;+<S5RRey3T{pG^ z%8h_5RlHPwBP+iwzw*DdK|A9DAzW=VdNUifaQmk7rU%*0Q@WH!wsv7ODw9-BiP~%$UmIBz5^QjkmzTFP_}^RVEj&hopV(-v(y;p|z zm(n)fbnGEsMCXzhu+K!t{ zfz0?$aRu4Z+ef4qHs>OGs`5jTBB&5S^BLR4Ta`QUIoc9Suk59u5{fVQ^|Rk{XkYUE z+phESle^KTYf>|Nwe)@m`B%o8{H7lE@h9CFdzLiDPT+3rb)K$Bza^stu8j0(?~OPg zgrFc0<>gYbo45F&H(`G;7&b62g$??N3AS=?z1ivHS$kfK0sL+FqgtaXnR5?gDOZb5X9Xw{0{ z5OVW3aXT9^WemuM&AOGwG@kfq(*f#x^72#CGK=P*U!wH1QC5wS%)ovJcX)1**bNcq zcx&P0TVE4*QQnZu+4zM^5%Dp_Y&T&)ei`b( zAS6>ApfZw8#cx%tC`XZ+A<1nN%VI{pe*Jihw?Y!XJ~ex-4xW zlLoq0hreB!& z!CS-|^v`+YM*yi_4f-^Ie*G4|&Xk=l{^CsthaEojBcdHCY;Ba}a0 zdUXJMxkVh*jDx~tZf2pLZG4wd#=*1G?5F~g*+M4WTTIRwh_I3gh&W=LKjpo2m}(2^ z3ONivd;AMvF5&nM$M4?Fn-E2uv^VJ+R(0fGaEcQRK2-F#kN)&LiMPw*MSqs;7`uB5 zDL`>8q<|R6k|zxvdXQAekW~qHDqtiaZ7pH5Lm3MRCk;kKq@G_!fGn5~cHgne~9BaVz4`XLv-1LJx|~ zHg>9Xa=oIVpK95EvwEOD$Ngb*1sijYoxLa_G#9gFUL&_ffKvph=(JGz>!68WIUWW8OIxN zVw(7NRfiZGo3$ct?82dymwf#1ELUlVU-$%oVh?ve5~JZLt&zv1&}B$SsyaAz6({ z6ui|qmy%s!mIAnLH$-h=m%J;M>$DWWRfFFhW&iODmk8ZD$*WGYzc@KB#ctiE*sVW| z9)qzCH50pa(sS(ALC>*U=NrdvHIBh@i{0AJt1ePBRzqA@L+lo4ryJ4e=jCcBWt~ci z(OSm>uS_*wO3HdETg2CHSY7&ZgEmz##ebG;dO<3$H0ISY(yw=%f;K>w1<{rr99-_6kV|iOdzpgv5Im>`7r#<38nR5X`~)hsc=_CR_5IT_#j)>8hk*$Np1F-fd$(84e{e(Y&vPNPb`Ms-w)?iqTHH6(DTOf5@R> z7up2wI*uPC{`B9C2J*jY;1U1F8h8mc@GbxUZ{R0oM>kl?t${CgdAo!9Q-j1tNyZDfj;Q7nuS z&L|Nw#hXz&FiIq&be1VTjM9Zsx-v?%Owln)45Rd5l%6t0&nU5s(wkBG$`k{m^kXXlnozAG&2hcrDKlPEnusL1j#OAHg6ErePuX2jY~G#f*u9XLLEmk?9=yn0 zF{R@AbLNWF-4%;_Ii5B<5c}*{g4kz@qtLdF|MZm}xh%Bg(Odk@teE%5v;O_&_h9R5wQ1ALTf}*bahC zo8|bjG0Im_jzf%m7-a7(M-5OAzrFukL|(fII}+tM%Gi#BO~`V52Q~y~?>|}kkUQI{ zD987V?KIfLEXNr{DIW;?(d;mVQ!x)zoWp6mEXU6XuXaN}ALaOkWxIfEx-7?~`?Fn^ zvRy6h;-2ldD91IH?FO=`vm7@Opxa2=Eh*a_#85X%uSI%9mZMG)(_Yf<9@0BxIsRR0 zZOs3Nl>S$FKRI3IcM?9A<#6&m!mcF|AaO|!=K*I0f~QFi(N#RctQ8epbi965&hJIB zuTAJ$CBF{^tTw^dDftZ)rrm_@tK>IQm~|6;eY|*m&ze zxO8bugJUShCj{}Z4kv=Av9;HM*p${dqb>XnXIp+nr>3e{NE9<=xJOmm5uKT`J%v#> zDQX9zcOd*ogmgE>@1)Ql@My{|iu|q=hTQ}|N|E18XP0;XNV2@Mt%+>{D5#UvhQsl;hRsHwyGIJjs>y8{8!2%~lzTnO)g z6T#Cm9s;FhyvE%;;qG!yQ*fs0^;Nl^q(%fJYjmiXk6M^dyv0W4yG zIix6p5*^N&OwopMNKzER(=r|crDZ%%iq2DT=QG?{P_&re%~|W5Arvv~K0tBST~hG$ zE;?(&A#@r&8Wr~dLZ_wHUk6jZz~kK;T-JW)QWi$tEE0mJWh8`7D@Y}7NTq!9u0`;) zT*5W1glwkZ=GwJ{!(97MRIA8JTS_Dtac&{sXo-FpJg8LT3eH9jahp+TB znUGxjt^$mJX%DXbKmq)a0d6Ek5z6mye&o@$2%eS|MWFQGxOSU@yMy6=3`KYGd;Z3? z2%eT5;-I{;Yb)8g12M@J&MJgaU&XEmyk^U3t~BpO7b$U0KMFijZ>%ruL+BE#Q7u;C^9n@!SOl zhk$R=`67@0h3R}*fxC)eYMPZZH3GS7ly3?Z=LQ8+BY+yg)VEkL^=%5KR$hao-$lXH zclf)q$RG}p(+yK{3FF_WB=WSJM%mY=hb+8xkP9;|>)FWibfj%9T8?ng_giIr3h;^`> z^$RHvt1w?U5kf8Fwoq_AwZs4gJBVTXA;!rpc%0TtEfFEqGB64$w;V+{Q}_#K8wJ$D zK!Zu)8>DbJQh@7ID)}J6nWM%PkTC z)N+gb0s+)??m#eg1)`|Y)gDC9G=iy}Dq+)LsC$q`=U4=;Ih?(ix-`-1gENXJb~yV{ zq;n(Q{)`tP)bdC^ka!VH?Qq6X?D0exc$|qCOHt8{G)rXJB%@O)9;Ivurgk_dP&NwlKIoi?z{SSd9%tDQLM=<1 zOxZ-rmc+u7zi@_eh?!=q53=MJuw)8dv*}5iP&$-++>|Gsc-RyrgM}v#YbQ+mqOQMQni_jNQmcg-mRbB6cQ;MO3uIIa^pU#B)jJ zC}N*t;hLVr&QrwBXJN4lkGzy2tNnv`*!+3{3+h&Q!lgP&JPQ?`MJ!HJ;c>%xR?+8q zp)|owXE&b3iatwN1h#_XCTppJ^J25*TCU)fG^@|c3eE}^rLD+v(`TiEvzkR|D?D!c ztWj{*Hmgs$g0qf=#VR=Rl8S8j3V2Z29nRMfq}{mIt!M6k9Sl;fdlP5UlIpEy-TyX; zWR-nmvueFdJgl;BYS#Vl6AyF$W)>K(sP1Nh4;7w|nl<;wiawtr>f5vXKU4JC#v-si z$=a^q>}P4noGytDf2Fjn1!8K!$M}K($J}+Cy%Wf;cwG0>o5vDke6-E@aywIp;ptne2 zwlw>r*%`xM3N9zQW+I*xMu3})twK1T!}%u^)`(EyGG+`2nQl5`@D^P&1|8*PbNDZu z26pa1RCI;YPh_tyIq3A4qoSKZLL{|3_Xu!93UWg-BmNA_E{~Q&^t3$JYRyVW%d`6} zhbU;dbYZM?A>fpjXzeJ-*yH4|y@K0O!S!UhP73w|3_Air##ZrfmT6H6a5o0*$<^Hz z&<7bPLa*he+CxlX|Ap7FhoVud-6w6RP^60*G5uQw7gt|-k>Lq8rGfnikIHRa&yMi~B;U#mKOhb7=IbHl;?7azeR8`hLdT!mCtCCbw z87dGUfiRkaFo+3*A~O;N5fuj@2?-DggCWSEXi%h4P??D{D(wK+qU`_#AtXh&wrHcR zHY(Z-0g@=loT0Vr{r0{?ZiT|Ozt`)1|F_n^)=H}GKKtx_?wR&*_Blth^$PV8&ZJ$@ z;fy`n6+K(0SH$$PMKRM;jlH?DX6((==`G-0(K%ufW$*`rx#B_f*Cc_x&;f6r=o1Lu zPrvd-Mj&_}?}`qO|A>yig!c&lj-Dr@7E>6%Lij5b`uuB>z^>;$l|k&Kt|Ts%!R6>_ zfgtu$2WXdcAowuvihf+@{RHpu4bK~Ur`58qV&2%x{HwgzF>mapR(Y2)Z|tR3d9T#D zV%IiyMTh5#UDYbrXP7H?RsSm27nm#dQmb5FVy@Urt#W-Dzf@k_b-6Y8*HnQW)iVRZ zS9n)+xYA6;zG=`Ypqj%zYqh;zy<(kBrCro2{u?O$dVw9(YOXC;eCMeGd!5x>?{F^c zt)|lV4FuojUD4QE9iUy(fgtu)9~FzHi}$gUIN&-ZdIo}pV!NCe_=oCEQsT(GkN+>QUo6%2mD}$$l*nJJ?Eb*ZXO%lImi@&J9?h*UdUq#|mS-jm4JRpCL z^3fy{FTMazef?2!c6@~?HpJO+P+qq~adv!zBwmaBKJoj{HD?E{MUu1Qd&tE@vhOpz z_-DcIWmuNmb^r5cWKI7=CTsdIXedh7QJio=cIbg%mAvM6alx)M^TW~ndGNUG`}~D%*n&Jc``1;Lf1SOdJ;p zI0#gV5pd3b9P~6JX%aC^GD;^I8%i=>C+TC74~P~zN&RJZU?cncV!{r*%=^JsOiF#t z?cNgKdt$8#o_tHq*YI93levFZ}a>ft6r9fO*CH?OSzG{Yd zknz4&@ivNY8Sh2$T{FCYFy3Jm?~H#&Q|vjT69B=8bB`TrFyhn1*@d2Torr zP8rh;^Ttm^iRX>8jJZLaGh!O%jSHOql(?kVE=279U<0~hxM`wZ@1p+4q&6+cfGbklY6+3rc04Ll+i9UL_JDBfB=zYsM8rlhsc_Rmw1q@$ zq|VA4$|_E0mB6ePi^OoO4AVkVC@aIX(3)Ab)LA8mvTCcd!fmX0t0kgCI97&fp_5Lk zODL%{om5vQwN#{ALdR+k3r!0>Ey}d8NnFi%o5VF1O%!KZR=lH}A=}pystfkB&=GZ^ zy5O}I634IL>eb7ph3lEsK$X>8F_>A+6+@d9X*lDJQ1Mw;4&Tqvq%&^Fm2Z z)=5rfk`IV!I?3=Ibq13P*HLG2?vsx_Xg=1yts0%|`89M6y%xZzo z>VZ&JB|58znAH-oBpfS4M_sCuS{6#`F`d*3Cbd*Np?AQQmKA?g4kli0S@VWbF0HZP zxV&Z^a2KdLcJ) z&d0?o&AQ}k%;9ytOJYkp2Jmy@jb?anGTt^7Z?kxt@wSL}n&G|2c<-xtTg48>TQ16) z;eEh(yH&hbMLFZm6BYWKVJLhW+=uE9HxvSTv-~-@IW(? z+EDnbPV)0ml3(Z~zhaUPh=V%G@I&ESCKYZde8>Is8=ciZLRlTrSsi0mi^cJ9tPDe; zI+T@RDAY2mlRB#(Ls^~HS)E~4OT@WwtPDfpf==pED5-j#R3npGDt^|7!oMsl-d0Y{ z7S=Uy8HWN6cfzrpp>wfprMdVI!eYH_F2=bEnD%W}W!j%B99Bg|b6NE^DDxUrG%UEG zBQFzqRe zXae22EnLMR)#@vGqbu$XLU;`t6Ozew==6H;*M~v41?e%717!JGNzJo(!zSZ{)+;86m7h2)Z_()%G{D$0b_n{B(4&G10 zm7|6qu=>6x_uJRO601?=`Zl;2x9!m|-zgrpYVNm3c$k-o-zmeKqh!ilOn4a&rbqQ* zzMLy_u6P`S&WQIUQjx`@@*Q1{zf4B~I$ep?R=&HeT*9A~9WZ-IDPZ)Xg#FM`jZ zS!084dCM-%{q};D+;7k7&HZ9%bHA)N_wSkFBJrwDk%rh8!OhJ`n#5Kn`MOT>51}Nt z=_KD~k`IV?bdvhZ=tJyVxZmDqQtE5!?zeB@e%rzQ{ym*lStzRybXI$q)nc(X94mE* z5vxEbD|LtwD;)NQ(Ojvs`Y@E$UvyUcnbi{UX*gEu5F=IxbW)#(lKMg?^%av^Dh}#H z>>KNf56S)Zb?^{%*FTf{?d#xo*2~>*-*e~tKpdvdr`N0QexpuugvmcDju|_VDqAT1 zIHxZcCog{!UABA+_gjr#;2${0Hs*}#jAIhqm&Z;}J zS|WOeV`UgiSL>v%2_==Olj_T)mWuwi&>8kR8!21^Y?}LRAmcqP2HP~THq^G_Pvn04 zIyl^BbiaKY9ATrgbVDcokv7upc8eQrtlJqU^P8B}D3#S*aWk`;D{iqF#;0NY-OB09 z#qGv))%`|2_YO{fPTX~Q?Szca@p>gp~{3u1ONyduV%qvCB8a~bbNF|Qfk0>)dY;=Ls9XS|oi1IBV0=8Y0y zD)YuejJaAoY{WFo8;@}MTJbw$x?$dUlqm7Mv79kCh!sXm!@ThXr#~fD>a`CM`(q5Ke2EBEjVv38zGde{Qv0nzCZ$?rRvCruwH-?gYNhi69Nj@N6(MjrE zEkwk=&ZNRk3tM=szNWK!Bb3#fI;*#s)nf6-aI6f|!h4~t4Aa7PX7#SlYG){`T{^4X z%xZ}!568+dEmY{F0->b-q?5vle3<>0ioZ1Lf%~}!exmlkP2#VNw@G}aPYZvut=LEI zx37a=+Cp`~uWWSqcj&Zmkf((`;+xB-g>RYFA(ho!@jbJeD-Jg+(ox1crsBOSjx*jo zak3d+4dd0Sc&~{c8Sgo9TCXj1)USePQ90o{>KVP#&WBdoMZMDMnBpSQpi`ud`c?4f zW+b(a`cIwYzd}h0yCx!WyfWJ10b#a>k-`S~WwVpC4cAc}c9LIKPN#)r7ib%d#R#2M z6th|^qQkK=bkx{TR)&rm&#b&UD_u9tm>*U(Xuby96ZNu}zf zIxwlFqLW>dOI_?M0_1-CI+$iRy5GJH`t82IH}8Pm>?D`UMR)sUa;b-%UCnmQ=WsQ% znk%lchlQkos2h6+^v@cL*ehi ziKzZ?Lt%p6EIFagGD$CCK2uyIrsx!DDEvJ*tr2@JCKYZd%;Wx9tg~7W%IZFy)gorKSUebxm0>766w1ml6qYcn#X75{p{$natR7=l zOT>zBtPDfp37yo+P*SUPQfryiQn6ki3Z?cHmE?Z=I{37GO+}OY?c3lpcHhC~L*Y3d z3VX#1mk))F++ki+JIq}1GP9a1es9;?KzF~9Li8%9FBh+0{#GF>*cQF;Z*Y#s#hXTo z1{LcqPM;_Ks8b9bEbsDQd5`E}u)Ha@>y&qL&Ns!bE9BhGIrpeJw~4)a&PvX?P3+Ti zVptpueu$pDIz<{52ZMiWMp7FVpX(&Q3?=z@o#fX{ z@&WO!PEv1@5LM=TCKYa29Ol0IozCiLD61-+)d^;`SX75&Wf&Gegt9UWi&M<1R%i87 zD66wNtMkliiMSY!m0?)a>7*J$N&QnN^$U|)DuVj3kR0N%R5;{h$FDo&%#J39oYnE~ z9CC=EleTU2x)l4B1Ex^aU(+9Cf%xSw(=9r8cK zA;np(rJiW3lg_cm$6_BXOnjLJUCi(w{^9Zo?ShUupPKBrw1F<(NahO{aOr}2A%}8F zhk8lHHX%Ee%P{C-4gEq6x?F-Uq@W8m-V&y^bUB5YF6|gh-{JohobYK)d5K2IhHf>2PGxSWhCZgzEs1$vE(D+*6YI_f%i*RFXKq@#0%vT4!Wcx-h)dz_+k!R(D9Mj zh6_<{7QS|7dor&3K;NVOgg@m{6DdD9O>N~9=p)p}+-NWKH+zYs zUWAfP{Sk1Fr=11c4od_sPN6@^)Yk1O$3-dhd$^WYp%tU%(P=gD03FB15=Zn#g@%D1 zl}Qjh*&eIWRd}F`#1nAzmvrGkUGcNhAAqaCnPzg#XyL`XxCdzONi^wwK>yd@Pb(Mm z3C*vR>9>_*04}^SbpURdcT?)wjC#{(Y43o`YPjyycjJp%lz9N{q>NdNajrM6AB#fc zf4S|{cjW%`e@feN-Ex0MU(x%MR^Pwghpy0eT6^hz2z`sj%oX~1Ny%N9_aUarabAT< zI7oKju`5J_^*c%GHFEkG{FF*}#>m1`f)lxIbRzeMIFXxwoI$vfx!959^0mYP*T2w( z4Rbr;A4sSl94+3W(bHZz6-=u1<3bl43fA-}cTx+DP}v8El&Kix&vNY#bUcK z?VZ?AUw0{zq}yuAwEMfW3hos>Lsau-59{4NSUWU9GZQVaR) zQo7k-4i%E_)loi6goz93X)n9v=Pgmo%~T+l`FVR>W17H|xxJhs2`aq_|73H(Ypz&q z(TZn{+>HBkq}Vl+=x~23o|W!7!uMKS9U+S-s?}&*Ttlh2ROd@GF4f6}1eUgDWpn-V z#X=8p7Rt7YFU>ge5MrEgdBm)l6KCN&XA}O>O+xZV7WEdP-B^U#|D}~~5wc#yT`Sfb zorsAgCO2kB(l>6981v`n?9?uE@(c4P73bs@<}2{qYFa!_O2E`v_vRNVV8(yKe^nk; z5yJ<~Et)f{NZ=ZuL5OT*4t`ll@%}@6IsE7aT z=AE_cq_kD~#XGC{${qD88=R#t^;?zG0%fIMVHT@4<2e0QuS@P#Iivw`SHZ0s4?KSC zaeAJNO5{rw?mh6?Ap+!TfX9A$b01^ORxyg4pDkbv01zO(YBMeg&RxU;f}vXr!5N5ZuY z5-yOjR^4}k8ts-@vt}j_Ehx-S9=l*R8cWVc{#8n)yKhu-!1L$&$ z0^Bh&f7E1QJ!K*Av{m1)r2f~HIN)3EZ6Acalpp1LFy6~*c!f%I z!!4t3!d8X3Ciy9w>^IXqGJE90{Ya4xl=U{1OHrxLR8x}aS;>{^8th7SBsm_IBGyXL z=ICC|QYmhL9cS+f=d)5&KgdGYSXU=UY*Z&($A~8-_b!ubtfQTMn}iw16Squqv~$dH z0w}B-rPy62o3Nk3%X_Lbz>OL}`S@`?N5CshZ@>Wpk(=qM!e3{SE9uu>|0UTv#A4Yy z0TT_)KxSvaYpIxkdB+`y2u^Q^eA+{Jm0oK`eN+!jgLL5_=BLWshWeO&3Gx1V3& zM(xe5yVM>wv`Ip#B06T&rE)ns#|6`+Ku*I|38vD>1XHs0X?ubR&;H2GtnwfJ15Gge z(Nz8uSOT@v%<#AqH(Zq#bW_tU0n*P&PIrR8Z!{*V0sOs^^%RpP2LuSZ4&Vw& zb>el1BE?*kQ#@%Z!O+usp{L=>2O)W~xN}-(Smf7MNxqSJG$f^WzUXa2`y{)Bvk=cy zs5P2eBn1uPZ;(v;#y2*gNhkvuFJa!qhqu^-ztxgwO)iZ#`cSczX5dP>w<%N>l!rBX!R<;WXD?sjl(h$)qRj&-F)Ulld3)gd_o z_hkHFc5k>!ITK2gqC#H3@)>O;;;(ka{;1U1 z{8d)px+s5@t%a|OFsd#jUidNY2i(0AUlpex z^IveZ^Bo<3^g?^z(cEK4J7!iT`Knx(W>$5^p^;3%|06Jv^T(?=r(esdQa<880?MFp zq#IM{o>|ppOwYY>Kj5Uzju}?&uXhf|W?0t^J`~TLMgJpiqW_VjeMcu8J@Vrid{xyS9V;Fi zi4k_2fVbm>?<5Y1I^p)6^t|dj?yBC2RnjchX55 z4psHK>o^XFI*G%fPP|U+&`i;H`D$D>1vT~3czTSP=&N1i&FSo`c3tdQ9h>XA)U!G+ zk9x!8-Aa2aJ;FSd+j|$)l4-2cTFpItR2H|XuHaes<)KBMKljQev;;f2hq(lIZV`|F%9HE-`t+}^Knd!K%d z+Pm)PW~IHi{#tv#p|YtN*#-bb|B{MlDKv9{)n{1Il9piSPA z>^tc{newr(dQsa?+_WDZm+Bt(w)0ix9sTlw` zb=|sZHB^83GbfbW5u^`CxS;#W(Jr}E(RKeL3!v$8PSTZ+J6X|Jt4LM!<+D!c_G^Ac zx0fXsT_9<3L2H*CE=5TiU19hR?^)lvCKg z3#)bGpi5QjF8}*A{}rwKdlY&a#`ikOvpz)Y-oVZln&-$qSU__|7_FPMXrtDRi)ur( zZb~<5-IOl90j>LWiEjIqN)Pt_zgl;Q#Sq2__^n!ZL|SyLbDJrSOzH?SsT0RW_*}7Z zBI-f+R#Sv8#gj;ew2KVs2-T2w2y2N6HaadM3ahijmMgFvM5~2r?X{ZLUJI=q!V_A1 z4QuTzJfXGMLPsOG1#r^Z86Kjwt2v>y*Nm^Jj`W>;`y{k>+6XQDxT{7Bt5=rsC zW306wll;e6Yd;39{TN{!gVz2-TrITrW0z?=ZCKk$@g3vzqpY=L6 zOY$9q)_#n%cBbI}LDB1fxQcW7wVW#D!~Vn2+CkxPH>S`%^B8OGwWO6_#{FuhaShQx z`cbBhygy`e-oBZ~dV%}Jna4p182TF%S34`K>KGLPw6`!Q0ZQ9Nkv_z$fe z|Dm-XnQ-Lr4`c9ktp12mYd`Kg0j>Qwlp$#CRj!kywIB0Wv(^qpvzoN_BhcE9kk)?8 zopb_P`?1$uRnXc`Kx;qFT05EvT6?vtx}ci0c059BuVJnIB((OEthJwn)(%xl)!Nk_ z0j-_ddm6Pjw03Im*`e)SWNdHJ+K<`pRofd{`>89m_X4iB`?OYLtsONEt)1F?aptl4 zN_&&muC(_lMYBHj27 zlh&@Zx5oV`&iRa%^D^$wF@+a0kLg-_v-aM^?fnY3_o>&Yz3Yw`we~BuH)-wE-lVnv zC+$sIJGJ*8(cYxBEA35M`^mWN)ZVJrUIVSY23mV9Ywb1A+G|;BuVJmd23mU!Ywe%~ zt-WSqO|_!6*P7VgPyX0f<4u`f^T|`|$Pb5yw!Z4zn(9w?Cg1^%xSDnc{6D3B?yHHT z9MJNS<75i7_~ZWL9lr9N09w_A6UVC#`c6!&`RU||Z^#QrCGU6$dB_ep>Fwk@k$dvQ zKYS;d#tHw4ey>bcN8)PJQIedRWAMgBu{X||71 z`;IfsDsLK_VYNMv`+uBp^2G7eRJ5*VeASD)!L~R-=EgZHMfwGp8{J`B@XUCLU1RH{ zch-Bh)nm5(KgqgCj!704ST3|I2n)rI<-iW+bk%A>SvRo75kY0f+Q&SS@US?tS7qZ& z8u-7n_(ojoB3yhhCNFY`JdhS0rsNk7om*IVM}AHb9QrpuOTNdVQJ)+oZ!^+v#GT($ zSh-7FKpk@z2V=@|IH|s$b8ntz* zMKslC6WbGQWD#KrpfsNxkMyyUw{^fc^$&Vn`6QGYu27|39s~;43%scz6;oiPifJ7e zd6pIv=qm3)vt||L%>g>E^AK&gUUSLgu6^YGb~)2dwS)C9UTs=ionhJ~MGN<{5mCLO zMdI*i5s!qTbYyAr#*)(-{&6^)8g(h?RfSaA;6<)b~LY;ik(z~JF7J=nEAXa$I z40907)zDNU)H&%R*J$fg_7*nL6`LEVPGYU4lc<;>8Gi z4snXXpN#4ZS<~t4rlqH(TXLPE1nJ7CL~q@ky@^)m=v~)fhn`Zwa(g#$*W^$mQiZP_ zB&QFOGDa3=Pqbeqi{kfJhdom{aI zB5IC%n<;9!eT7uwawWIy6_*{oMsmJpqI5^BOGM1+uuFEO#U&?Zw_PiFb!g0fYG%*7 z(n@?ajVa88Cn8eRYEW{hS{=_P7|)+20s>)plb49@vu8 zCd%)th4ZMP8$3a$8h@zm9=S6@Dq9})Sia~P5!_j}+_KYM=6Q98>+FVhiNRC;bDmx= zw0M8~kLP-C^u7nZ=Lc!IYr8w?4Ah=eue(ma>N@jcv`H+tyMB7z_0wi5K1%M**u5yW zPv>0;{<54ieS4mb=$~N1tGZKro^@SEFM2(dOQ{x=Yp4IGD1X_6Ge0#9Dw~K}Ix}SX z-Mb$?AGgQzs_R0F^NZq#**{p6Fue1wxbuDiiX%XAWZZd^&}iQ1I@|M{ZB*j9I->hQ z-1%-|i>VBhclb|7`FBqE>2%%AJ13rNJoD2nEzhf~MFVrNFQ<@Ntoh!m53Zou*X*C$$CAqtd3+QR(7VEcI#l>1~zE$q-l#g36 zv&mmtT2&qUhnXptUoA<`cTg<#vYE@QZ6IucR(I5-0?)D6c zfVj3rRl2*?M^}37J?b6ac*hneQHiU>+HGS(e8d(PJti86nzy)_XiFy1Dj|u8wy9hg zk)jZ78;EyqiKJYqM5LhuDO}o8B#O)*ZBcb2#K`yk6>-+dS~|6pnyzU0T>n= z1^!A#aBzw`3`j_j!{lLPkvff>Iso4<;_m|f4xnFSWm4GC;z{^BgLVuF*540Z$Gs7Cj{EB|TSL>qj9)E94Ms!Y-&V)eP&)_%-}G)RxW9ruyj zw4Z0Bs3Cct!E;Rl{dt3ve&5>17RIGcM6qSbJD*xy)VQ?%K0zIyN z;V=>g<8p1}`hv{Ns3EE)<&z==CR52olc{VbX;kJl?aV#% zlNG&2wgU{+4i|)!x#S*a%oRw@Gs%?R7HeY)ER|p#0CRTQqrtlDc>8o(SMZ2PFN@ic82ll6$M`cp z_3+iQFsqgPwUNCx4Di=R^~TcFj>OvYiRaJy&pP^G#p_~bO(wqTGHNn<)H?e4&pQX; z@j_az| z`zDoP)F!olerp`d8H%ht(usiR834! zaGi-#8zFT0g*CohyPX$M{oTiP*?AHDelTGa#Q)-yE)SP2zDk&Ox_3-McYQ>ktPDZl z@}pKQIt=nW@>-i%BTZd9|92QCm|Ij)J^(hS5=6`59D%$R`XliUbHxI4#YBGyc!pJn zxs2yD59T!wewzoBWOl?{r=%(CTJ93iUdO&+&Eg|9gM`@t2(y0lTQ&LQL-M<3xw}J3 z8&mMmcTcRP7?Q5tX3xznESS`-U`EcA{5jonr%Y5NaQCjWDO8E*)Njb%j5m<(&dTmW z_3tSZ4$n?ODH|MSDc9G z>D~&9#j}xQP}4L>_SrtzX%SMz#74Qk4sn*~$C68?v8nvt$9>x5YNS3LpA5xYHt(Cz z*dSZ^!EC3H94Cf}1xZ%-z7!jYN4uvI!^pu>G^P^h^|2&<2_=3Xi)xpza-qA0B2`;5 zMyqh$Q*gsl9Enp-Y7e%M7P?q2H1v+GEDI-;*Ue8Rji(##F%P&wbOMx?%o|*~PTt zLLF$_$l_UCnDFZ?g~tY zHh2jxU5w-iHV6&CoXcQX*0) zM6MliZEWvqEcJM!rZ;wc%8}4LklWDMiTaqLuo>n+t&@G4>JWjelukwp8_gT^Q94;R zzA@pE{lb%&>?|o6-n^r20Qwp!Yvesk@k3g&bOgc{qcfm!nOy@MZEXE)FG%*+Owqj< zG8m8nwpS(lc9T39s7$c4M>qdl08)KFq&mKHnSmOn91EkHpj>Gz@d=%XKmrN^^omJe%aA_pI>$+ z!+9mQtz7I9CS_JgZL$Q>j9B}Z(TzHpPy3hU5AFV|B0%{35SJRbFPeN;c8>M#?JblaT(R)GO9=DwOo(xWr<2Sw|i@vBQ3I6c&f<02wSNXwJn*bzRb9M*eX?c z1%+AKN#`eLUmbb6q^JtNY>M9Nm0>N&7rcGyA~Wl4!%SAxYn&lhBAE{@DOW4xvLvgy ztemTDpSm`g^}b=IgdVaj6%AqUfPaooUS|*4)x|Hn)6ifMT`R`d$>|k~BRP6puDjGD zC8`(7=;@7r(HQ}6OvPfaqz1nCi4MfKfo|2%z70A$+a+PTc~XKyEK8$C>$Pq=iCSt^ zvemia$|>+fN~Q=;5f8zS#xrry2>L&8LK|=`a;~A(qFHli=HXdY zSh~hL#M}HHvFk1irC$$GEg?J;?*@1B8lq@aF4Q1Y`7@i`-?>JQ|6lSB4OpA7Zi3n5 z8jeo5i%hL)H22e=@=55qFQ-vZI|sai;(ASZC&^5b(C{YI-RUFm)if;OcZgTIQ^l(V zrexkGgYWh1|Bh1&TnY#kIL@>(`zWNel$Vv=m0+0PSNHe*LcWc5#rgA#CnDUwyOV0` zT>LM$Q(@uwzMee`quBQ$NX7ZgA%B7$2V*9E|JcVbH|3oVSE(H;3a8A=g8NmYx&V>j z><#U+c{Yx8(ZEv$DM|SfWmtM|Dg4)3$~-}O6hz^3PV%pGcya6X6hGdsa2C861v94L zxBQe$GVA-AdoJOP#2jc-5@hl2;sHOC1{JE(&7Da(d!%a$-Iyu0NgW!g4MOpnut?19 z8tK_SnI!FnKaVg?MH8k+!=vCD9BqzBk1|KRDq&%pp=1IB8ka%)QXF|vB4WA8iP$2_ zp{4cWv}~k3Y;vv$N%P*VHwO&lEw?fTD)G~-#A0xH?MD4l?ubZh|UlE_Y$A7_OE+2pK z!XR?~4B6|AU@y(EK6U+w_z0EwNG5)xLOha)k0RnX8;Or4;8S}29$V>)tdFnoIG^b1}ZL2VjA;yJB^UmDojji;?L5gCI zo4p(X{L~_xUPS4|oF1>F-)oq$(GB<`U#(3Y^_VtdGZoe3Zfoh>`6`B4bF)Gw3alZmiD{f8pm!8JyoR9J%BnAhXu&?Q$dZM>l^{V5(~0;i!rw+r1a5r4guid_CorK<`YHO2aqPip zpmbURnLyvWj*l<}OlfVfRIwjUA2^<(UY1u#yR4Ui>Mf>p;qgmUQ%#Z{R*CrF?Q_Dk z+kaj%?}_;!G5AyT2jefCx9(t9%W27f+GO9*!GBtI?m+74#>B>p{?lgn4wpRrbmY^H zn6e0I7nT8fiKvKR*{YOfuoiH)by_zb0KVJJ-!m!>tBrXjJR4y8%v(zG>} zrd{~bq$;K9aK+L%no1L`3t_Ypf8++O|J{wg7t_BWTF6#E*uqG&^M#E3?|t>@!HA z_q%^}_B-@5Ju92Kz5I#QETZ3ppv9Wso3!2ve*k>`cwOT%q4jZdHnEpG+lRwlK(#So z?PV#wCfQm#IT5czA3jEadJ7_-OE6mzOzkb&=0~&=e#Sx_c>~qLR zZ1R^j`M6E4vB{@w@>!dF(I$UwlN)UEFE&_DRO2f~dY0Kqec1ob4W^Z(DGYX*?Zu=O z(6=oqL6iIaY+EEpPvC{PpGgH|$K>}0w)6Yj)s85`2_6ai=Uko1g!fV0Hz^^*N@@k0F2 zyr|4N#o+cO+!Tn1c~Zxr3w13>ge9^4Q;D6mpxqku?8!u%rCg=gLC{o!b|7dcr5sLz zb|GjQLAxqY7eUhr+Jm6I6sVh^WX0Gs2--)1Mi8_gL9Zp~00kOJ(18RUOwgeUG>V|Z z33>xTvlOU@pf?e8G(pEG&}f2=CFpGg9cOel>6-c@X4SMvz0o1Yj8!@=t#IC{wt*%l z$>qtvn~6^?J=`}sIK|X!)8TUIT{ls`x#lV%v|4zTH^`d4c60tXv5zrbS`jD9tA+)g&KcgUQ%T*#MU(`7hz?w8Pg4 z0IbnPvbA$%T<~g``(`rtZ9VHP4su(%)J)b0n)ssPf}+B zBmllf^Oq&VUTsm`MPHEWG(R2xLSGu=8XHpKg)qUJn=Ii`GSj0KD_nRm*|DeI$v@5T z3|S%(7AIS=i`5nbuZ2Ap=7t=KCxcDbkTSDl&`&X_N@-_Azpf=l)P#(8FiRU zRrgskW_|pTo(b6M5I@ihfNYSGM&{E5FKz$)1`LxWx+4LO+8LoO$)^PHAppO+%O1p5 zO0lah^9?A>wSo9N@x&l_jwHoNzafG0@RFWKf0GbOxC0Zb$*PMR*-7HY4RK?R z-0QWlxbY0lb7a=p*-smzi5rfetBWOZ6R(OJD^1is(6hA&Ko7B3xEhB@N!{vNp{F|9 zaP|~sN@roy4w$a?G^KY`pe$B86Z9&A`V}aPm2L#>PSBnTl*P)`1igl!nF^G}N?(HZ zC+Kwwl*P*R1RX@sAqtem$}obCAm~U18bj}JBSA+I^kxO>CFm^#y_KN2zLo@fEJ3OM zTz3(4yaJ6Q=tP3%5Oh-0kfYH|oklcVPjoV_aWUkVA@asHmZ_ z__X~xH@thaHa7?XI7PDxZ!4Hnz-Lw<*b$}KaQGIQmuDb5J^v-8|5N-7$wkjy@1EJ4 zpJ9t~`oeelWCxmjo1|59HJy!?CN&+gFDt=AdUn-F(>BSG*7A%Ly-AAOBfGbG5cpCp zh5SR50*NggR1j8iFOfX&gwAy-n%mTCs$3+S;Z2dvmY#JM8&(z_J?m^PS3?0jkQDA> zj|xC9i&4%Fh*qAR+Ot=Ac4^OX%ClR0PGHYQL?W-8w8Y4xm4m2OfkmD+oq5zHGeUG* z>W`@jqi~==hlHE)pVw3_v!D{SP_ZoZ%;Z9)cN!N}tg-2?5;{tVJQGT<#12G?4<4Y& zM9s2#iK-X9?l=X5ErB8=1+ucq9YC`yYyqeyM?2m8Pm+F$Pju3(LOO;7v$3N7OR%-Z z&%SF=gcT5q=h?-13E$lMgRPs&fk-7SHXf8JimxSd|5f%{q+_cmdRdnJ=pADE0b~v zAHiltIe7)M5NlRYd@t}(mL98re=}88QT_~^<-%D69oEv|pw#znoOoYTNpoivLJZJp zE)x5giz=h(p>> z{P6?ppjvYDaGXcN)YN3eD;bjq!(D~s;aYC|FeFE1Da6NV$@qa}WYFx$=2G5ZFS5O< zRnu>>FEK5DCa0resP8BrMfC96;*~8-*$zvQB)gN`&~CU>Es+&oE4x!YigZ`#u-zDo zNkon}=I~LD78PDQ=SZsXI;PjPe%k7FE>AMhZ9`Zo8df`|+djqB8I+J>c4AfR(pAt1 zEdENb;`Q!EN<{ae!#m-06qjjBMBj#v`@E6;8hZxZg$=!)YLOB}wx%ZoPN1QtK1{Ih zFw@nl*1>YTKNa>mo&c{`-hl=AAOa6DzQZso-G~aWm)~I|6JxitD$Ykyj+>1+ZlN5v zR(Rt$$L*Zs4#nc;RmHoI!!kbLjpvk!f#~TCIZxyG7t#VXmmO438K4FOIvNAyO!v#4|r3UZYfp6(;C^f zMD`gX`<#~b1&u7Hy+~wV*3y2jR8Tw0^(s_=oo_KNIvL1=@w69~1Nwg8o&3UPaK) z2>LgIeyKpy2>N$|eoatZghO=w1pSVn{~+iQ1=^LM#|V0ypeLJ@L~TUNIhv9<4I=pY z2Nf~=8qW0GRHNc3t_meso7W%9ZSghVAta4 zP4ed{eb@q$z+X*r+5&OZU&(kF_R1k|R37sOpKG4v?HQ zHIgQ#e4vG41WTq0mz-K0^0TJ|*o&6!qZB@HnxjA?ShZt2q)QSNH!(-4_)QCZ81 zs%%=7*QQ;$Mr)#4G*9|N@ESXGsDV^Qu zccx=Tak>Wj^8h&UJtFK&0vyvoL&Yqj7>0pR?xhIqD>Lsf>WIax#9Pu)z3v z*5-ZIO^R2V!)NQ;W_hA}MM{Vn#8y$LEp({)B1Q>1mM2;o0JcqCD*-FMSTx*=l?+q4 zvNCKLW-{-IPsSRWY#(N_?ar=)g0L^4e?@K`T;M5Y3P`kgprhY< ziFNfCFQh8ZQQC6{<=LY>cT%3CwdXFZ#KfdkU>d3K3RBCAxg|EeVo_X=&OEjBWQ6#u zNz1t=VN^v!_{;KWisY0X@6FBE`YGCNTY}K1-;*t*5POjfbAyn zD458BDT)zBRv4|ozywPjGiA)ARxRo9h7iOiOH8Py82Q{_b)6yF0~elL$Nshu+WG7U zr&<5g*T>URGWLpv zrI}d3+UXe77~7n%YH5*~1>P~^ZW?`ADGk<^?tP?5;q;^DzohDDS2U|Qrx-p;*j_N3 zuVj8LhWWKfIztoRvV&Qsm6E!^#ePT45V_l69kCrYTLXmJVTa7N!O^rphEN-bjz3Gz z?Izn;9nvqV5`u;-oa{Ip>_`sHmh;?fSQ+gv=1{D0fQ5FsK~|{QO!#3isWgMEnMtMF zYpOw(r{%^ELvGC=E7p?nLovvj8)CdVh}X~zGR+YCpDp~Q;B;T3S*AHlRUbatIZA9| zCHCM#1Eh67ld|ro=!G&t5vN#hj#O+A%$Km$JY<8!Xtr7`tJv{M4z|_E>1u}4m8?4@ z2gTl6T5DEVGTUlx4Yrzs)sC>*YgiqbZs&yCK@Bcg1GTP%m#)S2^cdpwGitAdafI62 zh?+^LeJi{XJRSBILoUD%&Dg3pmOZ!Rjg1-<@OmmRheq!uNAZwumxdy5%y3-3=vi*| zI+PQEZ%|&7J+N8ynzY=hzvgJ-Hm1TG%iP8?pWBoi?1!cDneQ~_xSMiJsPM*fj%?16 z%XW;?NO{O%nH+%WMhAmVB|B!CX2%M@u)%1o%8r{}J_W`0?qUC)^qswVai0)D?ZJB}YV?=iatT?9oM8KOoy{W!|gCZj}o*>fwJLt zf}qs|tyQ4i=-qxK=xKtUQK0DrJx9SHWvZz*Kncg#D3lZBIk*S;n!ZEKxV9QS`eu_6W+9n|AtoW~83q&a z*Pc;M)#OW5jht5jBK_kFs2_RI2c>aY*<_VL|Hgs?RN_}UAM%sG^2Q-!h7D2FZ>a3h zzq!Y2^e_D)IV=#!a+}PK(=QE>JAoN_ByI?8rH5@hE+<^+r><3FPs%Oy$)DuM$kcs|#Itbs??Esi7;pUup+NblTz&x9w1RI~`k0ZBC!?YYXLS$Od;Z z+8w0%6<$LY>SW(p(95uARY=iNRyNtj>iPjXM1MPJkk8-JCP82PEzF->JQ3E}l$kT2 zjs%K!&$6M;^S6Wu1ZpuyU1a>4vt3Kr&LpweOsHS$Np9?=__88_+m zAv!&3!%SFk8FlVPfs8$WPN9ts1K z!Gl0TZ@O3c9;<#2SHJU`J&$hotl?|lHs< z#}Q%=Lb7`x>|g3t?v2_#;o^n!z4IKattWhCj|N;kFK+k#6st=!vk z`o%(6Q%SV)rDM$?tXUe?JUQw~ptmXE><=l$N+!%Dgt^pMtYyUTv2t$;7i$IQctXk1mU65_j&`dnys70xeh-mf z+wH>O{F-pvcFpqJ-zOI#mw02y^6?g2ZD8Hg*S~6b}43t znzkEh{&FqNSj_<<`=<(TSI+feg}2-Ey1!_2(~0iKME4Ud?_V{#oc0;f{hOBdrJ=Zg zC%Ru(c)K&*Z!5e#rq_L^(d|iezbCqfwY*0)x|~);bWdn$)dsq?MEA!EZ!e~MnyTT9 zM)qnVdzQ$a*Ro#J$Z}d8k!{e@{;9M{Z_4!xQmzSBi1Bs7OF>CeQZmX#?xo;GY{K0u z{Fj2)2#cg**i?*66~m!nxHOCi6{C-e5v5^7YZzV?qpyk)r(q;$7>O!IKNX{;hS5sH zXrp5ES20pFjCLAEdllna6{Dku(OJW|O2xQN#qetw-878uD#id6qo;;}P*SDFu2C_r zS1~d*jJ_I1f63_9)n;;euIAQ#1sd+@zbRU76>Pf0*TE2?58Gw5q9UxNAmq0tOpx^3deEAUhe_Om&)JXo{F*xT*GFoGXh_exLd)~}=y6S7( z3QaoS?<-@$To{+o!e`;w6{jDfbuWJVmLEL(ckn-T40arCUWD$!n21lHBy60k*autGE@ zW;-4o-oPSfoD!`S&j>Cb`3zdHJ(a}vRO>Jy%W|UD)p~s45MLZrx*8p`KqaG((V$X} zDnd^KTwOfV0<|^+L@o8A2~AZG1Zi1T_5i|zzN!Rjt)IO6L0T_sSA_Ob;;(3lKo8U! zAuEwsxf>qXLrYx>9_}ETH9enNk*thW88%*RU=m8w#Ca5?4o2-Ivaurv-t|#aZVG{# zJBK>UiEcYe$&4(T4^%8NAY47pt{f?LxsDBE>hdij7e;!{d`^yt@~WG#*%_Lh=Iqsj zlcLl0SS0KVt)c*?!J76OouQ}w!{od`X+~%Iv#cs=$WV3baXv%ySm1iB)skKheVT5} z!MR9L&TUkZ=5Fcc?r3QG94Dmf^}k^sN*-#<^LUt@ssDB>%C83sd2Q3{VxaukL&AP& zA>o>(OSNedKGww|_wzB#?F{bh7J5ZzAN2GuU?j%4Mt z(#xAux)R-VBi$a9qnGBa?#($^`KjSPePKMsi#(A?GR3c=8)gCg@axPE(*9OH@eEnFO7!K-ukFM9^Y_-m5?piNbt> zE+pvv3bX}5$;}*BLQvK%D-mD>C^?wpmJ)QCf}2E8axTZMASi2K}!kxv;u8I6rLsM^90=(R#N_=`GZ=2?GTfXDbv?{Yw7(jq4Uy5aa2MGHVL~X zp&8=fR2-Fj5CX%qLma0H9ZY?ypbht}3NOvKv<2+L3okj^@@w)+e#E1RCvq3PebD|F z@+>KHnGo4&Gzq~2;e7dD77EE!=D}NM7EGR1Gy~7WNQkW_A-3kqLX76fFhcC`6QfKx zPChNJoQ2qvvU{0}g&6i9ULkDxe_x25(uCL_&;w>y|Hnd1+Yho8bwVw6JqfWsB*cod zvU^iSK^iFDwH71XuTY(9i@)kp_vM0&+Q+={ckCfj7oC-MB1y%@w_t_T~lB|d` zw7)8(!U`wI)Mon^_mA{^xQzNm$;+cmKawobLqRo}5=J=b-kdPPNq5#j7CmdSb!sk) z4ywM5rcIJy`tRh9CNZ^d4PX1F{f~JbKlX1MT^lqZsON2{$01-xmRW(CXi=25tjgHN z+7uz`rpS*-T*b%dB6zTuaeBQjbTC~jaim_tiqo(Xc)Kgk7b0$a-a$*E+sa6{4dqCw z^g5YtJEp4(9i46m!s?`9bz!Da!FCwp! zjXg!~HIp_&^ZIAj_u~wMm7+&cl?+8z&v4qh#w(x!oK*m57C~B5_F~lO(5uOf))|9 zSb_QodM`oe6Lg^hO(f|31bu*@B?^@Hr#?i`B?Mh+&}q?S_#+?Dgu-r21qaS5)4^10 z>HK9XhBA5oJE1^3y-6sLHT7HnFu0Y#MQz*68(>LON;PJb6*NAZR@6F23=14u@KZq~H78$|?Wn#mKs+)X}zWri7?GiU1cMMXIaZ~*w) zY=99Hd1J`&-y|T`lYrQO)h*JF7rI}4h_vJ4LP8#s+xSg&0Y4Wh&r45yGliaW)o16x zXV&pE@F}9~*TTtY3N%QXyGPF5L`B;A;V3qj+UN^ihaHzMbT#dv2@M&$Y~MRCczw|c zl*8ClbWl0A&{!}l$Yv|H(Acqs#)&O7ZeG~F$c;#8& zLgQ1OBeX3wEtKa-?HO)9Xfjc)*=S3K(dNM;HqYEb&O@$OF8Y#lA>xG$V7eJCy5e-QyjmN@7%v)%pxveZYv zqrk^vT0>Be93mluZIFv2c=7VVOYPZwqZNuK^Nk|BAkScV6%F|7C^C)PF+v{BP^Wf} zmLHEGX?7(!2FYsNhrey4XX{PW@vD!IGJQcJhD61tkXR($+=67{os;?=j;L{tk15g! z+0r$!2P7Q3_Me+bnuyZms3xMcZx&T>!%LUTs>=-|EszIcu-JWTr>V`U?mxIJWXELv zPeXQ;9L+;^+}-Mw=!;aEI#n~O1Nxo0lVZFm+2{lxi{S(xT9QpyaGBj`AS-l;%2oab(WP9P|I-&J#nwoAD42s&B8r;#*pv3LLG zqJcSc@_Y9J*)db5Z8dG7Si-5r#k1%1>DF!Dym?(G<>2B7T-Mxm(ySS{h+$I>86GTq zmSp2s(@B~r*2Z<&^#Ezdpf-z|_|8om=X&x-9?)cm7zG+dO%QR4nlKB~eRkR(3XjmE z(IhX(_PHtT%cn>#-aKaFpizT|OkAf%OAH=4>t5ph#q!ZOAjP;ZejA;Ux=GnFk0Y*? z69P7x`Gf!*K`yK~JwV_X72#PCDlBkOGMy0613|$2)5Q19LtfbTc`cQ==$X$+b!w*q zxcMl7NIpuyhMacKzD150?I-~!t0Zud05=YzJ{H_$fwxS(BzYKqPFUen%1vZ*r;0)UT0-j`ywkjiSq zKdYm1v5k)isB66F!rS1Mxplan7#yjjStj>J2p;@Yj}X+%t!;#Xj3NN}Js*5_w5gh$cO(aJO`38D49Cdc-F)SGxB;+> z+;jsB09=^Xm@T{2(ee3qWsg|0!k)c&Z3#2A3A01Dx{koa-ewXzS1Fjh zyWbB?S2voPyDL!M-QSa-R}=Ia1c*31X&>RKIyZa{*G@qbT6etU$ z0)kE_=!|go%j=rl>0)z=1Cff{8D}jmp2e*xR5>#M-&a_r(KcfJ+izXwX!gq6U_oQ@ zehf+etsQcGC7Cl`D{PA>r>Ko{(h--~cl7xaY1IGP<>v6HrQtWW*DM^VB}DfgJ$qf< z`Cd~O@boEbSq*vJBB7k=~J<%OAnFDW-EmxFkdH_8rifdd3m z0XB<@g?5VIi#BnnQtOuO$?ky68&a50L0SVBjDSO%%2qq}zV~SYzksMqirQq;% zGmf|xZZae2oNG-Y9;Udh1sV89?WTN4Ce|x5(IHoO5d>l?UnGmpd$8+BmQu_zDbf~g z1;a*{kT{gkcu2)Sii09;oKg7aimq7f7AA_EXt+QJ39XlsHt;oIBrn$pI$qjjFE7Dz zE1p;Kc{!R>`Hz6m_#96z%9-S%T#=PM5J1Q&)mDSdoRHAw365h^`?yhPl7gkkOd$52 zwz(bqAa%s1V+fQX^{Wwq_kP!@fJLsBT?q)}0sF6aoY8I+Oq&8GLfq2O{OTsl*w8#v zXU(G*;OM;(|6U;fVxujL)gwJsjFkiUneh&1&mok@;m zS3W2JqLvQ|7;MO^$ZkWB8E@9tgz|q&m_x&WGjv24XQ+BKHQ$V5B8$?Ta1&gF=vyW$ zMOWEr?L;v!aWNa|qT~-XM^=)E=G_vWZkM8gZuPPsQ$4xbMwkhNnb?HcvfCxHEbXls zGdabP%4w_;sN&nnG4xjr9o`O+Nr^*AUxGzFMcR0`?HNKY_1nD$VuXE)7I!%!8T8Pz5uZFoy#(=7vhI zcQ3tUIc*8a>UQx)iuoFa03t7fm0}g;l@H~?hJ4CJ2ffDMN>#{*@>F;e2zm!W?;5p*I!a|k*~;hRX%e1c9PXn_K4LD1<0ok7r93bZ9b?;+?Mg3eW-Nd%oo&;vs{hb1BL(oSFx}2boE6`+uK1tA31YJWWhHhivCx7I# zn(Xcq?-Xs56-8sVwRGV+6{E?(H7XhiC*g3Aklo>G_=tx0n|FL`Mz0p~@h(`?1h)WgrHPLHCGi#|ZHeLu84rBk$(=E1R7(>9H6d;>@O(U7bztHO=ho z8mL@D>dT-}x85{%$e4*6$n2b9k#QsmpB9Pz)Bg{9Zvr1xdG3#&GiUZQlT0#`%p?Rz zNCE*eBtXP~r~$$zkzGY(Nq_)>0Flk5VuKeg0@|P~fm(~LwIJ3@y;j)?khHef+e=$} zf7Bud2wOzL5<*CA`G3FfIcG9~ptQBUzyJU5KYNha1Gn3l(#mf@njmne~)6wy~ZywjwuJiK#4RYY?_-k)?jchHc#G`i`z ztFaFb>iS+AM7lW`bTfJ=p5h%Q=;r;R!r_BWLv*_7V>3pw8KVW=6cSUNu005F>^S0n zAu-kI+5|!;5h^66I$fJW=rlrw#Pony(6yO_&LUJuOb>VkU7Jg238C|~-V=0fA)$*2 zeO!YIy7nZYPZ9dG1{HLzl+b4gU8X?=U3-qu<%F)#pn|TgBJ@Q<%QUEQ?&HhLJJ*SKJ}Rwi;N~?AHRT#@#Zos--!ws;imB zDHR%ud>`eHhDf{|(;zlWn+kLc!WdBeh#?npd$=#peOxUCOwcF=(L+G?uo2u25Qptq z(x?NZQMwblq1;Fr12>B!Ga48DSD(klxSUVpB6fG8rjfYT;W~l~l5aU@^xOxJZtK$> zS^G{aT6|Am#L7raL~M+^JG2aRp@ll@wqWs$4v9t6?(T%hrYJ*E#;i}TJxNhUr;K}s z-amMaMpWSmW4d4tLNKxO!r+=4mJN5NwSs}}=Q0iXz%C=Btv}IAz zU2n|z%>=S~c(u_32aKJ}`c5sFmGPBr5zhZwR!C{h(eB+lIJNo)3>`fnZy=03S!cUv zVl%$7jTCORtWvb`o&R~YRBvP6(BT6`8`o_=wvn7;^VDf&NHsxsiyGO+DIery>unr0 zTAZtB)8wx|dvs#U>DWr0`c}V1rDzB1?f;|m^T`LHFCnbbD(3V%{%T;(SJta@ZLQT? zduZHlydKSR8Ci%38%h&v8g{p+p(Dcy?=h-1oq>3AlsJmP4Au+dmlvzZ%w3 z+5EaJ!d4uXXMSN)FUW3>I}=wr+%TEnHK|_!;OOgU1E2w_VIH`t3d?oo7P+!+$m8xC z1`zdx=QziT!*ht%&*)X@WE(Hu94@Qtk&f%m){)@#xq+ryGroVo<~jm;a z>mu&EiY)0OOR#F?$6h=e4ez$o+cvu{L)>SIEZs$x9#t#f_qG$@o;$tmv+J_N{T(7p zFOlWWs+DWJS|hU&=`_2px5&~*WVuUZ>07n(Q7_t#2i?8Xo19&DkGQ{AWXTa(`cwZNeXhtdP-GcYwX&_k=2fjUsnNBoqXu7pd7 zpMt>Y7Z8p15~9&w+(JfhpEt+=@?$WIx;;<<8^NZB!ZxeMj8irkeBk&yosv0EBmQ*i z|FE7#BmN-#mG)#$e|yq4$)3dTFT38Cglxm$?Tn4=PE<)IVH$XZQr-vh@;Ujn!+MP) z0ZssCGHPpn>Gf~JgRQ}uY^I30~e4ntX3*ape zkDS}B$2p)b9Ecc^)`DVx%*rqQ+ct4nlH6<1o&B<*IKbU>+p<}a$PeGkEy9g4g1Xy2 zl+syvMqYs&^0IxSO~4=?H34zB;1kt9&SJAA!^Io~APIi6YZ+Yk$4pSA$SjN9raD&2 z(AD7)1sw5dIU?R=r+MB@|g6KjrRl*q#WgKQ4!<%UBDwH8{Y;T5_ z`>mGjYtGqC>PD(Inh*)n#0uCAkT};TS0}&nv(bu_UIT~N>W~=NEPELL5Z)lrbqmsv zp_#iE=lY&h6OS>-=0}swry`Px2O8K;QlxA$9bBp)=pe$JH<;C?_0E`UV zJQ)$}Fbl;#3jDA**Kdw*v5RfOTs?%PFrJgu2!8sol>(dB81zSQmE(t>=R7Q#R|1n9 zI0)85*Mq6P-C(F@Y4uhue7%Swls|0oTYUHjzaMQn6X{RDd4TG3CSY!*BV@}*vI@bQ z9S*qp^?Q@LOSU(<@S{0uZiQ@1kCD`5>vFRLfzc7R9(wQ0;C0Q!1l*m8+PT)W4`MM3 zePpoqJ+8@0=w}teMw>7-70a9^+s01%FSF*T2~ngiNHe=Aii<#eB2U$d3@J*_WR0#& zviT@)kEKnoLs*mq#Prrgc+P~%C)IYT0@6O&m6YU4#@utKR=P5-b*l76^tcw;S$~AJ zYb8=ePN)&yEdHHipvU7tK5Hgv=rLT6(2^rN6Jxn8mNJ?{*GsNVlG(fon7HbOfdS1`xe_Yzfm7#D-OQvMW z{ElL^qi1Dkbymb(zZ6vgQKxIS;5#nL!HcE{>{Tx+p4~{jh~Qt1!HZJ#?j*@K=VI(e z2M|p(>cC#r@4miw-%86~*FMir_NZ4#y)NQJBJhKF1mJFn-{jp1>Qx8owcC|Y59)Ps zi+Tyvs~*(r@)lHwMRcRKkMgWB6g3!zKtc01H9>z5yU(LVH8>INcRHu|S{qO4j3cdTkPggSanD_Fby!>eZ(qH0lg_aR?ji3e_ z^5hY6#0OEgkOwOkJAAM#GSB8=1UDy-UCa_O$jDwe66tdcdKzQQ#|Zyes$$Gx-Wl`B{UCQ)vb-HMz)YMQBhFg2_W@ zG@(8XY9VLB(4?jSq465jN+|WrOzj92YGyD3C6v;9QwpKN`v|l!2u&lj3!%CkUr7Eb z$2VmX+C!^X=x$~a+KW(O`gy>svW7l{_9gTlEw4BU$RV^pp}88=&AfvM9YUxuz=3rr zp?QEt4u`nk0{s!;%VebBvqnd}i6i_ejZI@KU9+z}_)?~NZPY{DF1Lm3KckT*pzX#V zXx;=LEV(+t*aRP(cXc8*!B_8-CLJukIvJbb#@C{;3GS@5Mm#s4&z+(_w>^&rr-|oI z_xCtBwGn1J{yW9@tW{mYN)@xzT)mWVB2pqs&DTpU^!FB}7OxVjd-^**&Qee6r6T#P zr$niz^-`t&yF{sHR_Ws)TDXj*p3_SuvD9)=YK2~EmA{`T_2McK*hha>8B4vSml94( z)`(K;^it*i0ix8#Ro%kg>lK!IRWFsyXKfOteyEpv-9J#2dSg{aSgFk{^`>4bg{9sS zrQX&{ZSxNnrQTWjaagH$S?WE#R4Pkt7o~RUrFQ#=ic))5z8+Spf~5}Vr8=@yr6~0a zz0?Q(VWQNpR|?ZAy*EB&so&_OI|d@ z32oA#VoiTR=wA;?6C1Bzy)FqS8eG@X6uKcX!FNXR>UEQ3mh~J~J%?S-VK;I(jT|mL zheOX1VdU@_IimF(PCbXu$PsJg2pkE_#Na5w8;FncGlQdirmrfU zDpG~0Sq`&rW*8R6!cprT?(iR&Np4wao zi0tqtuw#w9H$ogjg7N0Z^A2;50QJyt3C03!Oof^U!uBna3;*Zm8EtvG(q)FDEI9nn z!yg!N?U902c+du$G7`H^+4s`aS<~MB3pgd|(nR;ey+$WOT>sKk)At_x1!0L&m*Ffy zrfSHrlBWQYSAFt`37braIGkU|7Wi0B&^$Rn6H!(JQ4$=pf!q1On;=Gy{b(;uGykOC8$y24gG?Ugw;NleHGpr9Q;pNODtspH%+?E#-z)A zOqJGsOPh2F*78C_0zrY>Z=u|X-J`iw8e)u@v4t_=6**?FW6UJp>)OX_)`mtVfRUMY z&*p#c%>$%aqqAQ4_KZHmpTxG2qjE&4rhw|zJ)KG;kX$&oxM*?V+!0e|;p}Gts)O+B zxqf3_2cruG;}Y?x^q2E}_$j;c$aG6eKbgbj8#YxyH-GVniNb<3RK{L!-dNI?JcK}s zNn3i=Z*$s-k0IuXhPRWxxM(%xv6hDnFv9{QODLosl7Y+60)4JFpc3P~DS;b5{^`d! z3t`+puwd#;k*~X4mLHGjg_NT>9mUMGF0sQ-q&dc>p&`YZ&TpBJ!(aqxQU zZ*67PI}|x#9k!g>+pQl^Hb5DoDNf<25B*NLj24!dL2^Y?E0<0%eIX-8b2egWJO;1a zVy1o|#~hKp3ASv}2oTU9 zOZ!!GMuUkQm$@nlsuu|TjsS#E)OEt5$8}(dC4%7qte$;Z8TeqcMQ7JX_Pk>CA@HTY zB{&vA1L7uKRj#xNDvq|5<@aiJINPI+Yt|%Wc0%cqbtGrkC!&s2*aM}t1b0FmU06rD zP_%M&6RpVvhvyawnLRQZdhY*rLubY$jJUJh&;Qh9bVI2mvQKtH?;ed2eMRnIgXbO* zKOjd_+3HV~ty~c+V33FvFvJk{U{Vqf@ncNN{8Clwzfzl&Io9&I_i4$JB{4^0Yz#{t z;$&n>YAIxo`3E3>l*ef*Tsh|SO^BgkVjd6!)(eCS84zx?No>WaC=NS(Ghqq7xc${9 zDI?a<>zKk~tiYmqbAcZ*IYUNeNwxv2mFK-I&}Yu>EnHpm3m;unxL_fq(uj1{ueg|7 zTg<_cn5yA;^ozedf&gI%fW4}lm>i{WnG5rpGqU&hCf6C^4cGahHg76rH4PIg!SRs2 zRB1aFuVmoSmaDmPDLlghxhYpoLRrpaCR(*7!JAF+n%P;o#Nz4Bs=9KJHjN)=Y3sfmKe$ZXE~yJAj*2mfd9g%j<#l?;4Vw$aF>90 zLVR)jz(13z%2nKC2K`Yi>^2*X=V$}xApHTvsu~b?#G6TQV}+#>g?|n&#L%n>%%9AE z&X|KsRb2}0Ke*I|2t1Qf&(*kADbE82<>~m8D*Y9GR>nBAlrXYC5SQM)Nf^T@&Q0jB zQovrC%Fb-VbZ`rZjxI3kN!p@4gGp`JqRIK1VIE3Iq$xzC-5L+13L}*{W9N;0959>_ zxBGm0kcYkV7Zn%I*JC6T-te=bgNa)OT*On>^7sF7#uVnCS}=EU!2&IT`QKi1&oPRA zG`Lm~U0=QWtqqS-rGMJ|g2#p+%*&Hd)GsI=&8JCBpZnA6tMMKp{lUWd3yTmvMoR_h zcn0T9##MoPUa#vHANuqwFFnad7R@bMC<=jC>hZ9bFLFt<;E$_}^yx41n*4X!Ii-2| zeUTs5Hi{*hT{3Is{*9z9;x&UaC1s1O{8TvPdTG$}dnwIDjGTh`bk)Xk$9&2keJpq2 z^Iy$ox#S)L_$Mg^BEsV6p>O?!^mcktaWTs;u}{=4gnh=K4SPg1dr6P3EeYG9#7Q6mTCqRh`$3T(g?xR&!qgprxc2# zk6Ozhl@oK$98KnCf&X4vZsa8VXvH+sW|>wgZUm)p?UwyHY8KQw>lFli-Yon3;@3Rq z1;vsB4iTW`vOgD~$DFU@$P5|0X_idKL*N!86Vxo+e%rN*3H2tU07|7WxKp-$m2Ani z*SjJUEH-<&2|5YRYQ>ym>Eup>(9M!*^V*>>7H}q-%UQ#AIZ{G%BGYW0P&)wIl*mkX zTFgVv({A@qWcwzcUiMszvSkP(#X}hLhk7Mi`|86g`m+n=ovS>avvLVKg#)jBk|YVi zpk&i-uW9F`ix=w5h!>?;_jt{_CSAIC!47<5aqe;La_x+@Y_;z2D_d>b!M zmzplrB?p_LQupxTI=qtxPYX6hcVVe?ua{5p`q&t6Opm76ZeD+nra-1Qu18aR58w3& z(;l2Lw&`;ztI~Cl6&-NxL*TIbJNBqo=UwWxv4}sivm5W+t6rUZx%Wo%%7=^=_0d|y zCtj}WD_X+FX}|9=T96ZLO6b1_$^`YfIBL%|4czHA?a(^OJV>q2bq?9LqC^_%LQJkc z4flzs4%=A3ANOZB=2hUSSBB%MBh-c=&JlZD#g|6n*PTWiMu|3vKCzGCldsf`ZPAh| zbq^XX8K*Yn%H#L1m?=Hd%6|2%Pe(YLpK(dP4K7LftxwLgmOnZ{p9_LJp?;;ESE5D) z5h869whZcvdq``{CNtKj1poaN1%Wrzw^g2;UXm_Mv042+Tv#n z@Q0EbqjZi@f-$Wkg1;zAZ<8|0gqX63$_BikbdMpLd#>{rkBFy&3z~pTKh-=6^#gY;keh&r~#-yZ5flO2Oy7tnU& zA(sW}Smwcr#oviK{AQqzSnI7($4wbpM;$k1y8(4D9f$+SDhzdKz)rjPd=38hq7GDW z1M2wUO{k;HShhOq*u|Dy)=>wVr~SSGbpVUy2AkRr+T%76f!K#=?Hn3xYBy|8Qv2aR z(1{~Qj~;BgA4s|B!kA!F(gSi zfrfLYW;ad|IA@xHa|(%bribI485+)EciM|YldsIuaE|c`SBedsGnY80ggEEXW={LW zGdfDaV*lJgJ4b`Kv7U9&(Kj& zypDo85(U9Y#&q%Noud>mej^!|{!g z$N&Owz+HJ0DQB|Ifh&Pwnna1mn3f&~_i z&a%K|9X4*Nc{46%0ywA(yd4=c%`w@WPta@J`KK3PjhY{a%UUc1h^c z?Dxw2L5CtHGo1NcvmT^>I^H2TXk)n@)*Y^mk=QLo0M_Km^|=qKR|$q=RDcy9!LhkM z?*Xs`=HkY{Mim&sQhYUT!#YCL##9Rd>eW;$pI+g$(glA!WCgYa*d_0q44>?c?X)C2 zljuU;)?vR_5lL55v?Sr)-!ODB?enVHja~M#>}8(kpjsEp25Hxl!LrD3wY!#~hu^lp zpdrg|$NyffZOIIYL+#I8(rtj=-^K)=@qei#p`je^F?f87H|K)1;Xc1vL)}SjQ@E=;43$;8g4zO|7nfqmgB3 zq-nPc+htFg5Y*u*k`<3zEp~~`JbtZlv6H4ylwH= zBF$Kbl6jkqf0c@w7z( zVS^VOv#UN`O>6^oLkqtmHc_bT`7LB#;e)hHvc+EkgW*Y4JgH_+s>PFP<lynD%XOPmKwl_u0O;rJ{gsQP)8cM%4#4U<&V70btD8KxU=Eu%lm^i22dpQ~NCW1g z{33J?BFF0IAkllPk+l}X|w`$l2iYx!`L86bP8s2KneMv zs$@5+LI~%;v2YeSBMa!HJGNXkC|vPoFohBVl) z#}{Y$0XJCUsg#RChBv=Y;iGApokiJqq`TX5tu35iFn59OB%AMT9dz^|zV`yiN(<9K zQl(MfW1T%>+gNW|XZOw$JcjX{tr$lcJy4t@!X}P?uv*yrOqn+y{D9`9h>w}IJ>cSw zKDl>h_nw0WioCINzY{COzw_p!ty*4XX)JqmapJCD5Ti^LNn=F(11$rD*5Z}hK3U4< zY0nu^QZTh>;S&$dES!7)yu}=td`>CW^utg7Z47JDGBTcc;h=)4kSYKsRuWtNYU;;# zaOLZdqwTtsp9ugH*Bw~;Y;!ZmEPSH4P%!)Jj!fFip3_`cBQHkp_&bjr=6*qoPFFaM z&tr3jh@t(}@*lIGjKt9lNsI%b!EEO}F&%^xC^0@-0KEa?9T zr5kNYn+BI<_i~0hFa?s%dSd$W9T<6x>SKtSsi3v5U2jIG+Y4n$)B~H9(xn zq(RciJ&9te!U3l3;1d=H4obaNctueft~fOewKA$X7tU>zFl5!r301@@Ny>f^Z&?*9 z2dw2GJ= zF*9K$fIr9kvhxV($HJQ+GjD=Sy9qKO45I!Y&kFg1*Yjg}CYr$R(mu4^xC%D(DX^JA zvn=mlYJwfzqy}@n;y0ySg=RUfeecv;Y}l_Uj{VX5pqL)D4*}WKT|&wIf*ibHcCU>L zUa&;&Lh1!7xs%=6>9y{dQg=RTXRyxZ-Q~6KfQj7sm>tmeI3FwS{X4wQ?DK&glP=Vq zr+;*mP+6i4OSCmgv==23MTsO)qJt=rtd(%F1T5p~Y@Lh}okfW*qC{6wB3+cAh992c zW{C`z=x&tgDN1CC5_gCay+jGR14M}kmgvnAcNrz_7A5WxCGHg^azu%K(uxEO`2ZNB z?b*B1vd6X8^ON0bJ=OUy1h05in0X z&VOl3#G9P?eXZqlhG395gZ{(d7zNj8PZC?%{&okr$V5+na-GevYHWM4I>HapiPuRp z&hMT6A!){fCl&;Y3&7L>f&5efVu}FNh^-^A*pfesXgS66fNsYCw-?MPWCict-J^^Z zfG!VRSU88v#^b8Bx<98J1~tx5qM-LNy70i~pY$QtNG5C%3Qt}j4(nm5!)ku`eExW1 zq$g(v2MouzvP4LgW_`Nh9UVOOA%d?6aBWOhBf*7pgZ&=BH*1*x{9OX40XCqY4r|J* zDc181?ytl84|{&P4jVpRhdmROJXnW4Fh++pT-be9hmAMt`{CYpJw$KmFROg6%t^tL ze;ZdeFga~`^YG&TgDW3}>v2uMRf*r}xYi*33*?=Ki)`=1kheb|zlvY-_CsdVhq5CJ z{^81@<1IS^P>Y7(T8NJ5p{tB1=kLnvt?K$1aR!Nzuc|!L z2QAO7irSQd@0jam8CH8ulykv}wV57M81c&D6e<_4h&R2~Vsk{C`Pzm7UR&|Chz-jT z!4hBp<#pierD(5{UtX`uFP|6MaS<=Yc-{Pp^+xc^?~UYFU{##gR;fSxTIkuZ69lIp z8TR-nKHgjDwTTADXm8;0^9`V;IvFp~5VtD+)^))FaOhRqvaa?#C( zMX_DnvK!sfiFI{`0~yh-t}W_{(%KbVmEd)>QkRH!A#|PfF7D2{deS342jz|ybvd-U z?p)RO)^+s`Yu8<@>u#g2dt0lk->P=Eu4_P8U3A}S8En)ww6(g1t!jVky6z9FYXs{W zWz;pMwYnZydFs}636F7le?82)9x>{g*jinaS8lv@T?Jw7qSIJQp-~rIJ_Df7FTL-J zV4Qu+ahx4i*BsV0Fs*6LcYGE6qwipp608Wi^7x4hxP7_1gRpJxGo=_Cf(~F5m1BZ*ZT#(K~#3Q^~%De-xbWy3I;kGq!AF z<&^c}{9_`Ci)YxB2;`Kv9k7_+m8GdH)r=3DrNDd;KguG%E;?Da4T{FNFcMbSn`-@K zm;_VTNhs;`MdxJUhjHj)xQl0fplI0T{`M_lfo^P7di@O4@b?gou%+2+^1HCGFE5|3 zgx%7QE#x!anH~KOS>^eQ<_3+SSWh`Tn=V)U!eSZ{2}LSr?hSHy%tb}FlC47Ad8&e$ zba@fxn(;B3@+z8AgG>3o|0bQ;m(f!gbG(BJmD#qqV{aOA_6wC3aO&^MaS~KnObJD0 zNN?^{^3I=ucaHfiRSMKncpEr7rS_~8@um=Gvs+j-yXa-Xj~Ts6X};bq>AcmuW$ilU zr~cJ^xX5tkzpQXWADIazVH4^RUBG#xeb5EaYs1D18gfP#G!HYM&5zLq^Dz<~J`#g$ z97ZrXOl#O^TOz=#DbE^&8@Xn?1-5!>An6s^VRJw3t(M!HJB{m<-*&et^@PpUxDLd3 zO%8H4_lmBPhm#U)6ls!#kkM3`HDx!N4|*ZBvK(Auwz>{HX|bj5cU3TlF0;1x!iLc; z>mVVyb_tM42iYmijvz>F=y}!QS?ZKbJ+G?K)Z^Q~)NL|VOlZ1h@r4eKTERP+m>wh1 z5iiRLWrf_)gGpmIBrS-(0K`)Py;N{+Ex2c{b;?ED8WDw3K3#WNmzwfFV4`od$MfpBAODoko;nkJH>w%H zS(cBb3srU?AI)-F^Evr&EEL-(3VEy{3%Kx^6~apx21qWbMJK2T*_Pn%8dW1ZRw@zY zj``%VnkQfqI9<#>QjWowomBG^@G-XTJ z2mp~%1M=gLpi;2}bbQqCF&=YNo2uOXQ`W?fI5UTSsyPQ+8%-zGisI`an?zHj^*lkw z+fvK_ulQ}aK?7piK+*MY9X#@#6HBcA!@GX-BVu(S`GJg%9oug0sb6wbrWMZUOcank zte8qd@~S4QX_FiiQ2lCplv&NT&xCTE_Onfie>P5WjkJhYuu3(p*5+Q)sHSv4RbcBN zlh33LKMX2#&eS>(3nh2FokzT2x%7;}t-TW&6&DRFymYVoSaw4MeDn4!5Qd@u_m4{*(hXCtr@y*9Ly*fKGve5xcQh zeWu5bpUDWzQ&L>;gqVVFFT4B~3xHFLi>A)TH^*co#zb_cA*DK^{M@W`cW_m`^PH_S zkw>?Tmj20vn0E4%8A@+KNjoZcKE|0nxqCO^MV-|augfpv6Vmg919XEV5%=*@hB{hM zq{d^C^x_%u!V1FRLWL6glSWI@M;F~UcHLh{0B&pvg~Div@nrEv3-Ly;hd(>JInoAa z5ogpdGR_vV9Yt8X{Jrtr&Hb5%GnF0%DY&7-76U<_KLG(98&?!qyKa5i>X(!|&^_?{ z@^bz%)~B!C=7tY-D9;8sQ8S0lx{o<7thZsJLXk4pG0c93b`-oRGg;Tj-YhlX9_FgY z{M3ARG|x{&CGlN%f|zpZNN_m8I3*zn!j)h_!+T%M^%PTOjMzaPgkqlgK%CN$f2~om z-%;+x@dBwxeVL;%l3DA&gBS3W$_{NjK7j76 z7!M)!drEKrGT)*b8!yKL$vxe)#CWukmnDVEpCFwm*5NrW_YsLIv57VcwCz`#>9yfY787ag$&rEvzheX}U z^A;^ELChqskt{66c7RR9QP!|&f?R)8(bhB*jN@C7qabAlF-&v&{i#)vmHw_mg#+9g z(r%?Bk+w~ddc<461AEp^;bP{8i@0U$G61nwS=qo#wlRMo{+m8;>LM@&qJi0~^97%$ zD=cX?Rbg!An$OcrGf8)yB}%$A{|08)={l^|LS z^Dv@Jen>pA;KF-UGE{-vwWi|#fyZxN!n_0W;=0h$X@Nki)bDiHtnW!+Gw`q zmYp{Z%{b1cU|+Gm#j`0YXJ=X8YzIvToT1@huV8**0Y~$O&U$&x+7LUw{L0tYPyO3k zBx^g1c4EA)MMt%4YcIAH?J^Ft!#n2M-`O#E?r~hJ%UV9_PQ6=>esA+|&Y{_bPb@0Y zA3x!xd~vIr6tedwSs(FnEg;(f1TjJ^3WIHL{36YCPKiuZ`)U`=LX{MAsVOoMhM`Kp z`hhGx%6=z$&_3d1#~n4h$+RI}yMJVMld>T~yPr54);E^u)Q;NE0kfMd{MjkQX+!(b zSRKYh?9<*N&F_Ghls(PeYpDz*uWx}iv+;bqjK?##FyPkTX4<^RG~sk->r44o7cy+l z%O9y_oOGMo&Fnaa0(9CeOox5t0a-{$F5C` z)r39$>6q0Vp5z4uiwl_#O#ENV9nMaeNWm(U zScXf15+s~rJ{fuM4sI2|eU(g^hq!Cq9-CjZuy8CMo|c%_48>ZTCV-v0c1rBjIkEFy zsA8#`uFLPX;}{xy8FEKSzHl$Xu$p5V=J$#E7TX1$uV^aXpRl(d`oUbn<`>R^`3qii zs2x^&Q}Zw>==QQNF6woLoHU{Pp2am2XfX!GzK?r~{x=0BO+VwOa2K=1GzcJh)5^}K zRc3dt^8>{_()@wRQ5=mkhIme_Q?HHfHxRk5AddScVoMyXBZcLWua zIZ*2!KD*YsF%4S~TcCDgEi68;^$VQ!ngeI1oIRrk&IZp~+)O!(jdEDA5wmNf!iq)f z#jK*3PZWz4#R8((T|vbbzutJ}nAx=nVTI{qkk7P>!tF)jBvCk76zKM_~2bv3q1_gy5 z3M)KLFB~Zfj~9g}h{BU1-qeR8YIbe@1_TgZM_{GXV)%eJ9gaMjvXzke@3)p8LE%d+VR|W^?df{%i}(; z-QFVO^P(LqwTx`X=P$Hq$9J`Mtk&A`vfhqPvuoF)9rCW5+F{w%qWX2B9p%mav2mw* zA-nFCxVqY%Ei!&zwBrZO{ZaQ?i+21-YsZhZcKk$dN80S#Eo{f0o7=IcMfGorcD&Wx zA8+qgFJ)ic7I(3BcZ-Za6Ycnq=Ki?&^A_!RUu(w>tsT4cc3{5lc{xRGTAK>2))82( zlU68xuNce<#H3)oZ4QW^mGtD__=^t+O89l4Hac)7?)u57z!_k+4+Awqd!$AV*0lZ2 z`UitG?G6QMn4{)e+_ic{8UEeunvXX0@edEwOsT2=!(Lduw-1~Mo=E&Zfg1JlK>3e> z@1E&I~SK0!nT!GV`p9D^-XV!O& zyIvDK+u_&`+6GQcJaP8KrkKESXkr|fSGocy|Bh+H+;6Soz_L?v^0G_pZAYtfeS4*m0*0t$KZ^A1Fm?|ByH=l4bF4kx};~Y zVC(xc?ZB&pX35~8iibThI!9V9R6tBtUc06FdHrhZRQ?gMq`h4bd-6-+5CmIf%JY}&~gn;3dt!b_pUHU=1=V#@LU z4^#hZ=o~1eBn~PnE=(NzL`k8f6r%jvd{NqWzjjFVsEu6tJC8oLT0x0fB{Rgnp=sXC zx0!8LN#P9TS@X-v6Yzr=XMI@Z+fmRto-NRoim@vRNf^=e(NPOGaFYN7^g>#W8}fmi zwfWjMNIxF?FBii{qQ2Gg+We0(ecDja2lX*d6bSmdns&p|8bW&SLi-`;jR>#DB3fKu zSSpC<<*;GUZt{$q9OEXGbaUnqB$QdZiA;$ur)nkv?a0)$@KoWt4nA<1n$Fa2 z;i)31d8Id!sXdsQ6`m@Bn^$^0OwDF$pYT)>47t*HRrq%{#A`QV!fDeiQjbS1kX-oIP33-=lgt7P z#TC|4PH;CMYeGS%OILO^9ggfz zvnRu(EtL9cy=XM-UR1w^yKuHpKJ?cvrpF@_Qa_TDwwa>-&Fo&L#+cppePRMGy0a3Y zN;xXp2M4=`CzXRsOeWWU9^F>JfDhJuO3wzfMf+{xZ(Gj>tAnn-RmjwB?n(;Oh0+gQ zvpAxvmO3$0D9mJ}QthR}3}}hwdnL|9gmOs=rw958;Turh7%tv5VV#I@%4vm&;DJ)f zN}A^^#cxfrW*F8$yn**N4E)Nz=%Pr@nf42Lngg!pcU+2&qVe3V<*Qaae4)|Nd@x2$ zp8>o#( z7T^xB0Y&p6Mq-NAuJOOg`Anl+Ft2-BZJs z)S!j(_W$f^nh8o*W>C6w)P&3gIWr9Ov>Q@70U^z5nMAsF3rHxlb^}T`4l8hxoQOv^ z&kI4{sL{})$<%_rDWGpqYp}GSZ&qS-(l^B^Qf*qQPT%ZIC4Eys-%wUh)#;m{exz>- z=v#BDPTvIeBYjgq-6-%j)|{%-H$nYK-xScd=2V@&3F=4srhvXRr|R@g zP(RW)1@x^sRi|%)`jNgVpl{8oI(-w=kMvCeeQQqb?>1@l4Wf}7=o`9Td<=&9W30C) zpZHqn8|G7JjppDX$_w$@as-;bVLd9(#)p=k@_am3nzATfSskyejaN3rE8mY-UPF#} zWlOyB)A$=E?e5cGI%z`_@|#Kf|FTJ|-0q}3f(ct%`n6Bmew?(c|JF(S!fj33&CAli z4AX`tEq4%K2d16y4W@0zhyP4WtKROUjRrgX)_1=4N!y>3cJ1FWX@P9N3e&!|>2I90 zA(R`M#y3yw5ab*7AZ&B|uVoL+w>xQlu!Dc+#jkzR4&bES@b^txa0p+@9=x^t@13-v z2^pHAH&5eGo^N+9xeVKbU%dBTozVAjH<*ny0Ne{0~ps z&|D4Ogr@N~llFfN(^_wL(zaV?k@mjwwNKiCoV2g~Ba;@$_7)ql%KEQw(uU@0=;oVA z`@c46ZMQpV6QNyH`J=CW(hkC;E&B)Xti~U-CObG>7W~_`VIht-G#zh7uOUb%k9PBo z!0TVf9N2Gn%656(BK`W!uYJl6#*`I~nVzRNlG_t^g|h}Bmi8ub*O|BenN#)#bo!Q{3QI z9tbE82lVJTFhdjyC2LM{w)S-{lOxl8@Kv4e*-pnmL)vc^W;%}lmR*NUA_7z?4~x$HT6Mb7K7QbEE|Ka;yuZOf`cq#4r2u(* zUOvv}B*hV_6uFc+E+q;#vGifMD3%`7=2(=?Je%AYHN2Dx=|S;90k>88xfL4hN_T}u zbQdj3j7>>J;8d$pWmVQ%m7!Jz+V8Jfl`9s;jV`n*h%Eg_8>6h=R(w?+S)Li@so9JE zmk(XSa8WS_URQ+YP=qOW3V*;Q2vNEaBPYIv%}*JQ|0ChX)Co0X+5j=Ofw@$U^tm4Q z=Xy572F?XD@D%6=gx#gPU>qm@Asf7p(j$wA*U7pWx0BxtqRMXuK@Ep9f+~MACTcjG z;Zwu&8|93AB2FlxHAtQZOo`~+<_RXI3{-w39P{A>UAPE?rII$2Og!+k@&FLw(={6= zr&RmJc|YE!D+cU_gYMgTWJQz?=&-zoc}wtA{!eOqw?ndZ^*4-x`A-@&1^tu~o5gTmASE$44?0cK|;kVjWxKst_1E9zRsSWow%%gzHYg51W4k z)ChFXjcxgsDSV%fe4jM!E!z>=h0t_DMI^~eZ+k*B3GG3sP<^iSCKB3<&}>5cXf-4e z+LzFK2+h%;9SH4DXfC0HG-xuRLkPW(&^!&ALg;WpM-n<(gQgNXme279#!6efQAt?M zA5FN1fC>x*^amRe5YSLtfn@FwNM?zZNT$1$NalWlWE?gk8Hd0#f3OnIocw$6%%NeP z56g&W4!_>5C7#J?70>(~2xZm34xv=ao}_;zKJh02HJG>Bi5eVOtUwJec`MllpaxY( z4bem$K7l%Xeh&}_Py^5gP(y%7BVHhlcz-OBMw~zzaj|Y9jR=7>B4V9H8md4Vs^7Xb zK-3TyLJhh;M?9AV^*JmFTqwF8M;k(^$6@I}sII?}Kq&P$ENO)5dK&_{@L8K%%sA0N54KqVJVy)nY zF2Ax!eLvE0!&qzif>|1BXn_~Ry1!jL$QJo-!U=h(p3L1dv3nn;9$@mqc_owKl61!0 zIj}H98koB)++Dw_;K#VU{4^qqBp*Xf!mJK9bqEj$6oA-=qf{$g?kqLYg(Nhg{<>|( zpQt%CWB$BFCG@UGmm+ZD$>4X&Ylv%gLyWuih;gTE@i7Q!DT?Le`Vp=nu$d#*`5vzS z#5EIK5ETj=aNP}*5x_H;J`(qZxU!*r#dNuRm}gCbz$w4$29-p?OMZx;D9%+T#pm$E zrQ325J!&4bibZTZVRLsxMtr9Z?fXjBbFjs8!6YknHYGp`Yw2v>}V;WY6= zorw|R%)7wXUa)S=c4ai#kYsY~@+v#TVm7}YJZDk2dzJSleSYpU_x8zfHTij@sK6S< z3ZQ=Is(hy@#!v5#al4{-#9jX^Y6lY91ZstWLaiLEwYOcL8LV}*hY7=uxZ1|J#!JCV z7FbJp6&jsfI+MIxy*B4!>PB1K#cS%d757RVVM4QgrR9CscF#}VQ_s`tpf*_N>;$U= zSkKgThSk9tSRI_9)j_Q*;d59W)NWBfgVn+3usS%m<*Ba8YWh1(-7?v#9$~G@3U5`f z^;u{Y458Snt8rKBgY~99TB|14)}Oi7H%YyRwim9PKrL)^uJ&IsK+0{sWdrq=4gRW@ ziLP@GW!DW$int$PS|Ug6!gF>m@r>#W+nk=~qeiP2iZC)`_lvyI5AK40&xUd0hj%;+ zP6+FN7&dY431=`j)BQKGuFF5s);6OyzctNUodk~r^=x=a;SMH7!?k8!X0;a8(1(1kkK= zT%1rD4Pd#_k68xp&*9nx_nu6LQ&}kv*J1n+e$INs;p83icw~`ZY2=y~hK<;cBsdWa zAzQr@A(f(1Y+g0Z^_FZ!EQOTFEcgLlBP&^|Cq{~R1q)yGv`u&9B5Y$8|Et5?n`EX% zjpcu}t8HrJ2eNuvan+lJMWnzAXNVPqfh7ztCFddiA#lb3xc(4?b(OGVjgcaU;2Mj% z+StZKZkFL(2j0^Wws^g#Og$b|V>|0HN(`xNNSWn@oQhL{P zhjG=J-Zeb}HIvbdnx3$2LL1aO#B1NF;mY235$3u(a5_40GVc2EsK80|$-RMN0$(1J zgU8(c*8ezoEMh?57;_v0!mL9d9h-8jao`ToImguV1LeUP$A+*K%6;2i@1tuz&l6qq z`Te46J|Dg@I#_2O5jciU`E=5;x=%;Fum84bzsI03GgRZ9k4~ztJNjUtS^z%9JAE?g z)4ET_^{IX!``GxvG1w*^d*nU!+T71307-vFB>i(~BJ9IX1ZrR(b^^9vun#+~o+PF| z7CaR>3HY#jV)dzf;KNUWsXryAKITfO0j55-MLiBoT?0&gV#`xgl08%3X)2r{`lP63 zpUe*LlR4|Rpik!MeL{4t^~u#oY1?K(BVpCHAmi9V(I<=dxOSsYEN9sRb*5{L@y{|nN zII3Rkee^qlqmz%-e|mIzl4k{atvYHIrp>YH`WIL1kjlQKUsmh=^72>pi#Bnh*NTa= zKFPa*%fR=&z-d14)RLI*cMia)uII_vSJl%+SW%lmE!c(r9|lfi1vI>_{S3SzD%jj& z9=`ddG{be&A~n`&G^5m7{`6bGjQj|iA)6BIK2I`ehh17~N@UDok9KdD`h%W;hJaQ$ zafy7i#gn3u=NFGi<_{4Jis`td4kM-RzhGY2R zAT_QQq$ZoBCTbihj5phzqf?rdvaMdW{n?~`Pj;=bG*cVFg9>VM0}ooJ@u0U&KPI>X z?!J7anj^^3w`?EDwx5{PyIj?Zpd){ZER(lK-6kD*9Npr%TWQ2s3o-(R3}l4X{W=+` z1sQ=qfO9|xwZP0a;X~4~T_A;8^>b2@TCj=1S|#Fr*Ly$;=QJ8}PNN~`qBjOm45r_~ zv!EfhK$pTI+y5S*afE_|)B-`A(J2L187PGyDx@`ufzx`z$>2#Oz%?z*Xo37wU^ko5 zloqH7)|k5lP6B*l(#e_=>9C{yEc;}qz)7Tb69g#}1j(rpq#k`vcIE{f-SHti zz;>U%4;cMCF?v5~*sU#oBlCc{Xz@E7nCnX|exDkd>>2e=)0hWD`yOi9zK6rx_sIHP zXx~J=eL6;mwer=;8b%j($^{uUQ`m*dG##T)*D(4F4WrK#7`+HDWnl-_AD>N(K1X}W zV13klVrR!f{I53`1wO?ffU?zp@_67=^9U_cg@v81%q8u^(wD*M7!r6cxPEVwSub+VY2)g&E?& zk_%9syRM=5$F1d0ZNeJkr*#xBC7E-UBdrvdj+%|_LFKK`x;@F_LpkKXP1HUDkEd9` z`rls+;O;uPxNthcHihH@MC)H77Jx6p*2Dr|v0u0WiRa_F|F8srltBKdG_y4cK&FrY zEO3>DNdT(J(~C|11#kS1BB6o*xKY5ONi07mM|_w8?Yd<45|ggY&oF*9uaa`|o{SH_S_t?mBlDPXUC zS{s}YNbt8cr&$^xmNp}(Exkz^@Fl{%bvN^8hA4nr`*2YJ{T(u1+DTk9AqrsnUvM7{ zQNSjM0+=p$2Y(in>5fa@_68TX*!kXuC_v3>76fDpL4e@SAPB%JryyXY&Yn$l=Ls zxAPWm^zGV4AD&MH`-Xjg@E8(88~P5}C&2NbQK=%OQ*%n^z;S@PlC!V`v3FFtpRWEi zqtEe_-Y2>TPN=7QpXd=dF&RAI2}%Ia2Gki% zfND8d9no+78^P+x0fB1fs0QzL9_(%Pl~c#&&Q<^$@l~xldOK9pUHd%-g_%L$@Sp$WLGVKa9K}0ha!MaCw_X8B7mb10UV_W zpxTvi0wRFwE$T6d08T&za2z6l1Mf6VEo3*jAqEKVlA`b~nZ5pXbjcjOOB&)DE^A%V zFmI2TF4gL#f%2mn)q>?y7VdWK!eqI)Sg?y19~122#mCVlmn}~iU2^&8w{`_D+m_%r zx;C(i~xnOuGO=qJwwuSPA0 z1gQGz+uuL z6aJQ4_rH1Df0IZ+1Q8ZqvcFoiJPJ+VmjCT%{$oY>mjCe^c*EP=@_z|2xzgqN;H$NdA7I^dfv|2JEH%~RPoTmCS2Xu!tU!2d67`MV@orP8h% zdjG#<%Ric^9h!zO4*%s0;gMxg$z;T`6(@xi*eYamCvErvTk57G-8m*6^sksC^@dzk znlc{&Lkz>?R^U3l%QRRk^ZNgUU8S9S4F}P3r7M_l7yKiMUrHkcv={lC&Q&AU*r(<0o3A zn0;{lbTt;djOC!(G*Av?G{#kieziHkTZFO5Xh_IvyxLAnxR%w}l;|IIu%NMne+2%g z_(vX`(%97T%cMWmm*chj-InlsQ`|l~GKU6D&34@Byntcb zd>g}tjYyMq!&71;WLx+cIm8&LBE{t97)7G<6){Fp8I92#Bkh-uW29h=2AX0r8e%a_ z0VB;6e?Y}xU2P)!REAWG#0xALr8dETw8b?b`Pufe6{=xJ}h z{QD>>;&7P9&f`F%ravTTOH|nq$3;K+>@Mc4sXx*kL6d|6 z3%nsGs#(yR=?NFjScM-#1m^I=ld$+Im*ZZM-LK1W*yd)bW=Ensu|2#T@ixVs7dVX*u;IodK~ghyi#wfN|Md^WwKTTn-u#V zuX)#`OHCJ?dnRA0Z@T3253iVVr75CPa-VP_gC**&E1rY;53BF4E8YXaCR=R95__QY zUbV63Wk;NPwdZB0h*`MXtL~a|@k0CE!HbrpJrgfoOqK?oaJG{Mo7(og>`JA6n4YCG zvRJz^OORR8kfl@4%kGRxSDG$&XTE*j2u6tNiH1hrQNic$oph!CGQ+qc&BL_bOuMT& z?QShCifQ*UtzU&p6cX_P_j#k4Fpvp@^@O49fC{e}xtcByE6)^fhM&>si>Aw$L@dC4 zUN6gyKsn#23UAC_Z!CN2@})7NPyG*6pj}N5i64Q7#g9kCkGP5K?a5en@hID-phCSi zP@cLMu3ts*X%()FE7SMu$q6%AvSF5gR7Js+V*d#IpX(o4;i3b!k}o|Z&A!trJ=jmf zq2sLO3+H3p`LTHNHTQcSNtI$pA~1+mXy=#8b_Whc1Eu1SQ&+sv?%`lm+aYP+LnJ$- z>y(_|fO8)}^~#bsE@Y^}ZO&gqW!mL=`8k9`)4sb?i%SKS*oiKv{VRnoYWPQ~N(q(n zHQjya`$NAriqdi}N2umQ?-n}xU#g%_s@gcrQ-73ev+=nerscAl;`RVB!+YRv*`H{i zMg@E|-R5)nfpRObVj{EjpN}|em(Bbz+wLv_cNFQUfdQ@olx1Q`e zBHLgG;LeO1 z4NlDLA|+7XC&@ugS7!osWYzGh+ld^BRn*sqhOE*;Z-1q(PAXiGbzq$-Mx7nYvu{~v zny9l&6%>7`wrl2h$QD4ngh$i6uYxcvQ`7wwp2(cCNR8I??}Z+JFaO9Y_w~vtP1mxC zE-JlVzF!}-#n+cgtoLZp7(#Oh?N4Z~28|_j5TQc|y-$Ps3C$yPIH4moXn@esgpMWj zK@A#5=r}^h6FNbI3f19Bgys`EMS}{};c0|UCv>I;6{^Ft2rVXbt_BsV!zF~yCv>3( z6{^FF34NT%yaJ7#4?+A^)P{ur$i>$(j5u5G0u;4T$5m*`86UJ=qKEOT$cYU@QQt* zO|pYwkURr#iCwV7>2Fn02Fj)qM2iVWG$XA*G@9L**+E2N2}cH+L62LTb^?*mXQ(a8 zSoB^Zk{E!Tej*Z6c-c6y=wXGUP>)|A5*X{)1y->eh(vfgY|A>^8+9fTk=&xrWKm}- z5lMuONPzwHIt8kT?8GI}nTVvThDbb`5rIgJ3Nke#ghB+TQt6H6qV9=Sc<*4k;87~Q zK0c|uRtuL2pvP{ z16p2zSRNwuVL~6#paQW>By=*NB8YIMS0I+DgccGyL(3}=OA(>737w-s1!9>;=%a)# z(4YdbEF$zVLZ1+*^;_UmZl(VFJ7AH9@fs${x0Wwnf+@$3rNY!wgPe%+|FHKh z@KF|5`|oA%mt;4aWRnnJ6S5dCAt8wvLc}ORZc!0Y(Q-*bKrVqqh>8?6QK~4>hI>G? zXwjllMM`Z%gb;#7i!E)brIxRMYH5oW5FzANE&u1t%)9UI5^hRszpvfjZSlmUcOI zLOP`#X{Qxs$D|g_I&m>4VpJ2Y*e!dubl4pDX^7YrwKM+g=kA@fj(?k}ZcfT+Cyi07 zd!?N;MZb;Mlkmmg3aO5rc`|MdH=Bo%JnXq!yF7V~)YWKh2ez4u%yt+sH z*^_B!EZx4Bux-L;XMF2DZ$WKxLSABj+mU!0T9cD6+dqfaK=;P9%XHI4eqNSb)AM^(1rXP_#v zEpw})S{3z71ZiyPj!?EVgxNMGjW}8H5TUpJoqBUtH!xvHv_|75P|2Qcj*EQ2nT~65 zXenk-!6L6a?j>S#yv4MxqHPyeQ#JWWC8(x+@QDn^&J%EHF+0D|ggF*UGU?M1upl0Ck*+V?LG>V(AT* zM0!IvZ&p)rJN8)c_kwHmgW&RvV$SIlQ(e$9eKdw`DT#46a5g4CoXd9+qp(M^V}I%i z+9RPu&y~|B7ncQXt4ss?7c(5ch?Xv#!ipWs>22GG(Opl{R9ss{DTxy@r~~aj#m$dr z>2=^9?v^5lIS!u7>`H2S7h*N;M#cKgw9BLHoY>OQY#EDd9y?lFY>Z_F2eB-IvCPF- z7ImR*jSs=DU*V2!y1sgB=|h64&0abYNOqnZ`wWk*$rwPrK@Vmo#Q8*0dVV&& zGIH=!8kh6*96$4O&{0dS2_ie6+h>r` z%ETQvG(Y53U9rvHCw+4JS|q`44`rYM4aT@Lr%AzS=lZ_1AMN<3Dbk*nx_OeduddMiEuU zo{Lw;1;>fH4!tt|W56bxoG2({S7k3(<6YD%b#i(84!*q+KPgEo|GIZbND{U@?Yip+{wlGYich z8$AZX*l26rL6``_xHJq~+AJxwV}zL?V3uTpr2uB_ ziHoutJ$8PFeX957rXWCXqUg)Eu$o9w-!HvAy~$(GYB691u%cjvSHm_TJ}s@@WiLK-_0S4eR{ zV3(g?TvtI{YlFpg`z9f-qPnlNxF#+!NzM4OE9^~m><)XBLU15ZLQPz!Pzl+hSB1DH zxjGfsnhlpFuG@8SJ#vn?F8Hsw;;uh&ZIs~&lhp6&D<-a`RGGalDz1>|<>%>L&k@%} zyM?%l4(UpZYeTI`TD$a;#r2*LaUH6Ji|baz%Lleymbf8QUq-s50)lcx7C0 zn!M?VUK#(f)8yzi&^AB&wJKveRmSas%DCL^l*6rhHT=hJr<_0)$2m3V(!a-Mby)(4Cafj z%_@Izw1w@%Z_Pk9^tBdSr3f_|mX|UvVuUS#ZksGrY958D&unAGrehG9b_XNBi8QKC zxa(w2Non8F2;(JF;C{xqB|fglX7R#jAH|E?i!ZPz-^m&F64X-JtLN5~rqm4V#h-sq zzKqq%n0tQJJLzyN)l?;J_ncG$d(}ymbBbrrttg%oa7jlxr5eI(`@~`uB9Bl%i6CRx zB#Wpqp~99AzOL7p!Eu0UXDrssEBNAfUZoGIU8FLw;+=%$eyh0knemNwfsMEo)5LZH zxHXxXJAXaDNztcZ)Pf&!W|8yYD7>3Kj9Fq~8K$t9g=o3&nCY*iQ;=N^;U1*ntG z!y_i%7huxMk)1MWMg2KUs`#duWO>abZG8Q*m^7Q1lsabG)bzOBbWFHM`F)k=;}Es~ z3oz(fSEmd*+;9$qO1~)v_1kQc_P%jh3@RrE;rQN;0ZSarGu)ghlfj|j)^#DtktF%`s^lri^2jJE8LBgtWmnOixheCCv3{#-z6 z{FV{w%bhdl!2V$7tf#&Q1I9PQoYbu*sqV*@#hiPE)NEOrHpWBD(fXbsxw!yiNNUL4Q*+!t*ZcZvz8Jzu#XDhD;2!g>hpxe_ zN`lYC@O#X?xAR-fiQiCwb}|8pYc)yB@LestYD*NPOCPdUS&B_{{f^}M2%#M9_}!j3 z6P|li6wj^1J|@f2vhWOTKg=_(sKtaH?S_JBo znnjYF@TtqM#C+LdveSJRW*J$=8l*YBEowF$C+fDd@NO~!EG@1q?l)s@ zISsn%y{rHWmK8A#g_usCQ-I=xq76X>m^#=j6+UHVyUh@YvSme--+(b&p85e&#qp;I zgp#DFZ>j3D2K6`bL)2T%vcq(ft%xc(z^}v=#bkd(P}6;i`e&Ou1+b+8bQ5E$`ngSg z(74)kmz`?-J@SEaBl$o%P*}uvlpf?)E~v%?^;)yq)jGs^v1mV~SEWB~H1jj~jT7;{($eeRt zkS2`i;D`lzu0x`xGnODmrQB3RyrlDG&)euO@HSW*sWGw&%L-)lhsI^}ZoIXt=@O}a z!_Ih)8&&xnRm2x{dCCSkGQ%EiB^%JIoLAZE6uS?8l-3)ZiBLDyQ>0|udT|W4ieVBO zYW>FCn)BluyOR`!Y(NdWqR*=Gs|FaqooP0)lYvl3b&;VxGTL^}H#z()%n7?hbL4JE zdYc>To6I(=C;F@vF49RC6|);>K0CbZG7vij#0MvCkY9DM#JjMBy2hMMWQlj*J>Qwc z6X$0*DDj>=@l@u)B!bfFO^LVn4QQP@Q4!`nW|j;!Xa(Q#p--%v-VdClkCB}HVll8B zmcjY_OcIhksE$82|{4`=)O+WX*)CT{IGCfi3lQ>=4w&d)y9nPMJhIVr5p57tq;2i!;&c(jQ>=Vd>ig-nFX~ zH%{-FZ?UXX+^)z}^Dyi842$Ku2)oaoXCH(UOZr0vJ8g8Q>#&R~M(E(tovvEN6N%GD z_=CersTeKTpU4ast`!OWBThOkQoPZ)c*NV+)gb<`p9RtEEEFAfIu}vkIw*r`1>_fp zoHkO9*gwg%$mEqA^_V5PeypwsH;KdilLq_fI-Cqhl9jj8PAGRb1agv#BHO?Lh2_RZ%<@t zb%Qq3=Bu;N-z5=gZT93klT%q@aLSENWoB#2S~_EhlSiO+`nP!^>M3P74mVx(lla!Z zjsNU~p;yE!5K-mTNciJ+^+6L7&+fE_w6>^pgnIgKbEJQkMe$ef)WWWzuz?73589!H zU0c89_;uIgRnbGXMb*PExVwJBXD5cneZmZ|KatZui5a#!Kkbv)5pka&{*zPjr%tAw zboGflYHuERv=IMVa*mG5{KR!r+9`J$ex4q9bTs~*$vJvU<|+5BPu?Qk<~;l6PnH

M!ZQwzYJlcGccEURr4qT2-IC|m^IB@wZOtb$=9&J8xBp!op_9rjfKZR}fG1zAR z?Pcl^ki)of+bGwJhj8AX$il`WEPf&jgX?~VODKW8_lZ&}?9^?zWkq?li~X|{{1-F{ z(7oB-QcI6HhIB3{KSn8J?W=IUhp7(;&!8}=%*8Dnv}OZ zsr4l%j?YIado9@J>CMXXP~BwyZon<_t9=L#>tvP^f+FNs`%+K@g6P!k$1IeUw|t@yL2lls?gtIm{^w;c78(F&Kb2@jh=q9fKO ziJCt353|ft0XpJUa>eTTE6}3RW3pbD**^R7C5xqqpC*MX_BW_Ut|HgE2L2VNM%POg zqjZA|<9+nrvL3s&la|#^5~iU`!mG zlS%i54%;zj&8j$cxqG3aICh~GC=DK!j`gZpEe0d`Tri_kGg_OfO_IXQO6az;_H041VWwH?iK!%}!mCSZh@;9i7UG02uNM@B>eEBvh<+Tg z;_%!QuR7hU{!vwDd1*$N50>Xr`NSk#z1LpzEX5`~A{R}<{aGB}?wLoHmfu7_;Gb{NH<&M@*D~jmN zeECFs7IM?ZK?=0ZlBGtTLbHBd7Q9OMY9%}aKD_)jd^=gK1i$<-B)dw^9yN0ey>Nf~ zKvjyJ22T%k`WU+Ib#yF{IFp3Czs$~gU6&}14dhr^dN2cjLYE>P)kxW|M0c1aezs0( za?EU3*4n_m;&wIS6=EO6!eDIj7#m4wc6m$MXu$Sy99^|Jj8_u@uSm+3Qu2y^vg{l%eSp3EYe7iRa^b!EHCO!K0L_BwhhDD@}L|qSvD(5;`CYs>Jmn4%fHdu@^4sM zeo8eP_esg8w8RWZ6m_S3K^`noPf^+sXOzD$J&N)({;$+mwoM-lSL%(C8E#CykPu^m z{&pIM!dakizlY0u5xAZiW0h9Wx;MD|OG6skQe;P$^~GC;K_AB@ow8xu+lj1iewGUP zU@HFT^%FRd&X1^*!M7p~lkUM4l2l(tChRim&}b%I0?iX3;bbpo&1th^S&`G`*jn}V*itTeZb#9fh{;Za(s_G0@?4hnjvIdzXqdBzKe{BzBquk zKLh#G8BCLdPE0gC%FK4tTIQ2O#L^%mTbmqwgke)G6+AaMaYs`iiAVyu$ym_SL`lS~ z=^un_N-}y;JtldVN20zc9tYjzF63FLE18IP&uPakS!BFuN+RRM9!SjWZ^C7hlb|^2 zxQc1(EWIf8KFl~VO`nnk4QyIP=1#Xs3A1dhkD#?-I?7ES8WwQVq-8x>9#&bLG|GvQ z4*i{?tqrvyqY)(V4bxfeEz@cbTNdZ3z5Q>VzxK+4Y8ADY-&yT($1VSXwf7QguIApa zU3N<}|TS21e-`#{TE z_uht6ho;~*=Z>Q~v$e@y^67M)6%&Z>%2xQREu))ln71d=XvgQ+vt0W`lZ?-EkLPM` z`%G)OV;?(o7N}qh3vI*kHobv*B-X$hJ+fv=tO!eDk2v?iV!l!!H>?I>1iJM5&p zVYm0@My3TfMsN>Z=o6!c%FgsQIxlL9ZE~tJ+vDI))G)I>er+mq0A`G!2AP1A%!%kb zcO_Z7kv|__!z9c!&m=D?2tuV03L7^z4b=+{Jm0~ESP?rWoIceD$Byl3J3W?nCc@iI z`k*tZJdOUWjXSa^pI0*iqy01vXeUz(y8VS?MKaf}m-)u+Z$ZWI6ni?66$`3SerklxdWA~0!SI)V%I&spl zS0!)r_EC;aOg!KQ29v-;UroyL?AN%szbA-XsFd-ejhd9XcFChrSY5_6G2xEf6cRK8 z*@VlDWD{;mZ{>p3h-->k?Q5UmUARhJxC33dfk})DHtI85qt>!pN_Tl!tcPM_AcBwt zN!mG~`Lq|Fj7;$j9y9Z+)PJLyI5c+~ABr#|;XtPEF? zSoI=rN_}=tf;2X#F=4c&lgRduc*SGKP7n<*?Cn|f_NyuC8kV<#yse1XT#C)3*umuW zOm6hvj5`UCJ?`Y{dMe$Jp>lqD>riS{!*-*sw2feGrEMg&6>OpMJddJ0-_+=FusoME zx8K4ucPq7(0O~eE-QMVNYN&BLV|{R9B}wGO>TVRzG=7K2v3=s{Q=d;9)O;sTqbR6= zCEi;bi(5-CIOx5JcCx98x0TVRx;ax(&-9^XIBvH|*4hjPKN|ELtet2s5vqpMOx9Y3 zWx4N0<|YAyByP3AfZ2%?r#(m(N947Ek@iS6ixM_4QgdOWA|0y_%xTtwN^V{>R;dUy z4?d@XW{w$_Wc0`p17T(4qq2=(1!ynmQ6QR17v}X4`-$3&23=++bDAcPL`NIMG9E4_ z$Rr4E@zy4igos!(P5iCeQ51VnjFVy__aqsjf+sA|s6#oHWG0R#ED3ZZ-z~9OXd;`P z7`xHz#0O$iayW4c5%(lwcAX`5w=sF`%Z}}u`E{M_Aa7=8i*>Cl%&E3X+ZOTa%`nx? zphXjXv}#3l`q=3TRClQL&^g`UyC74aV$Sp`&obXIkLgirH&3HwTh*e<~6T?qT?;6G@_8U!khuJq$Z_VoGl}vzFWF zVVw^-npEwCbY`|2BE7d@P3vY;KHSkr89k34bK-YwQugDVJ#+{^23Yzq>6b~O$*tfy ztoSK{NIUljTajePvoM}2BX55s-c{hwcESYl=Z#49$B|?N-;242z}4(UglKp!ghRqS zdNVsRWY32_fV@50^DPJXbbP)KL>J)_NhKU%Dkhp+TQIF=GjLm-7rmCs*yl)ZcQ(0+ zQ%hn3f$sG7sHP|cMl;66iWN^M$6ucV$8LC1=Nhe$=|fy2!E(v6&tl0BA4m5BqF(PJ z26Zk#)C+_O(82l;_7F25Ksb#0HxQuSPunD=R%gW}3ebDRiuc^-X9cbE!dS7d zu`^bT2P^Kez?sE2-j+6i4fB>=HY;eVtbV{)u@%k6N27vnA*z1It^U#tX23_rhGHAA zv=5;!D#BmZ1T*I!KS04#%jZm?%Wr3wO41PZ`DC;-gQR@hqJF^6Cgvb%>R}37CNGwf;hP?>th7nBt2i%$ zcNnRKgzYfyL;B0fTMTLb0!>VB$Af6 z8?79CnXTsJv<^#3BkiMLYSPi(tUKoIq)%}z$3O*+t&me!GOxUlgTROctt@=uC>AN1 zMkyb@*eqgu%-W*+XFx+xs6lOC%H8j3LIgGc6Kk)y*L+E`Qvb=7 zm6f~K+NAtvE=g8?j#93WHTNXP4pE1IH3G!I>!ZI>HEdB}Bhj&EbXh?DA>FNV#5!GS zoH=k4tjo*BaY#~^7ZMDrNQq{l3RR?`&Z`B&d+Qm=LV>m=29J!L7X(1VfPe@L#ujEH z;M%p&5Y=l3(QNk^$v3Nq=BzO^XZ2>CHpRoKRLAKqSZMAlX=(3DPJ_+6?5#uP?&&Q) zcn{8Ok&@x8BnA7-5_zC%Z##WKxyfE_lV*T>E9KNx0lA?;h`uzV(Pf9r&FT=j`2fXd zo`*(tbdSJukCNg!lgmq|OqyI?Rz3%D5U93gMK4jWG`z6L2MAyCp-bZZ%jfP;ufbos zivW^TIckXNmWLRm#`%^Zt5&oBs#{TwQK~gc?GFQ&s7sT#O;xvF(Y`5qy-n)CmyVrY zq;J}fg4D`53jHP?!blkp~L$vk~QWfT-n1!rNh7$UAydXX=_SvF+;C~KCGl!v7!T0?9_od zGh5Win6p-BE({HjD|sh@-RaFy1c~0c&=}i`F4vRap>S{dsW|H55+Ia`fzWR0r@AB5 zoRoFi+#?X`Pe0WYp_bIF)0W;vEoaoeN!EVIp)F&V$Ic8^GBMY3Wbg7g@vE(Q0Ng}H z^maGkwuW*)H`-=Ha|&k~)HWQlH|1zDVwJX&(N zikNJZjE8!>{sk!25m9Vnv8relr4=*>eaF%V{5^NL>*C;rTq zD*?-M-Jw41+{yAvv|_Q+>^o}ujl`{EwYO_$O_Ff22e!xOI*#hD!pJ3CWd?wHpBZ!} zg!GU6C<#fY*f*e~BnAsD<7|ObB*4K7F6I(&6b5A>9EF{4g_6C&CcW^YUV2g%&?m51 zg9wmTC2Y^?R}}Qgg-Sm{rY$SI^wa?5MQilZSJ=m*^t)w8J>O{(%jbV->GNN*Ngw{; zvP=JKDt$XE{X*KY+k*L-Tv%9?OyS2PmQ%qCWyf8`_>Iru>$n#`+SG+yxMl42(N-Sz z?mn=Rzjy5M^7-PrQLegF8Ve3QPmRVjK0XgNR0jJ@N1Kdw34631Bgg%OTQ1{5R0YcVPimz#6ds{~`+tf9uP;Po6*pk}hv0^4k3Fmh788*B( z_dn5>@{b*3BJW4!8Q2&=hi*=xK0gz^upRv_Hya#)rsm9Pbg*5*T$Y18P98zzgNZyp znEVDJFWBXYWS#hpL_S=Qk7VS9taZC?rd^uRY+XDCUHSPM>$HtF>5kXAxE7e|%EvPC zg5rSeQ(XD1Pw(GoacY}n)QDYC6wYl|*tsn^KzCa{i@Vns19u+WVu+F8zm8`nOo=7o$g`Rin0fx~i5gkc@qC!NW8VRL+?` zOLSyy2Fblf^jvwPW|a`lfnQR`g+68+I1!BwsDgJuT?)L{LD?VZP{@;l*qK|93PQbG z;r&VHZfejCG?jxv@LGdy38JGT`_Fa&db_Z z{Q1YjXI+I$xOSucGBbA0q`*};`Y+J^eSwDdQ; zWs{C%^5+}iNK7Y5Hj*@EO9U+V2`#hE_Fr#_RA58lZ+cu{C}^m;gAWBHr2h+tf^NHQQtqCwG!*=v z`1az3Izw3|QxAQ4#1@C^SKo2Y;_zh1Nbs*+9R3`o&;RLVcZP?k^enuqCLD!P_NOe6obrT-8mzxln(F8%*d>3_&dPu6u<&2QvA{55NNGp)rZpZ0pA3KnqUTNcQp2L(}B?QQ018JHjTI_5v7eh_F$P zy&;oM*Z$De8cnOttAp+q6dR_4UNSH!E}(^W2Wz3pszQ#T9SAQigTy@-T>spa=3d=N z|D3ePW8R%)aP!@Kk3<&ZJ+MBK{j|e-71?<7;u{!!m@>p7uBM<3446{Td{jR|31&%} z4*DeOXyXUGo=69sMUnW>l#T4_po7-U3h-N)8g!<3{R7Cz(ZarkDoV>2%#mn0WLj@yH_axSGV{ z8ZI92Wex%>zk`JBZ(XNve|BPbIr!%`spwZ+K5|TTW!Ix>=yO5&V78c$9d3#eKGpw! zAjdIy+6F!ERG-cH0&7Lqzc>F(@8PmZd4g$N=uH1#+rzbiE3={Aq^`O%r z^|yYQg1)>^>HQXJEqedXR>~_5=%vqNGuOwz(R~8TZ!mZI@Ulz)M=HI{RDp-kMN|g$ z?<-b?b`Zh+`znyh2M_TM0R{$IhfvBo1k$o~%^@9|eF!>)4SM$usC|hHgS^m1k>uKY zh5Gj_)@#6Ld3fhu(xUuJdUn>YccQlF{ac&#+DBKUU;miu>{+I^*nm1C1&Fr0p-o`f z#ujw6Lp8D0ROTy=>tdVD#P(qa9RJg0ID0!)Q-#b}Bv)-dUaoI%1gp%k8!8)i!^DWH zV5djb6y^q@FlQF7a6<$fw^NFMvWo7Yb&0!cp=XLpZ}afM81)rHx&SYY#POMJrY?L7 zv@0!mx-+}hq`)}Z3BQN`9=0Zb;QXdbR(c@c^wD<@^I>|!jFvjHOaBv~>hoMR1Rk%$ z&rsbEdIZxu2GJ?NK50B5wszd186*UAGoP9QY!6^95j_73x8e$74e9ZSHC&Iko9pq~ z%mLO|!5YO*tZ`5vSfSQAYy!e|JRwFYSmF{K+Qee9Fc3pRgnH7ZU4YHu|5Z zQTr{OH0pJ)>Ww;&$>_Ho-{i`EA2>Al*kw2Bzf$R^u+kqyAI~n77B|l=UpYbyrM=wT z-w3dDfHH6>kA9N@HlGv2%4jhGHv9;j!IC#mpAzNaIN3hd@!JcoSu8WP=j3#FZ2-nT z+5IQk(GYS-#nnXfOMQsF;4+9EAqzFPb}-9lTtbc&iRz>UD_!bnz@?5xcpdTh8y&ln z%+U>$><*9gPRZ$0u{b{<$I4rp!Z=9I%xbjmBH9E7nxUSR1> z8v4F$1=H{R8j1a=rj4KRs-k0KWF0^sZTW)btgR@4EA%3R6oFxr_M;$CzhpY=@U1+y z6LrC@ytW4FYPm}Jf8$nO?n!LroZ>vZOs&o*#J9WHI7atWbZV)kUXqa(v*%iltEFMF z^_48Oj{g46D{85As>LQ9Y~@_Jh^5xwz=f3oORarByYs@6wo9?pI{Htw)KY|{mfESM zmZ1|%E#tp|rPdM1sM2Pmlgg_v+fs{rMbIo(KP9=Hz~uI7d3jMcY*ESM3yXSFFpO1i zhmD|34ICP{)^-^(yh1Hp`PHTPQ2UjY;y@M(3oSi*y+h&KDLf_p zb3cr1tyg88vh`vXH1^!KzQcz1$vWjo3!u`|KhFTlnVEIUnOy{LAOn&ja*(^o+y;+} za+~aVgahTR?b^Gi!GE; zhqM9Jqwoq7uRBPMQ_xbmV_HjfV6xJ$`4&D;Z?PQsg2 zXBNKa88_~`NXx>JVMCKs-i>{IK9egstyoCfZ8Sl1P-Bazn;abLqSz>kji%Vx-7r>b ziz8#T-2r2@ws_50Z8tMkYfETwl%459#%f(5`=;&<_O=_*peEv`7QRm?f3euQ`RAY(4py{--ytN;aR65MpAn%Op3f2 zS#phT@VHrq$DqANjcxFFSbMz#?KOH_Lv@VSUhhVGJ>TA4EY7XvL(Z1Uh0E?`8$F`6 zp3hXxCBxMcOpiTeew=TxRmqDbV@AG2o{QK96zD@ukxx+TeVMi1%2h=#;Fp|I$Xz0G zJ4>rf>Y2NLR8dKGOH-%ME+tF+=eGZ`UnG8)S5!_fpPf5*N*TN+Pbrn)K9Mg>v`c*# z|6rW957VWP?r3$6$MWN+vST~fvyqP+*ycPCx(&+~5wMEHA|ljUmySKq2?x~z^KoVl zXv2<_(Lp;>5nQq1V#@+k6fFyINDg{09FjXPiyOh-adzI6Y4bh~g`$tPU3VTMzmtHd zGBfn5#C*0>zNxqORPX9}4oB)=|M%v|N8m<<^RhXD6VHP-YgjF08B--h}o3jlA6$M;>{2IB#?b7upgZ z?6ONr6z3nU8xut?`FsL%57s*svAIRqdK>-)B=siNJsC-r+(?7y*h1zMm}VE4LWowz zr_!IZQ;2W=u%kX~Mp3);D=i?`{r<|%CicbJr7d30o{KCXaoR>& zCPOjafCstm{m{vTIB|$M1n*|8kJD`XE6xTMn_MS{<1M;S1$jNV&~cv~m=aiNShYhJ z!+eA|F&-KsX~yl&Y%zNH;UAN}q>M>#js?UVw{xK-A?WZ=m-JJKIM8G5mUY_Z!-&g| z1SO}R>VZ(ZKkKypsz7M3^i#bNYV4bJ+Src{|70XN$n4w+v-1cxPz->8N9OGExY*&J zTpa#!=k2PF(hmO&zQ7>yB!o8=uI-k~IV;NYiS_sf^cFCN$cKwUV$?u>b5 z$c(ue)ap>C`K_$Itg++h$N%0vmzUci1ja|L>wmja_)L=*m(D-a#K@KI!p>himqsX2kI>R=*eYF_2`VLTO$8 z-jh_QfXA88m2-jSW1s-bW;SE`x9~BL47K9>-8!pp-m9O-$3Pn~=%$M^$REO>!(Vj9 zAo$NF-wTBrg9`tx7}T%3UHUlbvKiD)47&B=4C)oapqeM{>O_JBgNADi8vbv^pn_z( z)SPnJ4C){T{gBNz7oo@4fuF%@^1zPV8EbG8JG8Ph25~nY&hbzsF;N}QOhIluoN{M=JRHG(Jg5j{o7dT{jo&VHOTBC+{T)fFOPNE@ zc$z#NrO8vaaOs&84VU`?NBlIAS`)868&4kjt#nb8<~Y3%5?O-pVl4lt-y^T~Z{S;J zlq4tq(`<CK8gIx@xG!?B&C zHp%AI)++gkJ#aCoWivTZXWmmqf;I7y%ZRmXaCI=3WRgU_4-}kc!Qd|&=!dDvv3pC0 z#fAXIu_J1G{Mi%k?Ff&kXa2X3%W20|*XoF5iTn7=_7m{LeG;Cy zKYQ7G%Z}pHaDs^JT62S1gCv~>FHd(tY)D{?dGOPB{O)Z~YnfAbx>Gw>l%=KkuWDKG^#nOt)aG^!+WZbwm zWzL}F!NG5TV0vlgv|L|*_U2i|bMBcwJJ*-tE55I?oRHIJ-J|6*1exrcJ7@B>$^FtR z?kg#qJ~@5*EZmkcH+{t9^4Zt-tC)QcNYhcqkXNz1&7D4fN^WL0ij+L~w(|1Xz5;gJ z#)Z)RIa-}=lyk+BWpvmFqJ| zx4uqNl0Rzp3`%^_%imHqY6AruFPO);Yw`E5&^(+tP;}3N; z{1alk#(uyS5HowO@)3e$!Gs@29X*C!X{ynIrKX7#K4w>ggYD2p<+Mcag7~z+@Q=q^ z`Fd2uC*-sw@~ch)Qq_se`1~1IB;w*r>Ve7due-=0>w#fWU?kV~`Qxa^wNezMi<~}d zb;7JEv9tR0I@}|I(DITQ*ixlRL9d42BxMt56lYhI=n59o|5z}1(;De117<{A85!b2 zhw1~0lv`gPf=hT<<|&9iSx# z?yeE?I4~l(?y=3l9Z=GS#LhGoGXV0I)a01mZdo3UDVPF;YAtWKFDDmj3iCDMNN;x5 zwFZ7hrZ>B2gCeT#Pl2%L^yU}}^X_!o%bMfYzQ?UrjAXTvfFw9MVwmsJOcG}5^{=<}v zw~Lxj8?qX`*Fp!`cJ(d-!h0mrr`T&|H|MN1x|gU?4c>7GiyXMq-g={wCk<_u2TAPZ z*QRy2uG{Hxze5q%Z=clMaq9CMcv#ds^sA+S|MIY3XKwUn?JRB{u3O2F_|Zo@1hN+L z`{w;zhVxBzvq$o1K8t8ll3k=vfL_5zS2pZZ;;`3YTQ7~1HcN^{dROt)!cid|p{F)4 zHC_6=Sr@xNAhymft-yDcj7uamR#W*xGiDJP8@aoRDPBa&XBmS^g=H*n zX;CI+N9>BQTQAfJsnks29W8~TRvbZ2tv654+VRdY!l3-l9WBhNvpc#TF31((fN<-=w z91-=q^^@_2X2Ye|@3^(#=`(g|HR?CiH@!`~y!XN~*x71>T50(MvpEan4a`-qKh?JpwW5RpuS~L|7=jt zAi!vfH>*iTwZBol9@mr^?%4=*9=`iQS&DBPQmet&hwpIA9MoT$)!&%a-4}UgZ;U^nx4YZ!x zB&%_@?JBgu8CKV{S_4ckMt6y`du)qwxiXwsqbZNIxhYJsj!R>FMj?zz2ob2GYlSgYv&FIJ|*OO?dv>0{LI8 z7;smY6j4PlfrcDrC&3RmFvdgRIwvd&=R|l294~}L!KXkNRe+p>osxy!M#*x6`@qoj zjD`#dlHLMCW-FSdJ#P3gElaYu-)g4w2o4zCr?*+KS?#nn`lPjeWSR{$?5=2atnDs4 zaka~JyUSahYd!YXky6C&vewA87JJ+CvR$r8pi!#P6G5lET$u7il9yYWI=g6+9L=UM zvEIzxliNDl+=rQ3Dc*sMs%H)Jw=1YpiK@4tiX^JO zjH;iY%J2?iRGBrnD=v`3Y@!+tB z1=Vox5Jok!rhCx43W@4wK^09@qZ!p0K{eJplu_ML;|ro1M^twUsu-dg&!{E}sv_?& zMpd%pqadnMqM9nGVu@-RqnaV8%Df{O)$Ao3f~YEpYL1}t5>+Lmx?fPu^NwOv^OwvH zqFO*y4+*L`qN-w4j|i$o-kTWJV@q;^s1_5|lY%OqsA?G1(}JqjdmW=%wnXuc-tFEs zxvjlrMNI}zA(^CW6;kL?NAv8pJT!$u*HP$m6uO>=`YH4U3f(}VFY(Zx6uOZ@U!l-V zJd_<>eT_mlQ|J~Rno8KM6#6EG;@&orpI#KYjY79mDDz|3=;=+N4HVi)p?tgF(}zOe zrO>?;`aVygFNN--(4SN2ejb`ep}(Tg0~Gop5A8>x2PyO;3Oxj#IM31%-9PYn*4_>y zKJsJ1({$qL5ysQMXrpTEAYRR1p*Y}OIL4%O670cWg+LXl@cgEmLla1H#y&Dsed6;z2ETS5-h=uYAgvxbmCMpZe zba%n*%t6I%L9qKazZx_DzMIMWB2`2m!I_25$XAj54v>L^-wh8wLS;!HbZK!Nc?{ENR zh}eT`3p!kD=TaQqo$2N%IChK9ZTDn$7-M-1k>Y@O9R8RS#4SIGi0;Osp_S+``2vrY zWO_t?tSMwX)syATmP*gP*=nb64O>MAXDWTD+%LVOKV>Rw4=x<(xO(j@{^}0nfIy}O z=C>_IUF4~RK7F4k0V>slL8bb3jVGuG z3#n8OFLwn6RRx#ok>&32%r6=$Kbqe0*s7@Th$n{1i_<%nP^q5U<226f`0m#9m9r9M1w2@jZA7 zW9Ajl=eDlmwm{zxO7lfZ^9Or8k-=%cLTPSl^hB~WUkgriGo`r&n{h17t+}l)^EBTK zO0%BQ+(s5ixZahW1lk@HyMtmI$PI!P+sI_Mi7j^DJ^#+Z$an3M>mxoIufS+ryB7_W zKD2@7j?I@W(XeBR^|AXd6$}3J5z#8)0uEz?Vpv~Ah9I&5M~UDq10#3AaLJOdxYk$E z1_?Y(N{RvTwkpK?qFhM^9|kU>MLO6xArBiC#Mva;Tw!VRQJ;>JA#H7?8#>S zbpDTNUCuXg@2j8z?vE;l+xfKE-B395W<=6LcMHDbK=coMH((4HiPhvud{<$F$ioQn zF}@WTkJMf&c5!ii8E(I(8?iCQyb-T1^r+RI5$b&j>Qf2o$^>;yg8FQN`h0@=VuJc| zg8IV*b!oyq5qRY^B+-bZ67jtsNz*F0H@=JTN*aXLU=UgOJuftF{cU0b79vELt<{>6U$+V)6o*RQpxX-gcHwb^w!Y&$8_A z6q6soTuM4uDnFN!&PDT#MS3^}%o7DdWWJpS^3E7j$O>)3)`#l@)dv~T2Pbw5uDwZL zh3=_nm$X&rp7M4XtI+n5xXZJwUBxQ2<9P|I(C*#==ztvtvMw|^U_FSNbnRq4XvKZG zxY*XY#}kp+5ouvvp36qvpMxgSO&&LmauI01QLa56&u&jNZ{1NfK5tGS$>>W|7c|&lL z1vP%}je#U@ytE{T^CS}}$&r*~VQ`W+*Yxxb4;lOI zwHUSu(B$bxNmfvjbApqsT%u^}r%slgQ!&VGZs!BtcvIbd_oL0zr+?!($zajP9=19R ztUM`PtQap8MypB|9~iGBD7i@K7)j$5)-g9OU;8^6j@aUi-=WDihspAf^g97No-gU- z5UqvaZljxS5S&q#}TbiAUKYHiInI3#yRGO+7uuNA? zqzNQjXbam0C(A!>deaHBB0B6+6TaFGIOUu^bdfa;%wfx6(wC_}j-fF)VDp_?SVXx} zU&L`Ix>Q?FD9ZlFEogLM0Vh8kc1qDgrrH1 z&5CqyJ|e?}A+r-t?zHIN3=zV^bW`0+n-;nnqWVOYEuH zv97?H!IH!c2-r=w!G;4df~$SeG^lt%%FDywE&9sJ%M-{=nZa=}x|2uT7T)_%WYv(%FLoXHm zSI5eA!O*>alRDOb9+oRx|EpsqeeQpCtfX!Gua1>;q5svfwp>WZx(VaebN%_m>VK<_ zwTl>C@1tGa4cK0lD+-IUG1XJguQ62rn2NiVxcgXKsQ@2>VT!^MQV>>Q7xpk_$K0Om zm&~5*UhL1#lJ_B2-n6$EO2WR-ah*>|c!?@LW*3uZ?~4Wd5Znh%ySvc$eHCRPOR4aJ zDP5v+kfoG=!Ib`K%0M<7+$wZ}lB+mODabq+z#tx5qhCQO_%u^E{j-#UeBETDji*#> zJlT6=>xyf>`m$wCKW`#}9T}AQ%$nG69n{rByc%?QbF8Rd7j$ZrLr=L9losti0@ z?$`1=kMcX8=Qo1#yMXfhP$0imEWeKg<#$mHvvCgA$38w(ek{G?3CiyhrsI1GI=)2Y zB=Tv}@zo+p*Rnm>YWLDLu#r!bj&CK_t5K^PJ#MCtThr)G#9mM2)3n#~3_wxOYC68} z<+iTid-Bf*rTKkI^F`W!3`%n&rTI#u$HUUx6rAR3l;&nl$G0W7^#?r7twCwNNom#x z>-e?>#crqA9l<)j2Bza{O!77%D(>Aq9_~JWFH`!xe}1K3bf!b_O@pq#5BPC#!1jXMu%0fN>nj!CZ=-Pjdb4U#jS%0pSWO`t&5Pn4(Wl@`@??^dzsCqEAoq ziYfZ^B(IpFPfzlSDf;vzFQ(}G`=u)S^dv8)=-YUSias_rb5Wnl2XdzLn}!AuH6vI< zR~Dl5n|&drpPtJKo+MNH&7mYKgOj}ff=a(C408QS`9McXKRWe5pDUvD6UHhuhK1?F zy3%+R;*RHxm&`>IhAnS)@zzvZ~XBGPJz1r5w3xyvzl2g#)9BdaGXxEB$|iF7%uqlup}T#TGZv*Pea*8z0u zW{fO~R1=$m!?q4nvVdohpaU|ii~a#i{DR_>QyfyadpKJ|JcAHRbarUNcel>ikuv6< zo?W}X7lW*Yu%mQ(@vJF0YK(AV2@SKvl6)L2ya-E9hH>QN_y74ESt&TuGR+~S;9yO- z!jxG{oy(E##F0dtASc}s)r<-Ofr51E)(o6z2S;%9d{V{q$&<<}0;LW(RS`Eu^VKW2 zgWaQf#)ey!iJ+G^{gBFT#flTR{bJokt63ai6!Ot!tU1LxSKJIh{$rS16%22 zE+$Nz*ay`rxHxvELz+E{b1~R09oMXp9$sRq(lDDaUuWEejo8me{N`iK+;DD}b8;DL zP~*^`W*{`IK?PWd^W3=3L5{5$G7dd$JmT1eEY z%VNV9yT3^`d{N<$3g%oE8SvB94CWO|oLnT!*x+^0HXb z1FTpnPcV;i-DQ3O5+rZls;bga<4H0x90v_$oU9Cw~I1#YFXBfl@>W>5-A%CqOd z)*{8aT6VAt}%K7_-&r z{*lq%#jxJv&T$>FxFt_^4@2Dk?Xh^uz*Ft zkN}t-Sc0SR(B{z6=`YAPr{RHKaWn-FCOh-#4^4`cDrG(tqxQxxTS zofi)|c-RmlLiY9&A&au{Ks-4>@9IkrRB)o&Y{$cJJS?)}fl9$1C}ew-2-!~$^!D}i zKpCs2hf)ngAqOdhvbvw%PEm*GVHzGz(gR^wf>c13Ac0wegxO5wsTAVILm7pb@vvCN z(>{7yfQJTppo9*a@KA||WAs1?9i<1teDQaxLrUo&RXdgS7(GzPzLju7*CpENiT4;5 zJuCui>oplT{5kQ0glYq2f*v+&4~sQ<+fNlhQAZ>3P=tp%Vq89wKM)s#lJ_#it+3)@ zJRWN3VK^SPyYP^YhjmeSD8$2tSUeQrp_VWd)nLZMSPHS=A&)}3;(^jRL{SxZ*h*33 z@UYQ_htYW0Pf_&JjTA*GpCl@JSVtI2c`soK@Nj@0D8U1i9}3w>`Jo)Lbcl+*ixNB* zi6{y==n&vG$|{BIqjV_cee|xtD=5z^oCu+Z8hSguVm*~~cn?GLCIZvD>U-jWWrNb8 z;;f_0SK@)aotUthLMR(Yym**~hXYjmIav@sBSKIG(TD7K$iYK1J)mERK1L6yl<1T6 zK$yk-@z56!^$B=L#Y4k3^nPhl+z|`=SMy!|b3-@wb1x1BA#$;i{i`>ze{Jw0)TMAq9Jk18jAY*o7dh<+Tj;zzyG{U`L}2>lQ+)5ags4A!-) z`*8fz4bB!x-%4n6`Bj(S;PM+?ev`{@cKIzXzt!cpx%_sQ-{JB*UH%A{KhouQx%_UI zKg#9zxct#Be~il?>+*YD{y3LE-sMkl`MX!c?8)DwT9FlhO0}vee!t7#yV{^C{yx=4 zgW~U7Z89qUv}&_S@%MB2)2l6J#h+1awJ84n)i$fLr`m2){F&7byW-EPb~+S)c6EeP z@n2mX8KL+GRJ$S-|25Tam*UTQ##Wmg-)q zihp!ctc7Be2H3IRe+_wQ&54wTT5tk|!`&P{kl=KO zVP*`$DR779YI21V2e%|y3FO;7aj4JBiMny3Tq5#;Xizfw=1$?Ec@)}{l1dGKbDyE5 zy?F|09D6NcGk_hONhxGY!~SGTmr~jyBp!bq#ScJyeomuju*g;(Worq5K<(RN{-cIkllPK>Jlw&#N zw}L1q5apz>cb3pQONnx-pe*K;(-z(^qgSJ6NJDi&hO)DIXn$pgg6{s95%a`-{dv=&9SQAvY^~=T~Oh-#ajLL1^4<5-Oovmp3?eI(FF4Oh41CtZZ<~oC~WN+2}Q$p`Hn#o-J8j)g8 z4*a&13gCikbztu>U|$YgDD?yIp}{&ZLyqwG<^_LPB$~;wlJBY*`O@J0Y1yFmvI7r@5_aHIw-1aR>X9eA?< zJ|TdkHQ*QkpDfUUV+C-D0N$Yi#{pO~R0rNIfKLhFcnvraz^8B2fkguNT>&i7fTaM| z4%2~C1#qbVPSb!h09-a)2bKxoasiyJ0V@DpF+vB<5x|uKSg8T;2XNI$9XL+_R}0{L z4Y&Znx=}jtApu+?fK?js5dhZ~>cB+;_>2HPrU4fNxb7w$_@n?nD}Xf`@M!>_yIBX; z3gGtyaG3^N0pR*ubl@rhd|m+SG~ikQUl^?eVVT6HLf;p_=QQAY05{yK178rp7X@&G z27C#?m&WM8jRN=s0enRRZUS)QZ94EZ0eo2iH*3Hx0KPI-2W}O>R|W7*4OkE0rrUMk zHUa#h0B+ZSI{%GEN8X6Tlw{;LkPS zegL=Lr2~H@fIk+%0~+u{0N=b@2Obo_p9tVb8t@Q+_21Hge-Oa81n{s1{20J(<8|N> z0eo8k|Dpkp0=RvG4*XOA-x0uL8t?>wJ0|MDlLEL?08eSaW&j%|>A)5N+$Df*8n6Sv z#v&c~g#hjrz<+2!NolNZD%OFD!UcDa02&lF_LWN}0N*XqfffP$sQ}tEpaa0YlXYN( z0KO-HE)5t3;QOUIFj@eACV;UTFb=?dQ*>a00Dd5Vi5jpQfIpwA1APMc3js{lfGGg( zzefl56u@5!V5$b}4dAb)>A=1M_-g^|rvWnnJTP4cW(weM1Tb3z4gm1O89FdW0Dmih zxf(DJz=Jb&;9vp#odD)*z#9Pks7wbI2;lDp@J0dDO$?>!-a^EKcC06)7=2RooLQj{f`n zVDvhH{z9Oi)6nZV`pa(zqhAo{e+cvjjxLv80`tx;(3!W910|0n3+O9a!kbvaQnf5E zl!DWKO(Rh>lFb^)7D1xQ4~39y)kq8)$(tHUy&y5l4~LL!(@0Dj$##uo2S{c#NY!S! zDukp#Be7^CjT%Xlc#Bo84k3A0Be7{Ddo_~x1&LjLB!pz2M&i&&ey)-17bH&k(GZef zX(SOE$pMYzLqQTLFA5}DUbvGD7MPP7Nt{MJXBV8p!~Sq);QdS&&>K*M*Rb)<|+Rk}(>|SV1ySUK2uc zhendCk&M$w?iM72kLyG=fziC~uHFH_JPv>RaRuxDZVmPrF_Q zsZM*%Xzev?IaRr|j;iri`Ng1Wd`?4;(a`HP^b3T(O@1i|y+K2d)zB|#=#7NFUH(B3 z`V|d*hlbvypt&hFbq^dpnK&Xhky=-f#wisuKZ>Q=%X-DC4ugfe-Z*Z6b8DVKo7|E zA)r5~&`zNN;gk_D+Kg$7>JebL3vvU=tvldy>7nzb_nP%VIU??-|z-w6SI8U|wWv{2p=0y-83V)FD5I*Q;f??f1g$LOeZ7>FdPKAM( zJXOn$A)w|k5R-yOLf=A`15KtSMvZj-e7A}%I)Ly9J@5p=Lu_)`z*%{K` zRc-(OOVxj8F!(X~ECW9-zpH^Ig9cU%{&B(J#j?RbDL*0qR0A6WV6(x0S1|ZV*~-96 zf$763a8{=0+0HF5+4KPA7Xfg=N8x4{qPqh7$LWe)>?SAJguM+d;M2LJeoS60jK zDYdRWN}NHGeg<7WKb0i?|MKqtwTUwh;P~A=`O?cblh(97SG94yYIVU*=!RoD>#7*E zV{R*0u*_8BR%g+EgLSiz${dzWFm*Bt8`LpjI{HIl4g?E!T3BTn$mS1@39GDZG75vO zYL&v?pG#8ohy4Rv;FWvw+})GAe7U0K$wyt|kqjs}t?%C6^BD#G!)x?leC3F#1wN9Vg${ zD|Cc;tp7`l%*Ac@9uP&b#BDo&ntmyLGrTS`S~)-9dSYe~KdSqDcE02f*xp=SAY>Qi zO2P2efNh3sU&!`{>_AYrg9w#*zUdDsLw<}cmiR*|sPu}HDS0*cifc|D`SN^jA|5~f znn_PZK0ppm1xx`XY)T0+;xPckCpRm+Y)S(e6 z^kE32$l?wn6)b~h^x-TfaR=_DY%S{1j2>h#iY%rvkLWT>#jyn~Xu}B%VGNU)fqOaI zggWd*I|gtTj&@F0cn#Ok*C=6>I_$ zNTL@RoW&$=LVuE{K^+>ALLY`Oib+gk9?>dZJ|vJtFESXx1a4s#L7O)S8_<6}>1wA;8EarIyW|&4-VKL8%CbXjub<`)2L?1F3 z!8mSU7Lgi0(Aa_|q|lEcj9?r$FoStSSF=56L_1Dk1X;{rg6Ew@eUz^>GPsCrw80t6 zm2PNhAl;eb-^Uz}BOHi0wy5va&k`iR`qy16IYeulQU@8AK`6wvovHRi-6>ToIpj9J zV0XyU$-{@8c*n+xk4Y7(X>X*uUru(v>C`>*?v|r+=p~I^XG5|(>BL2@m@k#OLZhVQ zdtOEs9cb>}Zc=}sGv3j;_t3r+WvRb5c-wU9yIPZldUe(<8@Tbs!O0+t@s#nd-mbW$ zVC<6BnD_o~hj+?>(wRpxt5m!**-e{j+vCHUngd%s*S)!cZ6j{kvAfOBU6mbUqcuI? zY4q)pL$Y4pm>b;|Q_5B>7Y^Ls`tXL#+JC6CU5&bxt|UC<+uM))B+CyokYV%5QtNlJ zbyU&6{tvzA6BX1*4ix0<=la(0hv{m+#+5dL-okU|Cm*BO3Fp!lvEl;Wd=bxrR1BCqFagq+ZBJ{332Z8hKac6gzPM~-7?>l z`HKl_;47uiTVY)ZbMIebd!-^EOH|lS!$B!8vrW#1@q3}lDR zp~x!Rrv-Nzw!g1+wDLDkh0m87L`_SIe^m29r{j_D=C>k}3Ng&NZXIwxdGZQ%#-9dWb)+juBnl amy0~E29^+eJR!lyGyf8Qyhizb&g~yUfI>+C literal 0 HcmV?d00001 diff --git a/lib/drwssd.dl b/lib/drwssd.dl new file mode 100644 index 0000000000000000000000000000000000000000..4435fd33c7fb5b96ecc1f5c217eabc848fd877b1 GIT binary patch literal 209696 zcmdqK30zd={y%<}nPFgNU~pIlL>mwkmk~rW+!t_1LtL^_7?weiRlv37qSwmQyoeie zt*op}%WQK6MO-Pj%*?uG=Cv|4C3pE&^M8MybLKF>(7nI!>wEvdW%O{)c|M=#^L(Dq z@@(gPp7WjheDxRCpPkj`Z))yF)~a3F?jlD&K72eMso7}L)3rXqCdg^=rM7swG$zwsviEvWdmI>3T{0uvleQ{|2e|jQqWYOSM zmWXRgKh$Z$SV~XN$tp<8Pc6)#bnsMGdvqB|CZiyW<3p0{?c=gy4X$|csl}-@G=8Vjy7 zGJB>z{BXDQ)E?ct#dga~?-BcOX7}hG-P6)DGGa2*(y9J`JZt-e>KaGl%pW<9c#kn> zTZngiUOKDG6YswGJ;`@Mab_&#>AAFjfExD&sf9&$oGFi2M(}f#Cw|B{Cf8G!K3d9j zAS;Rw(QHU1e%UrVLKPQn>9*EQN)fjnpHCYjV}Z zW8NDZg9t(v#3yKG@-wb%ed-wtQZhI@SHxMk^+|`Gv~bq{<$t_+R0h%U#qlDJ$00JR zEkAx#(MmdBQim^9d4}tZIzJt!-^#Pg9=ea|!q0apyKnQIs9&n~91f%qU83$$(_?<6 zqNl}|^SZ>-$$KTQf{ctuZJ=nzA^n2HdY$SOjfq>L(y0yvIqrn=+#c&KLBJnTg+D6I z-sIc29%ySMfZ7Xb8vWj9JiI&ZM$#_p4XqFUc=~p))0(6NFMlNH0=w7ggM#XB2HpF0 z^6guy*85y28`^-1_d|QFcYQ*uK>lXC{Y$#-{p)RGwefA;o#5M{z7bW{o2@$RF_|ip zZ{7U0qmmFPu!PR7Z|m-F;`m5vRZ2LrG5U#JWalY>xAcH*tmM0B%CoEzl+*Yna%v-v zqk>n-d>p*swZ25qR^uoE4nJJ;vX=)uE@|qPr-C*FBFR20H77SUeOgwYy=&jpy!^bm zx%o4T`g7#g>=&QrEW83`#@i^7e@CV1$J-KNR4|z!Q6D6lgT(Pc)P7z-`#B4ke0hRx zIN?T96&5f9WJUKaZ~*?znDH8C)=I4g(j ze09S352*=EFSMr?+v9Rli;6f+l?VSWJTnRD)sz2v;v_QE`JUK_5NyI$OoQb$Y>i4lYu)A>{?VP>Mi zEU;&A!&&RG=2{V9WoG5%(EZTN{n2#aE;>4z_!;?qw?5>enfZCeLsN6Ja^Snvs($M6 z-giDoAD@+LFS3lW&$cAx=ceW{mUeBi{Y<=4o^e_8?6zp!5HU^7jL)6K9sOL#N)e!>3C)o=pC(PD6tWHxSV%m6^|hZ!YQ_1FrcJk}7sqL42x1$aKxZ~g zx=IF?o}ZVV3Ym#@#O=i;i|-P*q~O7+>Df~Y^JnH|#O3GY7joil@i&p@MNeMzfB-mPkfOwxomh{wPOGpFhsX000WBwuI(NYD^^t}A7 zH&Sx))3Yb!WsL(CSsN!l%!}O^liPgf6j1{?n+Y4#!T2j(cCIJJr#Y4VEzc*8DqMyn z{z8nfj`yzSeM@-N0Y12OFehx~)dM{Oeg2Er-&Uz3wU>BbPG86SbNW(2$9a9s z7wxG2EblvBH_p3~=Zx=?!#1>?I@KEWvu2e}w0Kl>*NNp(I`P$xv+llr_l-N8zG}nG zTNSO}ynFKw_p;$&jjldQyj-?wfK~Xm!9UX#Dmi-xVGN0LQ$~ycE8CtkcMJvw zvd#GNWWaeJ)hAOkSTKhJ=gZf^tW;cKC#{O}U8{jgdyL8+4=}%Iq%h5YBX3AF3~%!> zo#F%E<$c0iZ{STio~;eTb+PCd(fLJ!>!lXtXy9lb$3+~_?++AetoL}nTx%5saS0S5 zs(U`T?_uCwA)+Ti1RaA2`>le?yT+uBy=PzvqJrvY($rW54`b}TRy8I~>^&1@^Y9}q zC?(*a$#Z{l{e$~vxyyoDoIlaZ%Zgb5rL6O3im_{{qyB`>-C7}ibEsZqvfv5}LoIZm@9g_iqsk~_C z8;#!}@%I~)Iz`2e#ci1l8MrA#HhIqJo}@~U9g&+l)lOqw;vsT1n;%ReZTXE#C$Hx# zQ-yGKxQ5VCSYsdRC32Fjq7$UL(T{Z(54|&y*vrgA2Ph4RC&Tv^QJp_dora ztUaq}OzIe%sm=i%Cx1?rD45w$=M{YQC}U+FHFD=36An;Aa+ddcm-kx7d)?$I%7VW-V%U5fT89E%J(tsz9$gZ_Fg;)M{< zW*AGQ(>k0yL$r%{AC+gMLFLg}yP@H79U2Kkq{-qRtzSu5ukpHg1BQ?WRqK5HZC>3& zJ53#{o%0Z<--vc?6<>l%kr<_)6&AGH#P3Y56R!>)iehU=>Bp{%2HLeT}tV?YUdO zQ@Y5faKc{w9^L)iTZW1!^;NnDxi`HlMp*B7`0Um_=zYs? zm+ofl4bxCnt9a2js?LA6KCdpY!eYIrHPiSpcz1H$jhi9XdtPnn)bmL zZ#Q`E<>+aAvR zj||~^?qfH)yHRz$ce7mjs2hEI)%7~Sh*J6hq=O-(59(Fdf4@HeLEPF9eg6IUwE>}q zA-mJ+9t>Nfm#*%QD5t_nIz(%yKI;0Yos@frKI)gzjivQyrS3tDD2&zJj}gUj)_U&= zyY)Nt+exCoUWYhUk9j)>x6;Uv^n%V$_JeC^C2XNIx*fi%;BkP+6Xb!d)^bR&7zeAu zoDFzg&6C^*C6hR6)VFerIPE}{(K1xjV$^Ib;ds@0wCt``gstU!Z|Og;jfw1_ttQlZ{h`SF5NtEg+B0$WNb1S!oo|w!YV^Y%a|PvQC=7HzpbNvzE*& zfsJB_I*Zd}spPT8OY-o8JoRS!d#}={jG8;v>VYAa^^dmNuZWkl{EWHGrFSiUln_%f zP{ojDT2>CGoFH)KU6p}cJwG!OW2xb6R6*%?mu#;f{6c$f{w%wMkEW9vP%FBsnt58P zQO`E(j#A@MRFIQ}Q543PNEFm1zmaSU?X&DOsb(D;(F}Vb>7ha_SB-8h4%5(D-aot`uImwWMO_W<3k?GAxF%&N1q{LRG>7< zl?w4%c;gZ3UUIWgZbBALQu;SJA&*nKG+<^0Fs8~@-nvP2Xc{05qk)f-4;}j{fQ*3B z3EMUpCPJM^7C*{p=l9@_zyCZhY~;Nvc{HV72Y3w8@L#o+SFPkdLTGaAb(=SF`bw4N zfS^4qsHz1|KTmz|Wz3L~j8UDKAPitC|)hM^L9=|K!p1&luh6^1_|4Rci5eZJNDy@A3u z>s7TE{%G{ur(C=0jXAxCal1)tzLI?H)|FOO2FEVl{c=^S>QK)BV0oFjVIOn;T<_a{ zLAS5%4b=rBQ6IA1WS|UJue9B1z3y$ml2wTos1J+zc6^yHno9THj`r`HHEn`Ev4=`pcuuSFBeadK4H}#)7hC+;%`1#}f`( zZ-zd$UH4#^@VNO3ot`lNoO12vm7kw5D5V>daxntZlj*Nwa%j&5ym7w^vt}IA{0bWNxnYX5>87_vY=Vouxgy zB3dc!FH&jqskFaVGF;uS!-VIhsw^`94Dvs}(C5mFNVw7a%5xI>Vyn<`Y59{@q0_SR z3Aiqex!JJ{cPnFVc3y1|Vs3URx8Af?u==bsU(tiiouHdn%QXgmiOS$GU)Gy{BKBYF z_0xJ>{W45=J@%(pdtZLVe0lPTO2ioj)P5rPojINhIEn{psD_ zmp4l!wklQm1f1WCz4EbCqwP|Sw)MI4kyN7(r5f#kymsy87Gr^m+s)~2ZGsOtXHU3f zKTJO5&UY6&-gkSmLFiIVxp7gYjG?B*C3%5KHEN{8)S{E>o^Th%chijsv)`AueE#Re~#qruuV=FGy zICwZ$2S05xC!i*F4Rzv`=)+@BGd?oGMngPmYsaMQzeKHk5$4wRNm5$C`pD;$Gz=xssttGrOkw?8TP#Czz_bUIJ5>`oo6#bnIv6{7Bo@mqY7j^=ow|KBt~rJA6qV8X*3FPSyJX zrpya|u3dth-y zo}{5KP9A>hZ=~Ri!qnMp@3LgPnap#~_`&kF*2J$DNn z`g7D92CAbyyWnpR&q!T#;2Obeg9z+7$ZK3UkpAM3e$+J_nDRit{6N5}O7a7to@UD0 z111qu1kr0>$^+rvT|JV#513R)QW>SBR#s8%hyFwiObQw%1nh&NS|_y`CInX_t?gdi zTJ{8q_k-&1O@44+69!#`Q#tJ+eAIvzd?9`m&VC}6S*5w*D-iy=ctrtZPxPZvF?o-4 za5}C=u&|LaY61ng4tr@hw2BlE4+Zqc4HhzN)EAQZ58|b{Fby5~*wHq!RW=$P!MC^o zCJtdf6EeykPPtC65QIy7>!WHNXZTF5uZFW_6Pt}?deFr0pCc0Gu_pQ6T~S?= zQZL-U=i#>ggPirhjJjKfvHJUI%G1dT=!d0ljP#q;l}3;&$YO$re`qJmi(`wx@+eJ~ z0VpampfzNm@2P$M6OvO(W?_D=w0OvgUkc(iY8>Rp82p7-1mOtp5vvR7$a$?3vSX3K zfrK|=}YeYZi%J&vj@A7aC(;nlhG$)Xh zxjJ3MVA?r-p;-g*y1>xkRjQf`tEltvQ3X#E!U3$zx3YQU-~mcP>yrjNG>Oy=9LJgN zd-!e)^gV6xT^HqhEyMR*u?2SjuL8U=;3pqZxUMEHf zta?0$wIxBo@(`&}weJ~~rek%W@=QhTK@U+pX}qM{YtmMkJS$CJyG^=XCjCy6VTZ|k zyUAyp$%r9{ulZ+FEA(o&Z|M!)?`VAHn>eBS9Y5ds{yfeBrul&a&SwAlK_bq8_-;pKyMU|f@lA~{2lIGPU z5Zi}RZp@C;-hKQ!**OXylvb3VGqcz}i53EEG{pl2pyX{$-YcU>eZlCgJchCZC{#q@ zMddqf*V^H zhecdm+g_Kz7+*YpWY!H*R{qRlER_K?SpuQdWySBUpi-rJC!ulxlP{5ym-)@vLGnVW zV5xw!9l^~~rsNMpq8}scM2X{QYRVQF7cx-I#$7!cq#VQ95{MkiI+W1C)KCcsu?WDf z03sj2XFj$aZVW~pyU2OJNzAR6YY5Xwq{ncaZ2|sBHuvMD2{wsSEDz?w=BQDT*txs% zpwk>#^=zw&Bx+ZmvF=4Q9URFz24`Cc{z&9BZ~oDN$U&c2M+LbY+$?5XhW+|-b%UWu zyx0~3OiFJ5-t-xyZzu9XLSH2_G48uM~= z9dpa{-uq5^ANf~7?eN;t2$pmH9mdH6P z3?DzLcm-vJrr6spXi%_pwWanoFKYJEi|2H+jll)<2QlyZy5r-ma95C8RBRuLzA>)h zRT0}B03c@MuE`2~g>q9PW}A)+iRhWRDeNIG@)p>_kWBUaX1>6lV4{9_8%6Rl+gmd5 z{Ci^u5tmXGB$?S35fCD~`2Ef^Oqn1DpXQ_|0Ra5Cd=q=_=ah!&=|23TrGg?Wz;BqPko!irYv+?33jdFi$%kU%;Vp4hsAbSN>SG^CtuB@#g%DBi)^ zD-9@$zBt>vK#{z$ZOzV|ir{5av&8}|xk2w|2TX0KF>B{J+j^yd!M%qsqypeTN^3U> z_qJ{L106y5W3Jy^M8d~&-8@?{E=X2mcCBDTiGr+j);rh^5da#U*#6MFzPL_P9(Sf> z(PIwgxTI=HZpp924JBJ*jVPH5uGw>a37k30z@Gm~?vapHgmP_LfkS0auaIV8#1QM! z%mjey^F9q8Ky{TG-$&tg$Q3OE3qSq77Xh=1QqrborNBG@OhSUBsptFuwL?R5AvMmn z4FH27l(}rokZfv3Q?l)I(~AppY{|G`ZIIDevEagQWH(SSdn9Y4VxSyIW7EZu8Gg8% zmYRo^NN|J(T550X8Ma*jNYp?Z}U*g|klu%S&-X939$b zSw~GD`9xwyCTAnHfgCh;Ja4@p(-_Fv!U-S3IkjoCwxNO&E4Eou9&($^3BM8FibAMY z8!Ulgh!o&$6MrN_yg94Fn<%7Y<)QPE8Z27|5~%dM&vKduKcRjR zT}mW?l8p(EjT}oHqj`w2CCYg;j@tV~5ChS?;*=c82qq#y>e<@={kc&jfMIwOAPdX9 zHi7a%GMyZ=zaw2a+g=;ym~x(xkLJGQba7^p7J$G4IGpJ5>XXDF2|BANBQ-A_Bo`xr z0dJJeCUd1mll2I=wp9=ivU%b$`^p9@6wh;)DJwNg4%Fn1O=-5*fmpMTBQS$VZ z*hNGfZ%?FWvtALz=MfOZr~efD7b5{(I-(+aW22f7kh}12-*2Pb(r^p!Mc~>-vP8+q zKX3m()s(-fKe8#Z#N2{c-y2FbCrcU*Dd!i?dFgp7TN#Jp+SZqVDEZVEgWHmEKw+$vw>?h)$m7%-2_-~9YIGa= zPFty*^P9Z~PLjh(V^5GAPXLs6KFXFsJf}@fq2~~6z#u)sr$;O2|1rUzq&_b{Ki3w3 z3rRrdUkGNa|4v(EH6ST1iZ`z1tL1kao$oU`&y?QuxFq8`(@Stqhx5LrvlGvGcH%kj zoo8pdlLzOS(y44``Ob7_n)6I}PMGsdw4K-gH=Ug`=gcIIyMlK~#c*2R7`RFv=1a>P zWKD(js|x6)q6tX}Ciq18-eF`&v$OIt@@GqCIq$*`>>Urhqr=`= zB2#^0+{jymSHuWN3r|@4Xpbbm^b0{)$$KVxhUmh5{>JOy6}ait+mTaAzfdd*gz|XB z;yyiSxR0mJx(8Vg?(^D%n}8s_=(oU$%!wmZ^19C-JS}{XWz$cCms?Qv-oh(k>q@NoMeoyp&xI7sQmH}WY-9mquCq`TTJOo| zr^?Vv8TD5Z^86{&ylJ2vl4LZu`f1O3ZzMeFoI3XFklSGjfS}sy>EC;if7$Vp88I>* zvtk?WdA|fUxnvaEs_z66(zN1S_Nv^K?UP2)eQI%Wq4>S3l(&q^>`Iwdej^AuI!+hj zZ`OtQm_1B}?)nWpmmdirwGPtH-=X0Tq&*O5(S;KZ(rr!JJprq7_jnB!DK-oHlCeOb zW$&nX(TYIRbvCCVEkeb^9)ncBRq=i+(y1S&AA4cQIFMV}XhDryJ|>SXtHxzCkw-C; z2kJj*HJiyht1t3u*6barFQcZqIIEbwpSOnAmz%MZOAM-xKa|O}J&|;;r ztVZ?Tmv&cBVNr(D)KI;vvA}jmiv_j^_wW5_#Y<7?zR-B-zpi#Guz|O6MosNW^?U zMW@TGQHSxwl|^q;zdC!Gy%;Y~k3;v0d@E8(r9?ygLB(6}LUiV?tQ~C8A}X`}_-`Mb z^yIjd(Md1Z?~ZxMfr4QByM||L$)ob&DZFJ>X3vtWe=aSD6Cd(*X8Xv|E%wq)STtzvkEt=@k-urRr=j8U%pGV zF^nNvc-kV)RWB!bh57Ggi%w%0YwX=c){ri^Ib#}8M30`;Rfh$$x>a-sXAFd@4 zEG$3rVkwV=ywN_#D8X1>^5999VA32NS4nsL3=7H1Is42zR0-X?C!PtEV21d*Cvuy>idThG=e7Xl7#V06zRoC zKzcKM`N4hC&)6bQQ!dS8X(>dG47?YsAQ||$zq7SNY2a;PV{;;Xw`@!ssyLQ%WYTLC z{h&!+o?dT>?&7Q{Q7sckXjak?Y~rr5k5OytT2aO@DiHkIE*|I|%em^$JLh)LxLpgr* zpPh(u8iuURC=H$8VJcC2d0JYEL}`^o>8}6xMxx{l@*?U1BTR=WEnQwpY({O_AFZbl zHSOX#_GuHr7co0@jirndUt3bZ5*ed0-BOkjDd#E|k6Q^h9A zvqqw#xl&fn>?uDt)4f>?cC1Y!Qh9SWETQJkxH7HlqbCPY(ylo#EoLHnHt8h`*_gDX z1o6Ov2aj33hAfztwhN^I4v4Kxv6dBl-=y53Uv)Yh1*CT@G0`zGmUP7C6;Vwsars@l zSQ3gex>^S1B;LDGak)C3Z^IY&N3MR z{Y-dQP=5aGFAv8|NT%T!PcL!r;Bk|fkQ$D}lLTO4LfZ(H9b{DBoLe`5x@c;s*{TGE z6^aqgNy?!gfcgtP=Y^hHJz993@@8TwQS3|Y&9i+L1k;eAXj=Yk>ZaqJ!I6zRuTbjL zNgWlhq*u})FvFgiIy0x3#ayyrk_9hv1!NWA0&DP?GO|DbvI!+bG-P=y^^zn-%}l(0 zNQ0BG$sg?`l_$c;hr-mUL!xqS)*tmGWHg`lJX)Lp!@Pc_O(HnjMwOrn{W0V7HRO5) zSTbZUQi?q&yZ3K-^^_epkHx#L1m(m~4-(gpBZlrLaY@53dxq%ifzUp2BX3!q`QhLN zt;yITXFiffqp#=LnC@7{P36Sp>gDM7R%f0M zKKBAuB#|4#4P(-|IfaH=x90a|uXIF@nvR|w@0`EmA(ZfVorZ$^j5`FKxwX+JNA2NR z++fCtRtX4oc?eGvAW^lXpTEO{$0SkIV3Mfj_v6J4%q~1> zil~mgFU=2iQa+7=-jE@HG<)*Fw2{pkFKl53isF<9_c>QM?!q;40=z_^DbJv0iGCVP zNz?<`Yfw&SAnVqheeg>2O4WV`Ss3&!i`jc9`9La(s*YT}Fgwuj=V&J181sC*QfP3TnWp{_7pzeTc2R8+-?NLG`z%iMIt9?i$`wn%NqaMRUWc z{GxRwoJPC8M9{CNH`9=|oXzM1pcTVdfoo5gF#8wVt+X8(itG78f)wACC_TxCBG`*e zYiVIG{^nWDN@<%&b2A9cDtcTa;w-(UIyq)W!xic@| zcgN-XkuNd2P4k2H`IipWGX81Y=t>)~yK7##=Ho)!DGB2D!zBg5j9+;)w&qCrn@p(?jaY2c;Kh&61xsF*6Z-+q5P1OTmoj*>O!~abNNY*n)!k_Lq#}g-&Q_Gd4Z$68z+9ti|51(ntiN;cxC6o%hYbC=YThy#WpI0%4)JS z2zMaWo_f`DGE*%AR`HW2v={R~U}>4`$I`~^`2HdpzVzlhnn46ZrnW*0KNtrQdi{;> zS^Q#J4jwK7vG_Xrr5!j%;_$;U6eJIU#Pc`~fu-kh9D!wS1%YTB^uA{_js>v5PPh+- z)R#dk198yMIv!S|8NnhEYX0gUPL&#lkj(;|nHPDK+$B*I=Tp!Y)_f_w3+Ak}bVbTV zK!eSQTzKQqb{V{ob9_^_`wfsrrUEI}v~b_1f0DQ4*fWdc#89mmq2;@*QfRDZ6xOi( zrE$yS{tPD06lZS&@p({U7aU8$U4IA0sV2)UnFlsrA;JSb-Hl6Q`f!A>t?gE!_d!s@T9rL@HOp_Jcs zj7r2_pWRbO;><53aY|v-lwRmH`UkRfEW~G^NkLl!$LQ#}N)seD#5VHA(P?DJWa(t} zyneL}w={lDg<-KDR+$hvBl>E{70&-WgRT4((F7JOpPx8*FD#WF(y^rp!l^MWoJ~YW zCJq^f2NPIN`0+H55%t#U33w`Lx*w7{=qAb~+?DZEoY##-pvU}_a!*c(VF~rs12g1=?s2X{Phdg^G4>PpX~hrJtNBSu#bcJG znnxnh%hKf67x7+}A_~3l7bfo@(mN))U=FO*8Ix{Jr=axS?Ya$#c{nD%j}#4#fi^#g zPcru7C%#mo?T)cdAM`M^de}QN$3CYI0?eo8SuwKDE$VHN0;u{RxT`QTCx3Qt3W-UXMpw z0qWSNQd%%WoKU4qIm+4r^-6~I%iY4+mT_Lg~3_`y~JMMeF>q!=zMgcfGW z%IFs+moXxDYPT>8TNLUS7TrRg_2Jo!Z4>MSQLKm zUJplvMY)%eLW|Vbn77%5sRdyc>@|>+m7d)%OnSuFRbo7{gA2nQI{phrS>+XGyG9kv zOasj*)|3@RNffD(QP`GE!_u<{3`nX2yZ)E8fUah>T+zP&0^D2%ZuArLzo;L{B@{*U ziRzS_S~xW;&q{rB@17BzqEKz7M0mnzsBr|pExO;U{YZ`fIik5P%|Y>EIG3FMh6-mzgT&^4w%>5hcFt!Iqh*pQfMMIFM%6t~)q% zL^6x+%~)wG!L>6kNPN#je1{Dguhf5FVh#I$@5F#b89hec*V*IQ@MUoxCblToHAD)GJHB zK=o!0aXwM?93m`o>V=A+P(eKSjHvE-AW+<_>i$5jfsnOJ0$H-g3(2a@9!S<9*`PcV zhxDC4@k^)lH$w7To{^+a>l-x3p`SKhY_B&)2bJJjeh&t0=6Z1Q&n6qbr-%A;nge_g=e^$Bf4FBnC_ELkP{3)Fr-#oX+7H4| z!qaVNH7|iZJv?6EHMd2#=;gfOX*bvpR9UYB{%VhT4zdYE*PzJ3i? z*fB!2NN5DJII#%!8|7V84RkF3k@_I*LzBe@qa3m^yojz*Ta7iZ^U*pi=apUQ5_{c`ak9CVR(V} zTB&l2zLhsDbceoP<sV=3P#`KaKQDAZKoh6Sf7HQ^ZS=D5Kw zX~cp5KR56j`H$b`bzOow2aS)~Ax5m{+kB-8IN%|t91$b7@@?*^0#0k>l#^n_4!+F` z>VRt+gTS-0WTPjvn^1UCLD0znYW8S4U{)$AIXV7gEi8iWE z13Gzz8|=o_@gvU&f@eq;)No~l^_Rml8d8Gb2ND*+$TM70B+qb70S@z(Ao3O2K4aUE zc}}9gSZr334+t*l~?i zv#&Ju74uQ}fq@H*_SJV#nHQ>?IF7lQKC8YAkSr?)EFoHTG z7j7&ka<>^dzeUn%C7i~58r=H(sBZHHa|d{ws*S!^32$Ab>3q$z^Lp<`-qrc8=Y!U% zf;Jd}-X9RKk=B}wGuvU0B+i0M>9>$m2cN_vAY+7Rj8GXP)c$zS+I-x^n~$X(KZYyo zaX~Pjm~!H{N`{CGqG1p%gLugh6@%y*#G65kGDOWFtr*0QK>}omhC$2>63ieWGQ@*H z+A@fRLBeE+mO;W9q&BJyi7^JHV@nVo@28m&i9x_D7AUzo*mO*;U5IuwR zWsv?1GEjyX7-TSm#52fH8RE?#!x>~GgCxih9|jpi5M!eGg#Sr3XFi6Q*rRF3jwYFp zC7(EcY`p);4oI{fYc{XRW*H_`9WSLrK1tk2D-T>dl6Ct8`01vx(TH6Md!i_Ep_J?w|MTHab4 zFmu%aYi;0cb1h-i)&ryQTc%ME~@A+QLvr@26CQS z%yKSu=9DQPvK|6uP&l-VQ7G+I`(jdAmH)|PccrgnG*%N0z>hH6$a|`s<*n#dy8@;E znpeAyN+$|R>94Sy>zz59mHs-Tu%TD&%SmrlTJHzHZLSS%{f-&zg>0t3ZQi55ZQnN^ zPCk6-)CV}#-aq`2KKt`e)(-YN>BDV)2qk|$gx?lbyB)>WT6UUi4PM!`yVr)gf-9qH zs~C7Mz^d%peHE&ueL$tAF_ryMwFem5A)slqYmYRhQX5tKDFYt`*fYEKIH>dml@n{* zxfXUZs`hh+_65*{?AkAZ)(>cB%6hq?eHB%EmZ5zEG%>sOT*brIQ=#9QYfWKP%~OZJ z!)3ed+V9IcH%`BZ^ziK3OBL!?WnEk;{t#9BBcpg36m{9PSDI41DpCBU%-WdZb%|mf zQS9nU@z-e={&Ea|_ zzqi}+eU$vZd|6EM@>?nS{rIx(&FT9q`2*dSZ&vaL^A(|T{tgZGZw(QVk zZV1n{<+-R@$3sBithEjc7m6!`hH%^QD>^o3C7g<4qHOP$l@0`FqU^|5I7Q{6icSP) zs_Md*xtOg}RaONacT$J6F@*n2Q9%;^hPbGh>TmE|~`TMvnzps+N zKVKHxynH-7lj}c-UwglK`Gb}G@ovi>qU0auR+Ym6XQ~{@uU*?%m7|y{6M!J85^p=z z;yDjgndr7EW2q>n%5nVK(aozep5RQC6Zy5)#$`Rm%9gGKW zH{}Q%HJLXb;nHdyM;ta}kb5NRZw_-)4#So4)afvnMwl5q*Q?fneL!nrzJ63s1Pn@gc~|9iP2dK z!8wfJ3}W;aF*=Vi+R&9rj6SWlDKNT_-@{q&IfA+8kTtZSkK?R| z9BsH4c=I8;YRkPyd6TKSryNVzb*#} zbx3S8zprK0zNZj=pAl}V+7A?hA2EXO5~FvB(NEl}cDq7vr$Wyiqq`NlRgCU7FuIqo z=By8(+G=DCZK~QDMIi?$udLcb?7G(R0181+(&8vQT>X^CzGt7Ak1p{rf2KDdHNI{B zRCjDm*B}R)g5!>X+-Ll&q3}HT%yGvte!ai>Fou?g`6YTZCLu@hGFa_9x=-K6WrbSz z**nC@amqM|JHeZe>yC3-$59;5eU3XyAIyEhn@@1h#BpEp9nHu5DB!uz9mg4->we4; z&wa&9DR&)Tvy>s+H$0bog35NB<9E3we;?$}E95UQ@NhW{O@uN)s zCx!ga{7$#z?}7YPh5Rp!{4nlD1NmEyI+^^hGWpw%TQYfUPcL(Sm*2sL{7edd9QGPN z!A+rx)$`lAr+?$OaZmrwo3C(B|G}GoX2bkF80c5(tGExZIUGDMNb>XNM9klm;l4yd zEhs~NYQF?C_&2=AHopwU7p2>TZ(Nn}v$UB-)(yODPYtq%R%83f&Net~<+b ztI)PE+K8V>3KQH;8Kl-XTp`?^5hfQ^3TF&|>4;Q_c49=^5yQU{!(AA|)B-DcWQMyc zbfXoz?ih|yX!l^Wu@zZTPoa{tUV*DVf~?J4wU^**h5Jxm=Bi&h`myU;#}%k(fPgmm zv}2I4cCz0|Be!`t*gZ`nu)zY2z~Vqv>b8gA%HTzP=`dj}#?-F3!x`=f1$PwTst9+q zfVskxtcWqqia$eAdMzOG_w2>3Miy|Hry!|%0{}!nMpWo$TM4@Db1+H#m-^H&IOXBs;6*e z&{46^v{>x3O0gwES%Ujw=PSkjl@&X>aj^?ovCk>RzCgvQsn{2V6(ih}T&xtkR9M^I zJi~MX> z=)5OXM7YqAr)VVSEx>`X*E+TeW#byF{{yD}4}lm*(BJM*9KSzYJi7q%UWz{Nl zK5MaFM-@89Tjb}2Lg#a#>@k)6;;pa<*&Q~q+b5^1AO^fRP2XL7B zzZKTL)>!@Lnfkv2f~5ZMaV1Ub{@G&nE>V$8{XYt8Td?k*2#2ZvXMv`7uKmmvR_0Z? z%ud`dxMI`08^VewS{`Wps`Rh7kij|7kOvKNzkNq=4m9quzO@dR(iHDOivx}SQt1A! z&~?{uI~3ZyNQ0Pq^xLBNC(}H&=o}e&h|ZNpQgp+}=u3yE=ww*Oh^l}oP4x^Sjf~t4 zG<+1gz6xD;44V|%{*1P_$R!1enC|_Ifrc4bn+-IAMdv^xg!0M*jkfH%*6}l15{tMr zg>{=@B2D*Nq|{EK6yZXtgA1jOV#})TqSUY}t07JIno2I3Xt6P6469vdk{p}z^|zu_&a zdZa=(L80r8;V}yBL`HiA7)}x~{ksZPk3-gGs-B=I=rPJGtNL+vUF*0CRX-_miwEH4 zmnq_(Pydp|hUuS8{BKSFu3`d}COW5o86r*pQn^ggW%`F$ST_BeCOW5o(^<+8E?aa? zpmIg?PtFG_crbFDn?jSqd=S=iQO6xKloEnY(9Y?ru8pen0pf!p&E>`>SaF!7crtLH{|0{tJwL2KQnE{acR3GW{ho z{o9V0Wcte#`lX`zlJv-kvmSG>`J*g>GN%MqDy6JurP#T0R|%{Y&Hv;=Gr5-uXg%SW z%DpOIP2*m}l_`tseZsMhuBLObCmeql%@9gdgyUVxl|!W>cq4u__nuPr7Sa5DOJ#qkl>GrK`{RF6_9v|D z?Mm4@SlK(3vbQRwzAu_Dv{Y)PQtED2>Yjg5>RwjrKBd$eR_cDG)Lpoe%VH}6Oj_(J zmpdSGc_$o)#ELEMxyj{fiB29#?g(8meI0I5X1UyP1^*bhU(Fp=a(&ifopZS_m0YJ; zt}p05hwF2~aSHm1l?GGgEu>r(DW0$92bNj!zvI0W-}qALUY{1)-yk zOQQKa1v5%?W^+G+4hw0V1I)Run3oau%vry4%;T;ACy+`3mG97koOfIk17!+MPJRF< z*JV!X8gcRi0~|USje>k-1MCJsGe33KjjR!(Is()MHqPnojV@%)iG`|G2_AcsRGrK zrh>Tu%JY0f`)pP%eX#{bTLZNPK0{O!h|yp-jJ8pk&tjjdn6qH?p?{0fvkIf3O&NWm zfzfuVC6}8DB|>HXhJ^AB%IffMmGzBMR!4@vi0e#3#)9BEhgH>-ew51mHPz*7(C_wd z(f?YZ-@R#FT!j8`Q3OXgS+%mng2m*mmy0UApKMk8vm0Xc*%sE zdMP)6YV^!Y+#nU+yx`_7;|9ZA$w+h;xt>SiM{z0xkGt=kce zsrfT5jWCC)2(p|Tp)!@3kLoVW=3*X}tE5CCw30gT2_72c9p4IGZ^ft+tETpKxEHE<9Ft+fnR zd9h>YB7DIC#{sSH`Vw=f*3YObDH+cBh9z>#UCwmf{GdGU(xo(48dCZ0c|u^ewgjK@ zqPO}>!COg**0Tg>EK+K54r6Jk7ww6!IwyTz#5P!C(F0$da0IXYBdx?Uh>K||3)G$R z#7i#a$$5xmWkX7(g!Dc*;++=Uf?I;lWWsB6+YKO za*ZA;s7Ox(IkG$HdOhyfm-KoF-yk+~G1Z(tx|;1_P|Ch#;M@}=5|&S~B^ihkSV-5F z1h(-xA4~D}B`gO8*HPrf#2*5e(F^+N_^K)e#8KK?Zx8qjMPsDUPGLBwQhYqy%W$+a zKK{aAc<$m`h!8P|h=oRgBeK=xO?sYQ`=?JQP87o@~cU=^{M zvs~JKRQ}p)vu_(Ch%vct>;>W&p<3P8u8PjB1+SIhs|m!%3{OOCBbXQ=#Vc2d&v=US z5fL$-e{U#cV0;O69U>;!UcoDQYf6`kh!NQ7Ay#>a`#i*h9%8MBc+5lm+(Z1*L;Tu9 z{MJMK-b4JsL;Tr8{KbQXnTx-Ah<7~12ObUa;lBgX83!)%TGy~|QLPm{wW2}GVz|v{ zh$x0z^wkzFf~bRW{JWvrA(#kuLf(&!*kuIAHwY4;Q0?;wRYs_`=&u!n5I_AVLD-N- z9K;eQuJYu&tXATz*RVM2QWj^uJgy9J)-A&jXFY2Z3jLpmv!2r=#`;I>Q}$&)h2cYfn5(m3DGwpFd8_O2_>O z3dddj#dH0jWW-0OXFFl+yV9@sEoEO~VEaE0rkH=1TyHJty49J>31j58*cLuYq01~6 z#p&a^A$)cUx#{4)5AI4HPbCrtuU}!qExykRM+=+nwVfO-GbeSb6atJ2oZtHXNQzF9 zh)OZA#EkT1=?qhEZvYv&1{fD7@?N6ai{hVu_Y}YN6u@G5El(^pQ>9rTYyskt^U~+W(f3*LXDt2k-HbnTz$SW3KWWi`vK5T6sU#Q_x~&KI!B0hL8$$|CxYpytxKV_K>0CPc_Dg;x$nGTfF0 zRFhamtAC{2%Ns^p^FO3w^ZX~#7rFj4Gc_sz>jVMMCGfa_T zSG;lcHNqZOT!^nMOFNFWC|=UX>swzRMf#BT8Hq!V=2Gm(GHv^> zdHX18pDI*54MLbYjS~lXi9@`aey`p}xyh`+5Ub+x%gatt)_7?90(7q(G;3pnX+szF^xZz_6KW3O0^Z$073@>x+voQ1N!TcrleF?-^UQl^CFSM`A#BUWsO0=_gD|YW^buE9G|l?H3rvY&8yfR z|ErnQN|_{LveflNemM5PA*nKnuW?%f_Vj=;od4~MBr*otM@)PXAIHEaP;DC2o6mvU zI18etfh>%;UV&c9siLqk>o$|@ii%Td8%{1xQ>AiS7_|iADzD|!?gHHl??iUtH^W&fsm zz?9?u)Rswmj5HA?ZKmR|T~>LusLs`K(}JPIt~lbM$e~bVF%&rxN=(DC5sC~aMb7mUXL|CB zMj5d?GE7#$&UrvTYr}5LP<3Zq566+1#&P{{|4($zH$mp)2?_3n(9#Y#h9lgR-~|t1 zZyA{6`jtG(zX8|#aF|!~41N>WmZ2Osu$#c(vACXqV+Xz{Ncs7LKBJ8Ldxw`;Y+NZ;q2lXubCqUs?uj-S>!6appaE|K{2Zf2}HrCjd3V!m4PT8wK0ey zS6PaZOSw>x6It}?rH1I$l?|6ir0~^B9-Dg7e|$C$WoSSl$86>MB}JcUpW%hHENY&UX}1u*QnKMSHL6|wR)`~ zDv*dP*#Jdk-~Sx(IwJ0jT0JKhim26fdY34;hN#ukEkv!Jb|1BRvW2MC3yoq%{mKQ^ zQs}|BwQh*H{4)1uACqFhD)5P63vQ8H|85Iu!a~` z**9EbSe?ERM84rBVs$G#L{2UdS@5V7$+`o6BB$Tyn+281QK+sFt8%<+^N7{z<`Jun z{Nz7KypA}RLu!Lu{G>$ZR*%e2+-RnepET%^`H36!|AwD55G6lxL$d`_B0q71-Pn}; zuf7dG`E8Ta9h`n*UJBz^B393-Siol!moYbU8FM9b898kAf<_5jUEg9{Dhnvx&>|RN ztBd%58MnG#RN_`kC2T2h6*r9|SIdRKr7HhP0SKdYjW<1uP*Xo3BTWe}r8MIO-jLwy zfo9Aw9H);}u6MT^IX@aZv#M0^XO4l`QgyiTHZ}+FyTJQLMwQ5p)bXtBXx+#oA};XQ zLc#1g-1kx6X|1`SxB1ZD1b=5r22$SOL+=Z|@T4HPj(=<4Nl5#Q4@Gqw(#9k0Fdw>A z^hL0FP;_fmPiFSl54|-#wyHx!H0g#iVf|-(uh5<@~$f@GJB2EWcDgu*z1VT)iW`5IMLBu z*##^jp^2nI$vq=B5-aaYe`Kyt$gtTKw)J7N$fvH9_Th@H@%{LLGndN*8zp5~qXf`3ZK(=yu~ z(NZy%8f57-($LEP6q8?tm{(fB=HTmP2M_;0+`%!;V2W;#l#7Gcp~*(m^$6NJI4ZS_ z|93ceLuLMVICyiBDvhAx(~Vu;*-|Oo{{QFT4We@6;1Te!2EX1YxZM25ocuyJQpMKLc_hFiV+3u7Fv&wM+xQ{ zlW5+TR{we!w^fE-vAl?jOWRO-eM8hC{3=+T~W*OqaAi)e0!XRyBh?YSt z3=+m5;WEUNLE1A&B!hI4Azlp9g+aP9NVE*mF-Q!9^k9&lGDOcHu?*6iLHf!N1B3Ku zkbw*`ScZ5rNIZiKWsu=A#D_sfGDrf0jFBNm21#U)BnBBTLwp%zB0*YBGUFp%h_u>g zGMTH>_EkS&-j}?;X5VCgM+YQY_xUB8Yo^rfvk|g?8Y7e8Pq9V>%Ve-=3^tv?a#%j# z2IR5(f&)(%?ti+d=ILUWEgtl{KYTEAHsuPOYu?|=yf4VH2RuQuPnoOOCJ$A-waWa= zs{Yohpud=_2&3vo&<*U6|F~er?(Sf1cY4*0-Sf@Z1s)+bRkHQ$w(bsIXs(=6x%+u@ zWm;9`B0>hd(t0C!v3d8D-8Yt+cc)eEUe>GXujVRjvrtuvZ59{>>p@^3=fN_TbEPw< zOnH}e7bt_muGNe}d9SMFNfnj;j%0VGuVXY`AsT>JGTO*{a6QZWdatU#qx8DGstr^+ zQBX>Mi{;$t%-O8;O^m|3y{g_!+Opevul4)ps?d-R%wVt0NA$PtC-nEB?dILdyLTPj ziBr|R-MjVK2dc0=gTqJIY(dEfhVXl$s`jGTs?dGrDs1vlwIACvxPT8tRUKsD!vL$Y zt7;K?)EiVjZA|5}sH&q3?Ksf1*;OYSQ~5lq>J$T?2G}#Z>I|r0tB3<%VUq`!!p=rj zea+DR0W=}I>RX^;FNg!@%VJ&8zKg25z|bxNP0X&kgbfi6hW%i!GPR>>9^CyCF2l2{ ze#TA?F4V6?Rb6G-enB=}cGZoh+3KWhH_NQ9*=|Ku-DcVDBAYt9>ONxs8f*#AdHe}(Fs~iU%yRlD$6Rx7zbcrOgI^p;t z2x>?9J$UT+&<&SKre;dH@5?>W|W+?04peTwLv>^1p%7J|vsyHjoTsvG* zI8c=%f?v_8IVNq{^|~t!lAPgG>*5GW>~Zs}#C>8QmRVbRWN$v)+elu{lF?)ncCpSs~b^K~cyN zc3tJT57mB(9UFebs}oqTaG3X+tFdFlUcI>*J2vdm?V}wVU^z980o*a#v7wSovBq(N zc5K*<9UFGjjty1%kZSDMQ1y=P|6}jXK@A>`O5kN?au(uLSHQ ziqpXGTM((iS1RV+ir@?#efy(vzF*vUDR2l84)zC3;s;5ATn_w$A(A+ZmAX)>HzpRqK7^qRn*XraEu7S0BuA5l#E72KEulu`%5Dvmb zAsn!6vP#Uwafr%>Xl?FYV%w>JY=Q;BS>q6-!JyJBG!U&pQ|n1W`-IE1ay0#l7l^8$ zsP`4|6$dr-9@fP!F>X(w0^K75Cm63jVxyp4E0Zz8HK-#@_$zRLES6|s;;cZp25r$KZ4?G`v++1?b=EKnw8 zyqa3zPE4501D!b+_#nbHXa(-VL}Bf~TA%`_a)D`L*7K+Z?xxf2q0O*+_6p>+1V9r(9B5H%mb{+?U2*vghoo$gC2qLJG z<}=&JbhZnKt%cY=VOl;Y^qV}Xvwez>iicv0s154dF6JZoI*w`@QLQ~gIINF9#{ngD z97D|_YJ-ZijL(ScI0ib1+Mrg$O4BmL%m{_EN-qzhHr#H&G4Mjv29?eWSA8!;ZBXCq zrK^?)Q5#e`FLQ*5P~~|=r-P^s!$Ow_Q5#e`uU)k~n{+y_^O1a=muiAhJtJy^qPPDQ z0V0BX-5cEeZvsK-b#LRBVuJncs?Cq64QjF8yK1ozwLz_>9an9BL~T%;b~gv5(2H(p zl07=k`&X@QL~T&ZQ-X*ShN?7l5kzfJ%Yi2e|Y7os+(bPitiy%4oQrStJs%Y zDxJ?bLWIuCP@d0qI*8hU2oXAt;oT9nL8bG}Rm+2@4QhG5ziRU%YJ<}JKT<@7pwa&i zZvMkSP@4Z|{8G%eBUgRBqr{Tuy5k&?K`*-D^$@i|Ef%6Sz zx6`KOBd@M8oYe&-;w62QOew0~#X zi1{#F$k#CAOtPjiAkKoSn^QwKs5l1$R$0>+>^P7M7ZWfQQ_e}p^x4Iz=TS8Vw@x=) zryHuk9-Vd+(?-mP;n6Z0x)K^g46+*hkBT{mW2mamA5VEzjiEXJ-h;?my8Rr-k5pwfR1F&30HAK~ARIDD`U)mmAq)yi_6%Sz^wEmj+A zWerDN7$*>ip;Y)w13BU)^;fRgfM4G6qE{)!W+?Se5NV}>wS4M7H2y{%AMq6srQub2 z#u}7JV*LG7MyAidauLNr^ePE#MgfUS3F0+~^isrXKrTdWC=I;AQ5q1nLFJ8j4TzTz zn)ll}@3)xu-~I*fcbNBkI`8ewyIALq*bFMyHxO6gYFu~eTz4|pJ^zC1Ugo+_=enP{ zmgrn};Frpajdrdr{xwOIB1%JP-~dNy2vrA@1Y$FkdM8uOAvS|r`VX#JXOqOII{qgp z{VIXj3~H{AuKLcC#5a1buQ}IO^qePdEDe0gQ5q1Np_HOFlm|N?m6} z_tL;r@xAOSr(e^=4|06z&#LE_``&bM7-KZt0dY)T1Y?WW&>9mu0g6|lb5h1({!+wrSmTEIrxJ<@$lxQgOArI1 zf}sVX3ZhJdo(!B~=nQej06iV}g`tJwya9SVaFL-i#bsG+t3$MfI-(8VR~$uJXjorW zoH4&)9#_{`Nm=uWfV>j+KRIJ0v+9ibDgLJ~HD?UG5A2Nj36r^O_I-iM`2sN%VEOLU z{pnwjwQn_(oooXQMF~XMh0n=O{v2@^mOmX6NGDMW$N855L|*tv_hWBA#u^6)xg((iSVR~}~0$3!?L!<-WPE7P41v;61#nAz$ zY0D$N?ZUquQ)!A>q!$M|0xtZ;IMv(5?Azf+;+P$vvOSQBN0GfVup`ioDHe$yI>j(7 zHhdJgp%F=FfK0NdP7-klj3jT;N%moq^AUDHE2QwNFH`J&%=excQ;e5+FVLS!sVUpN zkNe&gW40sZ-M~Pz=v9p91X}3_2ebNv&MJdhEkF!`5Ul>x!0OImR!=mrx{Fy2(^=ge z%<5j970$GwX`U2+48>|;1FO+Gsju%2>GitP8`aQBrZV}(Vmbmy=$+`9hV%kXe^wMWeUlyD z=af$JB|6D0y}+|M#}YBeNO5Tc#krimOgv&tU*3>jMBR9Wn7lLa7-Oy!j~g*pHDLaU z(^rcpjp=I|(w`zq&l9CZjJZ}UHe$ZefcY$^uMKE=%mVm zNqwM`!o}wpmQRU~8qKhu@C^GYx(iu)FN)92t7SK#z9_!bhuzoa#oNhq`+4A7^YYh> z^Zs{cL_TRW?;ql!_mcRr>Ae3B=5|=+He3A6rJ5}e%pl|tJjUtIigIJRVF;h(^!1|B zm~I$w)ttUeoNiiyK|}bgUQg#Z#}aYDNU=@BaJj_kbHtTK^;pYqR;Rw%2JuhEds+O} z2rs~R!lKmoE5d~6F1rGM7G_JZWs}qQp~&k1^MZcq67^>S)gC>qHBSCRHG1KMo{XLeCVf09T}J zYfF$wY>P)l%C={UMIu?JNK*FWK<7pzwV9!lPSPJtva3$=Iwm<^bk|Ah)eyPC-%zX!Gef^%R)(2j0JFMPXEi98)exQ4P-gX{xGfYb z!_07pPU_BJQg`X3?q*U?iF+IM!27rdj#7KztKxpfdsU3FXwo>-viLn(nfW}BZ3$Kf z$5{~JtI@QOYaw-TlbB#>q7F`ER(UF`*#b9d!kh3e{0paqn5Kvsx3(YOT&{9kW^>UJAv^&{1CwW@YH8 ze`Z!2bXI=}X7!rRYBRHXQs8{Ls)-xk%Ft2&s*`#%nAF=kDcp$6BJ5qg18%o0ewUW} zJ`e1$EPvA|mv-Xjx5gcCH+R4-VsFz9_&#@-eR>BhVOFz6nO%;#U|UGgi&TQ9z8g!dieeXrte6+bZEU&TKf;r+yT zKdX3eh$D>mtT@&PublBtsCb)21>?;TRr;G@D0~_?jp`3I6i(^QayGbG&gpM_fhiV= zOFBgw3ZDjQ8j;k7!WErlT`Lh<-lJiACCu!(325rt{C5aMhD44A@XG@$;cAeE4 zBysF`R3r|9AfQt%5N>OT{%IHr5y7ksLm`q`h3l-m!K`9*R&mVgNf954m0>6}*GaVq zCe=zOmBgf;5^b#7T-?sO_#Ik^|2)vay8Lg(p^%I#6B`eOPF9+W-x4X-CUddhN*%R} zRWr>~nbmC3&8pY*POrgA>cQ#Hik?m1DoDjj(+l5=b1V_PjT8+k*3FzgNA$G@t3U_$+W2Dn8U87_K+S z-5_Cb58SI$ypJgsi9hNTX%KuC7}JQPHV8)RBr}6aX6qz#nB;tc$TUr7PQxI`V^X08 z!DQ~46LnTof>}+|Sv|n47Kj<4SQ!SvtYB7#K`@(H&D2@V31&4{XY~lPdQudHVr3Wv zkLjcq1e1C~C$*4CJtYvGMs*J?wu+}?5K-${>$O)Vp0hTrOf0qjtCb1b4z=96(yDpj zR$H5R;GV~L3i80cU|k6Xh&*sFB8g8s9F9NyqvnCbo+0+Y9fnV1z18;vJa9h*Ube!e z@v*)n`U833UO^wmUPKzMpq3>5V)Y#&4;;eCS&b^!kAY2A-`6zEcZ=7png?zx5A!|Z z4P}_~TI)_16MmBi(_8v5-^P_WTfBooXT*Dt@wTgYoy87oum^4z2Jw&Z!0iEs=7IYW zzO(n4-dwR?Z)Xg#uLEUh)@WSivt_U5f&0Kp9=KAyxeo?6_eXkjf5H@t#AiB18e(4u zzHCI&B)(>nU+5&i4JP@$PVxsPIbZxkC#k=TZX5gv58Tg8N_|b;1NS35a7Vb`|D>}z z7R;(#XLXWUEfAHVSgAvdSe*)Hr4BJ-b%t40>#TkWW_4a?b&**;DK3X%r4BJ-Ril%t z3nukXoz!nk>M0S>hnQqr{1JKJ4h3Wzx+`A|b0}c8DIPdu=d;*I#=I|VHuk{jokTU? zsgu}kBzzYOr%kVSUA9oVo70~a;kJe;P^F{GmLK7P^Vl?T9>qD9h-f24)geF>V>x}9 z@EOxpGn&$y*)%gcfiYK#7Di0fj3&%hoW5El8PioWn$p`4B{rkmG3Hv)-iWE1(S(`I z>FY$Nrf;cx;HbU)w$NiI1#rc#?uvRah^OoHYU$1_ip2GySg0O2Vv*K}g*KLM&{^~f zX3<+`aWk`+FZzaJp^7VF(T_=m8cPFgq_f?svl#i&rM3}fkjoz$3MQkgobY$o-T$gu^_u;Xo{a7{pWQ9N*Y zHr)d^*``UhDYnH2$pd#NFwJK4!2KAQZu5Q9c-}AIp|?*I($EX$Hj}x{Qn}3*v$<5W z#T;YjRMk7`oO3z-S@DQ5-7tiUIDNf%tZ4-W4bcU9Jv_lVmWYK$iiRQjG^Z~Ui;d~3 zdPpUDmec2mC5`HF8Nb_&+_1cG&{U)#zy)l%O-J$o=-NC(duiiTQm|~GA(J7LY{U&gr5lL-kDAP%P7)K?y zSsm6{9Svr6TxWHHSv@H#La{Q;3{^U*Q^BOp=%jvOQcsEVje6ik?tz!o9{8%b!g#NW zT76omw=MpVJaC5szuJP;!QX7YZyQew0iG823(4MOT0r<`OhU3xVfKaRq9d(dSYDh4t zp*pGCnAB6^4!b6o?zAudfIM)A0(aSs9=IO^BkaEK8h5~Z?4%Nwh>=Y@;CKdJpI?J%e*D5i=W=J%?C#UfFoQ>0<>eW1P(No`ov=_G#*Ci%NgQaDKH z%oiqyHq!Ja2~uS&4w7G?hK0>R62i>s6Z^2uReowVgs3Up$UwtR-Yy5q3*4|{sMI_TOHDVZ*IcuXKX4gYDkBfNnN z=hxEh2umH(byn*dkKcM7h4{vw1-DF?_$Cv&5##^hgBvQe8&YO`hFd9cO9eiObjt{R z)EiNhTT;{;Lf*nn7aNL9cHBS`xq&}?BMRN@!MB0XjV5mkQ){{z#7wuQ^r!F89|b3L zT0>r<5p=UhaCvYW2X5AA%{QO)MY-@>xeX=Mts&E0xV;0nZzwmR7;Xk}Dz~Me4nIj( zag^Idu38T&UwQ}n(0kM8I=aPVAl(*&8)Tw9+KngLcj((sw3|Zm{9{NDR6$2I=%p+mWuIS;7xC>U$?B~q65V=Y-he)ZI>$%33C9B3_UL^Ab>>5wZ=XP2 z(G4`I0{w}~YfV)8@;-rbQ``Ee%>cJ2eqd^EPVFn`RvWixA8xI|EiRI&HT_i+f70sv+I{FMZKt+ZLm#qV zv!5&Dn`DH%Z=3{~;vl(#uxDKWvi>AVsY53X!%yk(<8E1araO{t6wLe`1T*I!hb49VQeQAX@E+3LbAgh^&Qk}XdB zv%1T?wh~wpru}2$E< z{>kPtueoHtMJt{){B>MNBtKYY>d#P1oo`MKi?v$FGZ75F_>&7PsEm^f`#Zh-=3 z{Ac~0bLi3xhWDFUFk@PQz@

67KboZ*xlSB|$9YGfw%uQ@-q6x@`H1pc|8LKU1Si zlQQR`9{#g?jFv9D=G8yFS7jk`KX*5?So%7yAy~SZFPwPfnd8rpdNdw2$ADR1e0i7v z*&5*K-`+gH7}HgZ0_T?xF$MvM8WF=iPjZ+GS0c%gE;+^}$Gfb1Y}7jUeD%{6E9hm- zn2|SODq6X>9B-2naGM2HHg!`u$w}jcxXBrN=`@q)k=lCZ&PyPI*mDlpDv_!o&j|EWy?zIeJ<)(zoJ{cgV7-k z&d5wevK(pSTY_E+FCNalerEOz-1ahc0@83%54}dGK^OYcg(7*ll4bTCHa_cnR+=TP#NV1`0kMRGU~85S%^Q}b;s@U8oZS}#U?Mc z(EKrd=$y}xB5iuSv+1A|(bl;}GWkqJCRbZme^*;ag5z;1Y=snQj_iSyn7;N6rbyvj zCq?weMCKadN_Ip?B-=WKJtw($n_MFtZR}em%r%~vg_5I5%!N12(Q#@O{8zzG`p@g>Oh>l#a>Q?87_SZXO%b0kQ%eT*e z+1+eErM^Gr%H>N5n8fO9uC$B^giDOmtq!r|5fu##!&}3Ywn!!3i2d0$SK5^j4oVf) zA+4q@m!o4$;6|w|tFCjLX-#;XDbc#t9%sU{Km2tz1CG2wlMDZ8X24gO7BOl9W`&=e zxGQW;URO08dS)W)@cond);TN51=t0OJ_NHtng0a{y1tM^0kTnF=rXpCo9Cgd!f_J` zhMv{~J*_4E_GED9v`(?en=nBS&7t8az4cOO(-O&%=n~F+JWrt3XlRinG>E@WG94IQ zU&q%tDh5r6H(7_pJZIuLFv*1(ooM7=@XzyMcX>b!nYN-V#Evg zIhns|W$Rq)T=Q)6CPc9f6FqOZSy(?&qy6J*i(d)a&tZ!wlAMdBu!;1PR4h6Aq?3295FJfSdQBuMSO2| zZ?KyCo8S3gD$e1U#>LrWjf@WSMJB|gM5cr#hA))x0wS!rd7Ttl=r5BZOQncI)_8M7 zQS*48`v~gUbJY9=YCc#TpMuOaK_17P>L7eoel(%4dXCA|&R036x~ft>5OXS7fm;BV zZ7SSeHrH7?*Hz+1>`pBwZau9YU0oIKtK3%Ui6UB-XkT@duQEPxBGOlhq*z}$?`|oV z{Mga3GQwYOYvwB_jPgtImrlo=!Y%jZ(We|@i`{q3f6URscYO5mW37D0v&)Ys;W}_i zKkmQeXyZFJ`q-s*zGK)2~!G9VU$a%Um=k#kiRmw;GM?o1B zj&@}V*QJ+t8s2?>%qd)R-<13HOydTkf%M}{8+lKsbKV=%%X@(P<*DU25qF}XbMM1B zZ_#o#;ogra3`j4(dHA4x{#xf?9MyIG!H43xGw6TV?esr!jOPfsj}yT$QWa#|O&&Ri1m-bH!EN2k*x*gu*&Tp|Hx`2^9#1 zRleDE0->-f5DM$$W@3kCiu|Lm%2kzDRV$6A$EdNs>UrL*j=sw1Q(@x9%h}y4qsQ^j zE4ke(W5!dzn7CJId8KcdCvnT?Q_D}C&MjXM-13FSmY>BfKU;123-K4uUZv$9<{F%* zwV(fj<56n23-h_=9#1cyqqO{=^p-yh47B{&CpqU+TFxeQzlbS3lV1MB@aL}9@=KY< za;^RRL>t`CuH?L{)617AE&sgU@*4LSIOmI6&L-T~GliGa%hwM7^FDu#b7SL{f0bK) z6Sw@?*Qw=ej%`(1{*7z3{F`dazx^M!e2w%sZuxi7^0C|XmM=!j?@(HP7g~Ndwfx@Q z^Wye(^i{j+yI1@6yXv}E$CpN*iV$0xb+2xIAo`R?Y)R-|-Qq*n#qQND|L!{9y}H%M z@zv*&5zf!e<{G zA#^>hn;e3mwQL-e0Nq-7fTSU0J@%ewc7jD99m&WfY2Mga!QETX4I8YJ9h!AA7m|D3 z!mC>+y6F&PUMvqXFIKEvg&SO#{m&Z~D^^M=OZEFvm+}3m%lUrP75!HBlX@==VOYQt zz~ylG#NZR}G8zf1U?QNPFb?DvF4l&i0}mm!`92p^WO=kpUZbeQ|B<1v%xEac*SXjp zSQ%sw$TzuQ0xZ9V2_WBw5}D*XT~O}jyJ5zVE^jDTNNxR_1u6eeTmSz(Z|;cm99MxzVr)VJrM6%DD?SJUJ(Zr9?(M zadVL%>!r7Sdi;nmpDQ{>L_F$VXA1KrdE&|JcFk~ZHH8W5Vkvs7$sw$V$)bl1Z`AMO zi<&3-VoARjp1)WQ;VW3duhta&YAE=SqfqdxSixsG3I)F!N*uv0fRlpH@E`?W%?Sm+ zYIId)xUXVc1r&Vbfr4KZ<*R_&8R@G)QmpSdEBMDH|8Z9Ek3+#fP8i3b;Gc@ACIvpa zniYKLwyGN5iq)YcM3SNOW31pG9eorE{?Y7XM?0h+Pw*Xwf`6P8e5T+(rI-MxI&)6H zmQ$sC#D4^oLE%VOrf^;QaaQoFNr7*|{d%Tx1JOYGF{X{Yr_wp^jp@gGfcxdC$4S9w z3Oe^bobwhfXA|!In8JYcoHT9}4+=j1L&3*?DELRm96fSsIKGb89yKcXCwwQN z;GckA1O>m`RY3~=aepN%_|QZvNx?q~1^*~1_{ZG|C!ydU-|Q-ff`1YU{s~s_(M(YA zD_xa&m89U~5ej}4EBF=B)rx{&0R^9aLcy;G^!L;8V*#o_>6e((D=mLUQNqtW$vL0W zayF^^MNHwD^y94Hhi>_$Ok=s$etx12?q^nV-qq>Hmnbby3ck|v8uu4C=Zjj-CfwIE zg_qNh>k59OmVcF7eiOI+nb)c1YmOQf{HwJ*Dfra#q~QN2El&zQwfwtic~bDzmM`vJ z0R^91eivGPH?_Q~;8#JxuVMwi3JQKTEBIAV@T*zDuVMwiiWU4SDEL*Z;DZAc{Hn24 zm8{?=wT52ruWa+JuL^|a?_;XN*_F`t2VWH~La6%3^DD{ChllpQ%IvDj!#fclt_ps> zst!N*~i;oILR>*)H9F_f^jC4zuK>7*mOu!Y2dp z?pIO_ydJ_N!DP7}b9RLoQ-$z$6#=sZBW0zudzGiy3N!xyHX}tbR0a!EbSAH^lEFg; zQ)bjY)d<4!5F0OHRCX-26q2?8<0W%x<{bK&kii!hTj$|cis3oo1LRw2jblP?;lP>s z`S;~!6$o7U4lgpUA;06(W8~mQ+HJU%eWLu3OkV7ip?Up@(4oKhO$#K4aBq-YA|lO^ z<`Rw++t9E~M-TjJZtW{X0SP)Hq5LJm-I-TcFH8sg^^&=4bX~o*%xg-kvzPhnWoPN= z+In|?$&pqU&d>-z%^reArPW2huJ85GsIAQWJrD@=&zgjj$1lCk#uZi_*5s0}lm4t^ z%k>?pexMWX8;4TE`>NE-T%d5hz=0c7F?m+1n3gf&=V|?dE?e(6ZCXJNT&v0g2GNGM zH=CUN+MnFcE+^TkcCg&Wi(6l8@td|vk;1*u9nm9F#1D=Xu}COLMV1B!F!{;hl81j= zt}z{$V=}iZbF)7lS10WoQzKut^sKA3g~2}##1gNWVGe@1>KbZ6*cL67{j| z4W&+^t!u_n6Vso?lj=Ak=??B#wJzo%iR$9-5-KBubjpbtnN(6(NZd=hx36>Ku^=}q zht_27dT8o6dA^xC$%X2#T97|_{EYrtg;~e}`)<0m&Q59F?(L99ODYrdW)w~zNy|!3s-%QuOjHDhySyJjD&%ZA=JKkH^RyJv!`9v9wbE_YE+1A^g{!-J zfhna!8tH+Ny&WB#yCqwy&1Qc~ib%61yQ1N`p5fkLiulmBSX$_GB{uI7lNq^Oa;`8@ zx+B^p!e+EzC%aN&65}&lFOa-CG-|8K^RBc6MQ9jKv?c0&uh^S?>7qFBNLhT~5{8v& zZ(Oe}rrl#M)}J@Gk*!!t-s8Vuaqdl$-Mjqftl_&ypFih8@`dwJSbDHU@7{Jk7I)=b zus7QqwL3m=Hgfmqix(1#SxG!2LE^SteXswFGqD({XKLeXFZ<89S{KKKZ}*>ex1D^Z z{o3o}(za)v?$GUWSVw<-c;{kb{~p+q`zONhtA>XX;N?Ul+bJOxm?5;^|z` zJuI+u*CNYK_b$(CJ6z{iw}}s&@n7)tSl?{>=+hT^Zt%Vbwds_!$W`o4I0voi%x2fw z*Iegbjx>oycGoYPUBA3e#Yf4#X?y2I_v*MiE^SxVxf{Em54$NY9Ixtb+x@(&554Fu zR4%1jP_CW+Un2aw#+>`5uHUY)sHJlQ7Tv%1@ryC>&G|d2xd~?vA;b zCO~lrC=QLe7%nuLx4~t0!FEUdg&Lx}C+1==vBk6tly~^gM)-G*`Q>cQojb=~s6Y41 zUCkk_v@L3AI{uSqn$?MG>YElZ{5>)o2V2)nr!Gi;e{cX5Iid zRZ-C&0ZVnjZDq&I-+s9u7Z&zi;ZL37qSayj2tYKfSIQXHN6@vUw=yzE5FQ*Kxo+}y zsH0WJ5;ad@wAxEUx7mytaWr(NWlzFd`HY64ivdcj!89w}Tz!xSOa3&>3I)@q@QP=h zO@7KOU*=U}tQBL$80*G=m*x3R`A)MuK$dSZ%ek_gB@>07hYI@A)YN8t0R+t08F@Lm zywG-R@ms^`g(har=$DUmWm*{ixzx9m#`VlRfA-A6!f8|eS>p=xX64Eq&9cQT|H&-x zVVZADI+{;ozD;)4xXB7hAz$WctbYB+izsPEZs7<_IMZfAHd6h3f$ABF*+A-_!E1Rd zG4m*Xsut#uy9q-1>*SZ+*1uX-(ko`k@PQdtyVX4iA#>eiCaE!UXyIBEO#0^eE~Y|P zsw-Mu>h0jHlx&GH!hKrC!dG8hcaKDYeOCVx$2}4BJiF8IzhTCR^=+XwU+mvVGMTV8 zE1UPnMr<+j+N@Ps`i<)V1Z+K-V|BM~Nx)TwUBLm?#Xu_ve-7adn1*3*`!7VQ`dHQ0cY!Hf5*OHhB!KaT75*xIt zTZC+yJLWn3K8DXQ^m&pE_4q`fM=V6$VN0v%gWt>WK?Frke;vQy$H$JfRZ5rI!5xz% z^@JprR{ZEUEr#UBv<%xOVNp{aPxnR;w0iVH7x$=%MF ztVyyRlE&;T_Sae5?~OiH7yjOuGpA2^N@E>cti}E_wx|;ImpyvFzt#~8nA2D6?s}#f zr6yq6PJy)~SR!Ed*6(Yuw(o5_)jk>YU7d=f-ir@ZM!q-t%;^+g1xvpQ$zS2_vbwLo zA}rNcf%J-t@fXkf&)RzUPP#6qpX`pWnzWPMx>eYE`Y+mh;qg-X$#i^ONjsU=?V`Q+ z(|3rQ!vel4Up1TrRakt5lc3Udikt)${?p-oU@D#*ee(1zFcnWg+MOULL4`Y^8se^E zv#SzLf@(Mksy6#;Bl^*M4M2GZ4WaT5HI(-@V|nl3^4_VG7k2$Q|2f;;N_j^p<-H5# zy%+NSk4kw*DdoMd+Xee*ly~ek%A2W{H~U|eH?9N9=*cOjdgkUDA4j$9nFvTkKZZ_dy-6{3OFTFhjV9vliTKk*e6dFS*){#>T^fj^EDgj_T%9<*zfO-| zP~*h4*kK9P)iSQP4lC&Qs<=_bS!dQHC9f@>-%*&hyWbm!L98mbzWn?ly ziMXo(7Ns0-C-Q4h%xa>WmXS$TFKIy!n5}!D3qjYC4H^YBgnwJ9Al;z=7fvy1UuD@+?ROg@$Pt9aLanr<1-9 zprcBz`gV@k6bNOLI54KZPB!s_Y^IG#R`hx^k}UQENmdg4Hg_pTg`Guvcq!0pJs1lP zLcvZj(jT1BrE^@dil~ocjCk}tO%vA4RZX7ne%mG%zh`54o@iY&zB$TDe@frU#CKox z8)&?M$-z5wml4R&AzJqR(1z_yp*jSsgKB@^4zU6)MmC<-V$`@dzgUF>n|hDvh905z zh2V}L$Aqcvz7JG-QNPl58eW#wMu!%tsgY2Gq34C#ml{eZL?0yExZ9L}AZjKoYZ%B5gU(H6AaDboud!ndkCuES4hMfFXlb<{ytu>5syP{eR$aNj7``-bzh*(Mxq zBh8T7yhClB6xN?iRxEj-jXYaZLDkS{T-<3~amm=NCy6rb4U|p%U?N{mtre3gHWSvG z?PW<8C${cc-6fddZ17oJxg5@0_&k_}d2?Y@SzIzO9ML6noU!c~BaSiR%$e>oO_rmciI1P6}X5_waMg#iNNJ)KIZ;)>5x1$*!%T9o6g?tF>ai zRkEkoNrQNkxKl?72a4>W>W29)%hBm|VS`NWZn8Nxs+(-_;h(jc*Vaw8C-`MY%L9Jd znFtS>+`4qWOPET=*2}duZ5wRT#v`!+;RSl5vX)87%O|#{x21yOfQ1>;w)SnZC-U@9 zeRD<7hV$is$PV*TDQD<|#gzNdwwp{_?OI*A(jtYcmF)%_`e%(~Kde_7lu1gs(Y8ji z1tj}vs<}c`Tp{Xhomy}1Ut^Sg>+a%?j+F3B&%xl{sn<}WN_4<=W^!rnw6I>adaarx z^gLeFswIYMH4e3EX{P3}vb!yzS8aTHUDO~`WH;H?5;csYFx_NFYp=7L>}u%MwQO@O4J0{Qrjc)Ky2qN(U^5RU%fG2NA!qo z+!(3LC-p>iQfqM|?4zzKCDLwH5!)A54rD5M3Lr;GE(bTtQsi1nOWuk2A+3A!Cev>1 z1i+emCJ~S(JmoJ*-H(bQo6HBia_M|CzYdCwo&DPUtznZ@zUtTT+sHwdm6LNT?Cl8! z(`HW1!L#b(_+*Pse8uk&y}ZehfEAFQ61JM+z2K)_f?S%sC#zN4P%FP{lb>-e)z2}! zv+!n&Kbx>F!9GvdV3*l>2u7HgL_(K7%3s{g2eRr#9Q@0Ib|{Qmx%Go@QfVpHDtUox5ZjlO)T zAGwbP?DvMTpJ!07n%+cuh)Q}WlfI2ehZE^Li1eLC(j$oUJ=pEfq(>S_k0R3d@ArC` z^q5{XcM<7Km2@_f&WTOIoUJGx<3U`Wc)%OUDU;SD65A+Zn~xOl)C1n=Qg4hh^G;vX zQco?Q)Iv^;RZ?fs3-{)nX^?4v*xFTrfQBBjty9m z!w0@vQ=;O(1ss+R&DemkXG%*`tRTwQ@PU?#Vu(0(rdb6xG}GKd%QL+(T}nMNGGhUR zZ&hDYfj2j3@noi~FQjN*u4rV*z-I|aklS@EK4e;K!0ZLbmUso9AMp{GGbsHG{l>UO zj2BL))sry6`rY^p&2Bk^l(zG^~ zrcLP5v{g#e{;H*MG?XS%m$PUk{*!Yx=~RNg=lrrknN3zP6lE4xc^Rz@z*-{9JlfM$ z^?K4-#wfKftwt8=OZTHIO{d?{8JT3xQdgQ{T0zq_jye8;QkERJ*|g5&@_tcMVeL z{pK#se20FfW@J(!l&9BthWp>tSs} zuNU3wO{Q4o0f#>8%~rOyVP6xFCov~HPOt&$Z0-Z|?BN|57QtAKNV3VjaigH$#Khf? z|8WhPnO<`*YBjeOwbm1%u4u+0+d3Y_qCbv;Ks^N;u!}HL5lpqMwKJN}14K1Bnr5Bp zAT15c$ZSh^`$;2{5Af-~RhDGV4R^>rS@|<_ z)21-27`gg5cbkDULPdra$eD5|6W}8o!G|?^*79lJz;_Pw3}H!6ZKnL`<^dgdMSq5Q zU?!M}Y&9#v>;!Yh<>I%DV{$o)6f=8BKt@4|S)%uQtt`c22j)bi*COu>4Td{L7qM>Z)#y~fl&H)o>to4CvD>NX`5JT z^fAr4duD1P@-%ijVmWbo-ng+)_zSW~Kc+IwUXi(#SgErRvHak%3ma&-!3MZDFQ;%K zsp5|vOim+J9I(N;c@riU;&Xxh_nvQ8MBLfGD z0ru+K2NUz}#yWYl-f{PcvBQS+8@ocCN2{K7=fb}UW_ zD<(T(Uo6cg!P7A1M+Z`HoU=}-zQgE1w6ui)up#Hk){cnX(ii7C&2+BQ*1gu^AeW&_ z&14Ou`79zPFiOH)78w(`pT51<@aRm!R_f8EhP-iZB1EzUDF4{q)Y!8OnLBjXY&dTA4jUSX~c#21Js2Em_5fb1gyl9`d20w9S72$0*t zbzvbT4j=#7m7ddSEwv2>(pVZvVidfl3;N|u$aU5do3(jJOgta={0tCltyWSg*VgHM3Z2NWx-;lN!kZ$wq|8e zE$k((`T^_41kziPEBWn+^u>{K^*tn2FX`VLCcROPf{Vyp(X_$3QK`t7I{z&iy z3nURqX5D}Ap;?H8ljbzybv2BMgBTLD0ZrGb4%77PTwINtT7(!f0#XFuv<%SlhKNZt zuty9SIYJtN`JMc-H1|T6?FU7+pDanT#V!w#h9ESBRWc(w#cG777%bn7kAwX{uh(3{ z%{W5bQjnLGkMCgFLW6Iuy^kACI9k=}hpjjT)AH}hn~}#MGL%xRK5`cc%_+!ErTQr8 zrsw$?#iZvh_fMtG4`K@JNlu^hF2|H0-MK+LHCDCR@MuuA;bLD18q(sg`j|FIj+ExR zO_3|4n19Od;&AMN`9=!*w@+CC%MYb~=X>4>p4gH!&!*Q@I!`vk4fwD-F+`^xTb)Quk9_o4l&Vh};1kc2Z}?kGcCONzCb41v7F7VtW`mi5w}( z8It?HEWq)1q>t?2up3XO3@oCqsX2}*|EME#mr{O(#f-c;7?1?-_1*qQ8sJn$(5mH+p@phNmW&lI|X55I19GbN%9b`$oFm= z`*1@^v!~_fpd1Jsb0?Rhpwv1zOfN@XVHTFIXW$tlmR`m>efc{LFGD3PoQUd~Fp*Gn z`3D(#4Z|uA`;l^`-58S6uV?$17LrF(lU;pv@9PX~VnxCT4G@_hpRd?kVasLbIdjBd z_31gufjvnci)06@{rV<`$04z3Xj@Ag;00s16 z*g`~yr|1V(9JS=g!A^vLAZltdei)Pc!`+1B!CEqY7?LA06yhVbWc)xfGBg(Dn(_DF zG8!=zzbuntNPS27Bl*M2c~>(VWjoA265>vB8oS{LwS<>=X|d_bWsfSzL4%FKXiOh+ ztP$Tw_{~bZcE(RA@j52gv|MZTIu|7vsJ0@kBn_(#Q*D>z>Ih0mF*{2}*Ielg;cxLn zz_W+CD`^YY4IG?|Ff1-*ewfWkPTit8)Af!oSmqe;483UE+;l z{CgSyKE-^dBLg*mL=Ma7GH)!Wj4g|tT$i;rfe6Nt5|E8#Urw1fo|DHfYNn@5L`t(s zT1viw1Xf7n0Pir|BD>OOfS)#c_OIc>1xq@h}D)S~W%{BDuYZa2MiR24J z@~n&3CfJt*`T_1QG#_L*l~iLAXtS_tZE}vKA>rSyCG5+BCa~QBQ_yS=*B7) zNAWq(!gpWDQssLodGCcij6C>7!iNZt!ok1sta|(PZ;F=4`6zFH_OX$ljkg!@eiq1Q ziw98;|1))x=#yN>w2pnJnB5^KHlWBP*YhFPm(g#{qYD8G&8*Yf)GP4si>&M!teO4c z>wz;#pPcbPLE$p;^=43>b=&)zaE>85BEiwz{j2t4g$agH`2d@s2!IGhkvB6Bia#Xi?gqhz;BkcCE{GQS_fE#fiPHi~UFW_BLHTNYZL@1ZvS7t8{jwFPNf| zBJc+Kb6+^sJxNqz)p?@%f?olMYPB7i^3?CN#cr)emHLI7>{}eE8=iBe`UZH45Iruh zU>t>TgY6=u6);`~0?72QZoI6L&%s|M@uok`jv<-_pKSP(8`)`X`L1!$nWjz{IfxQs zT|aGXlyKi5()!MaQ#)^a;Y5G(@l7kpn~*nkq>u-&;yLEovAD@b1K%fBVFkXQ{3`<0 z(NQk>-0CIr<#4$!T>dRwmLg(2mK4l5lGRS0pKQ;vm zUb20V$+kDM20FokxWOgaHE?7nnQ1G&#RH{e|2$h%$9Rr`VR!n9!teGBU>l$eZ|7TdJ0ncNkqz_f0A7k3}K5%{U$`V-26!;+stHJsbs`*7uHCS5bi14j*Dps;XEr@U~JGx0Se71SyQj!GI z3#LuSK=F8Hy|MCh8WQ6s=8l`3n^;T!zXOqN8N-@CjuEQt1^tb2M)51wm z=?rF=YJ&lDd|X@=jh7(I22%|05h33eA>SFH8dG?W-%aslO_(b-m1a}{Yp0`MeRN~O zs%=Gv6?jLDynR@cQX1?l-MFG@L1ld+sE7cw{s@qNF+Iw~RyWx56kw!;paZT%x@)eBo=AZk98 zoW&;F2p!VfQ7Sp#qWqaL2qQ_}%jOTdKOun8{$dV=8V3{#lZRSD7-Vcx<-=y;eE5N| z7Fse4vNcq0GO1LuLCO8G(-g@$S~7kZa>F2F;)Pl=esIhTHpr+RXsOTmO@bCQ4My(02(P!cyXNKr&U8lY8#j(n zZ#AM0Ak;x6UJo-KGN4<3Mr9W)+jZoEjk==@zE@1U(fi4Bd}r4y!;vj!1g?nmE;7S{ z=7-eL0Kd~93?TyN09@&J?RvJ0F?c?4AGLNGSnJ|ox{1pA6$Y`T3zu_te#-M z6094+elHdN+CT?P%VlD8U7!{R&Kg$PFg@^}yi(JVe}^tGK3&lVcEcbnHbbMLKaOL~ z#-O4Auo9pt1G*tM7)Bkf87P}7Hc*&;dL|<<=sU>pYpocY3SBlb$-u-X$cSVb2BY#? z&nT~IB*v>o;zoc-DftFENe*5~8kvzvb{sTzKgT3W<4qjupsxI9hELy58XvLbQYhJN_0IL@>@p%XzS|XU@ zB*h)-{;IJ0jkH)!YuIF1&@MLU>a21P8(Od!kJ2|MZ!v8!LAO4{D+sI2?$b#@iwC@x za8O@CNTP-AVl!*g+7=!XA^S*z(Az72kRZy|rvJ@Vgt)%@y^;H}uUt;-UK7=t1Xf#K zQ}C+$LugH*6R#=Y6m63|RY{uWy9vE~)yZO1}3&+BSn=o|>6jJuUw>~H@rv)Lb z8W4yyLDIu+wiW(PW_sbYY5CU?Gr)$Ac%p`2u;_TN1Z%s650|t4N6W{Y&_JJ*zn13p z*)hn&%gf_f3vNIFoHRQq8dij}VgLbd2tpM%E@efQ3Mr&9mSC+`>WoP}c z)2+WO6Ei5|^s>sj;M0qT;8IMyNAB!#xuo!4j*#mj?cRc{ z>6C9KZ&81?>)2*AP}_{pn|&9SXD~=>Z?}PrNH!?GB(Cvftdep|Td+wVMTpK|P zC7?EH|LoUT^VeGrbq z!S+7y2z=A?VD&pkg=^2l8l`La+BX>p@Ety7>5C3R{aE>9Z5)0QEXtUmeM;sMuZgu! zD~`hC*W1^&kF)Ocn%~d9e91wZ`&@(>225*&wrxLh(H&lUe!W*gbr5Pap~ey_t=w3# za-;Y}6Be~4a!$f&NjQmw)0%MFYB*rH9~J@TdNL2(oyfV7La;D`bpb3q6?+ch#z8m& zY@hCi?fFuVecp(@V=iC1*pmyNM))4WC!-*|w-Ntl!tcA!8_D>$GJgN0sKLrUMj>ds z$sbTQDPzCa%iB$Er%hbL1`ZyC`21KUioUUCV3{|jcir$3cYe)XSS5*Ys) z+^UE(KSp?ogtvh3o-h_^Au)S;pErq%w3zXqCH&TezXbSgmX&zh?jzFg6X_LQFRetL zcB^3}wS|?`9Ih{1g77@$X-|1xpgb=I^)WX}yCH<|$+lfp4Zb{_in(bsV?DuviqQywI?kw@9aPn?48Pt@$Nb&E} zQj8T_MkGHd@pj=n2TQzNC)a$W&`c$o9}~?_wVa(?B6*%j zUeq#PR!DM64Uw$VQvOL*(vx!h2KWtu5;3|aa3vr~1W(&1vabX#P>8&{ZCN(`t zOK+p4x8wAi)$|TpdPgn2GpF}a)BRd{S1tWIPVcLxch}Od*V1p`^jp;QbS?cxE&V16 zwFErG)V-+p69QN1|io`;HY*W#~nr0`FZH7<7;+oOs!r}(oOQOve zbRao3rtH&B>;22M&vN)&OaLP4i*IK~4svV~-68@`idN0*()11k#0z~Td z0%+GI^jixraF{%}&E77kClBs$#AuR?mXu*{*bq64PYIqa{{g2cNM{?Lia%^0lazxW zSyJXfz_C2W-(@w&~WYA+oY}Dtq-K z@6%#d8^*&WMbjqdQVWuWur$pU)hKD~gS3YnjM_=WCH&Su{=FeLZCaW+gF4E|t~=Jy zTNlg*DwYVaoBTJMvL4Y?KZYUH^rXhQp`OzSngs3mxqdi746UrmcB05sefJPfUVcGa zj{0kwXJne5_FpFF5lS<*Qr272P;ns!a7wkN8|+5yl`>;i?_sGbq&LR3?YRJ##= z5AB#|PsWGZsBZJ^Mfkmq_%{=NUs}zh75`fqzkl3#;6)77H!GWYvodLq7HE&kS61S5xV%wmE;4=nZa4j^&`$1Ee*3WBvHJaPiZtRdK1g0&*pI)c4K zuni$4;GNpkv=`G-+7xAKnr&S(_Z9R?`cs^S5ONJdtU(wCIU5zHAx92ic&0Lqslv|a ze7xFkRVZmbpG{yNS}4g=tk;r5^4~q0*daI0wnO%h$m65TQ$n!(AwP-p&%aw}gQPP3 z-90sL{Ir58cpgN;X&DKpT`J`u~^|w9n9}6e#7{+GQ2eno^5>CI9gQGAbvnN#&W_iWq)oh6SRjO}oQC1O8 zz%cCnWdTKPW8Uy2du`OEV5NQ|4t_AzHcV9)C_2^lgq5seAw(k%a6zD`aRVsKiJWSr z+KupgXgf=LGJaaz1mK1BIuMpq%I29O(HhB;fjKv&*Y@TN{fR*Y6?-5udIr-uu|E(j zl3*DGyPaUe2*$_#hZAfB!LSmE1xvykNw84_yPsfuCT0x5G6{wiNVqQuFNa{`2{w^n zu>_k$uzZ3|C0HE6rW34yV1)$p5o{L0W)o}tuRJiG2#MZ+Ni{= zv6#kS!-~;ERzoCg-)%Y6r*rT*h|dzB(l;(f+?W-MAEe;x=17>`C;~8>j34vuUh=go zvq=A}853_QD9CyUp|)3K0*oDtw*>{-CIPUD1i)&nD3NA6$Nk!4q}di?xrUzWf2_>o z=K|&ViIYE0pyzD$*?H>=EBP7tw6)8)udA_{L(`l=k|0Pc$JyFY-H*+jR==_&*(JNn z?M)Y#8cwTR^PmbU=$mBl31J6OIB**l}3JiNh*x<*-UP9aixe4y!~d z&-!5%+{H=h`eBt=HDG@E%*h+-4<-sG4uFQ*Ss~B7D zl!d1bq{eh)W2-Y8TiTVJ#~-84#D8+U*0-NUzdl=}^ljs%oUpYzSW@YNai#BSE2aqN zYA*tvMmEPA;EhbvZr$x5zk4_O6Knk^H}dV4J4>pNu3+721UryVjD?2u6o zG9;F`yVkVkOl>I>8$j)eNwSd%#kR2+V%yO2XDZ?I@%L`PclV&)*ppyRKy$IHu zV0=pPW`gx4*sTQPQ;PiwHjrS03C5=s|3I(|g56Fq-VQU2V8aPEf?#~g;U0pGB-ki| z@hQdo2{wjc@N=uiOE$rB2sWNzd`gjyA-E?IET3R(&`c%Rbb=LxG!9N^V<~mMU5r7H zV)9#1KpJ#j{v$cTzKWplBJ4s?3){xvluAh>Y>NBfZHcB6m83_q`bvEr=arMeGpG``*TVkjNhO_9!xM zUcHecmfVO>_r8zUz3j#8mzg>D4AyDZEBj6!Vh`>i@9(|M@WH=ZKQ~o6uPB51J3pl@3~v1$)H<$1KrYNui5BW59f5R za$uRNbb;eMR1j)qlF3Y-(~5r<`=RAl;ZlFK@hc~81#9hT=znlUjxtYiZ3acbPd!kc zAt(KsHL!ZtlA!z%cWs1 z1Yx;@u>OP%Buo&NL4*w-S7xe z3PXzcH+0XJ>JhwCJNUbAUVZ|HNPM2Q@UsN$m-ea*6iNwHVu@4ucx3!Xjt>3ORtks%q>^|=MSiu@LTxrF6~|M z2eu~LH&8e_W9-2Ba6IVX#kXJP;ntmmIgdL3?dK5Rx|Dg z2%8ECLmQe12#w(_o%Yn%ynqNvoi4&qHz!iWGAO?hmCC~%bwE8qoj|2{cfvppx|!b5 zAzIaP+)rv|bDXF#Cg`TrBdsr(u67g#lF z_vpWuIQ<6GvX$yLW>PMbyOR6(UmBOm4l0UwL@A(5)r zh?HG9=#>w^dSsa@b$J)gHL9FsQpviS4gT25v>JAe`vsY*NV3zC#1V;q&ZvXSU6Mj; zfrdIp!dZtrMk1{h3E0yjNU>tuU&fkt!9+)ccIe|y6b<-%x< zS9%KQA78aUjxdjF-e%VPhfq(mO&fLt3VBkPfmRLo9C@7-^1@l2LehtzIfV=l^&+gK zriT&44`g?m9tdWdedRZMzJmq|&-dK0wi+=Fv!5zA1VY+_{j<~+R2iYluGb|V^h)IL zM0yv60-MQqAM|WkPKt$QLpBO+l;02vnH8x{G({XD)f6WEp7bkGsBG~HMIfjv_Lf-B z$9nNi_1dIgq2Oj~C+Y=~>>Wj!pwK!+bqHheTLuqJ2JZmH`48TZCxuS!? ztQW<4Ls2jKP7uojyt)1KSIA5M>Moiwjnw@WVl=i9A!XoK&NhrN8h1CR4IyO?dVPf5 zN7(&@A*2i(5eSgmY#R!2WmNhhtGE%hF>7N;yGYrMNJ>z}4UT-?-}-!tEmYo-M0cw= z1eOQxs3Y$lF>cV9DT4aVHLF{gO&y+^NXjh4*b7e%(gV*3&E8)W*Xw4(fFWsO)cuaH zHHS;xMBMU-?&WohtDLCW^IQi17SK12sCR1=)KNy-h;R}J=)HQMj?fG}yHEBO>2my-S#@8 zi2qA;hgi@~EZ6~9&;%MJB2!yvu-XZj-jNEsSz)l+RZs_PR>8c1^}DftMtJ>9*3U8` zS@#n4vy&qFq7F(l-6nUb9I$xMqE!zdEzKP~^fryYd2XxkgI%e<4TDMFhJe0B55rZw z!v%f2OLRD5h%!{rw-^p$6bCUz&^IU%bsBUJfN|rA&!I%rY0yN%CKCoFqE3UR5;mPM zC=qoUG>fp=gh7d@)1Y~Tl@PXoFhPSB5%wTqO9&G*XenWj5cVixf(Dfm_84I+2op5u zal%#-wu&%8gVqrC6k%my7)HZ>c%?rxFw8n&m+7x-7^cKhzI2^lrF9xa-KDXA#qV#h z-upJ4rBR0K=(ou~Ze1)PsCq^&pxg1x9|-@Icyf>hM`PXmdLb@}Xz2)j-s= z3di#}>VfFUuQIgc41T|Na8TlY*;MR68nisbhuE(my5u?8v_|gG1_84XIwB431Rmt* z`ES7L%t}D=d{By(RC#Kjc(u`KdchpI+T=7YSG%AHG~suH z<)G`pj;l5Y<4L%}Qg}u*rSMcd8#@QTRHKa)o^{UpIgK%!97x3(f2DSHjPuH<*aNG2 zN^z~GacmHAMLT5 zbC)Wqc|#X zIMCB59M9l5f&+YmoHJ(LJx5>f(;Zd&PA_`!_P)r25C|+>Fs)yQl4dETw9Hc77Ctz$ zL!fASzs^XEi8f>|W`EwWl=6S)^xKEsHDro_ox0X`1Jw?F)7dHN;X2)-+_z;lyUsUW zW(vq%vrBPjbN9U1NUS|xhpdj=cg)-|x6kX_?M7YF3#VCU+OU-`Zoh**RE7(k=x@@a zAE)0;f0@lYWZPA0?UOeg3ym6^Hx<+M%FGWZl0U<(jTtm>+!XeAYT@kk8;(UpjceW^ zH6BO1ckkfP`WrZG%)q=s@DXI6?H-Fuzu_3E5o+G07~_sVzJN_jF~+=MBL;~vKC|=K zCbE1@OQ)4bs;PII^~f|lALHmTV#7dNCV%_Q1CyFB$5#5(<>_XfVjS#uz%S1) zAOnNBgu+Mbn9J|@3;ww`>{n;ETI;vwqw#z7ehoHS%<236d%JJ=oVW&iH1CyTed^g) zp5hy;KP0gfr**<2ufpq@i{l`E0~xdK`{!!YsKx4rH*ZR@*r&w?zR5t(@hEbkMH%=~ zPHTJo&cv}9xP!x%n+#Zw4pNXjx5QK?i*yC=$+M~yCzve{GG%()X^3xZDpQ>G0NDH5 z+W=^Qs#pi%kiu#qphK&*vRc$}_YDJxe!^>M@#C6UM@`knY|7oy$)*JRl( zGB4E1!CKX};g(>n$*~)ywMhAVC0Ofm?FvYnZo#%SoPu_jAO%Bv*^TV$ckG^jK5~=S zdHznxv%C0w)Fz~6{)O2ouSxYr?RGX~*GBL5MrYT0#qXHiUT=17>~6&SuZ^o*)5RMj zz`oty*z8*W?%gx4o3wG)-v@Amq$YXbt3Br0|g6_YF1boIsy za7R%wMenVXsMJ|hO08V|S8sx-)MdA~O?GWp@%t80B~4TbR<8cki)&-x-FAE1X4j^R z-x;DxcTpv?a`oHZb^_dEx3_(EZI<}mQ&j0Cs@z(+dc9X0WcF@vQg&@`QKgTla+|2q zw{rCZUW^?V>bKjQoLzgn_w2?ng*fr^V-RRBUorV7)eTk$QyIqNwkTe77wl5$X=_O<%y|~q9dDk0c(($oa zL*4G)4_C9sd%^wT9TSBAF@3#(g#Xnb{ExnElgi)M2tUKV->26Rd>)fqSJ;w018hmJ zOSU9_f7|i4B=imjUuQ_4Z*u@(5T=7aDCKW|UOpGUw$q?-BftmXN=9$XFJ->?p2p}7 zV`Ni5w!wA@^5v!6zXZ6lw1pi6BZF2@2({!c zF8%0rvAdAmYw)f8v#~!;5x8yH>`2r{u-;~Aq`<<$4}KUm8gPVPg&Ojjor9Hf#p!V- zBH0oGY5K;St=446OW0m^Ci+aS3kZH5JJBgcW|?hPr+qbZ(NodyS_7_?H6oZSM)JH7 z;C~$2C-#~}J~XvnVL%m0sqo5T4~G~8G zH>V>iy;h`t*7ELn^zCSSv=K)g86+W8#2sS$T-l#qA20fBqgQmc<-T?M)oqPFlh|jn z=+mY3nW8(tA8WEfGIp>{AKIixe~;Dp3$M&zx}EjA(xee7S$xOjNFLeP^U~Q-1yScP z9hiq*X!107vdPiOZ)_W^N@<@sk;ZOltjSRy!L)Uf?Fbgi!cb5$Z2 z{5C!Fxo{`p+B35Mef0idr9Q%!2*tZI#*v5xmWB)-6|xgyhit{-|Gz2DJ+iILg_EX~ z=a8GyVkKv?r4BwZBJKGiQSNn2w+EPW#kI)uQ3l4%S#$ZF`c63KcM ztWniGx6!((vwnsQl~A2Ikh|B6GLwsE5y(#Csa%yVMd_6+(G^Kn4CH6%lk-_m7X%4g$ne1t{M$)1UWiqa~q8uDTcA32KO zuU{OjEK<|#9(xZ9> z(f{1km;GqS|Bt7>pE32nt+GjP9sh@>exE+|y&0k;4eMS0ATNI^A8e(Y*(G6c1GAfN zvbhloUiC{WVtf3-JSP>4f)Yy!j!$qNWYA5@r&OLl;TX_^7lzSPp0*}IrU=-q8+ClxD$Cq!z&#LL(B?THLzYPVOUW{8r}|yVAS{#h9ITf`STDlh zbcon?g!Lh;FJZS6CRF!1gbg4pmoPW$4JK?TVQ>wBMT|(E)=QXE_YqsZR-E?uy8NPmprq$D>WPtddy{2oZMKN2<6D>r* zi4xIbf!<<~FITj9a19L8^!r-E7E48oNM7R+(c)3PMX7JFXz|z@G23D$RgevLA=SSMPDuomk@i)Zu}<-QT3#iljgLM@(Six)(TWL{&l zXz`-n;w9fG(c+cWUx!+3VT)Hqi;iruU9@;jZ}GZsjA*fA_4}a~Z?MH%qD2Z@>=Z3_ z>n-;B#)%g1tS%3=*v}RRMT<^sQ6XCVR&Vj1?;g?O{ncU%MaNQ0&$$Gg(l-u-jn^EpC%3Y$|u9rs`Gc7SF)5UlKt$G7w~9GN7NAhyKeED~~jnqo+hyT19ul#5sq&}0ay4DJ3U-<0_No&)!z z?|`)>lLB?<*8q_feGcqU9e*3y>7ZJ8_0#!>$)Cf}FOuaF$;Wvd@wWwMe8Rfcyw#7TPqwaV}n zHNE{b*C%{zo|OUo50eP#x`cfnh;~Y4xjWb>n<|jV!m=qPNwK0xwO4peLi}?2cy4wZ z=aCVw#$I#D$iL}q%xthmh3eBf!y7t-?t+~@lzkQc`*!}PZAM&E$UO{4eAMLz4?Fv) zU7C1YTX2b%@s%Uv;=*~wMGqFv(^8KwMAy(rJ>Gxp zOJF=CeP2N*;X1bo6CQntD-e$1 zg9@h267{;v>+%zDy^y+tOHtEGEVH$0UPJ}k`K=Rk*i#|qWD#QE4OwgXy>*?Xrz$6w z6;L?l-K`zCZlF3;vp&M!8|IzL5-sK=bC{Yq)Y6}gRL;rBmYa<%3J<}5ODxl!a_k4P zH_@7Hzaz3t@%qJ&YpSE%ffZ~*WcNCjrrOgZx3H8y-ZeQkJH(stksO&}jf~zayCy~^ zT4Nwer$ejcMA`ue)~sRM0^ZZI9siJ}0Ap<{=U}+T!h+fTeF)Tr#VqnPNC%vz^eYN^ zBvWM)EE^DF7n$Q=p6Y;yq3hr>a|C0(TRiV-Z4hV68l7Di+2boq4DuEFnuFsI5k7wM zRrRYjLDk;2qWli64@Z0S(O^kJWe05k*hg}9T>yQg1RHFr&B2|~M_2ZdCQO!G-Na}z zz?Hd$RbpoPl^zFvc%=)|%0=8-?&E)&^|@i05!omEO7G12h`yqB@QUYl5ga~8GkzLC znLyAiaZ|c~qts7*$OvQpqwBKv1QreDll;A3! zH39y|p?&x^S1d=gPoOsSsv^wJw3O?XBqT8kHkce0%gnAW2n*KZs>SVWh*#^GMJ*D( z;CNuxC}Gg%)vViMiHd!``^j<(`?iEZvhb8n6pPJ??p*D19f8aSS5nh%LMs@g2H4|N zo%XO8t0x(Q#UyJA7!j@?4~QN>(2LTLfM`g2APVZPm2-yA;~gW3_()}ZWL|8V zHQN~oksRbwAjw%tlCzSINikkfA=HSKd|${B`*S>NfcwIzjrP94q+6EC;YGz4PMqMM zqExzy8%>})D!sBy)=IBg=(3S91m_l~aqf|oXTPx>lIdoA)A}tZdmGuWxgp0-8aq~wl0Sue9?Y^85 z^#{D1K9%*S70i3EV4+sP`me3O{TS6P8eHp$ zCVnsc;MoUgz&?FJ!9zol*kdWo(hG{m@M;pv=l%NHTHXiC?{@<|MnOo59j}GaRwoc3^X6LW+Gx=|Da!T{G zWF=9GIxJe4gseWWi8MvrW^k6IZk5$vivUgQ20!r*HK)jmQm|kQUVr>nlVa8QShhR( znU@aVC#rTI#6NT*t9Og|$eI6fauSEH zxzuad;BDRc*YwN0^wdIl!i*|l)F|=%X6(1bi06kqcH`!2u%ibCyha#pAqb=9^Yab^#kGKH~9r2p*~C5V~iSED7jICpC8gBv(N|VB}Znuc)!HYN6PN zs4)+Aa$(M$tLeJTh{Y<)bzFq+mEEh9%XGkSBdvgIyX?zxX2CWR+2^y7ZpYUbXY(EV zRdWv5LV%X|ashdWyI2y+y{YC5`@P^0BNLrj`1wQRHbOy#V$?^Yj!12cmXDZoZR=f; ziDs*9rD9r-lnSaT$K2VS3bmIx!|JucaLexqnCjWV7CBPFU?NkkozXb}rD|k`J2m!R z$A7rozmjeHqxH66Wy9K*B5e@2LQLB^JanQBuR5%wzc|s}aSkt+=d4T=#Rso_k|YV~ ze`IB^SJ^%J;)PlhG6boXeO}X^$(Jr(umRVY9s68+T)X4U+bp|$>Ne}n_{NJ9FI_ad zytd3ndql9&;c2}>wAY#0=!!vp;zl>31Vs2-uZ92tk%{{zU21F>Z1eAsiNw7?sHz9f2r4|BA#St*Wdb%^Xk0Iy*HUw-)oGhk2WG+ z@p5fnF%k|=JHOo+K~AtSalk(8?APUD-#b?swA-!h(k96?*jbnB82aw25^0zVS(y4< zxl>$q_@)A$+?8FQw;xyiY6Px2(s^a5W8^+p@ug8XyVV%OXfXycC$_P?@>jLvnvLYE z+Ix(VjCWqil_wlnHA}j`6$IWTR!2A;@E-lP-T_PYSR^zre_*1%76ey9BS#yLfJXd~ z6l*iY2eT(2f&3*!9ULl&APPA{r3(jbNl}9t`Y=bjfOkz72@`dM-$6(Cy@~KUfsK*N z^YVw{TJV3-QVsG}8R1w|e%;05&ofe|0?^7Z}Vs^EU+BQYLmg$tYpbJ(5gR$T_F86Rv+og&Uj1tq6Pm5+5vhhD^&u_)u*8)DLrF=KWQyc6l}WF zbrh7Kqo8D>Amj(0AwIox)B***^=PM<3mx(%*%xQniA{PUBPB2E34?p@gd8h@2n%5&)#%!Wjz+_Kez7Q67 zxd@&M&BZ$6vz)wq?kTHVfi!vhxE1wf;8THNe;tckP=6Dvz8R|xO5GJpd{{o*vp&Gt zd+wd_3KGak$OZ)kK`H;ijdvD|&taI7bqW*W@b*9naOT? zc0_J$qrpvtM-v`Hc-)|vr~|n%{t9P3+t)4>iVmy3qiCXD~ik4n;t}rmY4H5s#V9RImv8 zyJ_RQ34K5cR@XYJ|KiY>d}i->Wmdf8+8LB)QPb4)fI(&|sV_hf&_mN8oGPuN8Dp@S z7;FUw`?Ty?FZ6)8ie$m%HV9$VxOhh{ZOVcNKHo!GuwVwf#`?g{4yq8oJQ;YW_|i;5 zoR8tt1NbGsbRHwbLWDkciI8n)2r&SV~DJl+A?B&i9d}B=VMvLUGQ!Dgyv&!?8%AwfH z7HOZ?vUe)Pq=VXtl_ypxM@@Fn=qt_?GaUEb{27^Ii8Gfw z>-$Vv{obs98|%kewEF$D8*uRk?m(%#Kc&ey^KNeh?TtmBxmuk_))~w?L-ih5=T5E8 zaG!Nw!KJ%=HvAtc>L+sQMAhU@(~bz-#%~5p5?Ks4!=M4a;kt& z67V>a4xa)f^0Yi^{el842JUaXPMsAF=&(e{_17gOuomZ3_<`XdoClDb;*g?&_;RS@ zQZ*Rs;iI7#!*)0cZ;fS{d6=3_LVom@Km0j~^Fp8)@)=$8X70p$&pol=!iN_Iiwow> zTwE}-5Zr{v^S2*7lE^_77d*VU2;VTJ+oZ;t$Z339wQmL)fQ7|HKvfzNdq3_m;hiDu zdrOLtW>fGhBm?+-O!P^$(H=~XFnfEwj>$G!%+K7DOj{#b+)q-y#$}0mtnL$Mln}s|jmU~%!o@d-+tln*I0i&Tvbjt;!@p5dl?Ge`O zH1E-6tT|;~!CYQ%7`>}E4%|pwmkNwa^-gGeXmp&uJrqsonGaRyY;4>rnvNq3^9Dgq zbXWCbFR(~_HYLc05BAA!Q-`u#JeB@Y!5ewvkE1L<;JD%^-Qyn)%G=~R*0ceTWF&fBV zl*rpFRb`Pm0F5v#d{T`BHL2u&5V#KxeG{d{B!yfi+Q%!+Q6=V?sPMv4x}jc%PFGP4 z+i3G@n9k@W)B-DZ7)u{=s@e5Up&NEN)l9`4(L_Fvy+TqfKH<{^3-Ivi8utK_PGicR zmi_Ce8)MC{dNfz` zK%u6^BS4h?Nl-yqEf0t=IZr(rg#U>!cE@@hh>bZUX=Z;|%zwL*W2ed%PdK#kF%W87 zcm|18`}n+F%lUMc&Yw?3^86w_E!blpzf#D@twGQip4i||HxJ}>k{8ZjykHuFa4kSb zIOkIy^YOiWTF~p zkKIkUr%Lz_O`X30FY!PTDTq@sd#B$e)SkUFy7w47NYss+_meoG#h$+a{c3g9$~fNr zgMmH2<*PhR6pa;O{k00D+!)^Awo6MnJncFoOA4kHEqeIgS%vfNn*SgtF`tj0J-ztZ zkH)emts)a)7Y;6%2IUA~S|yD7g_KWw@(tB5M<;ekybipjXAUm^anmrzE_%4QPzWr~ z9GUzMpRN|US)1wOJMKSBnMX^QRybWxye$nCkKuPKU*vJe899-D-EiMR=Ul+G19`MK+Gq9$m{8SXEEAsIlFZGz2*pK~) zyk`l7slo*h7HXTWwL;f@D&2S+e z?Swu2v#Q(2-QfbEGLNVo{w|mAdGY5-yVfBK|Bc?AKXXAgt^2-@oRQ=uB@} zF49j1VN(`$)3wTUzlOzt`h-V)GFm+wo!^d-}D?38|FGpE`1Yo37H1-BYI3qA+^K@i1ROztKR9v?v6UM75m+){Xq1)ux5{X7r|DYdxT~D1v!Y^ZyO?m7tE1+P&QOu-!&PGWW3hG$rf$cqOH-Qy=V~- zEs{iw4x&Y}*22XW@U^S8b~ajc5iPok7Pp8NX`)3?YvE>#bhhYjwCEvPWQi6%MT=gd z#jRS42)5|W7PlEK`iT~|ixzi?7CE9te`!@B9{E7Hx9xjpHJJ2wJiprOtfLRWh2U47 z+@B}xo>cqQ;LYA$@BugvAAmis#LMsjxUkiE5k3Hy;RA4KYs9Nu`G)Y!74+}qB|$_t z^MHd|8TrZqHX$r~1~i9N#pUqr2)6(bGBYT~`FCb~K#HNWkb+scTLog8jQ2!dk(bXU zF0k0Lzlant#q)vO#{#o!yympG`el}}15nUmiwfuRweh%WEbcGKtdc2JpMcTIn8Jf! ze%6OrBiUe7rz{kf2JrnM<$Cdn{0YQJOJ@ZKj=;BiQgExYKYw{Re;x>WP02|m}hc)I^7wh$g4A5Z%hCeYwhmDw^!yb!D9-_nU9;?Hy zT-bY7hfOg0d-0uinPRr|n^o^N7tg~he+I`?VDck4?!k?Jgku~Ue}L~?9D4!J!m%Co zI^rN>dLK3n?v^* zifum@MIn(Pn)xJ%v?lB!eskeUwp_uMBEkz>!cvVb5ftLaEx+lSqTByPg_d%@8lL_t zDRmP@OEPk-ducR+C}fr8sd36S)!CS$_)PHrYqT(ErHK(#n7YaA5H=#Nh*!OqVpBxC zso~jyUTbkf#IvPHq=>J5UOT@Uy$*b>!)61&yk2MtBi6-u-TaF6M(``n8_6%<8o$?C zA+FgVt_eRw@ZXW4%SQ3C-ULVa{*A6*bQMvUW@W_?6y;|Q*4VvUV&4orEGh6GU zX!YRM`j`{yV=nucZ}jm%Yke$S9i|;^#U|}<;56I43OuZb_Fjzlj34g&itydJAuR`} z5;Yt`;~_L7_FZr8VQ*CpSFSOpKJrevQ5O~O(Rzs{!y`a{D4_qZboG3anNII5j!iBi13%^s#AltZb{O+(@W41Q_ZGO5YBOZG{_@T+DqgJb#{4Xt8M`?~ zP+N8fVgp^!@3#wAb3=kSg+5b1ru`WjXK&2!%EmEy`Mf1HTkR4@^1owF^bT^c3l`4{ z8na(U<-2$^<-5$v&`20*IdWSl-?4|F=w7guNFu{9--@nm!SXUb#!&7As0@etneRF~ z+@~>7G8$+GbJVSKG7aVWM{k?KKyR1hxyPgUi^@>mG^u21zW_@c`$dN2uc3C4Dkm?^R`xb|qZHlz(YveirnJb&9d z*=}_&@jj=vF(r>r&TqR@Nx5uyz4RiSvsUJyNz=3lTR-fMKz#Ed2CT5bRkG_%$fl58 zZ$7llWN{r_YPO~vaP9Y*-}PF!6J+ZJ5zZ|OfY@d7<~Zaua|N(qiImTv zT$bfZ{(CH>TK0u=FaMg?o;C}7F}fMZr8R3^{z_KRy`I&raj=d5b)2@<$|VkWW^%-G z*(r;%2o|(C_Qa?xyB|)DiS9#k)H&JyGsU$=HK$p97BHZT$on)czPL0<;&{eMGF{FU z69UM=Zd0`mK?GJ|lx%VgQS1kt^+omcy>qHVrblQtoRRz;l>!S;TwDav_*5VebT6rc z{5n>8{7;hf)StEGO36H$#rV+q`*m0$V?!^XOXe0de+C-90E#zqe;RjB?0pI6hPDFO z=~?QXlN{Jh7Klgc*Pe(+Xl(S1YkZC`-uB@yiQ;J#MhYciqEu35BJ#+8J2QjY=ueZj;cQK2Es_f&B2_B)7Psm!(+o~ zGLp-bV4Fa!&(~I5{;L_lX~ozBz&9VzC_JF?;&)zt1irC2qb%w&L`FrL`^T+ah)ues zHxElDG@nzZ&QzZlLfnV1F5S*$KP9u9h&aw}i#OyGMayYFjk9!vFod#&EepF?O(s)nT=&@)#aET1btbT zzXDZ5Z+2+l8|>=u{Q9eXCu{6nZN&nG;byLVxD9rp9mS8YWlO2-&2swP!(mc|dLnE` z)2oei8!^}HAg^3F5<*Nc0Y?aeJRFF7f}nk|*V2`WSa{xTQxCxm&~z|fy^`NhuiCzu z27P*5l0L1*9UbsP6<UivO#=gHv93~3v@R5Oq3%akRq`W7yLLMj#_gP=cB_O5 zHL#Of=6I<2OSVE%5*itR1pBDQZOFJW1vjRIZj2BWH>O&AN{X6f(xf#2EqKwa2bW`e zUVFQsP7LigaO%X!fs#l@wfzR;rQp*PgBM8lldmn{BTDSr*x!JWuXq%&n3YVk^s(=Z zmKL`hyqwSo{vxK)E^X6Cttw5Y7_zEbcz54EWU+>fZIW*vcCr|DVEBv-{E$DYw@`zC zxsAp4y49WKeOHdCPM8-!AzyRtgZCfAK&Eb1F>%)?&b7^yT@Sov&aSr}S`GxQp?)k4 zblM_tlCj)79hIap+Pg^iIj#*3mGO5y!2a4MrZKx6SPFde*yPt8^7&Y0JlM!`IQ8RvCjT{!-fv?tT@2CvB5Wtalo$>X~Z8@ z3GCf^!QP>+c)6R8E*(%yrG*2$WF7klGfrpBpSBp>f*4?Sna1?#c2S{NG6vIm;b=Q$ zyl@B1SJNz`TCl#xY$NBF;CoF`DOpXg94;X~#;m^eKdLnKuoy8M(19L;C%?OST7rRf{8SYZ zsAog7iE2L9UK}gNW$X)wO-%jSe{5ow;<{&Wlx=K&say4FIr_6LBe;g<6h6GTM8EvR zwfW+w?zG6+n{2s1hVKHZ4MescFuT~%&1L<%bM2H5)X0FduXeylPVHzqEJp?$OH@CM zQzQvF7{l2+vHjwXn$xH}o1p!^e@>(NY=riE(j55sn4?p=Yo`O}G@5yu8UA{c%<*F^ z#Oyz-QtDCMq#QQA<_do@!#Op@n#x8K+>FOFuh8$-?`Hb^hp>5#Ighuj&9A|>mAoY{ zf0R~n+NFnH8^e0j7A+_qUWjChn2A>`>cfQ1EnHMUnEK;*>WVXWExnt(1~%5{|Cn1i zqhN9IB6J5z{NbPb{Tr8kGIqq&CqSOYOgp~+sBke&o{wD}6cIM#Kl=#-tA(?QW-LP2 zggyHC*tL8-$qNe}EMz@!_kSz9*GBd-nb#_suf6)L`*niQ65}%13dW20nbM$~QN`&I(gWDX2 z{4&;Qc~}&TblDdN%}0&UOKwcxLpbVhk%Vc(Px1RD92r5#tmsDGihwXLD>(qkUpKT< z&Z+KP$6>`i%CtqX7e@sgo2i24j+VQlo4GIG%JxailGL!068F$%twLpWXcb=Oli)+B z_13aY_RaLCjl$X2ofy>-F_Oh7?UH3n(+ICC6W41w4%}IqalIpoz2#UkjIxhRQFzN; z;{7ghXPI7C+F;wyf}6#QZUV%yGXzTT-F9pT#pS=&c^Z{K~*vNYuqE| z)L1rkf@O)-Uo)u&n{gKZS+B`|X6o59PXF29S+kq)vvAW7Ycz6BO;lK;Xwk?b8pVi4 zaiWo5H0m8xtqB{A>y4dLlNi>tt!Qc!P1}p6Nup`8XbSXhO(?%bA1a!rY-$%8YA0WY zzb4Xu*4N8_W?IeJGpVgyZNi+Iu3=Y0bQj#6U0f|Fu9hyY*1eUc_syy45!MueNYKD*< zPR-!3W<#`Q!@`=~=__EfyrzMMo?y2*H6y|XI#LWYLJV}Y80c6r(7T(Oh8_l*-W%3* zyl5IJnobZ+CyJ(%BVN_-FKSLr{tTVZ4wJVw3e|bAD&3`6% zCT6z(?4+}2iaEB}dAMYp|IB1O`jWSt=d!<85dTHZTg@siWJTX%t>P3^{Nf>$1Ll8N z+}IC%xWR&&Wxm__^+SCeo#xam=Qws}%Hjt7076o95%S zz0OP77hjLRShKfT#ovf={Cm@UTzs?HINsLAu}d4r9(^2Gt^1zt=xp4O0zB3Uc&xM5 zDB&IPF!w_P$bQ=#6sHvo)nEVH_XtXS-(M5$KNElLWR(95u-OOxYGKe(EeEUHez@_T zV0F8X{MD>c-4NeUhupXy&#C_8**?De{MA#d>;CW#yy)BePXtc{{>NYKygW$$lm9rD z`mrh1CypQc6qiL&(h-048Z*`u8b;M~{N1WQ~o4|E>QoFC@ z)8fAHj?KC9#in-J6rIz`^!0q+H-!x@gvmf)a>U2rf*%0~yd}mY#EjPwhG#pKl>C?> z=Ke<8$?BjbuJzF7!WM|mk=6>E3^)=&YWqzo?0{jN)Dr%CN$F5l3cV=m7rSy-a zLz37r6ZXR_E5&&r^M50)QcnKlM;qAk=jy#XS#cj`}@1h<`xK>nkO zrU_0x4KjEP%MJMi-20M^`CACe%F8EHr}h;*dCkL*lP6z@#AhSHXXh3b6wjZDTFo*f zC7B13tDd3se;Dk)1he2p^XKIiOf4*y2C6AmZO@ED3|%jAZD?nW`2%Pva(uvjG{_n@ z7bYfw!9~S|fpHI)AmL4Kv|pbuTF2Zawx`H`Kl;9>7m%Pt)gsS#Jn+z3p3g4P&Ku{? zdW~&nmlV!ax2o&ZKUmcG1j~I+3M$lY3Kg+#n5*8!$TXUQ6ZbJDYSFWl4&dWnNau7z zF_80gz9#?ZyJWF$HD2t!Eta3oe~}FHxIE!frS>#IU&$$%S1zw$SJM{EU$}5qLD2&I zR==W_TRkAGpyPa(F9;E#S5~hu>#4%>6vzR7;m|&DJ-12F+VzaHZ-WnqlG&(Au#iTyXsXamP@$O* zc`cgC3>GBNK+>2(i$NmF^hnY!mI)(?2)6Ttmw_p)@H*HohGlW#Wqy`9S(d=E#PG6q zEOW6ez_JeEWgS`OW?3herG}Rw)*r$Evy2-tR_o z%ld?uA%{6EMp$+`%W}fY2Ds(QINP>p*xFF5C=_@c0*a9{ z=VrdqrUmMw^;%g-4|U{hKq)uFQAmpI5%FGdb^xVK4_oL+$g?4sooYHnt4BOqwf?Gz zaBXNBTM~+5

WM`5zf!*M%Zrn}OVkbDnOdGU+Dm7%dn+?4!ES9txZpDPQ!f%r95d-zF zn@&v570+Edb4+R2V2us-z9?1a3M5HAZGa8J^`K%_#pd_U7m^gDAVu^u7q`!5a^Ova zPttYHA`duj0^Lw&+sXhpVQ@LAoJr5RW_mqs7L9@3f$qzwkl?eG%l{{!%z|HN(kF7; zHGQXtWbOl~`al9n;nZV~ZmJy$;z~^YEmkE3>a6R$43VhyFCh)ny^~C2Y z@Oe|2z~=(j6Q8TV=S^h-p9@@1e69kYH<0+Xmf8GklKe6(56tK8`ik*zG&P=i%!z2N%Io z)7B->lJHOe_J#St%`VI>`@U^qh8ErT3$tl4{O?+r@M^m8!W@%slIHdN&KG8XF3gAj z-h~OI`Ry398r{lU{zqGwr3Np2#6Xz>L2_k^AHgdpE9 z%zypDG~dj^d>@PQ)X?vIVNzni^7=b@%y9YVn}wg1FaNU(GlVEZKfYg>5XAo95`HW< zvoHq?Gf5Lhe&-8w5EteX|IES!()V_I)!VeY`9{P6DY zd|?j8!YunI@TEq}TT>PtKK$J;%MjxHep&wOm!<7ymStD?Ob?j&oiEEFSe6_A>17Fo zd7Tck;?loxS++o%-|H~-{i^R$hiSi=Ww{wk@~x@g`LY~}W%lv|yo;Rt#iKD;X^EUNazxupiPd}42e9k?rx8)VC$i5Eb z%Jm=_e44gtKeWvj;iO{!FWFV0h@3LhrKJHh2-~7@VT%SfJdM((NNzdc6k86i)o$)5 z>nGfHZV|@}-gYd_z6yUWoK+#ti!x!6>D=#Z$>x9#1F!i z1!$4O-!1$x?j&fdh?sjf1C>w4#;?3LPneac!y?t+xYRdY>gY%jPwUf*vl*AD)o@=K z2=|4^ce~;k^lPqJ-9n>_O;ICCX`mexA6%(WRoj|i(XBo~%M!J}sv?u;%O>?MliJ;+ zHmK^$s`{TQ(kZKhRHjevmH9a!dwI2Rpt|gHjU5i5BX|6{id7uJzt2J)sh&9`u1v3<2W&+qCF< z?Lr{EPQceWB`q#;o2!z!gs}Ky;vCx|DzSGNi4(i8?KV$kTtMu^1$=F`MOVhP-{wV0 zdtdUln98_L+hTFj$(OV(t}-rVn-3=`ycn}j`*D&G7rQOK68WAJaN_etZEI7B4AqG^ z@%koU6S)KLqb={PBk!XV?<1A4c7$~$ER8TEU99l7CoF@oOu}FVT;UB6){C%g!uk-F zL|9+KZYL~C|LmD#4x0FA)RIfnOzwko~p3xi@UVg*h=(hz@8cxug0x5}f zIdv+A`FdXdIKnlgw3SFnwI?#uFmMhq&tjE$A7 zGZD3nHvMC$g&~2Ite_{$=88+B1Pbd zl(=}}i3EWs65`^BCwu}=_ofmM+oqWd<}WT`&^JccKx|1Slv9tzG!Nd2ym4j7 z8>gO)k%ta1DMho%IDUa68(v~$G~dASI~?P|=g@Af4##ak4>nwbF=iG^uBlMPxv3rm%pi3jr>$anMtIqs}L#ceA{LBM#c4>Dmd11t@kqR z`qMEm&u4NuzMAkrTpA(-B0tik3$+R}ubK9Mp}k<))XSCLXho4?-{S@DwQn=M9Xw}t z?)0i}P5$!S7w(-?5dZMYNYUI9#pbXxb5-nUjP)_(VEmrwUGdkxh}wmMHvSr6qF*Bi zYiw;dW&~^O?P0{fE54>azW!41k{NzNUX^Yam(C>bbvDesn6k+lf3d;Yu<8z}6P!qP zt~S5z+Ufb#Th8;eyRQk>Iy%Gd9{wY>U0`>A26p#nXm?-ZO8gRb_cdFcU%>AEOW56? z+xo~Y$cDk8F@WvLQDbnV35FFizd@q=>r^bv$z99$aVlGSBEP@J{J*K5C5f zLJ^+JxC5eY^gVkJbn?o0apIl8h7-f)AFe$bnGZAbGst`e-s`tkY40|(A?0HY@Ix#8 zsO4J3Z(8SXhH2ksTInBEZ21;g?Sl=VH-QN@n6%(WxodOj?=-k4rzJ_h3tcio?PITh zdVryJlc&yyasAxzS6aN&zhU&oI9QU)#d#twjrLA6IpdS!g-($}?>EDH|5$19imQVX zzwK?_(EM%o;YCDRRB1nOj32`~gWrF}QIA(Q9_P2f$dKU?8Aq7q60l7yj-5DRx!eZLDJ>x5zez-3FTYOGKIcT$Df`H`7y@5<9(FKh9d& zbw&}`<_mY3yd_c$uY&j|r;UMa#C#y#96dBOCr`(8$@9Ew!z_p68vm)_Da`TdNvBU~ z*v5Yv=;pL#(^>fEoF>ZgpPG95lpP*sG20aXX=lwK*_nPCZa2ZxYJ~r+%YVjgo^#go ztjBGcb2jQ(yW2YFZ1kphwDhuXQ>_2A*X%zz`ShuiKK~hj;|aF30odw42{dM-gO915 zzZ&Pq;pB5X;I9_oX2RC7LGjWjVXSXbZ#o?;y(fKE&gM{W2T`0 zI2;M9CLcdmmEPw>$KKW5;Z$^{cXg(}dJ2Y6-2*;7xW2QexaC_l4B7iO!d(6Qr=$HR z`ngp*AB)DUP=$Lw zI=QO$=so@_0r(vE^x5RkYd;&`r|RzPV-x(x;FEXk{Ff%!w%Z43qKa$)DBz`7!@z0&oPA z@#*9vb)T;2^V#y=4UhYeIxqG<`V;@rDaYzQKe{r>vkJ3T6}1M-=2%tTQ>%7KW#2L{ zYxQ|~`bP7jEu82LV&QB|@;=K~Am(TO)4brRWwFn9!E#RPaWd`&=jkH6O`Aa@IEB6! z{im_|ue_w4`d<+pY-zR*Uu_ZzTcH(!)h21l&oo+5YAJtoJ24_Yf>y{%z!u|42F@-snm1pf#`+&r$x2n>VtoGgcNC?<4%riaNbrMno5(3ZnGe8G5z|2+=H_^URAcY#|m!u#yU=D*d zYQ)>Fw}2GRY4qcqMnBF)Z}OuVT%LnxK|gAME`=wu?=3*%2?hD60fIOq=mg#|&4(*k@lcoN`Kz#i3;f%rSZS2Vpb)n6T~Hg)x%1o*_{lhr5E;5+(7_Q}rvlPK*b zNKpnzkwYUznSD-n>U}DCh|(eZbX)&vdKaJ0!kddBID3gZ&ekYMpT2~-;OZRi3Y!z= z3hN%Qv3syOB3E~XB?SR$A8e4l+T%lafzdvHC$RZ>V)Ope@Nf2Vjm!hqqL=FkV6AWU za(!e}vS;*;#<6#c@!i{eeDJ^(6ppUC?)x|H!T2WWR!ser(nM zkyd=mO#N7&sh`|vrnJZEeL_6e)k!f=a)HMF)L+93o?RBVwhPu5v}5VOm#lm5R&k+<=BzRVE)Dawb7{*30=1q|0Q977@c zvmCo>QUpZ*Qi%R6m%D>63(9wrFZ<+uzS!hmu*rBs(chWXB=~0t!C!D?5d85bQ}7?9 zvt?5v!3(tz{44%0!9NcI`zXP2jf{Lrc2Mvylbu(=Y#ok**6=2|H;VmDy*R&+U1cg% zjHe;=uL$SaqPELkp<)y>(NgFK&$d?efZJ`xvw=GT&vt6ksgpX-b_zV(DUD|%qXgM} z>Zy}<|5?GbopPQf(}rjpV74IkpMm@jw(X3@ww=+~wliScl1yGdx;Dpx@v>-)mrtk> z;t9640oW$Qdpm`AGsSzGCf*19C&0Er)(;*7I3&b($UXs12LnnGC7qi}y7-R+dgRz?(kO$o~TL=Rz>vR_)4%Ua-hG8HLAd~od-)>HMOdKkjBhbk(nKmUNw}XQ18^4 zSxxFcr%CeF4s6T&n^jrG5O*vpK51*2v@F|(I@g+>jTzyKe z#9z6rP05w{`@~YIa$XuFKagG}SUq*oUe_Khl#34vHu2&^f=#@*1XFU^{ID@4myiBn zPw=vJ8O|{!qC}VRFMnF9IaXe%QNVh5s|>h2Ee2*#Fs-s>??| zdpvkGY9+KjRaY;sTJ?ss2K?YRO~{k_g#7eI6QV7d=(S?WJe}m-z_lN8wOKp$zbKkh_HSB$uY9wo^%f9_v_l$^c*Ih^ zYzr_RKjFOK?e6D z7R+Xu!ED|vp@5j5O+~gY=09ZpNVfi5aSr@m%>Q1@uQ~g@m>(AQ3z!%}`~L+oe-qI9 z`rrSr67$Cpt;1gMsS*D%Q$&$kTrvfjcEyh1euxO!;2LEk$W^7A;bG@0+{|A!S?Ucv zsx);0@?{L3zu+MRpT&kCw+mqfIC1#mm)P^+{{TK!fCeS!9gYifWHx?eAk8dta3_M< zmYgF26|(p14EOG}Jrdc`o#^yfeF#VznQA)>5d~36@elj;+-w6YyFCLvH*vDQ`w*B8 z`K=}C;H0Zp6q;3-DsyFv29>RZ+7Y@%an7lCKRZ`TV-bN;WFeHK14uvUIlz!D(W1l@ zgD5^%=nL$^P{E_=?b3n}hs`qzvg-60_>Bw$ln4vg#WHzKMql>O1%*;D1No z{fDO3H+K3qjZY1hLCpVXOCi-#J}VXX#1Az(NCE8##N~($U5~s{QeF5BL9T`3eJrZ; zNmxq%^an-=OJ*2l@$dkLh_@GyFN2TIpO??+LcCupbY}19+0l(tCWIbn7l&d}qdu$)ckX?{K1iPY=bF4$Rj<}$PJuf^tJTaJRc67(skb=&5WX^2+ ztstJBEjRJVAbd1ho_$0-1=$+@eC*=+I7Nx#=JScf#H-@@M5Wh9^Z96JF?>EMp3fj9 zHvLK*9+BTDQxXn3@u04@5f7@Zcu?)}pjbDFi{}4f?_1!buCDxlk9j{bGnpilgaDJr zK!AjV5H*CLL4!OJ6c8<9fFuM22@oMDTBwO)ixS)LPAP5EE^Se0i(RW!d4v#L?VomO z7rS&`mkj^wq|p`c48z_FXRy{T@g5vPHtDwYdFgXR&+x?Iex% zvjdb9nk5Q6<7mUQ9*SkF&$3Ilr{ia}oKP=2H^{MAz2@2tR=?BVr&bGMq`JOovbCDA zBF&C9*sAp3dHi)5Y&4)1zo4CQpq;Ui!~VE7?03rc7*~dCh-aK-sy)Uo!MFzG10O%8 z<@hs?7y()0Cp_TeGWKV-!Ej*4g*N4o?|w+#`!df<(*7B3=M8kiWNMY1ZE7>@W~?JG zSPn|g)e8P7j*%DaEt#EaRP#!Md(b|6`|$HdkNwi{^Cqv)w8v}a4evhOPv`G**cne%=Bvx_W|SBv4YBARvudt@x3k|M2tHfio|3o*(2h?f2SvlhF`V(4N`M zYV4bNq3t}bxZCe_FlHEIh9hRRB4Tc0jFU09FlI!vLx4vT97XUbf^XBoqeatQcc968 zGtWuVz8UAwjS(5fiWv9!$e3agGf~8N?k2h>V=zRcK<_=x_Kq>~z5C#FbQbD&?!IP6 z)`e*Y^yrxB&8YRoQeSCv`GuLj>G&@5J<#kRax1PjN*>1ux$A_+)Fzo4m(aM09}W~l zGc=Pi*$K6>#fkwFFEo>ITt)3;2Yi976 zGcn|X=BvTqKZ6;oZ4$=iM38^Cap}d$3yeDHkOS8WY=O@7`|t;TSN;7SteSD(m|FP4zer)9^aUsY^8CjEr~{{_Q-ka^J#fg`)mqWnkw@!LFWZ%m+%W~T7`>ISSi%YkyPdEi z!r}hIaYo}Q^GuD)SA*vGF&3purkGj?NqA_P0Bbl4_zcxzw2-slp}I>`k=@8I z7)W;uhx_n4^^o8-6Ul3)kk_m(DJvvg@EWibh{fbI;5Xfw%_rwHZN=_0$H-_-92^Mi zGkcmV$(&(LGT(#KG#bB2vP}IH8O*NB7)*`0AK)*;S}bL_!FZl~#1)wf#<&8)QPnZB zQjL2_BKgcD6ZnjFXv=`3nm!2e5o|^~FXJTr2|hxnjAl=8)O*QCVga)G$VZgOoC#vo zU@9>h9}%wf`m)6S!4i|nN3Kv}swgo)J`$z#5imZzM8Pdw135AV zk&g@^A908Hh$oXVSs@NGj4g`h=pK&udv9XwEri7oHiEE`gpDFhaFyE#8%-E)z16wO z7{bO9Hl8rSRf-9lNEqDS>s)0rVfPSrFJXeKe2%dD344Gr!Bu7uHj6OaMyqp`*@Vp@ z>_Nf=SD8=P0>Ty&Cb-IC!X74UDPe-EEFqTlD>%N zt9Xuq6>$L|7ZopF!rxQp1}f9+G2V1@3`~49Asfr)l>T`Vt^pD^U}#c&+Yxeh%nk`_WLY}k*bmB-AZ07czF8L`FdD?_Sv7I>Y&rC!PMZS>4N(W8 z_a}6n_3Y=G`Zb|KIU{GDF-C75lzGM!^BQ8$!2kOtP;}gaGx1;54Ao(l4h!l_eV)BV z>f7s8_m+OzRrmg@Uv=-BexUqfimw#kX;&eGmp}qv=n5~q2cXUT(DkmcaN{rL6fEq9 z3b_?=J>~;0N7Pt1WzWzh!ojY@7Rh8qwJPdOE^1A29pwSBYQt#=2#q+&z{6bf&aQz9 zyz)VKvqABgWR1c7lJH?_j(0uk%(B%hTsYa&a2TOK;)RS?gO1|TNb9N9g(xMWr!wGf z7XET);F4T%UZ54C7PdrG3l=;E2b|MlDbRU7s%W}h8aGe2NzF4ap^M;|w(}CiD*B?v z0yu^~YvWp(17~27r1L_&a9*1j$MP*nEWe-c*4FY4>~`QAE9NT=wxhssieePLztT*N z5liMtn6;&3f!WY`l)|1EzMmMrA4gxO%DXXG25_)smk5XR6yztmcy|#FnSP;Fr)fwoDSr4#BDwzAbR0l+^8jkpZgE=JUId?o&A_67HHq^W-C(%(26$c}I);lE z_clMi!-xjsx%`Z0<1GR|5N?5=2&b>QLs&X}MJ9TDHNoSnb^tVb%}o)RdiVH>OcZM^ zaQCWZ!kq+=9Q|T;uxh8KxEj+qUN47-R-pryo3u{g9#fFi#Q}m4@*z3}!zilIhn7s@7|F#Fgd)(GiXBhCAYh=7YbNj+hW{luD95QAf;TN8B$u z;tb}4t(*^vO2oCe8`K$YP6Fj_b-r6&=;l?C5TwR!EE~sRplgFRY{D$jK%q3Lcn&24 z`BZ1Rf|b6#%xW(8q@#rkH%nOTt?zhuXdSt^}ywZV+ zOBcnCB`^B8WYDH8uPQ`ji_g^JH86#ZdzucWh++zNtDXXKHt%ysiWsJdV~TjDNDQV( zN;dUttuspsSM8AUkj#?8WNAod9e~?jTD*4cmg;oh{jCuRgr|uNv3<}=rRC79jx1aN zlhffGCIofZzM+Vyo7iEne7lm+z6T^(o2+$EuBmas2x1tI)pFnAY48M1-`Nqap>6qFkx(hXN8(aj*ywUH!x^eck|iRwN4sgvmYsYYp6+I5rY zY_?EaiGW1sxO*kh6JHv1BP3cMCefppk!aOFI*FbEdA>T}QzOv|cHqKmk|?Nn!xgyb zWh8odn?|BK-}%%@G;NSk`aK?c)xHicI;R_nj!}9g(YLqW5Q#Q~N%YudBueeSzJUJ6 zJy=VKG09IuHVkMe>@x^;auioT75|rvsd-9J#C{Z;@1!ugul&qoa(#I9((1hFR@pkkHFffir*fC>xZ(VSutm5|Dv{Y zWtHbMKoZgdj<0<-HVC z4~6#2ue@*mkm!*q5B_p+^6@zKQVp(DomBw?y;(I&$`>uIE?*LIrZo?}wwmdK`?>mx zLLRRUa?#??YSDX3R|d*wzBD-0TU2PEL#Dz5p1wU&S>s1(*XRhWN@rogeo0(EDzL`g zS|hHjGl`YFW{hLjJX2oH=IbI5RdI)gEP6hT#=H3u$kG>yJ(LQ9T+^q_o0}DXkmvbQ z7cZ-s_3;o?=k66j&0o0;L6x5sf_?+!Oc`@S2%)%<8}<`gfHGh1Bd9Eq1} zQOzh%ufyrYwn)TiMuwji)nsGVTR!225VM$w0l%pj?|ohH8yzv1(+#QXs+2RjcLbf< z5s4r{IVGPJg4%$b+e>Z;LDfVMWQs4YdUltPDP}LOevB|6gBQJ1%Q#~_?~#ombo;gp zOh1xkxzzI>SznoUi!2iHvff@C8AU&1lvd!`AiFnKC@75|1D;LTObZ*$KMDwIwC~r4 z;!OyrR+lfW!Cr=_(dLK>adMT}#9=eG>JKRz>NPX8%L!IEUum&?h8YaA))j>bD#rn=2Sx0rWiWIAIk2jPPmf zvC~G}X{X6Je4;}fgZtt!stQ-ebA(&tkhv%H6R<3Y0VqsDVOb7?a!v6sF9+|*PF55cg{g)EF=35NghjV*FYKC4WsSg<0eNyz?`sKGP`&$ zxd&2vPP^3`(-kZ45ZaX3gE*hn>5J(~kayq+$0a%8U_{|=d51$3-bWPFH?}KX6yE>f zKNM}|hnJvp zEwCQegN3jbY~rzn+VPUn?}zw{UhJ;W`XaIfec5&*d^c!fH%0>I>IV^Cf#-}}lE`C! zfUt^zFb%~pz7N9Lcp4xM3LZQI#}Dq8zYUOl>`U3f3*j47j|6YF_DQZZJH6W2r#jQj zV=bp-SeWHHCFgJlY|C<;xrvA2snSM5=wB;)pRny#U`X7gxQf{EMK%~2TbxN&cQ^oV z7?zyWTXD=l@!-6U`EE<2;&Hpu&10=!gS(ua=Bq}ZGTHt10_R8=B8CIiJjCGnocnDV z7XoO(dd2H<@0Rh^AbZkrnn8TIvfZmu({5j09H8(TqE;!s822{Wm*L){e~G_EF~?=3 zDCjjsk^88OTn!+5oSoRl8i^mtwB4M5aR7!ax#t^p^9PUi$%NfyBP@+$yAiH#P1uZe<|FR9VwRMr@ratn^bUFx?bJ+{lF?64uXz5gp-I80;VJiTg2 zj{hLx()*>SJ~X68y%5u7m3KJd3^*!);iiXO1DiA3-Gc!240*+obt;qbS^I;qVT26_ z%yrYgAnca-lRr8+VkBM{J?fR{_>&1;|KmY@Pkne>{CmQl^F2B9J@4qvqu~bO4tj_< zneg7Zgmb4ePe+Y~7qzw#zq=DZ7xR8MCg;7V@tNmb6Y=-_h~Jgq=R)4^igV7n?tXT% zG#Snr-jDwP-YVclvGH zc<*`p|G-bz91^RjEznT!t=^*#CuF|N_TV`NwaejdQM z`!wK6J!Sk8uK@dX?5d|;iRrS*aIJnS4ZnJ(_@^2FCB{F4c)N^S84z!mGu!*D0RYS~ zi2yeMeV+pWthgTmE|lPrcl3V+1nU1vY*)0rBUyyku2_#`V7)0!7AvpVfWKgtsr(x! zuh@vcVDf4F8!xZeguh_R0sc)unI`#V?|}G|6(645j5ZDY>MP!KtwmpJoUOeb8Diue zgO~v>zeQO9CCWPn6O;%LUk;E@-Z6wAU$Ck%U0!)d20>n=A})}M#7>Y^Uh{@9}yzw%hA9ymot+jaHor1a?tVSgJh^-rkaf1L2L{vhhRFQg1Mp?t{Qql#*;uqIz-U5vU^DkMD?zbk zrJ^_v#C93wX0OTvkLvJ?1|!dho58D%*48?cqzID_JL+zB9;&m*+Ww>~)PRU(j|y4( zPhDMIMdd@gjM600g!pxp+|(gHjU`Hat=0!<3j5egIHGANrJpT&-lu-wr;azOFZ#Ir zDFn*%**-@}8Ou<=>eEdm(g{8F%1~wdUfCn@053eB0`U)ZM|*5nwtUE^@a&m{mdj{| z6z+rI!i`1q6QswspBbfdf5fs9LXg|k80rNYgFw(BeFP0ZoE_UdLTvC6CCG|=6tnf#R zm34@kz@C5wc0?p8Kg+T)=+OQQ2KDxfB_{kO1Q+zj|7n!cXmVwHB$!l7l0ZTD?VOf& z^guz_3aclb{~lQX{R?K?KnXgJY=8HfeHqmW)A`|#M&j6ypmNp%zVHN{o8$Y2>HPf{ zhz6$U)McFiSZ**f5L%5-nS9Y!&M!Kl`h@--KrXftv$UUn94| z2mH_aW-ff@FRETvxzISDxIBln@FlfE?1?4Q?xJ9%h8XiBC5VJZqT2cSD^nFWAh`fa~8ZJ)W!|7u<=X`n9zdCucH! zHlySaT>l2Ah5g6|*A>0>*2Z9OVO!=3z18-eEB01IC!)6sd+RM6o#IJ(OaXuQy|w%U zi}a^cpSZW0*;{Xk-m>zxpVv9K3QEejfo>|dt`j|mEiWi5aXkpmV!V> zR*n&Ip4V{DcYlk}Q5&N)&p_aH(PLz1Ry$9}nc`ZV>Vl4VxG*#<=t$U-E*uXTqd4#q zQIa_cJn=xXwO@0^g-(C-EJ%$PQq~kjq)KBdY(m+BsRn5tJB(5E5y_UT* z;gln5gIfSjRbIo3Bx+}(36Jolnsd~%yU?Y--2+pOpHk}9p zHQL~|7dsjS2zx}>x!Ut_h^xSUi+fk^#I-AuxZFRMC!wKX+6t10<~&Lvi5X~nEw|A2 z+LU!kBdQi$gL28fWh0riN+#_9lQsxial;aP!KLUeIiWZ&<6=R2Ay+m&_-QdEG<(g$ zi%TD2B+)m*e4;;cSduXvHz!+D_xntHz3QF{xTS$(7{fpk(;&?g%`DJhTc#jETNVj* zSkwz%du!;;!};c$7;%fJ)d-Fby~g>BD`4EM^sywj`0j_tb5u+c-gic`Rd?5@}!~;wbXT*5I)YdkF3%+;u3~5FHji zV$f@H9H}Ip!bc*H7<(ikJthfB!`P3Kh96l?Dc!TCG3x+kO=ni@#Y5!G5C>e%`gMOi z@|tsHp|bRUEz+0&q;+Susqvv)_9Z^SZbbL_#aRV(Pj~wr^i6j+9|w_5eKU&PRRu1$ z4u9pOk}?~?g3GN1i-Wd_%na+xyBL4$qwc&6j?iL@c!I{d!14wby87YzW$8#9$w68L zN0%gWbcLOvaS(BO!<5xw#`tXU3f4l`b*i1|oH-o^mv1^&pB^E!;cmApQu*L6i*y7F za$1QenHuNB;{E&xDwj#QbX9g43R(f8^6)pdG6|Z9pxIIt9g7h!wZO}6risAI1((|4 zqFK4eLd@h9SMiXT{QjzOkz8IkK5i0+ZR~}PV|oC)6hbEJaf@^Cd`mYBK`^esF;Pmz z76`;bZnic0z?oEmI7e28v(-Z^t%(f*Jy{*mt32Q54s{#lKkxmH~3X1`$+{0mXw5h$Qn zh%@*b)UcC%yy;PY=D}Fp7E*qztICi@jJKS}Pah?pnY(z&9Nvz(s8W*d0LIjfDHvG> zN%_1*jTI9~9*QP23GM!A^9IiFr4YJx_c%pJ&k&3-u2ujx&y6oW+D9-~NutjbMfej4 zZ@gmPl1p-af=fR~lvVl_9+E@kt{+E4k6phvk!_P)SiGnj0eSZi#wpqo87f74TJmbe zo@6(8zNbX(R^iCARt$gFI0CH$Esb!3Veb>wWN@z@humRKDW-Pav*)4ME{n_w!vNOU zyizp{qS#Y$PBByFu~O!-4{tL%g<#=og_u@xsnc^1oWRs#ZyzIjvo6K7_^?NJL5ZWg zl6Xq^i8+wkGdnH)TDXq^fuggc&3`o>u?3WI5g6Glr!_n%aE=OdW)b`5@Es$iaHjF& z!3NE2pdz#{`UX;}v4TQg+EA88aUf`@i?05eM?l?HpSiw^!D-oIH6I^j67mxG)~+U*9fpWo3RX^u}`0$JFzeJkj% z&T~nQswZ8*=_@#)GtW{Z2x2GqzuZmJ@ExWJtqbXzMpD>?hQ?S8WFp8ykZp~@D`N)) z0PN-i5CyHQMaEy%ePE>_deA<4bTrcCqnGk!EagidTtOzly-nx&K3E1GsJPhCm+nN( z2ke)|$o;b}`r%hJ=c1GXmmF!>)s^TAr=$Hm+`L|CKAG#XN+rNrgPgurcz8uOXu={U zlkPO5(aXAS^zu`rrX9<^;?Dk22&Yz*FPXi#a?Y&Ti>v7EhupSpW7)S!NaIS%{79k+ zD&QO%&$ubNyJOMO(Pc;kM-tH>C;1LsGyb^cjK(-zn&i&Y=@0jINibhzhWO(P~bL$O|fF7q}b_r)0uNob;UwQdBAIE22IKPnbecjMsJ)-%>I?exIw&POWsSkFY8=9 zSelv$f=miQ`(>T$52!hL_<3_`2pY&bHvmveI^2{FF1vU^%}BNmMg45q2fTJ+7Lx;c z$&q`&>%_nIwwvJ*C@SwjolB5!=cV)cK`F8qc?g$KN*mA{8|2)L3kry4JP{%!t{u!y zw!}1Z;S?{WOp+Y#GC|jrzPlUr9Y#*|8BIV~+ly@7i+ph6L~ruJXD?f4CtyxYykYs^ z=fprjxDW}MCWjqsa|cb98#w1{GkQkx{M(7~H%^ZW+WPl}bAN0^FLmGszwmCRB-Lai zVVFNgoS@)dzIN~;w5I2=3xkDAE?elt1yaH7CL{v4kO-nf^IwD}Gbj;M^s!1WB!yca zgLm^2T3n&)$+deduKtyvL04=2c$r&B;no)+hrH@Sxb@Zcsc8K{*|AOA`q4IX|FW$w z>}!>F^}FHLA7Sh3#0udGeLr5_bb0HW{utSM9(w;b*|F!dYyDmQt<$|WUboI%t~7njv)iR`$)9RKk-ew*e$4RaYsg-6ETBR3Xt=X75ionT|9 zSsNo;i`|VvjN1A1cLW=*wcTWS?-tRT5N}AIF3I`NvTY}mXM2LsJV1Xa z+qP>RLw9-*f!*bD9vTxKmQMLlG7KIM?eYjKD@%Y5k`w|oy8rJVyevj^Xc`lYeCzY!)>p&e(_sHE*`b|)wtOb7A8eJ54!Pmh zA7$(RC|bW7oa_z!4Q}af*u-n|;=Z%u2~K}COXe@s8CbhP@_be2SlAsaWuCWRB*Ow1 znFdV6fbo=RjHgeL@3x>@Z=i*a@X1V8q$5+9m5_(@?v4f{#G+|_RT~Gi1Eb9lMGFiFepBPzKByZ%6#6tN zK(dP6bHI8?0dEm>R-lhtiLc^suUm;@Y>``uTVE5%Dg+b04LZYaTZzt6hvvY?UJ6@Y zfvu0fl_)NY2yZ1i<=*xZ9o_a4ue9_#31ceQsNKi<};zd65^YKc9Y$}DI zPy#G{-mzMNpdf;_k0>ZK$Tk=(Qc!4=qql2$d>FB9b4bFvUKoWw2^9nNr zEgCs_R{fRD5OM0i6_AiD^+obuw)I=k@>7L3-1`4y>(7eT z)0|J}%XPon>o<-MY0J4N3o_Y;waFCkSIc$knv?k!v?;T?qQsyAZ}dS{D|f3rW@s z(1SsPu)ri8G|~_hrkmW*4M8U&f`X2gU6RWV(39S&sv+gqNUzUp<16ITdoN#yYNSqucTB z%)8>E@)zLTzy$3&d(p@I0R3@d(x<|^kF%fN5&D=W^iw3 zZ{#iqPzqa^y}WXcyBxX23#Kte`!6O0EhZwdSV+2ui3IekA)U`7-M51xkV1tdk)kOi z1yW2|?PeV*Rv<;O6DiJ|b`{sJ0GS34k>V9diM<+99-nHJCfpZh2tHJrp9pkX|5P7;52}xsL86JXG&21&>}Z@IIe+KsvQ008 z`N*HbgSkM^=I0%s?3UK%0WtX)4|-^*F((HyfNEl*p9 zPVn(Rt-Up-A2g5p=;>myY%K_hJOwy|#`{94aU8x{7D~+7L$>Py&wtB~Zl@EVXxuXn zMTe)<(8BZbg2+ccj6gvC!P_mS-gQ3$AM&XOta6l&nxY% zIDak<#}nju-n5&@Q!GOc9E{S1^33fVogbd z-uYYGg8MdbS36h3-wi5h{)agGs+hCdK<2%gm#NofStg9oK8bnROFN1`Rdd#34_T#e zFVL`Z4Rh8HfQ7JmS<}|ys|#n>V_tUj&uCtz=;mc=ujXZjUd+pkpSgKi)Pq*31-{fs9;Dz; z#}IeV!&d3pCD%p4AL9KRlkl_ZaCSc+L|=V#%W~i~>Y;H+v1@KwNZV0+)T_9`l&23$e|foC_wO!E+jTb1Dr(5 zOPr+REtbZ8TqyR-I^Q4Dl#;w}r6qOHUc&bfo|bhs0DEBTz~Sd?gMtZAY*R6M6JXaLnk?UpknO0?$cA|?n$pxDi-pRhh!&O$u?{FcG1f=+BbvZy-Ufynk zC1$g@0$EeSOHxBOOX8C#0fT@$a!^cq0IKt-9uH92fvodq4Wkt^+)tfc)v0ew2XVxB1JF|l!JG1tLpq<%4VQ1E!*zBmf z(1&(reL?r8{>}FGJ29AM;%eBGu}Tt0EDo+>7^iU<4`@b2dpgFCMU2*h>jq}+ zL&uKG%(-CA(#EKbW7HzX=&-EwIjmdmzLoZy#Td1SF?vhZ**uIsM?OYh0aFX;tyyPp zWJ63YyL! zfA=#Zr#Nyy5F@u{Lz(d5m{w92MW|*TX_cu~nf}k!H8eY#JAY9nj|_Zc&$~l4zc4lP z7ca_RI;RRAq~}yh@F%G)KyZMu1nK2tXRrX#RvW$&UW@81g|PcCvO`m|1QjTc<{@!- zjUg5cJ$<`FONFB~JcOZIVCOEZ`E1y6GCH{96s4&r++q=6ism8!C-R`45GV4m`PloG z=in7q=^HDxVa$V!@u&ws!L2y*MLs=LQ@=+Nb+nNCYaqk+iZ`` zxGz8gc;bdJ@&{r>7RtIC#mK4Jl55fkjMP3e8p*ANNjeVX1kWs9CyksTM&|f9Db|BV zj^M8x99tS`KuPnAs^0HYpYf@<#9TYwTE=uoR+lIcqtZho=6kYbwPMBrCv60|pu9OD zTXSOz&9_B0$Sc#4DLj09270HcxTNqpIo$UKKNa*are7&X8$LScvT5gCX~HMLgijW> zux>1&6>an!ic@x|_RfHkY{0F}9maTi=pjrSX^B_c%pLHgReJv^4P{&&CZT@(XyOAz z4Z)DgFtmW9?#HBY8edYM6CBA%t?xe0nNv&31`saJNe2g(CJfv}+50+zL z4C(Ns%5J8(I4eKX3D6lGTnYrDUB;1=JoGe%?MX1Ra!hZZM$6jN#{v zLwPVMJJ~@qaVN~gqr}{BGYH$2cfjiw2b1z~Fv(MJpe{OiFzME-PQ7ziS*3O8p|x_} zx~f8=9uFr1p_hSfx=_N@TEgj7@-#`83&@(gy`YPebI7eeqjASgH#A*r-+pc82>Yzh zj5$U>Yn8sZ=7yPL8xfMAkU37mAis|o4mv-Bzn6uI>BlBAL^xZ_WnB9YOR!f4x6(aOddT+xRIi=3tM-2{gmFEY7S8K|X^H-&;qh+#ja12nd>B03&G7idOKeogQ%(0pa#7HS zLD6584}@IK{&T-|GBc?>-2 zhy3jG82dX3>bnVWxUZfyV@V?GR~rpMA#3og#$tv;nu7a_cp8n8-U~L zfJf~X{<2TYnWt6v=DRXa8=`k3_VlHMOBdj$Bxb?sxUc5=?%!Q;`hrjFOTm@3DPgz0 zZH(OKWv3i}a@J}12ZsL~z|H-3&pdtVf*(urXG^6NXV22Qh2KX3m%oMxYa)9gPy@0-4_{5<^sp}N*V ztp5=B8E?>!DhYOV^#s}RboL1!mc+j8^I+P^A z?N}MMvUuaf9qIuKi}}Kl=g+X1+?%qimybk}@0jS&W`gGnx09YqM&CcHs{FBK^Wi~~ z$!E?UIh#|{!o`ownfh4u9Id!FEqACdnl{O!5SYyG^JizxUx>>vmS&Bg zy?D{+q1B5XMAj^{vD>Re-ImUOd`^B&E}E2b>phDXFY*_O%QUW*dYG14J!F#erc_lt zR}wo2X!Z{unmu&bb>u@@^#jp!(cgS(GJ8(XBq}KGgbp5%ZhCSeR;TgFtCZ9Y(bfgd zW;u4Xir=HM>p$Zit9d%<(QJH$B@p4z6fRI!%E}<&(o{HFQir#zM!3lI84q}kVMo3u zI`SpVOqf0IlCuSVPcKCcZ@0SWt;%z?aq?x1k^1k2S*aKG;79msRalgB%5{xbK4gezcX$ecUBY7SKZ@KVbW zBX9lYM5RVJsog+n`Ep0SSMpNZi=*DdXw^g8xk>Hb9MW-fcxxNa60D-^xWxT``Cg1a zTT^kn4dI20mo1%>&(Wr}=Iym*)to1E$C7GYl=^fOoBrgvjw3`(YW3o!Q!yOWc5ZY1 zz&bNU1$0`zA0Eh~aIo%&`eiPCZH#|)!E86T!Tk5<`wi0MAHw@9g;ZD=3yJUWPw!GN z0g%h%tQdcI-+iuUD=vX>W#D3rcyp1p*jZ`bESra0&&V#xzC&>yQ>=N2&vxeN@nwv^ z#oEUBA1c-^)$z93{l+A`7wy+S-E6YNAK11*z%_+Eds!g$;*=h)jn7n#T#}hw`|X`$ zmFRu^j@=7iuV!psTH;2Wi`g6J%s=NHao#hc&4HBG_-2RLzD&%!m~_Bv%e&Y&Scttp zd`pZ~Qm}_QRtW?PaSRx7E=?4YI^ulth_-$#Bwa6LaNflXu>m36zzUw#@7Ic=v-YV1bw68R9susD4+T zIo_J;DzT@Vy{=q-Thl#$?bj9KOShN7NcEU(UoCqYNS$rqaj{Tg3bY4H!*pV?_m9^Ph4 z&FsXAzs)f)p{?CF7PpGn2H_eJ$B@0gfgoDv(6LJ99^b&%GS5T}8>?i$R@QdjFucV# z4w}67n+`DHe$gz(-m<7IZ;R2hMvZRv&Bi-jBlg=b-Ki9e$r&l$JSwx(ecOJoXD^|n z_snYRJa_hX4HmtZ|1K73|GeVgg*$z>?k{f}2gV$fUr2uZ1f5toer1*Ry`#}`p{a4v z1T9*VUs{zEH}~bSKktWwRKsB<9vcX@CTW_qOHwS->x#btv`&i?@*42&NRQ!v0*Lhe z`q~Gd6YN-{@1?UbpOe5z-)SIkX2qRq?&1ojl6U^u%Z;+par)_@hrh`d5AhVO4Wf45Q!@>{Oo=@vnK zw_=lC+(C9FZ2lbe$p0ht_?O-37Ks_7Ua<)WYlQ+MvSLt8h>8_#F3Vwc^sNSQorF3$ zox?Qf;_UE6)YR&Slk=?{FI7lBKOSG<8Yc(umC*go8-$+0bQOBohT6~jlSU8RO$f>g z)XVhJ$0oJ}v)X;7Z+%4BL~jXX!a0@Q`WG0{`?u+T?>4+-xc>hAIcjsV%PMU||8DNq zzh?dAgI8AlHaw+PE`D^8hQfE%hO$S@2p=ga16GBeXrWQv{t#tn$%d6I@+X8HmT(%Q-WDtX;Pmvsb4p#!%TB00Y9gZ zV>O;MJk2Pn6wevJ)gR*yG_$(btR66{Z<^JgnblvI)wj&*ug&T^X7ydO`a84w0U*Y* zJY-=nzleLSzAp<8!|k<`6wTJ(Rxj=l;iN5L1*gW_>Q!4M7_58Vao$Ee$ro?;+HeX; zvYp`9G1>5b7>+V@_z5f#c_LR!-%7?T(!VVmBFe!&z7BuC8yu{?*YsgH%8w`~3`hAV z!u1Kmkv;-m8~NUd`opw7T%WDs`X6Kcam-Ib%MFKRyA*YVFM+n%QXTP&b5r6!oac0d z3pko#w9Hh=JFpKhX0GQ9O+a}Spa!|}XD zGfubhCJo2B6nn>HCyn=`V5gqd9>wiocZ*-zG6ZHRAf$uUaFN_!cH;V!+YTu~_D#p~wrr#QUE6Ay9zfH#_wdZLhMl6<2M&TjAQ=;ym# z{QB$rhH|7ZN1DjtWsU(NN4lP4u&+qu$f(D~()v4wGDo(^;bD#(kt0{nakKAEkt46Z ze<%lbwJ&)JM2;BdxK-pR)N|bKD-k)0>T$KT{*F7DW1Pql%N!F#juJh`UB0_Sj!E^& zp&V3Ud!~vUam;a_$T3aNG2J&wB zBau12C~~aUb3E&tCUVrT$qnWBGIKPD9DSH$oyf64&#}ojLF8y$qxc>;=s7UEz2oAR z`eDqSN{ZQv+-cvSP~JpXfUs{9wvDjw5;lOa?S#ER*iOR4$;%%Qwu`VI5thz$FA?@K zVY>+%MA)l@?I8>v2V1;@32P>-g)p%e7(&?VguOwSztV}$(&VQ&+bMc8i$J5Jbd(RODSPnn)ztQafrA=dN0z*{!)c2eN&6j!pE$kdDY zpD)>@!2z2@CF@R8qkH@B_2F)GmqBhSlsqYLDBr?89?Y3|KJ8zsByZ>uR>7ErH2EyJ z{;+WR#Dya7WzC1%dz>P{;xd0u<$TIU`C_w0o5Rx~qj7qjA#N`cGE(cN$;vb}-82bv zCL14=5^!NtV0eUd!8m?`(q{*xEEm~csURIartJdWUsK-gxWmET>*jN)tje}1ud_6*HT)4P${@nR`Ev)LsvNy0F z5z_XS$6!51QQ;j6r3Dy@)hYDR^|V6iNSWNu>nx>Nv#aKmFS(<9_PjYf@~hjel*^h= zuL?hA{#zk9ltN&j1TYE`zzBIBpz^M-PgbhTyar&L%WtcOr~eJIr_4UmT56tUt-+N6 z^>S>bJ;`+&d~L(&c9J~}EAm;EcG;C@S-}S&$Zy^sLULG4kD6ol{tysfV1l z1)cYWtMj=~b*ATc+>iH_cB{@zR;Ns_P6eVXLv=9C4FxUpg4KD5)v1c8&Z2sE&+0rp zMy}54T*~S!JLEJj=zMg`Dc?Z6$iAGXIUYx|94iiaodkZ9qNp#yLzGa^Ka+py3z{v-nus!=W0_w$-aKvAT4ic{Vi(2+KistP#1%@G7`I)D^NY(A6$^f~Ny94PCJ1A* zVrZ(RC4p>c;=(_=me*DJjTT;}=m!Lw1Go)Bq(we#3W)2EYN70qxN#uLDijl;m<;jW z9-9Sra$W3p=KRa!Kjv(rZI3RiCeMFH#SHX1bQ$utk_C4mlIyo`%_w~ z+W;`nJ$R;oJs-iN-mhYR6nmq%GMd*&gY8~OP;p1d0WWUFN>rC6sw)%Kx|O z^|?fKZKAp{QGG1&!I^j^xqJ?ut>npgs_}~dz^jVzkViiU9{r4L0aV`g?quZ+c^;e} zrrA?1J7sH{qsCQZ*&w(Rez3yG_8H)^)PTe6+4d~U?%t9Vgp#D&2SOX#ODVabB)RtC zmOZ^CDGVjKj#56Ix?D#o*QzeI{dyz;&h(}ruF!rPZUg{AmIJLC)5i~zl%Tc8WYbpJ zy-f9kF2X!N&a%Va8eT{ps_u}23#mhkJ7g@RIws)C$Epq$3#ra;Nmxkr_l-W3sX7ca zJTo~Cu>_cwS+ED~Jmiha>2z5HBXZlwlpH)q=HZkPg`pYkKIHWt^u}ldHyUf1J3>Xp zT&t)!78T2);#pK;WKl_2DMZ1FpKiB zC>~jh9vWFxc73XEVyLK`YZZmnN~<@XMPat;h|Y^FD!)F>cXz0$f@>9ZD~n2CQH3n( z_Q;}&>I1&Xp`z}*R#D?vR3eL-z@kbbi@K|RfbX7AQIoD!)D#xgheb_gQTIg_HEqqm z`|b@DHT_yem9nTL7B!Pal|>d+v8LYlxlmD+y%&Y-eVKhOv(Jmn{?M9hW(OCpP^S)*KarF0+Wn-`{Pb55zLaoHmnoBRxGnI;*;oWZ$4U(AFS zO4kYFB1q&3%_YHwje~Ou#O^!SZ}~N+3bB^ZE=Uxck|mf7lh>^jV8G~XDoFQM}iZk;uyhjj?3uJ{DkrI!1!z@4t) zKi$+gkDbqtIE_#UVg<3eAtX_F2|hhE)k1H^1!{1$ypdPJ#-I@S%m+a2$5<^tf*Kvi zvk9x^?x&bs>~X$qt+pBtzia1d?OmYnO12=}D_36)Pkq29HRIW|uKSzZ`kVHwy$YHA zXe%5*hsdmgcO>a#_T!#pcH~S1nc;dLjm)q!YrWfC^D<}Ur{1%6z9$i zwCu~&PoQPDD(Y8Vs#EEvZlCj8tQ%g(vmH+@o)SFqc*u(1D0a6#&MF;y>Yh(Y{qBYO z-3xuY9qRWt3}(MI-qM8ih&2hz2B`Mo`Q4ZVZ^8J&G9m_w6KGEgtua=g*$2^D&BeK_ z=r$RiRC-t1+j~*kgF?P9H=qL|2qhi1dy=$!#_3bsX4y@({VTromuJJWPgmRf;@mqF zq|(&(@%*=&=TIG4r=6P;LVsOZr`@}0Lbxe01dGW!9ZQ&RH`Mm06V~_fnO^Z4U#>Xy zVIoZllXiQ}FT-l^!+x8RsJ7oN)b^)!wf$+R?N38(zng0NSE!AD*_R%FGV`R++sB{8 zpARxW5Ssh=56VuTqUt^!Z)5aL2SkvOH#ajsbpMVa`|KA-wB54TeqqG*dC)lNeOTtF(bN&eT|azZg~$CO{0PtD6SE{6*!! zv-@wiq#6sc6<$cci6Il1;9&!=J^(W?{zxt})`cd`tv|l!W2@my7TToUcwkhpp#pPSkJT zV%9Nc!(}S|?nsK$>zTGmGt|H}@wy`aaPvJkq{vUJwn>E#HWA>w7Bc-FvU5qYt`_gI3=(8_an-)U|CBUXlSx`sAh*UBf>WA@T>Br!)nkwfU% z{US$-o+C}y$`8;q|Kc6#%rRKxaPb|QE#pu79EHqryT~DI1&TzDJM|pnbgldZP4h2ulrYC#B8SlYPZBw%=sBk9TKW4l zt-Q!FjX9=^976M7Dss%!bCl^?`3g-dFLG2e$6S#^X#VGk91rO^s&uXVB26nVa#SqU+)>p2>9t^B$Ot^9_1xZ7;;CX-?|A$Lk6HP>4R3u)?8!kYRtrlY1lC9J6z+Iwp1 zQ^J~hp}nW3J|(QF7utJj>Qlm+dZE3iramRCsTbONYU)$MntGwVf1R+fre0|8si{v1 zYwCseo|^iUu%=#U@2ROz(KYo!t^997lz$wtuHOp04J93)5O{kps95erC*Od1W#}eN zR@n}n{I>VSoKIopZ$=*6X>*N@d~1;bSY6L;Bl4k5V}u zJGQz~`Eqq~<`_96t22uV(_uoLJUpzw%?;^qZ^`et84B7FLMzKJ%!0;|_%e^u^|!Yn zdbEfZI_|J$*)oPuRKYrys*>@(aCM3=S0~>!MxL0}If>PoBGk!K!_~PrRGn%09iPMd zrgy7ODXTM6uTB}FE5g+YYnClSv&`zuV|5;is7}@8>g2^^>lb53$$KyhsoQ_=%`y*5*uf*0x)DuBH2aAtTD8Vja^`K1sCzKVjmP;oca z(wba`Rk>m)#4nbKeuIA!4m(bh6zr0ejgn96DX4(!7GoWhzWRL{w(#wLrdXv;KsBP<_6G4@aMI19G4Mq zf8*Z&QzrMK`wpweduWPj0;DO~!wTc;NOEnRGMtO4e$aAwJL}J*R3>-)l$6ThqCT-w zIb75yRw{>!`ov1*a8aLFsT?lq6DyU&MSWtWa=56gE0zEI+DhecQCC+gH(pz*94-pB z%DRGi9>_NOp-8Q8RW}9mqTUtE;i9TT3g#s&swT3iN3Nk@-ii5UdaE|)P{E8dJL2=H zD42DlCC)89EN2rrmt^4hb+pbG)SKdINt-x!eVYfM)YRcs-6#|mq?&s&sG^55x@q%$$_k#;q@kqaCGF7W za%o{aA|qH6{TKIiM@BO;)g&ZUm@q>2p+k|Lc47@>f5P#CqVk^~vPmai*H9X%iWa-R zVN?WnaQpOzinJ*Y4(QwW%~({e8!9U2moJ=yL)>g8&{4@>praA(z6LtZ^uR{rxBtA@ z*ra3Q**9!b)0@|gjU-|t34{T4vgj2O1{CpgU&>CWhBOri`e#+opFL}Fb*RPk!mPQYA2ZKzf8bbfXF%=w4!A^s2sUW=CDJDv~4h%{)>X6rP&{^4HS4Uj(jIDI* z22O&cj4>oB_^#_oQRq?~i@Sab^M=D379(BPY0myAzDdau!z@T!eS5+l=&fFFS&UWNYMU{4$;N%k9Nk**p=$^s0>j4}c_Ze_OIe)g|2WMUJ)g-Hup$gA9 zJSBLVnddm61<*L;dJ3&+aKV*p{ho|F(RzhUgVAUMqw+ijT4L8f@fvDE`WD#pV3Lw% z-Avs#An7;?IElp|hBu0Q9%j7V{;pO=NrtzhP>E_jR>^?7QYAw@tF;`@1_s2x)eP;h!tP8{M!`zx?*f2?qrP}VrT31z;@XRb2Vofy!&ufWbJP_77UyrhDrb} zv?h7-tnsc~Ym)Q-$q6T*_D6&L-lYENTFCWHgJY}hdQ-L;oJl=PaIRMjZK|mXcpBF? zPct904#AOB0Ez$*hCToYOyVAFoJ&EAr?T$$^jOJu+-qH7nS=*^i3C0Lk>Yp4`viVo znjwCUY=d`JIaYGRGq>d49c51PO78dN)Z>PP|1!kYM)~sGZDyzB%}q7Lui6%cP&Ps* z_F932T4JJsUmFBY5SiZB5W|C`S^&pOy}3b%2oRD6$T5c=c>mYTiUxYK0j|FbfdT}! zCm_HayBH`z;0ObS2yE*Xb$Q|tB$Su|QQ#y5_Sp4uj%6ai#2*dPU-7mdfoV*9I|5}0 z9LYm~xmG75FcpE?JM^eijAES+$0Cr2zzMGoX<|77l8pdSa%?C983@#~-ORPYhQK%k zj=K?HHQE^9yIL7wHI6b+iNL8Kq}BrnD^?q$N7XYh50FL%m`fB$jEMr7SQN-KqCnQC z-G#(e2plmXumXYO3^02w12qWLGr;VtgJ~Mb21I)r*~M(n&QM=Bz+YdV7^gSEYiM1f zC)@fnCqnG+hDM^Dq;r(7X5e@*uz@uvWOqt1KsqY}WV;1{!c;>{Q=A8=7-WdwW2uwxbB{Kpn=P&~W2sHFTfCbhQQjNd~ z2Bsmf+lm0H5p&#zz$8H0SPoxtyssW;VjA&wMin8dodFiOofRYGeO8QB4P~zlR--uz zQ7o|8t|x9|oe4R^a#-?K)-d!6*16sRh^Rc@&R2*gk3&`KnV9d|or(af@e#{mb6WY* z8U#d3iH%0Si`Y07i@-btK4M?z4F|~?070k3>}K2Z5O{+Duz{Fn1{k$11Az<#+F;o0 zO-JCw3*eHOQhcpb{G3`TevTxGp9UN^L);$NP~qo`hSJNT1V;zrT=;zx>f1_H3UudJF)fX{-EDHh%Xccwa+SPZep7| zpwz)>MF1xmWhG#!Qxzp(bO$W%fYlwaxdV20z~K(K+yS>c;Bg0{-2tyV5aSNSx&v|U zfX^L>cLx&Ofkbzpk2{d$4)k>g`nd!B-2uNlkn9emxC5!~K$<%ca0dps14HTzsuCDl zXEZ2*tU8lX31qtiH`SRyxMiCe$VMQ3AzveUp?xNnO9bN?>AL|9(o~ zt~!5zC2)6LvR?^Is!K^$0+Z`fQMJW!XJp#)0nhGr^(8Fg7hmB7rp>?|cPt8Q4f z5-6+78Kwlv>xSnj&B?jazWL`nabU%JQ$UGrGf6vchMn^*hJD_=Ju}arJ8McFK@a!& zwtyZQ=?IuLSV2^)78vC|=m=P%0@kR2Eh=Dl$^nNX;H2U7tu&mDMuE4*?DH1xNgnNk z)0*28@O4L@F_m$z&B7bbz-z!>ZxK`WXUgQBDbvPOrfMk%Fy$DgOh?K)2Q#I2tQMES zxN(dd%DC*Fr3{zGSd<(|ON<+Y_j<=O@hwa|f{A^mSH0tYQt^l17rdJJhr(N5n{nn3 zg=739wG6j2!vtn1LWbfyX$FohIlYAw0K2VZpSMI5GLePcg+fXuv5+ZR2~(L`wDLZt zp4Kh(bfzw4>X};VvXysL4BF?tdr#e@;mT`ulXI0<9Y0Ape{N2@bk1l$7l>LhJK$PT z8E~(hGG|c0v+|y~g96b$Prw`J3dF210M^dYrturNAmku?u*VjKm7^#P|7^_KpheFRA7*g9rnP} z{P(56;OM{*5ncMsKIDW;qZw;7e?}!=9;-a}nr|R`VC`6?;Z^(D`Df24>%(~6L`*dj zQ(L65y9^tp*Kp+a%+@U?r@SVKT_~{s4W?>hs&6yZ*Y$4SCaO{T-H5lQgBDAkAFFJC z&6m!%?i{P^(BJxl@LOMGdH>Ave$4WAX>Wa{$6J5GRIf4BUZ&cuzf~L|m`K`b0TN1D ziG^i|;+&#-Gf8a! z&KLihFaFQ`v%eDK_8q35#q|Hh^zR1K&tm!$O#dF!zn_2hzeW0!Okc+Ir;xt<_k8^s z-@rfyItYMipU2nz=kQh0wr}O^i-Sa_OK>yg$vI!y;Y%04|GVOkfy@vSne(T8-no0~ z9=u6;O`3JS>s;5_t}|Vd+%10Ip1S!rN5(7N;~&~nx8Rn@cz9qczt{yQLCIB;MW&}N zse)zWjJ*y?*C7rW9yg`Tb6pFiD0!bWgQ;-#AhV-ukrWNU%EeNQJZMi{HQeJ2Kil=N z1pCRE=es&kO`qH%B`=W@4<1vNVyss$*5>#WNK(+WMpP&W@cojsAObh$0wE ztdWtr?(<%LoH_F+?DF|MFr4{5@0nrFz&M`s9vn>3*@^#qs0mrBdQq~sOdEjP@$7?^ zL;U9Pl}C%=i4ByjP#X;PT&%%{2)0~}Hk3vi239n}U@;~(oJNZUbB{FGNWn&_(c&dr zp^XMx8E3FDCN@s8RazogalFAMu#QggOL(;!FiFaMZ6f5FQHD$w13s_VWMU~`C8G^C zMX(nXOC>f9Y;A(U(gb@^u^Gf%V5MUWmM+*!ie(U+3$|{o!R86}vSJH}WrE?iSNr3& zNU#lxWf98(+c@4}d4j#7*b-s|U}cF0TPD~h#R`cn2iyFR!Q6tqs@O_m#bD(V47NtF z*Ay!uwiay5!v-rA>~+P~5nB&dkz}xqg1wvu_MH4!FHz@?3iHhD^^GBIM|-Y4OTDM2a25_)&RD5ios3__Mu{p z#7=?jn`*Grf_(2d-C$P*JEYh(Vl7}bX$HG4*kQ$5iQND@@}$Aq1UstOO=7pfYG)Yij$ofD#w_Aj ze7>ez4!e)dG?>l8e@lO^m@hFqSe?sY{(^m>SRk=ZV8>?}ELgBF6$>F23Ra(Pu&#oA zrI>?Q53m!n4Hhog*NXKf775mnVX!`eeWRF@SbwmSa|{+G*zXk^Kr9-pajwAz3ib!Z z1``_scIqjE4HfK=L7tya8(UfNP{iMVE@WB26KybUQ=u(v0|_v^9;5| zuolHiB>T6v)^a%H`XTpE`JOMEN`-Azb{*OE(*C@}({2>@hO%X3H%t4=GoH3w*fwRi zkgbsR*8)$wRoI)#R+8N&?X9JrcDt~*mEA$MO4|P{^R&B!y`$`IY4f!`mcyDG2NY_? zPi!xjS_osx?xS(5#kiO~g%xU^L+_UboeBr2a8MPj>^W1RMiy*TI6{S5Rq$cUO@(8! z;7f%%DjY{4>yYN|z=}+TdReej;RF>L)EfMl+f+Cy3;tASq{1mx2w*Erh10SSNQE<0 zXi|laY^AAiRu(!@;T#ptt3nW4Whz{dg4bvM`Veu~ZnT3WL}yrb4_d45q?pDvVKu`&gN&FisYRP$7{D6I9{1Y?G;wBnv~S zFp&z$s&GHsY${Bag<(`kp~4hZ_#J!IR7jPD7%EJoLYgW(z{*X98L}{(3N9+7tHOiq zHB%u&7GkL|mkRS#VFcS^DlCwNkyOZ}!Xj0OW3QVES+WpMg&bMP*YfzYa}=xacy=z4 zb~M=n8hn`=Jb}Go8oW>z#!z9oEM#bI6!MrQK6U$YgA!8!`{~(uPsVs zA(0AesZgp453x#9VVx{Ypu&18Y*dAZ*;}SUnJgqxVKWuVRpAk~%~aST3lphOL4~cV zFp0fwDpc|U-e#6>8EqrJ9X$V#7Lv>kYVJqbcFppsHl4q-W7T%ZRZON;RLM@hwu?V# zkFj?=9<<%krjXr3b}zS&vmG9GpR`lRR+HV&?Ns)zhdm%|D%pc%Yq))aRe9JW(oQ2= zOZFJI)7efBTPJNA+2dsExqTAn$1q;koY46jd-wy9p*7&zkQpesXR`N9Ki-pG$i>kt zw%ddny-+$wv)TJ5bjk~5a5RVQF`?65Xf8)jF-%+UvDu#SLi0G9&-R*7lNVaR(L(m2 z37z#qnH)XM_LFe74_&E_tCP z96iH6F`>&k-intGa5H~^#l0(F2TbUS7ZUewDf`rfu6iM{x@CB3c|I)HypY(Qp2ZFG zL@i!OY)}0CHBWTi3yJONIlP_hiCVpo*q)ZNBPMji3yJNih#fVdHZLT$CpW7#p_^Vv zY)>oj9=7Lgy6uI;R4t-sZo?qN$sx|*mbO@urIT($cDGu-d5*ae)Q5N)@a!r)PNJkYT60> zXSX;#bvMjn$qzK@q6NMwXReH(NuDtCrE!pv*dd%d?6Jx(^RHjW^w0T5=9KLE#o;Ud zJkn`0BDf1yvtWLJgPN>y{1jjv#6e7AcFY7dRkS*=sfZ4066=_xZN#jbGsE~g<8XWV z1XNhYECF#?N74U73u zic})iNCU!r9eOIV9%=31&;#ubJr>D8iji`}=2(GI!W~#Al7wU+1xPv4hS<70^mrs0 zNkeLo7R27ep+_PyNHQ`HaU9<7KnR_#3D&Z1yYT)Ab}k*77~x7A$dp%QiC)hj!sx7Qi@a|=MZNQ#zCr)bBI0I zp$|q>C?PEYlO*X{2>ys_IG70s zqiXus`geRv{C+=7?az-UiOX1sBL*;=zCv%isMpD_iijLs>@iRR~Q%;7a$xBBwp zWd6%hT!(W|WkhGYaxtbp-h9MPWx!aUMqhcX*5ELH$9_(I;B$V*aHBOA;FIJhWtfp;mn;cR>N)kG**sFD$e{64J&y;4vO!oSkNjAr`FC%_i#D}OUl8Au&nL*~+rB3s-^uS7 z&L|jREBB4mEo+0jT0X-Z*_d8LPG!nZo6(`G1v8rROMZ3yqK#h+a9X2(_34NiJA8vM zub-HyqqEb-uebB-yWoH{zc8mydzVj;@88|&%NGQ2(bFjg4&;}6IqjGQB>G0jh+`q0 z#8`K=e;*{i@7q3dKMWLnH_j*#crSDM7k0iU${7$DG63x^(Z`mC4s_PK!z`AgGk(F` z?t{dk>v~_t&(V4xekT0!^zFu9OyPwUfAL2z=+uilyqM!NiSr}>Y?==qAHJWY#y@L( zljt0rc$8m_?P%dfep<<%{EB-mA6figZk;+%^ZOCUH!O(5!tsdwaHEccshC#hu>9iC zZ#b+!J8Z2EpPwAQ*Qfv5=4;u>`c9s;>Vk!@NPN)rr2M$S>|Xv@bD229B*w-K$E#V) z-DPjTZG5RXjT)~rs|DxUYnwWoKeKq`2X2E_x06Lop7s1pwFqAlXYphI`1kpIEdgaN UewmNADP4XQ_g)O+??)g11sBboWB>pF literal 0 HcmV?d00001 diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..d73d3b1 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,269 @@ +const { ipcRenderer } = require('electron'); +const { dialog } = require('@electron/remote'); + +// 辅助函数:获取文件名 (保留扩展名) +function get_filename(filePath) { + if (!filePath) return ""; + return filePath.split(/[/\\]/).pop(); +} + +let s_lan = "zh"; + +function language_str(str) { + let t = { + "noFile": { zh: "当前文件不存在!", ozh: "This file does not exist!", en: "File not found!" }, + "saveTime": { zh: "保存时间:", ozh: "保存時間:", en: "Saved on:" }, + "selectFile": { zh: "请选择文件", ozh: "請選擇文件", en: "Please select a file" }, + "comfirm": { zh: "确认", ozh: "確認", en: "Confirm" }, + "cancel": { zh: "取消", ozh: "取消", en: "Cancel" }, + "SDFile": { zh: "SoonDesign模板文件", ozh: "SoonDesign模闆文件", en: "SoonDesign Template File" }, + "vers": { zh: "版本:", ozh: "版本:", en: "Version:" }, + "copyright": { zh: "版权所有 © 2023", ozh: "版權所有 © 2023", en: "Copyright © 2023" }, + "about": { zh: "关于 SoonDesign", ozh: "關於 SoonDesign", en: "About SoonDesign" }, + "lost": { zh: "(文件已丢失)", ozh: "(文件已丟失)", en: "(File Lost)" }, + "clickToDelete": { zh: "文件已丢失,点击删除此记录", ozh: "文件已丟失,點擊刪除此記錄", en: "File lost, click to remove" }, + "deleted": { zh: "已删除", ozh: "已刪除", en: "Deleted" }, + "whetherSave": { zh: "是否保存当前文件?", ozh: "是否保存當前文件?", en: "Save current file?" }, // 补全可能用到的翻译 + "delTitle": { zh: "警告", ozh: "警告", en: "Warning" }, + "delContent": { zh: "文件已丢失,确认是否删除?", ozh: "文件已丟失,確認是否刪除?", en: "File lost, confirm delete?" } + } + return t[str][s_lan] || t[str]['zh']; +} + +layui.use(['layer', 'form', 'jquery'], function () { + var $ = layui.$; + var layer = layui.layer + , form = layui.form; + + if (window.sysAPI && window.sysAPI.getAppVersion) { + window.sysAPI.getAppVersion().then(ver => { + $("#app-version").text("v" + ver); + }).catch(e => console.log(e)); + } + + ipcRenderer.send('get-sys-language'); + + ipcRenderer.on('close', (event, message) => { + ipcRenderer.send('run-close'); + }) + + function langua_ge(lan = 'zh') { + $("[language='m']").each(function (i) { + $(this).html($(this).attr(lan)); + }) + $("[language='t']").each(function (i) { + $(this).attr("title", $(this).attr(lan)); + }) + } + + $('#language_select').on('change', function () { + langua_ge($(this).find('option:selected').val()); + s_lan = $(this).find('option:selected').val(); + localStorage.setItem("lang", s_lan); + loadHistory(); + }); + + ipcRenderer.on('sys-lan', (event, data) => { + let lang = localStorage.getItem("lang"); + if (lang) { + s_lan = lang; + } else { + if (data.startsWith('zh')) { + s_lan = data === 'zh-TW' ? 'ozh' : 'zh'; + } else { + s_lan = 'en'; + } + } + langua_ge(s_lan); + $("#language_select").val(s_lan); + localStorage.setItem("lang", s_lan); + loadHistory(); + }); + + // ========================================== + // 加载列表 + // ========================================== + async function loadHistory() { + try { + const j = await window.sysAPI.readHistory(); + let h = ""; + let needUpdate = false; + + for (let item of j.history) { + let src = ""; + let fileExists = true; + let imgStyle = ""; + let realType = item.type; + + const soonData = await window.sysAPI.readJsonFile(item.path); + + if (soonData) { + src = soonData.fp; + let fileType = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + if (item.type != fileType) { + item.type = fileType; + realType = fileType; + needUpdate = true; + } + } else { + fileExists = false; + if (realType && (realType == 2 || realType == "2")) { + src = "./public/images/bg_2.png"; + } else { + src = "./public/images/bg_1.png"; + } + imgStyle = "opacity: 0.6; filter: grayscale(100%);"; + } + + let displayName = get_filename(item.path); + let cardTitle = item.path; + let cardStyle = ""; + + if (!fileExists) { + displayName += ` ${language_str("lost")}`; + cardTitle = language_str("clickToDelete"); + cardStyle = "border: 1px dashed #ff5722;"; + } + + h += `

+
+ +
${displayName}
+
+
` + } + + $(".card-list").html(h); + + if (needUpdate) { + await window.sysAPI.writeHistory({ history: j.history }); + } + + } catch (e) { + console.error("加载历史记录出错", e); + } + } + + // 按钮事件 + $("#openfile").click(function () { OpenDialog(); }); + $("#new1").click(function () { + // 这里如果你也想改成和保存一样的弹窗逻辑,可以在这里加 + ipcRenderer.send('open-design-page', "", 1); + }); + $("#new2").click(function () { ipcRenderer.send('open-design-page', "", 2); }); + + $("#about").click(async function () { + let version = "3.0.0"; + try { + if (window.sysAPI && window.sysAPI.getAppVersion) { + const v = await window.sysAPI.getAppVersion(); + version = "v" + v; + } + } catch (e) {} + + layer.open({ + type: 1 + , title: language_str("about") + , area: '450px;' + , id: 'LAY_layuipro' + , moveType: 1 + , content: '
' + + '
' + + '
Soon Design
' + + '
' + language_str("vers") + ' ' + version + '
' + + '
' + language_str("copyright") + '
' + + '
' + }); + }); + + // ========================================== + // 点击事件 (修改后:完全匹配“保存”弹窗样式) + // ========================================== + $(".card-list").on("click", '.card', async function () { + let filePath = $(this).attr("data"); + + // 再次检查文件是否存在 + const soonData = await window.sysAPI.readJsonFile(filePath); + + if (soonData) { + // 1. 文件存在 -> 正常打开 + let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + + const currentData = await window.sysAPI.readHistory(); + let newHistory = currentData.history.filter(item => item.path !== filePath); + newHistory.unshift({ path: filePath, time: Date.now(), type: type }); + await window.sysAPI.writeHistory({ history: newHistory }); + + ipcRenderer.send('open-design-page', filePath, type); + } else { + // 2. 文件不存在 -> 警告确认 + + // 【关键修改】 + // - 纯文字,左对齐 (text-align: left) + // - 适当的 padding + // - 去除所有图标 + let contentHtml = ` +
+ ${language_str("delContent")} +
+ `; + + // 使用 layer.open + layer.open({ + type: 1, + title: language_str("delTitle"), // 标题:警告 + content: contentHtml, + btn: [language_str("comfirm"), language_str("cancel")], + btnAlign: 'r', // 【关键修改】按钮右对齐 + area: ['300px', 'auto'], // 【关键修改】宽度设为 300px,标准小窗口 + resize: false, + shadeClose: true, + yes: async function(index){ + const currentData = await window.sysAPI.readHistory(); + const newHistory = currentData.history.filter(item => item.path !== filePath); + + if (newHistory.length !== currentData.history.length) { + await window.sysAPI.writeHistory({ history: newHistory }); + layer.msg(language_str("deleted"), { icon: 1, time: 1000 }); + loadHistory(); + } + layer.close(index); + } + }); + } + }); + + function OpenDialog() { + dialog.showOpenDialog({ + title: language_str("selectFile"), + buttonLabel: language_str("comfirm"), + filters: [ + { name: language_str("SDFile"), extensions: ['soon'] }, + ] + }).then(async (result) => { + if (result.canceled || !result.filePaths[0]) return; + + let filePath = result.filePaths[0]; + + const soonData = await window.sysAPI.readJsonFile(filePath); + if (!soonData) { + layer.msg(language_str("noFile")); + return; + } + + let type = soonData.soonType ? soonData.soonType : (soonData.backBlackPic ? 2 : 1); + + const currentData = await window.sysAPI.readHistory(); + let newHistory = currentData.history.filter(item => item.path !== filePath); + newHistory.unshift({ path: filePath, time: Date.now(), type: type }); + + await window.sysAPI.writeHistory({ history: newHistory }); + + ipcRenderer.send('open-design-page', filePath, type); + loadHistory(); + + }).catch(err => { + console.log(err) + }) + } +}); \ No newline at end of file diff --git a/main.js b/main.js index 9eabe76..88778ce 100644 --- a/main.js +++ b/main.js @@ -1,11 +1,21 @@ -const { Menu, app, BrowserWindow, dialog } = require("electron"); -let fs = require('fs') +const { ipcMain, Menu, shell, app, BrowserWindow, dialog } = require("electron"); +const fs = require('fs'); const fontList = require("font-list"); const path = require("path"); const child_process = require("child_process"); + console.log(`Node.js: ${process.versions.node}`); + +// 初始化 Remote 模块 require("@electron/remote/main").initialize(); + let preFilePath = ""; + +// 1. 定义数据路径(放在全局,方便后续调用) +const exePath = app.getPath('userData'); +const historyFilePath = path.join(exePath, 'data.json'); +console.log('User data path:', exePath); + app.on("will-finish-launching", () => { app.on("open-file", (e, filePath) => { preFilePath = filePath; @@ -16,15 +26,13 @@ app.on("will-finish-launching", () => { preFilePath = process.argv[1] === "." ? "" : process.argv[1]; } }); -// 使用用户数据目录,避免管理员权限问题 -const exePath = app.getPath('userData') -console.log('User data path:', exePath); + function createWindow() { // Create the browser window. let size = require("electron").screen.getPrimaryDisplay().workAreaSize; - //console.log(require('electron').screen.getPrimaryDisplay()); let width = parseInt(size.width); var mainWindow; + mainWindow = new BrowserWindow({ show: false, width: size.width, @@ -32,25 +40,31 @@ function createWindow() { resizable: false, icon: "public/images/favicon.ico", // sets window icon webPreferences: { - preload: path.join(__dirname, "preload.js"), + preload: path.join(__dirname, "preload.js"), // 确保你有这个文件 nodeIntegration: true, - contextIsolation: false, + contextIsolation: false, // 注意:为了安全建议后续开启,但目前保持你的配置 enableRemoteModule: true, }, }); mainWindow.maximize(); - // Menu.setApplicationMenu(null);//关闭菜单 - // and load the index.html of the app. - //mainWindow.maximize(); + //Menu.setApplicationMenu(null); // 关闭菜单 + + // 启动逻辑:如果有预打开的文件,直接跳转 if (preFilePath) { - let soonData = fs.readFileSync(preFilePath) - let j = JSON.parse(soonData.toString()); - let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; - mainWindow.loadURL(`file://${__dirname}/design${type}.html?file=${preFilePath}&type=${type}`); + try { + let soonData = fs.readFileSync(preFilePath) + let j = JSON.parse(soonData.toString()); + let type = j.soonType ? j.soonType : j.backBlackPic ? 2 : 1; + mainWindow.loadURL(`file://${__dirname}/design${type}.html?file=${preFilePath}&type=${type}`); + } catch (e) { + console.error("Error loading preFilePath:", e); + mainWindow.loadFile("index.html"); + } } else { mainWindow.loadFile("index.html"); } + var close_flag = 0; mainWindow.on("close", function (e) { if (close_flag == 0) { @@ -59,59 +73,132 @@ function createWindow() { } }); mainWindow.show(); - // mainWindow.webContents.openDevTools({mode:'bottom'}); - //1150 width 720 height - // Open the DevTools. - //mainWindow.webContents.openDevTools() - const { ipcMain } = require("electron"); + + // ========================================== + // 新增:安全的文件读写处理 (IPC Handle) + // ========================================== + + // 1. 读取历史记录 + ipcMain.handle('history:read', async () => { + try { + if (!fs.existsSync(historyFilePath)) { + return { history: [] }; + } + const data = fs.readFileSync(historyFilePath, 'utf-8'); + return JSON.parse(data); + } catch (err) { + console.error('主进程读取历史失败:', err); + return { history: [] }; + } + }); + + // 2. 写入历史记录 (包含权限检查) + ipcMain.handle('history:write', async (event, data) => { + try { + // 简单的权限检查 + if (fs.existsSync(historyFilePath)) { + try { + fs.accessSync(historyFilePath, fs.constants.R_OK | fs.constants.W_OK); + } catch (e) { + console.error("无权限写入配置文件"); + return { success: false, error: "PERMISSION_DENIED" }; + } + } + fs.writeFileSync(historyFilePath, JSON.stringify(data)); + return { success: true }; + } catch (err) { + console.error('主进程写入历史失败:', err); + return { success: false, error: err.message }; + } + }); + // 新增:获取软件版本号 + ipcMain.handle('app:get-version', () => { + return app.getVersion(); // 自动读取 package.json 中的 version 字段 + }); + + // 3. 读取 .soon 项目文件 (用于获取缩略图等) + ipcMain.handle('file:read-json', async (event, filePath) => { + try { + if (!fs.existsSync(filePath)) return null; + const data = fs.readFileSync(filePath, 'utf-8'); + return JSON.parse(data); + } catch (err) { + // console.error('读取文件失败:', filePath, err); + return null; + } + }); + + // ========================================== + // 原有的 IPC 监听 + // ========================================== + ipcMain.on("get-sys-fonts", (event) => { - fontList - .getFonts() + fontList.getFonts() .then((fonts) => { event.reply("font-list", fonts); }) .catch((err) => { - //console.log(err); + console.log(err); }); }); + ipcMain.on("get-sys-language", (event) => { event.reply("sys-lan", app.getLocale()); }); - ipcMain.on("open-help-file", (event) => { - // help 文件在应用目录,不在用户数据目录 - let helpPath = !app.isPackaged - ? path.join(process.cwd(), 'help', 'User Manual.pdf') - : path.join(path.dirname(process.execPath), 'resources', 'help', 'User Manual.pdf') - child_process.exec(`start "" "${helpPath}"`); + + // 修复后的 Help 文件打开逻辑 + ipcMain.on("open-help-file", async (event) => { + let helpPath; + const fileName = 'User Manual.pdf'; + + if (app.isPackaged) { + helpPath = path.join(process.resourcesPath, 'help', fileName); + } else { + helpPath = path.join(process.cwd(), 'help', fileName); + } + + console.log("Help file path: ", helpPath); + + if (fs.existsSync(helpPath)) { + try { + const errorMessage = await shell.openPath(helpPath); + if (errorMessage) { + console.error('Error opening help file:', errorMessage); + } else { + console.log('Help file opened successfully'); + } + } catch (err) { + console.error('Exception when opening file:', err); + } + } else { + console.error('Help file not found at:', helpPath); + } }); - // 监听从渲染进程发送来的请求 ipcMain.on("print-pdf", (event, pdfData) => { - // 打印 PDF 数据 printPDF(pdfData); }); function printPDF(pdfData) { - // 使用 Electron 的打印对话框或默认打印机进行打印 - // 创建一个隐藏的webview以加载PDF数据并进行打印 - const printWindow = new BrowserWindow({ show: true }); + const printWindow = new BrowserWindow({ show: true }); // 如果不想显示窗口,改 show: false printWindow.loadURL(pdfData); printWindow.webContents.on("did-finish-load", () => { - // 使用默认打印机打印PDF printWindow.webContents.print({}); - console.log("123") - //printWindow.close(); + // printWindow.close(); // 打印通常是异步的,直接关闭可能导致打印失败 }); } + ipcMain.on("open-design-page", (event, arg, type) => { arg = arg ? arg : 'empty' type = type ? type : '1' mainWindow.loadURL(`file://${__dirname}/design${type}.html?file=${arg}&type=${type}`); }); + ipcMain.on("open-first-page", (event, arg) => { mainWindow.loadURL(`file://${__dirname}/index.html`); }); + ipcMain.on("run-close", (event, arg) => { close_flag = 1; app.quit(); @@ -120,26 +207,18 @@ function createWindow() { require("@electron/remote/main").enable(mainWindow.webContents); } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. app.commandLine.appendSwitch("no-sandbox"); + app.whenReady().then(() => { createWindow(); - require("@electron/remote/main").initialize(); + // initialize 已经在顶部调用过,这里不需要重复调用,但保留也无害 + // require("@electron/remote/main").initialize(); + app.on("activate", function () { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); -// Quit when all windows are closed, except on macOS. There, it's common -// for applications and their menu bar to stay active until the user quits -// explicitly with Cmd + Q. app.on("window-all-closed", function () { if (process.platform !== "darwin") app.quit(); -}); - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +}); \ No newline at end of file diff --git a/package.json b/package.json index e92ad0d..0339f3b 100644 --- a/package.json +++ b/package.json @@ -1,104 +1,57 @@ { "name": "SoonDesign", - "version": "2.1.248", + "version": "2.3.017", "description": "tools of design soon files", "main": "main.js", + "author": "Jerry ", + "repository": { + "type": "git", + "url": "https://github.com/cello847/soondesign" + }, + "homepage": "https://soondesign.cardsoon.com", + "bugs": { + "url": "https://github.com/cello847/soondesign/issues" + }, "scripts": { - "start": "electron .", - "build": "electron-builder", - "build:win": "electron-builder --win --x64", - "build:mac": "electron-builder --mac --x64", - "build:linux": "electron-builder --linux --x64", - "build:all": "electron-builder -mwl" - }, - "build": { - "productName": "SoonDesign", - "appId": "com.SoonDesign", - "copyright": "", - "directories": { - "output": "build" - }, - "extraFiles": [ - "lib" - ], - "nsis": { - "oneClick": false, - "allowElevation": true, - "allowToChangeInstallationDirectory": true, - "installerIcon": "public/images/favicon.ico", - "uninstallerIcon": "public/images/favicon.ico", - "installerHeaderIcon": "public/images/favicon.ico", - "createDesktopShortcut": true, - "createStartMenuShortcut": true - }, - "fileAssociations": [ - { - "name": "SoonDesign file", - "ext": "soon", - "icon": "public/images/favicon.ico", - "description": "SoonDesign file associations" - } - ], - "asar": true, - "asarUnpack": [ - "lib/**" - ], - "win": { - "icon": "public/images/favicon.ico", - "requestedExecutionLevel": "asInvoker", - "target": [ - { - "target": "nsis", - "arch": [ - "x64" - ] - } - ] - }, - "mac": { - "target": [ - { "target": "dmg", "arch": ["x64"] }, - { "target": "zip", "arch": ["x64"] } - ], - "category": "public.app-category.utilities", - "icon": "public/images/favicon.icns" - }, - "linux": { - "target": [ - { "target": "AppImage", "arch": ["x64"] }, - { "target": "deb", "arch": ["x64"] } - ], - "category": "Utility", - "icon": "public/images/IconA256.png", - "desktop": { - "Name": "SoonDesign", - "Comment": "tools of design soon files" - } - } - }, + "start": "electron-forge start", + "package:x64": "electron-forge package --arch=x64", + "package:arm64": "electron-forge package --arch=arm64", + "package:universal": "electron-forge package --arch=universal" + }, "electronPackagerConfig": { "packageManager": "npm", - "icon": "./public/images/favicon.ico" + "icon": "./public/images/favicon.icns" }, - - "repository": "", + "config": { + "forge": { + "packagerConfig": { + "icon": "./public/images/favicon.icns", + "asar": true, + "extraResource": [ + "./help" + ], + "osxUniversal": { + } + } + } + }, "keywords": [ - "Electron", - "quick", + "SoonDesign", + "soon", "start", "tutorial", "demo" ], - "author": "", "license": "MIT", "devDependencies": { - "electron": "^25.0.0", - "electron-builder": "^23.3.3" + "@electron-forge/cli": "^6.2.0", + "electron": "^25.0.0" }, "dependencies": { "@electron/remote": "^2.0.8", "buffer": "^6.0.3", "bytenode": "^1.3.4", + "create-dmg": "^7.0.0", "electron-squirrel-startup": "^1.0.1", "font-list": "^1.4.2", "jr-qrcode": "^1.1.4", diff --git a/preload.js b/preload.js index afffd3a..247535b 100644 --- a/preload.js +++ b/preload.js @@ -1,5 +1,6 @@ -// All of the Node.js APIs are available in the preload process. -// It has the same sandbox as a Chrome extension. +const { ipcRenderer } = require('electron') + +// 1. 显示版本号逻辑 (保持不变) window.addEventListener('DOMContentLoaded', () => { const replaceText = (selector, text) => { const element = document.getElementById(selector) @@ -10,3 +11,19 @@ window.addEventListener('DOMContentLoaded', () => { replaceText(`${type}-version`, process.versions[type]) } }) + +// 2. 修正:直接挂载到 window 对象 +// 因为你的 main.js 中 contextIsolation: false,所以直接赋值即可 +window.sysAPI = { + // 读取历史记录 + readHistory: () => ipcRenderer.invoke('history:read'), + + // 写入历史记录 + writeHistory: (data) => ipcRenderer.invoke('history:write', data), + + // 读取任意 JSON 文件 + readJsonFile: (filePath) => ipcRenderer.invoke('file:read-json', filePath), + //获取软件版本 + getAppVersion: () => ipcRenderer.invoke('app:get-version') + +}; \ No newline at end of file diff --git a/public/images/back_bg_1.png b/public/images/back_bg_1.png index d0565abd7d53f258a2b952f78cd31918aac27d87..64497caea5141bb86dadbc2749d67ee4054aad74 100644 GIT binary patch literal 61855 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 KkpI2=&;JdTxHbv^ literal 133002 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 diff --git a/public/images/back_bg_2.png b/public/images/back_bg_2.png index d335ef0cc19c784e577e9e3afc112cba20c6e70b..a2c154a550b8538dc6fef385350329768a5e97a4 100644 GIT binary patch literal 9225 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 diff --git a/public/images/bg_1.png b/public/images/bg_1.png index abd53879bb1ccc96421abbbbf495aaa496ab088f..36fcc2e9742a3ed888a89440a1c283a63a069e4a 100644 GIT binary patch literal 87966 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 literal 144539 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

F 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 M_WrWQE#lh$17|=jh5!Hn diff --git a/public/images/front_bg2_2.png b/public/images/front_bg2_2.png index f3c5dbb0ebc9ecf6cf8a3b9801c8e112d65773ce..16d17fc6e276c27a4e027593a1e77a56971bf40a 100644 GIT binary patch literal 22372 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:T-et=HRCr?ht#h~b2!nJB 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 oDn5A-V&zopr01D1fRR910 literal 21873 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 Q(2WOFn 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 0); + +/** + * True when in environment that's probably Node.js + * @type boolean + */ +fabric.isLikelyNode = typeof Buffer !== 'undefined' && + typeof window === 'undefined'; + +/* _FROM_SVG_START_ */ +/** + * Attributes parsed from all SVG elements + * @type array + */ +fabric.SHARED_ATTRIBUTES = [ + 'display', + 'transform', + 'fill', 'fill-opacity', 'fill-rule', + 'opacity', + 'stroke', 'stroke-dasharray', 'stroke-linecap', 'stroke-dashoffset', + 'stroke-linejoin', 'stroke-miterlimit', + 'stroke-opacity', 'stroke-width', + 'id', 'paint-order', 'vector-effect', + 'instantiated_by_use', 'clip-path', +]; +/* _FROM_SVG_END_ */ + +/** + * Pixel per Inch as a default value set to 96. Can be changed for more realistic conversion. + */ +fabric.DPI = 96; +fabric.reNum = '(?:[-+]?(?:\\d+|\\d*\\.\\d+)(?:[eE][-+]?\\d+)?)'; +fabric.commaWsp = '(?:\\s+,?\\s*|,\\s*)'; +fabric.rePathCommand = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:[eE][-+]?\d+)?)/ig; +fabric.reNonWord = /[ \n\.,;!\?\-]/; +fabric.fontPaths = { }; +fabric.iMatrix = [1, 0, 0, 1, 0, 0]; +fabric.svgNS = 'http://www.w3.org/2000/svg'; + +/** + * Pixel limit for cache canvases. 1Mpx , 4Mpx should be fine. + * @since 1.7.14 + * @type Number + * @default + */ +fabric.perfLimitSizeTotal = 2097152; + +/** + * Pixel limit for cache canvases width or height. IE fixes the maximum at 5000 + * @since 1.7.14 + * @type Number + * @default + */ +fabric.maxCacheSideLimit = 4096; + +/** + * Lowest pixel limit for cache canvases, set at 256PX + * @since 1.7.14 + * @type Number + * @default + */ +fabric.minCacheSideLimit = 256; + +/** + * Cache Object for widths of chars in text rendering. + */ +fabric.charWidthsCache = { }; + +/** + * if webgl is enabled and available, textureSize will determine the size + * of the canvas backend + * @since 2.0.0 + * @type Number + * @default + */ +fabric.textureSize = 2048; + +/** + * When 'true', style information is not retained when copy/pasting text, making + * pasted text use destination style. + * Defaults to 'false'. + * @type Boolean + * @default + */ +fabric.disableStyleCopyPaste = false; + +/** + * Enable webgl for filtering picture is available + * A filtering backend will be initialized, this will both take memory and + * time since a default 2048x2048 canvas will be created for the gl context + * @since 2.0.0 + * @type Boolean + * @default + */ +fabric.enableGLFiltering = true; + +/** + * Device Pixel Ratio + * @see https://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/SettingUptheCanvas/SettingUptheCanvas.html + */ +fabric.devicePixelRatio = fabric.window.devicePixelRatio || + fabric.window.webkitDevicePixelRatio || + fabric.window.mozDevicePixelRatio || + 1; +/** + * Browser-specific constant to adjust CanvasRenderingContext2D.shadowBlur value, + * which is unitless and not rendered equally across browsers. + * + * Values that work quite well (as of October 2017) are: + * - Chrome: 1.5 + * - Edge: 1.75 + * - Firefox: 0.9 + * - Safari: 0.95 + * + * @since 2.0.0 + * @type Number + * @default 1 + */ +fabric.browserShadowBlurConstant = 1; + +/** + * This object contains the result of arc to bezier conversion for faster retrieving if the same arc needs to be converted again. + * It was an internal variable, is accessible since version 2.3.4 + */ +fabric.arcToSegmentsCache = { }; + +/** + * This object keeps the results of the boundsOfCurve calculation mapped by the joined arguments necessary to calculate it. + * It does speed up calculation, if you parse and add always the same paths, but in case of heavy usage of freedrawing + * you do not get any speed benefit and you get a big object in memory. + * The object was a private variable before, while now is appended to the lib so that you have access to it and you + * can eventually clear it. + * It was an internal variable, is accessible since version 2.3.4 + */ +fabric.boundsOfCurveCache = { }; + +/** + * If disabled boundsOfCurveCache is not used. For apps that make heavy usage of pencil drawing probably disabling it is better + * @default true + */ +fabric.cachesBoundsOfCurve = true; + +/** + * Skip performance testing of setupGLContext and force the use of putImageData that seems to be the one that works best on + * Chrome + old hardware. if your users are experiencing empty images after filtering you may try to force this to true + * this has to be set before instantiating the filtering backend ( before filtering the first image ) + * @type Boolean + * @default false + */ +fabric.forceGLPutImageData = false; + +fabric.initFilterBackend = function() { + if (fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize)) { + console.log('max texture size: ' + fabric.maxTextureSize); + return (new fabric.WebglFilterBackend({ tileSize: fabric.textureSize })); + } + else if (fabric.Canvas2dFilterBackend) { + return (new fabric.Canvas2dFilterBackend()); + } +}; + + +if (typeof document !== 'undefined' && typeof window !== 'undefined') { + // ensure globality even if entire library were function wrapped (as in Meteor.js packaging system) + window.fabric = fabric; +} + + +(function() { + + /** + * @private + * @param {String} eventName + * @param {Function} handler + */ + function _removeEventListener(eventName, handler) { + if (!this.__eventListeners[eventName]) { + return; + } + var eventListener = this.__eventListeners[eventName]; + if (handler) { + eventListener[eventListener.indexOf(handler)] = false; + } + else { + fabric.util.array.fill(eventListener, false); + } + } + + /** + * Observes specified event + * @memberOf fabric.Observable + * @alias on + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function that receives a notification when an event of the specified type occurs + * @return {Self} thisArg + * @chainable + */ + function on(eventName, handler) { + if (!this.__eventListeners) { + this.__eventListeners = { }; + } + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + this.on(prop, eventName[prop]); + } + } + else { + if (!this.__eventListeners[eventName]) { + this.__eventListeners[eventName] = []; + } + this.__eventListeners[eventName].push(handler); + } + return this; + } + + function _once(eventName, handler) { + var _handler = function () { + handler.apply(this, arguments); + this.off(eventName, _handler); + }.bind(this); + this.on(eventName, _handler); + } + + function once(eventName, handler) { + // one object with key/value pairs was passed + if (arguments.length === 1) { + for (var prop in eventName) { + _once.call(this, prop, eventName[prop]); + } + } + else { + _once.call(this, eventName, handler); + } + return this; + } + + /** + * Stops event observing for a particular event handler. Calling this method + * without arguments removes all handlers for all events + * @memberOf fabric.Observable + * @alias off + * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler}) + * @param {Function} handler Function to be deleted from EventListeners + * @return {Self} thisArg + * @chainable + */ + function off(eventName, handler) { + if (!this.__eventListeners) { + return this; + } + + // remove all key/value pairs (event name -> event handler) + if (arguments.length === 0) { + for (eventName in this.__eventListeners) { + _removeEventListener.call(this, eventName); + } + } + // one object with key/value pairs was passed + else if (arguments.length === 1 && typeof arguments[0] === 'object') { + for (var prop in eventName) { + _removeEventListener.call(this, prop, eventName[prop]); + } + } + else { + _removeEventListener.call(this, eventName, handler); + } + return this; + } + + /** + * Fires event with an optional options object + * @memberOf fabric.Observable + * @param {String} eventName Event name to fire + * @param {Object} [options] Options object + * @return {Self} thisArg + * @chainable + */ + function fire(eventName, options) { + if (!this.__eventListeners) { + return this; + } + + var listenersForEvent = this.__eventListeners[eventName]; + if (!listenersForEvent) { + return this; + } + + for (var i = 0, len = listenersForEvent.length; i < len; i++) { + listenersForEvent[i] && listenersForEvent[i].call(this, options || { }); + } + this.__eventListeners[eventName] = listenersForEvent.filter(function(value) { + return value !== false; + }); + return this; + } + + /** + * @namespace fabric.Observable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#events} + * @see {@link http://fabricjs.com/events|Events demo} + */ + fabric.Observable = { + fire: fire, + on: on, + once: once, + off: off, + }; +})(); + + +/** + * @namespace fabric.Collection + */ +fabric.Collection = { + + _objects: [], + + /** + * Adds objects to collection, Canvas or Group, then renders canvas + * (if `renderOnAddRemove` is not `false`). + * in case of Group no changes to bounding box are made. + * Objects should be instances of (or inherit from) fabric.Object + * Use of this function is highly discouraged for groups. + * you can add a bunch of objects with the add method but then you NEED + * to run a addWithUpdate call for the Group class or position/bbox will be wrong. + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + * @chainable + */ + add: function () { + this._objects.push.apply(this._objects, arguments); + if (this._onObjectAdded) { + for (var i = 0, length = arguments.length; i < length; i++) { + this._onObjectAdded(arguments[i]); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Inserts an object into collection at specified index, then renders canvas (if `renderOnAddRemove` is not `false`) + * An object should be an instance of (or inherit from) fabric.Object + * Use of this function is highly discouraged for groups. + * you can add a bunch of objects with the insertAt method but then you NEED + * to run a addWithUpdate call for the Group class or position/bbox will be wrong. + * @param {Object} object Object to insert + * @param {Number} index Index to insert object at + * @param {Boolean} nonSplicing When `true`, no splicing (shifting) of objects occurs + * @return {Self} thisArg + * @chainable + */ + insertAt: function (object, index, nonSplicing) { + var objects = this._objects; + if (nonSplicing) { + objects[index] = object; + } + else { + objects.splice(index, 0, object); + } + this._onObjectAdded && this._onObjectAdded(object); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Removes objects from a collection, then renders canvas (if `renderOnAddRemove` is not `false`) + * @param {...fabric.Object} object Zero or more fabric instances + * @return {Self} thisArg + * @chainable + */ + remove: function() { + var objects = this._objects, + index, somethingRemoved = false; + + for (var i = 0, length = arguments.length; i < length; i++) { + index = objects.indexOf(arguments[i]); + + // only call onObjectRemoved if an object was actually removed + if (index !== -1) { + somethingRemoved = true; + objects.splice(index, 1); + this._onObjectRemoved && this._onObjectRemoved(arguments[i]); + } + } + + this.renderOnAddRemove && somethingRemoved && this.requestRenderAll(); + return this; + }, + + /** + * Executes given function for each object in this group + * @param {Function} callback + * Callback invoked with current object as first argument, + * index - as second and an array of all objects - as third. + * Callback is invoked in a context of Global Object (e.g. `window`) + * when no `context` argument is given + * + * @param {Object} context Context (aka thisObject) + * @return {Self} thisArg + * @chainable + */ + forEachObject: function(callback, context) { + var objects = this.getObjects(); + for (var i = 0, len = objects.length; i < len; i++) { + callback.call(context, objects[i], i, objects); + } + return this; + }, + + /** + * Returns an array of children objects of this instance + * Type parameter introduced in 1.3.10 + * since 2.3.5 this method return always a COPY of the array; + * @param {String} [type] When specified, only objects of this type are returned + * @return {Array} + */ + getObjects: function(type) { + if (typeof type === 'undefined') { + return this._objects.concat(); + } + return this._objects.filter(function(o) { + return o.type === type; + }); + }, + + /** + * Returns object at specified index + * @param {Number} index + * @return {Self} thisArg + */ + item: function (index) { + return this._objects[index]; + }, + + /** + * Returns true if collection contains no objects + * @return {Boolean} true if collection is empty + */ + isEmpty: function () { + return this._objects.length === 0; + }, + + /** + * Returns a size of a collection (i.e: length of an array containing its objects) + * @return {Number} Collection size + */ + size: function() { + return this._objects.length; + }, + + /** + * Returns true if collection contains an object + * @param {Object} object Object to check against + * @param {Boolean} [deep=false] `true` to check all descendants, `false` to check only `_objects` + * @return {Boolean} `true` if collection contains an object + */ + contains: function (object, deep) { + if (this._objects.indexOf(object) > -1) { + return true; + } + else if (deep) { + return this._objects.some(function (obj) { + return typeof obj.contains === 'function' && obj.contains(object, true); + }); + } + return false; + }, + + /** + * Returns number representation of a collection complexity + * @return {Number} complexity + */ + complexity: function () { + return this._objects.reduce(function (memo, current) { + memo += current.complexity ? current.complexity() : 0; + return memo; + }, 0); + } +}; + + +/** + * @namespace fabric.CommonMethods + */ +fabric.CommonMethods = { + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + _setOptions: function(options) { + for (var prop in options) { + this.set(prop, options[prop]); + } + }, + + /** + * @private + * @param {Object} [filler] Options object + * @param {String} [property] property to set the Gradient to + */ + _initGradient: function(filler, property) { + if (filler && filler.colorStops && !(filler instanceof fabric.Gradient)) { + this.set(property, new fabric.Gradient(filler)); + } + }, + + /** + * @private + * @param {Object} [filler] Options object + * @param {String} [property] property to set the Pattern to + * @param {Function} [callback] callback to invoke after pattern load + */ + _initPattern: function(filler, property, callback) { + if (filler && filler.source && !(filler instanceof fabric.Pattern)) { + this.set(property, new fabric.Pattern(filler, callback)); + } + else { + callback && callback(); + } + }, + + /** + * @private + */ + _setObject: function(obj) { + for (var prop in obj) { + this._set(prop, obj[prop]); + } + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + if (typeof key === 'object') { + this._setObject(key); + } + else { + this._set(key, value); + } + return this; + }, + + _set: function(key, value) { + this[key] = value; + }, + + /** + * Toggles specified property from `true` to `false` or from `false` to `true` + * @param {String} property Property to toggle + * @return {fabric.Object} thisArg + * @chainable + */ + toggle: function(property) { + var value = this.get(property); + if (typeof value === 'boolean') { + this.set(property, !value); + } + return this; + }, + + /** + * Basic getter + * @param {String} property Property name + * @return {*} value of a property + */ + get: function(property) { + return this[property]; + } +}; + + +(function(global) { + + var sqrt = Math.sqrt, + atan2 = Math.atan2, + pow = Math.pow, + PiBy180 = Math.PI / 180, + PiBy2 = Math.PI / 2; + + /** + * @namespace fabric.util + */ + fabric.util = { + + /** + * Calculate the cos of an angle, avoiding returning floats for known results + * @static + * @memberOf fabric.util + * @param {Number} angle the angle in radians or in degree + * @return {Number} + */ + cos: function(angle) { + if (angle === 0) { return 1; } + if (angle < 0) { + // cos(a) = cos(-a) + angle = -angle; + } + var angleSlice = angle / PiBy2; + switch (angleSlice) { + case 1: case 3: return 0; + case 2: return -1; + } + return Math.cos(angle); + }, + + /** + * Calculate the sin of an angle, avoiding returning floats for known results + * @static + * @memberOf fabric.util + * @param {Number} angle the angle in radians or in degree + * @return {Number} + */ + sin: function(angle) { + if (angle === 0) { return 0; } + var angleSlice = angle / PiBy2, sign = 1; + if (angle < 0) { + // sin(-a) = -sin(a) + sign = -1; + } + switch (angleSlice) { + case 1: return sign; + case 2: return 0; + case 3: return -sign; + } + return Math.sin(angle); + }, + + /** + * Removes value from an array. + * Presence of value (and its position in an array) is determined via `Array.prototype.indexOf` + * @static + * @memberOf fabric.util + * @param {Array} array + * @param {*} value + * @return {Array} original array + */ + removeFromArray: function(array, value) { + var idx = array.indexOf(value); + if (idx !== -1) { + array.splice(idx, 1); + } + return array; + }, + + /** + * Returns random number between 2 specified ones. + * @static + * @memberOf fabric.util + * @param {Number} min lower limit + * @param {Number} max upper limit + * @return {Number} random value (between min and max) + */ + getRandomInt: function(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; + }, + + /** + * Transforms degrees to radians. + * @static + * @memberOf fabric.util + * @param {Number} degrees value in degrees + * @return {Number} value in radians + */ + degreesToRadians: function(degrees) { + return degrees * PiBy180; + }, + + /** + * Transforms radians to degrees. + * @static + * @memberOf fabric.util + * @param {Number} radians value in radians + * @return {Number} value in degrees + */ + radiansToDegrees: function(radians) { + return radians / PiBy180; + }, + + /** + * Rotates `point` around `origin` with `radians` + * @static + * @memberOf fabric.util + * @param {fabric.Point} point The point to rotate + * @param {fabric.Point} origin The origin of the rotation + * @param {Number} radians The radians of the angle for the rotation + * @return {fabric.Point} The new rotated point + */ + rotatePoint: function(point, origin, radians) { + var newPoint = new fabric.Point(point.x - origin.x, point.y - origin.y), + v = fabric.util.rotateVector(newPoint, radians); + return new fabric.Point(v.x, v.y).addEquals(origin); + }, + + /** + * Rotates `vector` with `radians` + * @static + * @memberOf fabric.util + * @param {Object} vector The vector to rotate (x and y) + * @param {Number} radians The radians of the angle for the rotation + * @return {Object} The new rotated point + */ + rotateVector: function(vector, radians) { + var sin = fabric.util.sin(radians), + cos = fabric.util.cos(radians), + rx = vector.x * cos - vector.y * sin, + ry = vector.x * sin + vector.y * cos; + return { + x: rx, + y: ry + }; + }, + + /** + * Apply transform t to point p + * @static + * @memberOf fabric.util + * @param {fabric.Point} p The point to transform + * @param {Array} t The transform + * @param {Boolean} [ignoreOffset] Indicates that the offset should not be applied + * @return {fabric.Point} The transformed point + */ + transformPoint: function(p, t, ignoreOffset) { + if (ignoreOffset) { + return new fabric.Point( + t[0] * p.x + t[2] * p.y, + t[1] * p.x + t[3] * p.y + ); + } + return new fabric.Point( + t[0] * p.x + t[2] * p.y + t[4], + t[1] * p.x + t[3] * p.y + t[5] + ); + }, + + /** + * Returns coordinates of points's bounding rectangle (left, top, width, height) + * @param {Array} points 4 points array + * @param {Array} [transform] an array of 6 numbers representing a 2x3 transform matrix + * @return {Object} Object with left, top, width, height properties + */ + makeBoundingBoxFromPoints: function(points, transform) { + if (transform) { + for (var i = 0; i < points.length; i++) { + points[i] = fabric.util.transformPoint(points[i], transform); + } + } + var xPoints = [points[0].x, points[1].x, points[2].x, points[3].x], + minX = fabric.util.array.min(xPoints), + maxX = fabric.util.array.max(xPoints), + width = maxX - minX, + yPoints = [points[0].y, points[1].y, points[2].y, points[3].y], + minY = fabric.util.array.min(yPoints), + maxY = fabric.util.array.max(yPoints), + height = maxY - minY; + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Invert transformation t + * @static + * @memberOf fabric.util + * @param {Array} t The transform + * @return {Array} The inverted transform + */ + invertTransform: function(t) { + var a = 1 / (t[0] * t[3] - t[1] * t[2]), + r = [a * t[3], -a * t[1], -a * t[2], a * t[0]], + o = fabric.util.transformPoint({ x: t[4], y: t[5] }, r, true); + r[4] = -o.x; + r[5] = -o.y; + return r; + }, + + /** + * A wrapper around Number#toFixed, which contrary to native method returns number, not string. + * @static + * @memberOf fabric.util + * @param {Number|String} number number to operate on + * @param {Number} fractionDigits number of fraction digits to "leave" + * @return {Number} + */ + toFixed: function(number, fractionDigits) { + return parseFloat(Number(number).toFixed(fractionDigits)); + }, + + /** + * Converts from attribute value to pixel value if applicable. + * Returns converted pixels or original value not converted. + * @param {Number|String} value number to operate on + * @param {Number} fontSize + * @return {Number|String} + */ + parseUnit: function(value, fontSize) { + var unit = /\D{0,2}$/.exec(value), + number = parseFloat(value); + if (!fontSize) { + fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + switch (unit[0]) { + case 'mm': + return number * fabric.DPI / 25.4; + + case 'cm': + return number * fabric.DPI / 2.54; + + case 'in': + return number * fabric.DPI; + + case 'pt': + return number * fabric.DPI / 72; // or * 4 / 3 + + case 'pc': + return number * fabric.DPI / 72 * 12; // or * 16 + + case 'em': + return number * fontSize; + + default: + return number; + } + }, + + /** + * Function which always returns `false`. + * @static + * @memberOf fabric.util + * @return {Boolean} + */ + falseFunction: function() { + return false; + }, + + /** + * Returns klass "Class" object of given namespace + * @memberOf fabric.util + * @param {String} type Type of object (eg. 'circle') + * @param {String} namespace Namespace to get klass "Class" object from + * @return {Object} klass "Class" + */ + getKlass: function(type, namespace) { + // capitalize first letter only + type = fabric.util.string.camelize(type.charAt(0).toUpperCase() + type.slice(1)); + return fabric.util.resolveNamespace(namespace)[type]; + }, + + /** + * Returns array of attributes for given svg that fabric parses + * @memberOf fabric.util + * @param {String} type Type of svg element (eg. 'circle') + * @return {Array} string names of supported attributes + */ + getSvgAttributes: function(type) { + var attributes = [ + 'instantiated_by_use', + 'style', + 'id', + 'class' + ]; + switch (type) { + case 'linearGradient': + attributes = attributes.concat(['x1', 'y1', 'x2', 'y2', 'gradientUnits', 'gradientTransform']); + break; + case 'radialGradient': + attributes = attributes.concat(['gradientUnits', 'gradientTransform', 'cx', 'cy', 'r', 'fx', 'fy', 'fr']); + break; + case 'stop': + attributes = attributes.concat(['offset', 'stop-color', 'stop-opacity']); + break; + } + return attributes; + }, + + /** + * Returns object of given namespace + * @memberOf fabric.util + * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' + * @return {Object} Object for given namespace (default fabric) + */ + resolveNamespace: function(namespace) { + if (!namespace) { + return fabric; + } + + var parts = namespace.split('.'), + len = parts.length, i, + obj = global || fabric.window; + + for (i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + + return obj; + }, + + /** + * Loads image element from given url and passes it to a callback + * @memberOf fabric.util + * @param {String} url URL representing an image + * @param {Function} callback Callback; invoked with loaded image + * @param {*} [context] Context to invoke callback in + * @param {Object} [crossOrigin] crossOrigin value to set image element to + */ + loadImage: function(url, callback, context, crossOrigin) { + if (!url) { + callback && callback.call(context, url); + return; + } + + var img = fabric.util.createImage(); + + /** @ignore */ + var onLoadCallback = function () { + callback && callback.call(context, img, false); + img = img.onload = img.onerror = null; + }; + + img.onload = onLoadCallback; + /** @ignore */ + img.onerror = function() { + fabric.log('Error loading ' + img.src); + callback && callback.call(context, null, true); + img = img.onload = img.onerror = null; + }; + + // data-urls appear to be buggy with crossOrigin + // https://github.com/kangax/fabric.js/commit/d0abb90f1cd5c5ef9d2a94d3fb21a22330da3e0a#commitcomment-4513767 + // see https://code.google.com/p/chromium/issues/detail?id=315152 + // https://bugzilla.mozilla.org/show_bug.cgi?id=935069 + // crossOrigin null is the same as not set. + if (url.indexOf('data') !== 0 && + crossOrigin !== undefined && + crossOrigin !== null) { + img.crossOrigin = crossOrigin; + } + + // IE10 / IE11-Fix: SVG contents from data: URI + // will only be available if the IMG is present + // in the DOM (and visible) + if (url.substring(0,14) === 'data:image/svg') { + img.onload = null; + fabric.util.loadImageInDom(img, onLoadCallback); + } + + img.src = url; + }, + + /** + * Attaches SVG image with data: URL to the dom + * @memberOf fabric.util + * @param {Object} img Image object with data:image/svg src + * @param {Function} callback Callback; invoked with loaded image + * @return {Object} DOM element (div containing the SVG image) + */ + loadImageInDom: function(img, onLoadCallback) { + var div = fabric.document.createElement('div'); + div.style.width = div.style.height = '1px'; + div.style.left = div.style.top = '-100%'; + div.style.position = 'absolute'; + div.appendChild(img); + fabric.document.querySelector('body').appendChild(div); + /** + * Wrap in function to: + * 1. Call existing callback + * 2. Cleanup DOM + */ + img.onload = function () { + onLoadCallback(); + div.parentNode.removeChild(div); + div = null; + }; + }, + + /** + * Creates corresponding fabric instances from their object representations + * @static + * @memberOf fabric.util + * @param {Array} objects Objects to enliven + * @param {Function} callback Callback to invoke when all objects are created + * @param {String} namespace Namespace to get klass "Class" object from + * @param {Function} reviver Method for further parsing of object elements, + * called after each fabric object created. + */ + enlivenObjects: function(objects, callback, namespace, reviver) { + objects = objects || []; + + var enlivenedObjects = [], + numLoadedObjects = 0, + numTotalObjects = objects.length; + + function onLoaded() { + if (++numLoadedObjects === numTotalObjects) { + callback && callback(enlivenedObjects.filter(function(obj) { + // filter out undefined objects (objects that gave error) + return obj; + })); + } + } + + if (!numTotalObjects) { + callback && callback(enlivenedObjects); + return; + } + + objects.forEach(function (o, index) { + // if sparse array + if (!o || !o.type) { + onLoaded(); + return; + } + var klass = fabric.util.getKlass(o.type, namespace); + klass.fromObject(o, function (obj, error) { + error || (enlivenedObjects[index] = obj); + reviver && reviver(o, obj, error); + onLoaded(); + }); + }); + }, + + /** + * Create and wait for loading of patterns + * @static + * @memberOf fabric.util + * @param {Array} patterns Objects to enliven + * @param {Function} callback Callback to invoke when all objects are created + * called after each fabric object created. + */ + enlivenPatterns: function(patterns, callback) { + patterns = patterns || []; + + function onLoaded() { + if (++numLoadedPatterns === numPatterns) { + callback && callback(enlivenedPatterns); + } + } + + var enlivenedPatterns = [], + numLoadedPatterns = 0, + numPatterns = patterns.length; + + if (!numPatterns) { + callback && callback(enlivenedPatterns); + return; + } + + patterns.forEach(function (p, index) { + if (p && p.source) { + new fabric.Pattern(p, function(pattern) { + enlivenedPatterns[index] = pattern; + onLoaded(); + }); + } + else { + enlivenedPatterns[index] = p; + onLoaded(); + } + }); + }, + + /** + * Groups SVG elements (usually those retrieved from SVG document) + * @static + * @memberOf fabric.util + * @param {Array} elements SVG elements to group + * @param {Object} [options] Options object + * @param {String} path Value to set sourcePath to + * @return {fabric.Object|fabric.Group} + */ + groupSVGElements: function(elements, options, path) { + var object; + if (elements && elements.length === 1) { + return elements[0]; + } + if (options) { + if (options.width && options.height) { + options.centerPoint = { + x: options.width / 2, + y: options.height / 2 + }; + } + else { + delete options.width; + delete options.height; + } + } + object = new fabric.Group(elements, options); + if (typeof path !== 'undefined') { + object.sourcePath = path; + } + return object; + }, + + /** + * Populates an object with properties of another object + * @static + * @memberOf fabric.util + * @param {Object} source Source object + * @param {Object} destination Destination object + * @return {Array} properties Properties names to include + */ + populateWithProperties: function(source, destination, properties) { + if (properties && Object.prototype.toString.call(properties) === '[object Array]') { + for (var i = 0, len = properties.length; i < len; i++) { + if (properties[i] in source) { + destination[properties[i]] = source[properties[i]]; + } + } + } + }, + + /** + * WARNING: THIS WAS TO SUPPORT OLD BROWSERS. deprecated. + * WILL BE REMOVED IN FABRIC 5.0 + * Draws a dashed line between two points + * + * This method is used to draw dashed line around selection area. + * See dotted stroke in canvas + * + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x start x coordinate + * @param {Number} y start y coordinate + * @param {Number} x2 end x coordinate + * @param {Number} y2 end y coordinate + * @param {Array} da dash array pattern + * @deprecated + */ + drawDashedLine: function(ctx, x, y, x2, y2, da) { + var dx = x2 - x, + dy = y2 - y, + len = sqrt(dx * dx + dy * dy), + rot = atan2(dy, dx), + dc = da.length, + di = 0, + draw = true; + + ctx.save(); + ctx.translate(x, y); + ctx.moveTo(0, 0); + ctx.rotate(rot); + + x = 0; + while (len > x) { + x += da[di++ % dc]; + if (x > len) { + x = len; + } + ctx[draw ? 'lineTo' : 'moveTo'](x, 0); + draw = !draw; + } + + ctx.restore(); + }, + + /** + * Creates canvas element + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ + createCanvasElement: function() { + return fabric.document.createElement('canvas'); + }, + + /** + * Creates a canvas element that is a copy of another and is also painted + * @param {CanvasElement} canvas to copy size and content of + * @static + * @memberOf fabric.util + * @return {CanvasElement} initialized canvas element + */ + copyCanvasElement: function(canvas) { + var newCanvas = fabric.util.createCanvasElement(); + newCanvas.width = canvas.width; + newCanvas.height = canvas.height; + newCanvas.getContext('2d').drawImage(canvas, 0, 0); + return newCanvas; + }, + + /** + * since 2.6.0 moved from canvas instance to utility. + * @param {CanvasElement} canvasEl to copy size and content of + * @param {String} format 'jpeg' or 'png', in some browsers 'webp' is ok too + * @param {Number} quality <= 1 and > 0 + * @static + * @memberOf fabric.util + * @return {String} data url + */ + toDataURL: function(canvasEl, format, quality) { + return canvasEl.toDataURL('image/' + format, quality); + }, + + /** + * Creates image element (works on client and node) + * @static + * @memberOf fabric.util + * @return {HTMLImageElement} HTML image element + */ + createImage: function() { + return fabric.document.createElement('img'); + }, + + /** + * Multiply matrix A by matrix B to nest transformations + * @static + * @memberOf fabric.util + * @param {Array} a First transformMatrix + * @param {Array} b Second transformMatrix + * @param {Boolean} is2x2 flag to multiply matrices as 2x2 matrices + * @return {Array} The product of the two transform matrices + */ + multiplyTransformMatrices: function(a, b, is2x2) { + // Matrix multiply a * b + return [ + a[0] * b[0] + a[2] * b[1], + a[1] * b[0] + a[3] * b[1], + a[0] * b[2] + a[2] * b[3], + a[1] * b[2] + a[3] * b[3], + is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4], + is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5] + ]; + }, + + /** + * Decomposes standard 2x3 matrix into transform components + * @static + * @memberOf fabric.util + * @param {Array} a transformMatrix + * @return {Object} Components of transform + */ + qrDecompose: function(a) { + var angle = atan2(a[1], a[0]), + denom = pow(a[0], 2) + pow(a[1], 2), + scaleX = sqrt(denom), + scaleY = (a[0] * a[3] - a[2] * a[1]) / scaleX, + skewX = atan2(a[0] * a[2] + a[1] * a [3], denom); + return { + angle: angle / PiBy180, + scaleX: scaleX, + scaleY: scaleY, + skewX: skewX / PiBy180, + skewY: 0, + translateX: a[4], + translateY: a[5] + }; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] angle in degrees + * @return {Number[]} transform matrix + */ + calcRotateMatrix: function(options) { + if (!options.angle) { + return fabric.iMatrix.concat(); + } + var theta = fabric.util.degreesToRadians(options.angle), + cos = fabric.util.cos(theta), + sin = fabric.util.sin(theta); + return [cos, sin, -sin, cos, 0, 0]; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet. + * is called DimensionsTransformMatrix because those properties are the one that influence + * the size of the resulting box of the object. + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Boolean} [options.flipX] + * @param {Boolean} [options.flipY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @return {Number[]} transform matrix + */ + calcDimensionsMatrix: function(options) { + var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX, + scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY, + scaleMatrix = [ + options.flipX ? -scaleX : scaleX, + 0, + 0, + options.flipY ? -scaleY : scaleY, + 0, + 0], + multiply = fabric.util.multiplyTransformMatrices, + degreesToRadians = fabric.util.degreesToRadians; + if (options.skewX) { + scaleMatrix = multiply( + scaleMatrix, + [1, 0, Math.tan(degreesToRadians(options.skewX)), 1], + true); + } + if (options.skewY) { + scaleMatrix = multiply( + scaleMatrix, + [1, Math.tan(degreesToRadians(options.skewY)), 0, 1], + true); + } + return scaleMatrix; + }, + + /** + * Returns a transform matrix starting from an object of the same kind of + * the one returned from qrDecompose, useful also if you want to calculate some + * transformations from an object that is not enlived yet + * @static + * @memberOf fabric.util + * @param {Object} options + * @param {Number} [options.angle] + * @param {Number} [options.scaleX] + * @param {Number} [options.scaleY] + * @param {Boolean} [options.flipX] + * @param {Boolean} [options.flipY] + * @param {Number} [options.skewX] + * @param {Number} [options.skewX] + * @param {Number} [options.translateX] + * @param {Number} [options.translateY] + * @return {Number[]} transform matrix + */ + composeMatrix: function(options) { + var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0], + multiply = fabric.util.multiplyTransformMatrices; + if (options.angle) { + matrix = multiply(matrix, fabric.util.calcRotateMatrix(options)); + } + if (options.scaleX !== 1 || options.scaleY !== 1 || + options.skewX || options.skewY || options.flipX || options.flipY) { + matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options)); + } + return matrix; + }, + + /** + * reset an object transform state to neutral. Top and left are not accounted for + * @static + * @memberOf fabric.util + * @param {fabric.Object} target object to transform + */ + resetObjectTransform: function (target) { + target.scaleX = 1; + target.scaleY = 1; + target.skewX = 0; + target.skewY = 0; + target.flipX = false; + target.flipY = false; + target.rotate(0); + }, + + /** + * Extract Object transform values + * @static + * @memberOf fabric.util + * @param {fabric.Object} target object to read from + * @return {Object} Components of transform + */ + saveObjectTransform: function (target) { + return { + scaleX: target.scaleX, + scaleY: target.scaleY, + skewX: target.skewX, + skewY: target.skewY, + angle: target.angle, + left: target.left, + flipX: target.flipX, + flipY: target.flipY, + top: target.top + }; + }, + + /** + * Returns true if context has transparent pixel + * at specified location (taking tolerance into account) + * @param {CanvasRenderingContext2D} ctx context + * @param {Number} x x coordinate + * @param {Number} y y coordinate + * @param {Number} tolerance Tolerance + */ + isTransparent: function(ctx, x, y, tolerance) { + + // If tolerance is > 0 adjust start coords to take into account. + // If moves off Canvas fix to 0 + if (tolerance > 0) { + if (x > tolerance) { + x -= tolerance; + } + else { + x = 0; + } + if (y > tolerance) { + y -= tolerance; + } + else { + y = 0; + } + } + + var _isTransparent = true, i, temp, + imageData = ctx.getImageData(x, y, (tolerance * 2) || 1, (tolerance * 2) || 1), + l = imageData.data.length; + + // Split image data - for tolerance > 1, pixelDataSize = 4; + for (i = 3; i < l; i += 4) { + temp = imageData.data[i]; + _isTransparent = temp <= 0; + if (_isTransparent === false) { + break; // Stop if colour found + } + } + + imageData = null; + + return _isTransparent; + }, + + /** + * Parse preserveAspectRatio attribute from element + * @param {string} attribute to be parsed + * @return {Object} an object containing align and meetOrSlice attribute + */ + parsePreserveAspectRatioAttribute: function(attribute) { + var meetOrSlice = 'meet', alignX = 'Mid', alignY = 'Mid', + aspectRatioAttrs = attribute.split(' '), align; + + if (aspectRatioAttrs && aspectRatioAttrs.length) { + meetOrSlice = aspectRatioAttrs.pop(); + if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') { + align = meetOrSlice; + meetOrSlice = 'meet'; + } + else if (aspectRatioAttrs.length) { + align = aspectRatioAttrs.pop(); + } + } + //divide align in alignX and alignY + alignX = align !== 'none' ? align.slice(1, 4) : 'none'; + alignY = align !== 'none' ? align.slice(5, 8) : 'none'; + return { + meetOrSlice: meetOrSlice, + alignX: alignX, + alignY: alignY + }; + }, + + /** + * Clear char widths cache for the given font family or all the cache if no + * fontFamily is specified. + * Use it if you know you are loading fonts in a lazy way and you are not waiting + * for custom fonts to load properly when adding text objects to the canvas. + * If a text object is added when its own font is not loaded yet, you will get wrong + * measurement and so wrong bounding boxes. + * After the font cache is cleared, either change the textObject text content or call + * initDimensions() to trigger a recalculation + * @memberOf fabric.util + * @param {String} [fontFamily] font family to clear + */ + clearFabricFontCache: function(fontFamily) { + fontFamily = (fontFamily || '').toLowerCase(); + if (!fontFamily) { + fabric.charWidthsCache = { }; + } + else if (fabric.charWidthsCache[fontFamily]) { + delete fabric.charWidthsCache[fontFamily]; + } + }, + + /** + * Given current aspect ratio, determines the max width and height that can + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Number} ar aspect ratio + * @param {Number} maximumArea Maximum area you want to achieve + * @return {Object.x} Limited dimensions by X + * @return {Object.y} Limited dimensions by Y + */ + limitDimsByArea: function(ar, maximumArea) { + var roughWidth = Math.sqrt(maximumArea * ar), + perfLimitSizeY = Math.floor(maximumArea / roughWidth); + return { x: Math.floor(roughWidth), y: perfLimitSizeY }; + }, + + capValue: function(min, value, max) { + return Math.max(min, Math.min(value, max)); + }, + + /** + * Finds the scale for the object source to fit inside the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to fit into destination + */ + findScaleToFit: function(source, destination) { + return Math.min(destination.width / source.width, destination.height / source.height); + }, + + /** + * Finds the scale for the object source to cover entirely the object destination, + * keeping aspect ratio intact. + * respect the total allowed area for the cache. + * @memberOf fabric.util + * @param {Object | fabric.Object} source + * @param {Number} source.height natural unscaled height of the object + * @param {Number} source.width natural unscaled width of the object + * @param {Object | fabric.Object} destination + * @param {Number} destination.height natural unscaled height of the object + * @param {Number} destination.width natural unscaled width of the object + * @return {Number} scale factor to apply to source to cover destination + */ + findScaleToCover: function(source, destination) { + return Math.max(destination.width / source.width, destination.height / source.height); + }, + + /** + * given an array of 6 number returns something like `"matrix(...numbers)"` + * @memberOf fabric.util + * @param {Array} transform an array with 6 numbers + * @return {String} transform matrix for svg + * @return {Object.y} Limited dimensions by Y + */ + matrixToSVG: function(transform) { + return 'matrix(' + transform.map(function(value) { + return fabric.util.toFixed(value, fabric.Object.NUM_FRACTION_DIGITS); + }).join(' ') + ')'; + }, + + /** + * given an object and a transform, apply the inverse transform to the object, + * this is equivalent to remove from that object that transformation, so that + * added in a space with the removed transform, the object will be the same as before. + * Removing from an object a transform that scale by 2 is like scaling it by 1/2. + * Removing from an object a transfrom that rotate by 30deg is like rotating by 30deg + * in the opposite direction. + * This util is used to add objects inside transformed groups or nested groups. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + removeTransformFromObject: function(object, transform) { + var inverted = fabric.util.invertTransform(transform), + finalTransform = fabric.util.multiplyTransformMatrices(inverted, object.calcOwnMatrix()); + fabric.util.applyTransformToObject(object, finalTransform); + }, + + /** + * given an object and a transform, apply the transform to the object. + * this is equivalent to change the space where the object is drawn. + * Adding to an object a transform that scale by 2 is like scaling it by 2. + * This is used when removing an object from an active selection for example. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + addTransformToObject: function(object, transform) { + fabric.util.applyTransformToObject( + object, + fabric.util.multiplyTransformMatrices(transform, object.calcOwnMatrix()) + ); + }, + + /** + * discard an object transform state and apply the one from the matrix. + * @memberOf fabric.util + * @param {fabric.Object} object the object you want to transform + * @param {Array} transform the destination transform + */ + applyTransformToObject: function(object, transform) { + var options = fabric.util.qrDecompose(transform), + center = new fabric.Point(options.translateX, options.translateY); + object.flipX = false; + object.flipY = false; + object.set('scaleX', options.scaleX); + object.set('scaleY', options.scaleY); + object.skewX = options.skewX; + object.skewY = options.skewY; + object.angle = options.angle; + object.setPositionByOrigin(center, 'center', 'center'); + }, + + /** + * given a width and height, return the size of the bounding box + * that can contains the box with width/height with applied transform + * described in options. + * Use to calculate the boxes around objects for controls. + * @memberOf fabric.util + * @param {Number} width + * @param {Number} height + * @param {Object} options + * @param {Number} options.scaleX + * @param {Number} options.scaleY + * @param {Number} options.skewX + * @param {Number} options.skewY + * @return {Object.x} width of containing + * @return {Object.y} height of containing + */ + sizeAfterTransform: function(width, height, options) { + var dimX = width / 2, dimY = height / 2, + points = [ + { + x: -dimX, + y: -dimY + }, + { + x: dimX, + y: -dimY + }, + { + x: -dimX, + y: dimY + }, + { + x: dimX, + y: dimY + }], + transformMatrix = fabric.util.calcDimensionsMatrix(options), + bbox = fabric.util.makeBoundingBoxFromPoints(points, transformMatrix); + return { + x: bbox.width, + y: bbox.height, + }; + } + }; +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + var _join = Array.prototype.join, + commandLengths = { + m: 2, + l: 2, + h: 1, + v: 1, + c: 6, + s: 4, + q: 4, + t: 2, + a: 7 + }, + repeatedCommands = { + m: 'l', + M: 'L' + }; + function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) { + var costh2 = fabric.util.cos(th2), + sinth2 = fabric.util.sin(th2), + costh3 = fabric.util.cos(th3), + sinth3 = fabric.util.sin(th3), + toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1, + toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1, + cp1X = fromX + mT * ( -cosTh * rx * sinth2 - sinTh * ry * costh2), + cp1Y = fromY + mT * ( -sinTh * rx * sinth2 + cosTh * ry * costh2), + cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3), + cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3); + + return ['C', + cp1X, cp1Y, + cp2X, cp2Y, + toX, toY + ]; + } + + /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp + * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here + * http://mozilla.org/MPL/2.0/ + */ + function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) { + var PI = Math.PI, th = rotateX * PI / 180, + sinTh = fabric.util.sin(th), + cosTh = fabric.util.cos(th), + fromX = 0, fromY = 0; + + rx = Math.abs(rx); + ry = Math.abs(ry); + + var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5, + py = -cosTh * toY * 0.5 + sinTh * toX * 0.5, + rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px, + pl = rx2 * ry2 - rx2 * py2 - ry2 * px2, + root = 0; + + if (pl < 0) { + var s = Math.sqrt(1 - pl / (rx2 * ry2)); + rx *= s; + ry *= s; + } + else { + root = (large === sweep ? -1.0 : 1.0) * + Math.sqrt( pl / (rx2 * py2 + ry2 * px2)); + } + + var cx = root * rx * py / ry, + cy = -root * ry * px / rx, + cx1 = cosTh * cx - sinTh * cy + toX * 0.5, + cy1 = sinTh * cx + cosTh * cy + toY * 0.5, + mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry), + dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry); + + if (sweep === 0 && dtheta > 0) { + dtheta -= 2 * PI; + } + else if (sweep === 1 && dtheta < 0) { + dtheta += 2 * PI; + } + + // Convert into cubic bezier segments <= 90deg + var segments = Math.ceil(Math.abs(dtheta / PI * 2)), + result = [], mDelta = dtheta / segments, + mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2), + th3 = mTheta + mDelta; + + for (var i = 0; i < segments; i++) { + result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY); + fromX = result[i][5]; + fromY = result[i][6]; + mTheta = th3; + th3 += mDelta; + } + return result; + } + + /* + * Private + */ + function calcVectorAngle(ux, uy, vx, vy) { + var ta = Math.atan2(uy, ux), + tb = Math.atan2(vy, vx); + if (tb >= ta) { + return tb - ta; + } + else { + return 2 * Math.PI - (ta - tb); + } + } + + /** + * Calculate bounding box of a beziercurve + * @param {Number} x0 starting point + * @param {Number} y0 + * @param {Number} x1 first control point + * @param {Number} y1 + * @param {Number} x2 secondo control point + * @param {Number} y2 + * @param {Number} x3 end of bezier + * @param {Number} y3 + */ + // taken from http://jsbin.com/ivomiq/56/edit no credits available for that. + // TODO: can we normalize this with the starting points set at 0 and then translated the bbox? + function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) { + var argsString; + if (fabric.cachesBoundsOfCurve) { + argsString = _join.call(arguments); + if (fabric.boundsOfCurveCache[argsString]) { + return fabric.boundsOfCurveCache[argsString]; + } + } + + var sqrt = Math.sqrt, + min = Math.min, max = Math.max, + abs = Math.abs, tvalues = [], + bounds = [[], []], + a, b, c, t, t1, t2, b2ac, sqrtb2ac; + + b = 6 * x0 - 12 * x1 + 6 * x2; + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; + c = 3 * x1 - 3 * x0; + + for (var i = 0; i < 2; ++i) { + if (i > 0) { + b = 6 * y0 - 12 * y1 + 6 * y2; + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; + c = 3 * y1 - 3 * y0; + } + + if (abs(a) < 1e-12) { + if (abs(b) < 1e-12) { + continue; + } + t = -c / b; + if (0 < t && t < 1) { + tvalues.push(t); + } + continue; + } + b2ac = b * b - 4 * c * a; + if (b2ac < 0) { + continue; + } + sqrtb2ac = sqrt(b2ac); + t1 = (-b + sqrtb2ac) / (2 * a); + if (0 < t1 && t1 < 1) { + tvalues.push(t1); + } + t2 = (-b - sqrtb2ac) / (2 * a); + if (0 < t2 && t2 < 1) { + tvalues.push(t2); + } + } + + var x, y, j = tvalues.length, jlen = j, mt; + while (j--) { + t = tvalues[j]; + mt = 1 - t; + x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); + bounds[0][j] = x; + + y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); + bounds[1][j] = y; + } + + bounds[0][jlen] = x0; + bounds[1][jlen] = y0; + bounds[0][jlen + 1] = x3; + bounds[1][jlen + 1] = y3; + var result = [ + { + x: min.apply(null, bounds[0]), + y: min.apply(null, bounds[1]) + }, + { + x: max.apply(null, bounds[0]), + y: max.apply(null, bounds[1]) + } + ]; + if (fabric.cachesBoundsOfCurve) { + fabric.boundsOfCurveCache[argsString] = result; + } + return result; + } + + /** + * Converts arc to a bunch of bezier curves + * @param {Number} fx starting point x + * @param {Number} fy starting point y + * @param {Array} coords Arc command + */ + function fromArcToBeziers(fx, fy, coords) { + var rx = coords[1], + ry = coords[2], + rot = coords[3], + large = coords[4], + sweep = coords[5], + tx = coords[6], + ty = coords[7], + segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + + for (var i = 0, len = segsNorm.length; i < len; i++) { + segsNorm[i][1] += fx; + segsNorm[i][2] += fy; + segsNorm[i][3] += fx; + segsNorm[i][4] += fy; + segsNorm[i][5] += fx; + segsNorm[i][6] += fy; + } + return segsNorm; + }; + + /** + * This function take a parsed SVG path and make it simpler for fabricJS logic. + * simplification consist of: only UPPERCASE absolute commands ( relative converted to absolute ) + * S converted in C, T converted in Q, A converted in C. + * @param {Array} path the array of commands of a parsed svg path for fabric.Path + * @return {Array} the simplified array of commands of a parsed svg path for fabric.Path + */ + function makePathSimpler(path) { + // x and y represent the last point of the path. the previous command point. + // we add them to each relative command to make it an absolute comment. + // we also swap the v V h H with L, because are easier to transform. + var x = 0, y = 0, len = path.length, + // x1 and y1 represent the last point of the subpath. the subpath is started with + // m or M command. When a z or Z command is drawn, x and y need to be resetted to + // the last x1 and y1. + x1 = 0, y1 = 0, current, i, converted, + // previous will host the letter of the previous command, to handle S and T. + // controlX and controlY will host the previous reflected control point + destinationPath = [], previous, controlX, controlY; + for (i = 0; i < len; ++i) { + converted = false; + current = path[i].slice(0); + switch (current[0]) { // first letter + case 'l': // lineto, relative + current[0] = 'L'; + current[1] += x; + current[2] += y; + // falls through + case 'L': + x = current[1]; + y = current[2]; + break; + case 'h': // horizontal lineto, relative + current[1] += x; + // falls through + case 'H': + current[0] = 'L'; + current[2] = y; + x = current[1]; + break; + case 'v': // vertical lineto, relative + current[1] += y; + // falls through + case 'V': + current[0] = 'L'; + y = current[1]; + current[1] = x; + current[2] = y; + break; + case 'm': // moveTo, relative + current[0] = 'M'; + current[1] += x; + current[2] += y; + // falls through + case 'M': + x = current[1]; + y = current[2]; + x1 = current[1]; + y1 = current[2]; + break; + case 'c': // bezierCurveTo, relative + current[0] = 'C'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + current[5] += x; + current[6] += y; + // falls through + case 'C': + controlX = current[3]; + controlY = current[4]; + x = current[5]; + y = current[6]; + break; + case 's': // shorthand cubic bezierCurveTo, relative + current[0] = 'S'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + // falls through + case 'S': + // would be sScC but since we are swapping sSc for C, we check just that. + if (previous === 'C') { + // calculate reflection of previous control points + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + else { + // If there is no previous command or if the previous command was not a C, c, S, or s, + // the control point is coincident with the current point + controlX = x; + controlY = y; + } + x = current[3]; + y = current[4]; + current[0] = 'C'; + current[5] = current[3]; + current[6] = current[4]; + current[3] = current[1]; + current[4] = current[2]; + current[1] = controlX; + current[2] = controlY; + // current[3] and current[4] are NOW the second control point. + // we keep it for the next reflection. + controlX = current[3]; + controlY = current[4]; + break; + case 'q': // quadraticCurveTo, relative + current[0] = 'Q'; + current[1] += x; + current[2] += y; + current[3] += x; + current[4] += y; + // falls through + case 'Q': + controlX = current[1]; + controlY = current[2]; + x = current[3]; + y = current[4]; + break; + case 't': // shorthand quadraticCurveTo, relative + current[0] = 'T'; + current[1] += x; + current[2] += y; + // falls through + case 'T': + if (previous === 'Q') { + // calculate reflection of previous control point + controlX = 2 * x - controlX; + controlY = 2 * y - controlY; + } + else { + // If there is no previous command or if the previous command was not a Q, q, T or t, + // assume the control point is coincident with the current point + controlX = x; + controlY = y; + } + current[0] = 'Q'; + x = current[1]; + y = current[2]; + current[1] = controlX; + current[2] = controlY; + current[3] = x; + current[4] = y; + break; + case 'a': + current[0] = 'A'; + current[6] += x; + current[7] += y; + // falls through + case 'A': + converted = true; + destinationPath = destinationPath.concat(fromArcToBeziers(x, y, current)); + x = current[6]; + y = current[7]; + break; + case 'z': + case 'Z': + x = x1; + y = y1; + break; + default: + } + if (!converted) { + destinationPath.push(current); + } + previous = current[0]; + } + return destinationPath; + }; + + /** + * Calc length from point x1,y1 to x2,y2 + * @param {Number} x1 starting point x + * @param {Number} y1 starting point y + * @param {Number} x2 starting point x + * @param {Number} y2 starting point y + * @return {Number} length of segment + */ + function calcLineLength(x1, y1, x2, y2) { + return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + + // functions for the Cubic beizer + // taken from: https://github.com/konvajs/konva/blob/7.0.5/src/shapes/Path.ts#L350 + function CB1(t) { + return t * t * t; + } + function CB2(t) { + return 3 * t * t * (1 - t); + } + function CB3(t) { + return 3 * t * (1 - t) * (1 - t); + } + function CB4(t) { + return (1 - t) * (1 - t) * (1 - t); + } + + function getPointOnCubicBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) { + return function(pct) { + var c1 = CB1(pct), c2 = CB2(pct), c3 = CB3(pct), c4 = CB4(pct); + return { + x: p4x * c1 + p3x * c2 + p2x * c3 + p1x * c4, + y: p4y * c1 + p3y * c2 + p2y * c3 + p1y * c4 + }; + }; + } + + function getTangentCubicIterator(p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y) { + return function (pct) { + var invT = 1 - pct, + tangentX = (3 * invT * invT * (p2x - p1x)) + (6 * invT * pct * (p3x - p2x)) + + (3 * pct * pct * (p4x - p3x)), + tangentY = (3 * invT * invT * (p2y - p1y)) + (6 * invT * pct * (p3y - p2y)) + + (3 * pct * pct * (p4y - p3y)); + return Math.atan2(tangentY, tangentX); + }; + } + + function QB1(t) { + return t * t; + } + + function QB2(t) { + return 2 * t * (1 - t); + } + + function QB3(t) { + return (1 - t) * (1 - t); + } + + function getPointOnQuadraticBezierIterator(p1x, p1y, p2x, p2y, p3x, p3y) { + return function(pct) { + var c1 = QB1(pct), c2 = QB2(pct), c3 = QB3(pct); + return { + x: p3x * c1 + p2x * c2 + p1x * c3, + y: p3y * c1 + p2y * c2 + p1y * c3 + }; + }; + } + + function getTangentQuadraticIterator(p1x, p1y, p2x, p2y, p3x, p3y) { + return function (pct) { + var invT = 1 - pct, + tangentX = (2 * invT * (p2x - p1x)) + (2 * pct * (p3x - p2x)), + tangentY = (2 * invT * (p2y - p1y)) + (2 * pct * (p3y - p2y)); + return Math.atan2(tangentY, tangentX); + }; + } + + + // this will run over a path segment ( a cubic or quadratic segment) and approximate it + // with 100 segemnts. This will good enough to calculate the length of the curve + function pathIterator(iterator, x1, y1) { + var tempP = { x: x1, y: y1 }, p, tmpLen = 0, perc; + for (perc = 1; perc <= 100; perc += 1) { + p = iterator(perc / 100); + tmpLen += calcLineLength(tempP.x, tempP.y, p.x, p.y); + tempP = p; + } + return tmpLen; + } + + /** + * Given a pathInfo, and a distance in pixels, find the percentage from 0 to 1 + * that correspond to that pixels run over the path. + * The percentage will be then used to find the correct point on the canvas for the path. + * @param {Array} segInfo fabricJS collection of information on a parsed path + * @param {Number} distance from starting point, in pixels. + * @return {Object} info object with x and y ( the point on canvas ) and angle, the tangent on that point; + */ + function findPercentageForDistance(segInfo, distance) { + var perc = 0, tmpLen = 0, iterator = segInfo.iterator, tempP = { x: segInfo.x, y: segInfo.y }, + p, nextLen, nextStep = 0.01, angleFinder = segInfo.angleFinder, lastPerc; + // nextStep > 0.0001 covers 0.00015625 that 1/64th of 1/100 + // the path + while (tmpLen < distance && perc <= 1 && nextStep > 0.0001) { + p = iterator(perc); + lastPerc = perc; + nextLen = calcLineLength(tempP.x, tempP.y, p.x, p.y); + // compare tmpLen each cycle with distance, decide next perc to test. + if ((nextLen + tmpLen) > distance) { + // we discard this step and we make smaller steps. + nextStep /= 2; + perc -= nextStep; + } + else { + tempP = p; + perc += nextStep; + tmpLen += nextLen; + } + } + p.angle = angleFinder(lastPerc); + return p; + } + + /** + * Run over a parsed and simplifed path and extrac some informations. + * informations are length of each command and starting point + * @param {Array} path fabricJS parsed path commands + * @return {Array} path commands informations + */ + function getPathSegmentsInfo(path) { + var totalLength = 0, len = path.length, current, + //x2 and y2 are the coords of segment start + //x1 and y1 are the coords of the current point + x1 = 0, y1 = 0, x2 = 0, y2 = 0, info = [], iterator, tempInfo, angleFinder; + for (var i = 0; i < len; i++) { + current = path[i]; + tempInfo = { + x: x1, + y: y1, + command: current[0], + }; + switch (current[0]) { //first letter + case 'M': + tempInfo.length = 0; + x2 = x1 = current[1]; + y2 = y1 = current[2]; + break; + case 'L': + tempInfo.length = calcLineLength(x1, y1, current[1], current[2]); + x1 = current[1]; + y1 = current[2]; + break; + case 'C': + iterator = getPointOnCubicBezierIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + angleFinder = getTangentCubicIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + tempInfo.iterator = iterator; + tempInfo.angleFinder = angleFinder; + tempInfo.length = pathIterator(iterator, x1, y1); + x1 = current[5]; + y1 = current[6]; + break; + case 'Q': + iterator = getPointOnQuadraticBezierIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4] + ); + angleFinder = getTangentQuadraticIterator( + x1, + y1, + current[1], + current[2], + current[3], + current[4] + ); + tempInfo.iterator = iterator; + tempInfo.angleFinder = angleFinder; + tempInfo.length = pathIterator(iterator, x1, y1); + x1 = current[3]; + y1 = current[4]; + break; + case 'Z': + case 'z': + // we add those in order to ease calculations later + tempInfo.destX = x2; + tempInfo.destY = y2; + tempInfo.length = calcLineLength(x1, y1, x2, y2); + x1 = x2; + y1 = y2; + break; + } + totalLength += tempInfo.length; + info.push(tempInfo); + } + info.push({ length: totalLength, x: x1, y: y1 }); + return info; + } + + function getPointOnPath(path, distance, infos) { + if (!infos) { + infos = getPathSegmentsInfo(path); + } + var i = 0; + while ((distance - infos[i].length > 0) && i < (infos.length - 2)) { + distance -= infos[i].length; + i++; + } + // var distance = infos[infos.length - 1] * perc; + var segInfo = infos[i], segPercent = distance / segInfo.length, + command = segInfo.command, segment = path[i], info; + + switch (command) { + case 'M': + return { x: segInfo.x, y: segInfo.y, angle: 0 }; + case 'Z': + case 'z': + info = new fabric.Point(segInfo.x, segInfo.y).lerp( + new fabric.Point(segInfo.destX, segInfo.destY), + segPercent + ); + info.angle = Math.atan2(segInfo.destY - segInfo.y, segInfo.destX - segInfo.x); + return info; + case 'L': + info = new fabric.Point(segInfo.x, segInfo.y).lerp( + new fabric.Point(segment[1], segment[2]), + segPercent + ); + info.angle = Math.atan2(segment[2] - segInfo.y, segment[1] - segInfo.x); + return info; + case 'C': + return findPercentageForDistance(segInfo, distance); + case 'Q': + return findPercentageForDistance(segInfo, distance); + } + } + + /** + * + * @param {string} pathString + * @return {(string|number)[][]} An array of SVG path commands + * @example Usage + * parsePath('M 3 4 Q 3 5 2 1 4 0 Q 9 12 2 1 4 0') === [ + * ['M', 3, 4], + * ['Q', 3, 5, 2, 1, 4, 0], + * ['Q', 9, 12, 2, 1, 4, 0], + * ]; + * + */ + function parsePath(pathString) { + var result = [], + coords = [], + currentPath, + parsed, + re = fabric.rePathCommand, + rNumber = '[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?\\s*', + rNumberCommaWsp = '(' + rNumber + ')' + fabric.commaWsp, + rFlagCommaWsp = '([01])' + fabric.commaWsp + '?', + rArcSeq = rNumberCommaWsp + '?' + rNumberCommaWsp + '?' + rNumberCommaWsp + rFlagCommaWsp + rFlagCommaWsp + + rNumberCommaWsp + '?(' + rNumber + ')', + regArcArgumentSequence = new RegExp(rArcSeq, 'g'), + match, + coordsStr, + // one of commands (m,M,l,L,q,Q,c,C,etc.) followed by non-command characters (i.e. command values) + path; + if (!pathString || !pathString.match) { + return result; + } + path = pathString.match(/[mzlhvcsqta][^mzlhvcsqta]*/gi); + + for (var i = 0, coordsParsed, len = path.length; i < len; i++) { + currentPath = path[i]; + + coordsStr = currentPath.slice(1).trim(); + coords.length = 0; + + var command = currentPath.charAt(0); + coordsParsed = [command]; + + if (command.toLowerCase() === 'a') { + // arcs have special flags that apparently don't require spaces so handle special + for (var args; (args = regArcArgumentSequence.exec(coordsStr));) { + for (var j = 1; j < args.length; j++) { + coords.push(args[j]); + } + } + } + else { + while ((match = re.exec(coordsStr))) { + coords.push(match[0]); + } + } + + for (var j = 0, jlen = coords.length; j < jlen; j++) { + parsed = parseFloat(coords[j]); + if (!isNaN(parsed)) { + coordsParsed.push(parsed); + } + } + + var commandLength = commandLengths[command.toLowerCase()], + repeatedCommand = repeatedCommands[command] || command; + + if (coordsParsed.length - 1 > commandLength) { + for (var k = 1, klen = coordsParsed.length; k < klen; k += commandLength) { + result.push([command].concat(coordsParsed.slice(k, k + commandLength))); + command = repeatedCommand; + } + } + else { + result.push(coordsParsed); + } + } + + return result; + }; + + /** + * + * Converts points to a smooth SVG path + * @param {{ x: number,y: number }[]} points Array of points + * @param {number} [correction] Apply a correction to the path (usually we use `width / 1000`). If value is undefined 0 is used as the correction value. + * @return {(string|number)[][]} An array of SVG path commands + */ + function getSmoothPathFromPoints(points, correction) { + var path = [], i, + p1 = new fabric.Point(points[0].x, points[0].y), + p2 = new fabric.Point(points[1].x, points[1].y), + len = points.length, multSignX = 1, multSignY = 0, manyPoints = len > 2; + correction = correction || 0; + + if (manyPoints) { + multSignX = points[2].x < p2.x ? -1 : points[2].x === p2.x ? 0 : 1; + multSignY = points[2].y < p2.y ? -1 : points[2].y === p2.y ? 0 : 1; + } + path.push(['M', p1.x - multSignX * correction, p1.y - multSignY * correction]); + for (i = 1; i < len; i++) { + if (!p1.eq(p2)) { + var midPoint = p1.midPointFrom(p2); + // p1 is our bezier control point + // midpoint is our endpoint + // start point is p(i-1) value. + path.push(['Q', p1.x, p1.y, midPoint.x, midPoint.y]); + } + p1 = points[i]; + if ((i + 1) < points.length) { + p2 = points[i + 1]; + } + } + if (manyPoints) { + multSignX = p1.x > points[i - 2].x ? 1 : p1.x === points[i - 2].x ? 0 : -1; + multSignY = p1.y > points[i - 2].y ? 1 : p1.y === points[i - 2].y ? 0 : -1; + } + path.push(['L', p1.x + multSignX * correction, p1.y + multSignY * correction]); + return path; + } + /** + * Transform a path by transforming each segment. + * it has to be a simplified path or it won't work. + * WARNING: this depends from pathOffset for correct operation + * @param {Array} path fabricJS parsed and simplified path commands + * @param {Array} transform matrix that represent the transformation + * @param {Object} [pathOffset] the fabric.Path pathOffset + * @param {Number} pathOffset.x + * @param {Number} pathOffset.y + * @returns {Array} the transformed path + */ + function transformPath(path, transform, pathOffset) { + if (pathOffset) { + transform = fabric.util.multiplyTransformMatrices( + transform, + [1, 0, 0, 1, -pathOffset.x, -pathOffset.y] + ); + } + return path.map(function(pathSegment) { + var newSegment = pathSegment.slice(0), point = {}; + for (var i = 1; i < pathSegment.length - 1; i += 2) { + point.x = pathSegment[i]; + point.y = pathSegment[i + 1]; + point = fabric.util.transformPoint(point, transform); + newSegment[i] = point.x; + newSegment[i + 1] = point.y; + } + return newSegment; + }); + } + + /** + * Calculate bounding box of a elliptic-arc + * @deprecated + * @param {Number} fx start point of arc + * @param {Number} fy + * @param {Number} rx horizontal radius + * @param {Number} ry vertical radius + * @param {Number} rot angle of horizontal axis + * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points + * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction + * @param {Number} tx end point of arc + * @param {Number} ty + */ + function getBoundsOfArc(fx, fy, rx, ry, rot, large, sweep, tx, ty) { + + var fromX = 0, fromY = 0, bound, bounds = [], + segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot); + + for (var i = 0, len = segs.length; i < len; i++) { + bound = getBoundsOfCurve(fromX, fromY, segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5], segs[i][6]); + bounds.push({ x: bound[0].x + fx, y: bound[0].y + fy }); + bounds.push({ x: bound[1].x + fx, y: bound[1].y + fy }); + fromX = segs[i][5]; + fromY = segs[i][6]; + } + return bounds; + }; + + /** + * Draws arc + * @deprecated + * @param {CanvasRenderingContext2D} ctx + * @param {Number} fx + * @param {Number} fy + * @param {Array} coords coords of the arc, without the front 'A/a' + */ + function drawArc(ctx, fx, fy, coords) { + coords = coords.slice(0).unshift('X'); // command A or a does not matter + var beziers = fromArcToBeziers(fx, fy, coords); + beziers.forEach(function(bezier) { + ctx.bezierCurveTo.apply(ctx, bezier.slice(1)); + }); + }; + + /** + * Join path commands to go back to svg format + * @param {Array} pathData fabricJS parsed path commands + * @return {String} joined path 'M 0 0 L 20 30' + */ + fabric.util.joinPath = function(pathData) { + return pathData.map(function (segment) { return segment.join(' '); }).join(' '); + }; + fabric.util.parsePath = parsePath; + fabric.util.makePathSimpler = makePathSimpler; + fabric.util.getSmoothPathFromPoints = getSmoothPathFromPoints; + fabric.util.getPathSegmentsInfo = getPathSegmentsInfo; + fabric.util.getBoundsOfCurve = getBoundsOfCurve; + fabric.util.getPointOnPath = getPointOnPath; + fabric.util.transformPath = transformPath; + /** + * Typo of `fromArcToBeziers` kept for not breaking the api once corrected. + * Will be removed in fabric 5.0 + * @deprecated + */ + fabric.util.fromArcToBeizers = fromArcToBeziers; + // kept because we do not want to make breaking changes. + // but useless and deprecated. + fabric.util.getBoundsOfArc = getBoundsOfArc; + fabric.util.drawArc = drawArc; +})(); + + +(function() { + + var slice = Array.prototype.slice; + + /** + * Invokes method on all items in a given array + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} method Name of a method to invoke + * @return {Array} + */ + function invoke(array, method) { + var args = slice.call(arguments, 2), result = []; + for (var i = 0, len = array.length; i < len; i++) { + result[i] = args.length ? array[i][method].apply(array[i], args) : array[i][method].call(array[i]); + } + return result; + } + + /** + * Finds maximum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {*} + */ + function max(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 >= value2; + }); + } + + /** + * Finds minimum value in array (not necessarily "first" one) + * @memberOf fabric.util.array + * @param {Array} array Array to iterate over + * @param {String} byProperty + * @return {*} + */ + function min(array, byProperty) { + return find(array, byProperty, function(value1, value2) { + return value1 < value2; + }); + } + + /** + * @private + */ + function fill(array, value) { + var k = array.length; + while (k--) { + array[k] = value; + } + return array; + } + + /** + * @private + */ + function find(array, byProperty, condition) { + if (!array || array.length === 0) { + return; + } + + var i = array.length - 1, + result = byProperty ? array[i][byProperty] : array[i]; + if (byProperty) { + while (i--) { + if (condition(array[i][byProperty], result)) { + result = array[i][byProperty]; + } + } + } + else { + while (i--) { + if (condition(array[i], result)) { + result = array[i]; + } + } + } + return result; + } + + /** + * @namespace fabric.util.array + */ + fabric.util.array = { + fill: fill, + invoke: invoke, + min: min, + max: max + }; + +})(); + + +(function() { + /** + * Copies all enumerable properties of one js object to another + * this does not and cannot compete with generic utils. + * Does not clone or extend fabric.Object subclasses. + * This is mostly for internal use and has extra handling for fabricJS objects + * it skips the canvas and group properties in deep cloning. + * @memberOf fabric.util.object + * @param {Object} destination Where to copy to + * @param {Object} source Where to copy from + * @param {Boolean} [deep] Whether to extend nested objects + * @return {Object} + */ + + function extend(destination, source, deep) { + // JScript DontEnum bug is not taken care of + // the deep clone is for internal use, is not meant to avoid + // javascript traps or cloning html element or self referenced objects. + if (deep) { + if (!fabric.isLikelyNode && source instanceof Element) { + // avoid cloning deep images, canvases, + destination = source; + } + else if (source instanceof Array) { + destination = []; + for (var i = 0, len = source.length; i < len; i++) { + destination[i] = extend({ }, source[i], deep); + } + } + else if (source && typeof source === 'object') { + for (var property in source) { + if (property === 'canvas' || property === 'group') { + // we do not want to clone this props at all. + // we want to keep the keys in the copy + destination[property] = null; + } + else if (source.hasOwnProperty(property)) { + destination[property] = extend({ }, source[property], deep); + } + } + } + else { + // this sounds odd for an extend but is ok for recursive use + destination = source; + } + } + else { + for (var property in source) { + destination[property] = source[property]; + } + } + return destination; + } + + /** + * Creates an empty object and copies all enumerable properties of another object to it + * This method is mostly for internal use, and not intended for duplicating shapes in canvas. + * @memberOf fabric.util.object + * @param {Object} object Object to clone + * @param {Boolean} [deep] Whether to clone nested objects + * @return {Object} + */ + + //TODO: this function return an empty object if you try to clone null + function clone(object, deep) { + return extend({ }, object, deep); + } + + /** @namespace fabric.util.object */ + fabric.util.object = { + extend: extend, + clone: clone + }; + fabric.util.object.extend(fabric.util, fabric.Observable); +})(); + + +(function() { + + /** + * Camelizes a string + * @memberOf fabric.util.string + * @param {String} string String to camelize + * @return {String} Camelized version of a string + */ + function camelize(string) { + return string.replace(/-+(.)?/g, function(match, character) { + return character ? character.toUpperCase() : ''; + }); + } + + /** + * Capitalizes a string + * @memberOf fabric.util.string + * @param {String} string String to capitalize + * @param {Boolean} [firstLetterOnly] If true only first letter is capitalized + * and other letters stay untouched, if false first letter is capitalized + * and other letters are converted to lowercase. + * @return {String} Capitalized version of a string + */ + function capitalize(string, firstLetterOnly) { + return string.charAt(0).toUpperCase() + + (firstLetterOnly ? string.slice(1) : string.slice(1).toLowerCase()); + } + + /** + * Escapes XML in a string + * @memberOf fabric.util.string + * @param {String} string String to escape + * @return {String} Escaped version of a string + */ + function escapeXml(string) { + return string.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + + /** + * Divide a string in the user perceived single units + * @memberOf fabric.util.string + * @param {String} textstring String to escape + * @return {Array} array containing the graphemes + */ + function graphemeSplit(textstring) { + var i = 0, chr, graphemes = []; + for (i = 0, chr; i < textstring.length; i++) { + if ((chr = getWholeChar(textstring, i)) === false) { + continue; + } + graphemes.push(chr); + } + return graphemes; + } + + // taken from mdn in the charAt doc page. + function getWholeChar(str, i) { + var code = str.charCodeAt(i); + + if (isNaN(code)) { + return ''; // Position not found + } + if (code < 0xD800 || code > 0xDFFF) { + return str.charAt(i); + } + + // High surrogate (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 <= code && code <= 0xDBFF) { + if (str.length <= (i + 1)) { + throw 'High surrogate without following low surrogate'; + } + var next = str.charCodeAt(i + 1); + if (0xDC00 > next || next > 0xDFFF) { + throw 'High surrogate without following low surrogate'; + } + return str.charAt(i) + str.charAt(i + 1); + } + // Low surrogate (0xDC00 <= code && code <= 0xDFFF) + if (i === 0) { + throw 'Low surrogate without preceding high surrogate'; + } + var prev = str.charCodeAt(i - 1); + + // (could change last hex to 0xDB7F to treat high private + // surrogates as single characters) + if (0xD800 > prev || prev > 0xDBFF) { + throw 'Low surrogate without preceding high surrogate'; + } + // We can pass over low surrogates now as the second component + // in a pair which we have already processed + return false; + } + + + /** + * String utilities + * @namespace fabric.util.string + */ + fabric.util.string = { + camelize: camelize, + capitalize: capitalize, + escapeXml: escapeXml, + graphemeSplit: graphemeSplit + }; +})(); + + +(function() { + + var slice = Array.prototype.slice, emptyFunction = function() { }, + + IS_DONTENUM_BUGGY = (function() { + for (var p in { toString: 1 }) { + if (p === 'toString') { + return false; + } + } + return true; + })(), + + /** @ignore */ + addMethods = function(klass, source, parent) { + for (var property in source) { + + if (property in klass.prototype && + typeof klass.prototype[property] === 'function' && + (source[property] + '').indexOf('callSuper') > -1) { + + klass.prototype[property] = (function(property) { + return function() { + + var superclass = this.constructor.superclass; + this.constructor.superclass = parent; + var returnValue = source[property].apply(this, arguments); + this.constructor.superclass = superclass; + + if (property !== 'initialize') { + return returnValue; + } + }; + })(property); + } + else { + klass.prototype[property] = source[property]; + } + + if (IS_DONTENUM_BUGGY) { + if (source.toString !== Object.prototype.toString) { + klass.prototype.toString = source.toString; + } + if (source.valueOf !== Object.prototype.valueOf) { + klass.prototype.valueOf = source.valueOf; + } + } + } + }; + + function Subclass() { } + + function callSuper(methodName) { + var parentMethod = null, + _this = this; + + // climb prototype chain to find method not equal to callee's method + while (_this.constructor.superclass) { + var superClassMethod = _this.constructor.superclass.prototype[methodName]; + if (_this[methodName] !== superClassMethod) { + parentMethod = superClassMethod; + break; + } + // eslint-disable-next-line + _this = _this.constructor.superclass.prototype; + } + + if (!parentMethod) { + return console.log('tried to callSuper ' + methodName + ', method not found in prototype chain', this); + } + + return (arguments.length > 1) + ? parentMethod.apply(this, slice.call(arguments, 1)) + : parentMethod.call(this); + } + + /** + * Helper for creation of "classes". + * @memberOf fabric.util + * @param {Function} [parent] optional "Class" to inherit from + * @param {Object} [properties] Properties shared by all instances of this class + * (be careful modifying objects defined here as this would affect all instances) + */ + function createClass() { + var parent = null, + properties = slice.call(arguments, 0); + + if (typeof properties[0] === 'function') { + parent = properties.shift(); + } + function klass() { + this.initialize.apply(this, arguments); + } + + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + Subclass.prototype = parent.prototype; + klass.prototype = new Subclass(); + parent.subclasses.push(klass); + } + for (var i = 0, length = properties.length; i < length; i++) { + addMethods(klass, properties[i], parent); + } + if (!klass.prototype.initialize) { + klass.prototype.initialize = emptyFunction; + } + klass.prototype.constructor = klass; + klass.prototype.callSuper = callSuper; + return klass; + } + + fabric.util.createClass = createClass; +})(); + + +(function () { + // since ie11 can use addEventListener but they do not support options, i need to check + var couldUseAttachEvent = !!fabric.document.createElement('div').attachEvent, + touchEvents = ['touchstart', 'touchmove', 'touchend']; + /** + * Adds an event listener to an element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.addListener = function(element, eventName, handler, options) { + element && element.addEventListener(eventName, handler, couldUseAttachEvent ? false : options); + }; + + /** + * Removes an event listener from an element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {String} eventName + * @param {Function} handler + */ + fabric.util.removeListener = function(element, eventName, handler, options) { + element && element.removeEventListener(eventName, handler, couldUseAttachEvent ? false : options); + }; + + function getTouchInfo(event) { + var touchProp = event.changedTouches; + if (touchProp && touchProp[0]) { + return touchProp[0]; + } + return event; + } + + fabric.util.getPointer = function(event) { + var element = event.target, + scroll = fabric.util.getScrollLeftTop(element), + _evt = getTouchInfo(event); + return { + x: _evt.clientX + scroll.left, + y: _evt.clientY + scroll.top + }; + }; + + fabric.util.isTouchEvent = function(event) { + return touchEvents.indexOf(event.type) > -1 || event.pointerType === 'touch'; + }; +})(); + + +(function () { + + /** + * Cross-browser wrapper for setting element's style + * @memberOf fabric.util + * @param {HTMLElement} element + * @param {Object} styles + * @return {HTMLElement} Element that was passed as a first argument + */ + function setStyle(element, styles) { + var elementStyle = element.style; + if (!elementStyle) { + return element; + } + if (typeof styles === 'string') { + element.style.cssText += ';' + styles; + return styles.indexOf('opacity') > -1 + ? setOpacity(element, styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) + : element; + } + for (var property in styles) { + if (property === 'opacity') { + setOpacity(element, styles[property]); + } + else { + var normalizedProperty = (property === 'float' || property === 'cssFloat') + ? (typeof elementStyle.styleFloat === 'undefined' ? 'cssFloat' : 'styleFloat') + : property; + elementStyle[normalizedProperty] = styles[property]; + } + } + return element; + } + + var parseEl = fabric.document.createElement('div'), + supportsOpacity = typeof parseEl.style.opacity === 'string', + supportsFilters = typeof parseEl.style.filter === 'string', + reOpacity = /alpha\s*\(\s*opacity\s*=\s*([^\)]+)\)/, + + /** @ignore */ + setOpacity = function (element) { return element; }; + + if (supportsOpacity) { + /** @ignore */ + setOpacity = function(element, value) { + element.style.opacity = value; + return element; + }; + } + else if (supportsFilters) { + /** @ignore */ + setOpacity = function(element, value) { + var es = element.style; + if (element.currentStyle && !element.currentStyle.hasLayout) { + es.zoom = 1; + } + if (reOpacity.test(es.filter)) { + value = value >= 0.9999 ? '' : ('alpha(opacity=' + (value * 100) + ')'); + es.filter = es.filter.replace(reOpacity, value); + } + else { + es.filter += ' alpha(opacity=' + (value * 100) + ')'; + } + return element; + }; + } + + fabric.util.setStyle = setStyle; + +})(); + + +(function() { + + var _slice = Array.prototype.slice; + + /** + * Takes id and returns an element with that id (if one exists in a document) + * @memberOf fabric.util + * @param {String|HTMLElement} id + * @return {HTMLElement|null} + */ + function getById(id) { + return typeof id === 'string' ? fabric.document.getElementById(id) : id; + } + + var sliceCanConvertNodelists, + /** + * Converts an array-like object (e.g. arguments or NodeList) to an array + * @memberOf fabric.util + * @param {Object} arrayLike + * @return {Array} + */ + toArray = function(arrayLike) { + return _slice.call(arrayLike, 0); + }; + + try { + sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array; + } + catch (err) { } + + if (!sliceCanConvertNodelists) { + toArray = function(arrayLike) { + var arr = new Array(arrayLike.length), i = arrayLike.length; + while (i--) { + arr[i] = arrayLike[i]; + } + return arr; + }; + } + + /** + * Creates specified element with specified attributes + * @memberOf fabric.util + * @param {String} tagName Type of an element to create + * @param {Object} [attributes] Attributes to set on an element + * @return {HTMLElement} Newly created element + */ + function makeElement(tagName, attributes) { + var el = fabric.document.createElement(tagName); + for (var prop in attributes) { + if (prop === 'class') { + el.className = attributes[prop]; + } + else if (prop === 'for') { + el.htmlFor = attributes[prop]; + } + else { + el.setAttribute(prop, attributes[prop]); + } + } + return el; + } + + /** + * Adds class to an element + * @memberOf fabric.util + * @param {HTMLElement} element Element to add class to + * @param {String} className Class to add to an element + */ + function addClass(element, className) { + if (element && (' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) { + element.className += (element.className ? ' ' : '') + className; + } + } + + /** + * Wraps element with another element + * @memberOf fabric.util + * @param {HTMLElement} element Element to wrap + * @param {HTMLElement|String} wrapper Element to wrap with + * @param {Object} [attributes] Attributes to set on a wrapper + * @return {HTMLElement} wrapper + */ + function wrapElement(element, wrapper, attributes) { + if (typeof wrapper === 'string') { + wrapper = makeElement(wrapper, attributes); + } + if (element.parentNode) { + element.parentNode.replaceChild(wrapper, element); + } + wrapper.appendChild(element); + return wrapper; + } + + /** + * Returns element scroll offsets + * @memberOf fabric.util + * @param {HTMLElement} element Element to operate on + * @return {Object} Object with left/top values + */ + function getScrollLeftTop(element) { + + var left = 0, + top = 0, + docElement = fabric.document.documentElement, + body = fabric.document.body || { + scrollLeft: 0, scrollTop: 0 + }; + + // While loop checks (and then sets element to) .parentNode OR .host + // to account for ShadowDOM. We still want to traverse up out of ShadowDOM, + // but the .parentNode of a root ShadowDOM node will always be null, instead + // it should be accessed through .host. See http://stackoverflow.com/a/24765528/4383938 + while (element && (element.parentNode || element.host)) { + + // Set element to element parent, or 'host' in case of ShadowDOM + element = element.parentNode || element.host; + + if (element === fabric.document) { + left = body.scrollLeft || docElement.scrollLeft || 0; + top = body.scrollTop || docElement.scrollTop || 0; + } + else { + left += element.scrollLeft || 0; + top += element.scrollTop || 0; + } + + if (element.nodeType === 1 && element.style.position === 'fixed') { + break; + } + } + + return { left: left, top: top }; + } + + /** + * Returns offset for a given element + * @function + * @memberOf fabric.util + * @param {HTMLElement} element Element to get offset for + * @return {Object} Object with "left" and "top" properties + */ + function getElementOffset(element) { + var docElem, + doc = element && element.ownerDocument, + box = { left: 0, top: 0 }, + offset = { left: 0, top: 0 }, + scrollLeftTop, + offsetAttributes = { + borderLeftWidth: 'left', + borderTopWidth: 'top', + paddingLeft: 'left', + paddingTop: 'top' + }; + + if (!doc) { + return offset; + } + + for (var attr in offsetAttributes) { + offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0; + } + + docElem = doc.documentElement; + if ( typeof element.getBoundingClientRect !== 'undefined' ) { + box = element.getBoundingClientRect(); + } + + scrollLeftTop = getScrollLeftTop(element); + + return { + left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left, + top: box.top + scrollLeftTop.top - (docElem.clientTop || 0) + offset.top + }; + } + + /** + * Returns style attribute value of a given element + * @memberOf fabric.util + * @param {HTMLElement} element Element to get style attribute for + * @param {String} attr Style attribute to get for element + * @return {String} Style attribute value of the given element. + */ + var getElementStyle; + if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) { + getElementStyle = function(element, attr) { + var style = fabric.document.defaultView.getComputedStyle(element, null); + return style ? style[attr] : undefined; + }; + } + else { + getElementStyle = function(element, attr) { + var value = element.style[attr]; + if (!value && element.currentStyle) { + value = element.currentStyle[attr]; + } + return value; + }; + } + + (function () { + var style = fabric.document.documentElement.style, + selectProp = 'userSelect' in style + ? 'userSelect' + : 'MozUserSelect' in style + ? 'MozUserSelect' + : 'WebkitUserSelect' in style + ? 'WebkitUserSelect' + : 'KhtmlUserSelect' in style + ? 'KhtmlUserSelect' + : ''; + + /** + * Makes element unselectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make unselectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementUnselectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = fabric.util.falseFunction; + } + if (selectProp) { + element.style[selectProp] = 'none'; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = 'on'; + } + return element; + } + + /** + * Makes element selectable + * @memberOf fabric.util + * @param {HTMLElement} element Element to make selectable + * @return {HTMLElement} Element that was passed in + */ + function makeElementSelectable(element) { + if (typeof element.onselectstart !== 'undefined') { + element.onselectstart = null; + } + if (selectProp) { + element.style[selectProp] = ''; + } + else if (typeof element.unselectable === 'string') { + element.unselectable = ''; + } + return element; + } + + fabric.util.makeElementUnselectable = makeElementUnselectable; + fabric.util.makeElementSelectable = makeElementSelectable; + })(); + + function getNodeCanvas(element) { + var impl = fabric.jsdomImplForWrapper(element); + return impl._canvas || impl._image; + }; + + function cleanUpJsdomNode(element) { + if (!fabric.isLikelyNode) { + return; + } + var impl = fabric.jsdomImplForWrapper(element); + if (impl) { + impl._image = null; + impl._canvas = null; + // unsure if necessary + impl._currentSrc = null; + impl._attributes = null; + impl._classList = null; + } + } + + function setImageSmoothing(ctx, value) { + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled || ctx.webkitImageSmoothingEnabled + || ctx.mozImageSmoothingEnabled || ctx.msImageSmoothingEnabled || ctx.oImageSmoothingEnabled; + ctx.imageSmoothingEnabled = value; + } + + /** + * setImageSmoothing sets the context imageSmoothingEnabled property. + * Used by canvas and by ImageObject. + * @memberOf fabric.util + * @since 4.0.0 + * @param {HTMLRenderingContext2D} ctx to set on + * @param {Boolean} value true or false + */ + fabric.util.setImageSmoothing = setImageSmoothing; + fabric.util.getById = getById; + fabric.util.toArray = toArray; + fabric.util.addClass = addClass; + fabric.util.makeElement = makeElement; + fabric.util.wrapElement = wrapElement; + fabric.util.getScrollLeftTop = getScrollLeftTop; + fabric.util.getElementOffset = getElementOffset; + fabric.util.getNodeCanvas = getNodeCanvas; + fabric.util.cleanUpJsdomNode = cleanUpJsdomNode; + +})(); + + +(function() { + + function addParamToUrl(url, param) { + return url + (/\?/.test(url) ? '&' : '?') + param; + } + + function emptyFn() { } + + /** + * Cross-browser abstraction for sending XMLHttpRequest + * @memberOf fabric.util + * @param {String} url URL to send XMLHttpRequest to + * @param {Object} [options] Options object + * @param {String} [options.method="GET"] + * @param {String} [options.parameters] parameters to append to url in GET or in body + * @param {String} [options.body] body to send with POST or PUT request + * @param {Function} options.onComplete Callback to invoke when request is completed + * @return {XMLHttpRequest} request + */ + function request(url, options) { + options || (options = { }); + + var method = options.method ? options.method.toUpperCase() : 'GET', + onComplete = options.onComplete || function() { }, + xhr = new fabric.window.XMLHttpRequest(), + body = options.body || options.parameters; + + /** @ignore */ + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + onComplete(xhr); + xhr.onreadystatechange = emptyFn; + } + }; + + if (method === 'GET') { + body = null; + if (typeof options.parameters === 'string') { + url = addParamToUrl(url, options.parameters); + } + } + + xhr.open(method, url, true); + + if (method === 'POST' || method === 'PUT') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + + xhr.send(body); + return xhr; + } + + fabric.util.request = request; +})(); + + +/** + * Wrapper around `console.log` (when available) + * @param {*} [values] Values to log + */ +fabric.log = console.log; + +/** + * Wrapper around `console.warn` (when available) + * @param {*} [values] Values to log as a warning + */ +fabric.warn = console.warn; + + +(function() { + + function noop() { + return false; + } + + function defaultEasing(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + + /** + * Changes value from one to another within certain period of time, invoking callbacks as value is being changed. + * @memberOf fabric.util + * @param {Object} [options] Animation options + * @param {Function} [options.onChange] Callback; invoked on every value change + * @param {Function} [options.onComplete] Callback; invoked when value change is completed + * @param {Number} [options.startValue=0] Starting value + * @param {Number} [options.endValue=100] Ending value + * @param {Number} [options.byValue=100] Value to modify the property by + * @param {Function} [options.easing] Easing function + * @param {Number} [options.duration=500] Duration of change (in ms) + * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. + * @returns {Function} abort function + */ + function animate(options) { + var cancel = false; + requestAnimFrame(function(timestamp) { + options || (options = { }); + + var start = timestamp || +new Date(), + duration = options.duration || 500, + finish = start + duration, time, + onChange = options.onChange || noop, + abort = options.abort || noop, + onComplete = options.onComplete || noop, + easing = options.easing || defaultEasing, + startValue = 'startValue' in options ? options.startValue : 0, + endValue = 'endValue' in options ? options.endValue : 100, + byValue = options.byValue || endValue - startValue; + + options.onStart && options.onStart(); + + (function tick(ticktime) { + // TODO: move abort call after calculation + // and pass (current,valuePerc, timePerc) as arguments + time = ticktime || +new Date(); + var currentTime = time > finish ? duration : (time - start), + timePerc = currentTime / duration, + current = easing(currentTime, startValue, byValue, duration), + valuePerc = Math.abs((current - startValue) / byValue); + if (cancel) { + return; + } + if (abort(current, valuePerc, timePerc)) { + // remove this in 4.0 + // does to even make sense to abort and run onComplete? + onComplete(endValue, 1, 1); + return; + } + if (time > finish) { + onChange(endValue, 1, 1); + onComplete(endValue, 1, 1); + return; + } + else { + onChange(current, valuePerc, timePerc); + requestAnimFrame(tick); + } + })(start); + }); + return function() { + cancel = true; + }; + } + + var _requestAnimFrame = fabric.window.requestAnimationFrame || + fabric.window.webkitRequestAnimationFrame || + fabric.window.mozRequestAnimationFrame || + fabric.window.oRequestAnimationFrame || + fabric.window.msRequestAnimationFrame || + function(callback) { + return fabric.window.setTimeout(callback, 1000 / 60); + }; + + var _cancelAnimFrame = fabric.window.cancelAnimationFrame || fabric.window.clearTimeout; + + /** + * requestAnimationFrame polyfill based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + * In order to get a precise start time, `requestAnimFrame` should be called as an entry into the method + * @memberOf fabric.util + * @param {Function} callback Callback to invoke + * @param {DOMElement} element optional Element to associate with animation + */ + function requestAnimFrame() { + return _requestAnimFrame.apply(fabric.window, arguments); + } + + function cancelAnimFrame() { + return _cancelAnimFrame.apply(fabric.window, arguments); + } + + fabric.util.animate = animate; + fabric.util.requestAnimFrame = requestAnimFrame; + fabric.util.cancelAnimFrame = cancelAnimFrame; +})(); + + +(function() { + // Calculate an in-between color. Returns a "rgba()" string. + // Credit: Edwin Martin + // http://www.bitstorm.org/jquery/color-animation/jquery.animate-colors.js + function calculateColor(begin, end, pos) { + var color = 'rgba(' + + parseInt((begin[0] + pos * (end[0] - begin[0])), 10) + ',' + + parseInt((begin[1] + pos * (end[1] - begin[1])), 10) + ',' + + parseInt((begin[2] + pos * (end[2] - begin[2])), 10); + + color += ',' + (begin && end ? parseFloat(begin[3] + pos * (end[3] - begin[3])) : 1); + color += ')'; + return color; + } + + /** + * Changes the color from one to another within certain period of time, invoking callbacks as value is being changed. + * @memberOf fabric.util + * @param {String} fromColor The starting color in hex or rgb(a) format. + * @param {String} toColor The starting color in hex or rgb(a) format. + * @param {Number} [duration] Duration of change (in ms). + * @param {Object} [options] Animation options + * @param {Function} [options.onChange] Callback; invoked on every value change + * @param {Function} [options.onComplete] Callback; invoked when value change is completed + * @param {Function} [options.colorEasing] Easing function. Note that this function only take two arguments (currentTime, duration). Thus the regular animation easing functions cannot be used. + * @param {Function} [options.abort] Additional function with logic. If returns true, onComplete is called. + * @returns {Function} abort function + */ + function animateColor(fromColor, toColor, duration, options) { + var startColor = new fabric.Color(fromColor).getSource(), + endColor = new fabric.Color(toColor).getSource(), + originalOnComplete = options.onComplete, + originalOnChange = options.onChange; + options = options || {}; + + return fabric.util.animate(fabric.util.object.extend(options, { + duration: duration || 500, + startValue: startColor, + endValue: endColor, + byValue: endColor, + easing: function (currentTime, startValue, byValue, duration) { + var posValue = options.colorEasing + ? options.colorEasing(currentTime, duration) + : 1 - Math.cos(currentTime / duration * (Math.PI / 2)); + return calculateColor(startValue, byValue, posValue); + }, + // has to take in account for color restoring; + onComplete: function(current, valuePerc, timePerc) { + if (originalOnComplete) { + return originalOnComplete( + calculateColor(endColor, endColor, 0), + valuePerc, + timePerc + ); + } + }, + onChange: function(current, valuePerc, timePerc) { + if (originalOnChange) { + if (Array.isArray(current)) { + return originalOnChange( + calculateColor(current, current, 0), + valuePerc, + timePerc + ); + } + originalOnChange(current, valuePerc, timePerc); + } + } + })); + } + + fabric.util.animateColor = animateColor; + +})(); + + +(function() { + + function normalize(a, c, p, s) { + if (a < Math.abs(c)) { + a = c; + s = p / 4; + } + else { + //handle the 0/0 case: + if (c === 0 && a === 0) { + s = p / (2 * Math.PI) * Math.asin(1); + } + else { + s = p / (2 * Math.PI) * Math.asin(c / a); + } + } + return { a: a, c: c, p: p, s: s }; + } + + function elastic(opts, t, d) { + return opts.a * + Math.pow(2, 10 * (t -= 1)) * + Math.sin( (t * d - opts.s) * (2 * Math.PI) / opts.p ); + } + + /** + * Cubic easing out + * @memberOf fabric.util.ease + */ + function easeOutCubic(t, b, c, d) { + return c * ((t = t / d - 1) * t * t + 1) + b; + } + + /** + * Cubic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutCubic(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t + 2) + b; + } + + /** + * Quartic easing in + * @memberOf fabric.util.ease + */ + function easeInQuart(t, b, c, d) { + return c * (t /= d) * t * t * t + b; + } + + /** + * Quartic easing out + * @memberOf fabric.util.ease + */ + function easeOutQuart(t, b, c, d) { + return -c * ((t = t / d - 1) * t * t * t - 1) + b; + } + + /** + * Quartic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutQuart(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + return -c / 2 * ((t -= 2) * t * t * t - 2) + b; + } + + /** + * Quintic easing in + * @memberOf fabric.util.ease + */ + function easeInQuint(t, b, c, d) { + return c * (t /= d) * t * t * t * t + b; + } + + /** + * Quintic easing out + * @memberOf fabric.util.ease + */ + function easeOutQuint(t, b, c, d) { + return c * ((t = t / d - 1) * t * t * t * t + 1) + b; + } + + /** + * Quintic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutQuint(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + return c / 2 * ((t -= 2) * t * t * t * t + 2) + b; + } + + /** + * Sinusoidal easing in + * @memberOf fabric.util.ease + */ + function easeInSine(t, b, c, d) { + return -c * Math.cos(t / d * (Math.PI / 2)) + c + b; + } + + /** + * Sinusoidal easing out + * @memberOf fabric.util.ease + */ + function easeOutSine(t, b, c, d) { + return c * Math.sin(t / d * (Math.PI / 2)) + b; + } + + /** + * Sinusoidal easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutSine(t, b, c, d) { + return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b; + } + + /** + * Exponential easing in + * @memberOf fabric.util.ease + */ + function easeInExpo(t, b, c, d) { + return (t === 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b; + } + + /** + * Exponential easing out + * @memberOf fabric.util.ease + */ + function easeOutExpo(t, b, c, d) { + return (t === d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b; + } + + /** + * Exponential easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutExpo(t, b, c, d) { + if (t === 0) { + return b; + } + if (t === d) { + return b + c; + } + t /= d / 2; + if (t < 1) { + return c / 2 * Math.pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b; + } + + /** + * Circular easing in + * @memberOf fabric.util.ease + */ + function easeInCirc(t, b, c, d) { + return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b; + } + + /** + * Circular easing out + * @memberOf fabric.util.ease + */ + function easeOutCirc(t, b, c, d) { + return c * Math.sqrt(1 - (t = t / d - 1) * t) + b; + } + + /** + * Circular easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutCirc(t, b, c, d) { + t /= d / 2; + if (t < 1) { + return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b; + } + return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b; + } + + /** + * Elastic easing in + * @memberOf fabric.util.ease + */ + function easeInElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + var opts = normalize(a, c, p, s); + return -elastic(opts, t, d) + b; + } + + /** + * Elastic easing out + * @memberOf fabric.util.ease + */ + function easeOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d; + if (t === 1) { + return b + c; + } + if (!p) { + p = d * 0.3; + } + var opts = normalize(a, c, p, s); + return opts.a * Math.pow(2, -10 * t) * Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) + opts.c + b; + } + + /** + * Elastic easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutElastic(t, b, c, d) { + var s = 1.70158, p = 0, a = c; + if (t === 0) { + return b; + } + t /= d / 2; + if (t === 2) { + return b + c; + } + if (!p) { + p = d * (0.3 * 1.5); + } + var opts = normalize(a, c, p, s); + if (t < 1) { + return -0.5 * elastic(opts, t, d) + b; + } + return opts.a * Math.pow(2, -10 * (t -= 1)) * + Math.sin((t * d - opts.s) * (2 * Math.PI) / opts.p ) * 0.5 + opts.c + b; + } + + /** + * Backwards easing in + * @memberOf fabric.util.ease + */ + function easeInBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * (t /= d) * t * ((s + 1) * t - s) + b; + } + + /** + * Backwards easing out + * @memberOf fabric.util.ease + */ + function easeOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; + } + + /** + * Backwards easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutBack(t, b, c, d, s) { + if (s === undefined) { + s = 1.70158; + } + t /= d / 2; + if (t < 1) { + return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; + } + return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; + } + + /** + * Bouncing easing in + * @memberOf fabric.util.ease + */ + function easeInBounce(t, b, c, d) { + return c - easeOutBounce (d - t, 0, c, d) + b; + } + + /** + * Bouncing easing out + * @memberOf fabric.util.ease + */ + function easeOutBounce(t, b, c, d) { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2 / 2.75)) { + return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; + } + else if (t < (2.5 / 2.75)) { + return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; + } + else { + return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; + } + } + + /** + * Bouncing easing in and out + * @memberOf fabric.util.ease + */ + function easeInOutBounce(t, b, c, d) { + if (t < d / 2) { + return easeInBounce (t * 2, 0, c, d) * 0.5 + b; + } + return easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + + /** + * Easing functions + * See Easing Equations by Robert Penner + * @namespace fabric.util.ease + */ + fabric.util.ease = { + + /** + * Quadratic easing in + * @memberOf fabric.util.ease + */ + easeInQuad: function(t, b, c, d) { + return c * (t /= d) * t + b; + }, + + /** + * Quadratic easing out + * @memberOf fabric.util.ease + */ + easeOutQuad: function(t, b, c, d) { + return -c * (t /= d) * (t - 2) + b; + }, + + /** + * Quadratic easing in and out + * @memberOf fabric.util.ease + */ + easeInOutQuad: function(t, b, c, d) { + t /= (d / 2); + if (t < 1) { + return c / 2 * t * t + b; + } + return -c / 2 * ((--t) * (t - 2) - 1) + b; + }, + + /** + * Cubic easing in + * @memberOf fabric.util.ease + */ + easeInCubic: function(t, b, c, d) { + return c * (t /= d) * t * t + b; + }, + + easeOutCubic: easeOutCubic, + easeInOutCubic: easeInOutCubic, + easeInQuart: easeInQuart, + easeOutQuart: easeOutQuart, + easeInOutQuart: easeInOutQuart, + easeInQuint: easeInQuint, + easeOutQuint: easeOutQuint, + easeInOutQuint: easeInOutQuint, + easeInSine: easeInSine, + easeOutSine: easeOutSine, + easeInOutSine: easeInOutSine, + easeInExpo: easeInExpo, + easeOutExpo: easeOutExpo, + easeInOutExpo: easeInOutExpo, + easeInCirc: easeInCirc, + easeOutCirc: easeOutCirc, + easeInOutCirc: easeInOutCirc, + easeInElastic: easeInElastic, + easeOutElastic: easeOutElastic, + easeInOutElastic: easeInOutElastic, + easeInBack: easeInBack, + easeOutBack: easeOutBack, + easeInOutBack: easeInOutBack, + easeInBounce: easeInBounce, + easeOutBounce: easeOutBounce, + easeInOutBounce: easeInOutBounce + }; + +})(); + + +(function(global) { + + 'use strict'; + + /** + * @name fabric + * @namespace + */ + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + parseUnit = fabric.util.parseUnit, + multiplyTransformMatrices = fabric.util.multiplyTransformMatrices, + + svgValidTagNames = ['path', 'circle', 'polygon', 'polyline', 'ellipse', 'rect', 'line', + 'image', 'text'], + svgViewBoxElements = ['symbol', 'image', 'marker', 'pattern', 'view', 'svg'], + svgInvalidAncestors = ['pattern', 'defs', 'symbol', 'metadata', 'clipPath', 'mask', 'desc'], + svgValidParents = ['symbol', 'g', 'a', 'svg', 'clipPath', 'defs'], + + attributesMap = { + cx: 'left', + x: 'left', + r: 'radius', + cy: 'top', + y: 'top', + display: 'visible', + visibility: 'visible', + transform: 'transformMatrix', + 'fill-opacity': 'fillOpacity', + 'fill-rule': 'fillRule', + 'font-family': 'fontFamily', + 'font-size': 'fontSize', + 'font-style': 'fontStyle', + 'font-weight': 'fontWeight', + 'letter-spacing': 'charSpacing', + 'paint-order': 'paintFirst', + 'stroke-dasharray': 'strokeDashArray', + 'stroke-dashoffset': 'strokeDashOffset', + 'stroke-linecap': 'strokeLineCap', + 'stroke-linejoin': 'strokeLineJoin', + 'stroke-miterlimit': 'strokeMiterLimit', + 'stroke-opacity': 'strokeOpacity', + 'stroke-width': 'strokeWidth', + 'text-decoration': 'textDecoration', + 'text-anchor': 'textAnchor', + opacity: 'opacity', + 'clip-path': 'clipPath', + 'clip-rule': 'clipRule', + 'vector-effect': 'strokeUniform', + 'image-rendering': 'imageSmoothing', + }, + + colorAttributes = { + stroke: 'strokeOpacity', + fill: 'fillOpacity' + }, + + fSize = 'font-size', cPath = 'clip-path'; + + fabric.svgValidTagNamesRegEx = getSvgRegex(svgValidTagNames); + fabric.svgViewBoxElementsRegEx = getSvgRegex(svgViewBoxElements); + fabric.svgInvalidAncestorsRegEx = getSvgRegex(svgInvalidAncestors); + fabric.svgValidParentsRegEx = getSvgRegex(svgValidParents); + + fabric.cssRules = { }; + fabric.gradientDefs = { }; + fabric.clipPaths = { }; + + function normalizeAttr(attr) { + // transform attribute names + if (attr in attributesMap) { + return attributesMap[attr]; + } + return attr; + } + + function normalizeValue(attr, value, parentAttributes, fontSize) { + var isArray = Object.prototype.toString.call(value) === '[object Array]', + parsed; + + if ((attr === 'fill' || attr === 'stroke') && value === 'none') { + value = ''; + } + else if (attr === 'strokeUniform') { + return (value === 'non-scaling-stroke'); + } + else if (attr === 'strokeDashArray') { + if (value === 'none') { + value = null; + } + else { + value = value.replace(/,/g, ' ').split(/\s+/).map(parseFloat); + } + } + else if (attr === 'transformMatrix') { + if (parentAttributes && parentAttributes.transformMatrix) { + value = multiplyTransformMatrices( + parentAttributes.transformMatrix, fabric.parseTransformAttribute(value)); + } + else { + value = fabric.parseTransformAttribute(value); + } + } + else if (attr === 'visible') { + value = value !== 'none' && value !== 'hidden'; + // display=none on parent element always takes precedence over child element + if (parentAttributes && parentAttributes.visible === false) { + value = false; + } + } + else if (attr === 'opacity') { + value = parseFloat(value); + if (parentAttributes && typeof parentAttributes.opacity !== 'undefined') { + value *= parentAttributes.opacity; + } + } + else if (attr === 'textAnchor' /* text-anchor */) { + value = value === 'start' ? 'left' : value === 'end' ? 'right' : 'center'; + } + else if (attr === 'charSpacing') { + // parseUnit returns px and we convert it to em + parsed = parseUnit(value, fontSize) / fontSize * 1000; + } + else if (attr === 'paintFirst') { + var fillIndex = value.indexOf('fill'); + var strokeIndex = value.indexOf('stroke'); + var value = 'fill'; + if (fillIndex > -1 && strokeIndex > -1 && strokeIndex < fillIndex) { + value = 'stroke'; + } + else if (fillIndex === -1 && strokeIndex > -1) { + value = 'stroke'; + } + } + else if (attr === 'href' || attr === 'xlink:href' || attr === 'font') { + return value; + } + else if (attr === 'imageSmoothing') { + return (value === 'optimizeQuality'); + } + else { + parsed = isArray ? value.map(parseUnit) : parseUnit(value, fontSize); + } + + return (!isArray && isNaN(parsed) ? value : parsed); + } + + /** + * @private + */ + function getSvgRegex(arr) { + return new RegExp('^(' + arr.join('|') + ')\\b', 'i'); + } + + /** + * @private + * @param {Object} attributes Array of attributes to parse + */ + function _setStrokeFillOpacity(attributes) { + for (var attr in colorAttributes) { + + if (typeof attributes[colorAttributes[attr]] === 'undefined' || attributes[attr] === '') { + continue; + } + + if (typeof attributes[attr] === 'undefined') { + if (!fabric.Object.prototype[attr]) { + continue; + } + attributes[attr] = fabric.Object.prototype[attr]; + } + + if (attributes[attr].indexOf('url(') === 0) { + continue; + } + + var color = new fabric.Color(attributes[attr]); + attributes[attr] = color.setAlpha(toFixed(color.getAlpha() * attributes[colorAttributes[attr]], 2)).toRgba(); + } + return attributes; + } + + /** + * @private + */ + function _getMultipleNodes(doc, nodeNames) { + var nodeName, nodeArray = [], nodeList, i, len; + for (i = 0, len = nodeNames.length; i < len; i++) { + nodeName = nodeNames[i]; + nodeList = doc.getElementsByTagName(nodeName); + nodeArray = nodeArray.concat(Array.prototype.slice.call(nodeList)); + } + return nodeArray; + } + + /** + * Parses "transform" attribute, returning an array of values + * @static + * @function + * @memberOf fabric + * @param {String} attributeValue String containing attribute value + * @return {Array} Array of 6 elements representing transformation matrix + */ + fabric.parseTransformAttribute = (function() { + function rotateMatrix(matrix, args) { + var cos = fabric.util.cos(args[0]), sin = fabric.util.sin(args[0]), + x = 0, y = 0; + if (args.length === 3) { + x = args[1]; + y = args[2]; + } + + matrix[0] = cos; + matrix[1] = sin; + matrix[2] = -sin; + matrix[3] = cos; + matrix[4] = x - (cos * x - sin * y); + matrix[5] = y - (sin * x + cos * y); + } + + function scaleMatrix(matrix, args) { + var multiplierX = args[0], + multiplierY = (args.length === 2) ? args[1] : args[0]; + + matrix[0] = multiplierX; + matrix[3] = multiplierY; + } + + function skewMatrix(matrix, args, pos) { + matrix[pos] = Math.tan(fabric.util.degreesToRadians(args[0])); + } + + function translateMatrix(matrix, args) { + matrix[4] = args[0]; + if (args.length === 2) { + matrix[5] = args[1]; + } + } + + // identity matrix + var iMatrix = fabric.iMatrix, + + // == begin transform regexp + number = fabric.reNum, + + commaWsp = fabric.commaWsp, + + skewX = '(?:(skewX)\\s*\\(\\s*(' + number + ')\\s*\\))', + + skewY = '(?:(skewY)\\s*\\(\\s*(' + number + ')\\s*\\))', + + rotate = '(?:(rotate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + ')' + + commaWsp + '(' + number + '))?\\s*\\))', + + scale = '(?:(scale)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + translate = '(?:(translate)\\s*\\(\\s*(' + number + ')(?:' + + commaWsp + '(' + number + '))?\\s*\\))', + + matrix = '(?:(matrix)\\s*\\(\\s*' + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + commaWsp + + '(' + number + ')' + + '\\s*\\))', + + transform = '(?:' + + matrix + '|' + + translate + '|' + + scale + '|' + + rotate + '|' + + skewX + '|' + + skewY + + ')', + + transforms = '(?:' + transform + '(?:' + commaWsp + '*' + transform + ')*' + ')', + + transformList = '^\\s*(?:' + transforms + '?)\\s*$', + + // http://www.w3.org/TR/SVG/coords.html#TransformAttribute + reTransformList = new RegExp(transformList), + // == end transform regexp + + reTransform = new RegExp(transform, 'g'); + + return function(attributeValue) { + + // start with identity matrix + var matrix = iMatrix.concat(), + matrices = []; + + // return if no argument was given or + // an argument does not match transform attribute regexp + if (!attributeValue || (attributeValue && !reTransformList.test(attributeValue))) { + return matrix; + } + + attributeValue.replace(reTransform, function(match) { + + var m = new RegExp(transform).exec(match).filter(function (match) { + // match !== '' && match != null + return (!!match); + }), + operation = m[1], + args = m.slice(2).map(parseFloat); + + switch (operation) { + case 'translate': + translateMatrix(matrix, args); + break; + case 'rotate': + args[0] = fabric.util.degreesToRadians(args[0]); + rotateMatrix(matrix, args); + break; + case 'scale': + scaleMatrix(matrix, args); + break; + case 'skewX': + skewMatrix(matrix, args, 2); + break; + case 'skewY': + skewMatrix(matrix, args, 1); + break; + case 'matrix': + matrix = args; + break; + } + + // snapshot current matrix into matrices array + matrices.push(matrix.concat()); + // reset + matrix = iMatrix.concat(); + }); + + var combinedMatrix = matrices[0]; + while (matrices.length > 1) { + matrices.shift(); + combinedMatrix = fabric.util.multiplyTransformMatrices(combinedMatrix, matrices[0]); + } + return combinedMatrix; + }; + })(); + + /** + * @private + */ + function parseStyleString(style, oStyle) { + var attr, value; + style.replace(/;\s*$/, '').split(';').forEach(function (chunk) { + var pair = chunk.split(':'); + + attr = pair[0].trim().toLowerCase(); + value = pair[1].trim(); + + oStyle[attr] = value; + }); + } + + /** + * @private + */ + function parseStyleObject(style, oStyle) { + var attr, value; + for (var prop in style) { + if (typeof style[prop] === 'undefined') { + continue; + } + + attr = prop.toLowerCase(); + value = style[prop]; + + oStyle[attr] = value; + } + } + + /** + * @private + */ + function getGlobalStylesForElement(element, svgUid) { + var styles = { }; + for (var rule in fabric.cssRules[svgUid]) { + if (elementMatchesRule(element, rule.split(' '))) { + for (var property in fabric.cssRules[svgUid][rule]) { + styles[property] = fabric.cssRules[svgUid][rule][property]; + } + } + } + return styles; + } + + /** + * @private + */ + function elementMatchesRule(element, selectors) { + var firstMatching, parentMatching = true; + //start from rightmost selector. + firstMatching = selectorMatches(element, selectors.pop()); + if (firstMatching && selectors.length) { + parentMatching = doesSomeParentMatch(element, selectors); + } + return firstMatching && parentMatching && (selectors.length === 0); + } + + function doesSomeParentMatch(element, selectors) { + var selector, parentMatching = true; + while (element.parentNode && element.parentNode.nodeType === 1 && selectors.length) { + if (parentMatching) { + selector = selectors.pop(); + } + element = element.parentNode; + parentMatching = selectorMatches(element, selector); + } + return selectors.length === 0; + } + + /** + * @private + */ + function selectorMatches(element, selector) { + var nodeName = element.nodeName, + classNames = element.getAttribute('class'), + id = element.getAttribute('id'), matcher, i; + // i check if a selector matches slicing away part from it. + // if i get empty string i should match + matcher = new RegExp('^' + nodeName, 'i'); + selector = selector.replace(matcher, ''); + if (id && selector.length) { + matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + if (classNames && selector.length) { + classNames = classNames.split(' '); + for (i = classNames.length; i--;) { + matcher = new RegExp('\\.' + classNames[i] + '(?![a-zA-Z\\-]+)', 'i'); + selector = selector.replace(matcher, ''); + } + } + return selector.length === 0; + } + + /** + * @private + * to support IE8 missing getElementById on SVGdocument and on node xmlDOM + */ + function elementById(doc, id) { + var el; + doc.getElementById && (el = doc.getElementById(id)); + if (el) { + return el; + } + var node, i, len, nodelist = doc.getElementsByTagName('*'); + for (i = 0, len = nodelist.length; i < len; i++) { + node = nodelist[i]; + if (id === node.getAttribute('id')) { + return node; + } + } + } + + /** + * @private + */ + function parseUseDirectives(doc) { + var nodelist = _getMultipleNodes(doc, ['use', 'svg:use']), i = 0; + while (nodelist.length && i < nodelist.length) { + var el = nodelist[i], + xlinkAttribute = el.getAttribute('xlink:href') || el.getAttribute('href'); + + if (xlinkAttribute === null) { + return; + } + + var xlink = xlinkAttribute.substr(1), + x = el.getAttribute('x') || 0, + y = el.getAttribute('y') || 0, + el2 = elementById(doc, xlink).cloneNode(true), + currentTrans = (el2.getAttribute('transform') || '') + ' translate(' + x + ', ' + y + ')', + parentNode, + oldLength = nodelist.length, attr, + j, + attrs, + len, + namespace = fabric.svgNS; + + applyViewboxTransform(el2); + if (/^svg$/i.test(el2.nodeName)) { + var el3 = el2.ownerDocument.createElementNS(namespace, 'g'); + for (j = 0, attrs = el2.attributes, len = attrs.length; j < len; j++) { + attr = attrs.item(j); + el3.setAttributeNS(namespace, attr.nodeName, attr.nodeValue); + } + // el2.firstChild != null + while (el2.firstChild) { + el3.appendChild(el2.firstChild); + } + el2 = el3; + } + + for (j = 0, attrs = el.attributes, len = attrs.length; j < len; j++) { + attr = attrs.item(j); + if (attr.nodeName === 'x' || attr.nodeName === 'y' || + attr.nodeName === 'xlink:href' || attr.nodeName === 'href') { + continue; + } + + if (attr.nodeName === 'transform') { + currentTrans = attr.nodeValue + ' ' + currentTrans; + } + else { + el2.setAttribute(attr.nodeName, attr.nodeValue); + } + } + + el2.setAttribute('transform', currentTrans); + el2.setAttribute('instantiated_by_use', '1'); + el2.removeAttribute('id'); + parentNode = el.parentNode; + parentNode.replaceChild(el2, el); + // some browsers do not shorten nodelist after replaceChild (IE8) + if (nodelist.length === oldLength) { + i++; + } + } + } + + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // matches, e.g.: +14.56e-12, etc. + var reViewBoxAttrValue = new RegExp( + '^' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*,?' + + '\\s*(' + fabric.reNum + '+)\\s*' + + '$' + ); + + /** + * Add a element that envelop all child elements and makes the viewbox transformMatrix descend on all elements + */ + function applyViewboxTransform(element) { + if (!fabric.svgViewBoxElementsRegEx.test(element.nodeName)) { + return {}; + } + var viewBoxAttr = element.getAttribute('viewBox'), + scaleX = 1, + scaleY = 1, + minX = 0, + minY = 0, + viewBoxWidth, viewBoxHeight, matrix, el, + widthAttr = element.getAttribute('width'), + heightAttr = element.getAttribute('height'), + x = element.getAttribute('x') || 0, + y = element.getAttribute('y') || 0, + preserveAspectRatio = element.getAttribute('preserveAspectRatio') || '', + missingViewBox = (!viewBoxAttr || !(viewBoxAttr = viewBoxAttr.match(reViewBoxAttrValue))), + missingDimAttr = (!widthAttr || !heightAttr || widthAttr === '100%' || heightAttr === '100%'), + toBeParsed = missingViewBox && missingDimAttr, + parsedDim = { }, translateMatrix = '', widthDiff = 0, heightDiff = 0; + + parsedDim.width = 0; + parsedDim.height = 0; + parsedDim.toBeParsed = toBeParsed; + + if (missingViewBox) { + if (((x || y) && element.parentNode && element.parentNode.nodeName !== '#document')) { + translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; + matrix = (element.getAttribute('transform') || '') + translateMatrix; + element.setAttribute('transform', matrix); + element.removeAttribute('x'); + element.removeAttribute('y'); + } + } + + if (toBeParsed) { + return parsedDim; + } + + if (missingViewBox) { + parsedDim.width = parseUnit(widthAttr); + parsedDim.height = parseUnit(heightAttr); + // set a transform for elements that have x y and are inner(only) SVGs + return parsedDim; + } + minX = -parseFloat(viewBoxAttr[1]); + minY = -parseFloat(viewBoxAttr[2]); + viewBoxWidth = parseFloat(viewBoxAttr[3]); + viewBoxHeight = parseFloat(viewBoxAttr[4]); + parsedDim.minX = minX; + parsedDim.minY = minY; + parsedDim.viewBoxWidth = viewBoxWidth; + parsedDim.viewBoxHeight = viewBoxHeight; + if (!missingDimAttr) { + parsedDim.width = parseUnit(widthAttr); + parsedDim.height = parseUnit(heightAttr); + scaleX = parsedDim.width / viewBoxWidth; + scaleY = parsedDim.height / viewBoxHeight; + } + else { + parsedDim.width = viewBoxWidth; + parsedDim.height = viewBoxHeight; + } + + // default is to preserve aspect ratio + preserveAspectRatio = fabric.util.parsePreserveAspectRatioAttribute(preserveAspectRatio); + if (preserveAspectRatio.alignX !== 'none') { + //translate all container for the effect of Mid, Min, Max + if (preserveAspectRatio.meetOrSlice === 'meet') { + scaleY = scaleX = (scaleX > scaleY ? scaleY : scaleX); + // calculate additional translation to move the viewbox + } + if (preserveAspectRatio.meetOrSlice === 'slice') { + scaleY = scaleX = (scaleX > scaleY ? scaleX : scaleY); + // calculate additional translation to move the viewbox + } + widthDiff = parsedDim.width - viewBoxWidth * scaleX; + heightDiff = parsedDim.height - viewBoxHeight * scaleX; + if (preserveAspectRatio.alignX === 'Mid') { + widthDiff /= 2; + } + if (preserveAspectRatio.alignY === 'Mid') { + heightDiff /= 2; + } + if (preserveAspectRatio.alignX === 'Min') { + widthDiff = 0; + } + if (preserveAspectRatio.alignY === 'Min') { + heightDiff = 0; + } + } + + if (scaleX === 1 && scaleY === 1 && minX === 0 && minY === 0 && x === 0 && y === 0) { + return parsedDim; + } + if ((x || y) && element.parentNode.nodeName !== '#document') { + translateMatrix = ' translate(' + parseUnit(x) + ' ' + parseUnit(y) + ') '; + } + + matrix = translateMatrix + ' matrix(' + scaleX + + ' 0' + + ' 0 ' + + scaleY + ' ' + + (minX * scaleX + widthDiff) + ' ' + + (minY * scaleY + heightDiff) + ') '; + // seems unused. + // parsedDim.viewboxTransform = fabric.parseTransformAttribute(matrix); + if (element.nodeName === 'svg') { + el = element.ownerDocument.createElementNS(fabric.svgNS, 'g'); + // element.firstChild != null + while (element.firstChild) { + el.appendChild(element.firstChild); + } + element.appendChild(el); + } + else { + el = element; + el.removeAttribute('x'); + el.removeAttribute('y'); + matrix = el.getAttribute('transform') + matrix; + } + el.setAttribute('transform', matrix); + return parsedDim; + } + + function hasAncestorWithNodeName(element, nodeName) { + while (element && (element = element.parentNode)) { + if (element.nodeName && nodeName.test(element.nodeName.replace('svg:', '')) + && !element.getAttribute('instantiated_by_use')) { + return true; + } + } + return false; + } + + /** + * Parses an SVG document, converts it to an array of corresponding fabric.* instances and passes them to a callback + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @param {Function} callback Callback to call when parsing is finished; + * It's being passed an array of elements (parsed from a document). + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [parsingOptions] options for parsing document + * @param {String} [parsingOptions.crossOrigin] crossOrigin settings + */ + fabric.parseSVGDocument = function(doc, callback, reviver, parsingOptions) { + if (!doc) { + return; + } + + parseUseDirectives(doc); + + var svgUid = fabric.Object.__uid++, i, len, + options = applyViewboxTransform(doc), + descendants = fabric.util.toArray(doc.getElementsByTagName('*')); + options.crossOrigin = parsingOptions && parsingOptions.crossOrigin; + options.svgUid = svgUid; + + if (descendants.length === 0 && fabric.isLikelyNode) { + // we're likely in node, where "o3-xml" library fails to gEBTN("*") + // https://github.com/ajaxorg/node-o3-xml/issues/21 + descendants = doc.selectNodes('//*[name(.)!="svg"]'); + var arr = []; + for (i = 0, len = descendants.length; i < len; i++) { + arr[i] = descendants[i]; + } + descendants = arr; + } + + var elements = descendants.filter(function(el) { + applyViewboxTransform(el); + return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')) && + !hasAncestorWithNodeName(el, fabric.svgInvalidAncestorsRegEx); // http://www.w3.org/TR/SVG/struct.html#DefsElement + }); + if (!elements || (elements && !elements.length)) { + callback && callback([], {}); + return; + } + var clipPaths = { }; + descendants.filter(function(el) { + return el.nodeName.replace('svg:', '') === 'clipPath'; + }).forEach(function(el) { + var id = el.getAttribute('id'); + clipPaths[id] = fabric.util.toArray(el.getElementsByTagName('*')).filter(function(el) { + return fabric.svgValidTagNamesRegEx.test(el.nodeName.replace('svg:', '')); + }); + }); + fabric.gradientDefs[svgUid] = fabric.getGradientDefs(doc); + fabric.cssRules[svgUid] = fabric.getCSSRules(doc); + fabric.clipPaths[svgUid] = clipPaths; + // Precedence of rules: style > class > attribute + fabric.parseElements(elements, function(instances, elements) { + if (callback) { + callback(instances, options, elements, descendants); + delete fabric.gradientDefs[svgUid]; + delete fabric.cssRules[svgUid]; + delete fabric.clipPaths[svgUid]; + } + }, clone(options), reviver, parsingOptions); + }; + + function recursivelyParseGradientsXlink(doc, gradient) { + var gradientsAttrs = ['gradientTransform', 'x1', 'x2', 'y1', 'y2', 'gradientUnits', 'cx', 'cy', 'r', 'fx', 'fy'], + xlinkAttr = 'xlink:href', + xLink = gradient.getAttribute(xlinkAttr).substr(1), + referencedGradient = elementById(doc, xLink); + if (referencedGradient && referencedGradient.getAttribute(xlinkAttr)) { + recursivelyParseGradientsXlink(doc, referencedGradient); + } + gradientsAttrs.forEach(function(attr) { + if (referencedGradient && !gradient.hasAttribute(attr) && referencedGradient.hasAttribute(attr)) { + gradient.setAttribute(attr, referencedGradient.getAttribute(attr)); + } + }); + if (!gradient.children.length) { + var referenceClone = referencedGradient.cloneNode(true); + while (referenceClone.firstChild) { + gradient.appendChild(referenceClone.firstChild); + } + } + gradient.removeAttribute(xlinkAttr); + } + + var reFontDeclaration = new RegExp( + '(normal|italic)?\\s*(normal|small-caps)?\\s*' + + '(normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900)?\\s*(' + + fabric.reNum + + '(?:px|cm|mm|em|pt|pc|in)*)(?:\\/(normal|' + fabric.reNum + '))?\\s+(.*)'); + + extend(fabric, { + /** + * Parses a short font declaration, building adding its properties to a style object + * @static + * @function + * @memberOf fabric + * @param {String} value font declaration + * @param {Object} oStyle definition + */ + parseFontDeclaration: function(value, oStyle) { + var match = value.match(reFontDeclaration); + + if (!match) { + return; + } + var fontStyle = match[1], + // font variant is not used + // fontVariant = match[2], + fontWeight = match[3], + fontSize = match[4], + lineHeight = match[5], + fontFamily = match[6]; + + if (fontStyle) { + oStyle.fontStyle = fontStyle; + } + if (fontWeight) { + oStyle.fontWeight = isNaN(parseFloat(fontWeight)) ? fontWeight : parseFloat(fontWeight); + } + if (fontSize) { + oStyle.fontSize = parseUnit(fontSize); + } + if (fontFamily) { + oStyle.fontFamily = fontFamily; + } + if (lineHeight) { + oStyle.lineHeight = lineHeight === 'normal' ? 1 : lineHeight; + } + }, + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition element + */ + getGradientDefs: function(doc) { + var tagArray = [ + 'linearGradient', + 'radialGradient', + 'svg:linearGradient', + 'svg:radialGradient'], + elList = _getMultipleNodes(doc, tagArray), + el, j = 0, gradientDefs = { }; + j = elList.length; + while (j--) { + el = elList[j]; + if (el.getAttribute('xlink:href')) { + recursivelyParseGradientsXlink(doc, el); + } + gradientDefs[el.getAttribute('id')] = el; + } + return gradientDefs; + }, + + /** + * Returns an object of attributes' name/value, given element and an array of attribute names; + * Parses parent "g" nodes recursively upwards. + * @static + * @memberOf fabric + * @param {DOMElement} element Element to parse + * @param {Array} attributes Array of attributes to parse + * @return {Object} object containing parsed attributes' names/values + */ + parseAttributes: function(element, attributes, svgUid) { + + if (!element) { + return; + } + + var value, + parentAttributes = { }, + fontSize, parentFontSize; + + if (typeof svgUid === 'undefined') { + svgUid = element.getAttribute('svgUid'); + } + // if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards + if (element.parentNode && fabric.svgValidParentsRegEx.test(element.parentNode.nodeName)) { + parentAttributes = fabric.parseAttributes(element.parentNode, attributes, svgUid); + } + + var ownAttributes = attributes.reduce(function(memo, attr) { + value = element.getAttribute(attr); + if (value) { // eslint-disable-line + memo[attr] = value; + } + return memo; + }, { }); + // add values parsed from style, which take precedence over attributes + // (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes) + var cssAttrs = extend( + getGlobalStylesForElement(element, svgUid), + fabric.parseStyleAttribute(element) + ); + ownAttributes = extend( + ownAttributes, + cssAttrs + ); + if (cssAttrs[cPath]) { + element.setAttribute(cPath, cssAttrs[cPath]); + } + fontSize = parentFontSize = parentAttributes.fontSize || fabric.Text.DEFAULT_SVG_FONT_SIZE; + if (ownAttributes[fSize]) { + // looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers. + ownAttributes[fSize] = fontSize = parseUnit(ownAttributes[fSize], parentFontSize); + } + + var normalizedAttr, normalizedValue, normalizedStyle = {}; + for (var attr in ownAttributes) { + normalizedAttr = normalizeAttr(attr); + normalizedValue = normalizeValue(normalizedAttr, ownAttributes[attr], parentAttributes, fontSize); + normalizedStyle[normalizedAttr] = normalizedValue; + } + if (normalizedStyle && normalizedStyle.font) { + fabric.parseFontDeclaration(normalizedStyle.font, normalizedStyle); + } + var mergedAttrs = extend(parentAttributes, normalizedStyle); + return fabric.svgValidParentsRegEx.test(element.nodeName) ? mergedAttrs : _setStrokeFillOpacity(mergedAttrs); + }, + + /** + * Transforms an array of svg elements to corresponding fabric.* instances + * @static + * @memberOf fabric + * @param {Array} elements Array of elements to parse + * @param {Function} callback Being passed an array of fabric instances (transformed from SVG elements) + * @param {Object} [options] Options object + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + */ + parseElements: function(elements, callback, options, reviver, parsingOptions) { + new fabric.ElementsParser(elements, callback, options, reviver, parsingOptions).parse(); + }, + + /** + * Parses "style" attribute, retuning an object with values + * @static + * @memberOf fabric + * @param {SVGElement} element Element to parse + * @return {Object} Objects with values parsed from style attribute of an element + */ + parseStyleAttribute: function(element) { + var oStyle = { }, + style = element.getAttribute('style'); + + if (!style) { + return oStyle; + } + + if (typeof style === 'string') { + parseStyleString(style, oStyle); + } + else { + parseStyleObject(style, oStyle); + } + + return oStyle; + }, + + /** + * Parses "points" attribute, returning an array of values + * @static + * @memberOf fabric + * @param {String} points points attribute string + * @return {Array} array of points + */ + parsePointsAttribute: function(points) { + + // points attribute is required and must not be empty + if (!points) { + return null; + } + + // replace commas with whitespace and remove bookending whitespace + points = points.replace(/,/g, ' ').trim(); + + points = points.split(/\s+/); + var parsedPoints = [], i, len; + + for (i = 0, len = points.length; i < len; i += 2) { + parsedPoints.push({ + x: parseFloat(points[i]), + y: parseFloat(points[i + 1]) + }); + } + + // odd number of points is an error + // if (parsedPoints.length % 2 !== 0) { + // return null; + // } + + return parsedPoints; + }, + + /** + * Returns CSS rules for a given SVG document + * @static + * @function + * @memberOf fabric + * @param {SVGDocument} doc SVG document to parse + * @return {Object} CSS rules of this document + */ + getCSSRules: function(doc) { + var styles = doc.getElementsByTagName('style'), i, len, + allRules = { }, rules; + + // very crude parsing of style contents + for (i = 0, len = styles.length; i < len; i++) { + var styleContents = styles[i].textContent; + + // remove comments + styleContents = styleContents.replace(/\/\*[\s\S]*?\*\//g, ''); + if (styleContents.trim() === '') { + continue; + } + rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g); + rules = rules.map(function(rule) { return rule.trim(); }); + // eslint-disable-next-line no-loop-func + rules.forEach(function(rule) { + + var match = rule.match(/([\s\S]*?)\s*\{([^}]*)\}/), + ruleObj = { }, declaration = match[2].trim(), + propertyValuePairs = declaration.replace(/;$/, '').split(/\s*;\s*/); + + for (i = 0, len = propertyValuePairs.length; i < len; i++) { + var pair = propertyValuePairs[i].split(/\s*:\s*/), + property = pair[0], + value = pair[1]; + ruleObj[property] = value; + } + rule = match[1]; + rule.split(',').forEach(function(_rule) { + _rule = _rule.replace(/^svg/i, '').trim(); + if (_rule === '') { + return; + } + if (allRules[_rule]) { + fabric.util.object.extend(allRules[_rule], ruleObj); + } + else { + allRules[_rule] = fabric.util.object.clone(ruleObj); + } + }); + }); + } + return allRules; + }, + + /** + * Takes url corresponding to an SVG document, and parses it into a set of fabric objects. + * Note that SVG is fetched via XMLHttpRequest, so it needs to conform to SOP (Same Origin Policy) + * @memberOf fabric + * @param {String} url + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources + */ + loadSVGFromURL: function(url, callback, reviver, options) { + + url = url.replace(/^\n\s*/, '').trim(); + new fabric.util.request(url, { + method: 'get', + onComplete: onComplete + }); + + function onComplete(r) { + + var xml = r.responseXML; + if (!xml || !xml.documentElement) { + callback && callback(null); + return false; + } + + fabric.parseSVGDocument(xml.documentElement, function (results, _options, elements, allElements) { + callback && callback(results, _options, elements, allElements); + }, reviver, options); + } + }, + + /** + * Takes string corresponding to an SVG document, and parses it into a set of fabric objects + * @memberOf fabric + * @param {String} string + * @param {Function} callback + * @param {Function} [reviver] Method for further parsing of SVG elements, called after each fabric object created. + * @param {Object} [options] Object containing options for parsing + * @param {String} [options.crossOrigin] crossOrigin crossOrigin setting to use for external resources + */ + loadSVGFromString: function(string, callback, reviver, options) { + var parser = new fabric.window.DOMParser(), + doc = parser.parseFromString(string.trim(), 'text/xml'); + fabric.parseSVGDocument(doc.documentElement, function (results, _options, elements, allElements) { + callback(results, _options, elements, allElements); + }, reviver, options); + } + }); + +})(typeof exports !== 'undefined' ? exports : this); + + +fabric.ElementsParser = function(elements, callback, options, reviver, parsingOptions, doc) { + this.elements = elements; + this.callback = callback; + this.options = options; + this.reviver = reviver; + this.svgUid = (options && options.svgUid) || 0; + this.parsingOptions = parsingOptions; + this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g; + this.doc = doc; +}; + +(function(proto) { + proto.parse = function() { + this.instances = new Array(this.elements.length); + this.numElements = this.elements.length; + this.createObjects(); + }; + + proto.createObjects = function() { + var _this = this; + this.elements.forEach(function(element, i) { + element.setAttribute('svgUid', _this.svgUid); + _this.createObject(element, i); + }); + }; + + proto.findTag = function(el) { + return fabric[fabric.util.string.capitalize(el.tagName.replace('svg:', ''))]; + }; + + proto.createObject = function(el, index) { + var klass = this.findTag(el); + if (klass && klass.fromElement) { + try { + klass.fromElement(el, this.createCallback(index, el), this.options); + } + catch (err) { + fabric.log(err); + } + } + else { + this.checkIfDone(); + } + }; + + proto.createCallback = function(index, el) { + var _this = this; + return function(obj) { + var _options; + _this.resolveGradient(obj, el, 'fill'); + _this.resolveGradient(obj, el, 'stroke'); + if (obj instanceof fabric.Image && obj._originalElement) { + _options = obj.parsePreserveAspectRatioAttribute(el); + } + obj._removeTransformMatrix(_options); + _this.resolveClipPath(obj, el); + _this.reviver && _this.reviver(el, obj); + _this.instances[index] = obj; + _this.checkIfDone(); + }; + }; + + proto.extractPropertyDefinition = function(obj, property, storage) { + var value = obj[property], regex = this.regexUrl; + if (!regex.test(value)) { + return; + } + regex.lastIndex = 0; + var id = regex.exec(value)[1]; + regex.lastIndex = 0; + return fabric[storage][this.svgUid][id]; + }; + + proto.resolveGradient = function(obj, el, property) { + var gradientDef = this.extractPropertyDefinition(obj, property, 'gradientDefs'); + if (gradientDef) { + var opacityAttr = el.getAttribute(property + '-opacity'); + var gradient = fabric.Gradient.fromElement(gradientDef, obj, opacityAttr, this.options); + obj.set(property, gradient); + } + }; + + proto.createClipPathCallback = function(obj, container) { + return function(_newObj) { + _newObj._removeTransformMatrix(); + _newObj.fillRule = _newObj.clipRule; + container.push(_newObj); + }; + }; + + proto.resolveClipPath = function(obj, usingElement) { + var clipPath = this.extractPropertyDefinition(obj, 'clipPath', 'clipPaths'), + element, klass, objTransformInv, container, gTransform, options; + if (clipPath) { + container = []; + objTransformInv = fabric.util.invertTransform(obj.calcTransformMatrix()); + // move the clipPath tag as sibling to the real element that is using it + var clipPathTag = clipPath[0].parentNode; + var clipPathOwner = usingElement; + while (clipPathOwner.parentNode && clipPathOwner.getAttribute('clip-path') !== obj.clipPath) { + clipPathOwner = clipPathOwner.parentNode; + } + clipPathOwner.parentNode.appendChild(clipPathTag); + for (var i = 0; i < clipPath.length; i++) { + element = clipPath[i]; + klass = this.findTag(element); + klass.fromElement( + element, + this.createClipPathCallback(obj, container), + this.options + ); + } + if (container.length === 1) { + clipPath = container[0]; + } + else { + clipPath = new fabric.Group(container); + } + gTransform = fabric.util.multiplyTransformMatrices( + objTransformInv, + clipPath.calcTransformMatrix() + ); + if (clipPath.clipPath) { + this.resolveClipPath(clipPath, clipPathOwner); + } + var options = fabric.util.qrDecompose(gTransform); + clipPath.flipX = false; + clipPath.flipY = false; + clipPath.set('scaleX', options.scaleX); + clipPath.set('scaleY', options.scaleY); + clipPath.angle = options.angle; + clipPath.skewX = options.skewX; + clipPath.skewY = 0; + clipPath.setPositionByOrigin({ x: options.translateX, y: options.translateY }, 'center', 'center'); + obj.clipPath = clipPath; + } + else { + // if clip-path does not resolve to any element, delete the property. + delete obj.clipPath; + } + }; + + proto.checkIfDone = function() { + if (--this.numElements === 0) { + this.instances = this.instances.filter(function(el) { + // eslint-disable-next-line no-eq-null, eqeqeq + return el != null; + }); + this.callback(this.instances, this.elements); + } + }; +})(fabric.ElementsParser.prototype); + + +(function(global) { + + 'use strict'; + + /* Adaptation of work of Kevin Lindsey (kevin@kevlindev.com) */ + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Point) { + fabric.warn('fabric.Point is already defined'); + return; + } + + fabric.Point = Point; + + /** + * Point class + * @class fabric.Point + * @memberOf fabric + * @constructor + * @param {Number} x + * @param {Number} y + * @return {fabric.Point} thisArg + */ + function Point(x, y) { + this.x = x; + this.y = y; + } + + Point.prototype = /** @lends fabric.Point.prototype */ { + + type: 'point', + + constructor: Point, + + /** + * Adds another point to this one and returns another one + * @param {fabric.Point} that + * @return {fabric.Point} new Point instance with added values + */ + add: function (that) { + return new Point(this.x + that.x, this.y + that.y); + }, + + /** + * Adds another point to this one + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + * @chainable + */ + addEquals: function (that) { + this.x += that.x; + this.y += that.y; + return this; + }, + + /** + * Adds value to this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} new Point with added value + */ + scalarAdd: function (scalar) { + return new Point(this.x + scalar, this.y + scalar); + }, + + /** + * Adds value to this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + scalarAddEquals: function (scalar) { + this.x += scalar; + this.y += scalar; + return this; + }, + + /** + * Subtracts another point from this point and returns a new one + * @param {fabric.Point} that + * @return {fabric.Point} new Point object with subtracted values + */ + subtract: function (that) { + return new Point(this.x - that.x, this.y - that.y); + }, + + /** + * Subtracts another point from this point + * @param {fabric.Point} that + * @return {fabric.Point} thisArg + * @chainable + */ + subtractEquals: function (that) { + this.x -= that.x; + this.y -= that.y; + return this; + }, + + /** + * Subtracts value from this point and returns a new one + * @param {Number} scalar + * @return {fabric.Point} + */ + scalarSubtract: function (scalar) { + return new Point(this.x - scalar, this.y - scalar); + }, + + /** + * Subtracts value from this point + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + scalarSubtractEquals: function (scalar) { + this.x -= scalar; + this.y -= scalar; + return this; + }, + + /** + * Multiplies this point by a value and returns a new one + * TODO: rename in scalarMultiply in 2.0 + * @param {Number} scalar + * @return {fabric.Point} + */ + multiply: function (scalar) { + return new Point(this.x * scalar, this.y * scalar); + }, + + /** + * Multiplies this point by a value + * TODO: rename in scalarMultiplyEquals in 2.0 + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + multiplyEquals: function (scalar) { + this.x *= scalar; + this.y *= scalar; + return this; + }, + + /** + * Divides this point by a value and returns a new one + * TODO: rename in scalarDivide in 2.0 + * @param {Number} scalar + * @return {fabric.Point} + */ + divide: function (scalar) { + return new Point(this.x / scalar, this.y / scalar); + }, + + /** + * Divides this point by a value + * TODO: rename in scalarDivideEquals in 2.0 + * @param {Number} scalar + * @return {fabric.Point} thisArg + * @chainable + */ + divideEquals: function (scalar) { + this.x /= scalar; + this.y /= scalar; + return this; + }, + + /** + * Returns true if this point is equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + eq: function (that) { + return (this.x === that.x && this.y === that.y); + }, + + /** + * Returns true if this point is less than another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lt: function (that) { + return (this.x < that.x && this.y < that.y); + }, + + /** + * Returns true if this point is less than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + lte: function (that) { + return (this.x <= that.x && this.y <= that.y); + }, + + /** + + * Returns true if this point is greater another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gt: function (that) { + return (this.x > that.x && this.y > that.y); + }, + + /** + * Returns true if this point is greater than or equal to another one + * @param {fabric.Point} that + * @return {Boolean} + */ + gte: function (that) { + return (this.x >= that.x && this.y >= that.y); + }, + + /** + * Returns new point which is the result of linear interpolation with this one and another one + * @param {fabric.Point} that + * @param {Number} t , position of interpolation, between 0 and 1 default 0.5 + * @return {fabric.Point} + */ + lerp: function (that, t) { + if (typeof t === 'undefined') { + t = 0.5; + } + t = Math.max(Math.min(1, t), 0); + return new Point(this.x + (that.x - this.x) * t, this.y + (that.y - this.y) * t); + }, + + /** + * Returns distance from this point and another one + * @param {fabric.Point} that + * @return {Number} + */ + distanceFrom: function (that) { + var dx = this.x - that.x, + dy = this.y - that.y; + return Math.sqrt(dx * dx + dy * dy); + }, + + /** + * Returns the point between this point and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + midPointFrom: function (that) { + return this.lerp(that); + }, + + /** + * Returns a new point which is the min of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + min: function (that) { + return new Point(Math.min(this.x, that.x), Math.min(this.y, that.y)); + }, + + /** + * Returns a new point which is the max of this and another one + * @param {fabric.Point} that + * @return {fabric.Point} + */ + max: function (that) { + return new Point(Math.max(this.x, that.x), Math.max(this.y, that.y)); + }, + + /** + * Returns string representation of this point + * @return {String} + */ + toString: function () { + return this.x + ',' + this.y; + }, + + /** + * Sets x/y of this point + * @param {Number} x + * @param {Number} y + * @chainable + */ + setXY: function (x, y) { + this.x = x; + this.y = y; + return this; + }, + + /** + * Sets x of this point + * @param {Number} x + * @chainable + */ + setX: function (x) { + this.x = x; + return this; + }, + + /** + * Sets y of this point + * @param {Number} y + * @chainable + */ + setY: function (y) { + this.y = y; + return this; + }, + + /** + * Sets x/y of this point from another point + * @param {fabric.Point} that + * @chainable + */ + setFromPoint: function (that) { + this.x = that.x; + this.y = that.y; + return this; + }, + + /** + * Swaps x/y of this point and another point + * @param {fabric.Point} that + */ + swap: function (that) { + var x = this.x, + y = this.y; + this.x = that.x; + this.y = that.y; + that.x = x; + that.y = y; + }, + + /** + * return a cloned instance of the point + * @return {fabric.Point} + */ + clone: function () { + return new Point(this.x, this.y); + } + }; + +})(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 */ { + + constructor: Intersection, + + /** + * Appends a point to intersection + * @param {fabric.Point} point + * @return {fabric.Intersection} thisArg + * @chainable + */ + appendPoint: function (point) { + this.points.push(point); + return this; + }, + + /** + * Appends points to intersection + * @param {Array} points + * @return {fabric.Intersection} thisArg + * @chainable + */ + appendPoints: function (points) { + this.points = this.points.concat(points); + return this; + } + }; + + /** + * Checks if one line intersects another + * TODO: rename in intersectSegmentSegment + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {fabric.Point} b1 + * @param {fabric.Point} b2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLineLine = function (a1, a2, b1, b2) { + var result, + uaT = (b2.x - b1.x) * (a1.y - b1.y) - (b2.y - b1.y) * (a1.x - b1.x), + ubT = (a2.x - a1.x) * (a1.y - b1.y) - (a2.y - a1.y) * (a1.x - b1.x), + uB = (b2.y - b1.y) * (a2.x - a1.x) - (b2.x - b1.x) * (a2.y - a1.y); + if (uB !== 0) { + var ua = uaT / uB, + ub = ubT / uB; + if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) { + result = new Intersection('Intersection'); + result.appendPoint(new fabric.Point(a1.x + ua * (a2.x - a1.x), a1.y + ua * (a2.y - a1.y))); + } + else { + result = new Intersection(); + } + } + else { + if (uaT === 0 || ubT === 0) { + result = new Intersection('Coincident'); + } + else { + result = new Intersection('Parallel'); + } + } + return result; + }; + + /** + * Checks if line intersects polygon + * TODO: rename in intersectSegmentPolygon + * fix detection of coincident + * @static + * @param {fabric.Point} a1 + * @param {fabric.Point} a2 + * @param {Array} points + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectLinePolygon = function(a1, a2, points) { + var result = new Intersection(), + length = points.length, + b1, b2, inter, i; + + for (i = 0; i < length; i++) { + b1 = points[i]; + b2 = points[(i + 1) % length]; + inter = Intersection.intersectLineLine(a1, a2, b1, b2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + + /** + * Checks if polygon intersects another polygon + * @static + * @param {Array} points1 + * @param {Array} points2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonPolygon = function (points1, points2) { + var result = new Intersection(), + length = points1.length, i; + + for (i = 0; i < length; i++) { + var a1 = points1[i], + a2 = points1[(i + 1) % length], + inter = Intersection.intersectLinePolygon(a1, a2, points2); + + result.appendPoints(inter.points); + } + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + + /** + * Checks if polygon intersects rectangle + * @static + * @param {Array} points + * @param {fabric.Point} r1 + * @param {fabric.Point} r2 + * @return {fabric.Intersection} + */ + fabric.Intersection.intersectPolygonRectangle = function (points, r1, r2) { + var min = r1.min(r2), + max = r1.max(r2), + topRight = new fabric.Point(max.x, min.y), + bottomLeft = new fabric.Point(min.x, max.y), + inter1 = Intersection.intersectLinePolygon(min, topRight, points), + inter2 = Intersection.intersectLinePolygon(topRight, max, points), + inter3 = Intersection.intersectLinePolygon(max, bottomLeft, points), + inter4 = Intersection.intersectLinePolygon(bottomLeft, min, points), + result = new Intersection(); + + result.appendPoints(inter1.points); + result.appendPoints(inter2.points); + result.appendPoints(inter3.points); + result.appendPoints(inter4.points); + + if (result.points.length > 0) { + result.status = 'Intersection'; + } + return result; + }; + +})(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) or hsl format or from known color list + * @return {fabric.Color} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2/#colors} + */ + function Color(color) { + if (!color) { + this.setSource([0, 0, 0, 1]); + } + else { + this._tryParsingColor(color); + } + } + + fabric.Color = Color; + + fabric.Color.prototype = /** @lends fabric.Color.prototype */ { + + /** + * @private + * @param {String|Array} color Color value to parse + */ + _tryParsingColor: function(color) { + var source; + + if (color in Color.colorNameMap) { + color = Color.colorNameMap[color]; + } + + if (color === 'transparent') { + source = [255, 255, 255, 0]; + } + + if (!source) { + source = Color.sourceFromHex(color); + } + if (!source) { + source = Color.sourceFromRgb(color); + } + if (!source) { + source = Color.sourceFromHsl(color); + } + if (!source) { + //if color is not recognize let's make black as canvas does + source = [0, 0, 0, 1]; + } + if (source) { + this.setSource(source); + } + }, + + /** + * Adapted from https://github.com/mjijackson + * @private + * @param {Number} r Red color value + * @param {Number} g Green color value + * @param {Number} b Blue color value + * @return {Array} Hsl color + */ + _rgbToHsl: function(r, g, b) { + r /= 255; g /= 255; b /= 255; + + var h, s, l, + max = fabric.util.array.max([r, g, b]), + min = fabric.util.array.min([r, g, b]); + + l = (max + min) / 2; + + if (max === min) { + h = s = 0; // achromatic + } + else { + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch (max) { + case r: + h = (g - b) / d + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / d + 2; + break; + case b: + h = (r - g) / d + 4; + break; + } + h /= 6; + } + + return [ + Math.round(h * 360), + Math.round(s * 100), + Math.round(l * 100) + ]; + }, + + /** + * Returns source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @return {Array} + */ + getSource: function() { + return this._source; + }, + + /** + * Sets source of this color (where source is an array representation; ex: [200, 200, 100, 1]) + * @param {Array} source + */ + setSource: function(source) { + this._source = source; + }, + + /** + * Returns color representation in RGB format + * @return {String} ex: rgb(0-255,0-255,0-255) + */ + toRgb: function() { + var source = this.getSource(); + return 'rgb(' + source[0] + ',' + source[1] + ',' + source[2] + ')'; + }, + + /** + * Returns color representation in RGBA format + * @return {String} ex: rgba(0-255,0-255,0-255,0-1) + */ + toRgba: function() { + var source = this.getSource(); + return 'rgba(' + source[0] + ',' + source[1] + ',' + source[2] + ',' + source[3] + ')'; + }, + + /** + * Returns color representation in HSL format + * @return {String} ex: hsl(0-360,0%-100%,0%-100%) + */ + toHsl: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsl(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%)'; + }, + + /** + * Returns color representation in HSLA format + * @return {String} ex: hsla(0-360,0%-100%,0%-100%,0-1) + */ + toHsla: function() { + var source = this.getSource(), + hsl = this._rgbToHsl(source[0], source[1], source[2]); + + return 'hsla(' + hsl[0] + ',' + hsl[1] + '%,' + hsl[2] + '%,' + source[3] + ')'; + }, + + /** + * Returns color representation in HEX format + * @return {String} ex: FF5555 + */ + toHex: function() { + var source = this.getSource(), r, g, b; + + r = source[0].toString(16); + r = (r.length === 1) ? ('0' + r) : r; + + g = source[1].toString(16); + g = (g.length === 1) ? ('0' + g) : g; + + b = source[2].toString(16); + b = (b.length === 1) ? ('0' + b) : b; + + return r.toUpperCase() + g.toUpperCase() + b.toUpperCase(); + }, + + /** + * Returns color representation in HEXA format + * @return {String} ex: FF5555CC + */ + toHexa: function() { + var source = this.getSource(), a; + + a = Math.round(source[3] * 255); + a = a.toString(16); + a = (a.length === 1) ? ('0' + a) : a; + + return this.toHex() + a.toUpperCase(); + }, + + /** + * Gets value of alpha channel for this color + * @return {Number} 0-1 + */ + getAlpha: function() { + return this.getSource()[3]; + }, + + /** + * Sets value of alpha channel for this color + * @param {Number} alpha Alpha value 0-1 + * @return {fabric.Color} thisArg + */ + setAlpha: function(alpha) { + var source = this.getSource(); + source[3] = alpha; + this.setSource(source); + return this; + }, + + /** + * Transforms color to its grayscale representation + * @return {fabric.Color} thisArg + */ + toGrayscale: function() { + var source = this.getSource(), + average = parseInt((source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), 10), + currentAlpha = source[3]; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Transforms color to its black and white representation + * @param {Number} threshold + * @return {fabric.Color} thisArg + */ + toBlackWhite: function(threshold) { + var source = this.getSource(), + average = (source[0] * 0.3 + source[1] * 0.59 + source[2] * 0.11).toFixed(0), + currentAlpha = source[3]; + + threshold = threshold || 127; + + average = (Number(average) < Number(threshold)) ? 0 : 255; + this.setSource([average, average, average, currentAlpha]); + return this; + }, + + /** + * Overlays color with another color + * @param {String|fabric.Color} otherColor + * @return {fabric.Color} thisArg + */ + overlayWith: function(otherColor) { + if (!(otherColor instanceof Color)) { + otherColor = new Color(otherColor); + } + + var result = [], + alpha = this.getAlpha(), + otherAlpha = 0.5, + source = this.getSource(), + otherSource = otherColor.getSource(), i; + + for (i = 0; i < 3; i++) { + result.push(Math.round((source[i] * (1 - otherAlpha)) + (otherSource[i] * otherAlpha))); + } + + result[3] = alpha; + this.setSource(result); + return this; + } + }; + + /** + * Regex matching color in RGB or RGBA formats (ex: rgb(0, 0, 0), rgba(255, 100, 10, 0.5), rgba( 255 , 100 , 10 , 0.5 ), rgb(1,1,1), rgba(100%, 60%, 10%, 0.5)) + * @static + * @field + * @memberOf fabric.Color + */ + // eslint-disable-next-line max-len + fabric.Color.reRGBa = /^rgba?\(\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*,\s*(\d{1,3}(?:\.\d+)?\%?)\s*(?:\s*,\s*((?:\d*\.?\d+)?)\s*)?\)$/i; + + /** + * Regex matching color in HSL or HSLA formats (ex: hsl(200, 80%, 10%), hsla(300, 50%, 80%, 0.5), hsla( 300 , 50% , 80% , 0.5 )) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHSLa = /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}\%)\s*,\s*(\d{1,3}\%)\s*(?:\s*,\s*(\d+(?:\.\d+)?)\s*)?\)$/i; + + /** + * Regex matching color in HEX format (ex: #FF5544CC, #FF5555, 010155, aff) + * @static + * @field + * @memberOf fabric.Color + */ + fabric.Color.reHex = /^#?([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})$/i; + + /** + * Map of the 148 color names with HEX code + * @static + * @field + * @memberOf fabric.Color + * @see: https://www.w3.org/TR/css3-color/#svg-color + */ + fabric.Color.colorNameMap = { + aliceblue: '#F0F8FF', + antiquewhite: '#FAEBD7', + aqua: '#00FFFF', + aquamarine: '#7FFFD4', + azure: '#F0FFFF', + beige: '#F5F5DC', + bisque: '#FFE4C4', + black: '#000000', + blanchedalmond: '#FFEBCD', + blue: '#0000FF', + blueviolet: '#8A2BE2', + brown: '#A52A2A', + burlywood: '#DEB887', + cadetblue: '#5F9EA0', + chartreuse: '#7FFF00', + chocolate: '#D2691E', + coral: '#FF7F50', + cornflowerblue: '#6495ED', + cornsilk: '#FFF8DC', + crimson: '#DC143C', + cyan: '#00FFFF', + darkblue: '#00008B', + darkcyan: '#008B8B', + darkgoldenrod: '#B8860B', + darkgray: '#A9A9A9', + darkgrey: '#A9A9A9', + darkgreen: '#006400', + darkkhaki: '#BDB76B', + darkmagenta: '#8B008B', + darkolivegreen: '#556B2F', + darkorange: '#FF8C00', + darkorchid: '#9932CC', + darkred: '#8B0000', + darksalmon: '#E9967A', + darkseagreen: '#8FBC8F', + darkslateblue: '#483D8B', + darkslategray: '#2F4F4F', + darkslategrey: '#2F4F4F', + darkturquoise: '#00CED1', + darkviolet: '#9400D3', + deeppink: '#FF1493', + deepskyblue: '#00BFFF', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1E90FF', + firebrick: '#B22222', + floralwhite: '#FFFAF0', + forestgreen: '#228B22', + fuchsia: '#FF00FF', + gainsboro: '#DCDCDC', + ghostwhite: '#F8F8FF', + gold: '#FFD700', + goldenrod: '#DAA520', + gray: '#808080', + grey: '#808080', + green: '#008000', + greenyellow: '#ADFF2F', + honeydew: '#F0FFF0', + hotpink: '#FF69B4', + indianred: '#CD5C5C', + indigo: '#4B0082', + ivory: '#FFFFF0', + khaki: '#F0E68C', + lavender: '#E6E6FA', + lavenderblush: '#FFF0F5', + lawngreen: '#7CFC00', + lemonchiffon: '#FFFACD', + lightblue: '#ADD8E6', + lightcoral: '#F08080', + lightcyan: '#E0FFFF', + lightgoldenrodyellow: '#FAFAD2', + lightgray: '#D3D3D3', + lightgrey: '#D3D3D3', + lightgreen: '#90EE90', + lightpink: '#FFB6C1', + lightsalmon: '#FFA07A', + lightseagreen: '#20B2AA', + lightskyblue: '#87CEFA', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#B0C4DE', + lightyellow: '#FFFFE0', + lime: '#00FF00', + limegreen: '#32CD32', + linen: '#FAF0E6', + magenta: '#FF00FF', + maroon: '#800000', + mediumaquamarine: '#66CDAA', + mediumblue: '#0000CD', + mediumorchid: '#BA55D3', + mediumpurple: '#9370DB', + mediumseagreen: '#3CB371', + mediumslateblue: '#7B68EE', + mediumspringgreen: '#00FA9A', + mediumturquoise: '#48D1CC', + mediumvioletred: '#C71585', + midnightblue: '#191970', + mintcream: '#F5FFFA', + mistyrose: '#FFE4E1', + moccasin: '#FFE4B5', + navajowhite: '#FFDEAD', + navy: '#000080', + oldlace: '#FDF5E6', + olive: '#808000', + olivedrab: '#6B8E23', + orange: '#FFA500', + orangered: '#FF4500', + orchid: '#DA70D6', + palegoldenrod: '#EEE8AA', + palegreen: '#98FB98', + paleturquoise: '#AFEEEE', + palevioletred: '#DB7093', + papayawhip: '#FFEFD5', + peachpuff: '#FFDAB9', + peru: '#CD853F', + pink: '#FFC0CB', + plum: '#DDA0DD', + powderblue: '#B0E0E6', + purple: '#800080', + rebeccapurple: '#663399', + red: '#FF0000', + rosybrown: '#BC8F8F', + royalblue: '#4169E1', + saddlebrown: '#8B4513', + salmon: '#FA8072', + sandybrown: '#F4A460', + seagreen: '#2E8B57', + seashell: '#FFF5EE', + sienna: '#A0522D', + silver: '#C0C0C0', + skyblue: '#87CEEB', + slateblue: '#6A5ACD', + slategray: '#708090', + slategrey: '#708090', + snow: '#FFFAFA', + springgreen: '#00FF7F', + steelblue: '#4682B4', + tan: '#D2B48C', + teal: '#008080', + thistle: '#D8BFD8', + tomato: '#FF6347', + turquoise: '#40E0D0', + violet: '#EE82EE', + wheat: '#F5DEB3', + white: '#FFFFFF', + whitesmoke: '#F5F5F5', + yellow: '#FFFF00', + yellowgreen: '#9ACD32' + }; + + /** + * @private + * @param {Number} p + * @param {Number} q + * @param {Number} t + * @return {Number} + */ + function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; + } + + /** + * Returns new color object, when given a color in RGB format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255) + * @return {fabric.Color} + */ + fabric.Color.fromRgb = function(color) { + return Color.fromSource(Color.sourceFromRgb(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in RGB or RGBA format + * @memberOf fabric.Color + * @param {String} color Color value ex: rgb(0-255,0-255,0-255), rgb(0%-100%,0%-100%,0%-100%) + * @return {Array} source + */ + fabric.Color.sourceFromRgb = function(color) { + var match = color.match(Color.reRGBa); + if (match) { + var r = parseInt(match[1], 10) / (/%$/.test(match[1]) ? 100 : 1) * (/%$/.test(match[1]) ? 255 : 1), + g = parseInt(match[2], 10) / (/%$/.test(match[2]) ? 100 : 1) * (/%$/.test(match[2]) ? 255 : 1), + b = parseInt(match[3], 10) / (/%$/.test(match[3]) ? 100 : 1) * (/%$/.test(match[3]) ? 255 : 1); + + return [ + parseInt(r, 10), + parseInt(g, 10), + parseInt(b, 10), + match[4] ? parseFloat(match[4]) : 1 + ]; + } + }; + + /** + * Returns new color object, when given a color in RGBA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromRgba = Color.fromRgb; + + /** + * Returns new color object, when given a color in HSL format + * @param {String} color Color value ex: hsl(0-260,0%-100%,0%-100%) + * @memberOf fabric.Color + * @return {fabric.Color} + */ + fabric.Color.fromHsl = function(color) { + return Color.fromSource(Color.sourceFromHsl(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HSL or HSLA format. + * Adapted from https://github.com/mjijackson + * @memberOf fabric.Color + * @param {String} color Color value ex: hsl(0-360,0%-100%,0%-100%) or hsla(0-360,0%-100%,0%-100%, 0-1) + * @return {Array} source + * @see http://http://www.w3.org/TR/css3-color/#hsl-color + */ + fabric.Color.sourceFromHsl = function(color) { + var match = color.match(Color.reHSLa); + if (!match) { + return; + } + + var h = (((parseFloat(match[1]) % 360) + 360) % 360) / 360, + s = parseFloat(match[2]) / (/%$/.test(match[2]) ? 100 : 1), + l = parseFloat(match[3]) / (/%$/.test(match[3]) ? 100 : 1), + r, g, b; + + if (s === 0) { + r = g = b = l; + } + else { + var q = l <= 0.5 ? l * (s + 1) : l + s - l * s, + p = l * 2 - q; + + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [ + Math.round(r * 255), + Math.round(g * 255), + Math.round(b * 255), + match[4] ? parseFloat(match[4]) : 1 + ]; + }; + + /** + * Returns new color object, when given a color in HSLA format + * @static + * @function + * @memberOf fabric.Color + * @param {String} color + * @return {fabric.Color} + */ + fabric.Color.fromHsla = Color.fromHsl; + + /** + * Returns new color object, when given a color in HEX format + * @static + * @memberOf fabric.Color + * @param {String} color Color value ex: FF5555 + * @return {fabric.Color} + */ + fabric.Color.fromHex = function(color) { + return Color.fromSource(Color.sourceFromHex(color)); + }; + + /** + * Returns array representation (ex: [100, 100, 200, 1]) of a color that's in HEX format + * @static + * @memberOf fabric.Color + * @param {String} color ex: FF5555 or FF5544CC (RGBa) + * @return {Array} source + */ + fabric.Color.sourceFromHex = function(color) { + if (color.match(Color.reHex)) { + var value = color.slice(color.indexOf('#') + 1), + isShortNotation = (value.length === 3 || value.length === 4), + isRGBa = (value.length === 8 || value.length === 4), + r = isShortNotation ? (value.charAt(0) + value.charAt(0)) : value.substring(0, 2), + g = isShortNotation ? (value.charAt(1) + value.charAt(1)) : value.substring(2, 4), + b = isShortNotation ? (value.charAt(2) + value.charAt(2)) : value.substring(4, 6), + a = isRGBa ? (isShortNotation ? (value.charAt(3) + value.charAt(3)) : value.substring(6, 8)) : 'FF'; + + return [ + parseInt(r, 16), + parseInt(g, 16), + parseInt(b, 16), + parseFloat((parseInt(a, 16) / 255).toFixed(2)) + ]; + } + }; + + /** + * Returns new color object, when given color in array representation (ex: [200, 100, 100, 0.5]) + * @static + * @memberOf fabric.Color + * @param {Array} source + * @return {fabric.Color} + */ + fabric.Color.fromSource = function(source) { + var oColor = new Color(); + oColor.setSource(source); + return oColor; + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + scaleMap = ['e', 'se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'], + skewMap = ['ns', 'nesw', 'ew', 'nwse'], + controls = {}, + LEFT = 'left', TOP = 'top', RIGHT = 'right', BOTTOM = 'bottom', CENTER = 'center', + opposite = { + top: BOTTOM, + bottom: TOP, + left: RIGHT, + right: LEFT, + center: CENTER, + }, radiansToDegrees = fabric.util.radiansToDegrees, + sign = (Math.sign || function(x) { return ((x > 0) - (x < 0)) || +x; }); + + /** + * Combine control position and object angle to find the control direction compared + * to the object center. + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + * @param {fabric.Control} control the control class + * @return {Number} 0 - 7 a quadrant number + */ + function findCornerQuadrant(fabricObject, control) { + var cornerAngle = fabricObject.angle + radiansToDegrees(Math.atan2(control.y, control.x)) + 360; + return Math.round((cornerAngle % 360) / 45); + } + + function fireEvent(eventName, options) { + var target = options.transform.target, + canvas = target.canvas, + canvasOptions = fabric.util.object.clone(options); + canvasOptions.target = target; + canvas && canvas.fire('object:' + eventName, canvasOptions); + target.fire(eventName, options); + } + + /** + * Inspect event and fabricObject properties to understand if the scaling action + * @param {Event} eventData from the user action + * @param {fabric.Object} fabricObject the fabric object about to scale + * @return {Boolean} true if scale is proportional + */ + function scaleIsProportional(eventData, fabricObject) { + var canvas = fabricObject.canvas, uniScaleKey = canvas.uniScaleKey, + uniformIsToggled = eventData[uniScaleKey]; + return (canvas.uniformScaling && !uniformIsToggled) || + (!canvas.uniformScaling && uniformIsToggled); + } + + /** + * Checks if transform is centered + * @param {Object} transform transform data + * @return {Boolean} true if transform is centered + */ + function isTransformCentered(transform) { + return transform.originX === CENTER && transform.originY === CENTER; + } + + /** + * Inspect fabricObject to understand if the current scaling action is allowed + * @param {fabric.Object} fabricObject the fabric object about to scale + * @param {String} by 'x' or 'y' or '' + * @param {Boolean} scaleProportionally true if we are trying to scale proportionally + * @return {Boolean} true if scaling is not allowed at current conditions + */ + function scalingIsForbidden(fabricObject, by, scaleProportionally) { + var lockX = fabricObject.lockScalingX, lockY = fabricObject.lockScalingY; + if (lockX && lockY) { + return true; + } + if (!by && (lockX || lockY) && scaleProportionally) { + return true; + } + if (lockX && by === 'x') { + return true; + } + if (lockY && by === 'y') { + return true; + } + return false; + } + + /** + * return the correct cursor style for the scale action + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function scaleCursorStyleHandler(eventData, control, fabricObject) { + var notAllowed = 'not-allowed', + scaleProportionally = scaleIsProportional(eventData, fabricObject), + by = ''; + if (control.x !== 0 && control.y === 0) { + by = 'x'; + } + else if (control.x === 0 && control.y !== 0) { + by = 'y'; + } + if (scalingIsForbidden(fabricObject, by, scaleProportionally)) { + return notAllowed; + } + var n = findCornerQuadrant(fabricObject, control); + return scaleMap[n] + '-resize'; + } + + /** + * return the correct cursor style for the skew action + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function skewCursorStyleHandler(eventData, control, fabricObject) { + var notAllowed = 'not-allowed'; + if (control.x !== 0 && fabricObject.lockSkewingY) { + return notAllowed; + } + if (control.y !== 0 && fabricObject.lockSkewingX) { + return notAllowed; + } + var n = findCornerQuadrant(fabricObject, control) % 4; + return skewMap[n] + '-resize'; + } + + /** + * Combine skew and scale style handlers to cover fabric standard use case + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function scaleSkewCursorStyleHandler(eventData, control, fabricObject) { + if (eventData[fabricObject.canvas.altActionKey]) { + return controls.skewCursorStyleHandler(eventData, control, fabricObject); + } + return controls.scaleCursorStyleHandler(eventData, control, fabricObject); + } + + /** + * Inspect event, control and fabricObject to return the correct action name + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} an action name + */ + function scaleOrSkewActionName(eventData, control, fabricObject) { + var isAlternative = eventData[fabricObject.canvas.altActionKey]; + if (control.x === 0) { + // then is scaleY or skewX + return isAlternative ? 'skewX' : 'scaleY'; + } + if (control.y === 0) { + // then is scaleY or skewX + return isAlternative ? 'skewY' : 'scaleX'; + } + } + + /** + * Find the correct style for the control that is used for rotation. + * this function is very simple and it just take care of not-allowed or standard cursor + * @param {Event} eventData the javascript event that is causing the scale + * @param {fabric.Control} control the control that is interested in the action + * @param {fabric.Object} fabricObject the fabric object that is interested in the action + * @return {String} a valid css string for the cursor + */ + function rotationStyleHandler(eventData, control, fabricObject) { + if (fabricObject.lockRotation) { + return 'not-allowed'; + } + return control.cursorStyle; + } + + function commonEventInfo(eventData, transform, x, y) { + return { + e: eventData, + transform: transform, + pointer: { + x: x, + y: y, + } + }; + } + + /** + * Wrap an action handler with saving/restoring object position on the transform. + * this is the code that permits to objects to keep their position while transforming. + * @param {Function} actionHandler the function to wrap + * @return {Function} a function with an action handler signature + */ + function wrapWithFixedAnchor(actionHandler) { + return function(eventData, transform, x, y) { + var target = transform.target, centerPoint = target.getCenterPoint(), + constraint = target.translateToOriginPoint(centerPoint, transform.originX, transform.originY), + actionPerformed = actionHandler(eventData, transform, x, y); + target.setPositionByOrigin(constraint, transform.originX, transform.originY); + return actionPerformed; + }; + } + + /** + * Wrap an action handler with firing an event if the action is performed + * @param {Function} actionHandler the function to wrap + * @return {Function} a function with an action handler signature + */ + function wrapWithFireEvent(eventName, actionHandler) { + return function(eventData, transform, x, y) { + var actionPerformed = actionHandler(eventData, transform, x, y); + if (actionPerformed) { + fireEvent(eventName, commonEventInfo(eventData, transform, x, y)); + } + return actionPerformed; + }; + } + + /** + * Transforms a point described by x and y in a distance from the top left corner of the object + * bounding box. + * @param {Object} transform + * @param {String} originX + * @param {String} originY + * @param {number} x + * @param {number} y + * @return {Fabric.Point} the normalized point + */ + function getLocalPoint(transform, originX, originY, x, y) { + var target = transform.target, + control = target.controls[transform.corner], + zoom = target.canvas.getZoom(), + padding = target.padding / zoom, + localPoint = target.toLocalPoint(new fabric.Point(x, y), originX, originY); + if (localPoint.x >= padding) { + localPoint.x -= padding; + } + if (localPoint.x <= -padding) { + localPoint.x += padding; + } + if (localPoint.y >= padding) { + localPoint.y -= padding; + } + if (localPoint.y <= padding) { + localPoint.y += padding; + } + localPoint.x -= control.offsetX; + localPoint.y -= control.offsetY; + return localPoint; + } + + /** + * Detect if the fabric object is flipped on one side. + * @param {fabric.Object} target + * @return {Boolean} true if one flip, but not two. + */ + function targetHasOneFlip(target) { + return target.flipX !== target.flipY; + } + + /** + * Utility function to compensate the scale factor when skew is applied on both axes + * @private + */ + function compensateScaleForSkew(target, oppositeSkew, scaleToCompensate, axis, reference) { + if (target[oppositeSkew] !== 0) { + var newDim = target._getTransformedDimensions()[axis]; + var newValue = reference / newDim * target[scaleToCompensate]; + target.set(scaleToCompensate, newValue); + } + } + + /** + * Action handler for skewing on the X axis + * @private + */ + function skewObjectX(eventData, transform, x, y) { + var target = transform.target, + // find how big the object would be, if there was no skewX. takes in account scaling + dimNoSkew = target._getTransformedDimensions(0, target.skewY), + localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + // the mouse is in the center of the object, and we want it to stay there. + // so the object will grow twice as much as the mouse. + // this makes the skew growth to localPoint * 2 - dimNoSkew. + totalSkewSize = Math.abs(localPoint.x * 2) - dimNoSkew.x, + currentSkew = target.skewX, newSkew; + if (totalSkewSize < 2) { + // let's make it easy to go back to position 0. + newSkew = 0; + } + else { + newSkew = radiansToDegrees( + Math.atan2((totalSkewSize / target.scaleX), (dimNoSkew.y / target.scaleY)) + ); + // now we have to find the sign of the skew. + // it mostly depend on the origin of transformation. + if (transform.originX === LEFT && transform.originY === BOTTOM) { + newSkew = -newSkew; + } + if (transform.originX === RIGHT && transform.originY === TOP) { + newSkew = -newSkew; + } + if (targetHasOneFlip(target)) { + newSkew = -newSkew; + } + } + var hasSkewed = currentSkew !== newSkew; + if (hasSkewed) { + var dimBeforeSkewing = target._getTransformedDimensions().y; + target.set('skewX', newSkew); + compensateScaleForSkew(target, 'skewY', 'scaleY', 'y', dimBeforeSkewing); + } + return hasSkewed; + } + + /** + * Action handler for skewing on the Y axis + * @private + */ + function skewObjectY(eventData, transform, x, y) { + var target = transform.target, + // find how big the object would be, if there was no skewX. takes in account scaling + dimNoSkew = target._getTransformedDimensions(target.skewX, 0), + localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + // the mouse is in the center of the object, and we want it to stay there. + // so the object will grow twice as much as the mouse. + // this makes the skew growth to localPoint * 2 - dimNoSkew. + totalSkewSize = Math.abs(localPoint.y * 2) - dimNoSkew.y, + currentSkew = target.skewY, newSkew; + if (totalSkewSize < 2) { + // let's make it easy to go back to position 0. + newSkew = 0; + } + else { + newSkew = radiansToDegrees( + Math.atan2((totalSkewSize / target.scaleY), (dimNoSkew.x / target.scaleX)) + ); + // now we have to find the sign of the skew. + // it mostly depend on the origin of transformation. + if (transform.originX === LEFT && transform.originY === BOTTOM) { + newSkew = -newSkew; + } + if (transform.originX === RIGHT && transform.originY === TOP) { + newSkew = -newSkew; + } + if (targetHasOneFlip(target)) { + newSkew = -newSkew; + } + } + var hasSkewed = currentSkew !== newSkew; + if (hasSkewed) { + var dimBeforeSkewing = target._getTransformedDimensions().x; + target.set('skewY', newSkew); + compensateScaleForSkew(target, 'skewX', 'scaleX', 'x', dimBeforeSkewing); + } + return hasSkewed; + } + + /** + * Wrapped Action handler for skewing on the Y axis, takes care of the + * skew direction and determine the correct transform origin for the anchor point + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function skewHandlerX(eventData, transform, x, y) { + // step1 figure out and change transform origin. + // if skewX > 0 and originY bottom we anchor on right + // if skewX > 0 and originY top we anchor on left + // if skewX < 0 and originY bottom we anchor on left + // if skewX < 0 and originY top we anchor on right + // if skewX is 0, we look for mouse position to understand where are we going. + var target = transform.target, currentSkew = target.skewX, originX, originY = transform.originY; + if (target.lockSkewingX) { + return false; + } + if (currentSkew === 0) { + var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); + if (localPointFromCenter.x > 0) { + // we are pulling right, anchor left; + originX = LEFT; + } + else { + // we are pulling right, anchor right + originX = RIGHT; + } + } + else { + if (currentSkew > 0) { + originX = originY === TOP ? LEFT : RIGHT; + } + if (currentSkew < 0) { + originX = originY === TOP ? RIGHT : LEFT; + } + // is the object flipped on one side only? swap the origin. + if (targetHasOneFlip(target)) { + originX = originX === LEFT ? RIGHT : LEFT; + } + } + + // once we have the origin, we find the anchor point + transform.originX = originX; + var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectX)); + return finalHandler(eventData, transform, x, y); + } + + /** + * Wrapped Action handler for skewing on the Y axis, takes care of the + * skew direction and determine the correct transform origin for the anchor point + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function skewHandlerY(eventData, transform, x, y) { + // step1 figure out and change transform origin. + // if skewY > 0 and originX left we anchor on top + // if skewY > 0 and originX right we anchor on bottom + // if skewY < 0 and originX left we anchor on bottom + // if skewY < 0 and originX right we anchor on top + // if skewY is 0, we look for mouse position to understand where are we going. + var target = transform.target, currentSkew = target.skewY, originY, originX = transform.originX; + if (target.lockSkewingY) { + return false; + } + if (currentSkew === 0) { + var localPointFromCenter = getLocalPoint(transform, CENTER, CENTER, x, y); + if (localPointFromCenter.y > 0) { + // we are pulling down, anchor up; + originY = TOP; + } + else { + // we are pulling up, anchor down + originY = BOTTOM; + } + } + else { + if (currentSkew > 0) { + originY = originX === LEFT ? TOP : BOTTOM; + } + if (currentSkew < 0) { + originY = originX === LEFT ? BOTTOM : TOP; + } + // is the object flipped on one side only? swap the origin. + if (targetHasOneFlip(target)) { + originY = originY === TOP ? BOTTOM : TOP; + } + } + + // once we have the origin, we find the anchor point + transform.originY = originY; + var finalHandler = wrapWithFireEvent('skewing', wrapWithFixedAnchor(skewObjectY)); + return finalHandler(eventData, transform, x, y); + } + + /** + * Action handler for rotation and snapping, without anchor point. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + * @private + */ + function rotationWithSnapping(eventData, transform, x, y) { + var t = transform, + target = t.target, + pivotPoint = target.translateToOriginPoint(target.getCenterPoint(), t.originX, t.originY); + + if (target.lockRotation) { + return false; + } + + var lastAngle = Math.atan2(t.ey - pivotPoint.y, t.ex - pivotPoint.x), + curAngle = Math.atan2(y - pivotPoint.y, x - pivotPoint.x), + angle = radiansToDegrees(curAngle - lastAngle + t.theta), + hasRotated = true; + + if (target.snapAngle > 0) { + var snapAngle = target.snapAngle, + snapThreshold = target.snapThreshold || snapAngle, + rightAngleLocked = Math.ceil(angle / snapAngle) * snapAngle, + leftAngleLocked = Math.floor(angle / snapAngle) * snapAngle; + + if (Math.abs(angle - leftAngleLocked) < snapThreshold) { + angle = leftAngleLocked; + } + else if (Math.abs(angle - rightAngleLocked) < snapThreshold) { + angle = rightAngleLocked; + } + } + + // normalize angle to positive value + if (angle < 0) { + angle = 360 + angle; + } + angle %= 360; + + hasRotated = target.angle !== angle; + target.angle = angle; + return hasRotated; + } + + /** + * Basic scaling logic, reused with different constrain for scaling X,Y, freely or equally. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @param {Object} options additional information for scaling + * @param {String} options.by 'x', 'y', 'equally' or '' to indicate type of scaling + * @return {Boolean} true if some change happened + * @private + */ + function scaleObject(eventData, transform, x, y, options) { + options = options || {}; + var target = transform.target, + lockScalingX = target.lockScalingX, lockScalingY = target.lockScalingY, + by = options.by, newPoint, scaleX, scaleY, dim, + scaleProportionally = scaleIsProportional(eventData, target), + forbidScaling = scalingIsForbidden(target, by, scaleProportionally), + signX, signY, gestureScale = transform.gestureScale; + + if (forbidScaling) { + return false; + } + if (gestureScale) { + scaleX = transform.scaleX * gestureScale; + scaleY = transform.scaleY * gestureScale; + } + else { + newPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y); + // use of sign: We use sign to detect change of direction of an action. sign usually change when + // we cross the origin point with the mouse. So a scale flip for example. There is an issue when scaling + // by center and scaling using one middle control ( default: mr, mt, ml, mb), the mouse movement can easily + // cross many time the origin point and flip the object. so we need a way to filter out the noise. + // This ternary here should be ok to filter out X scaling when we want Y only and vice versa. + signX = by !== 'y' ? sign(newPoint.x) : 1; + signY = by !== 'x' ? sign(newPoint.y) : 1; + if (!transform.signX) { + transform.signX = signX; + } + if (!transform.signY) { + transform.signY = signY; + } + + if (target.lockScalingFlip && + (transform.signX !== signX || transform.signY !== signY) + ) { + return false; + } + + dim = target._getTransformedDimensions(); + // missing detection of flip and logic to switch the origin + if (scaleProportionally && !by) { + // uniform scaling + var distance = Math.abs(newPoint.x) + Math.abs(newPoint.y), + original = transform.original, + originalDistance = Math.abs(dim.x * original.scaleX / target.scaleX) + + Math.abs(dim.y * original.scaleY / target.scaleY), + scale = distance / originalDistance; + scaleX = original.scaleX * scale; + scaleY = original.scaleY * scale; + } + else { + scaleX = Math.abs(newPoint.x * target.scaleX / dim.x); + scaleY = Math.abs(newPoint.y * target.scaleY / dim.y); + } + // if we are scaling by center, we need to double the scale + if (isTransformCentered(transform)) { + scaleX *= 2; + scaleY *= 2; + } + if (transform.signX !== signX && by !== 'y') { + transform.originX = opposite[transform.originX]; + scaleX *= -1; + transform.signX = signX; + } + if (transform.signY !== signY && by !== 'x') { + transform.originY = opposite[transform.originY]; + scaleY *= -1; + transform.signY = signY; + } + } + // minScale is taken are in the setter. + var oldScaleX = target.scaleX, oldScaleY = target.scaleY; + if (!by) { + !lockScalingX && target.set('scaleX', scaleX); + !lockScalingY && target.set('scaleY', scaleY); + } + else { + // forbidden cases already handled on top here. + by === 'x' && target.set('scaleX', scaleX); + by === 'y' && target.set('scaleY', scaleY); + } + return oldScaleX !== target.scaleX || oldScaleY !== target.scaleY; + } + + /** + * Generic scaling logic, to scale from corners either equally or freely. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectFromCorner(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y); + } + + /** + * Scaling logic for the X axis. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectX(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y , { by: 'x' }); + } + + /** + * Scaling logic for the Y axis. + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scaleObjectY(eventData, transform, x, y) { + return scaleObject(eventData, transform, x, y , { by: 'y' }); + } + + /** + * Composed action handler to either scale Y or skew X + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scalingYOrSkewingX(eventData, transform, x, y) { + // ok some safety needed here. + if (eventData[transform.target.canvas.altActionKey]) { + return controls.skewHandlerX(eventData, transform, x, y); + } + return controls.scalingY(eventData, transform, x, y); + } + + /** + * Composed action handler to either scale X or skew Y + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function scalingXOrSkewingY(eventData, transform, x, y) { + // ok some safety needed here. + if (eventData[transform.target.canvas.altActionKey]) { + return controls.skewHandlerY(eventData, transform, x, y); + } + return controls.scalingX(eventData, transform, x, y); + } + + /** + * Action handler to change textbox width + * Needs to be wrapped with `wrapWithFixedAnchor` to be effective + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if some change happened + */ + function changeWidth(eventData, transform, x, y) { + var target = transform.target, localPoint = getLocalPoint(transform, transform.originX, transform.originY, x, y), + strokePadding = target.strokeWidth / (target.strokeUniform ? target.scaleX : 1), + multiplier = isTransformCentered(transform) ? 2 : 1, + oldWidth = target.width, + newWidth = Math.abs(localPoint.x * multiplier / target.scaleX) - strokePadding; + target.set('width', Math.max(newWidth, 0)); + return oldWidth !== newWidth; + } + + /** + * Action handler + * @private + * @param {Event} eventData javascript event that is doing the transform + * @param {Object} transform javascript object containing a series of information around the current transform + * @param {number} x current mouse x position, canvas normalized + * @param {number} y current mouse y position, canvas normalized + * @return {Boolean} true if the translation occurred + */ + function dragHandler(eventData, transform, x, y) { + var target = transform.target, + newLeft = x - transform.offsetX, + newTop = y - transform.offsetY, + moveX = !target.get('lockMovementX') && target.left !== newLeft, + moveY = !target.get('lockMovementY') && target.top !== newTop; + moveX && target.set('left', newLeft); + moveY && target.set('top', newTop); + if (moveX || moveY) { + fireEvent('moving', commonEventInfo(eventData, transform, x, y)); + } + return moveX || moveY; + } + + controls.scaleCursorStyleHandler = scaleCursorStyleHandler; + controls.skewCursorStyleHandler = skewCursorStyleHandler; + controls.scaleSkewCursorStyleHandler = scaleSkewCursorStyleHandler; + controls.rotationWithSnapping = wrapWithFireEvent('rotating', wrapWithFixedAnchor(rotationWithSnapping)); + controls.scalingEqually = wrapWithFireEvent('scaling', wrapWithFixedAnchor( scaleObjectFromCorner)); + controls.scalingX = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectX)); + controls.scalingY = wrapWithFireEvent('scaling', wrapWithFixedAnchor(scaleObjectY)); + controls.scalingYOrSkewingX = scalingYOrSkewingX; + controls.scalingXOrSkewingY = scalingXOrSkewingY; + controls.changeWidth = wrapWithFireEvent('resizing', wrapWithFixedAnchor(changeWidth)); + controls.skewHandlerX = skewHandlerX; + controls.skewHandlerY = skewHandlerY; + controls.dragHandler = dragHandler; + controls.scaleOrSkewActionName = scaleOrSkewActionName; + controls.rotationStyleHandler = rotationStyleHandler; + controls.fireEvent = fireEvent; + controls.wrapWithFixedAnchor = wrapWithFixedAnchor; + controls.wrapWithFireEvent = wrapWithFireEvent; + controls.getLocalPoint = getLocalPoint; + fabric.controlsUtils = controls; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + degreesToRadians = fabric.util.degreesToRadians, + controls = fabric.controlsUtils; + + /** + * Render a round control, as per fabric features. + * This function is written to respect object properties like transparentCorners, cornerSize + * cornerColor, cornerStrokeColor + * plus the addition of offsetY and offsetX. + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Number} left x coordinate where the control center should be + * @param {Number} top y coordinate where the control center should be + * @param {Object} styleOverride override for fabric.Object controls style + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + */ + function renderCircleControl (ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + var xSize = this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, + ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, + transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ? + styleOverride.transparentCorners : fabricObject.transparentCorners, + methodName = transparentCorners ? 'stroke' : 'fill', + stroke = !transparentCorners && (styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor), + myLeft = left, + myTop = top, size; + ctx.save(); + ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; + ctx.strokeStyle = styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; + // as soon as fabric react v5, remove ie11, use proper ellipse code. + if (xSize > ySize) { + size = xSize; + ctx.scale(1.0, ySize / xSize); + myTop = top * xSize / ySize; + } + else if (ySize > xSize) { + size = ySize; + ctx.scale(xSize / ySize, 1.0); + myLeft = left * ySize / xSize; + } + else { + size = xSize; + } + // this is still wrong + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.arc(myLeft, myTop, size / 2, 0, 2 * Math.PI, false); + ctx[methodName](); + if (stroke) { + ctx.stroke(); + } + ctx.restore(); + } + + /** + * Render a square control, as per fabric features. + * This function is written to respect object properties like transparentCorners, cornerSize + * cornerColor, cornerStrokeColor + * plus the addition of offsetY and offsetX. + * @param {CanvasRenderingContext2D} ctx context to render on + * @param {Number} left x coordinate where the control center should be + * @param {Number} top y coordinate where the control center should be + * @param {Object} styleOverride override for fabric.Object controls style + * @param {fabric.Object} fabricObject the fabric object for which we are rendering controls + */ + function renderSquareControl(ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + var xSize = this.sizeX || styleOverride.cornerSize || fabricObject.cornerSize, + ySize = this.sizeY || styleOverride.cornerSize || fabricObject.cornerSize, + transparentCorners = typeof styleOverride.transparentCorners !== 'undefined' ? + styleOverride.transparentCorners : fabricObject.transparentCorners, + methodName = transparentCorners ? 'stroke' : 'fill', + stroke = !transparentCorners && ( + styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor + ), xSizeBy2 = xSize / 2, ySizeBy2 = ySize / 2; + ctx.save(); + ctx.fillStyle = styleOverride.cornerColor || fabricObject.cornerColor; + ctx.strokeStyle = styleOverride.cornerStrokeColor || fabricObject.cornerStrokeColor; + // this is still wrong + ctx.lineWidth = 1; + ctx.translate(left, top); + ctx.rotate(degreesToRadians(fabricObject.angle)); + // this does not work, and fixed with ( && ) does not make sense. + // to have real transparent corners we need the controls on upperCanvas + // transparentCorners || ctx.clearRect(-xSizeBy2, -ySizeBy2, xSize, ySize); + ctx[methodName + 'Rect'](-xSizeBy2, -ySizeBy2, xSize, ySize); + if (stroke) { + ctx.strokeRect(-xSizeBy2, -ySizeBy2, xSize, ySize); + } + ctx.restore(); + } + + controls.renderCircleControl = renderCircleControl; + controls.renderSquareControl = renderSquareControl; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + function Control(options) { + for (var i in options) { + this[i] = options[i]; + } + } + + fabric.Control = Control; + + fabric.Control.prototype = /** @lends fabric.Control.prototype */ { + + /** + * keep track of control visibility. + * mainly for backward compatibility. + * if you do not want to see a control, you can remove it + * from the controlset. + * @type {Boolean} + * @default true + */ + visible: true, + + /** + * Name of the action that the control will likely execute. + * This is optional. FabricJS uses to identify what the user is doing for some + * extra optimizations. If you are writing a custom control and you want to know + * somewhere else in the code what is going on, you can use this string here. + * you can also provide a custom getActionName if your control run multiple actions + * depending on some external state. + * default to scale since is the most common, used on 4 corners by default + * @type {String} + * @default 'scale' + */ + actionName: 'scale', + + /** + * Drawing angle of the control. + * NOT used for now, but name marked as needed for internal logic + * example: to reuse the same drawing function for different rotated controls + * @type {Number} + * @default 0 + */ + angle: 0, + + /** + * Relative position of the control. X + * 0,0 is the center of the Object, while -0.5 (left) or 0.5 (right) are the extremities + * of the bounding box. + * @type {Number} + * @default 0 + */ + x: 0, + + /** + * Relative position of the control. Y + * 0,0 is the center of the Object, while -0.5 (top) or 0.5 (bottom) are the extremities + * of the bounding box. + * @type {Number} + * @default 0 + */ + y: 0, + + /** + * Horizontal offset of the control from the defined position. In pixels + * Positive offset moves the control to the right, negative to the left. + * It used when you want to have position of control that does not scale with + * the bounding box. Example: rotation control is placed at x:0, y: 0.5 on + * the boundindbox, with an offset of 30 pixels vertically. Those 30 pixels will + * stay 30 pixels no matter how the object is big. Another example is having 2 + * controls in the corner, that stay in the same position when the object scale. + * of the bounding box. + * @type {Number} + * @default 0 + */ + offsetX: 0, + + /** + * Vertical offset of the control from the defined position. In pixels + * Positive offset moves the control to the bottom, negative to the top. + * @type {Number} + * @default 0 + */ + offsetY: 0, + + /** + * Sets the length of the control. If null, defaults to object's cornerSize. + * Expects both sizeX and sizeY to be set when set. + * @type {?Number} + * @default null + */ + sizeX: null, + + /** + * Sets the height of the control. If null, defaults to object's cornerSize. + * Expects both sizeX and sizeY to be set when set. + * @type {?Number} + * @default null + */ + sizeY: null, + + /** + * Sets the length of the touch area of the control. If null, defaults to object's touchCornerSize. + * Expects both touchSizeX and touchSizeY to be set when set. + * @type {?Number} + * @default null + */ + touchSizeX: null, + + /** + * Sets the height of the touch area of the control. If null, defaults to object's touchCornerSize. + * Expects both touchSizeX and touchSizeY to be set when set. + * @type {?Number} + * @default null + */ + touchSizeY: null, + + /** + * Css cursor style to display when the control is hovered. + * if the method `cursorStyleHandler` is provided, this property is ignored. + * @type {String} + * @default 'crosshair' + */ + cursorStyle: 'crosshair', + + /** + * If controls has an offsetY or offsetX, draw a line that connects + * the control to the bounding box + * @type {Boolean} + * @default false + */ + withConnection: false, + + /** + * The control actionHandler, provide one to handle action ( control being moved ) + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + actionHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * The control handler for mouse down, provide one to handle mouse down on control + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + mouseDownHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * The control mouseUpHandler, provide one to handle an effect on mouse up. + * @param {Event} eventData the native mouse event + * @param {Object} transformData properties of the current transform + * @param {Number} x x position of the cursor + * @param {Number} y y position of the cursor + * @return {Boolean} true if the action/event modified the object + */ + mouseUpHandler: function(/* eventData, transformData, x, y */) { }, + + /** + * Returns control actionHandler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getActionHandler: function(/* eventData, fabricObject, control */) { + return this.actionHandler; + }, + + /** + * Returns control mouseDown handler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getMouseDownHandler: function(/* eventData, fabricObject, control */) { + return this.mouseDownHandler; + }, + + /** + * Returns control mouseUp handler + * @param {Event} eventData the native mouse event + * @param {fabric.Object} fabricObject on which the control is displayed + * @param {fabric.Control} control control for which the action handler is being asked + * @return {Function} the action handler + */ + getMouseUpHandler: function(/* eventData, fabricObject, control */) { + return this.mouseUpHandler; + }, + + /** + * Returns control cursorStyle for css using cursorStyle. If you need a more elaborate + * function you can pass one in the constructor + * the cursorStyle property + * @param {Event} eventData the native mouse event + * @param {fabric.Control} control the current control ( likely this) + * @param {fabric.Object} object on which the control is displayed + * @return {String} + */ + cursorStyleHandler: function(eventData, control /* fabricObject */) { + return control.cursorStyle; + }, + + /** + * Returns the action name. The basic implementation just return the actionName property. + * @param {Event} eventData the native mouse event + * @param {fabric.Control} control the current control ( likely this) + * @param {fabric.Object} object on which the control is displayed + * @return {String} + */ + getActionName: function(eventData, control /* fabricObject */) { + return control.actionName; + }, + + /** + * Returns controls visibility + * @param {fabric.Object} object on which the control is displayed + * @param {String} controlKey key where the control is memorized on the + * @return {Boolean} + */ + getVisibility: function(fabricObject, controlKey) { + var objectVisibility = fabricObject._controlsVisibility; + if (objectVisibility && typeof objectVisibility[controlKey] !== 'undefined') { + return objectVisibility[controlKey]; + } + return this.visible; + }, + + /** + * Sets controls visibility + * @param {Boolean} visibility for the object + * @return {Void} + */ + setVisibility: function(visibility /* name, fabricObject */) { + this.visible = visibility; + }, + + + positionHandler: function(dim, finalMatrix /*, fabricObject, currentControl */) { + var point = fabric.util.transformPoint({ + x: this.x * dim.x + this.offsetX, + y: this.y * dim.y + this.offsetY }, finalMatrix); + return point; + }, + + /** + * Returns the coords for this control based on object values. + * @param {Number} objectAngle angle from the fabric object holding the control + * @param {Number} objectCornerSize cornerSize from the fabric object holding the control (or touchCornerSize if + * isTouch is true) + * @param {Number} centerX x coordinate where the control center should be + * @param {Number} centerY y coordinate where the control center should be + * @param {boolean} isTouch true if touch corner, false if normal corner + */ + calcCornerCoords: function(objectAngle, objectCornerSize, centerX, centerY, isTouch) { + var cosHalfOffset, + sinHalfOffset, + cosHalfOffsetComp, + sinHalfOffsetComp, + xSize = (isTouch) ? this.touchSizeX : this.sizeX, + ySize = (isTouch) ? this.touchSizeY : this.sizeY; + if (xSize && ySize && xSize !== ySize) { + // handle rectangular corners + var controlTriangleAngle = Math.atan2(ySize, xSize); + var cornerHypotenuse = Math.sqrt(xSize * xSize + ySize * ySize) / 2; + var newTheta = controlTriangleAngle - fabric.util.degreesToRadians(objectAngle); + var newThetaComp = Math.PI / 2 - controlTriangleAngle - fabric.util.degreesToRadians(objectAngle); + cosHalfOffset = cornerHypotenuse * fabric.util.cos(newTheta); + sinHalfOffset = cornerHypotenuse * fabric.util.sin(newTheta); + // use complementary angle for two corners + cosHalfOffsetComp = cornerHypotenuse * fabric.util.cos(newThetaComp); + sinHalfOffsetComp = cornerHypotenuse * fabric.util.sin(newThetaComp); + } + else { + // handle square corners + // use default object corner size unless size is defined + var cornerSize = (xSize && ySize) ? xSize : objectCornerSize; + /* 0.7071067812 stands for sqrt(2)/2 */ + cornerHypotenuse = cornerSize * 0.7071067812; + // complementary angles are equal since they're both 45 degrees + var newTheta = fabric.util.degreesToRadians(45 - objectAngle); + cosHalfOffset = cosHalfOffsetComp = cornerHypotenuse * fabric.util.cos(newTheta); + sinHalfOffset = sinHalfOffsetComp = cornerHypotenuse * fabric.util.sin(newTheta); + } + + return { + tl: { + x: centerX - sinHalfOffsetComp, + y: centerY - cosHalfOffsetComp, + }, + tr: { + x: centerX + cosHalfOffset, + y: centerY - sinHalfOffset, + }, + bl: { + x: centerX - cosHalfOffset, + y: centerY + sinHalfOffset, + }, + br: { + x: centerX + sinHalfOffsetComp, + y: centerY + cosHalfOffsetComp, + }, + }; + }, + + /** + * Render function for the control. + * When this function runs the context is unscaled. unrotate. Just retina scaled. + * all the functions will have to translate to the point left,top before starting Drawing + * if they want to draw a control where the position is detected. + * left and top are the result of the positionHandler function + * @param {RenderingContext2D} ctx the context where the control will be drawn + * @param {Number} left position of the canvas where we are about to render the control. + * @param {Number} top position of the canvas where we are about to render the control. + * @param {Object} styleOverride + * @param {fabric.Object} fabricObject the object where the control is about to be rendered + */ + render: function(ctx, left, top, styleOverride, fabricObject) { + styleOverride = styleOverride || {}; + switch (styleOverride.cornerStyle || fabricObject.cornerStyle) { + case 'circle': + fabric.controlsUtils.renderCircleControl.call(this, ctx, left, top, styleOverride, fabricObject); + break; + default: + fabric.controlsUtils.renderSquareControl.call(this, ctx, left, top, styleOverride, fabricObject); + } + }, + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + /* _FROM_SVG_START_ */ + function getColorStop(el, multiplier) { + var style = el.getAttribute('style'), + offset = el.getAttribute('offset') || 0, + color, colorAlpha, opacity, i; + + // convert percents to absolute values + offset = parseFloat(offset) / (/%$/.test(offset) ? 100 : 1); + offset = offset < 0 ? 0 : offset > 1 ? 1 : offset; + if (style) { + var keyValuePairs = style.split(/\s*;\s*/); + + if (keyValuePairs[keyValuePairs.length - 1] === '') { + keyValuePairs.pop(); + } + + for (i = keyValuePairs.length; i--; ) { + + var split = keyValuePairs[i].split(/\s*:\s*/), + key = split[0].trim(), + value = split[1].trim(); + + if (key === 'stop-color') { + color = value; + } + else if (key === 'stop-opacity') { + opacity = value; + } + } + } + + if (!color) { + color = el.getAttribute('stop-color') || 'rgb(0,0,0)'; + } + if (!opacity) { + opacity = el.getAttribute('stop-opacity'); + } + + color = new fabric.Color(color); + colorAlpha = color.getAlpha(); + opacity = isNaN(parseFloat(opacity)) ? 1 : parseFloat(opacity); + opacity *= colorAlpha * multiplier; + + return { + offset: offset, + color: color.toRgb(), + opacity: opacity + }; + } + + function getLinearCoords(el) { + return { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0 + }; + } + + function getRadialCoords(el) { + return { + x1: el.getAttribute('fx') || el.getAttribute('cx') || '50%', + y1: el.getAttribute('fy') || el.getAttribute('cy') || '50%', + r1: 0, + x2: el.getAttribute('cx') || '50%', + y2: el.getAttribute('cy') || '50%', + r2: el.getAttribute('r') || '50%' + }; + } + /* _FROM_SVG_END_ */ + + var clone = fabric.util.object.clone; + + /** + * Gradient class + * @class fabric.Gradient + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#gradients} + * @see {@link fabric.Gradient#initialize} for constructor definition + */ + fabric.Gradient = fabric.util.createClass(/** @lends fabric.Gradient.prototype */ { + + /** + * Horizontal offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetX: 0, + + /** + * Vertical offset for aligning gradients coming from SVG when outside pathgroups + * @type Number + * @default 0 + */ + offsetY: 0, + + /** + * A transform matrix to apply to the gradient before painting. + * Imported from svg gradients, is not applied with the current transform in the center. + * Before this transform is applied, the origin point is at the top left corner of the object + * plus the addition of offsetY and offsetX. + * @type Number[] + * @default null + */ + gradientTransform: null, + + /** + * coordinates units for coords. + * If `pixels`, the number of coords are in the same unit of width / height. + * If set as `percentage` the coords are still a number, but 1 means 100% of width + * for the X and 100% of the height for the y. It can be bigger than 1 and negative. + * allowed values pixels or percentage. + * @type String + * @default 'pixels' + */ + gradientUnits: 'pixels', + + /** + * Gradient type linear or radial + * @type String + * @default 'pixels' + */ + type: 'linear', + + /** + * Constructor + * @param {Object} options Options object with type, coords, gradientUnits and colorStops + * @param {Object} [options.type] gradient type linear or radial + * @param {Object} [options.gradientUnits] gradient units + * @param {Object} [options.offsetX] SVG import compatibility + * @param {Object} [options.offsetY] SVG import compatibility + * @param {Object[]} options.colorStops contains the colorstops. + * @param {Object} options.coords contains the coords of the gradient + * @param {Number} [options.coords.x1] X coordiante of the first point for linear or of the focal point for radial + * @param {Number} [options.coords.y1] Y coordiante of the first point for linear or of the focal point for radial + * @param {Number} [options.coords.x2] X coordiante of the second point for linear or of the center point for radial + * @param {Number} [options.coords.y2] Y coordiante of the second point for linear or of the center point for radial + * @param {Number} [options.coords.r1] only for radial gradient, radius of the inner circle + * @param {Number} [options.coords.r2] only for radial gradient, radius of the external circle + * @return {fabric.Gradient} thisArg + */ + initialize: function(options) { + options || (options = { }); + options.coords || (options.coords = { }); + + var coords, _this = this; + + // sets everything, then coords and colorstops get sets again + Object.keys(options).forEach(function(option) { + _this[option] = options[option]; + }); + + if (this.id) { + this.id += '_' + fabric.Object.__uid++; + } + else { + this.id = fabric.Object.__uid++; + } + + coords = { + x1: options.coords.x1 || 0, + y1: options.coords.y1 || 0, + x2: options.coords.x2 || 0, + y2: options.coords.y2 || 0 + }; + + if (this.type === 'radial') { + coords.r1 = options.coords.r1 || 0; + coords.r2 = options.coords.r2 || 0; + } + + this.coords = coords; + this.colorStops = options.colorStops.slice(); + }, + + /** + * Adds another colorStop + * @param {Object} colorStop Object with offset and color + * @return {fabric.Gradient} thisArg + */ + addColorStop: function(colorStops) { + for (var position in colorStops) { + var color = new fabric.Color(colorStops[position]); + this.colorStops.push({ + offset: parseFloat(position), + color: color.toRgb(), + opacity: color.getAlpha() + }); + } + return this; + }, + + /** + * Returns object representation of a gradient + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} + */ + toObject: function(propertiesToInclude) { + var object = { + type: this.type, + coords: this.coords, + colorStops: this.colorStops, + offsetX: this.offsetX, + offsetY: this.offsetY, + gradientUnits: this.gradientUnits, + gradientTransform: this.gradientTransform ? this.gradientTransform.concat() : this.gradientTransform + }; + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of an gradient + * @param {Object} object Object to create a gradient for + * @return {String} SVG representation of an gradient (linear/radial) + */ + toSVG: function(object, options) { + var coords = clone(this.coords, true), i, len, options = options || {}, + markup, commonAttributes, colorStops = clone(this.colorStops, true), + needsSwap = coords.r1 > coords.r2, + transform = this.gradientTransform ? this.gradientTransform.concat() : fabric.iMatrix.concat(), + offsetX = -this.offsetX, offsetY = -this.offsetY, + withViewport = !!options.additionalTransform, + gradientUnits = this.gradientUnits === 'pixels' ? 'userSpaceOnUse' : 'objectBoundingBox'; + // colorStops must be sorted ascending + colorStops.sort(function(a, b) { + return a.offset - b.offset; + }); + + if (gradientUnits === 'objectBoundingBox') { + offsetX /= object.width; + offsetY /= object.height; + } + else { + offsetX += object.width / 2; + offsetY += object.height / 2; + } + if (object.type === 'path' && this.gradientUnits !== 'percentage') { + offsetX -= object.pathOffset.x; + offsetY -= object.pathOffset.y; + } + + + transform[4] -= offsetX; + transform[5] -= offsetY; + + commonAttributes = 'id="SVGID_' + this.id + + '" gradientUnits="' + gradientUnits + '"'; + commonAttributes += ' gradientTransform="' + (withViewport ? + options.additionalTransform + ' ' : '') + fabric.util.matrixToSVG(transform) + '" '; + + if (this.type === 'linear') { + markup = [ + '\n' + ]; + } + else if (this.type === 'radial') { + // svg radial gradient has just 1 radius. the biggest. + markup = [ + '\n' + ]; + } + + if (this.type === 'radial') { + if (needsSwap) { + // svg goes from internal to external radius. if radius are inverted, swap color stops. + colorStops = colorStops.concat(); + colorStops.reverse(); + for (i = 0, len = colorStops.length; i < len; i++) { + colorStops[i].offset = 1 - colorStops[i].offset; + } + } + var minRadius = Math.min(coords.r1, coords.r2); + if (minRadius > 0) { + // i have to shift all colorStops and add new one in 0. + var maxRadius = Math.max(coords.r1, coords.r2), + percentageShift = minRadius / maxRadius; + for (i = 0, len = colorStops.length; i < len; i++) { + colorStops[i].offset += percentageShift * (1 - colorStops[i].offset); + } + } + } + + for (i = 0, len = colorStops.length; i < len; i++) { + var colorStop = colorStops[i]; + markup.push( + '\n' + ); + } + + markup.push((this.type === 'linear' ? '\n' : '\n')); + + return markup.join(''); + }, + /* _TO_SVG_END_ */ + + /** + * Returns an instance of CanvasGradient + * @param {CanvasRenderingContext2D} ctx Context to render on + * @return {CanvasGradient} + */ + toLive: function(ctx) { + var gradient, coords = fabric.util.object.clone(this.coords), i, len; + + if (!this.type) { + return; + } + + if (this.type === 'linear') { + gradient = ctx.createLinearGradient( + coords.x1, coords.y1, coords.x2, coords.y2); + } + else if (this.type === 'radial') { + gradient = ctx.createRadialGradient( + coords.x1, coords.y1, coords.r1, coords.x2, coords.y2, coords.r2); + } + + for (i = 0, len = this.colorStops.length; i < len; i++) { + var color = this.colorStops[i].color, + opacity = this.colorStops[i].opacity, + offset = this.colorStops[i].offset; + + if (typeof opacity !== 'undefined') { + color = new fabric.Color(color).setAlpha(opacity).toRgba(); + } + gradient.addColorStop(offset, color); + } + + return gradient; + } + }); + + fabric.util.object.extend(fabric.Gradient, { + + /* _FROM_SVG_START_ */ + /** + * Returns {@link fabric.Gradient} instance from an SVG element + * @static + * @memberOf fabric.Gradient + * @param {SVGGradientElement} el SVG gradient element + * @param {fabric.Object} instance + * @param {String} opacityAttr A fill-opacity or stroke-opacity attribute to multiply to each stop's opacity. + * @param {Object} svgOptions an object containing the size of the SVG in order to parse correctly gradients + * that uses gradientUnits as 'userSpaceOnUse' and percentages. + * @param {Object.number} viewBoxWidth width part of the viewBox attribute on svg + * @param {Object.number} viewBoxHeight height part of the viewBox attribute on svg + * @param {Object.number} width width part of the svg tag if viewBox is not specified + * @param {Object.number} height height part of the svg tag if viewBox is not specified + * @return {fabric.Gradient} Gradient instance + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + * @see http://www.w3.org/TR/SVG/pservers.html#RadialGradientElement + */ + fromElement: function(el, instance, opacityAttr, svgOptions) { + /** + * @example: + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + * OR + * + * + * + * + * + * + * + */ + + var multiplier = parseFloat(opacityAttr) / (/%$/.test(opacityAttr) ? 100 : 1); + multiplier = multiplier < 0 ? 0 : multiplier > 1 ? 1 : multiplier; + if (isNaN(multiplier)) { + multiplier = 1; + } + + var colorStopEls = el.getElementsByTagName('stop'), + type, + gradientUnits = el.getAttribute('gradientUnits') === 'userSpaceOnUse' ? + 'pixels' : 'percentage', + gradientTransform = el.getAttribute('gradientTransform') || '', + colorStops = [], + coords, i, offsetX = 0, offsetY = 0, + transformMatrix; + if (el.nodeName === 'linearGradient' || el.nodeName === 'LINEARGRADIENT') { + type = 'linear'; + coords = getLinearCoords(el); + } + else { + type = 'radial'; + coords = getRadialCoords(el); + } + + for (i = colorStopEls.length; i--; ) { + colorStops.push(getColorStop(colorStopEls[i], multiplier)); + } + + transformMatrix = fabric.parseTransformAttribute(gradientTransform); + + __convertPercentUnitsToValues(instance, coords, svgOptions, gradientUnits); + + if (gradientUnits === 'pixels') { + offsetX = -instance.left; + offsetY = -instance.top; + } + + var gradient = new fabric.Gradient({ + id: el.getAttribute('id'), + type: type, + coords: coords, + colorStops: colorStops, + gradientUnits: gradientUnits, + gradientTransform: transformMatrix, + offsetX: offsetX, + offsetY: offsetY, + }); + + return gradient; + } + /* _FROM_SVG_END_ */ + }); + + /** + * @private + */ + function __convertPercentUnitsToValues(instance, options, svgOptions, gradientUnits) { + var propValue, finalValue; + Object.keys(options).forEach(function(prop) { + propValue = options[prop]; + if (propValue === 'Infinity') { + finalValue = 1; + } + else if (propValue === '-Infinity') { + finalValue = 0; + } + else { + finalValue = parseFloat(options[prop], 10); + if (typeof propValue === 'string' && /^(\d+\.\d+)%|(\d+)%$/.test(propValue)) { + finalValue *= 0.01; + if (gradientUnits === 'pixels') { + // then we need to fix those percentages here in svg parsing + if (prop === 'x1' || prop === 'x2' || prop === 'r2') { + finalValue *= svgOptions.viewBoxWidth || svgOptions.width; + } + if (prop === 'y1' || prop === 'y2') { + finalValue *= svgOptions.viewBoxHeight || svgOptions.height; + } + } + } + } + options[prop] = finalValue; + }); + } +})(); + + +(function() { + + 'use strict'; + + var toFixed = fabric.util.toFixed; + + /** + * Pattern class + * @class fabric.Pattern + * @see {@link http://fabricjs.com/patterns|Pattern demo} + * @see {@link http://fabricjs.com/dynamic-patterns|DynamicPattern demo} + * @see {@link fabric.Pattern#initialize} for constructor definition + */ + + + fabric.Pattern = fabric.util.createClass(/** @lends fabric.Pattern.prototype */ { + + /** + * Repeat property of a pattern (one of repeat, repeat-x, repeat-y or no-repeat) + * @type String + * @default + */ + repeat: 'repeat', + + /** + * Pattern horizontal offset from object's left/top corner + * @type Number + * @default + */ + offsetX: 0, + + /** + * Pattern vertical offset from object's left/top corner + * @type Number + * @default + */ + offsetY: 0, + + /** + * crossOrigin value (one of "", "anonymous", "use-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @type String + * @default + */ + crossOrigin: '', + + /** + * transform matrix to change the pattern, imported from svgs. + * @type Array + * @default + */ + patternTransform: null, + + /** + * Constructor + * @param {Object} [options] Options object + * @param {Function} [callback] function to invoke after callback init. + * @return {fabric.Pattern} thisArg + */ + initialize: function(options, callback) { + options || (options = { }); + + this.id = fabric.Object.__uid++; + this.setOptions(options); + if (!options.source || (options.source && typeof options.source !== 'string')) { + callback && callback(this); + return; + } + else { + // img src string + var _this = this; + this.source = fabric.util.createImage(); + fabric.util.loadImage(options.source, function(img, isError) { + _this.source = img; + callback && callback(_this, isError); + }, null, this.crossOrigin); + } + }, + + /** + * Returns object representation of a pattern + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of a pattern instance + */ + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + source, object; + + // element + if (typeof this.source.src === 'string') { + source = this.source.src; + } + // element + else if (typeof this.source === 'object' && this.source.toDataURL) { + source = this.source.toDataURL(); + } + + object = { + type: 'pattern', + source: source, + repeat: this.repeat, + crossOrigin: this.crossOrigin, + offsetX: toFixed(this.offsetX, NUM_FRACTION_DIGITS), + offsetY: toFixed(this.offsetY, NUM_FRACTION_DIGITS), + patternTransform: this.patternTransform ? this.patternTransform.concat() : null + }; + fabric.util.populateWithProperties(this, object, propertiesToInclude); + + return object; + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a pattern + * @param {fabric.Object} object + * @return {String} SVG representation of a pattern + */ + toSVG: function(object) { + var patternSource = typeof this.source === 'function' ? this.source() : this.source, + patternWidth = patternSource.width / object.width, + patternHeight = patternSource.height / object.height, + patternOffsetX = this.offsetX / object.width, + patternOffsetY = this.offsetY / object.height, + patternImgSrc = ''; + if (this.repeat === 'repeat-x' || this.repeat === 'no-repeat') { + patternHeight = 1; + if (patternOffsetY) { + patternHeight += Math.abs(patternOffsetY); + } + } + if (this.repeat === 'repeat-y' || this.repeat === 'no-repeat') { + patternWidth = 1; + if (patternOffsetX) { + patternWidth += Math.abs(patternOffsetX); + } + + } + if (patternSource.src) { + patternImgSrc = patternSource.src; + } + else if (patternSource.toDataURL) { + patternImgSrc = patternSource.toDataURL(); + } + + return '\n' + + '\n' + + '\n'; + }, + /* _TO_SVG_END_ */ + + setOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Returns an instance of CanvasPattern + * @param {CanvasRenderingContext2D} ctx Context to create pattern + * @return {CanvasPattern} + */ + toLive: function(ctx) { + var source = this.source; + // if the image failed to load, return, and allow rest to continue loading + if (!source) { + return ''; + } + + // if an image + if (typeof source.src !== 'undefined') { + if (!source.complete) { + return ''; + } + if (source.naturalWidth === 0 || source.naturalHeight === 0) { + return ''; + } + } + return ctx.createPattern(source, this.repeat); + } + }); +})(); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + toFixed = fabric.util.toFixed; + + if (fabric.Shadow) { + fabric.warn('fabric.Shadow is already defined.'); + return; + } + + /** + * Shadow class + * @class fabric.Shadow + * @see {@link http://fabricjs.com/shadows|Shadow demo} + * @see {@link fabric.Shadow#initialize} for constructor definition + */ + fabric.Shadow = fabric.util.createClass(/** @lends fabric.Shadow.prototype */ { + + /** + * Shadow color + * @type String + * @default + */ + color: 'rgb(0,0,0)', + + /** + * Shadow blur + * @type Number + */ + blur: 0, + + /** + * Shadow horizontal offset + * @type Number + * @default + */ + offsetX: 0, + + /** + * Shadow vertical offset + * @type Number + * @default + */ + offsetY: 0, + + /** + * Whether the shadow should affect stroke operations + * @type Boolean + * @default + */ + affectStroke: false, + + /** + * Indicates whether toObject should include default values + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * When `false`, the shadow will scale with the object. + * When `true`, the shadow's offsetX, offsetY, and blur will not be affected by the object's scale. + * default to false + * @type Boolean + * @default + */ + nonScaling: false, + + /** + * Constructor + * @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px") + * @return {fabric.Shadow} thisArg + */ + initialize: function(options) { + + if (typeof options === 'string') { + options = this._parseShadow(options); + } + + for (var prop in options) { + this[prop] = options[prop]; + } + + this.id = fabric.Object.__uid++; + }, + + /** + * @private + * @param {String} shadow Shadow value to parse + * @return {Object} Shadow object with color, offsetX, offsetY and blur + */ + _parseShadow: function(shadow) { + var shadowStr = shadow.trim(), + offsetsAndBlur = fabric.Shadow.reOffsetsAndBlur.exec(shadowStr) || [], + color = shadowStr.replace(fabric.Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'; + + return { + color: color.trim(), + offsetX: parseFloat(offsetsAndBlur[1], 10) || 0, + offsetY: parseFloat(offsetsAndBlur[2], 10) || 0, + blur: parseFloat(offsetsAndBlur[3], 10) || 0 + }; + }, + + /** + * Returns a string representation of an instance + * @see http://www.w3.org/TR/css-text-decor-3/#text-shadow + * @return {String} Returns CSS3 text-shadow declaration + */ + toString: function() { + return [this.offsetX, this.offsetY, this.blur, this.color].join('px '); + }, + + /* _TO_SVG_START_ */ + /** + * Returns SVG representation of a shadow + * @param {fabric.Object} object + * @return {String} SVG representation of a shadow + */ + toSVG: function(object) { + var fBoxX = 40, fBoxY = 40, NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + offset = fabric.util.rotateVector( + { x: this.offsetX, y: this.offsetY }, + fabric.util.degreesToRadians(-object.angle)), + BLUR_BOX = 20, color = new fabric.Color(this.color); + + if (object.width && object.height) { + //http://www.w3.org/TR/SVG/filters.html#FilterEffectsRegion + // we add some extra space to filter box to contain the blur ( 20 ) + fBoxX = toFixed((Math.abs(offset.x) + this.blur) / object.width, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; + fBoxY = toFixed((Math.abs(offset.y) + this.blur) / object.height, NUM_FRACTION_DIGITS) * 100 + BLUR_BOX; + } + if (object.flipX) { + offset.x *= -1; + } + if (object.flipY) { + offset.y *= -1; + } + + return ( + '\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\n' + + '\t\t\n' + + '\t\t\n' + + '\t\n' + + '\n'); + }, + /* _TO_SVG_END_ */ + + /** + * Returns object representation of a shadow + * @return {Object} Object representation of a shadow instance + */ + toObject: function() { + if (this.includeDefaultValues) { + return { + color: this.color, + blur: this.blur, + offsetX: this.offsetX, + offsetY: this.offsetY, + affectStroke: this.affectStroke, + nonScaling: this.nonScaling + }; + } + var obj = { }, proto = fabric.Shadow.prototype; + + ['color', 'blur', 'offsetX', 'offsetY', 'affectStroke', 'nonScaling'].forEach(function(prop) { + if (this[prop] !== proto[prop]) { + obj[prop] = this[prop]; + } + }, this); + + return obj; + } + }); + + /** + * Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px") + * @static + * @field + * @memberOf fabric.Shadow + */ + // eslint-disable-next-line max-len + fabric.Shadow.reOffsetsAndBlur = /(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/; + +})(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, + toFixed = fabric.util.toFixed, + transformPoint = fabric.util.transformPoint, + invertTransform = fabric.util.invertTransform, + getNodeCanvas = fabric.util.getNodeCanvas, + createCanvasElement = fabric.util.createCanvasElement, + + CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element'); + + /** + * Static canvas class + * @class fabric.StaticCanvas + * @mixes fabric.Collection + * @mixes fabric.Observable + * @see {@link http://fabricjs.com/static_canvas|StaticCanvas demo} + * @see {@link fabric.StaticCanvas#initialize} for constructor definition + * @fires before:render + * @fires after:render + * @fires canvas:cleared + * @fires object:added + * @fires object:removed + */ + fabric.StaticCanvas = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(el, options) { + options || (options = { }); + this.renderAndResetBound = this.renderAndReset.bind(this); + this.requestRenderAllBound = this.requestRenderAll.bind(this); + this._initStatic(el, options); + }, + + /** + * Background color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setBackgroundColor}. + * @type {(String|fabric.Pattern)} + * @default + */ + backgroundColor: '', + + /** + * Background image of canvas instance. + * since 2.4.0 image caching is active, please when putting an image as background, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching + * @type fabric.Image + * @default + */ + backgroundImage: null, + + /** + * Overlay color of canvas instance. + * Should be set via {@link fabric.StaticCanvas#setOverlayColor} + * @since 1.3.9 + * @type {(String|fabric.Pattern)} + * @default + */ + overlayColor: '', + + /** + * Overlay image of canvas instance. + * since 2.4.0 image caching is active, please when putting an image as overlay, add to the + * canvas property a reference to the canvas it is on. Otherwise the image cannot detect the zoom + * vale. As an alternative you can disable image objectCaching + * @type fabric.Image + * @default + */ + overlayImage: null, + + /** + * Indicates whether toObject/toDatalessObject should include default values + * if set to false, takes precedence over the object value. + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * Indicates whether objects' state should be saved + * @type Boolean + * @default + */ + stateful: false, + + /** + * Indicates whether {@link fabric.Collection.add}, {@link fabric.Collection.insertAt} and {@link fabric.Collection.remove}, + * {@link fabric.StaticCanvas.moveTo}, {@link fabric.StaticCanvas.clear} and many more, should also re-render canvas. + * Disabling this option will not give a performance boost when adding/removing a lot of objects to/from canvas at once + * since the renders are quequed and executed one per frame. + * Disabling is suggested anyway and managing the renders of the app manually is not a big effort ( canvas.requestRenderAll() ) + * Left default to true to do not break documentation and old app, fiddles. + * @type Boolean + * @default + */ + renderOnAddRemove: true, + + /** + * Indicates whether object controls (borders/controls) are rendered above overlay image + * @type Boolean + * @default + */ + controlsAboveOverlay: false, + + /** + * Indicates whether the browser can be scrolled when using a touchscreen and dragging on the canvas + * @type Boolean + * @default + */ + allowTouchScrolling: false, + + /** + * Indicates whether this canvas will use image smoothing, this is on by default in browsers + * @type Boolean + * @default + */ + imageSmoothingEnabled: true, + + /** + * The transformation (in the format of Canvas transform) which focuses the viewport + * @type Array + * @default + */ + viewportTransform: fabric.iMatrix.concat(), + + /** + * if set to false background image is not affected by viewport transform + * @since 1.6.3 + * @type Boolean + * @default + */ + backgroundVpt: true, + + /** + * if set to false overlya image is not affected by viewport transform + * @since 1.6.3 + * @type Boolean + * @default + */ + overlayVpt: true, + + /** + * When true, canvas is scaled by devicePixelRatio for better rendering on retina screens + * @type Boolean + * @default + */ + enableRetinaScaling: true, + + /** + * Describe canvas element extension over design + * properties are tl,tr,bl,br. + * if canvas is not zoomed/panned those points are the four corner of canvas + * if canvas is viewportTransformed you those points indicate the extension + * of canvas element in plain untrasformed coordinates + * The coordinates get updated with @method calcViewportBoundaries. + * @memberOf fabric.StaticCanvas.prototype + */ + vptCoords: { }, + + /** + * Based on vptCoords and object.aCoords, skip rendering of objects that + * are not included in current viewport. + * May greatly help in applications with crowded canvas and use of zoom/pan + * If One of the corner of the bounding box of the object is on the canvas + * the objects get rendered. + * @memberOf fabric.StaticCanvas.prototype + * @type Boolean + * @default + */ + skipOffscreen: true, + + /** + * a fabricObject that, without stroke define a clipping area with their shape. filled in black + * the clipPath object gets used when the canvas has rendered, and the context is placed in the + * top left corner of the canvas. + * clipPath will clip away controls, if you do not want this to happen use controlsAboveOverlay = true + * @type fabric.Object + */ + clipPath: undefined, + + /** + * @private + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + */ + _initStatic: function(el, options) { + var cb = this.requestRenderAllBound; + this._objects = []; + this._createLowerCanvas(el); + this._initOptions(options); + // only initialize retina scaling once + if (!this.interactive) { + this._initRetinaScaling(); + } + + if (options.overlayImage) { + this.setOverlayImage(options.overlayImage, cb); + } + if (options.backgroundImage) { + this.setBackgroundImage(options.backgroundImage, cb); + } + if (options.backgroundColor) { + this.setBackgroundColor(options.backgroundColor, cb); + } + if (options.overlayColor) { + this.setOverlayColor(options.overlayColor, cb); + } + this.calcOffset(); + }, + + /** + * @private + */ + _isRetinaScaling: function() { + return (fabric.devicePixelRatio !== 1 && this.enableRetinaScaling); + }, + + /** + * @private + * @return {Number} retinaScaling if applied, otherwise 1; + */ + getRetinaScaling: function() { + return this._isRetinaScaling() ? fabric.devicePixelRatio : 1; + }, + + /** + * @private + */ + _initRetinaScaling: function() { + if (!this._isRetinaScaling()) { + return; + } + var scaleRatio = fabric.devicePixelRatio; + this.__initRetinaScaling(scaleRatio, this.lowerCanvasEl, this.contextContainer); + if (this.upperCanvasEl) { + this.__initRetinaScaling(scaleRatio, this.upperCanvasEl, this.contextTop); + } + }, + + __initRetinaScaling: function(scaleRatio, canvas, context) { + canvas.setAttribute('width', this.width * scaleRatio); + canvas.setAttribute('height', this.height * scaleRatio); + context.scale(scaleRatio, scaleRatio); + }, + + + /** + * Calculates canvas element offset relative to the document + * This method is also attached as "resize" event handler of window + * @return {fabric.Canvas} instance + * @chainable + */ + calcOffset: function () { + this._offset = getElementOffset(this.lowerCanvasEl); + return this; + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayImage|overlay image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set overlay to + * @param {Function} callback callback to invoke when image is loaded and set as an overlay + * @param {Object} [options] Optional options to set for the {@link fabric.Image|overlay image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/MnzHT/|jsFiddle demo} + * @example Normal overlayImage with left/top = 0 + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example overlayImage with different properties + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched overlayImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/jail_cell_bars.png', function(img, isError) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setOverlayImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched overlayImage #2 - width/height correspond to canvas width/height + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position overlayImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example overlayImage loaded from cross-origin + * canvas.setOverlayImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top', + * crossOrigin: 'anonymous' + * }); + */ + setOverlayImage: function (image, callback, options) { + return this.__setBgOverlayImage('overlayImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundImage|background image} for this canvas + * @param {(fabric.Image|String)} image fabric.Image instance or URL of an image to set background to + * @param {Function} callback Callback to invoke when image is loaded and set as background + * @param {Object} [options] Optional options to set for the {@link fabric.Image|background image}. + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/djnr8o7a/28/|jsFiddle demo} + * @example Normal backgroundImage with left/top = 0 + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example backgroundImage with different properties + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top' + * }); + * @example Stretched backgroundImage #1 - width/height correspond to canvas width/height + * fabric.Image.fromURL('http://fabricjs.com/assets/honey_im_subtle.png', function(img, isError) { + * img.set({width: canvas.width, height: canvas.height, originX: 'left', originY: 'top'}); + * canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas)); + * }); + * @example Stretched backgroundImage #2 - width/height correspond to canvas width/height + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * width: canvas.width, + * height: canvas.height, + * // Needed to position backgroundImage at 0/0 + * originX: 'left', + * originY: 'top' + * }); + * @example backgroundImage loaded from cross-origin + * canvas.setBackgroundImage('http://fabricjs.com/assets/honey_im_subtle.png', canvas.renderAll.bind(canvas), { + * opacity: 0.5, + * angle: 45, + * left: 400, + * top: 400, + * originX: 'left', + * originY: 'top', + * crossOrigin: 'anonymous' + * }); + */ + // TODO: fix stretched examples + setBackgroundImage: function (image, callback, options) { + return this.__setBgOverlayImage('backgroundImage', image, callback, options); + }, + + /** + * Sets {@link fabric.StaticCanvas#overlayColor|foreground color} for this canvas + * @param {(String|fabric.Pattern)} overlayColor Color or pattern to set foreground color to + * @param {Function} callback Callback to invoke when foreground color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/pB55h/|jsFiddle demo} + * @example Normal overlayColor - color value + * canvas.setOverlayColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as overlayColor with repeat and offset + * canvas.setOverlayColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setOverlayColor: function(overlayColor, callback) { + return this.__setBgOverlayColor('overlayColor', overlayColor, callback); + }, + + /** + * Sets {@link fabric.StaticCanvas#backgroundColor|background color} for this canvas + * @param {(String|fabric.Pattern)} backgroundColor Color or pattern to set background color to + * @param {Function} callback Callback to invoke when background color is set + * @return {fabric.Canvas} thisArg + * @chainable + * @see {@link http://jsfiddle.net/fabricjs/hXzvk/|jsFiddle demo} + * @example Normal backgroundColor - color value + * canvas.setBackgroundColor('rgba(255, 73, 64, 0.6)', canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png' + * }, canvas.renderAll.bind(canvas)); + * @example fabric.Pattern used as backgroundColor with repeat and offset + * canvas.setBackgroundColor({ + * source: 'http://fabricjs.com/assets/escheresque_ste.png', + * repeat: 'repeat', + * offsetX: 200, + * offsetY: 100 + * }, canvas.renderAll.bind(canvas)); + */ + setBackgroundColor: function(backgroundColor, callback) { + return this.__setBgOverlayColor('backgroundColor', backgroundColor, callback); + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundImage|backgroundImage} + * or {@link fabric.StaticCanvas#overlayImage|overlayImage}) + * @param {(fabric.Image|String|null)} image fabric.Image instance, URL of an image or null to set background or overlay to + * @param {Function} callback Callback to invoke when image is loaded and set as background or overlay. The first argument is the created image, the second argument is a flag indicating whether an error occurred or not. + * @param {Object} [options] Optional options to set for the {@link fabric.Image|image}. + */ + __setBgOverlayImage: function(property, image, callback, options) { + if (typeof image === 'string') { + fabric.util.loadImage(image, function(img, isError) { + if (img) { + var instance = new fabric.Image(img, options); + this[property] = instance; + instance.canvas = this; + } + callback && callback(img, isError); + }, this, options && options.crossOrigin); + } + else { + options && image.setOptions(options); + this[property] = image; + image && (image.canvas = this); + callback && callback(image, false); + } + + return this; + }, + + /** + * @private + * @param {String} property Property to set ({@link fabric.StaticCanvas#backgroundColor|backgroundColor} + * or {@link fabric.StaticCanvas#overlayColor|overlayColor}) + * @param {(Object|String|null)} color Object with pattern information, color value or null + * @param {Function} [callback] Callback is invoked when color is set + */ + __setBgOverlayColor: function(property, color, callback) { + this[property] = color; + this._initGradient(color, property); + this._initPattern(color, property, callback); + return this; + }, + + /** + * @private + */ + _createCanvasElement: function() { + var element = createCanvasElement(); + if (!element) { + throw CANVAS_INIT_ERROR; + } + if (!element.style) { + element.style = { }; + } + if (typeof element.getContext === 'undefined') { + throw CANVAS_INIT_ERROR; + } + return element; + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initOptions: function (options) { + var lowerCanvasEl = this.lowerCanvasEl; + this._setOptions(options); + + this.width = this.width || parseInt(lowerCanvasEl.width, 10) || 0; + this.height = this.height || parseInt(lowerCanvasEl.height, 10) || 0; + + if (!this.lowerCanvasEl.style) { + return; + } + + lowerCanvasEl.width = this.width; + lowerCanvasEl.height = this.height; + + lowerCanvasEl.style.width = this.width + 'px'; + lowerCanvasEl.style.height = this.height + 'px'; + + this.viewportTransform = this.viewportTransform.slice(); + }, + + /** + * Creates a bottom canvas + * @private + * @param {HTMLElement} [canvasEl] + */ + _createLowerCanvas: function (canvasEl) { + // canvasEl === 'HTMLCanvasElement' does not work on jsdom/node + if (canvasEl && canvasEl.getContext) { + this.lowerCanvasEl = canvasEl; + } + else { + this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement(); + } + + fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas'); + this._originalCanvasStyle = this.lowerCanvasEl.style; + if (this.interactive) { + this._applyCanvasStyle(this.lowerCanvasEl); + } + + this.contextContainer = this.lowerCanvasEl.getContext('2d'); + }, + + /** + * Returns canvas width (in px) + * @return {Number} + */ + getWidth: function () { + return this.width; + }, + + /** + * Returns canvas height (in px) + * @return {Number} + */ + getHeight: function () { + return this.height; + }, + + /** + * Sets width of this canvas instance + * @param {Number|String} value Value to set width to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setWidth: function (value, options) { + return this.setDimensions({ width: value }, options); + }, + + /** + * Sets height of this canvas instance + * @param {Number|String} value Value to set height to + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} instance + * @chainable true + */ + setHeight: function (value, options) { + return this.setDimensions({ height: value }, options); + }, + + /** + * Sets dimensions (width, height) of this canvas instance. when options.cssOnly flag active you should also supply the unit of measure (px/%/em) + * @param {Object} dimensions Object with width/height properties + * @param {Number|String} [dimensions.width] Width of canvas element + * @param {Number|String} [dimensions.height] Height of canvas element + * @param {Object} [options] Options object + * @param {Boolean} [options.backstoreOnly=false] Set the given dimensions only as canvas backstore dimensions + * @param {Boolean} [options.cssOnly=false] Set the given dimensions only as css dimensions + * @return {fabric.Canvas} thisArg + * @chainable + */ + setDimensions: function (dimensions, options) { + var cssValue; + + options = options || {}; + + for (var prop in dimensions) { + cssValue = dimensions[prop]; + + if (!options.cssOnly) { + this._setBackstoreDimension(prop, dimensions[prop]); + cssValue += 'px'; + this.hasLostContext = true; + } + + if (!options.backstoreOnly) { + this._setCssDimension(prop, cssValue); + } + } + if (this._isCurrentlyDrawing) { + this.freeDrawingBrush && this.freeDrawingBrush._setBrushStyles(); + } + this._initRetinaScaling(); + this.calcOffset(); + + if (!options.cssOnly) { + this.requestRenderAll(); + } + + return this; + }, + + /** + * Helper for setting width/height + * @private + * @param {String} prop property (width|height) + * @param {Number} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setBackstoreDimension: function (prop, value) { + this.lowerCanvasEl[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl[prop] = value; + } + + if (this.cacheCanvasEl) { + this.cacheCanvasEl[prop] = value; + } + + this[prop] = value; + + return this; + }, + + /** + * Helper for setting css width/height + * @private + * @param {String} prop property (width|height) + * @param {String} value value to set property to + * @return {fabric.Canvas} instance + * @chainable true + */ + _setCssDimension: function (prop, value) { + this.lowerCanvasEl.style[prop] = value; + + if (this.upperCanvasEl) { + this.upperCanvasEl.style[prop] = value; + } + + if (this.wrapperEl) { + this.wrapperEl.style[prop] = value; + } + + return this; + }, + + /** + * Returns canvas zoom level + * @return {Number} + */ + getZoom: function () { + return this.viewportTransform[0]; + }, + + /** + * Sets viewport transform of this canvas instance + * @param {Array} vpt the transform in the form of context.transform + * @return {fabric.Canvas} instance + * @chainable true + */ + setViewportTransform: function (vpt) { + var activeObject = this._activeObject, + backgroundObject = this.backgroundImage, + overlayObject = this.overlayImage, + object, i, len; + this.viewportTransform = vpt; + for (i = 0, len = this._objects.length; i < len; i++) { + object = this._objects[i]; + object.group || object.setCoords(true); + } + if (activeObject) { + activeObject.setCoords(); + } + if (backgroundObject) { + backgroundObject.setCoords(true); + } + if (overlayObject) { + overlayObject.setCoords(true); + } + this.calcViewportBoundaries(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Sets zoom level of this canvas instance, the zoom centered around point + * meaning that following zoom to point with the same point will have the visual + * effect of the zoom originating from that point. The point won't move. + * It has nothing to do with canvas center or visual center of the viewport. + * @param {fabric.Point} point to zoom with respect to + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + zoomToPoint: function (point, value) { + // TODO: just change the scale, preserve other transformations + var before = point, vpt = this.viewportTransform.slice(0); + point = transformPoint(point, invertTransform(this.viewportTransform)); + vpt[0] = value; + vpt[3] = value; + var after = transformPoint(point, vpt); + vpt[4] += before.x - after.x; + vpt[5] += before.y - after.y; + return this.setViewportTransform(vpt); + }, + + /** + * Sets zoom level of this canvas instance + * @param {Number} value to set zoom to, less than 1 zooms out + * @return {fabric.Canvas} instance + * @chainable true + */ + setZoom: function (value) { + this.zoomToPoint(new fabric.Point(0, 0), value); + return this; + }, + + /** + * Pan viewport so as to place point at top left corner of canvas + * @param {fabric.Point} point to move to + * @return {fabric.Canvas} instance + * @chainable true + */ + absolutePan: function (point) { + var vpt = this.viewportTransform.slice(0); + vpt[4] = -point.x; + vpt[5] = -point.y; + return this.setViewportTransform(vpt); + }, + + /** + * Pans viewpoint relatively + * @param {fabric.Point} point (position vector) to move by + * @return {fabric.Canvas} instance + * @chainable true + */ + relativePan: function (point) { + return this.absolutePan(new fabric.Point( + -point.x - this.viewportTransform[4], + -point.y - this.viewportTransform[5] + )); + }, + + /** + * Returns <canvas> element corresponding to this instance + * @return {HTMLCanvasElement} + */ + getElement: function () { + return this.lowerCanvasEl; + }, + + /** + * @private + * @param {fabric.Object} obj Object that was added + */ + _onObjectAdded: function(obj) { + this.stateful && obj.setupState(); + obj._set('canvas', this); + obj.setCoords(); + this.fire('object:added', { target: obj }); + obj.fire('added'); + }, + + /** + * @private + * @param {fabric.Object} obj Object that was removed + */ + _onObjectRemoved: function(obj) { + this.fire('object:removed', { target: obj }); + obj.fire('removed'); + delete obj.canvas; + }, + + /** + * Clears specified context of canvas element + * @param {CanvasRenderingContext2D} ctx Context to clear + * @return {fabric.Canvas} thisArg + * @chainable + */ + clearContext: function(ctx) { + ctx.clearRect(0, 0, this.width, this.height); + return this; + }, + + /** + * Returns context of canvas where objects are drawn + * @return {CanvasRenderingContext2D} + */ + getContext: function () { + return this.contextContainer; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + this.remove.apply(this, this.getObjects()); + this.backgroundImage = null; + this.overlayImage = null; + this.backgroundColor = ''; + this.overlayColor = ''; + if (this._hasITextHandlers) { + this.off('mouse:up', this._mouseUpITextHandler); + this._iTextInstances = null; + this._hasITextHandlers = false; + } + this.clearContext(this.contextContainer); + this.fire('canvas:cleared'); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Renders the canvas + * @return {fabric.Canvas} instance + * @chainable + */ + renderAll: function () { + var canvasToDrawOn = this.contextContainer; + this.renderCanvas(canvasToDrawOn, this._objects); + + return this; + }, + + /** + * Function created to be instance bound at initialization + * used in requestAnimationFrame rendering + * Let the fabricJS call it. If you call it manually you could have more + * animationFrame stacking on to of each other + * for an imperative rendering, use canvas.renderAll + * @private + * @return {fabric.Canvas} instance + * @chainable + */ + renderAndReset: function() { + this.isRendering = 0; + this.renderAll(); + }, + + /** + * Append a renderAll request to next animation frame. + * unless one is already in progress, in that case nothing is done + * a boolean flag will avoid appending more. + * @return {fabric.Canvas} instance + * @chainable + */ + requestRenderAll: function () { + if (!this.isRendering) { + this.isRendering = fabric.util.requestAnimFrame(this.renderAndResetBound); + } + return this; + }, + + /** + * Calculate the position of the 4 corner of canvas with current viewportTransform. + * helps to determinate when an object is in the current rendering viewport using + * object absolute coordinates ( aCoords ) + * @return {Object} points.tl + * @chainable + */ + calcViewportBoundaries: function() { + var points = { }, width = this.width, height = this.height, + iVpt = invertTransform(this.viewportTransform); + points.tl = transformPoint({ x: 0, y: 0 }, iVpt); + points.br = transformPoint({ x: width, y: height }, iVpt); + points.tr = new fabric.Point(points.br.x, points.tl.y); + points.bl = new fabric.Point(points.tl.x, points.br.y); + this.vptCoords = points; + return points; + }, + + cancelRequestedRender: function() { + if (this.isRendering) { + fabric.util.cancelAnimFrame(this.isRendering); + this.isRendering = 0; + } + }, + + /** + * Renders background, objects, overlay and controls. + * @param {CanvasRenderingContext2D} ctx + * @param {Array} objects to render + * @return {fabric.Canvas} instance + * @chainable + */ + renderCanvas: function(ctx, objects) { + var v = this.viewportTransform, path = this.clipPath; + this.cancelRequestedRender(); + this.calcViewportBoundaries(); + this.clearContext(ctx); + fabric.util.setImageSmoothing(ctx, this.imageSmoothingEnabled); + this.fire('before:render', { ctx: ctx, }); + this._renderBackground(ctx); + + ctx.save(); + //apply viewport transform once for all rendering process + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + this._renderObjects(ctx, objects); + ctx.restore(); + if (!this.controlsAboveOverlay && this.interactive) { + this.drawControls(ctx); + } + if (path) { + path.canvas = this; + // needed to setup a couple of variables + path.shouldCache(); + path._transformDone = true; + path.renderCache({ forClipping: true }); + this.drawClipPathOnCanvas(ctx); + } + this._renderOverlay(ctx); + if (this.controlsAboveOverlay && this.interactive) { + this.drawControls(ctx); + } + this.fire('after:render', { ctx: ctx, }); + }, + + /** + * Paint the cached clipPath on the lowerCanvasEl + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawClipPathOnCanvas: function(ctx) { + var v = this.viewportTransform, path = this.clipPath; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + // DEBUG: uncomment this line, comment the following + // ctx.globalAlpha = 0.4; + ctx.globalCompositeOperation = 'destination-in'; + path.transform(ctx); + ctx.scale(1 / path.zoomX, 1 / path.zoomY); + ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} objects to render + */ + _renderObjects: function(ctx, objects) { + var i, len; + for (i = 0, len = objects.length; i < len; ++i) { + objects[i] && objects[i].render(ctx); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {string} property 'background' or 'overlay' + */ + _renderBackgroundOrOverlay: function(ctx, property) { + var fill = this[property + 'Color'], object = this[property + 'Image'], + v = this.viewportTransform, needsVpt = this[property + 'Vpt']; + if (!fill && !object) { + return; + } + if (fill) { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(this.width, 0); + ctx.lineTo(this.width, this.height); + ctx.lineTo(0, this.height); + ctx.closePath(); + ctx.fillStyle = fill.toLive + ? fill.toLive(ctx, this) + : fill; + if (needsVpt) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } + ctx.transform(1, 0, 0, 1, fill.offsetX || 0, fill.offsetY || 0); + var m = fill.gradientTransform || fill.patternTransform; + m && ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + ctx.fill(); + ctx.restore(); + } + if (object) { + ctx.save(); + if (needsVpt) { + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + } + object.render(ctx); + ctx.restore(); + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderBackground: function(ctx) { + this._renderBackgroundOrOverlay(ctx, 'background'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderOverlay: function(ctx) { + this._renderBackgroundOrOverlay(ctx, 'overlay'); + }, + + /** + * Returns coordinates of a center of canvas. + * Returned value is an object with top and left properties + * @return {Object} object with "top" and "left" number values + */ + getCenter: function () { + return { + top: this.height / 2, + left: this.width / 2 + }; + }, + + /** + * Centers object horizontally in the canvas + * @param {fabric.Object} object Object to center horizontally + * @return {fabric.Canvas} thisArg + */ + centerObjectH: function (object) { + return this._centerObject(object, new fabric.Point(this.getCenter().left, object.getCenterPoint().y)); + }, + + /** + * Centers object vertically in the canvas + * @param {fabric.Object} object Object to center vertically + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObjectV: function (object) { + return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, this.getCenter().top)); + }, + + /** + * Centers object vertically and horizontally in the canvas + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + centerObject: function(object) { + var center = this.getCenter(); + + return this._centerObject(object, new fabric.Point(center.left, center.top)); + }, + + /** + * Centers object vertically and horizontally in the viewport + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObject: function(object) { + var vpCenter = this.getVpCenter(); + + return this._centerObject(object, vpCenter); + }, + + /** + * Centers object horizontally in the viewport, object.top is unchanged + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObjectH: function(object) { + var vpCenter = this.getVpCenter(); + this._centerObject(object, new fabric.Point(vpCenter.x, object.getCenterPoint().y)); + return this; + }, + + /** + * Centers object Vertically in the viewport, object.top is unchanged + * @param {fabric.Object} object Object to center vertically and horizontally + * @return {fabric.Canvas} thisArg + * @chainable + */ + viewportCenterObjectV: function(object) { + var vpCenter = this.getVpCenter(); + + return this._centerObject(object, new fabric.Point(object.getCenterPoint().x, vpCenter.y)); + }, + + /** + * Calculate the point in canvas that correspond to the center of actual viewport. + * @return {fabric.Point} vpCenter, viewport center + * @chainable + */ + getVpCenter: function() { + var center = this.getCenter(), + iVpt = invertTransform(this.viewportTransform); + return transformPoint({ x: center.left, y: center.top }, iVpt); + }, + + /** + * @private + * @param {fabric.Object} object Object to center + * @param {fabric.Point} center Center point + * @return {fabric.Canvas} thisArg + * @chainable + */ + _centerObject: function(object, center) { + object.setPositionByOrigin(center, 'center', 'center'); + object.setCoords(); + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Returns dataless JSON representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {String} json string + */ + toDatalessJSON: function (propertiesToInclude) { + return this.toDatalessObject(propertiesToInclude); + }, + + /** + * Returns object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function (propertiesToInclude) { + return this._toObjectMethod('toObject', propertiesToInclude); + }, + + /** + * Returns dataless object representation of canvas + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function (propertiesToInclude) { + return this._toObjectMethod('toDatalessObject', propertiesToInclude); + }, + + /** + * @private + */ + _toObjectMethod: function (methodName, propertiesToInclude) { + + var clipPath = this.clipPath, data = { + version: fabric.version, + objects: this._toObjects(methodName, propertiesToInclude), + }; + if (clipPath && !clipPath.excludeFromExport) { + data.clipPath = this._toObject(this.clipPath, methodName, propertiesToInclude); + } + extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude)); + + fabric.util.populateWithProperties(this, data, propertiesToInclude); + + return data; + }, + + /** + * @private + */ + _toObjects: function(methodName, propertiesToInclude) { + return this._objects.filter(function(object) { + return !object.excludeFromExport; + }).map(function(instance) { + return this._toObject(instance, methodName, propertiesToInclude); + }, this); + }, + + /** + * @private + */ + _toObject: function(instance, methodName, propertiesToInclude) { + var originalValue; + + if (!this.includeDefaultValues) { + originalValue = instance.includeDefaultValues; + instance.includeDefaultValues = false; + } + + var object = instance[methodName](propertiesToInclude); + if (!this.includeDefaultValues) { + instance.includeDefaultValues = originalValue; + } + return object; + }, + + /** + * @private + */ + __serializeBgOverlay: function(methodName, propertiesToInclude) { + var data = {}, bgImage = this.backgroundImage, overlayImage = this.overlayImage, + bgColor = this.backgroundColor, overlayColor = this.overlayColor; + + if (bgColor && bgColor.toObject) { + if (!bgColor.excludeFromExport) { + data.background = bgColor.toObject(propertiesToInclude); + } + } + else if (bgColor) { + data.background = bgColor; + } + + if (overlayColor && overlayColor.toObject) { + if (!overlayColor.excludeFromExport) { + data.overlay = overlayColor.toObject(propertiesToInclude); + } + } + else if (overlayColor) { + data.overlay = overlayColor; + } + + if (bgImage && !bgImage.excludeFromExport) { + data.backgroundImage = this._toObject(bgImage, methodName, propertiesToInclude); + } + if (overlayImage && !overlayImage.excludeFromExport) { + data.overlayImage = this._toObject(overlayImage, methodName, propertiesToInclude); + } + + return data; + }, + + /* _TO_SVG_START_ */ + /** + * When true, getSvgTransform() will apply the StaticCanvas.viewportTransform to the SVG transformation. When true, + * a zoomed canvas will then produce zoomed SVG output. + * @type Boolean + * @default + */ + svgViewportTransformation: true, + + /** + * Returns SVG representation of canvas + * @function + * @param {Object} [options] Options object for SVG output + * @param {Boolean} [options.suppressPreamble=false] If true xml tag is not included + * @param {Object} [options.viewBox] SVG viewbox object + * @param {Number} [options.viewBox.x] x-coordinate of viewbox + * @param {Number} [options.viewBox.y] y-coordinate of viewbox + * @param {Number} [options.viewBox.width] Width of viewbox + * @param {Number} [options.viewBox.height] Height of viewbox + * @param {String} [options.encoding=UTF-8] Encoding of SVG output + * @param {String} [options.width] desired width of svg with or without units + * @param {String} [options.height] desired height of svg with or without units + * @param {Function} [reviver] Method for further parsing of svg elements, called after each fabric object converted into svg representation. + * @return {String} SVG string + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} + * @see {@link http://jsfiddle.net/fabricjs/jQ3ZZ/|jsFiddle demo} + * @example Normal SVG output + * var svg = canvas.toSVG(); + * @example SVG output without preamble (without <?xml ../>) + * var svg = canvas.toSVG({suppressPreamble: true}); + * @example SVG output with viewBox attribute + * var svg = canvas.toSVG({ + * viewBox: { + * x: 100, + * y: 100, + * width: 200, + * height: 300 + * } + * }); + * @example SVG output with different encoding (default: UTF-8) + * var svg = canvas.toSVG({encoding: 'ISO-8859-1'}); + * @example Modify SVG output with reviver function + * var svg = canvas.toSVG(null, function(svg) { + * return svg.replace('stroke-dasharray: ; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; ', ''); + * }); + */ + toSVG: function(options, reviver) { + options || (options = { }); + options.reviver = reviver; + var markup = []; + + this._setSVGPreamble(markup, options); + this._setSVGHeader(markup, options); + if (this.clipPath) { + markup.push('\n'); + } + this._setSVGBgOverlayColor(markup, 'background'); + this._setSVGBgOverlayImage(markup, 'backgroundImage', reviver); + this._setSVGObjects(markup, reviver); + if (this.clipPath) { + markup.push('\n'); + } + this._setSVGBgOverlayColor(markup, 'overlay'); + this._setSVGBgOverlayImage(markup, 'overlayImage', reviver); + + markup.push(''); + + return markup.join(''); + }, + + /** + * @private + */ + _setSVGPreamble: function(markup, options) { + if (options.suppressPreamble) { + return; + } + markup.push( + '\n', + '\n' + ); + }, + + /** + * @private + */ + _setSVGHeader: function(markup, options) { + var width = options.width || this.width, + height = options.height || this.height, + vpt, viewBox = 'viewBox="0 0 ' + this.width + ' ' + this.height + '" ', + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + + if (options.viewBox) { + viewBox = 'viewBox="' + + options.viewBox.x + ' ' + + options.viewBox.y + ' ' + + options.viewBox.width + ' ' + + options.viewBox.height + '" '; + } + else { + if (this.svgViewportTransformation) { + vpt = this.viewportTransform; + viewBox = 'viewBox="' + + toFixed(-vpt[4] / vpt[0], NUM_FRACTION_DIGITS) + ' ' + + toFixed(-vpt[5] / vpt[3], NUM_FRACTION_DIGITS) + ' ' + + toFixed(this.width / vpt[0], NUM_FRACTION_DIGITS) + ' ' + + toFixed(this.height / vpt[3], NUM_FRACTION_DIGITS) + '" '; + } + } + + markup.push( + '\n', + 'Created with Fabric.js ', fabric.version, '\n', + '\n', + this.createSVGFontFacesMarkup(), + this.createSVGRefElementsMarkup(), + this.createSVGClipPathMarkup(options), + '\n' + ); + }, + + createSVGClipPathMarkup: function(options) { + var clipPath = this.clipPath; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + return '\n' + + this.clipPath.toClipPathSVG(options.reviver) + + '\n'; + } + return ''; + }, + + /** + * Creates markup containing SVG referenced elements like patterns, gradients etc. + * @return {String} + */ + createSVGRefElementsMarkup: function() { + var _this = this, + markup = ['background', 'overlay'].map(function(prop) { + var fill = _this[prop + 'Color']; + if (fill && fill.toLive) { + var shouldTransform = _this[prop + 'Vpt'], vpt = _this.viewportTransform, + object = { + width: _this.width / (shouldTransform ? vpt[0] : 1), + height: _this.height / (shouldTransform ? vpt[3] : 1) + }; + return fill.toSVG( + object, + { additionalTransform: shouldTransform ? fabric.util.matrixToSVG(vpt) : '' } + ); + } + }); + return markup.join(''); + }, + + /** + * Creates markup containing SVG font faces, + * font URLs for font faces must be collected by developers + * and are not extracted from the DOM by fabricjs + * @param {Array} objects Array of fabric objects + * @return {String} + */ + createSVGFontFacesMarkup: function() { + var markup = '', fontList = { }, obj, fontFamily, + style, row, rowIndex, _char, charIndex, i, len, + fontPaths = fabric.fontPaths, objects = []; + + this._objects.forEach(function add(object) { + objects.push(object); + if (object._objects) { + object._objects.forEach(add); + } + }); + + for (i = 0, len = objects.length; i < len; i++) { + obj = objects[i]; + fontFamily = obj.fontFamily; + if (obj.type.indexOf('text') === -1 || fontList[fontFamily] || !fontPaths[fontFamily]) { + continue; + } + fontList[fontFamily] = true; + if (!obj.styles) { + continue; + } + style = obj.styles; + for (rowIndex in style) { + row = style[rowIndex]; + for (charIndex in row) { + _char = row[charIndex]; + fontFamily = _char.fontFamily; + if (!fontList[fontFamily] && fontPaths[fontFamily]) { + fontList[fontFamily] = true; + } + } + } + } + + for (var j in fontList) { + markup += [ + '\t\t@font-face {\n', + '\t\t\tfont-family: \'', j, '\';\n', + '\t\t\tsrc: url(\'', fontPaths[j], '\');\n', + '\t\t}\n' + ].join(''); + } + + if (markup) { + markup = [ + '\t\n' + ].join(''); + } + + return markup; + }, + + /** + * @private + */ + _setSVGObjects: function(markup, reviver) { + var instance, i, len, objects = this._objects; + for (i = 0, len = objects.length; i < len; i++) { + instance = objects[i]; + if (instance.excludeFromExport) { + continue; + } + this._setSVGObject(markup, instance, reviver); + } + }, + + /** + * @private + */ + _setSVGObject: function(markup, instance, reviver) { + markup.push(instance.toSVG(reviver)); + }, + + /** + * @private + */ + _setSVGBgOverlayImage: function(markup, property, reviver) { + if (this[property] && !this[property].excludeFromExport && this[property].toSVG) { + markup.push(this[property].toSVG(reviver)); + } + }, + + /** + * @private + */ + _setSVGBgOverlayColor: function(markup, property) { + var filler = this[property + 'Color'], vpt = this.viewportTransform, finalWidth = this.width, + finalHeight = this.height; + if (!filler) { + return; + } + if (filler.toLive) { + var repeat = filler.repeat, iVpt = fabric.util.invertTransform(vpt), shouldInvert = this[property + 'Vpt'], + additionalTransform = shouldInvert ? fabric.util.matrixToSVG(iVpt) : ''; + markup.push( + '\n' + ); + } + else { + markup.push( + '\n' + ); + } + }, + /* _TO_SVG_END_ */ + + /** + * Moves an object or the objects of a multiple selection + * to the bottom of the stack of drawn objects + * @param {fabric.Object} object Object to send to back + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendToBack: function (object) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, objs; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = objs.length; i--;) { + obj = objs[i]; + removeFromArray(this._objects, obj); + this._objects.unshift(obj); + } + } + else { + removeFromArray(this._objects, object); + this._objects.unshift(object); + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Moves an object or the objects of a multiple selection + * to the top of the stack of drawn objects + * @param {fabric.Object} object Object to send + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringToFront: function (object) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, objs; + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = 0; i < objs.length; i++) { + obj = objs[i]; + removeFromArray(this._objects, obj); + this._objects.push(obj); + } + } + else { + removeFromArray(this._objects, object); + this._objects.push(object); + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * Moves an object or a selection down in stack of drawn objects + * An optional parameter, intersecting allows to move the object in behind + * the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + sendBackwards: function (object, intersecting) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, idx, newIdx, objs, objsMoved = 0; + + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = 0; i < objs.length; i++) { + obj = objs[i]; + idx = this._objects.indexOf(obj); + if (idx > 0 + objsMoved) { + newIdx = idx - 1; + removeFromArray(this._objects, obj); + this._objects.splice(newIdx, 0, obj); + } + objsMoved++; + } + } + else { + idx = this._objects.indexOf(object); + if (idx !== 0) { + // if object is not on the bottom of stack + newIdx = this._findNewLowerIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * @private + */ + _findNewLowerIndex: function(object, idx, intersecting) { + var newIdx, i; + + if (intersecting) { + newIdx = idx; + + // traverse down the stack looking for the nearest intersecting object + for (i = idx - 1; i >= 0; --i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx - 1; + } + + return newIdx; + }, + + /** + * Moves an object or a selection up in stack of drawn objects + * An optional parameter, intersecting allows to move the object in front + * of the first intersecting object. Where intersection is calculated with + * bounding box. If no intersection is found, there will not be change in the + * stack. + * @param {fabric.Object} object Object to send + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Canvas} thisArg + * @chainable + */ + bringForward: function (object, intersecting) { + if (!object) { + return this; + } + var activeSelection = this._activeObject, + i, obj, idx, newIdx, objs, objsMoved = 0; + + if (object === activeSelection && object.type === 'activeSelection') { + objs = activeSelection._objects; + for (i = objs.length; i--;) { + obj = objs[i]; + idx = this._objects.indexOf(obj); + if (idx < this._objects.length - 1 - objsMoved) { + newIdx = idx + 1; + removeFromArray(this._objects, obj); + this._objects.splice(newIdx, 0, obj); + } + objsMoved++; + } + } + else { + idx = this._objects.indexOf(object); + if (idx !== this._objects.length - 1) { + // if object is not on top of stack (last item in an array) + newIdx = this._findNewUpperIndex(object, idx, intersecting); + removeFromArray(this._objects, object); + this._objects.splice(newIdx, 0, object); + } + } + this.renderOnAddRemove && this.requestRenderAll(); + return this; + }, + + /** + * @private + */ + _findNewUpperIndex: function(object, idx, intersecting) { + var newIdx, i, len; + + if (intersecting) { + newIdx = idx; + + // traverse up the stack looking for the nearest intersecting object + for (i = idx + 1, len = this._objects.length; i < len; ++i) { + + var isIntersecting = object.intersectsWithObject(this._objects[i]) || + object.isContainedWithinObject(this._objects[i]) || + this._objects[i].isContainedWithinObject(object); + + if (isIntersecting) { + newIdx = i; + break; + } + } + } + else { + newIdx = idx + 1; + } + + return newIdx; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {fabric.Object} object Object to send + * @param {Number} index Position to move to + * @return {fabric.Canvas} thisArg + * @chainable + */ + moveTo: function (object, index) { + removeFromArray(this._objects, object); + this._objects.splice(index, 0, object); + return this.renderOnAddRemove && this.requestRenderAll(); + }, + + /** + * Clears a canvas element and dispose objects + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + // cancel eventually ongoing renders + if (this.isRendering) { + fabric.util.cancelAnimFrame(this.isRendering); + this.isRendering = 0; + } + this.forEachObject(function(object) { + object.dispose && object.dispose(); + }); + this._objects = []; + if (this.backgroundImage && this.backgroundImage.dispose) { + this.backgroundImage.dispose(); + } + this.backgroundImage = null; + if (this.overlayImage && this.overlayImage.dispose) { + this.overlayImage.dispose(); + } + this.overlayImage = null; + this._iTextInstances = null; + this.contextContainer = null; + // restore canvas style + this.lowerCanvasEl.classList.remove('lower-canvas'); + this.lowerCanvasEl.style = this._originalCanvasStyle; + delete this._originalCanvasStyle; + // restore canvas size to original size in case retina scaling was applied + this.lowerCanvasEl.setAttribute('width', this.width); + this.lowerCanvasEl.setAttribute('height', this.height); + fabric.util.cleanUpJsdomNode(this.lowerCanvasEl); + this.lowerCanvasEl = undefined; + return this; + }, + + /** + * Returns a string representation of an instance + * @return {String} string representation of an instance + */ + toString: function () { + return '#'; + } + }); + + extend(fabric.StaticCanvas.prototype, fabric.Observable); + extend(fabric.StaticCanvas.prototype, fabric.Collection); + extend(fabric.StaticCanvas.prototype, fabric.DataURLExporter); + + extend(fabric.StaticCanvas, /** @lends fabric.StaticCanvas */ { + + /** + * @static + * @type String + * @default + */ + EMPTY_JSON: '{"objects": [], "background": "white"}', + + /** + * Provides a way to check support of some of the canvas methods + * (either those of HTMLCanvasElement itself, or rendering context) + * + * @param {String} methodName Method to check support for; + * Could be one of "setLineDash" + * @return {Boolean | null} `true` if method is supported (or at least exists), + * `null` if canvas element or context can not be initialized + */ + supports: function (methodName) { + var el = createCanvasElement(); + + if (!el || !el.getContext) { + return null; + } + + var ctx = el.getContext('2d'); + if (!ctx) { + return null; + } + + switch (methodName) { + + case 'setLineDash': + return typeof ctx.setLineDash !== 'undefined'; + + default: + return null; + } + } + }); + + /** + * Returns Object representation of canvas + * this alias is provided because if you call JSON.stringify on an instance, + * the toJSON object will be invoked if it exists. + * Having a toJSON method means you can do JSON.stringify(myCanvas) + * @function + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON compatible object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#serialization} + * @see {@link http://jsfiddle.net/fabricjs/pec86/|jsFiddle demo} + * @example JSON without additional properties + * var json = canvas.toJSON(); + * @example JSON with additional properties included + * var json = canvas.toJSON(['lockMovementX', 'lockMovementY', 'lockRotation', 'lockScalingX', 'lockScalingY']); + * @example JSON without default values + * canvas.includeDefaultValues = false; + * var json = canvas.toJSON(); + */ + fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject; + + if (fabric.isLikelyNode) { + fabric.StaticCanvas.prototype.createPNGStream = function() { + var impl = getNodeCanvas(this.lowerCanvasEl); + return impl && impl.createPNGStream(); + }; + fabric.StaticCanvas.prototype.createJPEGStream = function(opts) { + var impl = getNodeCanvas(this.lowerCanvasEl); + return impl && impl.createJPEGStream(opts); + }; + } +})(); + + +/** + * BaseBrush class + * @class fabric.BaseBrush + * @see {@link http://fabricjs.com/freedrawing|Freedrawing demo} + */ +fabric.BaseBrush = fabric.util.createClass(/** @lends fabric.BaseBrush.prototype */ { + + /** + * Color of a brush + * @type String + * @default + */ + color: 'rgb(0, 0, 0)', + + /** + * Width of a brush, has to be a Number, no string literals + * @type Number + * @default + */ + width: 1, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property replaces "shadowColor" (String), "shadowOffsetX" (Number), + * "shadowOffsetY" (Number) and "shadowBlur" (Number) since v1.2.12 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Line endings style of a brush (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'round', + + /** + * Corner style of a brush (one of "bevel", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'round', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of a brush's + * @type Number + * @default + */ + strokeMiterLimit: 10, + + /** + * Stroke Dash Array. + * @type Array + * @default + */ + strokeDashArray: null, + + /** + * When `true`, the free drawing is limited to the whiteboard size. Default to false. + * @type Boolean + * @default false + */ + + limitedToCanvasSize: false, + + + /** + * Sets brush styles + * @private + */ + _setBrushStyles: function() { + var ctx = this.canvas.contextTop; + ctx.strokeStyle = this.color; + ctx.lineWidth = this.width; + ctx.lineCap = this.strokeLineCap; + ctx.miterLimit = this.strokeMiterLimit; + ctx.lineJoin = this.strokeLineJoin; + ctx.setLineDash(this.strokeDashArray || []); + }, + + /** + * Sets the transformation on given context + * @param {RenderingContext2d} ctx context to render on + * @private + */ + _saveAndTransform: function(ctx) { + var v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + }, + + /** + * Sets brush shadow styles + * @private + */ + _setShadow: function() { + if (!this.shadow) { + return; + } + + var canvas = this.canvas, + shadow = this.shadow, + ctx = canvas.contextTop, + zoom = canvas.getZoom(); + if (canvas && canvas._isRetinaScaling()) { + zoom *= fabric.devicePixelRatio; + } + + ctx.shadowColor = shadow.color; + ctx.shadowBlur = shadow.blur * zoom; + ctx.shadowOffsetX = shadow.offsetX * zoom; + ctx.shadowOffsetY = shadow.offsetY * zoom; + }, + + needsFullRender: function() { + var color = new fabric.Color(this.color); + return color.getAlpha() < 1 || !!this.shadow; + }, + + /** + * Removes brush shadow styles + * @private + */ + _resetShadow: function() { + var ctx = this.canvas.contextTop; + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * Check is pointer is outside canvas boundaries + * @param {Object} pointer + * @private + */ + _isOutSideCanvas: function(pointer) { + return pointer.x < 0 || pointer.x > this.canvas.getWidth() || pointer.y < 0 || pointer.y > this.canvas.getHeight(); + } +}); + + +(function() { + /** + * PencilBrush class + * @class fabric.PencilBrush + * @extends fabric.BaseBrush + */ + fabric.PencilBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.PencilBrush.prototype */ { + + /** + * Discard points that are less than `decimate` pixel distant from each other + * @type Number + * @default 0.4 + */ + decimate: 0.4, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.PencilBrush} Instance of a pencil brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this._points = []; + }, + + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + _drawSegment: function (ctx, p1, p2) { + var midPoint = p1.midPointFrom(p2); + ctx.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y); + return midPoint; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer, options) { + if (!this.canvas._isMainEvent(options.e)) { + return; + } + this._prepareForDrawing(pointer); + // capture coordinates immediately + // this allows to draw dots (when movement never occurs) + this._captureDrawingPath(pointer); + this._render(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer, options) { + if (!this.canvas._isMainEvent(options.e)) { + return; + } + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + if (this._captureDrawingPath(pointer) && this._points.length > 1) { + if (this.needsFullRender()) { + // redraw curve + // clear top canvas + this.canvas.clearContext(this.canvas.contextTop); + this._render(); + } + else { + var points = this._points, length = points.length, ctx = this.canvas.contextTop; + // draw the curve update + this._saveAndTransform(ctx); + if (this.oldEnd) { + ctx.beginPath(); + ctx.moveTo(this.oldEnd.x, this.oldEnd.y); + } + this.oldEnd = this._drawSegment(ctx, points[length - 2], points[length - 1], true); + ctx.stroke(); + ctx.restore(); + } + } + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function(options) { + if (!this.canvas._isMainEvent(options.e)) { + return true; + } + this.oldEnd = undefined; + this._finalizeAndAddPath(); + return false; + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _prepareForDrawing: function(pointer) { + + var p = new fabric.Point(pointer.x, pointer.y); + + this._reset(); + this._addPoint(p); + this.canvas.contextTop.moveTo(p.x, p.y); + }, + + /** + * @private + * @param {fabric.Point} point Point to be added to points array + */ + _addPoint: function(point) { + if (this._points.length > 1 && point.eq(this._points[this._points.length - 1])) { + return false; + } + this._points.push(point); + return true; + }, + + /** + * Clear points array and set contextTop canvas style. + * @private + */ + _reset: function() { + this._points = []; + this._setBrushStyles(); + this._setShadow(); + }, + + /** + * @private + * @param {Object} pointer Actual mouse position related to the canvas. + */ + _captureDrawingPath: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y); + return this._addPoint(pointerPoint); + }, + + /** + * Draw a smooth path on the topCanvas using quadraticCurveTo + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, i, len, + p1 = this._points[0], + p2 = this._points[1]; + + this._saveAndTransform(ctx); + ctx.beginPath(); + //if we only have 2 points in the path and they are the same + //it means that the user only clicked the canvas without moving the mouse + //then we should be drawing a dot. A path isn't drawn between two identical dots + //that's why we set them apart a bit + if (this._points.length === 2 && p1.x === p2.x && p1.y === p2.y) { + var width = this.width / 1000; + p1 = new fabric.Point(p1.x, p1.y); + p2 = new fabric.Point(p2.x, p2.y); + p1.x -= width; + p2.x += width; + } + ctx.moveTo(p1.x, p1.y); + + for (i = 1, len = this._points.length; i < len; i++) { + // we pick the point between pi + 1 & pi + 2 as the + // end point and p1 as our control point. + this._drawSegment(ctx, p1, p2); + p1 = this._points[i]; + p2 = this._points[i + 1]; + } + // Draw last line as a straight line while + // we wait for the next point to be able to calculate + // the bezier control point + ctx.lineTo(p1.x, p1.y); + ctx.stroke(); + ctx.restore(); + }, + + /** + * Converts points to SVG path + * @param {Array} points Array of points + * @return {(string|number)[][]} SVG path commands + */ + convertPointsToSVGPath: function (points) { + var correction = this.width / 1000; + return fabric.util.getSmoothPathFromPoints(points, correction); + }, + + /** + * @private + * @param {(string|number)[][]} pathData SVG path commands + * @returns {boolean} + */ + _isEmptySVGPath: function (pathData) { + var pathString = fabric.util.joinPath(pathData); + return pathString === 'M 0 0 Q 0 0 0 0 L 0 0'; + }, + + /** + * Creates fabric.Path object to add on canvas + * @param {(string|number)[][]} pathData Path data + * @return {fabric.Path} Path to add on canvas + */ + createPath: function(pathData) { + var path = new fabric.Path(pathData, { + fill: null, + stroke: this.color, + strokeWidth: this.width, + strokeLineCap: this.strokeLineCap, + strokeMiterLimit: this.strokeMiterLimit, + strokeLineJoin: this.strokeLineJoin, + strokeDashArray: this.strokeDashArray, + }); + if (this.shadow) { + this.shadow.affectStroke = true; + path.shadow = new fabric.Shadow(this.shadow); + } + + return path; + }, + + /** + * Decimate points array with the decimate value + */ + decimatePoints: function(points, distance) { + if (points.length <= 2) { + return points; + } + var zoom = this.canvas.getZoom(), adjustedDistance = Math.pow(distance / zoom, 2), + i, l = points.length - 1, lastPoint = points[0], newPoints = [lastPoint], + cDistance; + for (i = 1; i < l - 1; i++) { + cDistance = Math.pow(lastPoint.x - points[i].x, 2) + Math.pow(lastPoint.y - points[i].y, 2); + if (cDistance >= adjustedDistance) { + lastPoint = points[i]; + newPoints.push(lastPoint); + } + } + /** + * Add the last point from the original line to the end of the array. + * This ensures decimate doesn't delete the last point on the line, and ensures the line is > 1 point. + */ + newPoints.push(points[l]); + return newPoints; + }, + + /** + * On mouseup after drawing the path on contextTop canvas + * we use the points captured to create an new fabric path object + * and add it to the fabric canvas. + */ + _finalizeAndAddPath: function() { + var ctx = this.canvas.contextTop; + ctx.closePath(); + if (this.decimate) { + this._points = this.decimatePoints(this._points, this.decimate); + } + var pathData = this.convertPointsToSVGPath(this._points); + if (this._isEmptySVGPath(pathData)) { + // do not create 0 width/height paths, as they are + // rendered inconsistently across browsers + // Firefox 4, for example, renders a dot, + // whereas Chrome 10 renders nothing + this.canvas.requestRenderAll(); + return; + } + + var path = this.createPath(pathData); + this.canvas.clearContext(this.canvas.contextTop); + this.canvas.fire('before:path:created', { path: path }); + this.canvas.add(path); + this.canvas.requestRenderAll(); + path.setCoords(); + this._resetShadow(); + + + // fire event 'path' created + this.canvas.fire('path:created', { path: path }); + } + }); +})(); + + +/** + * CircleBrush class + * @class fabric.CircleBrush + */ +fabric.CircleBrush = fabric.util.createClass(fabric.BaseBrush, /** @lends fabric.CircleBrush.prototype */ { + + /** + * Width of a brush + * @type Number + * @default + */ + width: 10, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.CircleBrush} Instance of a circle brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.points = []; + }, + + /** + * Invoked inside on mouse down and mouse move + * @param {Object} pointer + */ + drawDot: function(pointer) { + var point = this.addPoint(pointer), + ctx = this.canvas.contextTop; + this._saveAndTransform(ctx); + this.dot(ctx, point); + ctx.restore(); + }, + + dot: function(ctx, point) { + ctx.fillStyle = point.fill; + ctx.beginPath(); + ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2, false); + ctx.closePath(); + ctx.fill(); + }, + + /** + * Invoked on mouse down + */ + onMouseDown: function(pointer) { + this.points.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + this.drawDot(pointer); + }, + + /** + * Render the full state of the brush + * @private + */ + _render: function() { + var ctx = this.canvas.contextTop, i, len, + points = this.points; + this._saveAndTransform(ctx); + for (i = 0, len = points.length; i < len; i++) { + this.dot(ctx, points[i]); + } + ctx.restore(); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + if (this.needsFullRender()) { + this.canvas.clearContext(this.canvas.contextTop); + this.addPoint(pointer); + this._render(); + } + else { + this.drawDot(pointer); + } + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove, i, len; + this.canvas.renderOnAddRemove = false; + + var circles = []; + + for (i = 0, len = this.points.length; i < len; i++) { + var point = this.points[i], + circle = new fabric.Circle({ + radius: point.radius, + left: point.x, + top: point.y, + originX: 'center', + originY: 'center', + fill: point.fill + }); + + this.shadow && (circle.shadow = new fabric.Shadow(this.shadow)); + + circles.push(circle); + } + var group = new fabric.Group(circles); + group.canvas = this.canvas; + + this.canvas.fire('before:path:created', { path: group }); + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.requestRenderAll(); + }, + + /** + * @param {Object} pointer + * @return {fabric.Point} Just added pointer point + */ + addPoint: function(pointer) { + var pointerPoint = new fabric.Point(pointer.x, pointer.y), + + circleRadius = fabric.util.getRandomInt( + Math.max(0, this.width - 20), this.width + 20) / 2, + + circleColor = new fabric.Color(this.color) + .setAlpha(fabric.util.getRandomInt(0, 100) / 100) + .toRgba(); + + pointerPoint.radius = circleRadius; + pointerPoint.fill = circleColor; + + this.points.push(pointerPoint); + + return pointerPoint; + } +}); + + +/** + * SprayBrush class + * @class fabric.SprayBrush + */ +fabric.SprayBrush = fabric.util.createClass( fabric.BaseBrush, /** @lends fabric.SprayBrush.prototype */ { + + /** + * Width of a spray + * @type Number + * @default + */ + width: 10, + + /** + * Density of a spray (number of dots per chunk) + * @type Number + * @default + */ + density: 20, + + /** + * Width of spray dots + * @type Number + * @default + */ + dotWidth: 1, + + /** + * Width variance of spray dots + * @type Number + * @default + */ + dotWidthVariance: 1, + + /** + * Whether opacity of a dot should be random + * @type Boolean + * @default + */ + randomOpacity: false, + + /** + * Whether overlapping dots (rectangles) should be removed (for performance reasons) + * @type Boolean + * @default + */ + optimizeOverlapping: true, + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.SprayBrush} Instance of a spray brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this.sprayChunks = []; + }, + + /** + * Invoked on mouse down + * @param {Object} pointer + */ + onMouseDown: function(pointer) { + this.sprayChunks.length = 0; + this.canvas.clearContext(this.canvas.contextTop); + this._setShadow(); + + this.addSprayChunk(pointer); + this.render(this.sprayChunkPoints); + }, + + /** + * Invoked on mouse move + * @param {Object} pointer + */ + onMouseMove: function(pointer) { + if (this.limitedToCanvasSize === true && this._isOutSideCanvas(pointer)) { + return; + } + this.addSprayChunk(pointer); + this.render(this.sprayChunkPoints); + }, + + /** + * Invoked on mouse up + */ + onMouseUp: function() { + var originalRenderOnAddRemove = this.canvas.renderOnAddRemove; + this.canvas.renderOnAddRemove = false; + + var rects = []; + + for (var i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + var sprayChunk = this.sprayChunks[i]; + + for (var j = 0, jlen = sprayChunk.length; j < jlen; j++) { + + var rect = new fabric.Rect({ + width: sprayChunk[j].width, + height: sprayChunk[j].width, + left: sprayChunk[j].x + 1, + top: sprayChunk[j].y + 1, + originX: 'center', + originY: 'center', + fill: this.color + }); + rects.push(rect); + } + } + + if (this.optimizeOverlapping) { + rects = this._getOptimizedRects(rects); + } + + var group = new fabric.Group(rects); + this.shadow && group.set('shadow', new fabric.Shadow(this.shadow)); + this.canvas.fire('before:path:created', { path: group }); + this.canvas.add(group); + this.canvas.fire('path:created', { path: group }); + + this.canvas.clearContext(this.canvas.contextTop); + this._resetShadow(); + this.canvas.renderOnAddRemove = originalRenderOnAddRemove; + this.canvas.requestRenderAll(); + }, + + /** + * @private + * @param {Array} rects + */ + _getOptimizedRects: function(rects) { + + // avoid creating duplicate rects at the same coordinates + var uniqueRects = { }, key, i, len; + + for (i = 0, len = rects.length; i < len; i++) { + key = rects[i].left + '' + rects[i].top; + if (!uniqueRects[key]) { + uniqueRects[key] = rects[i]; + } + } + var uniqueRectsArray = []; + for (key in uniqueRects) { + uniqueRectsArray.push(uniqueRects[key]); + } + + return uniqueRectsArray; + }, + + /** + * Render new chunk of spray brush + */ + render: function(sprayChunk) { + var ctx = this.canvas.contextTop, i, len; + ctx.fillStyle = this.color; + + this._saveAndTransform(ctx); + + for (i = 0, len = sprayChunk.length; i < len; i++) { + var point = sprayChunk[i]; + if (typeof point.opacity !== 'undefined') { + ctx.globalAlpha = point.opacity; + } + ctx.fillRect(point.x, point.y, point.width, point.width); + } + ctx.restore(); + }, + + /** + * Render all spray chunks + */ + _render: function() { + var ctx = this.canvas.contextTop, i, ilen; + ctx.fillStyle = this.color; + + this._saveAndTransform(ctx); + + for (i = 0, ilen = this.sprayChunks.length; i < ilen; i++) { + this.render(this.sprayChunks[i]); + } + ctx.restore(); + }, + + /** + * @param {Object} pointer + */ + addSprayChunk: function(pointer) { + this.sprayChunkPoints = []; + + var x, y, width, radius = this.width / 2, i; + + for (i = 0; i < this.density; i++) { + + x = fabric.util.getRandomInt(pointer.x - radius, pointer.x + radius); + y = fabric.util.getRandomInt(pointer.y - radius, pointer.y + radius); + + if (this.dotWidthVariance) { + width = fabric.util.getRandomInt( + // bottom clamp width to 1 + Math.max(1, this.dotWidth - this.dotWidthVariance), + this.dotWidth + this.dotWidthVariance); + } + else { + width = this.dotWidth; + } + + var point = new fabric.Point(x, y); + point.width = width; + + if (this.randomOpacity) { + point.opacity = fabric.util.getRandomInt(0, 100) / 100; + } + + this.sprayChunkPoints.push(point); + } + + this.sprayChunks.push(this.sprayChunkPoints); + } +}); + + +/** + * PatternBrush class + * @class fabric.PatternBrush + * @extends fabric.BaseBrush + */ +fabric.PatternBrush = fabric.util.createClass(fabric.PencilBrush, /** @lends fabric.PatternBrush.prototype */ { + + getPatternSrc: function() { + + var dotWidth = 20, + dotDistance = 5, + patternCanvas = fabric.util.createCanvasElement(), + patternCtx = patternCanvas.getContext('2d'); + + patternCanvas.width = patternCanvas.height = dotWidth + dotDistance; + + patternCtx.fillStyle = this.color; + patternCtx.beginPath(); + patternCtx.arc(dotWidth / 2, dotWidth / 2, dotWidth / 2, 0, Math.PI * 2, false); + patternCtx.closePath(); + patternCtx.fill(); + + return patternCanvas; + }, + + getPatternSrcFunction: function() { + return String(this.getPatternSrc).replace('this.color', '"' + this.color + '"'); + }, + + /** + * Creates "pattern" instance property + */ + getPattern: function() { + return this.canvas.contextTop.createPattern(this.source || this.getPatternSrc(), 'repeat'); + }, + + /** + * Sets brush styles + */ + _setBrushStyles: function() { + this.callSuper('_setBrushStyles'); + this.canvas.contextTop.strokeStyle = this.getPattern(); + }, + + /** + * Creates path + */ + createPath: function(pathData) { + var path = this.callSuper('createPath', pathData), + topLeft = path._getLeftTopCoords().scalarAdd(path.strokeWidth / 2); + + path.stroke = new fabric.Pattern({ + source: this.source || this.getPatternSrcFunction(), + offsetX: -topLeft.x, + offsetY: -topLeft.y + }); + return path; + } +}); + + +(function() { + + var getPointer = fabric.util.getPointer, + degreesToRadians = fabric.util.degreesToRadians, + isTouchEvent = fabric.util.isTouchEvent; + + /** + * Canvas class + * @class fabric.Canvas + * @extends fabric.StaticCanvas + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#canvas} + * @see {@link fabric.Canvas#initialize} for constructor definition + * + * @fires object:modified at the end of a transform or any change when statefull is true + * @fires object:rotating while an object is being rotated from the control + * @fires object:scaling while an object is being scaled by controls + * @fires object:moving while an object is being dragged + * @fires object:skewing while an object is being skewed from the controls + * + * @fires before:transform before a transform is is started + * @fires before:selection:cleared + * @fires selection:cleared + * @fires selection:updated + * @fires selection:created + * + * @fires path:created after a drawing operation ends and the path is added + * @fires mouse:down + * @fires mouse:move + * @fires mouse:up + * @fires mouse:down:before on mouse down, before the inner fabric logic runs + * @fires mouse:move:before on mouse move, before the inner fabric logic runs + * @fires mouse:up:before on mouse up, before the inner fabric logic runs + * @fires mouse:over + * @fires mouse:out + * @fires mouse:dblclick whenever a native dbl click event fires on the canvas. + * + * @fires dragover + * @fires dragenter + * @fires dragleave + * @fires drop + * @fires after:render at the end of the render process, receives the context in the callback + * @fires before:render at start the render process, receives the context in the callback + * + * the following events are deprecated: + * @fires object:rotated at the end of a rotation transform + * @fires object:scaled at the end of a scale transform + * @fires object:moved at the end of translation transform + * @fires object:skewed at the end of a skew transform + */ + fabric.Canvas = fabric.util.createClass(fabric.StaticCanvas, /** @lends fabric.Canvas.prototype */ { + + /** + * Constructor + * @param {HTMLElement | String} el <canvas> element to initialize instance on + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(el, options) { + options || (options = { }); + this.renderAndResetBound = this.renderAndReset.bind(this); + this.requestRenderAllBound = this.requestRenderAll.bind(this); + this._initStatic(el, options); + this._initInteractive(); + this._createCacheCanvas(); + }, + + /** + * When true, objects can be transformed by one side (unproportionally) + * when dragged on the corners that normally would not do that. + * @type Boolean + * @default + * @since fabric 4.0 // changed name and default value + */ + uniformScaling: true, + + /** + * Indicates which key switches uniform scaling. + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled. + * totally wrong named. this sounds like `uniform scaling` + * if Canvas.uniformScaling is true, pressing this will set it to false + * and viceversa. + * @since 1.6.2 + * @type String + * @default + */ + uniScaleKey: 'shiftKey', + + /** + * When true, objects use center point as the origin of scale transformation. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, objects use center point as the origin of rotate transformation. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: false, + + /** + * Indicates which key enable centered Transform + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled feature disabled. + * @since 1.6.2 + * @type String + * @default + */ + centeredKey: 'altKey', + + /** + * Indicates which key enable alternate action on corner + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled feature disabled. + * @since 1.6.2 + * @type String + * @default + */ + altActionKey: 'shiftKey', + + /** + * Indicates that canvas is interactive. This property should not be changed. + * @type Boolean + * @default + */ + interactive: true, + + /** + * Indicates whether group selection should be enabled + * @type Boolean + * @default + */ + selection: true, + + /** + * Indicates which key or keys enable multiple click selection + * Pass value as a string or array of strings + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * If `null` or empty or containing any other string that is not a modifier key + * feature is disabled. + * @since 1.6.2 + * @type String|Array + * @default + */ + selectionKey: 'shiftKey', + + /** + * Indicates which key enable alternative selection + * in case of target overlapping with active object + * values: 'altKey', 'shiftKey', 'ctrlKey'. + * For a series of reason that come from the general expectations on how + * things should work, this feature works only for preserveObjectStacking true. + * If `null` or 'none' or any other string that is not a modifier key + * feature is disabled. + * @since 1.6.5 + * @type null|String + * @default + */ + altSelectionKey: null, + + /** + * Color of selection + * @type String + * @default + */ + selectionColor: 'rgba(100, 100, 255, 0.3)', // blue + + /** + * Default dash array pattern + * If not empty the selection border is dashed + * @type Array + */ + selectionDashArray: [], + + /** + * Color of the border of selection (usually slightly darker than color of selection itself) + * @type String + * @default + */ + selectionBorderColor: 'rgba(255, 255, 255, 0.3)', + + /** + * Width of a line used in object/group selection + * @type Number + * @default + */ + selectionLineWidth: 1, + + /** + * Select only shapes that are fully contained in the dragged selection rectangle. + * @type Boolean + * @default + */ + selectionFullyContained: false, + + /** + * Default cursor value used when hovering over an object on canvas + * @type String + * @default + */ + hoverCursor: 'move', + + /** + * Default cursor value used when moving an object on canvas + * @type String + * @default + */ + moveCursor: 'move', + + /** + * Default cursor value used for the entire canvas + * @type String + * @default + */ + defaultCursor: 'default', + + /** + * Cursor value used during free drawing + * @type String + * @default + */ + freeDrawingCursor: 'crosshair', + + /** + * Cursor value used for rotation point + * @type String + * @default + */ + rotationCursor: 'crosshair', + + /** + * Cursor value used for disabled elements ( corners with disabled action ) + * @type String + * @since 2.0.0 + * @default + */ + notAllowedCursor: 'not-allowed', + + /** + * Default element class that's given to wrapper (div) element of canvas + * @type String + * @default + */ + containerClass: 'canvas-container', + + /** + * When true, object detection happens on per-pixel basis rather than on per-bounding-box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * Number of pixels around target pixel to tolerate (consider active) during object detection + * @type Number + * @default + */ + targetFindTolerance: 0, + + /** + * When true, target detection is skipped. Target detection will return always undefined. + * click selection won't work anymore, events will fire with no targets. + * if something is selected before setting it to true, it will be deselected at the first click. + * area selection will still work. check the `selection` property too. + * if you deactivate both, you should look into staticCanvas. + * @type Boolean + * @default + */ + skipTargetFind: false, + + /** + * When true, mouse events on canvas (mousedown/mousemove/mouseup) result in free drawing. + * After mousedown, mousemove creates a shape, + * and then mouseup finalizes it and adds an instance of `fabric.Path` onto canvas. + * @tutorial {@link http://fabricjs.com/fabric-intro-part-4#free_drawing} + * @type Boolean + * @default + */ + isDrawingMode: false, + + /** + * Indicates whether objects should remain in current stack position when selected. + * When false objects are brought to top and rendered as part of the selection group + * @type Boolean + * @default + */ + preserveObjectStacking: false, + + /** + * Indicates the angle that an object will lock to while rotating. + * @type Number + * @since 1.6.7 + * @default + */ + snapAngle: 0, + + /** + * Indicates the distance from the snapAngle the rotation will lock to the snapAngle. + * When `null`, the snapThreshold will default to the snapAngle. + * @type null|Number + * @since 1.6.7 + * @default + */ + snapThreshold: null, + + /** + * Indicates if the right click on canvas can output the context menu or not + * @type Boolean + * @since 1.6.5 + * @default + */ + stopContextMenu: false, + + /** + * Indicates if the canvas can fire right click events + * @type Boolean + * @since 1.6.5 + * @default + */ + fireRightClick: false, + + /** + * Indicates if the canvas can fire middle click events + * @type Boolean + * @since 1.7.8 + * @default + */ + fireMiddleClick: false, + + /** + * Keep track of the subTargets for Mouse Events + * @type fabric.Object[] + */ + targets: [], + + /** + * Keep track of the hovered target + * @type fabric.Object + * @private + */ + _hoveredTarget: null, + + /** + * hold the list of nested targets hovered + * @type fabric.Object[] + * @private + */ + _hoveredTargets: [], + + /** + * @private + */ + _initInteractive: function() { + this._currentTransform = null; + this._groupSelector = null; + this._initWrapperElement(); + this._createUpperCanvas(); + this._initEventListeners(); + + this._initRetinaScaling(); + + this.freeDrawingBrush = fabric.PencilBrush && new fabric.PencilBrush(this); + + this.calcOffset(); + }, + + /** + * Divides objects in two groups, one to render immediately + * and one to render as activeGroup. + * @return {Array} objects to render immediately and pushes the other in the activeGroup. + */ + _chooseObjectsToRender: function() { + var activeObjects = this.getActiveObjects(), + object, objsToRender, activeGroupObjects; + + if (activeObjects.length > 0 && !this.preserveObjectStacking) { + objsToRender = []; + activeGroupObjects = []; + for (var i = 0, length = this._objects.length; i < length; i++) { + object = this._objects[i]; + if (activeObjects.indexOf(object) === -1 ) { + objsToRender.push(object); + } + else { + activeGroupObjects.push(object); + } + } + if (activeObjects.length > 1) { + this._activeObject._objects = activeGroupObjects; + } + objsToRender.push.apply(objsToRender, activeGroupObjects); + } + else { + objsToRender = this._objects; + } + return objsToRender; + }, + + /** + * Renders both the top canvas and the secondary container canvas. + * @return {fabric.Canvas} instance + * @chainable + */ + renderAll: function () { + if (this.contextTopDirty && !this._groupSelector && !this.isDrawingMode) { + this.clearContext(this.contextTop); + this.contextTopDirty = false; + } + if (this.hasLostContext) { + this.renderTopLayer(this.contextTop); + } + var canvasToDrawOn = this.contextContainer; + this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); + return this; + }, + + renderTopLayer: function(ctx) { + ctx.save(); + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this.freeDrawingBrush && this.freeDrawingBrush._render(); + this.contextTopDirty = true; + } + // we render the top context - last object + if (this.selection && this._groupSelector) { + this._drawSelection(ctx); + this.contextTopDirty = true; + } + ctx.restore(); + }, + + /** + * Method to render only the top canvas. + * Also used to render the group selection box. + * @return {fabric.Canvas} thisArg + * @chainable + */ + renderTop: function () { + var ctx = this.contextTop; + this.clearContext(ctx); + this.renderTopLayer(ctx); + this.fire('after:render'); + return this; + }, + + /** + * @private + */ + _normalizePointer: function (object, pointer) { + var m = object.calcTransformMatrix(), + invertedM = fabric.util.invertTransform(m), + vptPointer = this.restorePointerVpt(pointer); + return fabric.util.transformPoint(vptPointer, invertedM); + }, + + /** + * Returns true if object is transparent at a certain location + * @param {fabric.Object} target Object to check + * @param {Number} x Left coordinate + * @param {Number} y Top coordinate + * @return {Boolean} + */ + isTargetTransparent: function (target, x, y) { + // in case the target is the activeObject, we cannot execute this optimization + // because we need to draw controls too. + if (target.shouldCache() && target._cacheCanvas && target !== this._activeObject) { + var normalizedPointer = this._normalizePointer(target, {x: x, y: y}), + targetRelativeX = Math.max(target.cacheTranslationX + (normalizedPointer.x * target.zoomX), 0), + targetRelativeY = Math.max(target.cacheTranslationY + (normalizedPointer.y * target.zoomY), 0); + + var isTransparent = fabric.util.isTransparent( + target._cacheContext, Math.round(targetRelativeX), Math.round(targetRelativeY), this.targetFindTolerance); + + return isTransparent; + } + + var ctx = this.contextCache, + originalColor = target.selectionBackgroundColor, v = this.viewportTransform; + + target.selectionBackgroundColor = ''; + + this.clearContext(ctx); + + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + target.render(ctx); + ctx.restore(); + + target.selectionBackgroundColor = originalColor; + + var isTransparent = fabric.util.isTransparent( + ctx, x, y, this.targetFindTolerance); + + return isTransparent; + }, + + /** + * takes an event and determines if selection key has been pressed + * @private + * @param {Event} e Event object + */ + _isSelectionKeyPressed: function(e) { + var selectionKeyPressed = false; + + if (Object.prototype.toString.call(this.selectionKey) === '[object Array]') { + selectionKeyPressed = !!this.selectionKey.find(function(key) { return e[key] === true; }); + } + else { + selectionKeyPressed = e[this.selectionKey]; + } + + return selectionKeyPressed; + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _shouldClearSelection: function (e, target) { + var activeObjects = this.getActiveObjects(), + activeObject = this._activeObject; + + return ( + !target + || + (target && + activeObject && + activeObjects.length > 1 && + activeObjects.indexOf(target) === -1 && + activeObject !== target && + !this._isSelectionKeyPressed(e)) + || + (target && !target.evented) + || + (target && + !target.selectable && + activeObject && + activeObject !== target) + ); + }, + + /** + * centeredScaling from object can't override centeredScaling from canvas. + * this should be fixed, since object setting should take precedence over canvas. + * also this should be something that will be migrated in the control properties. + * as ability to define the origin of the transformation that the control provide. + * @private + * @param {fabric.Object} target + * @param {String} action + * @param {Boolean} altKey + */ + _shouldCenterTransform: function (target, action, altKey) { + if (!target) { + return; + } + + var centerTransform; + + if (action === 'scale' || action === 'scaleX' || action === 'scaleY' || action === 'resizing') { + centerTransform = this.centeredScaling || target.centeredScaling; + } + else if (action === 'rotate') { + centerTransform = this.centeredRotation || target.centeredRotation; + } + + return centerTransform ? !altKey : altKey; + }, + + /** + * should disappear before release 4.0 + * @private + */ + _getOriginFromCorner: function(target, corner) { + var origin = { + x: target.originX, + y: target.originY + }; + + if (corner === 'ml' || corner === 'tl' || corner === 'bl') { + origin.x = 'right'; + } + else if (corner === 'mr' || corner === 'tr' || corner === 'br') { + origin.x = 'left'; + } + + if (corner === 'tl' || corner === 'mt' || corner === 'tr') { + origin.y = 'bottom'; + } + else if (corner === 'bl' || corner === 'mb' || corner === 'br') { + origin.y = 'top'; + } + return origin; + }, + + /** + * @private + * @param {Boolean} alreadySelected true if target is already selected + * @param {String} corner a string representing the corner ml, mr, tl ... + * @param {Event} e Event object + * @param {fabric.Object} [target] inserted back to help overriding. Unused + */ + _getActionFromCorner: function(alreadySelected, corner, e, target) { + if (!corner || !alreadySelected) { + return 'drag'; + } + var control = target.controls[corner]; + return control.getActionName(e, control, target); + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _setupCurrentTransform: function (e, target, alreadySelected) { + if (!target) { + return; + } + + var pointer = this.getPointer(e), corner = target.__corner, + control = target.controls[corner], + actionHandler = (alreadySelected && corner) ? + control.getActionHandler(e, target, control) : fabric.controlsUtils.dragHandler, + action = this._getActionFromCorner(alreadySelected, corner, e, target), + origin = this._getOriginFromCorner(target, corner), + altKey = e[this.centeredKey], + transform = { + target: target, + action: action, + actionHandler: actionHandler, + corner: corner, + scaleX: target.scaleX, + scaleY: target.scaleY, + skewX: target.skewX, + skewY: target.skewY, + // used by transation + offsetX: pointer.x - target.left, + offsetY: pointer.y - target.top, + originX: origin.x, + originY: origin.y, + ex: pointer.x, + ey: pointer.y, + lastX: pointer.x, + lastY: pointer.y, + // unsure they are useful anymore. + // left: target.left, + // top: target.top, + theta: degreesToRadians(target.angle), + // end of unsure + width: target.width * target.scaleX, + shiftKey: e.shiftKey, + altKey: altKey, + original: fabric.util.saveObjectTransform(target), + }; + + if (this._shouldCenterTransform(target, action, altKey)) { + transform.originX = 'center'; + transform.originY = 'center'; + } + transform.original.originX = origin.x; + transform.original.originY = origin.y; + this._currentTransform = transform; + this._beforeTransform(e); + }, + + /** + * Set the cursor type of the canvas element + * @param {String} value Cursor type of the canvas element. + * @see http://www.w3.org/TR/css3-ui/#cursor + */ + setCursor: function (value) { + this.upperCanvasEl.style.cursor = value; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx to draw the selection on + */ + _drawSelection: function (ctx) { + var selector = this._groupSelector, + viewportStart = new fabric.Point(selector.ex, selector.ey), + start = fabric.util.transformPoint(viewportStart, this.viewportTransform), + viewportExtent = new fabric.Point(selector.ex + selector.left, selector.ey + selector.top), + extent = fabric.util.transformPoint(viewportExtent, this.viewportTransform), + minX = Math.min(start.x, extent.x), + minY = Math.min(start.y, extent.y), + maxX = Math.max(start.x, extent.x), + maxY = Math.max(start.y, extent.y), + strokeOffset = this.selectionLineWidth / 2; + + if (this.selectionColor) { + ctx.fillStyle = this.selectionColor; + ctx.fillRect(minX, minY, maxX - minX, maxY - minY); + } + + if (!this.selectionLineWidth || !this.selectionBorderColor) { + return; + } + ctx.lineWidth = this.selectionLineWidth; + ctx.strokeStyle = this.selectionBorderColor; + + minX += strokeOffset; + minY += strokeOffset; + maxX -= strokeOffset; + maxY -= strokeOffset; + // selection border + fabric.Object.prototype._setLineDash.call(this, ctx, this.selectionDashArray); + ctx.strokeRect(minX, minY, maxX - minX, maxY - minY); + }, + + /** + * Method that determines what object we are clicking on + * the skipGroup parameter is for internal use, is needed for shift+click action + * 11/09/2018 TODO: would be cool if findTarget could discern between being a full target + * or the outside part of the corner. + * @param {Event} e mouse event + * @param {Boolean} skipGroup when true, activeGroup is skipped and only objects are traversed through + * @return {fabric.Object} the target found + */ + findTarget: function (e, skipGroup) { + if (this.skipTargetFind) { + return; + } + + var ignoreZoom = true, + pointer = this.getPointer(e, ignoreZoom), + activeObject = this._activeObject, + aObjects = this.getActiveObjects(), + activeTarget, activeTargetSubs, + isTouch = isTouchEvent(e), + shouldLookForActive = (aObjects.length > 1 && !skipGroup) || aObjects.length === 1; + + // first check current group (if one exists) + // active group does not check sub targets like normal groups. + // if active group just exits. + this.targets = []; + + // if we hit the corner of an activeObject, let's return that. + if (shouldLookForActive && activeObject._findTargetCorner(pointer, isTouch)) { + return activeObject; + } + if (aObjects.length > 1 && !skipGroup && activeObject === this._searchPossibleTargets([activeObject], pointer)) { + return activeObject; + } + if (aObjects.length === 1 && + activeObject === this._searchPossibleTargets([activeObject], pointer)) { + if (!this.preserveObjectStacking) { + return activeObject; + } + else { + activeTarget = activeObject; + activeTargetSubs = this.targets; + this.targets = []; + } + } + var target = this._searchPossibleTargets(this._objects, pointer); + if (e[this.altSelectionKey] && target && activeTarget && target !== activeTarget) { + target = activeTarget; + this.targets = activeTargetSubs; + } + return target; + }, + + /** + * Checks point is inside the object. + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @param {fabric.Object} obj Object to test against + * @param {Object} [globalPointer] x,y object of point coordinates relative to canvas used to search per pixel target. + * @return {Boolean} true if point is contained within an area of given object + * @private + */ + _checkTarget: function(pointer, obj, globalPointer) { + if (obj && + obj.visible && + obj.evented && + // http://www.geog.ubc.ca/courses/klink/gis.notes/ncgia/u32.html + // http://idav.ucdavis.edu/~okreylos/TAship/Spring2000/PointInPolygon.html + obj.containsPoint(pointer) + ) { + if ((this.perPixelTargetFind || obj.perPixelTargetFind) && !obj.isEditing) { + var isTransparent = this.isTargetTransparent(obj, globalPointer.x, globalPointer.y); + if (!isTransparent) { + return true; + } + } + else { + return true; + } + } + }, + + /** + * Function used to search inside objects an object that contains pointer in bounding box or that contains pointerOnCanvas when painted + * @param {Array} [objects] objects array to look into + * @param {Object} [pointer] x,y object of point coordinates we want to check. + * @return {fabric.Object} object that contains pointer + * @private + */ + _searchPossibleTargets: function(objects, pointer) { + // Cache all targets where their bounding box contains point. + var target, i = objects.length, subTarget; + // Do not check for currently grouped objects, since we check the parent group itself. + // until we call this function specifically to search inside the activeGroup + while (i--) { + var objToCheck = objects[i]; + var pointerToUse = objToCheck.group ? + this._normalizePointer(objToCheck.group, pointer) : pointer; + if (this._checkTarget(pointerToUse, objToCheck, pointer)) { + target = objects[i]; + if (target.subTargetCheck && target instanceof fabric.Group) { + subTarget = this._searchPossibleTargets(target._objects, pointer); + subTarget && this.targets.push(subTarget); + } + break; + } + } + return target; + }, + + /** + * Returns pointer coordinates without the effect of the viewport + * @param {Object} pointer with "x" and "y" number values + * @return {Object} object with "x" and "y" number values + */ + restorePointerVpt: function(pointer) { + return fabric.util.transformPoint( + pointer, + fabric.util.invertTransform(this.viewportTransform) + ); + }, + + /** + * Returns pointer coordinates relative to canvas. + * Can return coordinates with or without viewportTransform. + * ignoreZoom false gives back coordinates that represent + * the point clicked on canvas element. + * ignoreZoom true gives back coordinates after being processed + * by the viewportTransform ( sort of coordinates of what is displayed + * on the canvas where you are clicking. + * ignoreZoom true = HTMLElement coordinates relative to top,left + * ignoreZoom false, default = fabric space coordinates, the same used for shape position + * To interact with your shapes top and left you want to use ignoreZoom true + * most of the time, while ignoreZoom false will give you coordinates + * compatible with the object.oCoords system. + * of the time. + * @param {Event} e + * @param {Boolean} ignoreZoom + * @return {Object} object with "x" and "y" number values + */ + getPointer: function (e, ignoreZoom) { + // return cached values if we are in the event processing chain + if (this._absolutePointer && !ignoreZoom) { + return this._absolutePointer; + } + if (this._pointer && ignoreZoom) { + return this._pointer; + } + + var pointer = getPointer(e), + upperCanvasEl = this.upperCanvasEl, + bounds = upperCanvasEl.getBoundingClientRect(), + boundsWidth = bounds.width || 0, + boundsHeight = bounds.height || 0, + cssScale; + + if (!boundsWidth || !boundsHeight ) { + if ('top' in bounds && 'bottom' in bounds) { + boundsHeight = Math.abs( bounds.top - bounds.bottom ); + } + if ('right' in bounds && 'left' in bounds) { + boundsWidth = Math.abs( bounds.right - bounds.left ); + } + } + + this.calcOffset(); + pointer.x = pointer.x - this._offset.left; + pointer.y = pointer.y - this._offset.top; + if (!ignoreZoom) { + pointer = this.restorePointerVpt(pointer); + } + + var retinaScaling = this.getRetinaScaling(); + if (retinaScaling !== 1) { + pointer.x /= retinaScaling; + pointer.y /= retinaScaling; + } + + if (boundsWidth === 0 || boundsHeight === 0) { + // If bounds are not available (i.e. not visible), do not apply scale. + cssScale = { width: 1, height: 1 }; + } + else { + cssScale = { + width: upperCanvasEl.width / boundsWidth, + height: upperCanvasEl.height / boundsHeight + }; + } + + return { + x: pointer.x * cssScale.width, + y: pointer.y * cssScale.height + }; + }, + + /** + * @private + * @throws {CANVAS_INIT_ERROR} If canvas can not be initialized + */ + _createUpperCanvas: function () { + var lowerCanvasClass = this.lowerCanvasEl.className.replace(/\s*lower-canvas\s*/, ''), + lowerCanvasEl = this.lowerCanvasEl, upperCanvasEl = this.upperCanvasEl; + + // there is no need to create a new upperCanvas element if we have already one. + if (upperCanvasEl) { + upperCanvasEl.className = ''; + } + else { + upperCanvasEl = this._createCanvasElement(); + this.upperCanvasEl = upperCanvasEl; + } + fabric.util.addClass(upperCanvasEl, 'upper-canvas ' + lowerCanvasClass); + + this.wrapperEl.appendChild(upperCanvasEl); + + this._copyCanvasStyle(lowerCanvasEl, upperCanvasEl); + this._applyCanvasStyle(upperCanvasEl); + this.contextTop = upperCanvasEl.getContext('2d'); + }, + + /** + * @private + */ + _createCacheCanvas: function () { + this.cacheCanvasEl = this._createCanvasElement(); + this.cacheCanvasEl.setAttribute('width', this.width); + this.cacheCanvasEl.setAttribute('height', this.height); + this.contextCache = this.cacheCanvasEl.getContext('2d'); + }, + + /** + * @private + */ + _initWrapperElement: function () { + this.wrapperEl = fabric.util.wrapElement(this.lowerCanvasEl, 'div', { + 'class': this.containerClass + }); + fabric.util.setStyle(this.wrapperEl, { + width: this.width + 'px', + height: this.height + 'px', + position: 'relative' + }); + fabric.util.makeElementUnselectable(this.wrapperEl); + }, + + /** + * @private + * @param {HTMLElement} element canvas element to apply styles on + */ + _applyCanvasStyle: function (element) { + var width = this.width || element.width, + height = this.height || element.height; + + fabric.util.setStyle(element, { + position: 'absolute', + width: width + 'px', + height: height + 'px', + left: 0, + top: 0, + 'touch-action': this.allowTouchScrolling ? 'manipulation' : 'none', + '-ms-touch-action': this.allowTouchScrolling ? 'manipulation' : 'none' + }); + element.width = width; + element.height = height; + fabric.util.makeElementUnselectable(element); + }, + + /** + * Copy the entire inline style from one element (fromEl) to another (toEl) + * @private + * @param {Element} fromEl Element style is copied from + * @param {Element} toEl Element copied style is applied to + */ + _copyCanvasStyle: function (fromEl, toEl) { + toEl.style.cssText = fromEl.style.cssText; + }, + + /** + * Returns context of canvas where object selection is drawn + * @return {CanvasRenderingContext2D} + */ + getSelectionContext: function() { + return this.contextTop; + }, + + /** + * Returns <canvas> element on which object selection is drawn + * @return {HTMLCanvasElement} + */ + getSelectionElement: function () { + return this.upperCanvasEl; + }, + + /** + * Returns currently active object + * @return {fabric.Object} active object + */ + getActiveObject: function () { + return this._activeObject; + }, + + /** + * Returns an array with the current selected objects + * @return {fabric.Object} active object + */ + getActiveObjects: function () { + var active = this._activeObject; + if (active) { + if (active.type === 'activeSelection' && active._objects) { + return active._objects.slice(0); + } + else { + return [active]; + } + } + return []; + }, + + /** + * @private + * @param {fabric.Object} obj Object that was removed + */ + _onObjectRemoved: function(obj) { + // removing active object should fire "selection:cleared" events + if (obj === this._activeObject) { + this.fire('before:selection:cleared', { target: obj }); + this._discardActiveObject(); + this.fire('selection:cleared', { target: obj }); + obj.fire('deselected'); + } + if (obj === this._hoveredTarget){ + this._hoveredTarget = null; + this._hoveredTargets = []; + } + this.callSuper('_onObjectRemoved', obj); + }, + + /** + * @private + * Compares the old activeObject with the current one and fires correct events + * @param {fabric.Object} obj old activeObject + */ + _fireSelectionEvents: function(oldObjects, e) { + var somethingChanged = false, objects = this.getActiveObjects(), + added = [], removed = []; + oldObjects.forEach(function(oldObject) { + if (objects.indexOf(oldObject) === -1) { + somethingChanged = true; + oldObject.fire('deselected', { + e: e, + target: oldObject + }); + removed.push(oldObject); + } + }); + objects.forEach(function(object) { + if (oldObjects.indexOf(object) === -1) { + somethingChanged = true; + object.fire('selected', { + e: e, + target: object + }); + added.push(object); + } + }); + if (oldObjects.length > 0 && objects.length > 0) { + somethingChanged && this.fire('selection:updated', { + e: e, + selected: added, + deselected: removed, + // added for backward compatibility + // deprecated + updated: added[0] || removed[0], + target: this._activeObject, + }); + } + else if (objects.length > 0) { + this.fire('selection:created', { + e: e, + selected: added, + target: this._activeObject, + }); + } + else if (oldObjects.length > 0) { + this.fire('selection:cleared', { + e: e, + deselected: removed, + }); + } + }, + + /** + * Sets given object as the only active object on canvas + * @param {fabric.Object} object Object to set as an active one + * @param {Event} [e] Event (passed along when firing "object:selected") + * @return {fabric.Canvas} thisArg + * @chainable + */ + setActiveObject: function (object, e) { + var currentActives = this.getActiveObjects(); + this._setActiveObject(object, e); + this._fireSelectionEvents(currentActives, e); + return this; + }, + + /** + * This is a private method for now. + * This is supposed to be equivalent to setActiveObject but without firing + * any event. There is commitment to have this stay this way. + * This is the functional part of setActiveObject. + * @private + * @param {Object} object to set as active + * @param {Event} [e] Event (passed along when firing "object:selected") + * @return {Boolean} true if the selection happened + */ + _setActiveObject: function(object, e) { + if (this._activeObject === object) { + return false; + } + if (!this._discardActiveObject(e, object)) { + return false; + } + if (object.onSelect({ e: e })) { + return false; + } + this._activeObject = object; + return true; + }, + + /** + * This is a private method for now. + * This is supposed to be equivalent to discardActiveObject but without firing + * any events. There is commitment to have this stay this way. + * This is the functional part of discardActiveObject. + * @param {Event} [e] Event (passed along when firing "object:deselected") + * @param {Object} object to set as active + * @return {Boolean} true if the selection happened + * @private + */ + _discardActiveObject: function(e, object) { + var obj = this._activeObject; + if (obj) { + // onDeselect return TRUE to cancel selection; + if (obj.onDeselect({ e: e, object: object })) { + return false; + } + this._activeObject = null; + } + return true; + }, + + /** + * Discards currently active object and fire events. If the function is called by fabric + * as a consequence of a mouse event, the event is passed as a parameter and + * sent to the fire function for the custom events. When used as a method the + * e param does not have any application. + * @param {event} e + * @return {fabric.Canvas} thisArg + * @chainable + */ + discardActiveObject: function (e) { + var currentActives = this.getActiveObjects(), activeObject = this.getActiveObject(); + if (currentActives.length) { + this.fire('before:selection:cleared', { target: activeObject, e: e }); + } + this._discardActiveObject(e); + this._fireSelectionEvents(currentActives, e); + return this; + }, + + /** + * Clears a canvas element and removes all event listeners + * @return {fabric.Canvas} thisArg + * @chainable + */ + dispose: function () { + var wrapper = this.wrapperEl; + this.removeListeners(); + wrapper.removeChild(this.upperCanvasEl); + wrapper.removeChild(this.lowerCanvasEl); + this.contextCache = null; + this.contextTop = null; + ['upperCanvasEl', 'cacheCanvasEl'].forEach((function(element) { + fabric.util.cleanUpJsdomNode(this[element]); + this[element] = undefined; + }).bind(this)); + if (wrapper.parentNode) { + wrapper.parentNode.replaceChild(this.lowerCanvasEl, this.wrapperEl); + } + delete this.wrapperEl; + fabric.StaticCanvas.prototype.dispose.call(this); + return this; + }, + + /** + * Clears all contexts (background, main, top) of an instance + * @return {fabric.Canvas} thisArg + * @chainable + */ + clear: function () { + // this.discardActiveGroup(); + this.discardActiveObject(); + this.clearContext(this.contextTop); + return this.callSuper('clear'); + }, + + /** + * Draws objects' controls (borders/controls) + * @param {CanvasRenderingContext2D} ctx Context to render controls on + */ + drawControls: function(ctx) { + var activeObject = this._activeObject; + + if (activeObject) { + activeObject._renderControls(ctx); + } + }, + + /** + * @private + */ + _toObject: function(instance, methodName, propertiesToInclude) { + //If the object is part of the current selection group, it should + //be transformed appropriately + //i.e. it should be serialised as it would appear if the selection group + //were to be destroyed. + var originalProperties = this._realizeGroupTransformOnObject(instance), + object = this.callSuper('_toObject', instance, methodName, propertiesToInclude); + //Undo the damage we did by changing all of its properties + this._unwindGroupTransformOnObject(instance, originalProperties); + return object; + }, + + /** + * Realises an object's group transformation on it + * @private + * @param {fabric.Object} [instance] the object to transform (gets mutated) + * @returns the original values of instance which were changed + */ + _realizeGroupTransformOnObject: function(instance) { + if (instance.group && instance.group.type === 'activeSelection' && this._activeObject === instance.group) { + var layoutProps = ['angle', 'flipX', 'flipY', 'left', 'scaleX', 'scaleY', 'skewX', 'skewY', 'top']; + //Copy all the positionally relevant properties across now + var originalValues = {}; + layoutProps.forEach(function(prop) { + originalValues[prop] = instance[prop]; + }); + fabric.util.addTransformToObject(instance, this._activeObject.calcOwnMatrix()); + return originalValues; + } + else { + return null; + } + }, + + /** + * Restores the changed properties of instance + * @private + * @param {fabric.Object} [instance] the object to un-transform (gets mutated) + * @param {Object} [originalValues] the original values of instance, as returned by _realizeGroupTransformOnObject + */ + _unwindGroupTransformOnObject: function(instance, originalValues) { + if (originalValues) { + instance.set(originalValues); + } + }, + + /** + * @private + */ + _setSVGObject: function(markup, instance, reviver) { + //If the object is in a selection group, simulate what would happen to that + //object when the group is deselected + var originalProperties = this._realizeGroupTransformOnObject(instance); + this.callSuper('_setSVGObject', markup, instance, reviver); + this._unwindGroupTransformOnObject(instance, originalProperties); + }, + + setViewportTransform: function (vpt) { + if (this.renderOnAddRemove && this._activeObject && this._activeObject.isEditing) { + this._activeObject.clearContextTop(); + } + fabric.StaticCanvas.prototype.setViewportTransform.call(this, vpt); + } + }); + + // copying static properties manually to work around Opera's bug, + // where "prototype" property is enumerable and overrides existing prototype + for (var prop in fabric.StaticCanvas) { + if (prop !== 'prototype') { + fabric.Canvas[prop] = fabric.StaticCanvas[prop]; + } + } +})(); + + +(function() { + + var addListener = fabric.util.addListener, + removeListener = fabric.util.removeListener, + RIGHT_CLICK = 3, MIDDLE_CLICK = 2, LEFT_CLICK = 1, + addEventOptions = { passive: false }; + + function checkClick(e, value) { + return e.button && (e.button === value - 1); + } + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * Contains the id of the touch event that owns the fabric transform + * @type Number + * @private + */ + mainTouchId: null, + + /** + * Adds mouse listeners to canvas + * @private + */ + _initEventListeners: function () { + // in case we initialized the class twice. This should not happen normally + // but in some kind of applications where the canvas element may be changed + // this is a workaround to having double listeners. + this.removeListeners(); + this._bindEvents(); + this.addOrRemove(addListener, 'add'); + }, + + /** + * return an event prefix pointer or mouse. + * @private + */ + _getEventPrefix: function () { + return this.enablePointerEvents ? 'pointer' : 'mouse'; + }, + + addOrRemove: function(functor, eventjsFunctor) { + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + functor(fabric.window, 'resize', this._onResize); + functor(canvasElement, eventTypePrefix + 'down', this._onMouseDown); + functor(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + functor(canvasElement, eventTypePrefix + 'out', this._onMouseOut); + functor(canvasElement, eventTypePrefix + 'enter', this._onMouseEnter); + functor(canvasElement, 'wheel', this._onMouseWheel); + functor(canvasElement, 'contextmenu', this._onContextMenu); + functor(canvasElement, 'dblclick', this._onDoubleClick); + functor(canvasElement, 'dragover', this._onDragOver); + functor(canvasElement, 'dragenter', this._onDragEnter); + functor(canvasElement, 'dragleave', this._onDragLeave); + functor(canvasElement, 'drop', this._onDrop); + if (!this.enablePointerEvents) { + functor(canvasElement, 'touchstart', this._onTouchStart, addEventOptions); + } + if (typeof eventjs !== 'undefined' && eventjsFunctor in eventjs) { + eventjs[eventjsFunctor](canvasElement, 'gesture', this._onGesture); + eventjs[eventjsFunctor](canvasElement, 'drag', this._onDrag); + eventjs[eventjsFunctor](canvasElement, 'orientation', this._onOrientationChange); + eventjs[eventjsFunctor](canvasElement, 'shake', this._onShake); + eventjs[eventjsFunctor](canvasElement, 'longpress', this._onLongPress); + } + }, + + /** + * Removes all event listeners + */ + removeListeners: function() { + this.addOrRemove(removeListener, 'remove'); + // if you dispose on a mouseDown, before mouse up, you need to clean document to... + var eventTypePrefix = this._getEventPrefix(); + removeListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + removeListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + removeListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + }, + + /** + * @private + */ + _bindEvents: function() { + if (this.eventsBound) { + // for any reason we pass here twice we do not want to bind events twice. + return; + } + this._onMouseDown = this._onMouseDown.bind(this); + this._onTouchStart = this._onTouchStart.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + this._onMouseUp = this._onMouseUp.bind(this); + this._onTouchEnd = this._onTouchEnd.bind(this); + this._onResize = this._onResize.bind(this); + this._onGesture = this._onGesture.bind(this); + this._onDrag = this._onDrag.bind(this); + this._onShake = this._onShake.bind(this); + this._onLongPress = this._onLongPress.bind(this); + this._onOrientationChange = this._onOrientationChange.bind(this); + this._onMouseWheel = this._onMouseWheel.bind(this); + this._onMouseOut = this._onMouseOut.bind(this); + this._onMouseEnter = this._onMouseEnter.bind(this); + this._onContextMenu = this._onContextMenu.bind(this); + this._onDoubleClick = this._onDoubleClick.bind(this); + this._onDragOver = this._onDragOver.bind(this); + this._onDragEnter = this._simpleEventHandler.bind(this, 'dragenter'); + this._onDragLeave = this._simpleEventHandler.bind(this, 'dragleave'); + this._onDrop = this._simpleEventHandler.bind(this, 'drop'); + this.eventsBound = true; + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js gesture + * @param {Event} [self] Inner Event object + */ + _onGesture: function(e, self) { + this.__onTransformGesture && this.__onTransformGesture(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js drag + * @param {Event} [self] Inner Event object + */ + _onDrag: function(e, self) { + this.__onDrag && this.__onDrag(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on wheel event + */ + _onMouseWheel: function(e) { + this.__onMouseWheel(e); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseOut: function(e) { + var target = this._hoveredTarget; + this.fire('mouse:out', { target: target, e: e }); + this._hoveredTarget = null; + target && target.fire('mouseout', { e: e }); + + var _this = this; + this._hoveredTargets.forEach(function(_target){ + _this.fire('mouse:out', { target: target, e: e }); + _target && target.fire('mouseout', { e: e }); + }); + this._hoveredTargets = []; + + if (this._iTextInstances) { + this._iTextInstances.forEach(function(obj) { + if (obj.isEditing) { + obj.hiddenTextarea.focus(); + } + }); + } + }, + + /** + * @private + * @param {Event} e Event object fired on mouseenter + */ + _onMouseEnter: function(e) { + // This find target and consequent 'mouse:over' is used to + // clear old instances on hovered target. + // calling findTarget has the side effect of killing target.__corner. + // as a short term fix we are not firing this if we are currently transforming. + // as a long term fix we need to separate the action of finding a target with the + // side effects we added to it. + if (!this._currentTransform && !this.findTarget(e)) { + this.fire('mouse:over', { target: null, e: e }); + this._hoveredTarget = null; + this._hoveredTargets = []; + } + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js orientation change + * @param {Event} [self] Inner Event object + */ + _onOrientationChange: function(e, self) { + this.__onOrientationChange && this.__onOrientationChange(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onShake: function(e, self) { + this.__onShake && this.__onShake(e, self); + }, + + /** + * @private + * @param {Event} [e] Event object fired on Event.js shake + * @param {Event} [self] Inner Event object + */ + _onLongPress: function(e, self) { + this.__onLongPress && this.__onLongPress(e, self); + }, + + /** + * prevent default to allow drop event to be fired + * @private + * @param {Event} [e] Event object fired on Event.js shake + */ + _onDragOver: function(e) { + e.preventDefault(); + var target = this._simpleEventHandler('dragover', e); + this._fireEnterLeaveEvents(target, e); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onContextMenu: function (e) { + if (this.stopContextMenu) { + e.stopPropagation(); + e.preventDefault(); + } + return false; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onDoubleClick: function (e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'dblclick'); + this._resetTransformEventData(e); + }, + + /** + * Return a the id of an event. + * returns either the pointerId or the identifier or 0 for the mouse event + * @private + * @param {Event} evt Event object + */ + getPointerId: function(evt) { + var changedTouches = evt.changedTouches; + + if (changedTouches) { + return changedTouches[0] && changedTouches[0].identifier; + } + + if (this.enablePointerEvents) { + return evt.pointerId; + } + + return -1; + }, + + /** + * Determines if an event has the id of the event that is considered main + * @private + * @param {evt} event Event object + */ + _isMainEvent: function(evt) { + if (evt.isPrimary === true) { + return true; + } + if (evt.isPrimary === false) { + return false; + } + if (evt.type === 'touchend' && evt.touches.length === 0) { + return true; + } + if (evt.changedTouches) { + return evt.changedTouches[0].identifier === this.mainTouchId; + } + return true; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onTouchStart: function(e) { + e.preventDefault(); + if (this.mainTouchId === null) { + this.mainTouchId = this.getPointerId(e); + } + this.__onMouseDown(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + addListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + addListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + // Unbind mousedown to prevent double triggers from touch devices + removeListener(canvasElement, eventTypePrefix + 'down', this._onMouseDown); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseDown: function (e) { + this.__onMouseDown(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + removeListener(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + addListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + addListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onTouchEnd: function(e) { + if (e.touches.length > 0) { + // if there are still touches stop here + return; + } + this.__onMouseUp(e); + this._resetTransformEventData(); + this.mainTouchId = null; + var eventTypePrefix = this._getEventPrefix(); + removeListener(fabric.document, 'touchend', this._onTouchEnd, addEventOptions); + removeListener(fabric.document, 'touchmove', this._onMouseMove, addEventOptions); + var _this = this; + if (this._willAddMouseDown) { + clearTimeout(this._willAddMouseDown); + } + this._willAddMouseDown = setTimeout(function() { + // Wait 400ms before rebinding mousedown to prevent double triggers + // from touch devices + addListener(_this.upperCanvasEl, eventTypePrefix + 'down', _this._onMouseDown); + _this._willAddMouseDown = 0; + }, 400); + }, + + /** + * @private + * @param {Event} e Event object fired on mouseup + */ + _onMouseUp: function (e) { + this.__onMouseUp(e); + this._resetTransformEventData(); + var canvasElement = this.upperCanvasEl, + eventTypePrefix = this._getEventPrefix(); + if (this._isMainEvent(e)) { + removeListener(fabric.document, eventTypePrefix + 'up', this._onMouseUp); + removeListener(fabric.document, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + addListener(canvasElement, eventTypePrefix + 'move', this._onMouseMove, addEventOptions); + } + }, + + /** + * @private + * @param {Event} e Event object fired on mousemove + */ + _onMouseMove: function (e) { + !this.allowTouchScrolling && e.preventDefault && e.preventDefault(); + this.__onMouseMove(e); + }, + + /** + * @private + */ + _onResize: function () { + this.calcOffset(); + }, + + /** + * Decides whether the canvas should be redrawn in mouseup and mousedown events. + * @private + * @param {Object} target + */ + _shouldRender: function(target) { + var activeObject = this._activeObject; + + if ( + !!activeObject !== !!target || + (activeObject && target && (activeObject !== target)) + ) { + // this covers: switch of target, from target to no target, selection of target + // multiSelection with key and mouse + return true; + } + else if (activeObject && activeObject.isEditing) { + // if we mouse up/down over a editing textbox a cursor change, + // there is no need to re render + return false; + } + return false; + }, + + /** + * Method that defines the actions when mouse is released on canvas. + * The method resets the currentTransform parameters, store the image corner + * position in the image object and render the canvas on top. + * @private + * @param {Event} e Event object fired on mouseup + */ + __onMouseUp: function (e) { + var target, transform = this._currentTransform, + groupSelector = this._groupSelector, shouldRender = false, + isClick = (!groupSelector || (groupSelector.left === 0 && groupSelector.top === 0)); + this._cacheTransformEventData(e); + target = this._target; + this._handleEvent(e, 'up:before'); + // if right/middle click just fire events and return + // target undefined will make the _handleEvent search the target + if (checkClick(e, RIGHT_CLICK)) { + if (this.fireRightClick) { + this._handleEvent(e, 'up', RIGHT_CLICK, isClick); + } + return; + } + + if (checkClick(e, MIDDLE_CLICK)) { + if (this.fireMiddleClick) { + this._handleEvent(e, 'up', MIDDLE_CLICK, isClick); + } + this._resetTransformEventData(); + return; + } + + if (this.isDrawingMode && this._isCurrentlyDrawing) { + this._onMouseUpInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + if (transform) { + this._finalizeCurrentTransform(e); + shouldRender = transform.actionPerformed; + } + if (!isClick) { + var targetWasActive = target === this._activeObject; + this._maybeGroupObjects(e); + if (!shouldRender) { + shouldRender = ( + this._shouldRender(target) || + (!targetWasActive && target === this._activeObject) + ); + } + } + if (target) { + if (target.selectable && target !== this._activeObject && target.activeOn === 'up') { + this.setActiveObject(target, e); + shouldRender = true; + } + else { + var corner = target._findTargetCorner( + this.getPointer(e, true), + fabric.util.isTouchEvent(e) + ); + var control = target.controls[corner], + mouseUpHandler = control && control.getMouseUpHandler(e, target, control); + if (mouseUpHandler) { + var pointer = this.getPointer(e); + mouseUpHandler(e, transform, pointer.x, pointer.y); + } + } + target.isMoving = false; + } + this._setCursorFromEvent(e, target); + this._handleEvent(e, 'up', LEFT_CLICK, isClick); + this._groupSelector = null; + this._currentTransform = null; + // reset the target information about which corner is selected + target && (target.__corner = 0); + if (shouldRender) { + this.requestRenderAll(); + } + else if (!isClick) { + this.renderTop(); + } + }, + + /** + * @private + * Handle event firing for target and subtargets + * @param {Event} e event from mouse + * @param {String} eventType event to fire (up, down or move) + * @return {Fabric.Object} target return the the target found, for internal reasons. + */ + _simpleEventHandler: function(eventType, e) { + var target = this.findTarget(e), + targets = this.targets, + options = { + e: e, + target: target, + subTargets: targets, + }; + this.fire(eventType, options); + target && target.fire(eventType, options); + if (!targets) { + return target; + } + for (var i = 0; i < targets.length; i++) { + targets[i].fire(eventType, options); + } + return target; + }, + + /** + * @private + * Handle event firing for target and subtargets + * @param {Event} e event from mouse + * @param {String} eventType event to fire (up, down or move) + * @param {fabric.Object} targetObj receiving event + * @param {Number} [button] button used in the event 1 = left, 2 = middle, 3 = right + * @param {Boolean} isClick for left button only, indicates that the mouse up happened without move. + */ + _handleEvent: function(e, eventType, button, isClick) { + var target = this._target, + targets = this.targets || [], + options = { + e: e, + target: target, + subTargets: targets, + button: button || LEFT_CLICK, + isClick: isClick || false, + pointer: this._pointer, + absolutePointer: this._absolutePointer, + transform: this._currentTransform + }; + if (eventType === 'up') { + options.currentTarget = this.findTarget(e); + options.currentSubTargets = this.targets; + } + this.fire('mouse:' + eventType, options); + target && target.fire('mouse' + eventType, options); + for (var i = 0; i < targets.length; i++) { + targets[i].fire('mouse' + eventType, options); + } + }, + + /** + * @private + * @param {Event} e send the mouse event that generate the finalize down, so it can be used in the event + */ + _finalizeCurrentTransform: function(e) { + + var transform = this._currentTransform, + target = transform.target, + eventName, + options = { + e: e, + target: target, + transform: transform, + action: transform.action, + }; + + if (target._scaling) { + target._scaling = false; + } + + target.setCoords(); + + if (transform.actionPerformed || (this.stateful && target.hasStateChanged())) { + if (transform.actionPerformed) { + // this is not friendly to the new control api. + // is deprecated. + eventName = this._addEventOptions(options, transform); + this._fire(eventName, options); + } + this._fire('modified', options); + } + }, + + /** + * Mutate option object in order to add by property and give back the event name. + * @private + * @deprecated since 4.2.0 + * @param {Object} options to mutate + * @param {Object} transform to inspect action from + */ + _addEventOptions: function(options, transform) { + // we can probably add more details at low cost + // scale change, rotation changes, translation changes + var eventName, by; + switch (transform.action) { + case 'scaleX': + eventName = 'scaled'; + by = 'x'; + break; + case 'scaleY': + eventName = 'scaled'; + by = 'y'; + break; + case 'skewX': + eventName = 'skewed'; + by = 'x'; + break; + case 'skewY': + eventName = 'skewed'; + by = 'y'; + break; + case 'scale': + eventName = 'scaled'; + by = 'equally'; + break; + case 'rotate': + eventName = 'rotated'; + break; + case 'drag': + eventName = 'moved'; + break; + } + options.by = by; + return eventName; + }, + + /** + * @private + * @param {Event} e Event object fired on mousedown + */ + _onMouseDownInDrawingMode: function(e) { + this._isCurrentlyDrawing = true; + if (this.getActiveObject()) { + this.discardActiveObject(e).requestRenderAll(); + } + var pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseDown(pointer, { e: e, pointer: pointer }); + this._handleEvent(e, 'down'); + }, + + /** + * @private + * @param {Event} e Event object fired on mousemove + */ + _onMouseMoveInDrawingMode: function(e) { + if (this._isCurrentlyDrawing) { + var pointer = this.getPointer(e); + this.freeDrawingBrush.onMouseMove(pointer, { e: e, pointer: pointer }); + } + this.setCursor(this.freeDrawingCursor); + this._handleEvent(e, 'move'); + }, + + /** + * @private + * @param {Event} e Event object fired on mouseup + */ + _onMouseUpInDrawingMode: function(e) { + var pointer = this.getPointer(e); + this._isCurrentlyDrawing = this.freeDrawingBrush.onMouseUp({ e: e, pointer: pointer }); + this._handleEvent(e, 'up'); + }, + + /** + * Method that defines the actions when mouse is clicked on canvas. + * The method inits the currentTransform parameters and renders all the + * canvas so the current image can be placed on the top canvas and the rest + * in on the container one. + * @private + * @param {Event} e Event object fired on mousedown + */ + __onMouseDown: function (e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'down:before'); + var target = this._target; + // if right click just fire events + if (checkClick(e, RIGHT_CLICK)) { + if (this.fireRightClick) { + this._handleEvent(e, 'down', RIGHT_CLICK); + } + return; + } + + if (checkClick(e, MIDDLE_CLICK)) { + if (this.fireMiddleClick) { + this._handleEvent(e, 'down', MIDDLE_CLICK); + } + return; + } + + if (this.isDrawingMode) { + this._onMouseDownInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + + // ignore if some object is being transformed at this moment + if (this._currentTransform) { + return; + } + + var pointer = this._pointer; + // save pointer for check in __onMouseUp event + this._previousPointer = pointer; + var shouldRender = this._shouldRender(target), + shouldGroup = this._shouldGroup(e, target); + if (this._shouldClearSelection(e, target)) { + this.discardActiveObject(e); + } + else if (shouldGroup) { + this._handleGrouping(e, target); + target = this._activeObject; + } + + if (this.selection && (!target || + (!target.selectable && !target.isEditing && target !== this._activeObject))) { + this._groupSelector = { + ex: this._absolutePointer.x, + ey: this._absolutePointer.y, + top: 0, + left: 0 + }; + } + + if (target) { + var alreadySelected = target === this._activeObject; + if (target.selectable && target.activeOn === 'down') { + this.setActiveObject(target, e); + } + var corner = target._findTargetCorner( + this.getPointer(e, true), + fabric.util.isTouchEvent(e) + ); + target.__corner = corner; + if (target === this._activeObject && (corner || !shouldGroup)) { + this._setupCurrentTransform(e, target, alreadySelected); + var control = target.controls[corner], + pointer = this.getPointer(e), + mouseDownHandler = control && control.getMouseDownHandler(e, target, control); + if (mouseDownHandler) { + mouseDownHandler(e, this._currentTransform, pointer.x, pointer.y); + } + } + } + this._handleEvent(e, 'down'); + // we must renderAll so that we update the visuals + (shouldRender || shouldGroup) && this.requestRenderAll(); + }, + + /** + * reset cache form common information needed during event processing + * @private + */ + _resetTransformEventData: function() { + this._target = null; + this._pointer = null; + this._absolutePointer = null; + }, + + /** + * Cache common information needed during event processing + * @private + * @param {Event} e Event object fired on event + */ + _cacheTransformEventData: function(e) { + // reset in order to avoid stale caching + this._resetTransformEventData(); + this._pointer = this.getPointer(e, true); + this._absolutePointer = this.restorePointerVpt(this._pointer); + this._target = this._currentTransform ? this._currentTransform.target : this.findTarget(e) || null; + }, + + /** + * @private + */ + _beforeTransform: function(e) { + var t = this._currentTransform; + this.stateful && t.target.saveState(); + this.fire('before:transform', { + e: e, + transform: t, + }); + }, + + /** + * Method that defines the actions when mouse is hovering the canvas. + * The currentTransform parameter will define whether the user is rotating/scaling/translating + * an image or neither of them (only hovering). A group selection is also possible and would cancel + * all any other type of action. + * In case of an image transformation only the top canvas will be rendered. + * @private + * @param {Event} e Event object fired on mousemove + */ + __onMouseMove: function (e) { + this._handleEvent(e, 'move:before'); + this._cacheTransformEventData(e); + var target, pointer; + + if (this.isDrawingMode) { + this._onMouseMoveInDrawingMode(e); + return; + } + + if (!this._isMainEvent(e)) { + return; + } + + var groupSelector = this._groupSelector; + + // We initially clicked in an empty area, so we draw a box for multiple selection + if (groupSelector) { + pointer = this._absolutePointer; + + groupSelector.left = pointer.x - groupSelector.ex; + groupSelector.top = pointer.y - groupSelector.ey; + + this.renderTop(); + } + else if (!this._currentTransform) { + target = this.findTarget(e) || null; + this._setCursorFromEvent(e, target); + this._fireOverOutEvents(target, e); + } + else { + this._transformObject(e); + } + this._handleEvent(e, 'move'); + this._resetTransformEventData(); + }, + + /** + * Manage the mouseout, mouseover events for the fabric object on the canvas + * @param {Fabric.Object} target the target where the target from the mousemove event + * @param {Event} e Event object fired on mousemove + * @private + */ + _fireOverOutEvents: function(target, e) { + var _hoveredTarget = this._hoveredTarget, + _hoveredTargets = this._hoveredTargets, targets = this.targets, + length = Math.max(_hoveredTargets.length, targets.length); + + this.fireSyntheticInOutEvents(target, e, { + oldTarget: _hoveredTarget, + evtOut: 'mouseout', + canvasEvtOut: 'mouse:out', + evtIn: 'mouseover', + canvasEvtIn: 'mouse:over', + }); + for (var i = 0; i < length; i++){ + this.fireSyntheticInOutEvents(targets[i], e, { + oldTarget: _hoveredTargets[i], + evtOut: 'mouseout', + evtIn: 'mouseover', + }); + } + this._hoveredTarget = target; + this._hoveredTargets = this.targets.concat(); + }, + + /** + * Manage the dragEnter, dragLeave events for the fabric objects on the canvas + * @param {Fabric.Object} target the target where the target from the onDrag event + * @param {Event} e Event object fired on ondrag + * @private + */ + _fireEnterLeaveEvents: function(target, e) { + var _draggedoverTarget = this._draggedoverTarget, + _hoveredTargets = this._hoveredTargets, targets = this.targets, + length = Math.max(_hoveredTargets.length, targets.length); + + this.fireSyntheticInOutEvents(target, e, { + oldTarget: _draggedoverTarget, + evtOut: 'dragleave', + evtIn: 'dragenter', + }); + for (var i = 0; i < length; i++) { + this.fireSyntheticInOutEvents(targets[i], e, { + oldTarget: _hoveredTargets[i], + evtOut: 'dragleave', + evtIn: 'dragenter', + }); + } + this._draggedoverTarget = target; + }, + + /** + * Manage the synthetic in/out events for the fabric objects on the canvas + * @param {Fabric.Object} target the target where the target from the supported events + * @param {Event} e Event object fired + * @param {Object} config configuration for the function to work + * @param {String} config.targetName property on the canvas where the old target is stored + * @param {String} [config.canvasEvtOut] name of the event to fire at canvas level for out + * @param {String} config.evtOut name of the event to fire for out + * @param {String} [config.canvasEvtIn] name of the event to fire at canvas level for in + * @param {String} config.evtIn name of the event to fire for in + * @private + */ + fireSyntheticInOutEvents: function(target, e, config) { + var inOpt, outOpt, oldTarget = config.oldTarget, outFires, inFires, + targetChanged = oldTarget !== target, canvasEvtIn = config.canvasEvtIn, canvasEvtOut = config.canvasEvtOut; + if (targetChanged) { + inOpt = { e: e, target: target, previousTarget: oldTarget }; + outOpt = { e: e, target: oldTarget, nextTarget: target }; + } + inFires = target && targetChanged; + outFires = oldTarget && targetChanged; + if (outFires) { + canvasEvtOut && this.fire(canvasEvtOut, outOpt); + oldTarget.fire(config.evtOut, outOpt); + } + if (inFires) { + canvasEvtIn && this.fire(canvasEvtIn, inOpt); + target.fire(config.evtIn, inOpt); + } + }, + + /** + * Method that defines actions when an Event Mouse Wheel + * @param {Event} e Event object fired on mouseup + */ + __onMouseWheel: function(e) { + this._cacheTransformEventData(e); + this._handleEvent(e, 'wheel'); + this._resetTransformEventData(); + }, + + /** + * @private + * @param {Event} e Event fired on mousemove + */ + _transformObject: function(e) { + var pointer = this.getPointer(e), + transform = this._currentTransform; + + transform.reset = false; + transform.shiftKey = e.shiftKey; + transform.altKey = e[this.centeredKey]; + + this._performTransformAction(e, transform, pointer); + transform.actionPerformed && this.requestRenderAll(); + }, + + /** + * @private + */ + _performTransformAction: function(e, transform, pointer) { + var x = pointer.x, + y = pointer.y, + action = transform.action, + actionPerformed = false, + actionHandler = transform.actionHandler; + // this object could be created from the function in the control handlers + + + if (actionHandler) { + actionPerformed = actionHandler(e, transform, x, y); + } + if (action === 'drag' && actionPerformed) { + transform.target.isMoving = true; + this.setCursor(transform.target.moveCursor || this.moveCursor); + } + transform.actionPerformed = transform.actionPerformed || actionPerformed; + }, + + /** + * @private + */ + _fire: fabric.controlsUtils.fireEvent, + + /** + * Sets the cursor depending on where the canvas is being hovered. + * Note: very buggy in Opera + * @param {Event} e Event object + * @param {Object} target Object that the mouse is hovering, if so. + */ + _setCursorFromEvent: function (e, target) { + if (!target) { + this.setCursor(this.defaultCursor); + return false; + } + var hoverCursor = target.hoverCursor || this.hoverCursor, + activeSelection = this._activeObject && this._activeObject.type === 'activeSelection' ? + this._activeObject : null, + // only show proper corner when group selection is not active + corner = (!activeSelection || !activeSelection.contains(target)) + // here we call findTargetCorner always with undefined for the touch parameter. + // we assume that if you are using a cursor you do not need to interact with + // the bigger touch area. + && target._findTargetCorner(this.getPointer(e, true)); + + if (!corner) { + if (target.subTargetCheck){ + // hoverCursor should come from top-most subTarget, + // so we walk the array backwards + this.targets.concat().reverse().map(function(_target){ + hoverCursor = _target.hoverCursor || hoverCursor; + }); + } + this.setCursor(hoverCursor); + } + else { + this.setCursor(this.getCornerCursor(corner, target, e)); + } + }, + + /** + * @private + */ + getCornerCursor: function(corner, target, e) { + var control = target.controls[corner]; + return control.cursorStyleHandler(e, control, target); + } + }); +})(); + + +(function() { + + var min = Math.min, + max = Math.max; + + fabric.util.object.extend(fabric.Canvas.prototype, /** @lends fabric.Canvas.prototype */ { + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + * @return {Boolean} + */ + _shouldGroup: function(e, target) { + var activeObject = this._activeObject; + return activeObject && this._isSelectionKeyPressed(e) && target && target.selectable && this.selection && + (activeObject !== target || activeObject.type === 'activeSelection') && !target.onSelect({ e: e }); + }, + + /** + * @private + * @param {Event} e Event object + * @param {fabric.Object} target + */ + _handleGrouping: function (e, target) { + var activeObject = this._activeObject; + // avoid multi select when shift click on a corner + if (activeObject.__corner) { + return; + } + if (target === activeObject) { + // if it's a group, find target again, using activeGroup objects + target = this.findTarget(e, true); + // if even object is not found or we are on activeObjectCorner, bail out + if (!target || !target.selectable) { + return; + } + } + if (activeObject && activeObject.type === 'activeSelection') { + this._updateActiveSelection(target, e); + } + else { + this._createActiveSelection(target, e); + } + }, + + /** + * @private + */ + _updateActiveSelection: function(target, e) { + var activeSelection = this._activeObject, + currentActiveObjects = activeSelection._objects.slice(0); + if (activeSelection.contains(target)) { + activeSelection.removeWithUpdate(target); + this._hoveredTarget = target; + this._hoveredTargets = this.targets.concat(); + if (activeSelection.size() === 1) { + // activate last remaining object + this._setActiveObject(activeSelection.item(0), e); + } + } + else { + activeSelection.addWithUpdate(target); + this._hoveredTarget = activeSelection; + this._hoveredTargets = this.targets.concat(); + } + this._fireSelectionEvents(currentActiveObjects, e); + }, + + /** + * @private + */ + _createActiveSelection: function(target, e) { + var currentActives = this.getActiveObjects(), group = this._createGroup(target); + this._hoveredTarget = group; + // ISSUE 4115: should we consider subTargets here? + // this._hoveredTargets = []; + // this._hoveredTargets = this.targets.concat(); + this._setActiveObject(group, e); + this._fireSelectionEvents(currentActives, e); + }, + + /** + * @private + * @param {Object} target + */ + _createGroup: function(target) { + var objects = this._objects, + isActiveLower = objects.indexOf(this._activeObject) < objects.indexOf(target), + groupObjects = isActiveLower + ? [this._activeObject, target] + : [target, this._activeObject]; + this._activeObject.isEditing && this._activeObject.exitEditing(); + return new fabric.ActiveSelection(groupObjects, { + canvas: this + }); + }, + + /** + * @private + * @param {Event} e mouse event + */ + _groupSelectedObjects: function (e) { + + var group = this._collectObjects(e), + aGroup; + + // do not create group for 1 element only + if (group.length === 1) { + this.setActiveObject(group[0], e); + } + else if (group.length > 1) { + aGroup = new fabric.ActiveSelection(group.reverse(), { + canvas: this + }); + this.setActiveObject(aGroup, e); + } + }, + + /** + * @private + */ + _collectObjects: function(e) { + var group = [], + currentObject, + x1 = this._groupSelector.ex, + y1 = this._groupSelector.ey, + x2 = x1 + this._groupSelector.left, + y2 = y1 + this._groupSelector.top, + selectionX1Y1 = new fabric.Point(min(x1, x2), min(y1, y2)), + selectionX2Y2 = new fabric.Point(max(x1, x2), max(y1, y2)), + allowIntersect = !this.selectionFullyContained, + isClick = x1 === x2 && y1 === y2; + // we iterate reverse order to collect top first in case of click. + for (var i = this._objects.length; i--; ) { + currentObject = this._objects[i]; + + if (!currentObject || !currentObject.selectable || !currentObject.visible) { + continue; + } + + if ((allowIntersect && currentObject.intersectsWithRect(selectionX1Y1, selectionX2Y2, true)) || + currentObject.isContainedWithinRect(selectionX1Y1, selectionX2Y2, true) || + (allowIntersect && currentObject.containsPoint(selectionX1Y1, null, true)) || + (allowIntersect && currentObject.containsPoint(selectionX2Y2, null, true)) + ) { + group.push(currentObject); + // only add one object if it's a click + if (isClick) { + break; + } + } + } + + if (group.length > 1) { + group = group.filter(function(object) { + return !object.onSelect({ e: e }); + }); + } + + return group; + }, + + /** + * @private + */ + _maybeGroupObjects: function(e) { + if (this.selection && this._groupSelector) { + this._groupSelectedObjects(e); + } + this.setCursor(this.defaultCursor); + // clear selection and current transformation + this._groupSelector = null; + } + }); + +})(); + + +(function () { + fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Exports canvas element to a dataurl image. Note that when multiplier is used, cropping is scaled appropriately + * @param {Object} [options] Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by, to have consistent + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 2.0.0 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + * @see {@link http://jsfiddle.net/fabricjs/NfZVb/|jsFiddle demo} + * @example Generate jpeg dataURL with lower quality + * var dataURL = canvas.toDataURL({ + * format: 'jpeg', + * quality: 0.8 + * }); + * @example Generate cropped png dataURL (clipping of canvas) + * var dataURL = canvas.toDataURL({ + * format: 'png', + * left: 100, + * top: 100, + * width: 200, + * height: 200 + * }); + * @example Generate double scaled png dataURL + * var dataURL = canvas.toDataURL({ + * format: 'png', + * multiplier: 2 + * }); + */ + toDataURL: function (options) { + options || (options = { }); + + var format = options.format || 'png', + quality = options.quality || 1, + multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? this.getRetinaScaling() : 1), + canvasEl = this.toCanvasElement(multiplier, options); + return fabric.util.toDataURL(canvasEl, format, quality); + }, + + /** + * Create a new HTMLCanvas element painted with the current canvas content. + * No need to resize the actual one or repaint it. + * Will transfer object ownership to a new canvas, paint it, and set everything back. + * This is an intermediary step used to get to a dataUrl but also it is useful to + * create quick image copies of a canvas without passing for the dataUrl string + * @param {Number} [multiplier] a zoom factor. + * @param {Object} [cropping] Cropping informations + * @param {Number} [cropping.left] Cropping left offset. + * @param {Number} [cropping.top] Cropping top offset. + * @param {Number} [cropping.width] Cropping width. + * @param {Number} [cropping.height] Cropping height. + */ + toCanvasElement: function(multiplier, cropping) { + multiplier = multiplier || 1; + cropping = cropping || { }; + var scaledWidth = (cropping.width || this.width) * multiplier, + scaledHeight = (cropping.height || this.height) * multiplier, + zoom = this.getZoom(), + originalWidth = this.width, + originalHeight = this.height, + newZoom = zoom * multiplier, + vp = this.viewportTransform, + translateX = (vp[4] - (cropping.left || 0)) * multiplier, + translateY = (vp[5] - (cropping.top || 0)) * multiplier, + originalInteractive = this.interactive, + newVp = [newZoom, 0, 0, newZoom, translateX, translateY], + originalRetina = this.enableRetinaScaling, + canvasEl = fabric.util.createCanvasElement(), + originalContextTop = this.contextTop; + canvasEl.width = scaledWidth; + canvasEl.height = scaledHeight; + this.contextTop = null; + this.enableRetinaScaling = false; + this.interactive = false; + this.viewportTransform = newVp; + this.width = scaledWidth; + this.height = scaledHeight; + this.calcViewportBoundaries(); + this.renderCanvas(canvasEl.getContext('2d'), this._objects); + this.viewportTransform = vp; + this.width = originalWidth; + this.height = originalHeight; + this.calcViewportBoundaries(); + this.interactive = originalInteractive; + this.enableRetinaScaling = originalRetina; + this.contextTop = originalContextTop; + return canvasEl; + }, + }); + +})(); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + /** + * Populates canvas with data from the specified JSON. + * JSON format must conform to the one of {@link fabric.Canvas#toJSON} + * @param {String|Object} json JSON string or object + * @param {Function} callback Callback, invoked when json is parsed + * and corresponding objects (e.g: {@link fabric.Image}) + * are initialized + * @param {Function} [reviver] Method for further parsing of JSON elements, called after each fabric object created. + * @return {fabric.Canvas} instance + * @chainable + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#deserialization} + * @see {@link http://jsfiddle.net/fabricjs/fmgXt/|jsFiddle demo} + * @example loadFromJSON + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas)); + * @example loadFromJSON with reviver + * canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) { + * // `o` = json object + * // `object` = fabric.Object instance + * // ... do some stuff ... + * }); + */ + loadFromJSON: function (json, callback, reviver) { + if (!json) { + return; + } + + // serialize if it wasn't already + var serialized = (typeof json === 'string') + ? JSON.parse(json) + : fabric.util.object.clone(json); + + var _this = this, + clipPath = serialized.clipPath, + renderOnAddRemove = this.renderOnAddRemove; + + this.renderOnAddRemove = false; + + delete serialized.clipPath; + + this._enlivenObjects(serialized.objects, function (enlivenedObjects) { + _this.clear(); + _this._setBgOverlay(serialized, function () { + if (clipPath) { + _this._enlivenObjects([clipPath], function (enlivenedCanvasClip) { + _this.clipPath = enlivenedCanvasClip[0]; + _this.__setupCanvas.call(_this, serialized, enlivenedObjects, renderOnAddRemove, callback); + }); + } + else { + _this.__setupCanvas.call(_this, serialized, enlivenedObjects, renderOnAddRemove, callback); + } + }); + }, reviver); + return this; + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Array} restored canvas objects + * @param {Function} cached renderOnAddRemove callback + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + __setupCanvas: function(serialized, enlivenedObjects, renderOnAddRemove, callback) { + var _this = this; + enlivenedObjects.forEach(function(obj, index) { + // we splice the array just in case some custom classes restored from JSON + // will add more object to canvas at canvas init. + _this.insertAt(obj, index); + }); + this.renderOnAddRemove = renderOnAddRemove; + // remove parts i cannot set as options + delete serialized.objects; + delete serialized.backgroundImage; + delete serialized.overlayImage; + delete serialized.background; + delete serialized.overlay; + // this._initOptions does too many things to just + // call it. Normally loading an Object from JSON + // create the Object instance. Here the Canvas is + // already an instance and we are just loading things over it + this._setOptions(serialized); + this.renderAll(); + callback && callback(); + }, + + /** + * @private + * @param {Object} serialized Object with background and overlay information + * @param {Function} callback Invoked after all background and overlay images/patterns loaded + */ + _setBgOverlay: function(serialized, callback) { + var loaded = { + backgroundColor: false, + overlayColor: false, + backgroundImage: false, + overlayImage: false + }; + + if (!serialized.backgroundImage && !serialized.overlayImage && !serialized.background && !serialized.overlay) { + callback && callback(); + return; + } + + var cbIfLoaded = function () { + if (loaded.backgroundImage && loaded.overlayImage && loaded.backgroundColor && loaded.overlayColor) { + callback && callback(); + } + }; + + this.__setBgOverlay('backgroundImage', serialized.backgroundImage, loaded, cbIfLoaded); + this.__setBgOverlay('overlayImage', serialized.overlayImage, loaded, cbIfLoaded); + this.__setBgOverlay('backgroundColor', serialized.background, loaded, cbIfLoaded); + this.__setBgOverlay('overlayColor', serialized.overlay, loaded, cbIfLoaded); + }, + + /** + * @private + * @param {String} property Property to set (backgroundImage, overlayImage, backgroundColor, overlayColor) + * @param {(Object|String)} value Value to set + * @param {Object} loaded Set loaded property to true if property is set + * @param {Object} callback Callback function to invoke after property is set + */ + __setBgOverlay: function(property, value, loaded, callback) { + var _this = this; + + if (!value) { + loaded[property] = true; + callback && callback(); + return; + } + + if (property === 'backgroundImage' || property === 'overlayImage') { + fabric.util.enlivenObjects([value], function(enlivedObject){ + _this[property] = enlivedObject[0]; + loaded[property] = true; + callback && callback(); + }); + } + else { + this['set' + fabric.util.string.capitalize(property, true)](value, function() { + loaded[property] = true; + callback && callback(); + }); + } + }, + + /** + * @private + * @param {Array} objects + * @param {Function} callback + * @param {Function} [reviver] + */ + _enlivenObjects: function (objects, callback, reviver) { + if (!objects || objects.length === 0) { + callback && callback([]); + return; + } + + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, null, reviver); + }, + + /** + * @private + * @param {String} format + * @param {Function} callback + */ + _toDataURL: function (format, callback) { + this.clone(function (clone) { + callback(clone.toDataURL(format)); + }); + }, + + /** + * @private + * @param {String} format + * @param {Number} multiplier + * @param {Function} callback + */ + _toDataURLWithMultiplier: function (format, multiplier, callback) { + this.clone(function (clone) { + callback(clone.toDataURLWithMultiplier(format, multiplier)); + }); + }, + + /** + * Clones canvas instance + * @param {Object} [callback] Receives cloned instance as a first argument + * @param {Array} [properties] Array of properties to include in the cloned canvas and children + */ + clone: function (callback, properties) { + var data = JSON.stringify(this.toJSON(properties)); + this.cloneWithoutData(function(clone) { + clone.loadFromJSON(data, function() { + callback && callback(clone); + }); + }); + }, + + /** + * Clones canvas instance without cloning existing data. + * This essentially copies canvas dimensions, clipping properties, etc. + * but leaves data empty (so that you can populate it with your own) + * @param {Object} [callback] Receives cloned instance as a first argument + */ + cloneWithoutData: function(callback) { + var el = fabric.util.createCanvasElement(); + + el.width = this.width; + el.height = this.height; + + var clone = new fabric.Canvas(el); + if (this.backgroundImage) { + clone.setBackgroundImage(this.backgroundImage.src, function() { + clone.renderAll(); + callback && callback(clone); + }); + clone.backgroundImageOpacity = this.backgroundImageOpacity; + clone.backgroundImageStretch = this.backgroundImageStretch; + } + else { + callback && callback(clone); + } + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + toFixed = fabric.util.toFixed, + capitalize = fabric.util.string.capitalize, + degreesToRadians = fabric.util.degreesToRadians, + objectCaching = !fabric.isLikelyNode, + ALIASING_LIMIT = 2; + + if (fabric.Object) { + return; + } + + /** + * Root object class from which all 2d shape classes inherit from + * @class fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#objects} + * @see {@link fabric.Object#initialize} for constructor definition + * + * @fires added + * @fires removed + * + * @fires selected + * @fires deselected + * @fires modified + * @fires modified + * @fires moved + * @fires scaled + * @fires rotated + * @fires skewed + * + * @fires rotating + * @fires scaling + * @fires moving + * @fires skewing + * + * @fires mousedown + * @fires mouseup + * @fires mouseover + * @fires mouseout + * @fires mousewheel + * @fires mousedblclick + * + * @fires dragover + * @fires dragenter + * @fires dragleave + * @fires drop + */ + fabric.Object = fabric.util.createClass(fabric.CommonMethods, /** @lends fabric.Object.prototype */ { + + /** + * Type of an object (rect, circle, path, etc.). + * Note that this property is meant to be read-only and not meant to be modified. + * If you modify, certain parts of Fabric (such as JSON loading) won't work correctly. + * @type String + * @default + */ + type: 'object', + + /** + * Horizontal origin of transformation of an object (one of "left", "right", "center") + * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups + * @type String + * @default + */ + originX: 'left', + + /** + * Vertical origin of transformation of an object (one of "top", "bottom", "center") + * See http://jsfiddle.net/1ow02gea/244/ on how originX/originY affect objects in groups + * @type String + * @default + */ + originY: 'top', + + /** + * Top position of an object. Note that by default it's relative to object top. You can change this by setting originY={top/center/bottom} + * @type Number + * @default + */ + top: 0, + + /** + * Left position of an object. Note that by default it's relative to object left. You can change this by setting originX={left/center/right} + * @type Number + * @default + */ + left: 0, + + /** + * Object width + * @type Number + * @default + */ + width: 0, + + /** + * Object height + * @type Number + * @default + */ + height: 0, + + /** + * Object scale factor (horizontal) + * @type Number + * @default + */ + scaleX: 1, + + /** + * Object scale factor (vertical) + * @type Number + * @default + */ + scaleY: 1, + + /** + * When true, an object is rendered as flipped horizontally + * @type Boolean + * @default + */ + flipX: false, + + /** + * When true, an object is rendered as flipped vertically + * @type Boolean + * @default + */ + flipY: false, + + /** + * Opacity of an object + * @type Number + * @default + */ + opacity: 1, + + /** + * Angle of rotation of an object (in degrees) + * @type Number + * @default + */ + angle: 0, + + /** + * Angle of skew on x axes of an object (in degrees) + * @type Number + * @default + */ + skewX: 0, + + /** + * Angle of skew on y axes of an object (in degrees) + * @type Number + * @default + */ + skewY: 0, + + /** + * Size of object's controlling corners (in pixels) + * @type Number + * @default + */ + cornerSize: 13, + + /** + * Size of object's controlling corners when touch interaction is detected + * @type Number + * @default + */ + touchCornerSize: 24, + + /** + * When true, object's controlling corners are rendered as transparent inside (i.e. stroke instead of fill) + * @type Boolean + * @default + */ + transparentCorners: true, + + /** + * Default cursor value used when hovering over this object on canvas + * @type String + * @default + */ + hoverCursor: null, + + /** + * Default cursor value used when moving this object on canvas + * @type String + * @default + */ + moveCursor: null, + + /** + * Padding between object and its controlling borders (in pixels) + * @type Number + * @default + */ + padding: 0, + + /** + * Color of controlling borders of an object (when it's active) + * @type String + * @default + */ + borderColor: 'rgb(178,204,255)', + + /** + * Array specifying dash pattern of an object's borders (hasBorder must be true) + * @since 1.6.2 + * @type Array + */ + borderDashArray: null, + + /** + * Color of controlling corners of an object (when it's active) + * @type String + * @default + */ + cornerColor: 'rgb(178,204,255)', + + /** + * Color of controlling corners of an object (when it's active and transparentCorners false) + * @since 1.6.2 + * @type String + * @default + */ + cornerStrokeColor: null, + + /** + * Specify style of control, 'rect' or 'circle' + * @since 1.6.2 + * @type String + */ + cornerStyle: 'rect', + + /** + * Array specifying dash pattern of an object's control (hasBorder must be true) + * @since 1.6.2 + * @type Array + */ + cornerDashArray: null, + + /** + * When true, this object will use center point as the origin of transformation + * when being scaled via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredScaling: false, + + /** + * When true, this object will use center point as the origin of transformation + * when being rotated via the controls. + * Backwards incompatibility note: This property replaces "centerTransform" (Boolean). + * @since 1.3.4 + * @type Boolean + * @default + */ + centeredRotation: true, + + /** + * Color of object's fill + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + fill: 'rgb(0,0,0)', + + /** + * Fill rule used to fill an object + * accepted values are nonzero, evenodd + * Backwards incompatibility note: This property was used for setting globalCompositeOperation until v1.4.12 (use `fabric.Object#globalCompositeOperation` instead) + * @type String + * @default + */ + fillRule: 'nonzero', + + /** + * Composite rule used for canvas globalCompositeOperation + * @type String + * @default + */ + globalCompositeOperation: 'source-over', + + /** + * Background color of an object. + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + backgroundColor: '', + + /** + * Selection Background color of an object. colored layer behind the object when it is active. + * does not mix good with globalCompositeOperation methods. + * @type String + * @default + */ + selectionBackgroundColor: '', + + /** + * When defined, an object is rendered via stroke and this property specifies its color + * takes css colors https://www.w3.org/TR/css-color-3/ + * @type String + * @default + */ + stroke: null, + + /** + * Width of a stroke used to render this object + * @type Number + * @default + */ + strokeWidth: 1, + + /** + * Array specifying dash pattern of an object's stroke (stroke must be defined) + * @type Array + */ + strokeDashArray: null, + + /** + * Line offset of an object's stroke + * @type Number + * @default + */ + strokeDashOffset: 0, + + /** + * Line endings style of an object's stroke (one of "butt", "round", "square") + * @type String + * @default + */ + strokeLineCap: 'butt', + + /** + * Corner style of an object's stroke (one of "bevel", "round", "miter") + * @type String + * @default + */ + strokeLineJoin: 'miter', + + /** + * Maximum miter length (used for strokeLineJoin = "miter") of an object's stroke + * @type Number + * @default + */ + strokeMiterLimit: 4, + + /** + * Shadow object representing shadow of this shape + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * Opacity of object's controlling borders when object is active and moving + * @type Number + * @default + */ + borderOpacityWhenMoving: 0.4, + + /** + * Scale factor of object's controlling borders + * bigger number will make a thicker border + * border is 1, so this is basically a border thickness + * since there is no way to change the border itself. + * @type Number + * @default + */ + borderScaleFactor: 1, + + /** + * Minimum allowed scale value of an object + * @type Number + * @default + */ + minScaleLimit: 0, + + /** + * When set to `false`, an object can not be selected for modification (using either point-click-based or group-based selection). + * But events still fire on it. + * @type Boolean + * @default + */ + selectable: true, + + /** + * When set to `false`, an object can not be a target of events. All events propagate through it. Introduced in v1.3.4 + * @type Boolean + * @default + */ + evented: true, + + /** + * When set to `false`, an object is not rendered on canvas + * @type Boolean + * @default + */ + visible: true, + + /** + * When set to `false`, object's controls are not displayed and can not be used to manipulate object + * @type Boolean + * @default + */ + hasControls: true, + + /** + * When set to `false`, object's controlling borders are not rendered + * @type Boolean + * @default + */ + hasBorders: true, + + /** + * When set to `true`, objects are "found" on canvas on per-pixel basis rather than according to bounding box + * @type Boolean + * @default + */ + perPixelTargetFind: false, + + /** + * When `false`, default object's values are not included in its serialization + * @type Boolean + * @default + */ + includeDefaultValues: true, + + /** + * When `true`, object horizontal movement is locked + * @type Boolean + * @default + */ + lockMovementX: false, + + /** + * When `true`, object vertical movement is locked + * @type Boolean + * @default + */ + lockMovementY: false, + + /** + * When `true`, object rotation is locked + * @type Boolean + * @default + */ + lockRotation: false, + + /** + * When `true`, object horizontal scaling is locked + * @type Boolean + * @default + */ + lockScalingX: false, + + /** + * When `true`, object vertical scaling is locked + * @type Boolean + * @default + */ + lockScalingY: false, + + /** + * When `true`, object horizontal skewing is locked + * @type Boolean + * @default + */ + lockSkewingX: false, + + /** + * When `true`, object vertical skewing is locked + * @type Boolean + * @default + */ + lockSkewingY: false, + + /** + * When `true`, object cannot be flipped by scaling into negative values + * @type Boolean + * @default + */ + lockScalingFlip: false, + + /** + * When `true`, object is not exported in OBJECT/JSON + * @since 1.6.3 + * @type Boolean + * @default + */ + excludeFromExport: false, + + /** + * When `true`, object is cached on an additional canvas. + * When `false`, object is not cached unless necessary ( clipPath ) + * default to true + * @since 1.7.0 + * @type Boolean + * @default true + */ + objectCaching: objectCaching, + + /** + * When `true`, object properties are checked for cache invalidation. In some particular + * situation you may want this to be disabled ( spray brush, very big, groups) + * or if your application does not allow you to modify properties for groups child you want + * to disable it for groups. + * default to false + * since 1.7.0 + * @type Boolean + * @default false + */ + statefullCache: false, + + /** + * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled + * too much and will be redrawn with correct details at the end of scaling. + * this setting is performance and application dependant. + * default to true + * since 1.7.0 + * @type Boolean + * @default true + */ + noScaleCache: true, + + /** + * When `false`, the stoke width will scale with the object. + * When `true`, the stroke will always match the exact pixel size entered for stroke width. + * default to false + * @since 2.6.0 + * @type Boolean + * @default false + * @type Boolean + * @default false + */ + strokeUniform: false, + + /** + * When set to `true`, object's cache will be rerendered next render call. + * since 1.7.0 + * @type Boolean + * @default true + */ + dirty: true, + + /** + * keeps the value of the last hovered corner during mouse move. + * 0 is no corner, or 'mt', 'ml', 'mtr' etc.. + * It should be private, but there is no harm in using it as + * a read-only property. + * @type number|string|any + * @default 0 + */ + __corner: 0, + + /** + * Determines if the fill or the stroke is drawn first (one of "fill" or "stroke") + * @type String + * @default + */ + paintFirst: 'fill', + + /** + * When 'down', object is set to active on mousedown/touchstart + * When 'up', object is set to active on mouseup/touchend + * Experimental. Let's see if this breaks anything before supporting officially + * @private + * since 4.4.0 + * @type String + * @default 'down' + */ + activeOn: 'down', + + /** + * List of properties to consider when checking if state + * of an object is changed (fabric.Object#hasStateChanged) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: ( + 'top left width height scaleX scaleY flipX flipY originX originY transformMatrix ' + + 'stroke strokeWidth strokeDashArray strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit ' + + 'angle opacity fill globalCompositeOperation shadow visible backgroundColor ' + + 'skewX skewY fillRule paintFirst clipPath strokeUniform' + ).split(' '), + + /** + * List of properties to consider when checking if cache needs refresh + * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single + * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty + * and refreshed at the next render + * @type Array + */ + cacheProperties: ( + 'fill stroke strokeWidth strokeDashArray width height paintFirst strokeUniform' + + ' strokeLineCap strokeDashOffset strokeLineJoin strokeMiterLimit backgroundColor clipPath' + ).split(' '), + + /** + * List of properties to consider for animating colors. + * @type Array + */ + colorProperties: ( + 'fill stroke backgroundColor' + ).split(' '), + + /** + * a fabricObject that, without stroke define a clipping area with their shape. filled in black + * the clipPath object gets used when the object has rendered, and the context is placed in the center + * of the object cacheCanvas. + * If you want 0,0 of a clipPath to align with an object center, use clipPath.originX/Y to 'center' + * @type fabric.Object + */ + clipPath: undefined, + + /** + * Meaningful ONLY when the object is used as clipPath. + * if true, the clipPath will make the object clip to the outside of the clipPath + * since 2.4.0 + * @type boolean + * @default false + */ + inverted: false, + + /** + * Meaningful ONLY when the object is used as clipPath. + * if true, the clipPath will have its top and left relative to canvas, and will + * not be influenced by the object transform. This will make the clipPath relative + * to the canvas, but clipping just a particular object. + * WARNING this is beta, this feature may change or be renamed. + * since 2.4.0 + * @type boolean + * @default false + */ + absolutePositioned: false, + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * Create a the canvas used to keep the cached copy of the object + * @private + */ + _createCacheCanvas: function() { + this._cacheProperties = {}; + this._cacheCanvas = fabric.util.createCanvasElement(); + this._cacheContext = this._cacheCanvas.getContext('2d'); + this._updateCacheCanvas(); + // if canvas gets created, is empty, so dirty. + this.dirty = true; + }, + + /** + * Limit the cache dimensions so that X * Y do not cross fabric.perfLimitSizeTotal + * and each side do not cross fabric.cacheSideLimit + * those numbers are configurable so that you can get as much detail as you want + * making bargain with performances. + * @param {Object} dims + * @param {Object} dims.width width of canvas + * @param {Object} dims.height height of canvas + * @param {Object} dims.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @param {Object} dims.zoomY zoomY zoom value to unscale the canvas before drawing cache + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _limitCacheSize: function(dims) { + var perfLimitSizeTotal = fabric.perfLimitSizeTotal, + width = dims.width, height = dims.height, + max = fabric.maxCacheSideLimit, min = fabric.minCacheSideLimit; + if (width <= max && height <= max && width * height <= perfLimitSizeTotal) { + if (width < min) { + dims.width = min; + } + if (height < min) { + dims.height = min; + } + return dims; + } + var ar = width / height, limitedDims = fabric.util.limitDimsByArea(ar, perfLimitSizeTotal), + capValue = fabric.util.capValue, + x = capValue(min, limitedDims.x, max), + y = capValue(min, limitedDims.y, max); + if (width > x) { + dims.zoomX /= width / x; + dims.width = x; + dims.capped = true; + } + if (height > y) { + dims.zoomY /= height / y; + dims.height = y; + dims.capped = true; + } + return dims; + }, + + /** + * Return the dimension and the zoom level needed to create a cache canvas + * big enough to host the object to be cached. + * @private + * @return {Object}.x width of object to be cached + * @return {Object}.y height of object to be cached + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _getCacheCanvasDimensions: function() { + var objectScale = this.getTotalObjectScaling(), + // caculate dimensions without skewing + dim = this._getTransformedDimensions(0, 0), + neededX = dim.x * objectScale.scaleX / this.scaleX, + neededY = dim.y * objectScale.scaleY / this.scaleY; + return { + // for sure this ALIASING_LIMIT is slightly creating problem + // in situation in which the cache canvas gets an upper limit + // also objectScale contains already scaleX and scaleY + width: neededX + ALIASING_LIMIT, + height: neededY + ALIASING_LIMIT, + zoomX: objectScale.scaleX, + zoomY: objectScale.scaleY, + x: neededX, + y: neededY + }; + }, + + /** + * Update width and height of the canvas for cache + * returns true or false if canvas needed resize. + * @private + * @return {Boolean} true if the canvas has been resized + */ + _updateCacheCanvas: function() { + var targetCanvas = this.canvas; + if (this.noScaleCache && targetCanvas && targetCanvas._currentTransform) { + var target = targetCanvas._currentTransform.target, + action = targetCanvas._currentTransform.action; + if (this === target && action.slice && action.slice(0, 5) === 'scale') { + return false; + } + } + var canvas = this._cacheCanvas, + dims = this._limitCacheSize(this._getCacheCanvasDimensions()), + minCacheSize = fabric.minCacheSideLimit, + width = dims.width, height = dims.height, drawingWidth, drawingHeight, + zoomX = dims.zoomX, zoomY = dims.zoomY, + dimensionsChanged = width !== this.cacheWidth || height !== this.cacheHeight, + zoomChanged = this.zoomX !== zoomX || this.zoomY !== zoomY, + shouldRedraw = dimensionsChanged || zoomChanged, + additionalWidth = 0, additionalHeight = 0, shouldResizeCanvas = false; + if (dimensionsChanged) { + var canvasWidth = this._cacheCanvas.width, + canvasHeight = this._cacheCanvas.height, + sizeGrowing = width > canvasWidth || height > canvasHeight, + sizeShrinking = (width < canvasWidth * 0.9 || height < canvasHeight * 0.9) && + canvasWidth > minCacheSize && canvasHeight > minCacheSize; + shouldResizeCanvas = sizeGrowing || sizeShrinking; + if (sizeGrowing && !dims.capped && (width > minCacheSize || height > minCacheSize)) { + additionalWidth = width * 0.1; + additionalHeight = height * 0.1; + } + } + if (this instanceof fabric.Text && this.path) { + shouldRedraw = true; + shouldResizeCanvas = true; + additionalWidth += this.getHeightOfLine(0) * this.zoomX; + additionalHeight += this.getHeightOfLine(0) * this.zoomY; + } + if (shouldRedraw) { + if (shouldResizeCanvas) { + canvas.width = Math.ceil(width + additionalWidth); + canvas.height = Math.ceil(height + additionalHeight); + } + else { + this._cacheContext.setTransform(1, 0, 0, 1, 0, 0); + this._cacheContext.clearRect(0, 0, canvas.width, canvas.height); + } + drawingWidth = dims.x / 2; + drawingHeight = dims.y / 2; + this.cacheTranslationX = Math.round(canvas.width / 2 - drawingWidth) + drawingWidth; + this.cacheTranslationY = Math.round(canvas.height / 2 - drawingHeight) + drawingHeight; + this.cacheWidth = width; + this.cacheHeight = height; + this._cacheContext.translate(this.cacheTranslationX, this.cacheTranslationY); + this._cacheContext.scale(zoomX, zoomY); + this.zoomX = zoomX; + this.zoomY = zoomY; + return true; + } + return false; + }, + + /** + * Sets object's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + this._setOptions(options); + this._initGradient(options.fill, 'fill'); + this._initGradient(options.stroke, 'stroke'); + this._initPattern(options.fill, 'fill'); + this._initPattern(options.stroke, 'stroke'); + }, + + /** + * Transforms context when rendering an object + * @param {CanvasRenderingContext2D} ctx Context + */ + transform: function(ctx) { + var needFullTransform = (this.group && !this.group._transformDone) || + (this.group && this.canvas && ctx === this.canvas.contextTop); + var m = this.calcTransformMatrix(!needFullTransform); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + }, + + /** + * Returns an object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS, + + object = { + type: this.type, + version: fabric.version, + originX: this.originX, + originY: this.originY, + left: toFixed(this.left, NUM_FRACTION_DIGITS), + top: toFixed(this.top, NUM_FRACTION_DIGITS), + width: toFixed(this.width, NUM_FRACTION_DIGITS), + height: toFixed(this.height, NUM_FRACTION_DIGITS), + fill: (this.fill && this.fill.toObject) ? this.fill.toObject() : this.fill, + stroke: (this.stroke && this.stroke.toObject) ? this.stroke.toObject() : this.stroke, + strokeWidth: toFixed(this.strokeWidth, NUM_FRACTION_DIGITS), + strokeDashArray: this.strokeDashArray ? this.strokeDashArray.concat() : this.strokeDashArray, + strokeLineCap: this.strokeLineCap, + strokeDashOffset: this.strokeDashOffset, + strokeLineJoin: this.strokeLineJoin, + strokeUniform: this.strokeUniform, + strokeMiterLimit: toFixed(this.strokeMiterLimit, NUM_FRACTION_DIGITS), + scaleX: toFixed(this.scaleX, NUM_FRACTION_DIGITS), + scaleY: toFixed(this.scaleY, NUM_FRACTION_DIGITS), + angle: toFixed(this.angle, NUM_FRACTION_DIGITS), + flipX: this.flipX, + flipY: this.flipY, + opacity: toFixed(this.opacity, NUM_FRACTION_DIGITS), + shadow: (this.shadow && this.shadow.toObject) ? this.shadow.toObject() : this.shadow, + visible: this.visible, + backgroundColor: this.backgroundColor, + fillRule: this.fillRule, + paintFirst: this.paintFirst, + globalCompositeOperation: this.globalCompositeOperation, + skewX: toFixed(this.skewX, NUM_FRACTION_DIGITS), + skewY: toFixed(this.skewY, NUM_FRACTION_DIGITS), + }; + + if (this.clipPath && !this.clipPath.excludeFromExport) { + object.clipPath = this.clipPath.toObject(propertiesToInclude); + object.clipPath.inverted = this.clipPath.inverted; + object.clipPath.absolutePositioned = this.clipPath.absolutePositioned; + } + + fabric.util.populateWithProperties(this, object, propertiesToInclude); + if (!this.includeDefaultValues) { + object = this._removeDefaultValues(object); + } + + return object; + }, + + /** + * Returns (dataless) object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + // will be overwritten by subclasses + return this.toObject(propertiesToInclude); + }, + + /** + * @private + * @param {Object} object + */ + _removeDefaultValues: function(object) { + var prototype = fabric.util.getKlass(object.type).prototype, + stateProperties = prototype.stateProperties; + stateProperties.forEach(function(prop) { + if (prop === 'left' || prop === 'top') { + return; + } + if (object[prop] === prototype[prop]) { + delete object[prop]; + } + var isArray = Object.prototype.toString.call(object[prop]) === '[object Array]' && + Object.prototype.toString.call(prototype[prop]) === '[object Array]'; + + // basically a check for [] === [] + if (isArray && object[prop].length === 0 && prototype[prop].length === 0) { + delete object[prop]; + } + }); + + return object; + }, + + /** + * Returns a string representation of an instance + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Return the object scale factor counting also the group scaling + * @return {Object} object with scaleX and scaleY properties + */ + getObjectScaling: function() { + // if the object is a top level one, on the canvas, we go for simple aritmetic + // otherwise the complex method with angles will return approximations and decimals + // and will likely kill the cache when not needed + // https://github.com/fabricjs/fabric.js/issues/7157 + if (!this.group) { + return { + scaleX: this.scaleX, + scaleY: this.scaleY, + }; + } + // if we are inside a group total zoom calculation is complex, we defer to generic matrices + var options = fabric.util.qrDecompose(this.calcTransformMatrix()); + return { scaleX: Math.abs(options.scaleX), scaleY: Math.abs(options.scaleY) }; + }, + + /** + * Return the object scale factor counting also the group scaling, zoom and retina + * @return {Object} object with scaleX and scaleY properties + */ + getTotalObjectScaling: function() { + var scale = this.getObjectScaling(), scaleX = scale.scaleX, scaleY = scale.scaleY; + if (this.canvas) { + var zoom = this.canvas.getZoom(); + var retina = this.canvas.getRetinaScaling(); + scaleX *= zoom * retina; + scaleY *= zoom * retina; + } + return { scaleX: scaleX, scaleY: scaleY }; + }, + + /** + * Return the object opacity counting also the group property + * @return {Number} + */ + getObjectOpacity: function() { + var opacity = this.opacity; + if (this.group) { + opacity *= this.group.getObjectOpacity(); + } + return opacity; + }, + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Object} thisArg + */ + _set: function(key, value) { + var shouldConstrainValue = (key === 'scaleX' || key === 'scaleY'), + isChanged = this[key] !== value, groupNeedsUpdate = false; + + if (shouldConstrainValue) { + value = this._constrainScale(value); + } + if (key === 'scaleX' && value < 0) { + this.flipX = !this.flipX; + value *= -1; + } + else if (key === 'scaleY' && value < 0) { + this.flipY = !this.flipY; + value *= -1; + } + else if (key === 'shadow' && value && !(value instanceof fabric.Shadow)) { + value = new fabric.Shadow(value); + } + else if (key === 'dirty' && this.group) { + this.group.set('dirty', value); + } + + this[key] = value; + + if (isChanged) { + groupNeedsUpdate = this.group && this.group.isOnACache(); + if (this.cacheProperties.indexOf(key) > -1) { + this.dirty = true; + groupNeedsUpdate && this.group.set('dirty', true); + } + else if (groupNeedsUpdate && this.stateProperties.indexOf(key) > -1) { + this.group.set('dirty', true); + } + } + return this; + }, + + /** + * This callback function is called by the parent group of an object every + * time a non-delegated property changes on the group. It is passed the key + * and value as parameters. Not adding in this function's signature to avoid + * Travis build error about unused variables. + */ + setOnGroup: function() { + // implemented by sub-classes, as needed. + }, + + /** + * Retrieves viewportTransform from Object's canvas if possible + * @method getViewportTransform + * @memberOf fabric.Object.prototype + * @return {Array} + */ + getViewportTransform: function() { + if (this.canvas && this.canvas.viewportTransform) { + return this.canvas.viewportTransform; + } + return fabric.iMatrix.concat(); + }, + + /* + * @private + * return if the object would be visible in rendering + * @memberOf fabric.Object.prototype + * @return {Boolean} + */ + isNotVisible: function() { + return this.opacity === 0 || + (!this.width && !this.height && this.strokeWidth === 0) || + !this.visible; + }, + + /** + * Renders an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + // do not render if width/height are zeros or object is not visible + if (this.isNotVisible()) { + return; + } + if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { + return; + } + ctx.save(); + this._setupCompositeOperation(ctx); + this.drawSelectionBackground(ctx); + this.transform(ctx); + this._setOpacity(ctx); + this._setShadow(ctx, this); + if (this.shouldCache()) { + this.renderCache(); + this.drawCacheOnCanvas(ctx); + } + else { + this._removeCacheCanvas(); + this.dirty = false; + this.drawObject(ctx); + if (this.objectCaching && this.statefullCache) { + this.saveState({ propertySet: 'cacheProperties' }); + } + } + ctx.restore(); + }, + + renderCache: function(options) { + options = options || {}; + if (!this._cacheCanvas) { + this._createCacheCanvas(); + } + if (this.isCacheDirty()) { + this.statefullCache && this.saveState({ propertySet: 'cacheProperties' }); + this.drawObject(this._cacheContext, options.forClipping); + this.dirty = false; + } + }, + + /** + * Remove cacheCanvas and its dimensions from the objects + */ + _removeCacheCanvas: function() { + this._cacheCanvas = null; + this.cacheWidth = 0; + this.cacheHeight = 0; + }, + + /** + * return true if the object will draw a stroke + * Does not consider text styles. This is just a shortcut used at rendering time + * We want it to be an approximation and be fast. + * wrote to avoid extra caching, it has to return true when stroke happens, + * can guess when it will not happen at 100% chance, does not matter if it misses + * some use case where the stroke is invisible. + * @since 3.0.0 + * @returns Boolean + */ + hasStroke: function() { + return this.stroke && this.stroke !== 'transparent' && this.strokeWidth !== 0; + }, + + /** + * return true if the object will draw a fill + * Does not consider text styles. This is just a shortcut used at rendering time + * We want it to be an approximation and be fast. + * wrote to avoid extra caching, it has to return true when fill happens, + * can guess when it will not happen at 100% chance, does not matter if it misses + * some use case where the fill is invisible. + * @since 3.0.0 + * @returns Boolean + */ + hasFill: function() { + return this.fill && this.fill !== 'transparent'; + }, + + /** + * When set to `true`, force the object to have its own cache, even if it is inside a group + * it may be needed when your object behave in a particular way on the cache and always needs + * its own isolated canvas to render correctly. + * Created to be overridden + * since 1.7.12 + * @returns Boolean + */ + needsItsOwnCache: function() { + if (this.paintFirst === 'stroke' && + this.hasFill() && this.hasStroke() && typeof this.shadow === 'object') { + return true; + } + if (this.clipPath) { + return true; + } + return false; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * objectCaching is a global flag, wins over everything + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * Read as: cache if is needed, or if the feature is enabled but we are not already caching. + * @return {Boolean} + */ + shouldCache: function() { + this.ownCaching = this.needsItsOwnCache() || ( + this.objectCaching && + (!this.group || !this.group.isOnACache()) + ); + return this.ownCaching; + }, + + /** + * Check if this object or a child object will cast a shadow + * used by Group.shouldCache to know if child has a shadow recursively + * @return {Boolean} + */ + willDrawShadow: function() { + return !!this.shadow && (this.shadow.offsetX !== 0 || this.shadow.offsetY !== 0); + }, + + /** + * Execute the drawing operation for an object clipPath + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawClipPathOnCache: function(ctx) { + var path = this.clipPath; + ctx.save(); + // DEBUG: uncomment this line, comment the following + // ctx.globalAlpha = 0.4 + if (path.inverted) { + ctx.globalCompositeOperation = 'destination-out'; + } + else { + ctx.globalCompositeOperation = 'destination-in'; + } + //ctx.scale(1 / 2, 1 / 2); + if (path.absolutePositioned) { + var m = fabric.util.invertTransform(this.calcTransformMatrix()); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + path.transform(ctx); + ctx.scale(1 / path.zoomX, 1 / path.zoomY); + ctx.drawImage(path._cacheCanvas, -path.cacheTranslationX, -path.cacheTranslationY); + ctx.restore(); + }, + + /** + * Execute the drawing operation for an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawObject: function(ctx, forClipping) { + var originalFill = this.fill, originalStroke = this.stroke; + if (forClipping) { + this.fill = 'black'; + this.stroke = ''; + this._setClippingProperties(ctx); + } + else { + this._renderBackground(ctx); + } + this._render(ctx); + this._drawClipPath(ctx); + this.fill = originalFill; + this.stroke = originalStroke; + }, + + _drawClipPath: function(ctx) { + var path = this.clipPath; + if (!path) { return; } + // needed to setup a couple of variables + // path canvas gets overridden with this one. + // TODO find a better solution? + path.canvas = this.canvas; + path.shouldCache(); + path._transformDone = true; + path.renderCache({ forClipping: true }); + this.drawClipPathOnCache(ctx); + }, + + /** + * Paint the cached copy of the object on the target context. + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawCacheOnCanvas: function(ctx) { + ctx.scale(1 / this.zoomX, 1 / this.zoomY); + ctx.drawImage(this._cacheCanvas, -this.cacheTranslationX, -this.cacheTranslationY); + }, + + /** + * Check if cache is dirty + * @param {Boolean} skipCanvas skip canvas checks because this object is painted + * on parent canvas. + */ + isCacheDirty: function(skipCanvas) { + if (this.isNotVisible()) { + return false; + } + if (this._cacheCanvas && !skipCanvas && this._updateCacheCanvas()) { + // in this case the context is already cleared. + return true; + } + else { + if (this.dirty || + (this.clipPath && this.clipPath.absolutePositioned) || + (this.statefullCache && this.hasStateChanged('cacheProperties')) + ) { + if (this._cacheCanvas && !skipCanvas) { + var width = this.cacheWidth / this.zoomX; + var height = this.cacheHeight / this.zoomY; + this._cacheContext.clearRect(-width / 2, -height / 2, width, height); + } + return true; + } + } + return false; + }, + + /** + * Draws a background for the object big as its untransformed dimensions + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderBackground: function(ctx) { + if (!this.backgroundColor) { + return; + } + var dim = this._getNonTransformedDimensions(); + ctx.fillStyle = this.backgroundColor; + + ctx.fillRect( + -dim.x / 2, + -dim.y / 2, + dim.x, + dim.y + ); + // if there is background color no other shadows + // should be casted + this._removeShadow(ctx); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setOpacity: function(ctx) { + if (this.group && !this.group._transformDone) { + ctx.globalAlpha = this.getObjectOpacity(); + } + else { + ctx.globalAlpha *= this.opacity; + } + }, + + _setStrokeStyles: function(ctx, decl) { + var stroke = decl.stroke; + if (stroke) { + ctx.lineWidth = decl.strokeWidth; + ctx.lineCap = decl.strokeLineCap; + ctx.lineDashOffset = decl.strokeDashOffset; + ctx.lineJoin = decl.strokeLineJoin; + ctx.miterLimit = decl.strokeMiterLimit; + if (stroke.toLive) { + if (stroke.gradientUnits === 'percentage' || stroke.gradientTransform || stroke.patternTransform) { + // need to transform gradient in a pattern. + // this is a slow process. If you are hitting this codepath, and the object + // is not using caching, you should consider switching it on. + // we need a canvas as big as the current object caching canvas. + this._applyPatternForTransformedGradient(ctx, stroke); + } + else { + // is a simple gradient or pattern + ctx.strokeStyle = stroke.toLive(ctx, this); + this._applyPatternGradientTransform(ctx, stroke); + } + } + else { + // is a color + ctx.strokeStyle = decl.stroke; + } + } + }, + + _setFillStyles: function(ctx, decl) { + var fill = decl.fill; + if (fill) { + if (fill.toLive) { + ctx.fillStyle = fill.toLive(ctx, this); + this._applyPatternGradientTransform(ctx, decl.fill); + } + else { + ctx.fillStyle = fill; + } + } + }, + + _setClippingProperties: function(ctx) { + ctx.globalAlpha = 1; + ctx.strokeStyle = 'transparent'; + ctx.fillStyle = '#000000'; + }, + + /** + * @private + * Sets line dash + * @param {CanvasRenderingContext2D} ctx Context to set the dash line on + * @param {Array} dashArray array representing dashes + */ + _setLineDash: function(ctx, dashArray) { + if (!dashArray || dashArray.length === 0) { + return; + } + // Spec requires the concatenation of two copies the dash list when the number of elements is odd + if (1 & dashArray.length) { + dashArray.push.apply(dashArray, dashArray); + } + ctx.setLineDash(dashArray); + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [styleOverride] properties to override the object style + */ + _renderControls: function(ctx, styleOverride) { + var vpt = this.getViewportTransform(), + matrix = this.calcTransformMatrix(), + options, drawBorders, drawControls; + styleOverride = styleOverride || { }; + drawBorders = typeof styleOverride.hasBorders !== 'undefined' ? styleOverride.hasBorders : this.hasBorders; + drawControls = typeof styleOverride.hasControls !== 'undefined' ? styleOverride.hasControls : this.hasControls; + matrix = fabric.util.multiplyTransformMatrices(vpt, matrix); + options = fabric.util.qrDecompose(matrix); + ctx.save(); + ctx.translate(options.translateX, options.translateY); + ctx.lineWidth = 1 * this.borderScaleFactor; + if (!this.group) { + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + } + ctx.rotate(degreesToRadians(options.angle)); + if (styleOverride.forActiveSelection || this.group) { + drawBorders && this.drawBordersInGroup(ctx, options, styleOverride); + } + else { + drawBorders && this.drawBorders(ctx, styleOverride); + } + drawControls && this.drawControls(ctx, styleOverride); + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _setShadow: function(ctx) { + if (!this.shadow) { + return; + } + + var shadow = this.shadow, canvas = this.canvas, scaling, + multX = (canvas && canvas.viewportTransform[0]) || 1, + multY = (canvas && canvas.viewportTransform[3]) || 1; + if (shadow.nonScaling) { + scaling = { scaleX: 1, scaleY: 1 }; + } + else { + scaling = this.getObjectScaling(); + } + if (canvas && canvas._isRetinaScaling()) { + multX *= fabric.devicePixelRatio; + multY *= fabric.devicePixelRatio; + } + ctx.shadowColor = shadow.color; + ctx.shadowBlur = shadow.blur * fabric.browserShadowBlurConstant * + (multX + multY) * (scaling.scaleX + scaling.scaleY) / 4; + ctx.shadowOffsetX = shadow.offsetX * multX * scaling.scaleX; + ctx.shadowOffsetY = shadow.offsetY * multY * scaling.scaleY; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _removeShadow: function(ctx) { + if (!this.shadow) { + return; + } + + ctx.shadowColor = ''; + ctx.shadowBlur = ctx.shadowOffsetX = ctx.shadowOffsetY = 0; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} filler fabric.Pattern or fabric.Gradient + * @return {Object} offset.offsetX offset for text rendering + * @return {Object} offset.offsetY offset for text rendering + */ + _applyPatternGradientTransform: function(ctx, filler) { + if (!filler || !filler.toLive) { + return { offsetX: 0, offsetY: 0 }; + } + var t = filler.gradientTransform || filler.patternTransform; + var offsetX = -this.width / 2 + filler.offsetX || 0, + offsetY = -this.height / 2 + filler.offsetY || 0; + + if (filler.gradientUnits === 'percentage') { + ctx.transform(this.width, 0, 0, this.height, offsetX, offsetY); + } + else { + ctx.transform(1, 0, 0, 1, offsetX, offsetY); + } + if (t) { + ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5]); + } + return { offsetX: offsetX, offsetY: offsetY }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderPaintInOrder: function(ctx) { + if (this.paintFirst === 'stroke') { + this._renderStroke(ctx); + this._renderFill(ctx); + } + else { + this._renderFill(ctx); + this._renderStroke(ctx); + } + }, + + /** + * @private + * function that actually render something on the context. + * empty here to allow Obects to work on tests to benchmark fabric functionalites + * not related to rendering + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(/* ctx */) { + + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderFill: function(ctx) { + if (!this.fill) { + return; + } + + ctx.save(); + this._setFillStyles(ctx, this); + if (this.fillRule === 'evenodd') { + ctx.fill('evenodd'); + } + else { + ctx.fill(); + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderStroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + + ctx.save(); + if (this.strokeUniform && this.group) { + var scaling = this.getObjectScaling(); + ctx.scale(1 / scaling.scaleX, 1 / scaling.scaleY); + } + else if (this.strokeUniform) { + ctx.scale(1 / this.scaleX, 1 / this.scaleY); + } + this._setLineDash(ctx, this.strokeDashArray); + this._setStrokeStyles(ctx, this); + ctx.stroke(); + ctx.restore(); + }, + + /** + * This function try to patch the missing gradientTransform on canvas gradients. + * transforming a context to transform the gradient, is going to transform the stroke too. + * we want to transform the gradient but not the stroke operation, so we create + * a transformed gradient on a pattern and then we use the pattern instead of the gradient. + * this method has drwabacks: is slow, is in low resolution, needs a patch for when the size + * is limited. + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {fabric.Gradient} filler a fabric gradient instance + */ + _applyPatternForTransformedGradient: function(ctx, filler) { + var dims = this._limitCacheSize(this._getCacheCanvasDimensions()), + pCanvas = fabric.util.createCanvasElement(), pCtx, retinaScaling = this.canvas.getRetinaScaling(), + width = dims.x / this.scaleX / retinaScaling, height = dims.y / this.scaleY / retinaScaling; + pCanvas.width = width; + pCanvas.height = height; + pCtx = pCanvas.getContext('2d'); + pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); + pCtx.lineTo(0, height); pCtx.closePath(); + pCtx.translate(width / 2, height / 2); + pCtx.scale( + dims.zoomX / this.scaleX / retinaScaling, + dims.zoomY / this.scaleY / retinaScaling + ); + this._applyPatternGradientTransform(pCtx, filler); + pCtx.fillStyle = filler.toLive(ctx); + pCtx.fill(); + ctx.translate(-this.width / 2 - this.strokeWidth / 2, -this.height / 2 - this.strokeWidth / 2); + ctx.scale( + retinaScaling * this.scaleX / dims.zoomX, + retinaScaling * this.scaleY / dims.zoomY + ); + ctx.strokeStyle = pCtx.createPattern(pCanvas, 'no-repeat'); + }, + + /** + * This function is an helper for svg import. it returns the center of the object in the svg + * untransformed coordinates + * @private + * @return {Object} center point from element coordinates + */ + _findCenterFromElement: function() { + return { x: this.left + this.width / 2, y: this.top + this.height / 2 }; + }, + + /** + * This function is an helper for svg import. it decompose the transformMatrix + * and assign properties to object. + * untransformed coordinates + * @private + * @chainable + */ + _assignTransformMatrixProps: function() { + if (this.transformMatrix) { + var options = fabric.util.qrDecompose(this.transformMatrix); + this.flipX = false; + this.flipY = false; + this.set('scaleX', options.scaleX); + this.set('scaleY', options.scaleY); + this.angle = options.angle; + this.skewX = options.skewX; + this.skewY = 0; + } + }, + + /** + * This function is an helper for svg import. it removes the transform matrix + * and set to object properties that fabricjs can handle + * @private + * @param {Object} preserveAspectRatioOptions + * @return {thisArg} + */ + _removeTransformMatrix: function(preserveAspectRatioOptions) { + var center = this._findCenterFromElement(); + if (this.transformMatrix) { + this._assignTransformMatrixProps(); + center = fabric.util.transformPoint(center, this.transformMatrix); + } + this.transformMatrix = null; + if (preserveAspectRatioOptions) { + this.scaleX *= preserveAspectRatioOptions.scaleX; + this.scaleY *= preserveAspectRatioOptions.scaleY; + this.cropX = preserveAspectRatioOptions.cropX; + this.cropY = preserveAspectRatioOptions.cropY; + center.x += preserveAspectRatioOptions.offsetLeft; + center.y += preserveAspectRatioOptions.offsetTop; + this.width = preserveAspectRatioOptions.width; + this.height = preserveAspectRatioOptions.height; + } + this.setPositionByOrigin(center, 'center', 'center'); + }, + + /** + * Clones an instance, using a callback method will work for every object. + * @param {Function} callback Callback is invoked with a clone as a first argument + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + */ + clone: function(callback, propertiesToInclude) { + var objectForm = this.toObject(propertiesToInclude); + if (this.constructor.fromObject) { + this.constructor.fromObject(objectForm, callback); + } + else { + fabric.Object._fromObject('Object', objectForm, callback); + } + }, + + /** + * Creates an instance of fabric.Image out of an object + * makes use of toCanvasElement. + * Once this method was based on toDataUrl and loadImage, so it also had a quality + * and format option. toCanvasElement is faster and produce no loss of quality. + * If you need to get a real Jpeg or Png from an object, using toDataURL is the right way to do it. + * toCanvasElement and then toBlob from the obtained canvas is also a good option. + * This method is sync now, but still support the callback because we did not want to break. + * When fabricJS 5.0 will be planned, this will probably be changed to not have a callback. + * @param {Function} callback callback, invoked with an instance as a first argument + * @param {Object} [options] for clone as image, passed to toDataURL + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {fabric.Object} thisArg + */ + cloneAsImage: function(callback, options) { + var canvasEl = this.toCanvasElement(options); + if (callback) { + callback(new fabric.Image(canvasEl)); + } + return this; + }, + + /** + * Converts an object into a HTMLCanvas element + * @param {Object} options Options object + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {HTMLCanvasElement} Returns DOM element with the fabric.Object + */ + toCanvasElement: function(options) { + options || (options = { }); + + var utils = fabric.util, origParams = utils.saveObjectTransform(this), + originalGroup = this.group, + originalShadow = this.shadow, abs = Math.abs, + multiplier = (options.multiplier || 1) * (options.enableRetinaScaling ? fabric.devicePixelRatio : 1); + delete this.group; + if (options.withoutTransform) { + utils.resetObjectTransform(this); + } + if (options.withoutShadow) { + this.shadow = null; + } + + var el = fabric.util.createCanvasElement(), + // skip canvas zoom and calculate with setCoords now. + boundingRect = this.getBoundingRect(true, true), + shadow = this.shadow, scaling, + shadowOffset = { x: 0, y: 0 }, shadowBlur, + width, height; + + if (shadow) { + shadowBlur = shadow.blur; + if (shadow.nonScaling) { + scaling = { scaleX: 1, scaleY: 1 }; + } + else { + scaling = this.getObjectScaling(); + } + // consider non scaling shadow. + shadowOffset.x = 2 * Math.round(abs(shadow.offsetX) + shadowBlur) * (abs(scaling.scaleX)); + shadowOffset.y = 2 * Math.round(abs(shadow.offsetY) + shadowBlur) * (abs(scaling.scaleY)); + } + width = boundingRect.width + shadowOffset.x; + height = boundingRect.height + shadowOffset.y; + // if the current width/height is not an integer + // we need to make it so. + el.width = Math.ceil(width); + el.height = Math.ceil(height); + var canvas = new fabric.StaticCanvas(el, { + enableRetinaScaling: false, + renderOnAddRemove: false, + skipOffscreen: false, + }); + if (options.format === 'jpeg') { + canvas.backgroundColor = '#fff'; + } + this.setPositionByOrigin(new fabric.Point(canvas.width / 2, canvas.height / 2), 'center', 'center'); + + var originalCanvas = this.canvas; + canvas.add(this); + var canvasEl = canvas.toCanvasElement(multiplier || 1, options); + this.shadow = originalShadow; + this.set('canvas', originalCanvas); + if (originalGroup) { + this.group = originalGroup; + } + this.set(origParams).setCoords(); + // canvas.dispose will call image.dispose that will nullify the elements + // since this canvas is a simple element for the process, we remove references + // to objects in this way in order to avoid object trashing. + canvas._objects = []; + canvas.dispose(); + canvas = null; + + return canvasEl; + }, + + /** + * Converts an object into a data-url-like string + * @param {Object} options Options object + * @param {String} [options.format=png] The format of the output image. Either "jpeg" or "png" + * @param {Number} [options.quality=1] Quality level (0..1). Only used for jpeg. + * @param {Number} [options.multiplier=1] Multiplier to scale by + * @param {Number} [options.left] Cropping left offset. Introduced in v1.2.14 + * @param {Number} [options.top] Cropping top offset. Introduced in v1.2.14 + * @param {Number} [options.width] Cropping width. Introduced in v1.2.14 + * @param {Number} [options.height] Cropping height. Introduced in v1.2.14 + * @param {Boolean} [options.enableRetinaScaling] Enable retina scaling for clone image. Introduce in 1.6.4 + * @param {Boolean} [options.withoutTransform] Remove current object transform ( no scale , no angle, no flip, no skew ). Introduced in 2.3.4 + * @param {Boolean} [options.withoutShadow] Remove current object shadow. Introduced in 2.4.2 + * @return {String} Returns a data: URL containing a representation of the object in the format specified by options.format + */ + toDataURL: function(options) { + options || (options = { }); + return fabric.util.toDataURL(this.toCanvasElement(options), options.format || 'png', options.quality || 1); + }, + + /** + * Returns true if specified type is identical to the type of an instance + * @param {String} type Type to check against + * @return {Boolean} + */ + isType: function(type) { + return this.type === type; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance (is 1 unless subclassed) + */ + complexity: function() { + return 1; + }, + + /** + * Returns a JSON representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} JSON + */ + toJSON: function(propertiesToInclude) { + // delegate, not alias + return this.toObject(propertiesToInclude); + }, + + /** + * Sets "angle" of an instance with centered rotation + * @param {Number} angle Angle value (in degrees) + * @return {fabric.Object} thisArg + * @chainable + */ + rotate: function(angle) { + var shouldCenterOrigin = (this.originX !== 'center' || this.originY !== 'center') && this.centeredRotation; + + if (shouldCenterOrigin) { + this._setOriginToCenter(); + } + + this.set('angle', angle); + + if (shouldCenterOrigin) { + this._resetOrigin(); + } + + return this; + }, + + /** + * Centers object horizontally on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerH: function () { + this.canvas && this.canvas.centerObjectH(this); + return this; + }, + + /** + * Centers object horizontally on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenterH: function () { + this.canvas && this.canvas.viewportCenterObjectH(this); + return this; + }, + + /** + * Centers object vertically on canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + centerV: function () { + this.canvas && this.canvas.centerObjectV(this); + return this; + }, + + /** + * Centers object vertically on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenterV: function () { + this.canvas && this.canvas.viewportCenterObjectV(this); + return this; + }, + + /** + * Centers object vertically and horizontally on canvas to which is was added last + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + center: function () { + this.canvas && this.canvas.centerObject(this); + return this; + }, + + /** + * Centers object on current viewport of canvas to which it was added last. + * You might need to call `setCoords` on an object after centering, to update controls area. + * @return {fabric.Object} thisArg + * @chainable + */ + viewportCenter: function () { + this.canvas && this.canvas.viewportCenterObject(this); + return this; + }, + + /** + * Returns coordinates of a pointer relative to an object + * @param {Event} e Event to operate upon + * @param {Object} [pointer] Pointer to operate upon (instead of event) + * @return {Object} Coordinates of a pointer (x, y) + */ + getLocalPointer: function(e, pointer) { + pointer = pointer || this.canvas.getPointer(e); + var pClicked = new fabric.Point(pointer.x, pointer.y), + objectLeftTop = this._getLeftTopCoords(); + if (this.angle) { + pClicked = fabric.util.rotatePoint( + pClicked, objectLeftTop, degreesToRadians(-this.angle)); + } + return { + x: pClicked.x - objectLeftTop.x, + y: pClicked.y - objectLeftTop.y + }; + }, + + /** + * Sets canvas globalCompositeOperation for specific object + * custom composition operation for the particular object can be specified using globalCompositeOperation property + * @param {CanvasRenderingContext2D} ctx Rendering canvas context + */ + _setupCompositeOperation: function (ctx) { + if (this.globalCompositeOperation) { + ctx.globalCompositeOperation = this.globalCompositeOperation; + } + } + }); + + fabric.util.createAccessors && fabric.util.createAccessors(fabric.Object); + + extend(fabric.Object.prototype, fabric.Observable); + + /** + * Defines the number of fraction digits to use when serializing object values. + * You can use it to increase/decrease precision of such values like left, top, scaleX, scaleY, etc. + * @static + * @memberOf fabric.Object + * @constant + * @type Number + */ + fabric.Object.NUM_FRACTION_DIGITS = 2; + + fabric.Object._fromObject = function(className, object, callback, extraParam) { + var klass = fabric[className]; + object = clone(object, true); + fabric.util.enlivenPatterns([object.fill, object.stroke], function(patterns) { + if (typeof patterns[0] !== 'undefined') { + object.fill = patterns[0]; + } + if (typeof patterns[1] !== 'undefined') { + object.stroke = patterns[1]; + } + fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { + object.clipPath = enlivedProps[0]; + var instance = extraParam ? new klass(object[extraParam], object) : new klass(object); + callback && callback(instance); + }); + }); + }; + + /** + * Unique id used internally when creating SVG elements + * @static + * @memberOf fabric.Object + * @type Number + */ + fabric.Object.__uid = 0; +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians, + originXOffset = { + left: -0.5, + center: 0, + right: 0.5 + }, + originYOffset = { + top: -0.5, + center: 0, + bottom: 0.5 + }; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Translates the coordinates from a set of origin to another (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} fromOriginX Horizontal origin: 'left', 'center' or 'right' + * @param {String} fromOriginY Vertical origin: 'top', 'center' or 'bottom' + * @param {String} toOriginX Horizontal origin: 'left', 'center' or 'right' + * @param {String} toOriginY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToGivenOrigin: function(point, fromOriginX, fromOriginY, toOriginX, toOriginY) { + var x = point.x, + y = point.y, + offsetX, offsetY, dim; + + if (typeof fromOriginX === 'string') { + fromOriginX = originXOffset[fromOriginX]; + } + else { + fromOriginX -= 0.5; + } + + if (typeof toOriginX === 'string') { + toOriginX = originXOffset[toOriginX]; + } + else { + toOriginX -= 0.5; + } + + offsetX = toOriginX - fromOriginX; + + if (typeof fromOriginY === 'string') { + fromOriginY = originYOffset[fromOriginY]; + } + else { + fromOriginY -= 0.5; + } + + if (typeof toOriginY === 'string') { + toOriginY = originYOffset[toOriginY]; + } + else { + toOriginY -= 0.5; + } + + offsetY = toOriginY - fromOriginY; + + if (offsetX || offsetY) { + dim = this._getTransformedDimensions(); + x = point.x + offsetX * dim.x; + y = point.y + offsetY * dim.y; + } + + return new fabric.Point(x, y); + }, + + /** + * Translates the coordinates from origin to center coordinates (based on the object's dimensions) + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToCenterPoint: function(point, originX, originY) { + var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center'); + if (this.angle) { + return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle)); + } + return p; + }, + + /** + * Translates the coordinates from center to origin coordinates (based on the object's dimensions) + * @param {fabric.Point} center The point which corresponds to center of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + translateToOriginPoint: function(center, originX, originY) { + var p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); + if (this.angle) { + return fabric.util.rotatePoint(p, center, degreesToRadians(this.angle)); + } + return p; + }, + + /** + * Returns the real center coordinates of the object + * @return {fabric.Point} + */ + getCenterPoint: function() { + var leftTop = new fabric.Point(this.left, this.top); + return this.translateToCenterPoint(leftTop, this.originX, this.originY); + }, + + /** + * Returns the coordinates of the object based on center coordinates + * @param {fabric.Point} point The point which corresponds to the originX and originY params + * @return {fabric.Point} + */ + // getOriginPoint: function(center) { + // return this.translateToOriginPoint(center, this.originX, this.originY); + // }, + + /** + * Returns the coordinates of the object as if it has a different origin + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + getPointByOrigin: function(originX, originY) { + var center = this.getCenterPoint(); + return this.translateToOriginPoint(center, originX, originY); + }, + + /** + * Returns the point in local coordinates + * @param {fabric.Point} point The point relative to the global coordinate system + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {fabric.Point} + */ + toLocalPoint: function(point, originX, originY) { + var center = this.getCenterPoint(), + p, p2; + + if (typeof originX !== 'undefined' && typeof originY !== 'undefined' ) { + p = this.translateToGivenOrigin(center, 'center', 'center', originX, originY); + } + else { + p = new fabric.Point(this.left, this.top); + } + + p2 = new fabric.Point(point.x, point.y); + if (this.angle) { + p2 = fabric.util.rotatePoint(p2, center, -degreesToRadians(this.angle)); + } + return p2.subtractEquals(p); + }, + + /** + * Returns the point in global coordinates + * @param {fabric.Point} The point relative to the local coordinate system + * @return {fabric.Point} + */ + // toGlobalPoint: function(point) { + // return fabric.util.rotatePoint(point, this.getCenterPoint(), degreesToRadians(this.angle)).addEquals(new fabric.Point(this.left, this.top)); + // }, + + /** + * Sets the position of the object taking into consideration the object's origin + * @param {fabric.Point} pos The new position of the object + * @param {String} originX Horizontal origin: 'left', 'center' or 'right' + * @param {String} originY Vertical origin: 'top', 'center' or 'bottom' + * @return {void} + */ + setPositionByOrigin: function(pos, originX, originY) { + var center = this.translateToCenterPoint(pos, originX, originY), + position = this.translateToOriginPoint(center, this.originX, this.originY); + this.set('left', position.x); + this.set('top', position.y); + }, + + /** + * @param {String} to One of 'left', 'center', 'right' + */ + adjustPosition: function(to) { + var angle = degreesToRadians(this.angle), + hypotFull = this.getScaledWidth(), + xFull = fabric.util.cos(angle) * hypotFull, + yFull = fabric.util.sin(angle) * hypotFull, + offsetFrom, offsetTo; + + //TODO: this function does not consider mixed situation like top, center. + if (typeof this.originX === 'string') { + offsetFrom = originXOffset[this.originX]; + } + else { + offsetFrom = this.originX - 0.5; + } + if (typeof to === 'string') { + offsetTo = originXOffset[to]; + } + else { + offsetTo = to - 0.5; + } + this.left += xFull * (offsetTo - offsetFrom); + this.top += yFull * (offsetTo - offsetFrom); + this.setCoords(); + this.originX = to; + }, + + /** + * Sets the origin/position of the object to it's center point + * @private + * @return {void} + */ + _setOriginToCenter: function() { + this._originalOriginX = this.originX; + this._originalOriginY = this.originY; + + var center = this.getCenterPoint(); + + this.originX = 'center'; + this.originY = 'center'; + + this.left = center.x; + this.top = center.y; + }, + + /** + * Resets the origin/position of the object to it's original origin + * @private + * @return {void} + */ + _resetOrigin: function() { + var originPoint = this.translateToOriginPoint( + this.getCenterPoint(), + this._originalOriginX, + this._originalOriginY); + + this.originX = this._originalOriginX; + this.originY = this._originalOriginY; + + this.left = originPoint.x; + this.top = originPoint.y; + + this._originalOriginX = null; + this._originalOriginY = null; + }, + + /** + * @private + */ + _getLeftTopCoords: function() { + return this.translateToOriginPoint(this.getCenterPoint(), 'left', 'top'); + }, + }); + +})(); + + +(function() { + + function arrayFromCoords(coords) { + return [ + new fabric.Point(coords.tl.x, coords.tl.y), + new fabric.Point(coords.tr.x, coords.tr.y), + new fabric.Point(coords.br.x, coords.br.y), + new fabric.Point(coords.bl.x, coords.bl.y) + ]; + } + + var util = fabric.util, + degreesToRadians = util.degreesToRadians, + multiplyMatrices = util.multiplyTransformMatrices, + transformPoint = util.transformPoint; + + util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Describe object's corner position in canvas element coordinates. + * properties are depending on control keys and padding the main controls. + * each property is an object with x, y and corner. + * The `corner` property contains in a similar manner the 4 points of the + * interactive area of the corner. + * The coordinates depends from the controls positionHandler and are used + * to draw and locate controls + * @memberOf fabric.Object.prototype + */ + oCoords: null, + + /** + * Describe object's corner position in canvas object absolute coordinates + * properties are tl,tr,bl,br and describe the four main corner. + * each property is an object with x, y, instance of Fabric.Point. + * The coordinates depends from this properties: width, height, scaleX, scaleY + * skewX, skewY, angle, strokeWidth, top, left. + * Those coordinates are useful to understand where an object is. They get updated + * with oCoords but they do not need to be updated when zoom or panning change. + * The coordinates get updated with @method setCoords. + * You can calculate them without updating with @method calcACoords(); + * @memberOf fabric.Object.prototype + */ + aCoords: null, + + /** + * Describe object's corner position in canvas element coordinates. + * includes padding. Used of object detection. + * set and refreshed with setCoords and calcCoords. + * @memberOf fabric.Object.prototype + */ + lineCoords: null, + + /** + * storage for object transform matrix + */ + ownMatrixCache: null, + + /** + * storage for object full transform matrix + */ + matrixCache: null, + + /** + * custom controls interface + * controls are added by default_controls.js + */ + controls: { }, + + /** + * return correct set of coordinates for intersection + * this will return either aCoords or lineCoords. + * @param {Boolean} absolute will return aCoords if true or lineCoords + * @return {Object} {tl, tr, br, bl} points + */ + _getCoords: function(absolute, calculate) { + if (calculate) { + return (absolute ? this.calcACoords() : this.calcLineCoords()); + } + if (!this.aCoords || !this.lineCoords) { + this.setCoords(true); + } + return (absolute ? this.aCoords : this.lineCoords); + }, + + /** + * return correct set of coordinates for intersection + * this will return either aCoords or lineCoords. + * The coords are returned in an array. + * @return {Array} [tl, tr, br, bl] of points + */ + getCoords: function(absolute, calculate) { + return arrayFromCoords(this._getCoords(absolute, calculate)); + }, + + /** + * Checks if object intersects with an area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object intersects with an area formed by 2 points + */ + intersectsWithRect: function(pointTL, pointBR, absolute, calculate) { + var coords = this.getCoords(absolute, calculate), + intersection = fabric.Intersection.intersectPolygonRectangle( + coords, + pointTL, + pointBR + ); + return intersection.status === 'Intersection'; + }, + + /** + * Checks if object intersects with another object + * @param {Object} other Object to test + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object intersects with another object + */ + intersectsWithObject: function(other, absolute, calculate) { + var intersection = fabric.Intersection.intersectPolygonPolygon( + this.getCoords(absolute, calculate), + other.getCoords(absolute, calculate) + ); + + return intersection.status === 'Intersection' + || other.isContainedWithinObject(this, absolute, calculate) + || this.isContainedWithinObject(other, absolute, calculate); + }, + + /** + * Checks if object is fully contained within area of another object + * @param {Object} other Object to test + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is fully contained within area of another object + */ + isContainedWithinObject: function(other, absolute, calculate) { + var points = this.getCoords(absolute, calculate), + otherCoords = absolute ? other.aCoords : other.lineCoords, + i = 0, lines = other._getImageLines(otherCoords); + for (; i < 4; i++) { + if (!other.containsPoint(points[i], lines)) { + return false; + } + } + return true; + }, + + /** + * Checks if object is fully contained within area formed by 2 points + * @param {Object} pointTL top-left point of area + * @param {Object} pointBR bottom-right point of area + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is fully contained within area formed by 2 points + */ + isContainedWithinRect: function(pointTL, pointBR, absolute, calculate) { + var boundingRect = this.getBoundingRect(absolute, calculate); + + return ( + boundingRect.left >= pointTL.x && + boundingRect.left + boundingRect.width <= pointBR.x && + boundingRect.top >= pointTL.y && + boundingRect.top + boundingRect.height <= pointBR.y + ); + }, + + /** + * Checks if point is inside the object + * @param {fabric.Point} point Point to check against + * @param {Object} [lines] object returned from @method _getImageLines + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if point is inside the object + */ + containsPoint: function(point, lines, absolute, calculate) { + var coords = this._getCoords(absolute, calculate), + lines = lines || this._getImageLines(coords), + xPoints = this._findCrossPoints(point, lines); + // if xPoints is odd then point is inside the object + return (xPoints !== 0 && xPoints % 2 === 1); + }, + + /** + * Checks if object is contained within the canvas with current viewportTransform + * the check is done stopping at first point that appears on screen + * @param {Boolean} [calculate] use coordinates of current position instead of .aCoords + * @return {Boolean} true if object is fully or partially contained within canvas + */ + isOnScreen: function(calculate) { + if (!this.canvas) { + return false; + } + var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; + var points = this.getCoords(true, calculate); + // if some point is on screen, the object is on screen. + if (points.some(function(point) { + return point.x <= pointBR.x && point.x >= pointTL.x && + point.y <= pointBR.y && point.y >= pointTL.y; + })) { + return true; + } + // no points on screen, check intersection with absolute coordinates + if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { + return true; + } + return this._containsCenterOfCanvas(pointTL, pointBR, calculate); + }, + + /** + * Checks if the object contains the midpoint between canvas extremities + * Does not make sense outside the context of isOnScreen and isPartiallyOnScreen + * @private + * @param {Fabric.Point} pointTL Top Left point + * @param {Fabric.Point} pointBR Top Right point + * @param {Boolean} calculate use coordinates of current position instead of .oCoords + * @return {Boolean} true if the object contains the point + */ + _containsCenterOfCanvas: function(pointTL, pointBR, calculate) { + // worst case scenario the object is so big that contains the screen + var centerPoint = { x: (pointTL.x + pointBR.x) / 2, y: (pointTL.y + pointBR.y) / 2 }; + if (this.containsPoint(centerPoint, null, true, calculate)) { + return true; + } + return false; + }, + + /** + * Checks if object is partially contained within the canvas with current viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords + * @return {Boolean} true if object is partially contained within canvas + */ + isPartiallyOnScreen: function(calculate) { + if (!this.canvas) { + return false; + } + var pointTL = this.canvas.vptCoords.tl, pointBR = this.canvas.vptCoords.br; + if (this.intersectsWithRect(pointTL, pointBR, true, calculate)) { + return true; + } + var allPointsAreOutside = this.getCoords(true, calculate).every(function(point) { + return (point.x >= pointBR.x || point.x <= pointTL.x) && + (point.y >= pointBR.y || point.y <= pointTL.y); + }); + return allPointsAreOutside && this._containsCenterOfCanvas(pointTL, pointBR, calculate); + }, + + /** + * Method that returns an object with the object edges in it, given the coordinates of the corners + * @private + * @param {Object} oCoords Coordinates of the object corners + */ + _getImageLines: function(oCoords) { + + var lines = { + topline: { + o: oCoords.tl, + d: oCoords.tr + }, + rightline: { + o: oCoords.tr, + d: oCoords.br + }, + bottomline: { + o: oCoords.br, + d: oCoords.bl + }, + leftline: { + o: oCoords.bl, + d: oCoords.tl + } + }; + + // // debugging + // if (this.canvas.contextTop) { + // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); + // } + + return lines; + }, + + /** + * Helper method to determine how many cross points are between the 4 object edges + * and the horizontal line determined by a point on canvas + * @private + * @param {fabric.Point} point Point to check + * @param {Object} lines Coordinates of the object being evaluated + */ + // remove yi, not used but left code here just in case. + _findCrossPoints: function(point, lines) { + var b1, b2, a1, a2, xi, // yi, + xcount = 0, + iLine; + + for (var lineKey in lines) { + iLine = lines[lineKey]; + // optimisation 1: line below point. no cross + if ((iLine.o.y < point.y) && (iLine.d.y < point.y)) { + continue; + } + // optimisation 2: line above point. no cross + if ((iLine.o.y >= point.y) && (iLine.d.y >= point.y)) { + continue; + } + // optimisation 3: vertical line case + if ((iLine.o.x === iLine.d.x) && (iLine.o.x >= point.x)) { + xi = iLine.o.x; + // yi = point.y; + } + // calculate the intersection point + else { + b1 = 0; + b2 = (iLine.d.y - iLine.o.y) / (iLine.d.x - iLine.o.x); + a1 = point.y - b1 * point.x; + a2 = iLine.o.y - b2 * iLine.o.x; + + xi = -(a1 - a2) / (b1 - b2); + // yi = a1 + b1 * xi; + } + // dont count xi < point.x cases + if (xi >= point.x) { + xcount += 1; + } + // optimisation 4: specific for square images + if (xcount === 2) { + break; + } + } + return xcount; + }, + + /** + * Returns coordinates of object's bounding rectangle (left, top, width, height) + * the box is intended as aligned to axis of canvas. + * @param {Boolean} [absolute] use coordinates without viewportTransform + * @param {Boolean} [calculate] use coordinates of current position instead of .oCoords / .aCoords + * @return {Object} Object with left, top, width, height properties + */ + getBoundingRect: function(absolute, calculate) { + var coords = this.getCoords(absolute, calculate); + return util.makeBoundingBoxFromPoints(coords); + }, + + /** + * Returns width of an object's bounding box counting transformations + * before 2.0 it was named getWidth(); + * @return {Number} width value + */ + getScaledWidth: function() { + return this._getTransformedDimensions().x; + }, + + /** + * Returns height of an object bounding box counting transformations + * before 2.0 it was named getHeight(); + * @return {Number} height value + */ + getScaledHeight: function() { + return this._getTransformedDimensions().y; + }, + + /** + * Makes sure the scale is valid and modifies it if necessary + * @private + * @param {Number} value + * @return {Number} + */ + _constrainScale: function(value) { + if (Math.abs(value) < this.minScaleLimit) { + if (value < 0) { + return -this.minScaleLimit; + } + else { + return this.minScaleLimit; + } + } + else if (value === 0) { + return 0.0001; + } + return value; + }, + + /** + * Scales an object (equally by x and y) + * @param {Number} value Scale factor + * @return {fabric.Object} thisArg + * @chainable + */ + scale: function(value) { + this._set('scaleX', value); + this._set('scaleY', value); + return this.setCoords(); + }, + + /** + * Scales an object to a given width, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New width value + * @param {Boolean} absolute ignore viewport + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToWidth: function(value, absolute) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRect(absolute).width / this.getScaledWidth(); + return this.scale(value / this.width / boundingRectFactor); + }, + + /** + * Scales an object to a given height, with respect to bounding box (scaling by x/y equally) + * @param {Number} value New height value + * @param {Boolean} absolute ignore viewport + * @return {fabric.Object} thisArg + * @chainable + */ + scaleToHeight: function(value, absolute) { + // adjust to bounding rect factor so that rotated shapes would fit as well + var boundingRectFactor = this.getBoundingRect(absolute).height / this.getScaledHeight(); + return this.scale(value / this.height / boundingRectFactor); + }, + + /** + * Calculates and returns the .coords of an object. + * unused by the library, only for the end dev. + * @return {Object} Object with tl, tr, br, bl .... + * @chainable + * @deprecated + */ + calcCoords: function(absolute) { + // this is a compatibility function to avoid removing calcCoords now. + if (absolute) { + return this.calcACoords(); + } + return this.calcOCoords(); + }, + + calcLineCoords: function() { + var vpt = this.getViewportTransform(), + padding = this.padding, angle = degreesToRadians(this.angle), + cos = util.cos(angle), sin = util.sin(angle), + cosP = cos * padding, sinP = sin * padding, cosPSinP = cosP + sinP, + cosPMinusSinP = cosP - sinP, aCoords = this.calcACoords(); + + var lineCoords = { + tl: transformPoint(aCoords.tl, vpt), + tr: transformPoint(aCoords.tr, vpt), + bl: transformPoint(aCoords.bl, vpt), + br: transformPoint(aCoords.br, vpt), + }; + + if (padding) { + lineCoords.tl.x -= cosPMinusSinP; + lineCoords.tl.y -= cosPSinP; + lineCoords.tr.x += cosPSinP; + lineCoords.tr.y -= cosPMinusSinP; + lineCoords.bl.x -= cosPSinP; + lineCoords.bl.y += cosPMinusSinP; + lineCoords.br.x += cosPMinusSinP; + lineCoords.br.y += cosPSinP; + } + + return lineCoords; + }, + + calcOCoords: function() { + var rotateMatrix = this._calcRotateMatrix(), + translateMatrix = this._calcTranslateMatrix(), + vpt = this.getViewportTransform(), + startMatrix = multiplyMatrices(vpt, translateMatrix), + finalMatrix = multiplyMatrices(startMatrix, rotateMatrix), + finalMatrix = multiplyMatrices(finalMatrix, [1 / vpt[0], 0, 0, 1 / vpt[3], 0, 0]), + dim = this._calculateCurrentDimensions(), + coords = {}; + this.forEachControl(function(control, key, fabricObject) { + coords[key] = control.positionHandler(dim, finalMatrix, fabricObject); + }); + + // debug code + // var canvas = this.canvas; + // setTimeout(function() { + // canvas.contextTop.clearRect(0, 0, 700, 700); + // canvas.contextTop.fillStyle = 'green'; + // Object.keys(coords).forEach(function(key) { + // var control = coords[key]; + // canvas.contextTop.fillRect(control.x, control.y, 3, 3); + // }); + // }, 50); + return coords; + }, + + calcACoords: function() { + var rotateMatrix = this._calcRotateMatrix(), + translateMatrix = this._calcTranslateMatrix(), + finalMatrix = multiplyMatrices(translateMatrix, rotateMatrix), + dim = this._getTransformedDimensions(), + w = dim.x / 2, h = dim.y / 2; + return { + // corners + tl: transformPoint({ x: -w, y: -h }, finalMatrix), + tr: transformPoint({ x: w, y: -h }, finalMatrix), + bl: transformPoint({ x: -w, y: h }, finalMatrix), + br: transformPoint({ x: w, y: h }, finalMatrix) + }; + }, + + /** + * Sets corner and controls position coordinates based on current angle, width and height, left and top. + * oCoords are used to find the corners + * aCoords are used to quickly find an object on the canvas + * lineCoords are used to quickly find object during pointer events. + * See {@link https://github.com/kangax/fabric.js/wiki/When-to-call-setCoords|When-to-call-setCoords} + * @param {Boolean} [skipCorners] skip calculation of oCoords. + * @return {fabric.Object} thisArg + * @chainable + */ + setCoords: function(skipCorners) { + this.aCoords = this.calcACoords(); + // in case we are in a group, for how the inner group target check works, + // lineCoords are exactly aCoords. Since the vpt gets absorbed by the normalized pointer. + this.lineCoords = this.group ? this.aCoords : this.calcLineCoords(); + if (skipCorners) { + return this; + } + // set coordinates of the draggable boxes in the corners used to scale/rotate the image + this.oCoords = this.calcOCoords(); + this._setCornerCoords && this._setCornerCoords(); + return this; + }, + + /** + * calculate rotation matrix of an object + * @return {Array} rotation matrix for the object + */ + _calcRotateMatrix: function() { + return util.calcRotateMatrix(this); + }, + + /** + * calculate the translation matrix for an object transform + * @return {Array} rotation matrix for the object + */ + _calcTranslateMatrix: function() { + var center = this.getCenterPoint(); + return [1, 0, 0, 1, center.x, center.y]; + }, + + transformMatrixKey: function(skipGroup) { + var sep = '_', prefix = ''; + if (!skipGroup && this.group) { + prefix = this.group.transformMatrixKey(skipGroup) + sep; + }; + return prefix + this.top + sep + this.left + sep + this.scaleX + sep + this.scaleY + + sep + this.skewX + sep + this.skewY + sep + this.angle + sep + this.originX + sep + this.originY + + sep + this.width + sep + this.height + sep + this.strokeWidth + this.flipX + this.flipY; + }, + + /** + * calculate transform matrix that represents the current transformations from the + * object's properties. + * @param {Boolean} [skipGroup] return transform matrix for object not counting parent transformations + * There are some situation in which this is useful to avoid the fake rotation. + * @return {Array} transform matrix for the object + */ + calcTransformMatrix: function(skipGroup) { + var matrix = this.calcOwnMatrix(); + if (skipGroup || !this.group) { + return matrix; + } + var key = this.transformMatrixKey(skipGroup), cache = this.matrixCache || (this.matrixCache = {}); + if (cache.key === key) { + return cache.value; + } + if (this.group) { + matrix = multiplyMatrices(this.group.calcTransformMatrix(false), matrix); + } + cache.key = key; + cache.value = matrix; + return matrix; + }, + + /** + * calculate transform matrix that represents the current transformations from the + * object's properties, this matrix does not include the group transformation + * @return {Array} transform matrix for the object + */ + calcOwnMatrix: function() { + var key = this.transformMatrixKey(true), cache = this.ownMatrixCache || (this.ownMatrixCache = {}); + if (cache.key === key) { + return cache.value; + } + var tMatrix = this._calcTranslateMatrix(), + options = { + angle: this.angle, + translateX: tMatrix[4], + translateY: tMatrix[5], + scaleX: this.scaleX, + scaleY: this.scaleY, + skewX: this.skewX, + skewY: this.skewY, + flipX: this.flipX, + flipY: this.flipY, + }; + cache.key = key; + cache.value = util.composeMatrix(options); + return cache.value; + }, + + /* + * Calculate object dimensions from its properties + * @private + * @deprecated since 3.4.0, please use fabric.util._calcDimensionsTransformMatrix + * not including or including flipX, flipY to emulate the flipping boolean + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _calcDimensionsTransformMatrix: function(skewX, skewY, flipping) { + return util.calcDimensionsMatrix({ + skewX: skewX, + skewY: skewY, + scaleX: this.scaleX * (flipping && this.flipX ? -1 : 1), + scaleY: this.scaleY * (flipping && this.flipY ? -1 : 1) + }); + }, + + /* + * Calculate object dimensions from its properties + * @private + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _getNonTransformedDimensions: function() { + var strokeWidth = this.strokeWidth, + w = this.width + strokeWidth, + h = this.height + strokeWidth; + return { x: w, y: h }; + }, + + /* + * Calculate object bounding box dimensions from its properties scale, skew. + * @param {Number} skewX, a value to override current skewX + * @param {Number} skewY, a value to override current skewY + * @private + * @return {Object} .x width dimension + * @return {Object} .y height dimension + */ + _getTransformedDimensions: function(skewX, skewY) { + if (typeof skewX === 'undefined') { + skewX = this.skewX; + } + if (typeof skewY === 'undefined') { + skewY = this.skewY; + } + var dimensions, dimX, dimY, + noSkew = skewX === 0 && skewY === 0; + + if (this.strokeUniform) { + dimX = this.width; + dimY = this.height; + } + else { + dimensions = this._getNonTransformedDimensions(); + dimX = dimensions.x; + dimY = dimensions.y; + } + if (noSkew) { + return this._finalizeDimensions(dimX * this.scaleX, dimY * this.scaleY); + } + var bbox = util.sizeAfterTransform(dimX, dimY, { + scaleX: this.scaleX, + scaleY: this.scaleY, + skewX: skewX, + skewY: skewY, + }); + return this._finalizeDimensions(bbox.x, bbox.y); + }, + + /* + * Calculate object bounding box dimensions from its properties scale, skew. + * @param Number width width of the bbox + * @param Number height height of the bbox + * @private + * @return {Object} .x finalized width dimension + * @return {Object} .y finalized height dimension + */ + _finalizeDimensions: function(width, height) { + return this.strokeUniform ? + { x: width + this.strokeWidth, y: height + this.strokeWidth } + : + { x: width, y: height }; + }, + + /* + * Calculate object dimensions for controls box, including padding and canvas zoom. + * and active selection + * private + */ + _calculateCurrentDimensions: function() { + var vpt = this.getViewportTransform(), + dim = this._getTransformedDimensions(), + p = transformPoint(dim, vpt, true); + return p.scalarAdd(2 * this.padding); + }, + }); +})(); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Moves an object to the bottom of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + sendToBack: function() { + if (this.group) { + fabric.StaticCanvas.prototype.sendToBack.call(this.group, this); + } + else if (this.canvas) { + this.canvas.sendToBack(this); + } + return this; + }, + + /** + * Moves an object to the top of the stack of drawn objects + * @return {fabric.Object} thisArg + * @chainable + */ + bringToFront: function() { + if (this.group) { + fabric.StaticCanvas.prototype.bringToFront.call(this.group, this); + } + else if (this.canvas) { + this.canvas.bringToFront(this); + } + return this; + }, + + /** + * Moves an object down in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object behind next lower intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + sendBackwards: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.sendBackwards.call(this.group, this, intersecting); + } + else if (this.canvas) { + this.canvas.sendBackwards(this, intersecting); + } + return this; + }, + + /** + * Moves an object up in stack of drawn objects + * @param {Boolean} [intersecting] If `true`, send object in front of next upper intersecting object + * @return {fabric.Object} thisArg + * @chainable + */ + bringForward: function(intersecting) { + if (this.group) { + fabric.StaticCanvas.prototype.bringForward.call(this.group, this, intersecting); + } + else if (this.canvas) { + this.canvas.bringForward(this, intersecting); + } + return this; + }, + + /** + * Moves an object to specified level in stack of drawn objects + * @param {Number} index New position of object + * @return {fabric.Object} thisArg + * @chainable + */ + moveTo: function(index) { + if (this.group && this.group.type !== 'activeSelection') { + fabric.StaticCanvas.prototype.moveTo.call(this.group, this, index); + } + else if (this.canvas) { + this.canvas.moveTo(this, index); + } + return this; + } +}); + + +/* _TO_SVG_START_ */ +(function() { + function getSvgColorString(prop, value) { + if (!value) { + return prop + ': none; '; + } + else if (value.toLive) { + return prop + ': url(#SVGID_' + value.id + '); '; + } + else { + var color = new fabric.Color(value), + str = prop + ': ' + color.toRgb() + '; ', + opacity = color.getAlpha(); + if (opacity !== 1) { + //change the color in rgb + opacity + str += prop + '-opacity: ' + opacity.toString() + '; '; + } + return str; + } + } + + var toFixed = fabric.util.toFixed; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Returns styles-string for svg-export + * @param {Boolean} skipShadow a boolean to skip shadow filter output + * @return {String} + */ + getSvgStyles: function(skipShadow) { + + var fillRule = this.fillRule ? this.fillRule : 'nonzero', + strokeWidth = this.strokeWidth ? this.strokeWidth : '0', + strokeDashArray = this.strokeDashArray ? this.strokeDashArray.join(' ') : 'none', + strokeDashOffset = this.strokeDashOffset ? this.strokeDashOffset : '0', + strokeLineCap = this.strokeLineCap ? this.strokeLineCap : 'butt', + strokeLineJoin = this.strokeLineJoin ? this.strokeLineJoin : 'miter', + strokeMiterLimit = this.strokeMiterLimit ? this.strokeMiterLimit : '4', + opacity = typeof this.opacity !== 'undefined' ? this.opacity : '1', + visibility = this.visible ? '' : ' visibility: hidden;', + filter = skipShadow ? '' : this.getSvgFilter(), + fill = getSvgColorString('fill', this.fill), + stroke = getSvgColorString('stroke', this.stroke); + + return [ + stroke, + 'stroke-width: ', strokeWidth, '; ', + 'stroke-dasharray: ', strokeDashArray, '; ', + 'stroke-linecap: ', strokeLineCap, '; ', + 'stroke-dashoffset: ', strokeDashOffset, '; ', + 'stroke-linejoin: ', strokeLineJoin, '; ', + 'stroke-miterlimit: ', strokeMiterLimit, '; ', + fill, + 'fill-rule: ', fillRule, '; ', + 'opacity: ', opacity, ';', + filter, + visibility + ].join(''); + }, + + /** + * Returns styles-string for svg-export + * @param {Object} style the object from which to retrieve style properties + * @param {Boolean} useWhiteSpace a boolean to include an additional attribute in the style. + * @return {String} + */ + getSvgSpanStyles: function(style, useWhiteSpace) { + var term = '; '; + var fontFamily = style.fontFamily ? + 'font-family: ' + (((style.fontFamily.indexOf('\'') === -1 && style.fontFamily.indexOf('"') === -1) ? + '\'' + style.fontFamily + '\'' : style.fontFamily)) + term : ''; + var strokeWidth = style.strokeWidth ? 'stroke-width: ' + style.strokeWidth + term : '', + fontFamily = fontFamily, + fontSize = style.fontSize ? 'font-size: ' + style.fontSize + 'px' + term : '', + fontStyle = style.fontStyle ? 'font-style: ' + style.fontStyle + term : '', + fontWeight = style.fontWeight ? 'font-weight: ' + style.fontWeight + term : '', + fill = style.fill ? getSvgColorString('fill', style.fill) : '', + stroke = style.stroke ? getSvgColorString('stroke', style.stroke) : '', + textDecoration = this.getSvgTextDecoration(style), + deltaY = style.deltaY ? 'baseline-shift: ' + (-style.deltaY) + '; ' : ''; + if (textDecoration) { + textDecoration = 'text-decoration: ' + textDecoration + term; + } + + return [ + stroke, + strokeWidth, + fontFamily, + fontSize, + fontStyle, + fontWeight, + textDecoration, + fill, + deltaY, + useWhiteSpace ? 'white-space: pre; ' : '' + ].join(''); + }, + + /** + * Returns text-decoration property for svg-export + * @param {Object} style the object from which to retrieve style properties + * @return {String} + */ + getSvgTextDecoration: function(style) { + return ['overline', 'underline', 'line-through'].filter(function(decoration) { + return style[decoration.replace('-', '')]; + }).join(' '); + }, + + /** + * Returns filter for svg shadow + * @return {String} + */ + getSvgFilter: function() { + return this.shadow ? 'filter: url(#SVGID_' + this.shadow.id + ');' : ''; + }, + + /** + * Returns id attribute for svg output + * @return {String} + */ + getSvgCommons: function() { + return [ + this.id ? 'id="' + this.id + '" ' : '', + this.clipPath ? 'clip-path="url(#' + this.clipPath.clipPathId + ')" ' : '', + ].join(''); + }, + + /** + * Returns transform-string for svg-export + * @param {Boolean} use the full transform or the single object one. + * @return {String} + */ + getSvgTransform: function(full, additionalTransform) { + var transform = full ? this.calcTransformMatrix() : this.calcOwnMatrix(), + svgTransform = 'transform="' + fabric.util.matrixToSVG(transform); + return svgTransform + + (additionalTransform || '') + '" '; + }, + + _setSVGBg: function(textBgRects) { + if (this.backgroundColor) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + textBgRects.push( + '\t\t\n'); + } + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + return this._createBaseSVGMarkup(this._toSVG(reviver), { reviver: reviver }); + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + return '\t' + this._createBaseClipPathSVGMarkup(this._toSVG(reviver), { reviver: reviver }); + }, + + /** + * @private + */ + _createBaseClipPathSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var reviver = options.reviver, + additionalTransform = options.additionalTransform || '', + commonPieces = [ + this.getSvgTransform(true, additionalTransform), + this.getSvgCommons(), + ].join(''), + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'); + objectMarkup[index] = commonPieces; + return reviver ? reviver(objectMarkup.join('')) : objectMarkup.join(''); + }, + + /** + * @private + */ + _createBaseSVGMarkup: function(objectMarkup, options) { + options = options || {}; + var noStyle = options.noStyle, + reviver = options.reviver, + styleInfo = noStyle ? '' : 'style="' + this.getSvgStyles() + '" ', + shadowInfo = options.withShadow ? 'style="' + this.getSvgFilter() + '" ' : '', + clipPath = this.clipPath, + vectorEffect = this.strokeUniform ? 'vector-effect="non-scaling-stroke" ' : '', + absoluteClipPath = clipPath && clipPath.absolutePositioned, + stroke = this.stroke, fill = this.fill, shadow = this.shadow, + commonPieces, markup = [], clipPathMarkup, + // insert commons in the markup, style and svgCommons + index = objectMarkup.indexOf('COMMON_PARTS'), + additionalTransform = options.additionalTransform; + if (clipPath) { + clipPath.clipPathId = 'CLIPPATH_' + fabric.Object.__uid++; + clipPathMarkup = '\n' + + clipPath.toClipPathSVG(reviver) + + '\n'; + } + if (absoluteClipPath) { + markup.push( + '\n' + ); + } + markup.push( + '\n' + ); + commonPieces = [ + styleInfo, + vectorEffect, + noStyle ? '' : this.addPaintOrder(), ' ', + additionalTransform ? 'transform="' + additionalTransform + '" ' : '', + ].join(''); + objectMarkup[index] = commonPieces; + if (fill && fill.toLive) { + markup.push(fill.toSVG(this)); + } + if (stroke && stroke.toLive) { + markup.push(stroke.toSVG(this)); + } + if (shadow) { + markup.push(shadow.toSVG(this)); + } + if (clipPath) { + markup.push(clipPathMarkup); + } + markup.push(objectMarkup.join('')); + markup.push('\n'); + absoluteClipPath && markup.push('\n'); + return reviver ? reviver(markup.join('')) : markup.join(''); + }, + + addPaintOrder: function() { + return this.paintFirst !== 'fill' ? ' paint-order="' + this.paintFirst + '" ' : ''; + } + }); +})(); +/* _TO_SVG_END_ */ + + +(function() { + + var extend = fabric.util.object.extend, + originalSet = 'stateProperties'; + + /* + Depends on `stateProperties` + */ + function saveProps(origin, destination, props) { + var tmpObj = { }, deep = true; + props.forEach(function(prop) { + tmpObj[prop] = origin[prop]; + }); + + extend(origin[destination], tmpObj, deep); + } + + function _isEqual(origValue, currentValue, firstPass) { + if (origValue === currentValue) { + // if the objects are identical, return + return true; + } + else if (Array.isArray(origValue)) { + if (!Array.isArray(currentValue) || origValue.length !== currentValue.length) { + return false; + } + for (var i = 0, len = origValue.length; i < len; i++) { + if (!_isEqual(origValue[i], currentValue[i])) { + return false; + } + } + return true; + } + else if (origValue && typeof origValue === 'object') { + var keys = Object.keys(origValue), key; + if (!currentValue || + typeof currentValue !== 'object' || + (!firstPass && keys.length !== Object.keys(currentValue).length) + ) { + return false; + } + for (var i = 0, len = keys.length; i < len; i++) { + key = keys[i]; + // since clipPath is in the statefull cache list and the clipPath objects + // would be iterated as an object, this would lead to possible infinite recursion + // we do not want to compare those. + if (key === 'canvas' || key === 'group') { + continue; + } + if (!_isEqual(origValue[key], currentValue[key])) { + return false; + } + } + return true; + } + } + + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * Returns true if object state (one of its state properties) was changed + * @param {String} [propertySet] optional name for the set of property we want to save + * @return {Boolean} true if instance' state has changed since `{@link fabric.Object#saveState}` was called + */ + hasStateChanged: function(propertySet) { + propertySet = propertySet || originalSet; + var dashedPropertySet = '_' + propertySet; + if (Object.keys(this[dashedPropertySet]).length < this[propertySet].length) { + return true; + } + return !_isEqual(this[dashedPropertySet], this, true); + }, + + /** + * Saves state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + saveState: function(options) { + var propertySet = options && options.propertySet || originalSet, + destination = '_' + propertySet; + if (!this[destination]) { + return this.setupState(options); + } + saveProps(this, destination, this[propertySet]); + if (options && options.stateProperties) { + saveProps(this, destination, options.stateProperties); + } + return this; + }, + + /** + * Setups state of an object + * @param {Object} [options] Object with additional `stateProperties` array to include when saving state + * @return {fabric.Object} thisArg + */ + setupState: function(options) { + options = options || { }; + var propertySet = options.propertySet || originalSet; + options.propertySet = propertySet; + this['_' + propertySet] = { }; + this.saveState(options); + return this; + } + }); +})(); + + +(function() { + + var degreesToRadians = fabric.util.degreesToRadians; + + fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Determines which corner has been clicked + * @private + * @param {Object} pointer The pointer indicating the mouse position + * @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or false if nothing is found + */ + _findTargetCorner: function(pointer, forTouch) { + // objects in group, anykind, are not self modificable, + // must not return an hovered corner. + if (!this.hasControls || this.group || (!this.canvas || this.canvas._activeObject !== this)) { + return false; + } + + var ex = pointer.x, + ey = pointer.y, + xPoints, + lines, keys = Object.keys(this.oCoords), + j = keys.length - 1, i; + this.__corner = 0; + + // cycle in reverse order so we pick first the one on top + for (; j >= 0; j--) { + i = keys[j]; + if (!this.isControlVisible(i)) { + continue; + } + + lines = this._getImageLines(forTouch ? this.oCoords[i].touchCorner : this.oCoords[i].corner); + // // debugging + // + // this.canvas.contextTop.fillRect(lines.bottomline.d.x, lines.bottomline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.bottomline.o.x, lines.bottomline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.leftline.d.x, lines.leftline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.leftline.o.x, lines.leftline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.topline.d.x, lines.topline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.topline.o.x, lines.topline.o.y, 2, 2); + // + // this.canvas.contextTop.fillRect(lines.rightline.d.x, lines.rightline.d.y, 2, 2); + // this.canvas.contextTop.fillRect(lines.rightline.o.x, lines.rightline.o.y, 2, 2); + + xPoints = this._findCrossPoints({ x: ex, y: ey }, lines); + if (xPoints !== 0 && xPoints % 2 === 1) { + this.__corner = i; + return i; + } + } + return false; + }, + + /** + * Calls a function for each control. The function gets called, + * with the control, the object that is calling the iterator and the control's key + * @param {Function} fn function to iterate over the controls over + */ + forEachControl: function(fn) { + for (var i in this.controls) { + fn(this.controls[i], i, this); + }; + }, + + /** + * Sets the coordinates of the draggable boxes in the corners of + * the image used to scale/rotate it. + * note: if we would switch to ROUND corner area, all of this would disappear. + * everything would resolve to a single point and a pythagorean theorem for the distance + * @private + */ + _setCornerCoords: function() { + var coords = this.oCoords; + + for (var control in coords) { + var controlObject = this.controls[control]; + coords[control].corner = controlObject.calcCornerCoords( + this.angle, this.cornerSize, coords[control].x, coords[control].y, false); + coords[control].touchCorner = controlObject.calcCornerCoords( + this.angle, this.touchCornerSize, coords[control].x, coords[control].y, true); + } + }, + + /** + * Draws a colored layer behind the object, inside its selection borders. + * Requires public options: padding, selectionBackgroundColor + * this function is called when the context is transformed + * has checks to be skipped when the object is on a staticCanvas + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @return {fabric.Object} thisArg + * @chainable + */ + drawSelectionBackground: function(ctx) { + if (!this.selectionBackgroundColor || + (this.canvas && !this.canvas.interactive) || + (this.canvas && this.canvas._activeObject !== this) + ) { + return this; + } + ctx.save(); + var center = this.getCenterPoint(), wh = this._calculateCurrentDimensions(), + vpt = this.canvas.viewportTransform; + ctx.translate(center.x, center.y); + ctx.scale(1 / vpt[0], 1 / vpt[3]); + ctx.rotate(degreesToRadians(this.angle)); + ctx.fillStyle = this.selectionBackgroundColor; + ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y); + ctx.restore(); + return this; + }, + + /** + * Draws borders of an object's bounding box. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawBorders: function(ctx, styleOverride) { + styleOverride = styleOverride || {}; + var wh = this._calculateCurrentDimensions(), + strokeWidth = this.borderScaleFactor, + width = wh.x + strokeWidth, + height = wh.y + strokeWidth, + hasControls = typeof styleOverride.hasControls !== 'undefined' ? + styleOverride.hasControls : this.hasControls, + shouldStroke = false; + + ctx.save(); + ctx.strokeStyle = styleOverride.borderColor || this.borderColor; + this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray); + + ctx.strokeRect( + -width / 2, + -height / 2, + width, + height + ); + + if (hasControls) { + ctx.beginPath(); + this.forEachControl(function(control, key, fabricObject) { + // in this moment, the ctx is centered on the object. + // width and height of the above function are the size of the bbox. + if (control.withConnection && control.getVisibility(fabricObject, key)) { + // reset movement for each control + shouldStroke = true; + ctx.moveTo(control.x * width, control.y * height); + ctx.lineTo( + control.x * width + control.offsetX, + control.y * height + control.offsetY + ); + } + }); + if (shouldStroke) { + ctx.stroke(); + } + } + ctx.restore(); + return this; + }, + + /** + * Draws borders of an object's bounding box when it is inside a group. + * Requires public properties: width, height + * Requires public options: padding, borderColor + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {object} options object representing current object parameters + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawBordersInGroup: function(ctx, options, styleOverride) { + styleOverride = styleOverride || {}; + var bbox = fabric.util.sizeAfterTransform(this.width, this.height, options), + strokeWidth = this.strokeWidth, + strokeUniform = this.strokeUniform, + borderScaleFactor = this.borderScaleFactor, + width = + bbox.x + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleX) + borderScaleFactor, + height = + bbox.y + strokeWidth * (strokeUniform ? this.canvas.getZoom() : options.scaleY) + borderScaleFactor; + ctx.save(); + this._setLineDash(ctx, styleOverride.borderDashArray || this.borderDashArray); + ctx.strokeStyle = styleOverride.borderColor || this.borderColor; + ctx.strokeRect( + -width / 2, + -height / 2, + width, + height + ); + + ctx.restore(); + return this; + }, + + /** + * Draws corners of an object's bounding box. + * Requires public properties: width, height + * Requires public options: cornerSize, padding + * @param {CanvasRenderingContext2D} ctx Context to draw on + * @param {Object} styleOverride object to override the object style + * @return {fabric.Object} thisArg + * @chainable + */ + drawControls: function(ctx, styleOverride) { + styleOverride = styleOverride || {}; + ctx.save(); + var retinaScaling = this.canvas.getRetinaScaling(), matrix, p; + ctx.setTransform(retinaScaling, 0, 0, retinaScaling, 0, 0); + ctx.strokeStyle = ctx.fillStyle = styleOverride.cornerColor || this.cornerColor; + if (!this.transparentCorners) { + ctx.strokeStyle = styleOverride.cornerStrokeColor || this.cornerStrokeColor; + } + this._setLineDash(ctx, styleOverride.cornerDashArray || this.cornerDashArray); + this.setCoords(); + if (this.group) { + // fabricJS does not really support drawing controls inside groups, + // this piece of code here helps having at least the control in places. + // If an application needs to show some objects as selected because of some UI state + // can still call Object._renderControls() on any object they desire, independently of groups. + // using no padding, circular controls and hiding the rotating cursor is higly suggested, + matrix = this.group.calcTransformMatrix(); + } + this.forEachControl(function(control, key, fabricObject) { + p = fabricObject.oCoords[key]; + if (control.getVisibility(fabricObject, key)) { + if (matrix) { + p = fabric.util.transformPoint(p, matrix); + } + control.render(ctx, p.x, p.y, styleOverride, fabricObject); + } + }); + ctx.restore(); + + return this; + }, + + /** + * Returns true if the specified control is visible, false otherwise. + * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. + * @returns {Boolean} true if the specified control is visible, false otherwise + */ + isControlVisible: function(controlKey) { + return this.controls[controlKey] && this.controls[controlKey].getVisibility(this, controlKey); + }, + + /** + * Sets the visibility of the specified control. + * @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'. + * @param {Boolean} visible true to set the specified control visible, false otherwise + * @return {fabric.Object} thisArg + * @chainable + */ + setControlVisible: function(controlKey, visible) { + if (!this._controlsVisibility) { + this._controlsVisibility = {}; + } + this._controlsVisibility[controlKey] = visible; + return this; + }, + + /** + * Sets the visibility state of object controls. + * @param {Object} [options] Options object + * @param {Boolean} [options.bl] true to enable the bottom-left control, false to disable it + * @param {Boolean} [options.br] true to enable the bottom-right control, false to disable it + * @param {Boolean} [options.mb] true to enable the middle-bottom control, false to disable it + * @param {Boolean} [options.ml] true to enable the middle-left control, false to disable it + * @param {Boolean} [options.mr] true to enable the middle-right control, false to disable it + * @param {Boolean} [options.mt] true to enable the middle-top control, false to disable it + * @param {Boolean} [options.tl] true to enable the top-left control, false to disable it + * @param {Boolean} [options.tr] true to enable the top-right control, false to disable it + * @param {Boolean} [options.mtr] true to enable the middle-top-rotate control, false to disable it + * @return {fabric.Object} thisArg + * @chainable + */ + setControlsVisibility: function(options) { + options || (options = { }); + + for (var p in options) { + this.setControlVisible(p, options[p]); + } + return this; + }, + + + /** + * This callback function is called every time _discardActiveObject or _setActiveObject + * try to to deselect this object. If the function returns true, the process is cancelled + * @param {Object} [options] options sent from the upper functions + * @param {Event} [options.e] event if the process is generated by an event + */ + onDeselect: function() { + // implemented by sub-classes, as needed. + }, + + + /** + * This callback function is called every time _discardActiveObject or _setActiveObject + * try to to select this object. If the function returns true, the process is cancelled + * @param {Object} [options] options sent from the upper functions + * @param {Event} [options.e] event if the process is generated by an event + */ + onSelect: function() { + // implemented by sub-classes, as needed. + } + }); +})(); + + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Animation duration (in ms) for fx* methods + * @type Number + * @default + */ + FX_DURATION: 500, + + /** + * Centers object horizontally with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectH: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.left, + endValue: this.getCenter().left, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('left', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Centers object vertically with animation. + * @param {fabric.Object} object Object to center + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxCenterObjectV: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.top, + endValue: this.getCenter().top, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('top', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + + return this; + }, + + /** + * Same as `fabric.Canvas#remove` but animated + * @param {fabric.Object} object Object to remove + * @param {Object} [callbacks] Callbacks object with optional "onComplete" and/or "onChange" properties + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxRemove: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: object.opacity, + endValue: 0, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('opacity', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function () { + _this.remove(object); + onComplete(); + } + }); + + return this; + } +}); + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + /** + * Animates object's properties + * @param {String|Object} property Property to animate (if string) or properties to animate (if object) + * @param {Number|Object} value Value to animate property to (if string was given first) or options object + * @return {fabric.Object} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#animation} + * @chainable + * + * As object — multiple properties + * + * object.animate({ left: ..., top: ... }); + * object.animate({ left: ..., top: ... }, { duration: ... }); + * + * As string — one property + * + * object.animate('left', ...); + * object.animate('left', { duration: ... }); + * + */ + animate: function() { + if (arguments[0] && typeof arguments[0] === 'object') { + var propsToAnimate = [], prop, skipCallbacks; + for (prop in arguments[0]) { + propsToAnimate.push(prop); + } + for (var i = 0, len = propsToAnimate.length; i < len; i++) { + prop = propsToAnimate[i]; + skipCallbacks = i !== len - 1; + this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks); + } + } + else { + this._animate.apply(this, arguments); + } + return this; + }, + + /** + * @private + * @param {String} property Property to animate + * @param {String} to Value to animate to + * @param {Object} [options] Options object + * @param {Boolean} [skipCallbacks] When true, callbacks like onchange and oncomplete are not invoked + */ + _animate: function(property, to, options, skipCallbacks) { + var _this = this, propPair; + + to = to.toString(); + + if (!options) { + options = { }; + } + else { + options = fabric.util.object.clone(options); + } + + if (~property.indexOf('.')) { + propPair = property.split('.'); + } + + var propIsColor = + _this.colorProperties.indexOf(property) > -1 || + (propPair && _this.colorProperties.indexOf(propPair[1]) > -1); + + var currentValue = propPair + ? this.get(propPair[0])[propPair[1]] + : this.get(property); + + if (!('from' in options)) { + options.from = currentValue; + } + + if (!propIsColor) { + if (~to.indexOf('=')) { + to = currentValue + parseFloat(to.replace('=', '')); + } + else { + to = parseFloat(to); + } + } + + var _options = { + startValue: options.from, + endValue: to, + byValue: options.by, + easing: options.easing, + duration: options.duration, + abort: options.abort && function(value, valueProgress, timeProgress) { + return options.abort.call(_this, value, valueProgress, timeProgress); + }, + onChange: function (value, valueProgress, timeProgress) { + if (propPair) { + _this[propPair[0]][propPair[1]] = value; + } + else { + _this.set(property, value); + } + if (skipCallbacks) { + return; + } + options.onChange && options.onChange(value, valueProgress, timeProgress); + }, + onComplete: function (value, valueProgress, timeProgress) { + if (skipCallbacks) { + return; + } + + _this.setCoords(); + options.onComplete && options.onComplete(value, valueProgress, timeProgress); + } + }; + + if (propIsColor) { + return fabric.util.animateColor(_options.startValue, _options.endValue, _options.duration, _options); + } + else { + return fabric.util.animate(_options); + } + } +}); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + clone = fabric.util.object.clone, + coordProps = { x1: 1, x2: 1, y1: 1, y2: 1 }; + + if (fabric.Line) { + fabric.warn('fabric.Line is already defined'); + return; + } + + /** + * Line class + * @class fabric.Line + * @extends fabric.Object + * @see {@link fabric.Line#initialize} for constructor definition + */ + fabric.Line = fabric.util.createClass(fabric.Object, /** @lends fabric.Line.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'line', + + /** + * x value or first line edge + * @type Number + * @default + */ + x1: 0, + + /** + * y value or first line edge + * @type Number + * @default + */ + y1: 0, + + /** + * x value or second line edge + * @type Number + * @default + */ + x2: 0, + + /** + * y value or second line edge + * @type Number + * @default + */ + y2: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('x1', 'x2', 'y1', 'y2'), + + /** + * Constructor + * @param {Array} [points] Array of points + * @param {Object} [options] Options object + * @return {fabric.Line} thisArg + */ + initialize: function(points, options) { + if (!points) { + points = [0, 0, 0, 0]; + } + + this.callSuper('initialize', options); + + this.set('x1', points[0]); + this.set('y1', points[1]); + this.set('x2', points[2]); + this.set('y2', points[3]); + + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Object} [options] Options + */ + _setWidthHeight: function(options) { + options || (options = { }); + + this.width = Math.abs(this.x2 - this.x1); + this.height = Math.abs(this.y2 - this.y1); + + this.left = 'left' in options + ? options.left + : this._getLeftToOriginX(); + + this.top = 'top' in options + ? options.top + : this._getTopToOriginY(); + }, + + /** + * @private + * @param {String} key + * @param {*} value + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + if (typeof coordProps[key] !== 'undefined') { + this._setWidthHeight(); + } + return this; + }, + + /** + * @private + * @return {Number} leftToOriginX Distance from left edge of canvas to originX of Line. + */ + _getLeftToOriginX: makeEdgeToOriginGetter( + { // property names + origin: 'originX', + axis1: 'x1', + axis2: 'x2', + dimension: 'width' + }, + { // possible values of origin + nearest: 'left', + center: 'center', + farthest: 'right' + } + ), + + /** + * @private + * @return {Number} topToOriginY Distance from top edge of canvas to originY of Line. + */ + _getTopToOriginY: makeEdgeToOriginGetter( + { // property names + origin: 'originY', + axis1: 'y1', + axis2: 'y2', + dimension: 'height' + }, + { // possible values of origin + nearest: 'top', + center: 'center', + farthest: 'bottom' + } + ), + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + + + var p = this.calcLinePoints(); + ctx.moveTo(p.x1, p.y1); + ctx.lineTo(p.x2, p.y2); + + ctx.lineWidth = this.strokeWidth; + + // TODO: test this + // make sure setting "fill" changes color of a line + // (by copying fillStyle to strokeStyle, since line is stroked, not filled) + var origStrokeStyle = ctx.strokeStyle; + ctx.strokeStyle = this.stroke || ctx.fillStyle; + this.stroke && this._renderStroke(ctx); + ctx.strokeStyle = origStrokeStyle; + }, + + /** + * This function is an helper for svg import. it returns the center of the object in the svg + * untransformed coordinates + * @private + * @return {Object} center point from element coordinates + */ + _findCenterFromElement: function() { + return { + x: (this.x1 + this.x2) / 2, + y: (this.y1 + this.y2) / 2, + }; + }, + + /** + * Returns object representation of an instance + * @method toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), this.calcLinePoints()); + }, + + /* + * Calculate object dimensions from its properties + * @private + */ + _getNonTransformedDimensions: function() { + var dim = this.callSuper('_getNonTransformedDimensions'); + if (this.strokeLineCap === 'butt') { + if (this.width === 0) { + dim.y -= this.strokeWidth; + } + if (this.height === 0) { + dim.x -= this.strokeWidth; + } + } + return dim; + }, + + /** + * Recalculates line points given width and height + * @private + */ + calcLinePoints: function() { + var xMult = this.x1 <= this.x2 ? -1 : 1, + yMult = this.y1 <= this.y2 ? -1 : 1, + x1 = (xMult * this.width * 0.5), + y1 = (yMult * this.height * 0.5), + x2 = (xMult * this.width * -0.5), + y2 = (yMult * this.height * -0.5); + + return { + x1: x1, + x2: x2, + y1: y1, + y2: y2 + }; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var p = this.calcLinePoints(); + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Line.fromElement}) + * @static + * @memberOf fabric.Line + * @see http://www.w3.org/TR/SVG/shapes.html#LineElement + */ + fabric.Line.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x1 y1 x2 y2'.split(' ')); + + /** + * Returns fabric.Line instance from an SVG element + * @static + * @memberOf fabric.Line + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @param {Function} [callback] callback function invoked after parsing + */ + fabric.Line.fromElement = function(element, callback, options) { + options = options || { }; + var parsedAttributes = fabric.parseAttributes(element, fabric.Line.ATTRIBUTE_NAMES), + points = [ + parsedAttributes.x1 || 0, + parsedAttributes.y1 || 0, + parsedAttributes.x2 || 0, + parsedAttributes.y2 || 0 + ]; + callback(new fabric.Line(points, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Line instance from an object representation + * @static + * @memberOf fabric.Line + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + */ + fabric.Line.fromObject = function(object, callback) { + function _callback(instance) { + delete instance.points; + callback && callback(instance); + }; + var options = clone(object, true); + options.points = [object.x1, object.y1, object.x2, object.y2]; + fabric.Object._fromObject('Line', options, _callback, 'points'); + }; + + /** + * Produces a function that calculates distance from canvas edge to Line origin. + */ + function makeEdgeToOriginGetter(propertyNames, originValues) { + var origin = propertyNames.origin, + axis1 = propertyNames.axis1, + axis2 = propertyNames.axis2, + dimension = propertyNames.dimension, + nearest = originValues.nearest, + center = originValues.center, + farthest = originValues.farthest; + + return function() { + switch (this.get(origin)) { + case nearest: + return Math.min(this.get(axis1), this.get(axis2)); + case center: + return Math.min(this.get(axis1), this.get(axis2)) + (0.5 * this.get(dimension)); + case farthest: + return Math.max(this.get(axis1), this.get(axis2)); + } + }; + + } + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + pi = Math.PI; + + if (fabric.Circle) { + fabric.warn('fabric.Circle is already defined.'); + return; + } + + /** + * Circle class + * @class fabric.Circle + * @extends fabric.Object + * @see {@link fabric.Circle#initialize} for constructor definition + */ + fabric.Circle = fabric.util.createClass(fabric.Object, /** @lends fabric.Circle.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'circle', + + /** + * Radius of this circle + * @type Number + * @default + */ + radius: 0, + + /** + * Start angle of the circle, moving clockwise + * deprecated type, this should be in degree, this was an oversight. + * probably will change to degrees in next major version + * @type Number + * @default 0 + */ + startAngle: 0, + + /** + * End angle of the circle + * deprecated type, this should be in degree, this was an oversight. + * probably will change to degrees in next major version + * @type Number + * @default 2Pi + */ + endAngle: pi * 2, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('radius', 'startAngle', 'endAngle'), + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Circle} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + + if (key === 'radius') { + this.setRadius(value); + } + + return this; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['radius', 'startAngle', 'endAngle'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var svgString, x = 0, y = 0, + angle = (this.endAngle - this.startAngle) % ( 2 * pi); + + if (angle === 0) { + svgString = [ + '\n' + ]; + } + else { + var startX = fabric.util.cos(this.startAngle) * this.radius, + startY = fabric.util.sin(this.startAngle) * this.radius, + endX = fabric.util.cos(this.endAngle) * this.radius, + endY = fabric.util.sin(this.endAngle) * this.radius, + largeFlag = angle > pi ? '1' : '0'; + svgString = [ + '\n' + ]; + } + return svgString; + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + ctx.arc( + 0, + 0, + this.radius, + this.startAngle, + this.endAngle, false); + this._renderPaintInOrder(ctx); + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusX: function() { + return this.get('radius') * this.get('scaleX'); + }, + + /** + * Returns vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRadiusY: function() { + return this.get('radius') * this.get('scaleY'); + }, + + /** + * Sets radius of an object (and updates width accordingly) + * @return {fabric.Circle} thisArg + */ + setRadius: function(value) { + this.radius = value; + return this.set('width', value * 2).set('height', value * 2); + }, + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Circle.fromElement}) + * @static + * @memberOf fabric.Circle + * @see: http://www.w3.org/TR/SVG/shapes.html#CircleElement + */ + fabric.Circle.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy r'.split(' ')); + + /** + * Returns {@link fabric.Circle} instance from an SVG element + * @static + * @memberOf fabric.Circle + * @param {SVGElement} element Element to parse + * @param {Function} [callback] Options callback invoked after parsing is finished + * @param {Object} [options] Options object + * @throws {Error} If value of `r` attribute is missing or invalid + */ + fabric.Circle.fromElement = function(element, callback) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Circle.ATTRIBUTE_NAMES); + + if (!isValidRadius(parsedAttributes)) { + throw new Error('value of `r` attribute is required and can not be negative'); + } + + parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.radius; + parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.radius; + callback(new fabric.Circle(parsedAttributes)); + }; + + /** + * @private + */ + function isValidRadius(attributes) { + return (('radius' in attributes) && (attributes.radius >= 0)); + } + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Circle} instance from an object representation + * @static + * @memberOf fabric.Circle + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + * @return {void} + */ + fabric.Circle.fromObject = function(object, callback) { + fabric.Object._fromObject('Circle', object, callback); + }; + +})(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', + + /** + * Width is set to 100 to compensate the old initialize code that was setting it to 100 + * @type Number + * @default + */ + width: 100, + + /** + * Height is set to 100 to compensate the old initialize code that was setting it to 100 + * @type Number + * @default + */ + height: 100, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2; + + ctx.beginPath(); + ctx.moveTo(-widthBy2, heightBy2); + ctx.lineTo(0, -heightBy2); + ctx.lineTo(widthBy2, heightBy2); + ctx.closePath(); + + this._renderPaintInOrder(ctx); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var widthBy2 = this.width / 2, + heightBy2 = this.height / 2, + points = [ + -widthBy2 + ' ' + heightBy2, + '0 ' + -heightBy2, + widthBy2 + ' ' + heightBy2 + ].join(','); + return [ + '' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /** + * Returns {@link fabric.Triangle} instance from an object representation + * @static + * @memberOf fabric.Triangle + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + */ + fabric.Triangle.fromObject = function(object, callback) { + return fabric.Object._fromObject('Triangle', object, callback); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + piBy2 = Math.PI * 2; + + if (fabric.Ellipse) { + fabric.warn('fabric.Ellipse is already defined.'); + return; + } + + /** + * Ellipse class + * @class fabric.Ellipse + * @extends fabric.Object + * @return {fabric.Ellipse} thisArg + * @see {@link fabric.Ellipse#initialize} for constructor definition + */ + fabric.Ellipse = fabric.util.createClass(fabric.Object, /** @lends fabric.Ellipse.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'ellipse', + + /** + * Horizontal radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical radius + * @type Number + * @default + */ + ry: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), + + /** + * Constructor + * @param {Object} [options] Options object + * @return {fabric.Ellipse} thisArg + */ + initialize: function(options) { + this.callSuper('initialize', options); + this.set('rx', options && options.rx || 0); + this.set('ry', options && options.ry || 0); + }, + + /** + * @private + * @param {String} key + * @param {*} value + * @return {fabric.Ellipse} thisArg + */ + _set: function(key, value) { + this.callSuper('_set', key, value); + switch (key) { + + case 'rx': + this.rx = value; + this.set('width', value * 2); + break; + + case 'ry': + this.ry = value; + this.set('height', value * 2); + break; + + } + return this; + }, + + /** + * Returns horizontal radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRx: function() { + return this.get('rx') * this.get('scaleX'); + }, + + /** + * Returns Vertical radius of an object (according to how an object is scaled) + * @return {Number} + */ + getRy: function() { + return this.get('ry') * this.get('scaleY'); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render on + */ + _render: function(ctx) { + ctx.beginPath(); + ctx.save(); + ctx.transform(1, 0, 0, this.ry / this.rx, 0, 0); + ctx.arc( + 0, + 0, + this.rx, + 0, + piBy2, + false); + ctx.restore(); + this._renderPaintInOrder(ctx); + }, + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Ellipse.fromElement}) + * @static + * @memberOf fabric.Ellipse + * @see http://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ + fabric.Ellipse.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('cx cy rx ry'.split(' ')); + + /** + * Returns {@link fabric.Ellipse} instance from an SVG element + * @static + * @memberOf fabric.Ellipse + * @param {SVGElement} element Element to parse + * @param {Function} [callback] Options callback invoked after parsing is finished + * @return {fabric.Ellipse} + */ + fabric.Ellipse.fromElement = function(element, callback) { + + var parsedAttributes = fabric.parseAttributes(element, fabric.Ellipse.ATTRIBUTE_NAMES); + + parsedAttributes.left = (parsedAttributes.left || 0) - parsedAttributes.rx; + parsedAttributes.top = (parsedAttributes.top || 0) - parsedAttributes.ry; + callback(new fabric.Ellipse(parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Ellipse} instance from an object representation + * @static + * @memberOf fabric.Ellipse + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as first argument + * @return {void} + */ + fabric.Ellipse.fromObject = function(object, callback) { + fabric.Object._fromObject('Ellipse', object, callback); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + if (fabric.Rect) { + fabric.warn('fabric.Rect is already defined'); + return; + } + + /** + * Rectangle class + * @class fabric.Rect + * @extends fabric.Object + * @return {fabric.Rect} thisArg + * @see {@link fabric.Rect#initialize} for constructor definition + */ + fabric.Rect = fabric.util.createClass(fabric.Object, /** @lends fabric.Rect.prototype */ { + + /** + * List of properties to consider when checking if state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat('rx', 'ry'), + + /** + * Type of an object + * @type String + * @default + */ + type: 'rect', + + /** + * Horizontal border radius + * @type Number + * @default + */ + rx: 0, + + /** + * Vertical border radius + * @type Number + * @default + */ + ry: 0, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('rx', 'ry'), + + /** + * Constructor + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(options) { + this.callSuper('initialize', options); + this._initRxRy(); + }, + + /** + * Initializes rx/ry attributes + * @private + */ + _initRxRy: function() { + if (this.rx && !this.ry) { + this.ry = this.rx; + } + else if (this.ry && !this.rx) { + this.rx = this.ry; + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + + // 1x1 case (used in spray brush) optimization was removed because + // with caching and higher zoom level this makes more damage than help + + var rx = this.rx ? Math.min(this.rx, this.width / 2) : 0, + ry = this.ry ? Math.min(this.ry, this.height / 2) : 0, + w = this.width, + h = this.height, + x = -this.width / 2, + y = -this.height / 2, + isRounded = rx !== 0 || ry !== 0, + /* "magic number" for bezier approximations of arcs (http://itc.ktu.lt/itc354/Riskus354.pdf) */ + k = 1 - 0.5522847498; + ctx.beginPath(); + + ctx.moveTo(x + rx, y); + + ctx.lineTo(x + w - rx, y); + isRounded && ctx.bezierCurveTo(x + w - k * rx, y, x + w, y + k * ry, x + w, y + ry); + + ctx.lineTo(x + w, y + h - ry); + isRounded && ctx.bezierCurveTo(x + w, y + h - k * ry, x + w - k * rx, y + h, x + w - rx, y + h); + + ctx.lineTo(x + rx, y + h); + isRounded && ctx.bezierCurveTo(x + k * rx, y + h, x, y + h - k * ry, x, y + h - ry); + + ctx.lineTo(x, y + ry); + isRounded && ctx.bezierCurveTo(x, y + k * ry, x + k * rx, y, x + rx, y); + + ctx.closePath(); + + this._renderPaintInOrder(ctx); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['rx', 'ry'].concat(propertiesToInclude)); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var x = -this.width / 2, y = -this.height / 2; + return [ + '\n' + ]; + }, + /* _TO_SVG_END_ */ + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Rect.fromElement`) + * @static + * @memberOf fabric.Rect + * @see: http://www.w3.org/TR/SVG/shapes.html#RectElement + */ + fabric.Rect.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y rx ry width height'.split(' ')); + + /** + * Returns {@link fabric.Rect} instance from an SVG element + * @static + * @memberOf fabric.Rect + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Rect.fromElement = function(element, callback, options) { + if (!element) { + return callback(null); + } + options = options || { }; + + var parsedAttributes = fabric.parseAttributes(element, fabric.Rect.ATTRIBUTE_NAMES); + parsedAttributes.left = parsedAttributes.left || 0; + parsedAttributes.top = parsedAttributes.top || 0; + parsedAttributes.height = parsedAttributes.height || 0; + parsedAttributes.width = parsedAttributes.width || 0; + var rect = new fabric.Rect(extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + rect.visible = rect.visible && rect.width > 0 && rect.height > 0; + callback(rect); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns {@link fabric.Rect} instance from an object representation + * @static + * @memberOf fabric.Rect + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Rect instance is created + */ + fabric.Rect.fromObject = function(object, callback) { + return fabric.Object._fromObject('Rect', object, callback); + }; + +})(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.Polyline) { + fabric.warn('fabric.Polyline is already defined'); + return; + } + + /** + * Polyline class + * @class fabric.Polyline + * @extends fabric.Object + * @see {@link fabric.Polyline#initialize} for constructor definition + */ + fabric.Polyline = fabric.util.createClass(fabric.Object, /** @lends fabric.Polyline.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polyline', + + /** + * Points array + * @type Array + * @default + */ + points: null, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('points'), + + /** + * Constructor + * @param {Array} points Array of points (where each point is an object with x and y) + * @param {Object} [options] Options object + * @return {fabric.Polyline} thisArg + * @example + * var poly = new fabric.Polyline([ + * { x: 10, y: 10 }, + * { x: 50, y: 30 }, + * { x: 40, y: 70 }, + * { x: 60, y: 50 }, + * { x: 100, y: 150 }, + * { x: 40, y: 100 } + * ], { + * stroke: 'red', + * left: 100, + * top: 100 + * }); + */ + initialize: function(points, options) { + options = options || {}; + this.points = points || []; + this.callSuper('initialize', options); + this._setPositionDimensions(options); + }, + + _setPositionDimensions: function(options) { + var calcDim = this._calcDimensions(options), correctLeftTop; + this.width = calcDim.width; + this.height = calcDim.height; + if (!options.fromSVG) { + correctLeftTop = this.translateToGivenOrigin( + { x: calcDim.left - this.strokeWidth / 2, y: calcDim.top - this.strokeWidth / 2 }, + 'left', + 'top', + this.originX, + this.originY + ); + } + if (typeof options.left === 'undefined') { + this.left = options.fromSVG ? calcDim.left : correctLeftTop.x; + } + if (typeof options.top === 'undefined') { + this.top = options.fromSVG ? calcDim.top : correctLeftTop.y; + } + this.pathOffset = { + x: calcDim.left + this.width / 2, + y: calcDim.top + this.height / 2 + }; + }, + + /** + * Calculate the polygon min and max point from points array, + * returning an object with left, top, width, height to measure the + * polygon size + * @return {Object} object.left X coordinate of the polygon leftmost point + * @return {Object} object.top Y coordinate of the polygon topmost point + * @return {Object} object.width distance between X coordinates of the polygon leftmost and rightmost point + * @return {Object} object.height distance between Y coordinates of the polygon topmost and bottommost point + * @private + */ + _calcDimensions: function() { + + var points = this.points, + minX = min(points, 'x') || 0, + minY = min(points, 'y') || 0, + maxX = max(points, 'x') || 0, + maxY = max(points, 'y') || 0, + width = (maxX - minX), + height = (maxY - minY); + + return { + left: minX, + top: minY, + width: width, + height: height + }; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + points: this.points.concat() + }); + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var points = [], diffX = this.pathOffset.x, diffY = this.pathOffset.y, + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + + for (var i = 0, len = this.points.length; i < len; i++) { + points.push( + toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS), ',', + toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS), ' ' + ); + } + return [ + '<' + this.type + ' ', 'COMMON_PARTS', + 'points="', points.join(''), + '" />\n' + ]; + }, + /* _TO_SVG_END_ */ + + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + commonRender: function(ctx) { + var point, len = this.points.length, + x = this.pathOffset.x, + y = this.pathOffset.y; + + if (!len || isNaN(this.points[len - 1].y)) { + // do not draw if no points or odd points + // NaN comes from parseFloat of a empty string in parser + return false; + } + ctx.beginPath(); + ctx.moveTo(this.points[0].x - x, this.points[0].y - y); + for (var i = 0; i < len; i++) { + point = this.points[i]; + ctx.lineTo(point.x - x, point.y - y); + } + return true; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + if (!this.commonRender(ctx)) { + return; + } + this._renderPaintInOrder(ctx); + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.get('points').length; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Polyline.fromElement}) + * @static + * @memberOf fabric.Polyline + * @see: http://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ + fabric.Polyline.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns fabric.Polyline instance from an SVG element + * @static + * @memberOf fabric.Polyline + * @param {SVGElement} element Element to parser + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Polyline.fromElementGenerator = function(_class) { + return function(element, callback, options) { + if (!element) { + return callback(null); + } + options || (options = { }); + + var points = fabric.parsePointsAttribute(element.getAttribute('points')), + parsedAttributes = fabric.parseAttributes(element, fabric[_class].ATTRIBUTE_NAMES); + parsedAttributes.fromSVG = true; + callback(new fabric[_class](points, extend(parsedAttributes, options))); + }; + }; + + fabric.Polyline.fromElement = fabric.Polyline.fromElementGenerator('Polyline'); + + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polyline instance from an object representation + * @static + * @memberOf fabric.Polyline + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + */ + fabric.Polyline.fromObject = function(object, callback) { + return fabric.Object._fromObject('Polyline', object, callback, 'points'); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.Polygon) { + fabric.warn('fabric.Polygon is already defined'); + return; + } + + /** + * Polygon class + * @class fabric.Polygon + * @extends fabric.Polyline + * @see {@link fabric.Polygon#initialize} for constructor definition + */ + fabric.Polygon = fabric.util.createClass(fabric.Polyline, /** @lends fabric.Polygon.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'polygon', + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + if (!this.commonRender(ctx)) { + return; + } + ctx.closePath(); + this._renderPaintInOrder(ctx); + }, + + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Polygon.fromElement`) + * @static + * @memberOf fabric.Polygon + * @see: http://www.w3.org/TR/SVG/shapes.html#PolygonElement + */ + fabric.Polygon.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(); + + /** + * Returns {@link fabric.Polygon} instance from an SVG element + * @static + * @memberOf fabric.Polygon + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Polygon.fromElement = fabric.Polyline.fromElementGenerator('Polygon'); + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Polygon instance from an object representation + * @static + * @memberOf fabric.Polygon + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + * @return {void} + */ + fabric.Polygon.fromObject = function(object, callback) { + fabric.Object._fromObject('Polygon', object, callback, 'points'); + }; + +})(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, + toFixed = fabric.util.toFixed; + + if (fabric.Path) { + fabric.warn('fabric.Path is already defined'); + return; + } + + /** + * Path class + * @class fabric.Path + * @extends fabric.Object + * @tutorial {@link http://fabricjs.com/fabric-intro-part-1#path_and_pathgroup} + * @see {@link fabric.Path#initialize} for constructor definition + */ + fabric.Path = fabric.util.createClass(fabric.Object, /** @lends fabric.Path.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'path', + + /** + * Array of path points + * @type Array + * @default + */ + path: null, + + cacheProperties: fabric.Object.prototype.cacheProperties.concat('path', 'fillRule'), + + stateProperties: fabric.Object.prototype.stateProperties.concat('path'), + + /** + * Constructor + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + * @return {fabric.Path} thisArg + */ + initialize: function(path, options) { + options = options || { }; + this.callSuper('initialize', options); + if (!path) { + path = []; + } + + var fromArray = _toString.call(path) === '[object Array]'; + + this.path = fabric.util.makePathSimpler( + fromArray ? path : fabric.util.parsePath(path) + ); + + if (!this.path) { + return; + } + fabric.Polyline.prototype._setPositionDimensions.call(this, options); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _renderPathCommands: function(ctx) { + var current, // current instruction + subpathStartX = 0, + subpathStartY = 0, + x = 0, // current x + y = 0, // current y + controlX = 0, // current control point x + controlY = 0, // current control point y + l = -this.pathOffset.x, + t = -this.pathOffset.y; + + ctx.beginPath(); + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + ctx.lineTo(x + l, y + t); + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + ctx.moveTo(x + l, y + t); + break; + + case 'C': // bezierCurveTo, absolute + x = current[5]; + y = current[6]; + controlX = current[3]; + controlY = current[4]; + ctx.bezierCurveTo( + current[1] + l, + current[2] + t, + controlX + l, + controlY + t, + x + l, + y + t + ); + break; + + case 'Q': // quadraticCurveTo, absolute + ctx.quadraticCurveTo( + current[1] + l, + current[2] + t, + current[3] + l, + current[4] + t + ); + x = current[3]; + y = current[4]; + controlX = current[1]; + controlY = current[2]; + break; + + case 'z': + case 'Z': + x = subpathStartX; + y = subpathStartY; + ctx.closePath(); + break; + } + } + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx context to render path on + */ + _render: function(ctx) { + this._renderPathCommands(ctx); + this._renderPaintInOrder(ctx); + }, + + /** + * Returns string representation of an instance + * @return {String} string representation of an instance + */ + toString: function() { + return '#'; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return extend(this.callSuper('toObject', propertiesToInclude), { + path: this.path.map(function(item) { return item.slice(); }), + }); + }, + + /** + * Returns dataless object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var o = this.toObject(['sourcePath'].concat(propertiesToInclude)); + if (o.sourcePath) { + delete o.path; + } + return o; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var path = fabric.util.joinPath(this.path); + return [ + '\n' + ]; + }, + + _getOffsetTransform: function() { + var digits = fabric.Object.NUM_FRACTION_DIGITS; + return ' translate(' + toFixed(-this.pathOffset.x, digits) + ', ' + + toFixed(-this.pathOffset.y, digits) + ')'; + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + var additionalTransform = this._getOffsetTransform(); + return '\t' + this._createBaseClipPathSVGMarkup( + this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform } + ); + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + var additionalTransform = this._getOffsetTransform(); + return this._createBaseSVGMarkup(this._toSVG(), { reviver: reviver, additionalTransform: additionalTransform }); + }, + /* _TO_SVG_END_ */ + + /** + * Returns number representation of an instance complexity + * @return {Number} complexity of this instance + */ + complexity: function() { + return this.path.length; + }, + + /** + * @private + */ + _calcDimensions: function() { + + var aX = [], + aY = [], + current, // current instruction + subpathStartX = 0, + subpathStartY = 0, + x = 0, // current x + y = 0, // current y + bounds; + + for (var i = 0, len = this.path.length; i < len; ++i) { + + current = this.path[i]; + + switch (current[0]) { // first letter + + case 'L': // lineto, absolute + x = current[1]; + y = current[2]; + bounds = []; + break; + + case 'M': // moveTo, absolute + x = current[1]; + y = current[2]; + subpathStartX = x; + subpathStartY = y; + bounds = []; + break; + + case 'C': // bezierCurveTo, absolute + bounds = fabric.util.getBoundsOfCurve(x, y, + current[1], + current[2], + current[3], + current[4], + current[5], + current[6] + ); + x = current[5]; + y = current[6]; + break; + + case 'Q': // quadraticCurveTo, absolute + bounds = fabric.util.getBoundsOfCurve(x, y, + current[1], + current[2], + current[1], + current[2], + current[3], + current[4] + ); + x = current[3]; + y = current[4]; + break; + + case 'z': + case 'Z': + x = subpathStartX; + y = subpathStartY; + break; + } + bounds.forEach(function (point) { + aX.push(point.x); + aY.push(point.y); + }); + aX.push(x); + aY.push(y); + } + + var minX = min(aX) || 0, + minY = min(aY) || 0, + maxX = max(aX) || 0, + maxY = max(aY) || 0, + deltaX = maxX - minX, + deltaY = maxY - minY; + + return { + left: minX, + top: minY, + width: deltaX, + height: deltaY + }; + } + }); + + /** + * Creates an instance of fabric.Path from an object + * @static + * @memberOf fabric.Path + * @param {Object} object + * @param {Function} [callback] Callback to invoke when an fabric.Path instance is created + */ + fabric.Path.fromObject = function(object, callback) { + if (typeof object.sourcePath === 'string') { + var pathUrl = object.sourcePath; + fabric.loadSVGFromURL(pathUrl, function (elements) { + var path = elements[0]; + path.setOptions(object); + callback && callback(path); + }); + } + else { + fabric.Object._fromObject('Path', object, callback, 'path'); + } + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by `fabric.Path.fromElement`) + * @static + * @memberOf fabric.Path + * @see http://www.w3.org/TR/SVG/paths.html#PathElement + */ + fabric.Path.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat(['d']); + + /** + * Creates an instance of fabric.Path from an SVG element + * @static + * @memberOf fabric.Path + * @param {SVGElement} element to parse + * @param {Function} callback Callback to invoke when an fabric.Path instance is created + * @param {Object} [options] Options object + * @param {Function} [callback] Options callback invoked after parsing is finished + */ + fabric.Path.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Path.ATTRIBUTE_NAMES); + parsedAttributes.fromSVG = true; + callback(new fabric.Path(parsedAttributes.d, extend(parsedAttributes, options))); + }; + /* _FROM_SVG_END_ */ + +})(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; + + if (fabric.Group) { + return; + } + + /** + * Group class + * @class fabric.Group + * @extends fabric.Object + * @mixes fabric.Collection + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} + * @see {@link fabric.Group#initialize} for constructor definition + */ + fabric.Group = fabric.util.createClass(fabric.Object, fabric.Collection, /** @lends fabric.Group.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'group', + + /** + * Width of stroke + * @type Number + * @default + */ + strokeWidth: 0, + + /** + * Indicates if click, mouseover, mouseout events & hoverCursor should also check for subtargets + * @type Boolean + * @default + */ + subTargetCheck: false, + + /** + * Groups are container, do not render anything on theyr own, ence no cache properties + * @type Array + * @default + */ + cacheProperties: [], + + /** + * setOnGroup is a method used for TextBox that is no more used since 2.0.0 The behavior is still + * available setting this boolean to true. + * @type Boolean + * @since 2.0.0 + * @default + */ + useSetOnGroup: false, + + /** + * Constructor + * @param {Object} objects Group objects + * @param {Object} [options] Options object + * @param {Boolean} [isAlreadyGrouped] if true, objects have been grouped already. + * @return {Object} thisArg + */ + initialize: function(objects, options, isAlreadyGrouped) { + options = options || {}; + this._objects = []; + // if objects enclosed in a group have been grouped already, + // we cannot change properties of objects. + // Thus we need to set options to group without objects, + isAlreadyGrouped && this.callSuper('initialize', options); + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + if (!isAlreadyGrouped) { + var center = options && options.centerPoint; + // we want to set origins before calculating the bounding box. + // so that the topleft can be set with that in mind. + // if specific top and left are passed, are overwritten later + // with the callSuper('initialize', options) + if (options.originX !== undefined) { + this.originX = options.originX; + } + if (options.originY !== undefined) { + this.originY = options.originY; + } + // if coming from svg i do not want to calc bounds. + // i assume width and height are passed along options + center || this._calcBounds(); + this._updateObjectsCoords(center); + delete options.centerPoint; + this.callSuper('initialize', options); + } + else { + this._updateObjectsACoords(); + } + + this.setCoords(); + }, + + /** + * @private + */ + _updateObjectsACoords: function() { + var skipControls = true; + for (var i = this._objects.length; i--; ){ + this._objects[i].setCoords(skipControls); + } + }, + + /** + * @private + * @param {Boolean} [skipCoordsChange] if true, coordinates of objects enclosed in a group do not change + */ + _updateObjectsCoords: function(center) { + var center = center || this.getCenterPoint(); + for (var i = this._objects.length; i--; ){ + this._updateObjectCoords(this._objects[i], center); + } + }, + + /** + * @private + * @param {Object} object + * @param {fabric.Point} center, current center of group. + */ + _updateObjectCoords: function(object, center) { + var objectLeft = object.left, + objectTop = object.top, + skipControls = true; + + object.set({ + left: objectLeft - center.x, + top: objectTop - center.y + }); + object.group = this; + object.setCoords(skipControls); + }, + + /** + * Returns string represenation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Adds an object to a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + addWithUpdate: function(object) { + var nested = !!this.group; + this._restoreObjectsState(); + fabric.util.resetObjectTransform(this); + if (object) { + if (nested) { + // if this group is inside another group, we need to pre transform the object + fabric.util.removeTransformFromObject(object, this.group.calcTransformMatrix()); + } + this._objects.push(object); + object.group = this; + object._set('canvas', this.canvas); + } + this._calcBounds(); + this._updateObjectsCoords(); + this.dirty = true; + if (nested) { + this.group.addWithUpdate(); + } + else { + this.setCoords(); + } + return this; + }, + + /** + * Removes an object from a group; Then recalculates group's dimension, position. + * @param {Object} object + * @return {fabric.Group} thisArg + * @chainable + */ + removeWithUpdate: function(object) { + this._restoreObjectsState(); + fabric.util.resetObjectTransform(this); + + this.remove(object); + this._calcBounds(); + this._updateObjectsCoords(); + this.setCoords(); + this.dirty = true; + return this; + }, + + /** + * @private + */ + _onObjectAdded: function(object) { + this.dirty = true; + object.group = this; + object._set('canvas', this.canvas); + }, + + /** + * @private + */ + _onObjectRemoved: function(object) { + this.dirty = true; + delete object.group; + }, + + /** + * @private + */ + _set: function(key, value) { + var i = this._objects.length; + if (this.useSetOnGroup) { + while (i--) { + this._objects[i].setOnGroup(key, value); + } + } + if (key === 'canvas') { + while (i--) { + this._objects[i]._set(key, value); + } + } + fabric.Object.prototype._set.call(this, key, value); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + var _includeDefaultValues = this.includeDefaultValues; + var objsToObject = this._objects + .filter(function (obj) { + return !obj.excludeFromExport; + }) + .map(function (obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + var obj = fabric.Object.prototype.toObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + /** + * Returns object representation of an instance, in dataless mode. + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toDatalessObject: function(propertiesToInclude) { + var objsToObject, sourcePath = this.sourcePath; + if (sourcePath) { + objsToObject = sourcePath; + } + else { + var _includeDefaultValues = this.includeDefaultValues; + objsToObject = this._objects.map(function(obj) { + var originalDefaults = obj.includeDefaultValues; + obj.includeDefaultValues = _includeDefaultValues; + var _obj = obj.toDatalessObject(propertiesToInclude); + obj.includeDefaultValues = originalDefaults; + return _obj; + }); + } + var obj = fabric.Object.prototype.toDatalessObject.call(this, propertiesToInclude); + obj.objects = objsToObject; + return obj; + }, + + /** + * Renders instance on a given context + * @param {CanvasRenderingContext2D} ctx context to render instance on + */ + render: function(ctx) { + this._transformDone = true; + this.callSuper('render', ctx); + this._transformDone = false; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group is already cached. + * @return {Boolean} + */ + shouldCache: function() { + var ownCache = fabric.Object.prototype.shouldCache.call(this); + if (ownCache) { + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].willDrawShadow()) { + this.ownCaching = false; + return false; + } + } + } + return ownCache; + }, + + /** + * Check if this object or a child object will cast a shadow + * @return {Boolean} + */ + willDrawShadow: function() { + if (fabric.Object.prototype.willDrawShadow.call(this)) { + return true; + } + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].willDrawShadow()) { + return true; + } + } + return false; + }, + + /** + * Check if this group or its parent group are caching, recursively up + * @return {Boolean} + */ + isOnACache: function() { + return this.ownCaching || (this.group && this.group.isOnACache()); + }, + + /** + * Execute the drawing operation for an object on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawObject: function(ctx) { + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i].render(ctx); + } + this._drawClipPath(ctx); + }, + + /** + * Check if cache is dirty + */ + isCacheDirty: function(skipCanvas) { + if (this.callSuper('isCacheDirty', skipCanvas)) { + return true; + } + if (!this.statefullCache) { + return false; + } + for (var i = 0, len = this._objects.length; i < len; i++) { + if (this._objects[i].isCacheDirty(true)) { + if (this._cacheCanvas) { + // if this group has not a cache canvas there is nothing to clean + var x = this.cacheWidth / this.zoomX, y = this.cacheHeight / this.zoomY; + this._cacheContext.clearRect(-x / 2, -y / 2, x, y); + } + return true; + } + } + return false; + }, + + /** + * Restores original state of each of group objects (original state is that which was before group was created). + * if the nested boolean is true, the original state will be restored just for the + * first group and not for all the group chain + * @private + * @param {Boolean} nested tell the function to restore object state up to the parent group and not more + * @return {fabric.Group} thisArg + * @chainable + */ + _restoreObjectsState: function() { + var groupMatrix = this.calcOwnMatrix(); + this._objects.forEach(function(object) { + // instead of using _this = this; + fabric.util.addTransformToObject(object, groupMatrix); + delete object.group; + object.setCoords(); + }); + return this; + }, + + /** + * Realises the transform from this group onto the supplied object + * i.e. it tells you what would happen if the supplied object was in + * the group, and then the group was destroyed. It mutates the supplied + * object. + * Warning: this method is not useful anymore, it has been kept to no break the api. + * is not used in the fabricJS codebase + * this method will be reduced to using the utility. + * @private + * @deprecated + * @param {fabric.Object} object + * @param {Array} parentMatrix parent transformation + * @return {fabric.Object} transformedObject + */ + realizeTransform: function(object, parentMatrix) { + fabric.util.addTransformToObject(object, parentMatrix); + return object; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + destroy: function() { + // when group is destroyed objects needs to get a repaint to be eventually + // displayed on canvas. + this._objects.forEach(function(object) { + object.set('dirty', true); + }); + return this._restoreObjectsState(); + }, + + /** + * make a group an active selection, remove the group from canvas + * the group has to be on canvas for this to work. + * @return {fabric.ActiveSelection} thisArg + * @chainable + */ + toActiveSelection: function() { + if (!this.canvas) { + return; + } + var objects = this._objects, canvas = this.canvas; + this._objects = []; + var options = this.toObject(); + delete options.objects; + var activeSelection = new fabric.ActiveSelection([]); + activeSelection.set(options); + activeSelection.type = 'activeSelection'; + canvas.remove(this); + objects.forEach(function(object) { + object.group = activeSelection; + object.dirty = true; + canvas.add(object); + }); + activeSelection.canvas = canvas; + activeSelection._objects = objects; + canvas._activeObject = activeSelection; + activeSelection.setCoords(); + return activeSelection; + }, + + /** + * Destroys a group (restoring state of its objects) + * @return {fabric.Group} thisArg + * @chainable + */ + ungroupOnCanvas: function() { + return this._restoreObjectsState(); + }, + + /** + * Sets coordinates of all objects inside group + * @return {fabric.Group} thisArg + * @chainable + */ + setObjectsCoords: function() { + var skipControls = true; + this.forEachObject(function(object) { + object.setCoords(skipControls); + }); + return this; + }, + + /** + * @private + */ + _calcBounds: function(onlyWidthHeight) { + var aX = [], + aY = [], + o, prop, coords, + props = ['tr', 'br', 'bl', 'tl'], + i = 0, iLen = this._objects.length, + j, jLen = props.length; + + for ( ; i < iLen; ++i) { + o = this._objects[i]; + coords = o.calcACoords(); + for (j = 0; j < jLen; j++) { + prop = props[j]; + aX.push(coords[prop].x); + aY.push(coords[prop].y); + } + o.aCoords = coords; + } + + this._getBounds(aX, aY, onlyWidthHeight); + }, + + /** + * @private + */ + _getBounds: function(aX, aY, onlyWidthHeight) { + var minXY = new fabric.Point(min(aX), min(aY)), + maxXY = new fabric.Point(max(aX), max(aY)), + top = minXY.y || 0, left = minXY.x || 0, + width = (maxXY.x - minXY.x) || 0, + height = (maxXY.y - minXY.y) || 0; + this.width = width; + this.height = height; + if (!onlyWidthHeight) { + // the bounding box always finds the topleft most corner. + // whatever is the group origin, we set up here the left/top position. + this.setPositionByOrigin({ x: left, y: top }, 'left', 'top'); + } + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG: function(reviver) { + var svgString = ['\n']; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t\t', this._objects[i].toSVG(reviver)); + } + svgString.push('\n'); + return svgString; + }, + + /** + * Returns styles-string for svg-export, specific version for group + * @return {String} + */ + getSvgStyles: function() { + var opacity = typeof this.opacity !== 'undefined' && this.opacity !== 1 ? + 'opacity: ' + this.opacity + ';' : '', + visibility = this.visible ? '' : ' visibility: hidden;'; + return [ + opacity, + this.getSvgFilter(), + visibility + ].join(''); + }, + + /** + * Returns svg clipPath representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toClipPathSVG: function(reviver) { + var svgString = []; + + for (var i = 0, len = this._objects.length; i < len; i++) { + svgString.push('\t', this._objects[i].toClipPathSVG(reviver)); + } + + return this._createBaseClipPathSVGMarkup(svgString, { reviver: reviver }); + }, + /* _TO_SVG_END_ */ + }); + + /** + * Returns {@link fabric.Group} instance from an object representation + * @static + * @memberOf fabric.Group + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an group instance is created + */ + fabric.Group.fromObject = function(object, callback) { + var objects = object.objects, + options = fabric.util.object.clone(object, true); + delete options.objects; + if (typeof objects === 'string') { + // it has to be an url or something went wrong. + fabric.loadSVGFromURL(objects, function (elements) { + var group = fabric.util.groupSVGElements(elements, object, objects); + group.set(options); + callback && callback(group); + }); + return; + } + fabric.util.enlivenObjects(objects, function(enlivenedObjects) { + fabric.util.enlivenObjects([object.clipPath], function(enlivedClipPath) { + var options = fabric.util.object.clone(object, true); + options.clipPath = enlivedClipPath[0]; + delete options.objects; + callback && callback(new fabric.Group(enlivenedObjects, options, true)); + }); + }); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }); + + if (fabric.ActiveSelection) { + return; + } + + /** + * Group class + * @class fabric.ActiveSelection + * @extends fabric.Group + * @tutorial {@link http://fabricjs.com/fabric-intro-part-3#groups} + * @see {@link fabric.ActiveSelection#initialize} for constructor definition + */ + fabric.ActiveSelection = fabric.util.createClass(fabric.Group, /** @lends fabric.ActiveSelection.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'activeSelection', + + /** + * Constructor + * @param {Object} objects ActiveSelection objects + * @param {Object} [options] Options object + * @return {Object} thisArg + */ + initialize: function(objects, options) { + options = options || {}; + this._objects = objects || []; + for (var i = this._objects.length; i--; ) { + this._objects[i].group = this; + } + + if (options.originX) { + this.originX = options.originX; + } + if (options.originY) { + this.originY = options.originY; + } + this._calcBounds(); + this._updateObjectsCoords(); + fabric.Object.prototype.initialize.call(this, options); + this.setCoords(); + }, + + /** + * Change te activeSelection to a normal group, + * High level function that automatically adds it to canvas as + * active object. no events fired. + * @since 2.0.0 + * @return {fabric.Group} + */ + toGroup: function() { + var objects = this._objects.concat(); + this._objects = []; + var options = fabric.Object.prototype.toObject.call(this); + var newGroup = new fabric.Group([]); + delete options.type; + newGroup.set(options); + objects.forEach(function(object) { + object.canvas.remove(object); + object.group = newGroup; + }); + newGroup._objects = objects; + if (!this.canvas) { + return newGroup; + } + var canvas = this.canvas; + canvas.add(newGroup); + canvas._activeObject = newGroup; + newGroup.setCoords(); + return newGroup; + }, + + /** + * If returns true, deselection is cancelled. + * @since 2.0.0 + * @return {Boolean} [cancel] + */ + onDeselect: function() { + this.destroy(); + return false; + }, + + /** + * Returns string representation of a group + * @return {String} + */ + toString: function() { + return '#'; + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * objectCaching is a global flag, wins over everything + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * @return {Boolean} + */ + shouldCache: function() { + return false; + }, + + /** + * Check if this group or its parent group are caching, recursively up + * @return {Boolean} + */ + isOnACache: function() { + return false; + }, + + /** + * Renders controls and borders for the object + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [styleOverride] properties to override the object style + * @param {Object} [childrenOverride] properties to override the children overrides + */ + _renderControls: function(ctx, styleOverride, childrenOverride) { + ctx.save(); + ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1; + this.callSuper('_renderControls', ctx, styleOverride); + childrenOverride = childrenOverride || { }; + if (typeof childrenOverride.hasControls === 'undefined') { + childrenOverride.hasControls = false; + } + childrenOverride.forActiveSelection = true; + for (var i = 0, len = this._objects.length; i < len; i++) { + this._objects[i]._renderControls(ctx, childrenOverride); + } + ctx.restore(); + }, + }); + + /** + * Returns {@link fabric.ActiveSelection} instance from an object representation + * @static + * @memberOf fabric.ActiveSelection + * @param {Object} object Object to create a group from + * @param {Function} [callback] Callback to invoke when an ActiveSelection instance is created + */ + fabric.ActiveSelection.fromObject = function(object, callback) { + fabric.util.enlivenObjects(object.objects, function(enlivenedObjects) { + delete object.objects; + callback && callback(new fabric.ActiveSelection(enlivenedObjects, object, true)); + }); + }; + +})(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', + + /** + * Width of a stroke. + * For image quality a stroke multiple of 2 gives better results. + * @type Number + * @default + */ + strokeWidth: 0, + + /** + * When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`. + * This allows for relative urls as image src. + * @since 2.7.0 + * @type Boolean + * @default + */ + srcFromAttribute: false, + + /** + * private + * contains last value of scaleX to detect + * if the Image got resized after the last Render + * @type Number + */ + _lastScaleX: 1, + + /** + * private + * contains last value of scaleY to detect + * if the Image got resized after the last Render + * @type Number + */ + _lastScaleY: 1, + + /** + * private + * contains last value of scaling applied by the apply filter chain + * @type Number + */ + _filterScalingX: 1, + + /** + * private + * contains last value of scaling applied by the apply filter chain + * @type Number + */ + _filterScalingY: 1, + + /** + * minimum scale factor under which any resizeFilter is triggered to resize the image + * 0 will disable the automatic resize. 1 will trigger automatically always. + * number bigger than 1 are not implemented yet. + * @type Number + */ + minimumScaleTrigger: 0.5, + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat('cropX', 'cropY'), + + /** + * List of properties to consider when checking if cache needs refresh + * Those properties are checked by statefullCache ON ( or lazy mode if we want ) or from single + * calls to Object.set(key, value). If the key is in this list, the object is marked as dirty + * and refreshed at the next render + * @type Array + */ + cacheProperties: fabric.Object.prototype.cacheProperties.concat('cropX', 'cropY'), + + /** + * key used to retrieve the texture representing this image + * @since 2.0.0 + * @type String + * @default + */ + cacheKey: '', + + /** + * Image crop in pixels from original image size. + * @since 2.0.0 + * @type Number + * @default + */ + cropX: 0, + + /** + * Image crop in pixels from original image size. + * @since 2.0.0 + * @type Number + * @default + */ + cropY: 0, + + /** + * Indicates whether this canvas will use image smoothing when painting this image. + * Also influence if the cacheCanvas for this image uses imageSmoothing + * @since 4.0.0-beta.11 + * @type Boolean + * @default + */ + imageSmoothing: true, + + /** + * Constructor + * Image can be initialized with any canvas drawable or a string. + * The string should be a url and will be loaded as an image. + * Canvas and Image element work out of the box, while videos require extra code to work. + * Please check video element events for seeking. + * @param {HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | String} element Image element + * @param {Object} [options] Options object + * @param {function} [callback] callback function to call after eventual filters applied. + * @return {fabric.Image} thisArg + */ + initialize: function(element, options) { + options || (options = { }); + this.filters = []; + this.cacheKey = 'texture' + fabric.Object.__uid++; + this.callSuper('initialize', options); + this._initElement(element, options); + }, + + /** + * Returns image element which this instance if based on + * @return {HTMLImageElement} Image element + */ + getElement: function() { + return this._element || {}; + }, + + /** + * Sets image element for this instance to a specified one. + * If filters defined they are applied to new image. + * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. + * @param {HTMLImageElement} element + * @param {Object} [options] Options object + * @return {fabric.Image} thisArg + * @chainable + */ + setElement: function(element, options) { + this.removeTexture(this.cacheKey); + this.removeTexture(this.cacheKey + '_filtered'); + this._element = element; + this._originalElement = element; + this._initConfig(options); + if (this.filters.length !== 0) { + this.applyFilters(); + } + // resizeFilters work on the already filtered copy. + // we need to apply resizeFilters AFTER normal filters. + // applyResizeFilters is run more often than normal filters + // and is triggered by user interactions rather than dev code + if (this.resizeFilter) { + this.applyResizeFilters(); + } + return this; + }, + + /** + * Delete a single texture if in webgl mode + */ + removeTexture: function(key) { + var backend = fabric.filterBackend; + if (backend && backend.evictCachesForKey) { + backend.evictCachesForKey(key); + } + }, + + /** + * Delete textures, reference to elements and eventually JSDOM cleanup + */ + dispose: function() { + this.removeTexture(this.cacheKey); + this.removeTexture(this.cacheKey + '_filtered'); + this._cacheContext = undefined; + ['_originalElement', '_element', '_filteredEl', '_cacheCanvas'].forEach((function(element) { + fabric.util.cleanUpJsdomNode(this[element]); + this[element] = undefined; + }).bind(this)); + }, + + /** + * Get the crossOrigin value (of the corresponding image element) + */ + getCrossOrigin: function() { + return this._originalElement && (this._originalElement.crossOrigin || null); + }, + + /** + * Returns original size of an image + * @return {Object} Object with "width" and "height" properties + */ + getOriginalSize: function() { + var element = this.getElement(); + return { + width: element.naturalWidth || element.width, + height: element.naturalHeight || element.height + }; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _stroke: function(ctx) { + if (!this.stroke || this.strokeWidth === 0) { + return; + } + var w = this.width / 2, h = this.height / 2; + ctx.beginPath(); + ctx.moveTo(-w, -h); + ctx.lineTo(w, -h); + ctx.lineTo(w, h); + ctx.lineTo(-w, h); + ctx.lineTo(-w, -h); + ctx.closePath(); + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var filters = []; + + this.filters.forEach(function(filterObj) { + if (filterObj) { + filters.push(filterObj.toObject()); + } + }); + var object = extend( + this.callSuper( + 'toObject', + ['cropX', 'cropY'].concat(propertiesToInclude) + ), { + src: this.getSrc(), + crossOrigin: this.getCrossOrigin(), + filters: filters, + }); + if (this.resizeFilter) { + object.resizeFilter = this.resizeFilter.toObject(); + } + return object; + }, + + /** + * Returns true if an image has crop applied, inspecting values of cropX,cropY,width,height. + * @return {Boolean} + */ + hasCrop: function() { + return this.cropX || this.cropY || this.width < this._element.width || this.height < this._element.height; + }, + + /* _TO_SVG_START_ */ + /** + * Returns svg representation of an instance + * @return {Array} an array of strings with the specific svg representation + * of the instance + */ + _toSVG: function() { + var svgString = [], imageMarkup = [], strokeSvg, element = this._element, + x = -this.width / 2, y = -this.height / 2, clipPath = '', imageRendering = ''; + if (!element) { + return []; + } + if (this.hasCrop()) { + var clipPathId = fabric.Object.__uid++; + svgString.push( + '\n', + '\t\n', + '\n' + ); + clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" '; + } + if (!this.imageSmoothing) { + imageRendering = '" image-rendering="optimizeSpeed'; + } + imageMarkup.push('\t\n'); + + if (this.stroke || this.strokeDashArray) { + var origFill = this.fill; + this.fill = null; + strokeSvg = [ + '\t\n' + ]; + this.fill = origFill; + } + if (this.paintFirst !== 'fill') { + svgString = svgString.concat(strokeSvg, imageMarkup); + } + else { + svgString = svgString.concat(imageMarkup, strokeSvg); + } + return svgString; + }, + /* _TO_SVG_END_ */ + + /** + * Returns source of an image + * @param {Boolean} filtered indicates if the src is needed for svg + * @return {String} Source of an image + */ + getSrc: function(filtered) { + var element = filtered ? this._element : this._originalElement; + if (element) { + if (element.toDataURL) { + return element.toDataURL(); + } + + if (this.srcFromAttribute) { + return element.getAttribute('src'); + } + else { + return element.src; + } + } + else { + return this.src || ''; + } + }, + + /** + * Sets source of an image + * @param {String} src Source string (URL) + * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied) + * @param {Object} [options] Options object + * @param {String} [options.crossOrigin] crossOrigin value (one of "", "anonymous", "use-credentials") + * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes + * @return {fabric.Image} thisArg + * @chainable + */ + setSrc: function(src, callback, options) { + fabric.util.loadImage(src, function(img, isError) { + this.setElement(img, options); + this._setWidthHeight(); + callback && callback(this, isError); + }, this, options && options.crossOrigin); + return this; + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of an instance + */ + toString: function() { + return '#'; + }, + + applyResizeFilters: function() { + var filter = this.resizeFilter, + minimumScale = this.minimumScaleTrigger, + objectScale = this.getTotalObjectScaling(), + scaleX = objectScale.scaleX, + scaleY = objectScale.scaleY, + elementToFilter = this._filteredEl || this._originalElement; + if (this.group) { + this.set('dirty', true); + } + if (!filter || (scaleX > minimumScale && scaleY > minimumScale)) { + this._element = elementToFilter; + this._filterScalingX = 1; + this._filterScalingY = 1; + this._lastScaleX = scaleX; + this._lastScaleY = scaleY; + return; + } + if (!fabric.filterBackend) { + fabric.filterBackend = fabric.initFilterBackend(); + } + var canvasEl = fabric.util.createCanvasElement(), + cacheKey = this._filteredEl ? (this.cacheKey + '_filtered') : this.cacheKey, + sourceWidth = elementToFilter.width, sourceHeight = elementToFilter.height; + canvasEl.width = sourceWidth; + canvasEl.height = sourceHeight; + this._element = canvasEl; + this._lastScaleX = filter.scaleX = scaleX; + this._lastScaleY = filter.scaleY = scaleY; + fabric.filterBackend.applyFilters( + [filter], elementToFilter, sourceWidth, sourceHeight, this._element, cacheKey); + this._filterScalingX = canvasEl.width / this._originalElement.width; + this._filterScalingY = canvasEl.height / this._originalElement.height; + }, + + /** + * Applies filters assigned to this image (from "filters" array) or from filter param + * @method applyFilters + * @param {Array} filters to be applied + * @param {Boolean} forResizing specify if the filter operation is a resize operation + * @return {thisArg} return the fabric.Image object + * @chainable + */ + applyFilters: function(filters) { + + filters = filters || this.filters || []; + filters = filters.filter(function(filter) { return filter && !filter.isNeutralState(); }); + this.set('dirty', true); + + // needs to clear out or WEBGL will not resize correctly + this.removeTexture(this.cacheKey + '_filtered'); + + if (filters.length === 0) { + this._element = this._originalElement; + this._filteredEl = null; + this._filterScalingX = 1; + this._filterScalingY = 1; + return this; + } + + var imgElement = this._originalElement, + sourceWidth = imgElement.naturalWidth || imgElement.width, + sourceHeight = imgElement.naturalHeight || imgElement.height; + + if (this._element === this._originalElement) { + // if the element is the same we need to create a new element + var canvasEl = fabric.util.createCanvasElement(); + canvasEl.width = sourceWidth; + canvasEl.height = sourceHeight; + this._element = canvasEl; + this._filteredEl = canvasEl; + } + else { + // clear the existing element to get new filter data + // also dereference the eventual resized _element + this._element = this._filteredEl; + this._filteredEl.getContext('2d').clearRect(0, 0, sourceWidth, sourceHeight); + // we also need to resize again at next renderAll, so remove saved _lastScaleX/Y + this._lastScaleX = 1; + this._lastScaleY = 1; + } + if (!fabric.filterBackend) { + fabric.filterBackend = fabric.initFilterBackend(); + } + fabric.filterBackend.applyFilters( + filters, this._originalElement, sourceWidth, sourceHeight, this._element, this.cacheKey); + if (this._originalElement.width !== this._element.width || + this._originalElement.height !== this._element.height) { + this._filterScalingX = this._element.width / this._originalElement.width; + this._filterScalingY = this._element.height / this._originalElement.height; + } + return this; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + fabric.util.setImageSmoothing(ctx, this.imageSmoothing); + if (this.isMoving !== true && this.resizeFilter && this._needsResize()) { + this.applyResizeFilters(); + } + this._stroke(ctx); + this._renderPaintInOrder(ctx); + }, + + /** + * Paint the cached copy of the object on the target context. + * it will set the imageSmoothing for the draw operation + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + drawCacheOnCanvas: function(ctx) { + fabric.util.setImageSmoothing(ctx, this.imageSmoothing); + fabric.Object.prototype.drawCacheOnCanvas.call(this, ctx); + }, + + /** + * Decide if the object should cache or not. Create its own cache level + * needsItsOwnCache should be used when the object drawing method requires + * a cache step. None of the fabric classes requires it. + * Generally you do not cache objects in groups because the group outside is cached. + * This is the special image version where we would like to avoid caching where possible. + * Essentially images do not benefit from caching. They may require caching, and in that + * case we do it. Also caching an image usually ends in a loss of details. + * A full performance audit should be done. + * @return {Boolean} + */ + shouldCache: function() { + return this.needsItsOwnCache(); + }, + + _renderFill: function(ctx) { + var elementToDraw = this._element; + if (!elementToDraw) { + return; + } + var scaleX = this._filterScalingX, scaleY = this._filterScalingY, + w = this.width, h = this.height, min = Math.min, max = Math.max, + // crop values cannot be lesser than 0. + cropX = max(this.cropX, 0), cropY = max(this.cropY, 0), + elWidth = elementToDraw.naturalWidth || elementToDraw.width, + elHeight = elementToDraw.naturalHeight || elementToDraw.height, + sX = cropX * scaleX, + sY = cropY * scaleY, + // the width height cannot exceed element width/height, starting from the crop offset. + sW = min(w * scaleX, elWidth - sX), + sH = min(h * scaleY, elHeight - sY), + x = -w / 2, y = -h / 2, + maxDestW = min(w, elWidth / scaleX - cropX), + maxDestH = min(h, elHeight / scaleY - cropY); + + elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, maxDestW, maxDestH); + }, + + /** + * needed to check if image needs resize + * @private + */ + _needsResize: function() { + var scale = this.getTotalObjectScaling(); + return (scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY); + }, + + /** + * @private + */ + _resetWidthHeight: function() { + this.set(this.getOriginalSize()); + }, + + /** + * The Image class's initialization method. This method is automatically + * called by the constructor. + * @private + * @param {HTMLImageElement|String} element The element representing the image + * @param {Object} [options] Options object + */ + _initElement: function(element, options) { + this.setElement(fabric.util.getById(element), options); + fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); + }, + + /** + * @private + * @param {Object} [options] Options object + */ + _initConfig: function(options) { + options || (options = { }); + this.setOptions(options); + this._setWidthHeight(options); + }, + + /** + * @private + * @param {Array} filters to be initialized + * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created + */ + _initFilters: function(filters, callback) { + if (filters && filters.length) { + fabric.util.enlivenObjects(filters, function(enlivenedObjects) { + callback && callback(enlivenedObjects); + }, 'fabric.Image.filters'); + } + else { + callback && callback(); + } + }, + + /** + * @private + * Set the width and the height of the image object, using the element or the + * options. + * @param {Object} [options] Object with width/height properties + */ + _setWidthHeight: function(options) { + options || (options = { }); + var el = this.getElement(); + this.width = options.width || el.naturalWidth || el.width || 0; + this.height = options.height || el.naturalHeight || el.height || 0; + }, + + /** + * Calculate offset for center and scale factor for the image in order to respect + * the preserveAspectRatio attribute + * @private + * @return {Object} + */ + parsePreserveAspectRatioAttribute: function() { + var pAR = fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio || ''), + rWidth = this._element.width, rHeight = this._element.height, + scaleX = 1, scaleY = 1, offsetLeft = 0, offsetTop = 0, cropX = 0, cropY = 0, + offset, pWidth = this.width, pHeight = this.height, parsedAttributes = { width: pWidth, height: pHeight }; + if (pAR && (pAR.alignX !== 'none' || pAR.alignY !== 'none')) { + if (pAR.meetOrSlice === 'meet') { + scaleX = scaleY = fabric.util.findScaleToFit(this._element, parsedAttributes); + offset = (pWidth - rWidth * scaleX) / 2; + if (pAR.alignX === 'Min') { + offsetLeft = -offset; + } + if (pAR.alignX === 'Max') { + offsetLeft = offset; + } + offset = (pHeight - rHeight * scaleY) / 2; + if (pAR.alignY === 'Min') { + offsetTop = -offset; + } + if (pAR.alignY === 'Max') { + offsetTop = offset; + } + } + if (pAR.meetOrSlice === 'slice') { + scaleX = scaleY = fabric.util.findScaleToCover(this._element, parsedAttributes); + offset = rWidth - pWidth / scaleX; + if (pAR.alignX === 'Mid') { + cropX = offset / 2; + } + if (pAR.alignX === 'Max') { + cropX = offset; + } + offset = rHeight - pHeight / scaleY; + if (pAR.alignY === 'Mid') { + cropY = offset / 2; + } + if (pAR.alignY === 'Max') { + cropY = offset; + } + rWidth = pWidth / scaleX; + rHeight = pHeight / scaleY; + } + } + else { + scaleX = pWidth / rWidth; + scaleY = pHeight / rHeight; + } + return { + width: rWidth, + height: rHeight, + scaleX: scaleX, + scaleY: scaleY, + offsetLeft: offsetLeft, + offsetTop: offsetTop, + cropX: cropX, + cropY: cropY + }; + } + }); + + /** + * Default CSS class name for canvas + * @static + * @type String + * @default + */ + fabric.Image.CSS_CANVAS = 'canvas-img'; + + /** + * Alias for getSrc + * @static + */ + fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; + + /** + * Creates an instance of fabric.Image from its object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} callback Callback to invoke when an image instance is created + */ + fabric.Image.fromObject = function(_object, callback) { + var object = fabric.util.object.clone(_object); + fabric.util.loadImage(object.src, function(img, isError) { + if (isError) { + callback && callback(null, true); + return; + } + fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) { + object.filters = filters || []; + fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) { + object.resizeFilter = resizeFilters[0]; + fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) { + object.clipPath = enlivedProps[0]; + var image = new fabric.Image(img, object); + callback(image, false); + }); + }); + }); + }, null, object.crossOrigin); + }; + + /** + * Creates an instance of fabric.Image from an URL string + * @static + * @param {String} url URL to create an image from + * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument). Second argument is a boolean indicating if an error occurred or not. + * @param {Object} [imgOptions] Options object + */ + fabric.Image.fromURL = function(url, callback, imgOptions) { + fabric.util.loadImage(url, function(img, isError) { + callback && callback(new fabric.Image(img, imgOptions), isError); + }, null, imgOptions && imgOptions.crossOrigin); + }; + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) + * @static + * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} + */ + fabric.Image.ATTRIBUTE_NAMES = + fabric.SHARED_ATTRIBUTES.concat( + 'x y width height preserveAspectRatio xlink:href crossOrigin image-rendering'.split(' ') + ); + + /** + * Returns {@link fabric.Image} instance from an SVG element + * @static + * @param {SVGElement} element Element to parse + * @param {Object} [options] Options object + * @param {Function} callback Callback to execute when fabric.Image object is created + * @return {fabric.Image} Instance of fabric.Image + */ + fabric.Image.fromElement = function(element, callback, options) { + var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); + fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, + extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); + }; + /* _FROM_SVG_END_ */ + +})(typeof exports !== 'undefined' ? exports : this); + + +fabric.util.object.extend(fabric.Object.prototype, /** @lends fabric.Object.prototype */ { + + /** + * @private + * @return {Number} angle value + */ + _getAngleValueForStraighten: function() { + var angle = this.angle % 360; + if (angle > 0) { + return Math.round((angle - 1) / 90) * 90; + } + return Math.round(angle / 90) * 90; + }, + + /** + * Straightens an object (rotating it from current angle to one of 0, 90, 180, 270, etc. depending on which is closer) + * @return {fabric.Object} thisArg + * @chainable + */ + straighten: function() { + this.rotate(this._getAngleValueForStraighten()); + return this; + }, + + /** + * Same as {@link fabric.Object.prototype.straighten} but with animation + * @param {Object} callbacks Object with callback functions + * @param {Function} [callbacks.onComplete] Invoked on completion + * @param {Function} [callbacks.onChange] Invoked on every step of animation + * @return {fabric.Object} thisArg + * @chainable + */ + fxStraighten: function(callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + fabric.util.animate({ + startValue: this.get('angle'), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.rotate(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + }); + + return this; + } +}); + +fabric.util.object.extend(fabric.StaticCanvas.prototype, /** @lends fabric.StaticCanvas.prototype */ { + + /** + * Straightens object, then rerenders canvas + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + straightenObject: function (object) { + object.straighten(); + this.requestRenderAll(); + return this; + }, + + /** + * Same as {@link fabric.Canvas.prototype.straightenObject}, but animated + * @param {fabric.Object} object Object to straighten + * @return {fabric.Canvas} thisArg + * @chainable + */ + fxStraightenObject: function (object) { + object.fxStraighten({ + onChange: this.requestRenderAllBound + }); + return this; + } +}); + + +(function() { + + 'use strict'; + + /** + * Tests if webgl supports certain precision + * @param {WebGL} Canvas WebGL context to test on + * @param {String} Precision to test can be any of following: 'lowp', 'mediump', 'highp' + * @returns {Boolean} Whether the user's browser WebGL supports given precision. + */ + function testPrecision(gl, precision){ + var fragmentSource = 'precision ' + precision + ' float;\nvoid main(){}'; + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + return false; + } + return true; + } + + /** + * Indicate whether this filtering backend is supported by the user's browser. + * @param {Number} tileSize check if the tileSize is supported + * @returns {Boolean} Whether the user's browser supports WebGL. + */ + fabric.isWebglSupported = function(tileSize) { + if (fabric.isLikelyNode) { + return false; + } + tileSize = tileSize || fabric.WebglFilterBackend.prototype.tileSize; + var canvas = document.createElement('canvas'); + var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + var isSupported = false; + // eslint-disable-next-line + if (gl) { + fabric.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); + isSupported = fabric.maxTextureSize >= tileSize; + var precisions = ['highp', 'mediump', 'lowp']; + for (var i = 0; i < 3; i++){ + if (testPrecision(gl, precisions[i])){ + fabric.webGlPrecision = precisions[i]; + break; + }; + } + } + this.isSupported = isSupported; + return isSupported; + }; + + fabric.WebglFilterBackend = WebglFilterBackend; + + /** + * WebGL filter backend. + */ + function WebglFilterBackend(options) { + if (options && options.tileSize) { + this.tileSize = options.tileSize; + } + this.setupGLContext(this.tileSize, this.tileSize); + this.captureGPUInfo(); + }; + + WebglFilterBackend.prototype = /** @lends fabric.WebglFilterBackend.prototype */ { + + tileSize: 2048, + + /** + * Experimental. This object is a sort of repository of help layers used to avoid + * of recreating them during frequent filtering. If you are previewing a filter with + * a slider you probably do not want to create help layers every filter step. + * in this object there will be appended some canvases, created once, resized sometimes + * cleared never. Clearing is left to the developer. + **/ + resources: { + + }, + + /** + * Setup a WebGL context suitable for filtering, and bind any needed event handlers. + */ + setupGLContext: function(width, height) { + this.dispose(); + this.createWebGLCanvas(width, height); + // eslint-disable-next-line + this.aPosition = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]); + this.chooseFastestCopyGLTo2DMethod(width, height); + }, + + /** + * Pick a method to copy data from GL context to 2d canvas. In some browsers using + * putImageData is faster than drawImage for that specific operation. + */ + chooseFastestCopyGLTo2DMethod: function(width, height) { + var canMeasurePerf = typeof window.performance !== 'undefined', canUseImageData; + try { + new ImageData(1, 1); + canUseImageData = true; + } + catch (e) { + canUseImageData = false; + } + // eslint-disable-next-line no-undef + var canUseArrayBuffer = typeof ArrayBuffer !== 'undefined'; + // eslint-disable-next-line no-undef + var canUseUint8Clamped = typeof Uint8ClampedArray !== 'undefined'; + + if (!(canMeasurePerf && canUseImageData && canUseArrayBuffer && canUseUint8Clamped)) { + return; + } + + var targetCanvas = fabric.util.createCanvasElement(); + // eslint-disable-next-line no-undef + var imageBuffer = new ArrayBuffer(width * height * 4); + if (fabric.forceGLPutImageData) { + this.imageBuffer = imageBuffer; + this.copyGLTo2D = copyGLTo2DPutImageData; + return; + } + var testContext = { + imageBuffer: imageBuffer, + destinationWidth: width, + destinationHeight: height, + targetCanvas: targetCanvas + }; + var startTime, drawImageTime, putImageDataTime; + targetCanvas.width = width; + targetCanvas.height = height; + + startTime = window.performance.now(); + copyGLTo2DDrawImage.call(testContext, this.gl, testContext); + drawImageTime = window.performance.now() - startTime; + + startTime = window.performance.now(); + copyGLTo2DPutImageData.call(testContext, this.gl, testContext); + putImageDataTime = window.performance.now() - startTime; + + if (drawImageTime > putImageDataTime) { + this.imageBuffer = imageBuffer; + this.copyGLTo2D = copyGLTo2DPutImageData; + } + else { + this.copyGLTo2D = copyGLTo2DDrawImage; + } + }, + + /** + * Create a canvas element and associated WebGL context and attaches them as + * class properties to the GLFilterBackend class. + */ + createWebGLCanvas: function(width, height) { + var canvas = fabric.util.createCanvasElement(); + canvas.width = width; + canvas.height = height; + var glOptions = { + alpha: true, + premultipliedAlpha: false, + depth: false, + stencil: false, + antialias: false + }, + gl = canvas.getContext('webgl', glOptions); + if (!gl) { + gl = canvas.getContext('experimental-webgl', glOptions); + } + if (!gl) { + return; + } + gl.clearColor(0, 0, 0, 0); + // this canvas can fire webglcontextlost and webglcontextrestored + this.canvas = canvas; + this.gl = gl; + }, + + /** + * Attempts to apply the requested filters to the source provided, drawing the filtered output + * to the provided target canvas. + * + * @param {Array} filters The filters to apply. + * @param {HTMLImageElement|HTMLCanvasElement} source The source to be filtered. + * @param {Number} width The width of the source input. + * @param {Number} height The height of the source input. + * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. + * @param {String|undefined} cacheKey A key used to cache resources related to the source. If + * omitted, caching will be skipped. + */ + applyFilters: function(filters, source, width, height, targetCanvas, cacheKey) { + var gl = this.gl; + var cachedTexture; + if (cacheKey) { + cachedTexture = this.getCachedTexture(cacheKey, source); + } + var pipelineState = { + originalWidth: source.width || source.originalWidth, + originalHeight: source.height || source.originalHeight, + sourceWidth: width, + sourceHeight: height, + destinationWidth: width, + destinationHeight: height, + context: gl, + sourceTexture: this.createTexture(gl, width, height, !cachedTexture && source), + targetTexture: this.createTexture(gl, width, height), + originalTexture: cachedTexture || + this.createTexture(gl, width, height, !cachedTexture && source), + passes: filters.length, + webgl: true, + aPosition: this.aPosition, + programCache: this.programCache, + pass: 0, + filterBackend: this, + targetCanvas: targetCanvas + }; + var tempFbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo); + filters.forEach(function(filter) { filter && filter.applyTo(pipelineState); }); + resizeCanvasIfNeeded(pipelineState); + this.copyGLTo2D(gl, pipelineState); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.deleteTexture(pipelineState.sourceTexture); + gl.deleteTexture(pipelineState.targetTexture); + gl.deleteFramebuffer(tempFbo); + targetCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); + return pipelineState; + }, + + /** + * Detach event listeners, remove references, and clean up caches. + */ + dispose: function() { + if (this.canvas) { + this.canvas = null; + this.gl = null; + } + this.clearWebGLCaches(); + }, + + /** + * Wipe out WebGL-related caches. + */ + clearWebGLCaches: function() { + this.programCache = {}; + this.textureCache = {}; + }, + + /** + * Create a WebGL texture object. + * + * Accepts specific dimensions to initialize the texture to or a source image. + * + * @param {WebGLRenderingContext} gl The GL context to use for creating the texture. + * @param {Number} width The width to initialize the texture at. + * @param {Number} height The height to initialize the texture. + * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source for the texture data. + * @returns {WebGLTexture} + */ + createTexture: function(gl, width, height, textureImageSource) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + if (textureImageSource) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureImageSource); + } + else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + return texture; + }, + + /** + * Can be optionally used to get a texture from the cache array + * + * If an existing texture is not found, a new texture is created and cached. + * + * @param {String} uniqueId A cache key to use to find an existing texture. + * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the + * texture cache entry if one does not already exist. + */ + getCachedTexture: function(uniqueId, textureImageSource) { + if (this.textureCache[uniqueId]) { + return this.textureCache[uniqueId]; + } + else { + var texture = this.createTexture( + this.gl, textureImageSource.width, textureImageSource.height, textureImageSource); + this.textureCache[uniqueId] = texture; + return texture; + } + }, + + /** + * Clear out cached resources related to a source image that has been + * filtered previously. + * + * @param {String} cacheKey The cache key provided when the source image was filtered. + */ + evictCachesForKey: function(cacheKey) { + if (this.textureCache[cacheKey]) { + this.gl.deleteTexture(this.textureCache[cacheKey]); + delete this.textureCache[cacheKey]; + } + }, + + copyGLTo2D: copyGLTo2DDrawImage, + + /** + * Attempt to extract GPU information strings from a WebGL context. + * + * Useful information when debugging or blacklisting specific GPUs. + * + * @returns {Object} A GPU info object with renderer and vendor strings. + */ + captureGPUInfo: function() { + if (this.gpuInfo) { + return this.gpuInfo; + } + var gl = this.gl, gpuInfo = { renderer: '', vendor: '' }; + if (!gl) { + return gpuInfo; + } + var ext = gl.getExtension('WEBGL_debug_renderer_info'); + if (ext) { + var renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL); + var vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL); + if (renderer) { + gpuInfo.renderer = renderer.toLowerCase(); + } + if (vendor) { + gpuInfo.vendor = vendor.toLowerCase(); + } + } + this.gpuInfo = gpuInfo; + return gpuInfo; + }, + }; +})(); + +function resizeCanvasIfNeeded(pipelineState) { + var targetCanvas = pipelineState.targetCanvas, + width = targetCanvas.width, height = targetCanvas.height, + dWidth = pipelineState.destinationWidth, + dHeight = pipelineState.destinationHeight; + + if (width !== dWidth || height !== dHeight) { + targetCanvas.width = dWidth; + targetCanvas.height = dHeight; + } +} + +/** + * Copy an input WebGL canvas on to an output 2D canvas. + * + * The WebGL canvas is assumed to be upside down, with the top-left pixel of the + * desired output image appearing in the bottom-left corner of the WebGL canvas. + * + * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. + * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. + * @param {Object} pipelineState The 2D target canvas to copy on to. + */ +function copyGLTo2DDrawImage(gl, pipelineState) { + var glCanvas = gl.canvas, targetCanvas = pipelineState.targetCanvas, + ctx = targetCanvas.getContext('2d'); + ctx.translate(0, targetCanvas.height); // move it down again + ctx.scale(1, -1); // vertical flip + // where is my image on the big glcanvas? + var sourceY = glCanvas.height - targetCanvas.height; + ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, 0, 0, + targetCanvas.width, targetCanvas.height); +} + +/** + * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData + * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra). + * + * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from. + * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to. + * @param {Object} pipelineState The 2D target canvas to copy on to. + */ +function copyGLTo2DPutImageData(gl, pipelineState) { + var targetCanvas = pipelineState.targetCanvas, ctx = targetCanvas.getContext('2d'), + dWidth = pipelineState.destinationWidth, + dHeight = pipelineState.destinationHeight, + numBytes = dWidth * dHeight * 4; + + // eslint-disable-next-line no-undef + var u8 = new Uint8Array(this.imageBuffer, 0, numBytes); + // eslint-disable-next-line no-undef + var u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes); + + gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8); + var imgData = new ImageData(u8Clamped, dWidth, dHeight); + ctx.putImageData(imgData, 0, 0); +} + + +(function() { + + 'use strict'; + + var noop = function() {}; + + fabric.Canvas2dFilterBackend = Canvas2dFilterBackend; + + /** + * Canvas 2D filter backend. + */ + function Canvas2dFilterBackend() {}; + + Canvas2dFilterBackend.prototype = /** @lends fabric.Canvas2dFilterBackend.prototype */ { + evictCachesForKey: noop, + dispose: noop, + clearWebGLCaches: noop, + + /** + * Experimental. This object is a sort of repository of help layers used to avoid + * of recreating them during frequent filtering. If you are previewing a filter with + * a slider you probably do not want to create help layers every filter step. + * in this object there will be appended some canvases, created once, resized sometimes + * cleared never. Clearing is left to the developer. + **/ + resources: { + + }, + + /** + * Apply a set of filters against a source image and draw the filtered output + * to the provided destination canvas. + * + * @param {EnhancedFilter} filters The filter to apply. + * @param {HTMLImageElement|HTMLCanvasElement} sourceElement The source to be filtered. + * @param {Number} sourceWidth The width of the source input. + * @param {Number} sourceHeight The height of the source input. + * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn. + */ + applyFilters: function(filters, sourceElement, sourceWidth, sourceHeight, targetCanvas) { + var ctx = targetCanvas.getContext('2d'); + ctx.drawImage(sourceElement, 0, 0, sourceWidth, sourceHeight); + var imageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); + var originalImageData = ctx.getImageData(0, 0, sourceWidth, sourceHeight); + var pipelineState = { + sourceWidth: sourceWidth, + sourceHeight: sourceHeight, + imageData: imageData, + originalEl: sourceElement, + originalImageData: originalImageData, + canvasEl: targetCanvas, + ctx: ctx, + filterBackend: this, + }; + filters.forEach(function(filter) { filter.applyTo(pipelineState); }); + if (pipelineState.imageData.width !== sourceWidth || pipelineState.imageData.height !== sourceHeight) { + targetCanvas.width = pipelineState.imageData.width; + targetCanvas.height = pipelineState.imageData.height; + } + ctx.putImageData(pipelineState.imageData, 0, 0); + return pipelineState; + }, + + }; +})(); + + +/** + * @namespace fabric.Image.filters + * @memberOf fabric.Image + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#image_filters} + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + */ +fabric.Image = fabric.Image || { }; +fabric.Image.filters = fabric.Image.filters || { }; + +/** + * Root filter class from which all filter classes inherit from + * @class fabric.Image.filters.BaseFilter + * @memberOf fabric.Image.filters + */ +fabric.Image.filters.BaseFilter = fabric.util.createClass(/** @lends fabric.Image.filters.BaseFilter.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'BaseFilter', + + /** + * Array of attributes to send with buffers. do not modify + * @private + */ + + vertexSource: 'attribute vec2 aPosition;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vTexCoord = aPosition;\n' + + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + + '}', + + fragmentSource: 'precision highp float;\n' + + 'varying vec2 vTexCoord;\n' + + 'uniform sampler2D uTexture;\n' + + 'void main() {\n' + + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + + '}', + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + if (options) { + this.setOptions(options); + } + }, + + /** + * Sets filter's properties from options + * @param {Object} [options] Options object + */ + setOptions: function(options) { + for (var prop in options) { + this[prop] = options[prop]; + } + }, + + /** + * Compile this filter's shader program. + * + * @param {WebGLRenderingContext} gl The GL canvas context to use for shader compilation. + * @param {String} fragmentSource fragmentShader source for compilation + * @param {String} vertexSource vertexShader source for compilation + */ + createProgram: function(gl, fragmentSource, vertexSource) { + fragmentSource = fragmentSource || this.fragmentSource; + vertexSource = vertexSource || this.vertexSource; + if (fabric.webGlPrecision !== 'highp'){ + fragmentSource = fragmentSource.replace( + /precision highp float/g, + 'precision ' + fabric.webGlPrecision + ' float' + ); + } + var vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, vertexSource); + gl.compileShader(vertexShader); + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Vertex shader compile error for ' + this.type + ': ' + + gl.getShaderInfoLog(vertexShader) + ); + } + + var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, fragmentSource); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Fragment shader compile error for ' + this.type + ': ' + + gl.getShaderInfoLog(fragmentShader) + ); + } + + var program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + throw new Error( + // eslint-disable-next-line prefer-template + 'Shader link error for "${this.type}" ' + + gl.getProgramInfoLog(program) + ); + } + + var attributeLocations = this.getAttributeLocations(gl, program); + var uniformLocations = this.getUniformLocations(gl, program) || { }; + uniformLocations.uStepW = gl.getUniformLocation(program, 'uStepW'); + uniformLocations.uStepH = gl.getUniformLocation(program, 'uStepH'); + return { + program: program, + attributeLocations: attributeLocations, + uniformLocations: uniformLocations + }; + }, + + /** + * Return a map of attribute names to WebGLAttributeLocation objects. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {WebGLShaderProgram} program The shader program from which to take attribute locations. + * @returns {Object} A map of attribute names to attribute locations. + */ + getAttributeLocations: function(gl, program) { + return { + aPosition: gl.getAttribLocation(program, 'aPosition'), + }; + }, + + /** + * Return a map of uniform names to WebGLUniformLocation objects. + * + * Intended to be overridden by subclasses. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {WebGLShaderProgram} program The shader program from which to take uniform locations. + * @returns {Object} A map of uniform names to uniform locations. + */ + getUniformLocations: function (/* gl, program */) { + // in case i do not need any special uniform i need to return an empty object + return { }; + }, + + /** + * Send attribute data from this filter to its shader program on the GPU. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {Object} attributeLocations A map of shader attribute names to their locations. + */ + sendAttributeData: function(gl, attributeLocations, aPositionData) { + var attributeLocation = attributeLocations.aPosition; + var buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.enableVertexAttribArray(attributeLocation); + gl.vertexAttribPointer(attributeLocation, 2, gl.FLOAT, false, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, aPositionData, gl.STATIC_DRAW); + }, + + _setupFrameBuffer: function(options) { + var gl = options.context, width, height; + if (options.passes > 1) { + width = options.destinationWidth; + height = options.destinationHeight; + if (options.sourceWidth !== width || options.sourceHeight !== height) { + gl.deleteTexture(options.targetTexture); + options.targetTexture = options.filterBackend.createTexture(gl, width, height); + } + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, + options.targetTexture, 0); + } + else { + // draw last filter on canvas and not to framebuffer. + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.finish(); + } + }, + + _swapTextures: function(options) { + options.passes--; + options.pass++; + var temp = options.targetTexture; + options.targetTexture = options.sourceTexture; + options.sourceTexture = temp; + }, + + /** + * Generic isNeutral implementation for one parameter based filters. + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * Other filters may need their own version ( ColorMatrix, HueRotation, gamma, ComposedFilter ) + * @param {Object} options + **/ + isNeutralState: function(/* options */) { + var main = this.mainParameter, + _class = fabric.Image.filters[this.type].prototype; + if (main) { + if (Array.isArray(_class[main])) { + for (var i = _class[main].length; i--;) { + if (this[main][i] !== _class[main][i]) { + return false; + } + } + return true; + } + else { + return _class[main] === this[main]; + } + } + else { + return false; + } + }, + + /** + * Apply this filter to the input image data provided. + * + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + if (options.webgl) { + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + } + else { + this.applyTo2d(options); + } + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + if (!options.programCache.hasOwnProperty(this.type)) { + options.programCache[this.type] = this.createProgram(options.context); + } + return options.programCache[this.type]; + }, + + /** + * Apply this filter using webgl. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.originalTexture The texture of the original input image. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyToWebGL: function(options) { + var gl = options.context; + var shader = this.retrieveShader(options); + if (options.pass === 0 && options.originalTexture) { + gl.bindTexture(gl.TEXTURE_2D, options.originalTexture); + } + else { + gl.bindTexture(gl.TEXTURE_2D, options.sourceTexture); + } + gl.useProgram(shader.program); + this.sendAttributeData(gl, shader.attributeLocations, options.aPosition); + + gl.uniform1f(shader.uniformLocations.uStepW, 1 / options.sourceWidth); + gl.uniform1f(shader.uniformLocations.uStepH, 1 / options.sourceHeight); + + this.sendUniformData(gl, shader.uniformLocations); + gl.viewport(0, 0, options.destinationWidth, options.destinationHeight); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + }, + + bindAdditionalTexture: function(gl, texture, textureUnit) { + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_2D, texture); + // reset active texture to 0 as usual + gl.activeTexture(gl.TEXTURE0); + }, + + unbindAdditionalTexture: function(gl, textureUnit) { + gl.activeTexture(textureUnit); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.activeTexture(gl.TEXTURE0); + }, + + getMainParameter: function() { + return this[this.mainParameter]; + }, + + setMainParameter: function(value) { + this[this.mainParameter] = value; + }, + + /** + * Send uniform data from this filter to its shader program on the GPU. + * + * Intended to be overridden by subclasses. + * + * @param {WebGLRenderingContext} gl The canvas context used to compile the shader program. + * @param {Object} uniformLocations A map of shader uniform names to their locations. + */ + sendUniformData: function(/* gl, uniformLocations */) { + // Intentionally left blank. Override me in subclasses. + }, + + /** + * If needed by a 2d filter, this functions can create an helper canvas to be used + * remember that options.targetCanvas is available for use till end of chain. + */ + createHelpLayer: function(options) { + if (!options.helpLayer) { + var helpLayer = document.createElement('canvas'); + helpLayer.width = options.sourceWidth; + helpLayer.height = options.sourceHeight; + options.helpLayer = helpLayer; + } + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + var object = { type: this.type }, mainP = this.mainParameter; + if (mainP) { + object[mainP] = this[mainP]; + } + return object; + }, + + /** + * Returns a JSON representation of an instance + * @return {Object} JSON + */ + toJSON: function() { + // delegate, not alias + return this.toObject(); + } +}); + +fabric.Image.filters.BaseFilter.fromObject = function(object, callback) { + var filter = new fabric.Image.filters[object.type](object); + callback && callback(filter); + return filter; +}; + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Color Matrix filter class + * @class fabric.Image.filters.ColorMatrix + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.ColorMatrix#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @see {@Link http://www.webwasp.co.uk/tutorials/219/Color_Matrix_Filter.php} + * @see {@Link http://phoboslab.org/log/2013/11/fast-image-filters-with-webgl} + * @example Kodachrome filter + * var filter = new fabric.Image.filters.ColorMatrix({ + * matrix: [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.ColorMatrix = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.ColorMatrix.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'ColorMatrix', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'varying vec2 vTexCoord;\n' + + 'uniform mat4 uColorMatrix;\n' + + 'uniform vec4 uConstants;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color *= uColorMatrix;\n' + + 'color += uConstants;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Colormatrix for pixels. + * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning + * outside the -1, 1 range. + * 0.0039215686 is the part of 1 that get translated to 1 in 2d + * @param {Array} matrix array of 20 numbers. + * @default + */ + matrix: [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ], + + mainParameter: 'matrix', + + /** + * Lock the colormatrix on the color part, skipping alpha, manly for non webgl scenario + * to save some calculation + */ + colorsOnly: true, + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.callSuper('initialize', options); + // create a new array instead mutating the prototype with push + this.matrix = this.matrix.slice(0); + }, + + /** + * Apply the ColorMatrix operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + iLen = data.length, + m = this.matrix, + r, g, b, a, i, colorsOnly = this.colorsOnly; + + for (i = 0; i < iLen; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + if (colorsOnly) { + data[i] = r * m[0] + g * m[1] + b * m[2] + m[4] * 255; + data[i + 1] = r * m[5] + g * m[6] + b * m[7] + m[9] * 255; + data[i + 2] = r * m[10] + g * m[11] + b * m[12] + m[14] * 255; + } + else { + a = data[i + 3]; + data[i] = r * m[0] + g * m[1] + b * m[2] + a * m[3] + m[4] * 255; + data[i + 1] = r * m[5] + g * m[6] + b * m[7] + a * m[8] + m[9] * 255; + data[i + 2] = r * m[10] + g * m[11] + b * m[12] + a * m[13] + m[14] * 255; + data[i + 3] = r * m[15] + g * m[16] + b * m[17] + a * m[18] + m[19] * 255; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uColorMatrix: gl.getUniformLocation(program, 'uColorMatrix'), + uConstants: gl.getUniformLocation(program, 'uConstants'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var m = this.matrix, + matrix = [ + m[0], m[1], m[2], m[3], + m[5], m[6], m[7], m[8], + m[10], m[11], m[12], m[13], + m[15], m[16], m[17], m[18] + ], + constants = [m[4], m[9], m[14], m[19]]; + gl.uniformMatrix4fv(uniformLocations.uColorMatrix, false, matrix); + gl.uniform4fv(uniformLocations.uConstants, constants); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] function to invoke after filter creation + * @return {fabric.Image.filters.ColorMatrix} Instance of fabric.Image.filters.ColorMatrix + */ + fabric.Image.filters.ColorMatrix.fromObject = fabric.Image.filters.BaseFilter.fromObject; +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Brightness filter class + * @class fabric.Image.filters.Brightness + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Brightness#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Brightness({ + * brightness: 0.05 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Brightness = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Brightness.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Brightness', + + /** + * Fragment source for the brightness program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uBrightness;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color.rgb += uBrightness;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Brightness value, from -1 to 1. + * translated to -255 to 255 for 2d + * 0.0039215686 is the part of 1 that get translated to 1 in 2d + * @param {Number} brightness + * @default + */ + brightness: 0, + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'brightness', + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.brightness === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, i, len = data.length, + brightness = Math.round(this.brightness * 255); + for (i = 0; i < len; i += 4) { + data[i] = data[i] + brightness; + data[i + 1] = data[i + 1] + brightness; + data[i + 2] = data[i + 2] + brightness; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uBrightness: gl.getUniformLocation(program, 'uBrightness'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uBrightness, this.brightness); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Brightness} Instance of fabric.Image.filters.Brightness + */ + fabric.Image.filters.Brightness.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Adapted from html5rocks article + * @class fabric.Image.filters.Convolute + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Convolute#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example Sharpen filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 0, -1, 0, + * -1, 5, -1, + * 0, -1, 0 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Blur filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9, + * 1/9, 1/9, 1/9 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Emboss filter + * var filter = new fabric.Image.filters.Convolute({ + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + * @example Emboss filter with opaqueness + * var filter = new fabric.Image.filters.Convolute({ + * opaque: true, + * matrix: [ 1, 1, 1, + * 1, 0.7, -1, + * -1, -1, -1 ] + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Convolute = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Convolute.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Convolute', + + /* + * Opaque value (true/false) + */ + opaque: false, + + /* + * matrix for the filter, max 9x9 + */ + matrix: [0, 0, 0, 0, 1, 0, 0, 0, 0], + + /** + * Fragment source for the brightness program + */ + fragmentSource: { + Convolute_3_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[9];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 1), uStepH * (h - 1));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 3.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_3_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[9];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 3.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 3.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 1.0), uStepH * (h - 1.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 3.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_5_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[25];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 5.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_5_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[25];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 5.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 5.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 2.0), uStepH * (h - 2.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 5.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_7_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[49];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 7.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_7_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[49];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 7.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 7.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 3.0), uStepH * (h - 3.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 7.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + Convolute_9_1: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[81];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 0);\n' + + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + + 'color += texture2D(uTexture, vTexCoord + matrixPos) * uMatrix[int(h * 9.0 + w)];\n' + + '}\n' + + '}\n' + + 'gl_FragColor = color;\n' + + '}', + Convolute_9_0: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uMatrix[81];\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = vec4(0, 0, 0, 1);\n' + + 'for (float h = 0.0; h < 9.0; h+=1.0) {\n' + + 'for (float w = 0.0; w < 9.0; w+=1.0) {\n' + + 'vec2 matrixPos = vec2(uStepW * (w - 4.0), uStepH * (h - 4.0));\n' + + 'color.rgb += texture2D(uTexture, vTexCoord + matrixPos).rgb * uMatrix[int(h * 9.0 + w)];\n' + + '}\n' + + '}\n' + + 'float alpha = texture2D(uTexture, vTexCoord).a;\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.a = alpha;\n' + + '}', + }, + + /** + * Constructor + * @memberOf fabric.Image.filters.Convolute.prototype + * @param {Object} [options] Options object + * @param {Boolean} [options.opaque=false] Opaque value (true/false) + * @param {Array} [options.matrix] Filter matrix + */ + + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var size = Math.sqrt(this.matrix.length); + var cacheKey = this.type + '_' + size + '_' + (this.opaque ? 1 : 0); + var shaderSource = this.fragmentSource[cacheKey]; + if (!options.programCache.hasOwnProperty(cacheKey)) { + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + weights = this.matrix, + side = Math.round(Math.sqrt(weights.length)), + halfSide = Math.floor(side / 2), + sw = imageData.width, + sh = imageData.height, + output = options.ctx.createImageData(sw, sh), + dst = output.data, + // go through the destination image pixels + alphaFac = this.opaque ? 1 : 0, + r, g, b, a, dstOff, + scx, scy, srcOff, wt, + x, y, cx, cy; + + for (y = 0; y < sh; y++) { + for (x = 0; x < sw; x++) { + dstOff = (y * sw + x) * 4; + // calculate the weighed sum of the source image pixels that + // fall under the convolution matrix + r = 0; g = 0; b = 0; a = 0; + + for (cy = 0; cy < side; cy++) { + for (cx = 0; cx < side; cx++) { + scy = y + cy - halfSide; + scx = x + cx - halfSide; + + // eslint-disable-next-line max-depth + if (scy < 0 || scy >= sh || scx < 0 || scx >= sw) { + continue; + } + + srcOff = (scy * sw + scx) * 4; + wt = weights[cy * side + cx]; + + r += data[srcOff] * wt; + g += data[srcOff + 1] * wt; + b += data[srcOff + 2] * wt; + // eslint-disable-next-line max-depth + if (!alphaFac) { + a += data[srcOff + 3] * wt; + } + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + if (!alphaFac) { + dst[dstOff + 3] = a; + } + else { + dst[dstOff + 3] = data[dstOff + 3]; + } + } + } + options.imageData = output; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uMatrix: gl.getUniformLocation(program, 'uMatrix'), + uOpaque: gl.getUniformLocation(program, 'uOpaque'), + uHalfSize: gl.getUniformLocation(program, 'uHalfSize'), + uSize: gl.getUniformLocation(program, 'uSize'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1fv(uniformLocations.uMatrix, this.matrix); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + opaque: this.opaque, + matrix: this.matrix + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Convolute} Instance of fabric.Image.filters.Convolute + */ + fabric.Image.filters.Convolute.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Grayscale image filter class + * @class fabric.Image.filters.Grayscale + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Grayscale(); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Grayscale = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Grayscale.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Grayscale', + + fragmentSource: { + average: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float average = (color.r + color.b + color.g) / 3.0;\n' + + 'gl_FragColor = vec4(average, average, average, color.a);\n' + + '}', + lightness: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uMode;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + + 'float average = (max(max(col.r, col.g),col.b) + min(min(col.r, col.g),col.b)) / 2.0;\n' + + 'gl_FragColor = vec4(average, average, average, col.a);\n' + + '}', + luminosity: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uMode;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 col = texture2D(uTexture, vTexCoord);\n' + + 'float average = 0.21 * col.r + 0.72 * col.g + 0.07 * col.b;\n' + + 'gl_FragColor = vec4(average, average, average, col.a);\n' + + '}', + }, + + + /** + * Grayscale mode, between 'average', 'lightness', 'luminosity' + * @param {String} type + * @default + */ + mode: 'average', + + mainParameter: 'mode', + + /** + * Apply the Grayscale operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + len = data.length, value, + mode = this.mode; + for (i = 0; i < len; i += 4) { + if (mode === 'average') { + value = (data[i] + data[i + 1] + data[i + 2]) / 3; + } + else if (mode === 'lightness') { + value = (Math.min(data[i], data[i + 1], data[i + 2]) + + Math.max(data[i], data[i + 1], data[i + 2])) / 2; + } + else if (mode === 'luminosity') { + value = 0.21 * data[i] + 0.72 * data[i + 1] + 0.07 * data[i + 2]; + } + data[i] = value; + data[i + 1] = value; + data[i + 2] = value; + } + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode; + if (!options.programCache.hasOwnProperty(cacheKey)) { + var shaderSource = this.fragmentSource[this.mode]; + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uMode: gl.getUniformLocation(program, 'uMode'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + // default average mode. + var mode = 1; + gl.uniform1i(uniformLocations.uMode, mode); + }, + + /** + * Grayscale filter isNeutralState implementation + * The filter is never neutral + * on the image + **/ + isNeutralState: function() { + return false; + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Grayscale} Instance of fabric.Image.filters.Grayscale + */ + fabric.Image.filters.Grayscale.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Invert filter class + * @class fabric.Image.filters.Invert + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Invert(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Invert = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Invert.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Invert', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform int uInvert;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'if (uInvert == 1) {\n' + + 'gl_FragColor = vec4(1.0 - color.r,1.0 -color.g,1.0 -color.b,color.a);\n' + + '} else {\n' + + 'gl_FragColor = color;\n' + + '}\n' + + '}', + + /** + * Filter invert. if false, does nothing + * @param {Boolean} invert + * @default + */ + invert: true, + + mainParameter: 'invert', + + /** + * Apply the Invert operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + len = data.length; + for (i = 0; i < len; i += 4) { + data[i] = 255 - data[i]; + data[i + 1] = 255 - data[i + 1]; + data[i + 2] = 255 - data[i + 2]; + } + }, + + /** + * Invert filter isNeutralState implementation + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * @param {Object} options + **/ + isNeutralState: function() { + return !this.invert; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uInvert: gl.getUniformLocation(program, 'uInvert'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1i(uniformLocations.uInvert, this.invert); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Invert} Instance of fabric.Image.filters.Invert + */ + fabric.Image.filters.Invert.fromObject = fabric.Image.filters.BaseFilter.fromObject; + + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Noise filter class + * @class fabric.Image.filters.Noise + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Noise#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Noise({ + * noise: 700 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Noise = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Noise.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Noise', + + /** + * Fragment source for the noise program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uStepH;\n' + + 'uniform float uNoise;\n' + + 'uniform float uSeed;\n' + + 'varying vec2 vTexCoord;\n' + + 'float rand(vec2 co, float seed, float vScale) {\n' + + 'return fract(sin(dot(co.xy * vScale ,vec2(12.9898 , 78.233))) * 43758.5453 * (seed + 0.01) / 2.0);\n' + + '}\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'color.rgb += (0.5 - rand(vTexCoord, uSeed, 0.1 / uStepH)) * uNoise;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'noise', + + /** + * Noise value, from + * @param {Number} noise + * @default + */ + noise: 0, + + /** + * Apply the Brightness operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.noise === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, i, len = data.length, + noise = this.noise, rand; + + for (i = 0, len = data.length; i < len; i += 4) { + + rand = (0.5 - Math.random()) * noise; + + data[i] += rand; + data[i + 1] += rand; + data[i + 2] += rand; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uNoise: gl.getUniformLocation(program, 'uNoise'), + uSeed: gl.getUniformLocation(program, 'uSeed'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uNoise, this.noise / 255); + gl.uniform1f(uniformLocations.uSeed, Math.random()); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + noise: this.noise + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Noise} Instance of fabric.Image.filters.Noise + */ + fabric.Image.filters.Noise.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Pixelate filter class + * @class fabric.Image.filters.Pixelate + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Pixelate#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Pixelate({ + * blocksize: 8 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Pixelate = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Pixelate.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Pixelate', + + blocksize: 4, + + mainParameter: 'blocksize', + + /** + * Fragment source for the Pixelate program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uBlocksize;\n' + + 'uniform float uStepW;\n' + + 'uniform float uStepH;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'float blockW = uBlocksize * uStepW;\n' + + 'float blockH = uBlocksize * uStepW;\n' + + 'int posX = int(vTexCoord.x / blockW);\n' + + 'int posY = int(vTexCoord.y / blockH);\n' + + 'float fposX = float(posX);\n' + + 'float fposY = float(posY);\n' + + 'vec2 squareCoords = vec2(fposX * blockW, fposY * blockH);\n' + + 'vec4 color = texture2D(uTexture, squareCoords);\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Apply the Pixelate operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, + iLen = imageData.height, + jLen = imageData.width, + index, i, j, r, g, b, a, + _i, _j, _iLen, _jLen; + + for (i = 0; i < iLen; i += this.blocksize) { + for (j = 0; j < jLen; j += this.blocksize) { + + index = (i * 4) * jLen + (j * 4); + + r = data[index]; + g = data[index + 1]; + b = data[index + 2]; + a = data[index + 3]; + + _iLen = Math.min(i + this.blocksize, iLen); + _jLen = Math.min(j + this.blocksize, jLen); + for (_i = i; _i < _iLen; _i++) { + for (_j = j; _j < _jLen; _j++) { + index = (_i * 4) * jLen + (_j * 4); + data[index] = r; + data[index + 1] = g; + data[index + 2] = b; + data[index + 3] = a; + } + } + } + } + }, + + /** + * Indicate when the filter is not gonna apply changes to the image + **/ + isNeutralState: function() { + return this.blocksize === 1; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uBlocksize: gl.getUniformLocation(program, 'uBlocksize'), + uStepW: gl.getUniformLocation(program, 'uStepW'), + uStepH: gl.getUniformLocation(program, 'uStepH'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uBlocksize, this.blocksize); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Pixelate} Instance of fabric.Image.filters.Pixelate + */ + fabric.Image.filters.Pixelate.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Remove white filter class + * @class fabric.Image.filters.RemoveColor + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.RemoveColor#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.RemoveColor({ + * threshold: 0.2, + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.RemoveColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.RemoveColor.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'RemoveColor', + + /** + * Color to remove, in any format understood by fabric.Color. + * @param {String} type + * @default + */ + color: '#FFFFFF', + + /** + * Fragment source for the brightness program + */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec4 uLow;\n' + + 'uniform vec4 uHigh;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'gl_FragColor = texture2D(uTexture, vTexCoord);\n' + + 'if(all(greaterThan(gl_FragColor.rgb,uLow.rgb)) && all(greaterThan(uHigh.rgb,gl_FragColor.rgb))) {\n' + + 'gl_FragColor.a = 0.0;\n' + + '}\n' + + '}', + + /** + * distance to actual color, as value up or down from each r,g,b + * between 0 and 1 + **/ + distance: 0.02, + + /** + * For color to remove inside distance, use alpha channel for a smoother deletion + * NOT IMPLEMENTED YET + **/ + useAlpha: false, + + /** + * Constructor + * @memberOf fabric.Image.filters.RemoveWhite.prototype + * @param {Object} [options] Options object + * @param {Number} [options.color=#RRGGBB] Threshold value + * @param {Number} [options.distance=10] Distance value + */ + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, i, + distance = this.distance * 255, + r, g, b, + source = new fabric.Color(this.color).getSource(), + lowC = [ + source[0] - distance, + source[1] - distance, + source[2] - distance, + ], + highC = [ + source[0] + distance, + source[1] + distance, + source[2] + distance, + ]; + + + for (i = 0; i < data.length; i += 4) { + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + if (r > lowC[0] && + g > lowC[1] && + b > lowC[2] && + r < highC[0] && + g < highC[1] && + b < highC[2]) { + data[i + 3] = 0; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uLow: gl.getUniformLocation(program, 'uLow'), + uHigh: gl.getUniformLocation(program, 'uHigh'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var source = new fabric.Color(this.color).getSource(), + distance = parseFloat(this.distance), + lowC = [ + 0 + source[0] / 255 - distance, + 0 + source[1] / 255 - distance, + 0 + source[2] / 255 - distance, + 1 + ], + highC = [ + source[0] / 255 + distance, + source[1] / 255 + distance, + source[2] / 255 + distance, + 1 + ]; + gl.uniform4fv(uniformLocations.uLow, lowC); + gl.uniform4fv(uniformLocations.uHigh, highC); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + color: this.color, + distance: this.distance + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.RemoveColor} Instance of fabric.Image.filters.RemoveWhite + */ + fabric.Image.filters.RemoveColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + var matrices = { + Brownie: [ + 0.59970,0.34553,-0.27082,0,0.186, + -0.03770,0.86095,0.15059,0,-0.1449, + 0.24113,-0.07441,0.44972,0,-0.02965, + 0,0,0,1,0 + ], + Vintage: [ + 0.62793,0.32021,-0.03965,0,0.03784, + 0.02578,0.64411,0.03259,0,0.02926, + 0.04660,-0.08512,0.52416,0,0.02023, + 0,0,0,1,0 + ], + Kodachrome: [ + 1.12855,-0.39673,-0.03992,0,0.24991, + -0.16404,1.08352,-0.05498,0,0.09698, + -0.16786,-0.56034,1.60148,0,0.13972, + 0,0,0,1,0 + ], + Technicolor: [ + 1.91252,-0.85453,-0.09155,0,0.04624, + -0.30878,1.76589,-0.10601,0,-0.27589, + -0.23110,-0.75018,1.84759,0,0.12137, + 0,0,0,1,0 + ], + Polaroid: [ + 1.438,-0.062,-0.062,0,0, + -0.122,1.378,-0.122,0,0, + -0.016,-0.016,1.483,0,0, + 0,0,0,1,0 + ], + Sepia: [ + 0.393, 0.769, 0.189, 0, 0, + 0.349, 0.686, 0.168, 0, 0, + 0.272, 0.534, 0.131, 0, 0, + 0, 0, 0, 1, 0 + ], + BlackWhite: [ + 1.5, 1.5, 1.5, 0, -1, + 1.5, 1.5, 1.5, 0, -1, + 1.5, 1.5, 1.5, 0, -1, + 0, 0, 0, 1, 0, + ] + }; + + for (var key in matrices) { + filters[key] = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.Sepia.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: key, + + /** + * Colormatrix for the effect + * array of 20 floats. Numbers in positions 4, 9, 14, 19 loose meaning + * outside the -1, 1 range. + * @param {Array} matrix array of 20 numbers. + * @default + */ + matrix: matrices[key], + + /** + * Lock the matrix export for this kind of static, parameter less filters. + */ + mainParameter: false, + /** + * Lock the colormatrix on the color part, skipping alpha + */ + colorsOnly: true, + + }); + fabric.Image.filters[key].fromObject = fabric.Image.filters.BaseFilter.fromObject; + } +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + 'use strict'; + + var fabric = global.fabric, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Color Blend filter class + * @class fabric.Image.filter.BlendColor + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.BlendColor({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.BlendImage({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + + filters.BlendColor = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blend.prototype */ { + type: 'BlendColor', + + /** + * Color to make the blend operation with. default to a reddish color since black or white + * gives always strong result. + **/ + color: '#F95C63', + + /** + * Blend mode for the filter: one of multiply, add, diff, screen, subtract, + * darken, lighten, overlay, exclusion, tint. + **/ + mode: 'multiply', + + /** + * alpha value. represent the strength of the blend color operation. + **/ + alpha: 1, + + /** + * Fragment source for the Multiply program + */ + fragmentSource: { + multiply: 'gl_FragColor.rgb *= uColor.rgb;\n', + screen: 'gl_FragColor.rgb = 1.0 - (1.0 - gl_FragColor.rgb) * (1.0 - uColor.rgb);\n', + add: 'gl_FragColor.rgb += uColor.rgb;\n', + diff: 'gl_FragColor.rgb = abs(gl_FragColor.rgb - uColor.rgb);\n', + subtract: 'gl_FragColor.rgb -= uColor.rgb;\n', + lighten: 'gl_FragColor.rgb = max(gl_FragColor.rgb, uColor.rgb);\n', + darken: 'gl_FragColor.rgb = min(gl_FragColor.rgb, uColor.rgb);\n', + exclusion: 'gl_FragColor.rgb += uColor.rgb - 2.0 * (uColor.rgb * gl_FragColor.rgb);\n', + overlay: 'if (uColor.r < 0.5) {\n' + + 'gl_FragColor.r *= 2.0 * uColor.r;\n' + + '} else {\n' + + 'gl_FragColor.r = 1.0 - 2.0 * (1.0 - gl_FragColor.r) * (1.0 - uColor.r);\n' + + '}\n' + + 'if (uColor.g < 0.5) {\n' + + 'gl_FragColor.g *= 2.0 * uColor.g;\n' + + '} else {\n' + + 'gl_FragColor.g = 1.0 - 2.0 * (1.0 - gl_FragColor.g) * (1.0 - uColor.g);\n' + + '}\n' + + 'if (uColor.b < 0.5) {\n' + + 'gl_FragColor.b *= 2.0 * uColor.b;\n' + + '} else {\n' + + 'gl_FragColor.b = 1.0 - 2.0 * (1.0 - gl_FragColor.b) * (1.0 - uColor.b);\n' + + '}\n', + tint: 'gl_FragColor.rgb *= (1.0 - uColor.a);\n' + + 'gl_FragColor.rgb += uColor.rgb;\n', + }, + + /** + * build the fragment source for the filters, joining the common part with + * the specific one. + * @param {String} mode the mode of the filter, a key of this.fragmentSource + * @return {String} the source to be compiled + * @private + */ + buildSource: function(mode) { + return 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'gl_FragColor = color;\n' + + 'if (color.a > 0.0) {\n' + + this.fragmentSource[mode] + + '}\n' + + '}'; + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode, shaderSource; + if (!options.programCache.hasOwnProperty(cacheKey)) { + shaderSource = this.buildSource(this.mode); + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + /** + * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + data = imageData.data, iLen = data.length, + tr, tg, tb, + r, g, b, + source, alpha1 = 1 - this.alpha; + + source = new fabric.Color(this.color).getSource(); + tr = source[0] * this.alpha; + tg = source[1] * this.alpha; + tb = source[2] * this.alpha; + + for (var i = 0; i < iLen; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + break; + case 'screen': + data[i] = 255 - (255 - r) * (255 - tr) / 255; + data[i + 1] = 255 - (255 - g) * (255 - tg) / 255; + data[i + 2] = 255 - (255 - b) * (255 - tb) / 255; + break; + case 'add': + data[i] = r + tr; + data[i + 1] = g + tg; + data[i + 2] = b + tb; + break; + case 'diff': + case 'difference': + data[i] = Math.abs(r - tr); + data[i + 1] = Math.abs(g - tg); + data[i + 2] = Math.abs(b - tb); + break; + case 'subtract': + data[i] = r - tr; + data[i + 1] = g - tg; + data[i + 2] = b - tb; + break; + case 'darken': + data[i] = Math.min(r, tr); + data[i + 1] = Math.min(g, tg); + data[i + 2] = Math.min(b, tb); + break; + case 'lighten': + data[i] = Math.max(r, tr); + data[i + 1] = Math.max(g, tg); + data[i + 2] = Math.max(b, tb); + break; + case 'overlay': + data[i] = tr < 128 ? (2 * r * tr / 255) : (255 - 2 * (255 - r) * (255 - tr) / 255); + data[i + 1] = tg < 128 ? (2 * g * tg / 255) : (255 - 2 * (255 - g) * (255 - tg) / 255); + data[i + 2] = tb < 128 ? (2 * b * tb / 255) : (255 - 2 * (255 - b) * (255 - tb) / 255); + break; + case 'exclusion': + data[i] = tr + r - ((2 * tr * r) / 255); + data[i + 1] = tg + g - ((2 * tg * g) / 255); + data[i + 2] = tb + b - ((2 * tb * b) / 255); + break; + case 'tint': + data[i] = tr + r * alpha1; + data[i + 1] = tg + g * alpha1; + data[i + 2] = tb + b * alpha1; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uColor: gl.getUniformLocation(program, 'uColor'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var source = new fabric.Color(this.color).getSource(); + source[0] = this.alpha * source[0] / 255; + source[1] = this.alpha * source[1] / 255; + source[2] = this.alpha * source[2] / 255; + source[3] = this.alpha; + gl.uniform4fv(uniformLocations.uColor, source); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + color: this.color, + mode: this.mode, + alpha: this.alpha + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.BlendColor} Instance of fabric.Image.filters.BlendColor + */ + fabric.Image.filters.BlendColor.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + 'use strict'; + + var fabric = global.fabric, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Image Blend filter class + * @class fabric.Image.filter.BlendImage + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @example + * var filter = new fabric.Image.filters.BlendColor({ + * color: '#000', + * mode: 'multiply' + * }); + * + * var filter = new fabric.Image.filters.BlendImage({ + * image: fabricImageObject, + * mode: 'multiply', + * alpha: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + + filters.BlendImage = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.BlendImage.prototype */ { + type: 'BlendImage', + + /** + * Color to make the blend operation with. default to a reddish color since black or white + * gives always strong result. + **/ + image: null, + + /** + * Blend mode for the filter: one of multiply, add, diff, screen, subtract, + * darken, lighten, overlay, exclusion, tint. + **/ + mode: 'multiply', + + /** + * alpha value. represent the strength of the blend image operation. + * not implemented. + **/ + alpha: 1, + + vertexSource: 'attribute vec2 aPosition;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'uniform mat3 uTransformMatrix;\n' + + 'void main() {\n' + + 'vTexCoord = aPosition;\n' + + 'vTexCoord2 = (uTransformMatrix * vec3(aPosition, 1.0)).xy;\n' + + 'gl_Position = vec4(aPosition * 2.0 - 1.0, 0.0, 1.0);\n' + + '}', + + /** + * Fragment source for the Multiply program + */ + fragmentSource: { + multiply: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform sampler2D uImage;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + + 'color.rgba *= color2.rgba;\n' + + 'gl_FragColor = color;\n' + + '}', + mask: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform sampler2D uImage;\n' + + 'uniform vec4 uColor;\n' + + 'varying vec2 vTexCoord;\n' + + 'varying vec2 vTexCoord2;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec4 color2 = texture2D(uImage, vTexCoord2);\n' + + 'color.a = color2.a;\n' + + 'gl_FragColor = color;\n' + + '}', + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var cacheKey = this.type + '_' + this.mode; + var shaderSource = this.fragmentSource[this.mode]; + if (!options.programCache.hasOwnProperty(cacheKey)) { + options.programCache[cacheKey] = this.createProgram(options.context, shaderSource); + } + return options.programCache[cacheKey]; + }, + + applyToWebGL: function(options) { + // load texture to blend. + var gl = options.context, + texture = this.createTexture(options.filterBackend, this.image); + this.bindAdditionalTexture(gl, texture, gl.TEXTURE1); + this.callSuper('applyToWebGL', options); + this.unbindAdditionalTexture(gl, gl.TEXTURE1); + }, + + createTexture: function(backend, image) { + return backend.getCachedTexture(image.cacheKey, image._element); + }, + + /** + * Calculate a transformMatrix to adapt the image to blend over + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + calculateMatrix: function() { + var image = this.image, + width = image._element.width, + height = image._element.height; + return [ + 1 / image.scaleX, 0, 0, + 0, 1 / image.scaleY, 0, + -image.left / width, -image.top / height, 1 + ]; + }, + + /** + * Apply the Blend operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, + resources = options.filterBackend.resources, + data = imageData.data, iLen = data.length, + width = imageData.width, + height = imageData.height, + tr, tg, tb, ta, + r, g, b, a, + canvas1, context, image = this.image, blendData; + + if (!resources.blendImage) { + resources.blendImage = fabric.util.createCanvasElement(); + } + canvas1 = resources.blendImage; + context = canvas1.getContext('2d'); + if (canvas1.width !== width || canvas1.height !== height) { + canvas1.width = width; + canvas1.height = height; + } + else { + context.clearRect(0, 0, width, height); + } + context.setTransform(image.scaleX, 0, 0, image.scaleY, image.left, image.top); + context.drawImage(image._element, 0, 0, width, height); + blendData = context.getImageData(0, 0, width, height).data; + for (var i = 0; i < iLen; i += 4) { + + r = data[i]; + g = data[i + 1]; + b = data[i + 2]; + a = data[i + 3]; + + tr = blendData[i]; + tg = blendData[i + 1]; + tb = blendData[i + 2]; + ta = blendData[i + 3]; + + switch (this.mode) { + case 'multiply': + data[i] = r * tr / 255; + data[i + 1] = g * tg / 255; + data[i + 2] = b * tb / 255; + data[i + 3] = a * ta / 255; + break; + case 'mask': + data[i + 3] = ta; + break; + } + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uTransformMatrix: gl.getUniformLocation(program, 'uTransformMatrix'), + uImage: gl.getUniformLocation(program, 'uImage'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var matrix = this.calculateMatrix(); + gl.uniform1i(uniformLocations.uImage, 1); // texture unit 1. + gl.uniformMatrix3fv(uniformLocations.uTransformMatrix, false, matrix); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + image: this.image && this.image.toObject(), + mode: this.mode, + alpha: this.alpha + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} callback to be invoked after filter creation + * @return {fabric.Image.filters.BlendImage} Instance of fabric.Image.filters.BlendImage + */ + fabric.Image.filters.BlendImage.fromObject = function(object, callback) { + fabric.Image.fromObject(object.image, function(image) { + var options = fabric.util.object.clone(object); + options.image = image; + callback(new fabric.Image.filters.BlendImage(options)); + }); + }; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), pow = Math.pow, floor = Math.floor, + sqrt = Math.sqrt, abs = Math.abs, round = Math.round, sin = Math.sin, + ceil = Math.ceil, + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Resize image filter class + * @class fabric.Image.filters.Resize + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Resize(); + * object.filters.push(filter); + * object.applyFilters(canvas.renderAll.bind(canvas)); + */ + filters.Resize = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Resize.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Resize', + + /** + * Resize type + * for webgl resizeType is just lanczos, for canvas2d can be: + * bilinear, hermite, sliceHack, lanczos. + * @param {String} resizeType + * @default + */ + resizeType: 'hermite', + + /** + * Scale factor for resizing, x axis + * @param {Number} scaleX + * @default + */ + scaleX: 1, + + /** + * Scale factor for resizing, y axis + * @param {Number} scaleY + * @default + */ + scaleY: 1, + + /** + * LanczosLobes parameter for lanczos filter, valid for resizeType lanczos + * @param {Number} lanczosLobes + * @default + */ + lanczosLobes: 3, + + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uDelta: gl.getUniformLocation(program, 'uDelta'), + uTaps: gl.getUniformLocation(program, 'uTaps'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform2fv(uniformLocations.uDelta, this.horizontal ? [1 / this.width, 0] : [0, 1 / this.height]); + gl.uniform1fv(uniformLocations.uTaps, this.taps); + }, + + /** + * Retrieves the cached shader. + * @param {Object} options + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + retrieveShader: function(options) { + var filterWindow = this.getFilterWindow(), cacheKey = this.type + '_' + filterWindow; + if (!options.programCache.hasOwnProperty(cacheKey)) { + var fragmentShader = this.generateShader(filterWindow); + options.programCache[cacheKey] = this.createProgram(options.context, fragmentShader); + } + return options.programCache[cacheKey]; + }, + + getFilterWindow: function() { + var scale = this.tempScale; + return Math.ceil(this.lanczosLobes / scale); + }, + + getTaps: function() { + var lobeFunction = this.lanczosCreate(this.lanczosLobes), scale = this.tempScale, + filterWindow = this.getFilterWindow(), taps = new Array(filterWindow); + for (var i = 1; i <= filterWindow; i++) { + taps[i - 1] = lobeFunction(i * scale); + } + return taps; + }, + + /** + * Generate vertex and shader sources from the necessary steps numbers + * @param {Number} filterWindow + */ + generateShader: function(filterWindow) { + var offsets = new Array(filterWindow), + fragmentShader = this.fragmentSourceTOP, filterWindow; + + for (var i = 1; i <= filterWindow; i++) { + offsets[i - 1] = i + '.0 * uDelta'; + } + + fragmentShader += 'uniform float uTaps[' + filterWindow + '];\n'; + fragmentShader += 'void main() {\n'; + fragmentShader += ' vec4 color = texture2D(uTexture, vTexCoord);\n'; + fragmentShader += ' float sum = 1.0;\n'; + + offsets.forEach(function(offset, i) { + fragmentShader += ' color += texture2D(uTexture, vTexCoord + ' + offset + ') * uTaps[' + i + '];\n'; + fragmentShader += ' color += texture2D(uTexture, vTexCoord - ' + offset + ') * uTaps[' + i + '];\n'; + fragmentShader += ' sum += 2.0 * uTaps[' + i + '];\n'; + }); + fragmentShader += ' gl_FragColor = color / sum;\n'; + fragmentShader += '}'; + return fragmentShader; + }, + + fragmentSourceTOP: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec2 uDelta;\n' + + 'varying vec2 vTexCoord;\n', + + /** + * Apply the resize filter to the image + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + if (options.webgl) { + options.passes++; + this.width = options.sourceWidth; + this.horizontal = true; + this.dW = Math.round(this.width * this.scaleX); + this.dH = options.sourceHeight; + this.tempScale = this.dW / this.width; + this.taps = this.getTaps(); + options.destinationWidth = this.dW; + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + options.sourceWidth = options.destinationWidth; + + this.height = options.sourceHeight; + this.horizontal = false; + this.dH = Math.round(this.height * this.scaleY); + this.tempScale = this.dH / this.height; + this.taps = this.getTaps(); + options.destinationHeight = this.dH; + this._setupFrameBuffer(options); + this.applyToWebGL(options); + this._swapTextures(options); + options.sourceHeight = options.destinationHeight; + } + else { + this.applyTo2d(options); + } + }, + + isNeutralState: function() { + return this.scaleX === 1 && this.scaleY === 1; + }, + + lanczosCreate: function(lobes) { + return function(x) { + if (x >= lobes || x <= -lobes) { + return 0.0; + } + if (x < 1.19209290E-07 && x > -1.19209290E-07) { + return 1.0; + } + x *= Math.PI; + var xx = x / lobes; + return (sin(x) / x) * sin(xx) / xx; + }; + }, + + /** + * Applies filter to canvas element + * @memberOf fabric.Image.filters.Resize.prototype + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} scaleX + * @param {Number} scaleY + */ + applyTo2d: function(options) { + var imageData = options.imageData, + scaleX = this.scaleX, + scaleY = this.scaleY; + + this.rcpScaleX = 1 / scaleX; + this.rcpScaleY = 1 / scaleY; + + var oW = imageData.width, oH = imageData.height, + dW = round(oW * scaleX), dH = round(oH * scaleY), + newData; + + if (this.resizeType === 'sliceHack') { + newData = this.sliceByTwo(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'hermite') { + newData = this.hermiteFastResize(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'bilinear') { + newData = this.bilinearFiltering(options, oW, oH, dW, dH); + } + else if (this.resizeType === 'lanczos') { + newData = this.lanczosResize(options, oW, oH, dW, dH); + } + options.imageData = newData; + }, + + /** + * Filter sliceByTwo + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + sliceByTwo: function(options, oW, oH, dW, dH) { + var imageData = options.imageData, + mult = 0.5, doneW = false, doneH = false, stepW = oW * mult, + stepH = oH * mult, resources = fabric.filterBackend.resources, + tmpCanvas, ctx, sX = 0, sY = 0, dX = oW, dY = 0; + if (!resources.sliceByTwo) { + resources.sliceByTwo = document.createElement('canvas'); + } + tmpCanvas = resources.sliceByTwo; + if (tmpCanvas.width < oW * 1.5 || tmpCanvas.height < oH) { + tmpCanvas.width = oW * 1.5; + tmpCanvas.height = oH; + } + ctx = tmpCanvas.getContext('2d'); + ctx.clearRect(0, 0, oW * 1.5, oH); + ctx.putImageData(imageData, 0, 0); + + dW = floor(dW); + dH = floor(dH); + + while (!doneW || !doneH) { + oW = stepW; + oH = stepH; + if (dW < floor(stepW * mult)) { + stepW = floor(stepW * mult); + } + else { + stepW = dW; + doneW = true; + } + if (dH < floor(stepH * mult)) { + stepH = floor(stepH * mult); + } + else { + stepH = dH; + doneH = true; + } + ctx.drawImage(tmpCanvas, sX, sY, oW, oH, dX, dY, stepW, stepH); + sX = dX; + sY = dY; + dY += stepH; + } + return ctx.getImageData(sX, sY, dW, dH); + }, + + /** + * Filter lanczosResize + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + lanczosResize: function(options, oW, oH, dW, dH) { + + function process(u) { + var v, i, weight, idx, a, red, green, + blue, alpha, fX, fY; + center.x = (u + 0.5) * ratioX; + icenter.x = floor(center.x); + for (v = 0; v < dH; v++) { + center.y = (v + 0.5) * ratioY; + icenter.y = floor(center.y); + a = 0; red = 0; green = 0; blue = 0; alpha = 0; + for (i = icenter.x - range2X; i <= icenter.x + range2X; i++) { + if (i < 0 || i >= oW) { + continue; + } + fX = floor(1000 * abs(i - center.x)); + if (!cacheLanc[fX]) { + cacheLanc[fX] = { }; + } + for (var j = icenter.y - range2Y; j <= icenter.y + range2Y; j++) { + if (j < 0 || j >= oH) { + continue; + } + fY = floor(1000 * abs(j - center.y)); + if (!cacheLanc[fX][fY]) { + cacheLanc[fX][fY] = lanczos(sqrt(pow(fX * rcpRatioX, 2) + pow(fY * rcpRatioY, 2)) / 1000); + } + weight = cacheLanc[fX][fY]; + if (weight > 0) { + idx = (j * oW + i) * 4; + a += weight; + red += weight * srcData[idx]; + green += weight * srcData[idx + 1]; + blue += weight * srcData[idx + 2]; + alpha += weight * srcData[idx + 3]; + } + } + } + idx = (v * dW + u) * 4; + destData[idx] = red / a; + destData[idx + 1] = green / a; + destData[idx + 2] = blue / a; + destData[idx + 3] = alpha / a; + } + + if (++u < dW) { + return process(u); + } + else { + return destImg; + } + } + + var srcData = options.imageData.data, + destImg = options.ctx.createImageData(dW, dH), + destData = destImg.data, + lanczos = this.lanczosCreate(this.lanczosLobes), + ratioX = this.rcpScaleX, ratioY = this.rcpScaleY, + rcpRatioX = 2 / this.rcpScaleX, rcpRatioY = 2 / this.rcpScaleY, + range2X = ceil(ratioX * this.lanczosLobes / 2), + range2Y = ceil(ratioY * this.lanczosLobes / 2), + cacheLanc = { }, center = { }, icenter = { }; + + return process(0); + }, + + /** + * bilinearFiltering + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + bilinearFiltering: function(options, oW, oH, dW, dH) { + var a, b, c, d, x, y, i, j, xDiff, yDiff, chnl, + color, offset = 0, origPix, ratioX = this.rcpScaleX, + ratioY = this.rcpScaleY, + w4 = 4 * (oW - 1), img = options.imageData, + pixels = img.data, destImage = options.ctx.createImageData(dW, dH), + destPixels = destImage.data; + for (i = 0; i < dH; i++) { + for (j = 0; j < dW; j++) { + x = floor(ratioX * j); + y = floor(ratioY * i); + xDiff = ratioX * j - x; + yDiff = ratioY * i - y; + origPix = 4 * (y * oW + x); + + for (chnl = 0; chnl < 4; chnl++) { + a = pixels[origPix + chnl]; + b = pixels[origPix + 4 + chnl]; + c = pixels[origPix + w4 + chnl]; + d = pixels[origPix + w4 + 4 + chnl]; + color = a * (1 - xDiff) * (1 - yDiff) + b * xDiff * (1 - yDiff) + + c * yDiff * (1 - xDiff) + d * xDiff * yDiff; + destPixels[offset++] = color; + } + } + } + return destImage; + }, + + /** + * hermiteFastResize + * @param {Object} canvasEl Canvas element to apply filter to + * @param {Number} oW Original Width + * @param {Number} oH Original Height + * @param {Number} dW Destination Width + * @param {Number} dH Destination Height + * @returns {ImageData} + */ + hermiteFastResize: function(options, oW, oH, dW, dH) { + var ratioW = this.rcpScaleX, ratioH = this.rcpScaleY, + ratioWHalf = ceil(ratioW / 2), + ratioHHalf = ceil(ratioH / 2), + img = options.imageData, data = img.data, + img2 = options.ctx.createImageData(dW, dH), data2 = img2.data; + for (var j = 0; j < dH; j++) { + for (var i = 0; i < dW; i++) { + var x2 = (i + j * dW) * 4, weight = 0, weights = 0, weightsAlpha = 0, + gxR = 0, gxG = 0, gxB = 0, gxA = 0, centerY = (j + 0.5) * ratioH; + for (var yy = floor(j * ratioH); yy < (j + 1) * ratioH; yy++) { + var dy = abs(centerY - (yy + 0.5)) / ratioHHalf, + centerX = (i + 0.5) * ratioW, w0 = dy * dy; + for (var xx = floor(i * ratioW); xx < (i + 1) * ratioW; xx++) { + var dx = abs(centerX - (xx + 0.5)) / ratioWHalf, + w = sqrt(w0 + dx * dx); + /* eslint-disable max-depth */ + if (w > 1 && w < -1) { + continue; + } + //hermite filter + weight = 2 * w * w * w - 3 * w * w + 1; + if (weight > 0) { + dx = 4 * (xx + yy * oW); + //alpha + gxA += weight * data[dx + 3]; + weightsAlpha += weight; + //colors + if (data[dx + 3] < 255) { + weight = weight * data[dx + 3] / 250; + } + gxR += weight * data[dx]; + gxG += weight * data[dx + 1]; + gxB += weight * data[dx + 2]; + weights += weight; + } + /* eslint-enable max-depth */ + } + } + data2[x2] = gxR / weights; + data2[x2 + 1] = gxG / weights; + data2[x2 + 2] = gxB / weights; + data2[x2 + 3] = gxA / weightsAlpha; + } + } + return img2; + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return { + type: this.type, + scaleX: this.scaleX, + scaleY: this.scaleY, + resizeType: this.resizeType, + lanczosLobes: this.lanczosLobes + }; + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Resize} Instance of fabric.Image.filters.Resize + */ + fabric.Image.filters.Resize.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Contrast filter class + * @class fabric.Image.filters.Contrast + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Contrast#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Contrast({ + * contrast: 0.25 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Contrast = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Contrast.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Contrast', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uContrast;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float contrastF = 1.015 * (uContrast + 1.0) / (1.0 * (1.015 - uContrast));\n' + + 'color.rgb = contrastF * (color.rgb - 0.5) + 0.5;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * contrast value, range from -1 to 1. + * @param {Number} contrast + * @default 0 + */ + contrast: 0, + + mainParameter: 'contrast', + + /** + * Constructor + * @memberOf fabric.Image.filters.Contrast.prototype + * @param {Object} [options] Options object + * @param {Number} [options.contrast=0] Value to contrast the image up (-1...1) + */ + + /** + * Apply the Contrast operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + if (this.contrast === 0) { + return; + } + var imageData = options.imageData, i, len, + data = imageData.data, len = data.length, + contrast = Math.floor(this.contrast * 255), + contrastF = 259 * (contrast + 255) / (255 * (259 - contrast)); + + for (i = 0; i < len; i += 4) { + data[i] = contrastF * (data[i] - 128) + 128; + data[i + 1] = contrastF * (data[i + 1] - 128) + 128; + data[i + 2] = contrastF * (data[i + 2] - 128) + 128; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uContrast: gl.getUniformLocation(program, 'uContrast'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uContrast, this.contrast); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Contrast} Instance of fabric.Image.filters.Contrast + */ + fabric.Image.filters.Contrast.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Saturate filter class + * @class fabric.Image.filters.Saturation + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Saturation#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Saturation({ + * saturation: 1 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Saturation = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Saturation.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Saturation', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uSaturation;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float rgMax = max(color.r, color.g);\n' + + 'float rgbMax = max(rgMax, color.b);\n' + + 'color.r += rgbMax != color.r ? (rgbMax - color.r) * uSaturation : 0.00;\n' + + 'color.g += rgbMax != color.g ? (rgbMax - color.g) * uSaturation : 0.00;\n' + + 'color.b += rgbMax != color.b ? (rgbMax - color.b) * uSaturation : 0.00;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Saturation value, from -1 to 1. + * Increases/decreases the color saturation. + * A value of 0 has no effect. + * + * @param {Number} saturation + * @default + */ + saturation: 0, + + mainParameter: 'saturation', + + /** + * Constructor + * @memberOf fabric.Image.filters.Saturate.prototype + * @param {Object} [options] Options object + * @param {Number} [options.saturate=0] Value to saturate the image (-1...1) + */ + + /** + * Apply the Saturation operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.saturation === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, len = data.length, + adjust = -this.saturation, i, max; + + for (i = 0; i < len; i += 4) { + max = Math.max(data[i], data[i + 1], data[i + 2]); + data[i] += max !== data[i] ? (max - data[i]) * adjust : 0; + data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * adjust : 0; + data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * adjust : 0; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uSaturation: gl.getUniformLocation(program, 'uSaturation'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uSaturation, -this.saturation); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Saturation} Instance of fabric.Image.filters.Saturate + */ + fabric.Image.filters.Saturation.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Vibrance filter class + * @class fabric.Image.filters.Vibrance + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Vibrance#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Vibrance({ + * vibrance: 1 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Vibrance = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Vibrance.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Vibrance', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform float uVibrance;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'float max = max(color.r, max(color.g, color.b));\n' + + 'float avg = (color.r + color.g + color.b) / 3.0;\n' + + 'float amt = (abs(max - avg) * 2.0) * uVibrance;\n' + + 'color.r += max != color.r ? (max - color.r) * amt : 0.00;\n' + + 'color.g += max != color.g ? (max - color.g) * amt : 0.00;\n' + + 'color.b += max != color.b ? (max - color.b) * amt : 0.00;\n' + + 'gl_FragColor = color;\n' + + '}', + + /** + * Vibrance value, from -1 to 1. + * Increases/decreases the saturation of more muted colors with less effect on saturated colors. + * A value of 0 has no effect. + * + * @param {Number} vibrance + * @default + */ + vibrance: 0, + + mainParameter: 'vibrance', + + /** + * Constructor + * @memberOf fabric.Image.filters.Vibrance.prototype + * @param {Object} [options] Options object + * @param {Number} [options.vibrance=0] Vibrance value for the image (between -1 and 1) + */ + + /** + * Apply the Vibrance operation to a Uint8ClampedArray representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8ClampedArray to be filtered. + */ + applyTo2d: function(options) { + if (this.vibrance === 0) { + return; + } + var imageData = options.imageData, + data = imageData.data, len = data.length, + adjust = -this.vibrance, i, max, avg, amt; + + for (i = 0; i < len; i += 4) { + max = Math.max(data[i], data[i + 1], data[i + 2]); + avg = (data[i] + data[i + 1] + data[i + 2]) / 3; + amt = ((Math.abs(max - avg) * 2 / 255) * adjust); + data[i] += max !== data[i] ? (max - data[i]) * amt : 0; + data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * amt : 0; + data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * amt : 0; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uVibrance: gl.getUniformLocation(program, 'uVibrance'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform1f(uniformLocations.uVibrance, -this.vibrance); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Vibrance} Instance of fabric.Image.filters.Vibrance + */ + fabric.Image.filters.Vibrance.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Blur filter class + * @class fabric.Image.filters.Blur + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Blur#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Blur({ + * blur: 0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + * canvas.renderAll(); + */ + filters.Blur = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Blur.prototype */ { + + type: 'Blur', + + /* +'gl_FragColor = vec4(0.0);', +'gl_FragColor += texture2D(texture, vTexCoord + -7 * uDelta)*0.0044299121055113265;', +'gl_FragColor += texture2D(texture, vTexCoord + -6 * uDelta)*0.00895781211794;', +'gl_FragColor += texture2D(texture, vTexCoord + -5 * uDelta)*0.0215963866053;', +'gl_FragColor += texture2D(texture, vTexCoord + -4 * uDelta)*0.0443683338718;', +'gl_FragColor += texture2D(texture, vTexCoord + -3 * uDelta)*0.0776744219933;', +'gl_FragColor += texture2D(texture, vTexCoord + -2 * uDelta)*0.115876621105;', +'gl_FragColor += texture2D(texture, vTexCoord + -1 * uDelta)*0.147308056121;', +'gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;', +'gl_FragColor += texture2D(texture, vTexCoord + 1 * uDelta)*0.147308056121;', +'gl_FragColor += texture2D(texture, vTexCoord + 2 * uDelta)*0.115876621105;', +'gl_FragColor += texture2D(texture, vTexCoord + 3 * uDelta)*0.0776744219933;', +'gl_FragColor += texture2D(texture, vTexCoord + 4 * uDelta)*0.0443683338718;', +'gl_FragColor += texture2D(texture, vTexCoord + 5 * uDelta)*0.0215963866053;', +'gl_FragColor += texture2D(texture, vTexCoord + 6 * uDelta)*0.00895781211794;', +'gl_FragColor += texture2D(texture, vTexCoord + 7 * uDelta)*0.0044299121055113265;', +*/ + + /* eslint-disable max-len */ + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec2 uDelta;\n' + + 'varying vec2 vTexCoord;\n' + + 'const float nSamples = 15.0;\n' + + 'vec3 v3offset = vec3(12.9898, 78.233, 151.7182);\n' + + 'float random(vec3 scale) {\n' + + /* use the fragment position for a different seed per-pixel */ + 'return fract(sin(dot(gl_FragCoord.xyz, scale)) * 43758.5453);\n' + + '}\n' + + 'void main() {\n' + + 'vec4 color = vec4(0.0);\n' + + 'float total = 0.0;\n' + + 'float offset = random(v3offset);\n' + + 'for (float t = -nSamples; t <= nSamples; t++) {\n' + + 'float percent = (t + offset - 0.5) / nSamples;\n' + + 'float weight = 1.0 - abs(percent);\n' + + 'color += texture2D(uTexture, vTexCoord + uDelta * percent) * weight;\n' + + 'total += weight;\n' + + '}\n' + + 'gl_FragColor = color / total;\n' + + '}', + /* eslint-enable max-len */ + + /** + * blur value, in percentage of image dimensions. + * specific to keep the image blur constant at different resolutions + * range between 0 and 1. + */ + blur: 0, + + mainParameter: 'blur', + + applyTo: function(options) { + if (options.webgl) { + // this aspectRatio is used to give the same blur to vertical and horizontal + this.aspectRatio = options.sourceWidth / options.sourceHeight; + options.passes++; + this._setupFrameBuffer(options); + this.horizontal = true; + this.applyToWebGL(options); + this._swapTextures(options); + this._setupFrameBuffer(options); + this.horizontal = false; + this.applyToWebGL(options); + this._swapTextures(options); + } + else { + this.applyTo2d(options); + } + }, + + applyTo2d: function(options) { + // paint canvasEl with current image data. + //options.ctx.putImageData(options.imageData, 0, 0); + options.imageData = this.simpleBlur(options); + }, + + simpleBlur: function(options) { + var resources = options.filterBackend.resources, canvas1, canvas2, + width = options.imageData.width, + height = options.imageData.height; + + if (!resources.blurLayer1) { + resources.blurLayer1 = fabric.util.createCanvasElement(); + resources.blurLayer2 = fabric.util.createCanvasElement(); + } + canvas1 = resources.blurLayer1; + canvas2 = resources.blurLayer2; + if (canvas1.width !== width || canvas1.height !== height) { + canvas2.width = canvas1.width = width; + canvas2.height = canvas1.height = height; + } + var ctx1 = canvas1.getContext('2d'), + ctx2 = canvas2.getContext('2d'), + nSamples = 15, + random, percent, j, i, + blur = this.blur * 0.06 * 0.5; + + // load first canvas + ctx1.putImageData(options.imageData, 0, 0); + ctx2.clearRect(0, 0, width, height); + + for (i = -nSamples; i <= nSamples; i++) { + random = (Math.random() - 0.5) / 4; + percent = i / nSamples; + j = blur * percent * width + random; + ctx2.globalAlpha = 1 - Math.abs(percent); + ctx2.drawImage(canvas1, j, random); + ctx1.drawImage(canvas2, 0, 0); + ctx2.globalAlpha = 1; + ctx2.clearRect(0, 0, canvas2.width, canvas2.height); + } + for (i = -nSamples; i <= nSamples; i++) { + random = (Math.random() - 0.5) / 4; + percent = i / nSamples; + j = blur * percent * height + random; + ctx2.globalAlpha = 1 - Math.abs(percent); + ctx2.drawImage(canvas1, random, j); + ctx1.drawImage(canvas2, 0, 0); + ctx2.globalAlpha = 1; + ctx2.clearRect(0, 0, canvas2.width, canvas2.height); + } + options.ctx.drawImage(canvas1, 0, 0); + var newImageData = options.ctx.getImageData(0, 0, canvas1.width, canvas1.height); + ctx1.globalAlpha = 1; + ctx1.clearRect(0, 0, canvas1.width, canvas1.height); + return newImageData; + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + delta: gl.getUniformLocation(program, 'uDelta'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + var delta = this.chooseRightDelta(); + gl.uniform2fv(uniformLocations.delta, delta); + }, + + /** + * choose right value of image percentage to blur with + * @returns {Array} a numeric array with delta values + */ + chooseRightDelta: function() { + var blurScale = 1, delta = [0, 0], blur; + if (this.horizontal) { + if (this.aspectRatio > 1) { + // image is wide, i want to shrink radius horizontal + blurScale = 1 / this.aspectRatio; + } + } + else { + if (this.aspectRatio < 1) { + // image is tall, i want to shrink radius vertical + blurScale = this.aspectRatio; + } + } + blur = blurScale * this.blur * 0.12; + if (this.horizontal) { + delta[0] = blur; + } + else { + delta[1] = blur; + } + return delta; + }, + }); + + /** + * Deserialize a JSON definition of a BlurFilter into a concrete instance. + */ + filters.Blur.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * Gamma filter class + * @class fabric.Image.filters.Gamma + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.Gamma#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.Gamma({ + * gamma: [1, 0.5, 2.1] + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.Gamma = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Gamma.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Gamma', + + fragmentSource: 'precision highp float;\n' + + 'uniform sampler2D uTexture;\n' + + 'uniform vec3 uGamma;\n' + + 'varying vec2 vTexCoord;\n' + + 'void main() {\n' + + 'vec4 color = texture2D(uTexture, vTexCoord);\n' + + 'vec3 correction = (1.0 / uGamma);\n' + + 'color.r = pow(color.r, correction.r);\n' + + 'color.g = pow(color.g, correction.g);\n' + + 'color.b = pow(color.b, correction.b);\n' + + 'gl_FragColor = color;\n' + + 'gl_FragColor.rgb *= color.a;\n' + + '}', + + /** + * Gamma array value, from 0.01 to 2.2. + * @param {Array} gamma + * @default + */ + gamma: [1, 1, 1], + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'gamma', + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.gamma = [1, 1, 1]; + filters.BaseFilter.prototype.initialize.call(this, options); + }, + + /** + * Apply the Gamma operation to a Uint8Array representing the pixels of an image. + * + * @param {Object} options + * @param {ImageData} options.imageData The Uint8Array to be filtered. + */ + applyTo2d: function(options) { + var imageData = options.imageData, data = imageData.data, + gamma = this.gamma, len = data.length, + rInv = 1 / gamma[0], gInv = 1 / gamma[1], + bInv = 1 / gamma[2], i; + + if (!this.rVals) { + // eslint-disable-next-line + this.rVals = new Uint8Array(256); + // eslint-disable-next-line + this.gVals = new Uint8Array(256); + // eslint-disable-next-line + this.bVals = new Uint8Array(256); + } + + // This is an optimization - pre-compute a look-up table for each color channel + // instead of performing these pow calls for each pixel in the image. + for (i = 0, len = 256; i < len; i++) { + this.rVals[i] = Math.pow(i / 255, rInv) * 255; + this.gVals[i] = Math.pow(i / 255, gInv) * 255; + this.bVals[i] = Math.pow(i / 255, bInv) * 255; + } + for (i = 0, len = data.length; i < len; i += 4) { + data[i] = this.rVals[data[i]]; + data[i + 1] = this.gVals[data[i + 1]]; + data[i + 2] = this.bVals[data[i + 2]]; + } + }, + + /** + * Return WebGL uniform locations for this filter's shader. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {WebGLShaderProgram} program This filter's compiled shader program. + */ + getUniformLocations: function(gl, program) { + return { + uGamma: gl.getUniformLocation(program, 'uGamma'), + }; + }, + + /** + * Send data from this filter to its shader program's uniforms. + * + * @param {WebGLRenderingContext} gl The GL canvas context used to compile this filter's shader. + * @param {Object} uniformLocations A map of string uniform names to WebGLUniformLocation objects + */ + sendUniformData: function(gl, uniformLocations) { + gl.uniform3fv(uniformLocations.uGamma, this.gamma); + }, + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.Gamma} Instance of fabric.Image.filters.Gamma + */ + fabric.Image.filters.Gamma.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * A container class that knows how to apply a sequence of filters to an input image. + */ + filters.Composed = createClass(filters.BaseFilter, /** @lends fabric.Image.filters.Composed.prototype */ { + + type: 'Composed', + + /** + * A non sparse array of filters to apply + */ + subFilters: [], + + /** + * Constructor + * @param {Object} [options] Options object + */ + initialize: function(options) { + this.callSuper('initialize', options); + // create a new array instead mutating the prototype with push + this.subFilters = this.subFilters.slice(0); + }, + + /** + * Apply this container's filters to the input image provided. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be applied. + */ + applyTo: function(options) { + options.passes += this.subFilters.length - 1; + this.subFilters.forEach(function(filter) { + filter.applyTo(options); + }); + }, + + /** + * Serialize this filter into JSON. + * + * @returns {Object} A JSON representation of this filter. + */ + toObject: function() { + return fabric.util.object.extend(this.callSuper('toObject'), { + subFilters: this.subFilters.map(function(filter) { return filter.toObject(); }), + }); + }, + + isNeutralState: function() { + return !this.subFilters.some(function(filter) { return !filter.isNeutralState(); }); + } + }); + + /** + * Deserialize a JSON definition of a ComposedFilter into a concrete instance. + */ + fabric.Image.filters.Composed.fromObject = function(object, callback) { + var filters = object.subFilters || [], + subFilters = filters.map(function(filter) { + return new fabric.Image.filters[filter.type](filter); + }), + instance = new fabric.Image.filters.Composed({ subFilters: subFilters }); + callback && callback(instance); + return instance; + }; +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + filters = fabric.Image.filters, + createClass = fabric.util.createClass; + + /** + * HueRotation filter class + * @class fabric.Image.filters.HueRotation + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + * @see {@link fabric.Image.filters.HueRotation#initialize} for constructor definition + * @see {@link http://fabricjs.com/image-filters|ImageFilters demo} + * @example + * var filter = new fabric.Image.filters.HueRotation({ + * rotation: -0.5 + * }); + * object.filters.push(filter); + * object.applyFilters(); + */ + filters.HueRotation = createClass(filters.ColorMatrix, /** @lends fabric.Image.filters.HueRotation.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'HueRotation', + + /** + * HueRotation value, from -1 to 1. + * the unit is radians + * @param {Number} myParameter + * @default + */ + rotation: 0, + + /** + * Describe the property that is the filter parameter + * @param {String} m + * @default + */ + mainParameter: 'rotation', + + calculateMatrix: function() { + var rad = this.rotation * Math.PI, cos = fabric.util.cos(rad), sin = fabric.util.sin(rad), + aThird = 1 / 3, aThirdSqtSin = Math.sqrt(aThird) * sin, OneMinusCos = 1 - cos; + this.matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + this.matrix[0] = cos + OneMinusCos / 3; + this.matrix[1] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[2] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[5] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[6] = cos + aThird * OneMinusCos; + this.matrix[7] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[10] = aThird * OneMinusCos - aThirdSqtSin; + this.matrix[11] = aThird * OneMinusCos + aThirdSqtSin; + this.matrix[12] = cos + aThird * OneMinusCos; + }, + + /** + * HueRotation isNeutralState implementation + * Used only in image applyFilters to discard filters that will not have an effect + * on the image + * @param {Object} options + **/ + isNeutralState: function(options) { + this.calculateMatrix(); + return filters.BaseFilter.prototype.isNeutralState.call(this, options); + }, + + /** + * Apply this filter to the input image data provided. + * + * Determines whether to use WebGL or Canvas2D based on the options.webgl flag. + * + * @param {Object} options + * @param {Number} options.passes The number of filters remaining to be executed + * @param {Boolean} options.webgl Whether to use webgl to render the filter. + * @param {WebGLTexture} options.sourceTexture The texture setup as the source to be filtered. + * @param {WebGLTexture} options.targetTexture The texture where filtered output should be drawn. + * @param {WebGLRenderingContext} options.context The GL context used for rendering. + * @param {Object} options.programCache A map of compiled shader programs, keyed by filter type. + */ + applyTo: function(options) { + this.calculateMatrix(); + filters.BaseFilter.prototype.applyTo.call(this, options); + }, + + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {function} [callback] to be invoked after filter creation + * @return {fabric.Image.filters.HueRotation} Instance of fabric.Image.filters.HueRotation + */ + fabric.Image.filters.HueRotation.fromObject = fabric.Image.filters.BaseFilter.fromObject; + +})(typeof exports !== 'undefined' ? exports : this); + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = { }), + clone = fabric.util.object.clone; + + if (fabric.Text) { + fabric.warn('fabric.Text is already defined'); + return; + } + + var additionalProps = + ('fontFamily fontWeight fontSize text underline overline linethrough' + + ' textAlign fontStyle lineHeight textBackgroundColor charSpacing styles' + + ' direction path pathStartOffset pathSide').split(' '); + + /** + * Text class + * @class fabric.Text + * @extends fabric.Object + * @return {fabric.Text} thisArg + * @tutorial {@link http://fabricjs.com/fabric-intro-part-2#text} + * @see {@link fabric.Text#initialize} for constructor definition + */ + fabric.Text = fabric.util.createClass(fabric.Object, /** @lends fabric.Text.prototype */ { + + /** + * Properties which when set cause object to change dimensions + * @type Array + * @private + */ + _dimensionAffectingProps: [ + 'fontSize', + 'fontWeight', + 'fontFamily', + 'fontStyle', + 'lineHeight', + 'text', + 'charSpacing', + 'textAlign', + 'styles', + 'path', + 'pathStartOffset', + 'pathSide' + ], + + /** + * @private + */ + _reNewline: /\r?\n/, + + /** + * Use this regular expression to filter for whitespaces that is not a new line. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reSpacesAndTabs: /[ \t\r]/g, + + /** + * Use this regular expression to filter for whitespace that is not a new line. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reSpaceAndTab: /[ \t\r]/, + + /** + * Use this regular expression to filter consecutive groups of non spaces. + * Mostly used when text is 'justify' aligned. + * @private + */ + _reWords: /\S+/g, + + /** + * Type of an object + * @type String + * @default + */ + type: 'text', + + /** + * Font size (in pixels) + * @type Number + * @default + */ + fontSize: 40, + + /** + * Font weight (e.g. bold, normal, 400, 600, 800) + * @type {(Number|String)} + * @default + */ + fontWeight: 'normal', + + /** + * Font family + * @type String + * @default + */ + fontFamily: 'Times New Roman', + + /** + * Text decoration underline. + * @type Boolean + * @default + */ + underline: false, + + /** + * Text decoration overline. + * @type Boolean + * @default + */ + overline: false, + + /** + * Text decoration linethrough. + * @type Boolean + * @default + */ + linethrough: false, + + /** + * Text alignment. Possible values: "left", "center", "right", "justify", + * "justify-left", "justify-center" or "justify-right". + * @type String + * @default + */ + textAlign: 'left', + + /** + * Font style . Possible values: "", "normal", "italic" or "oblique". + * @type String + * @default + */ + fontStyle: 'normal', + + /** + * Line height + * @type Number + * @default + */ + lineHeight: 1.16, + + /** + * Superscript schema object (minimum overlap) + * @type {Object} + * @default + */ + superscript: { + size: 0.60, // fontSize factor + baseline: -0.35 // baseline-shift factor (upwards) + }, + + /** + * Subscript schema object (minimum overlap) + * @type {Object} + * @default + */ + subscript: { + size: 0.60, // fontSize factor + baseline: 0.11 // baseline-shift factor (downwards) + }, + + /** + * Background color of text lines + * @type String + * @default + */ + textBackgroundColor: '', + + /** + * List of properties to consider when checking if + * state of an object is changed ({@link fabric.Object#hasStateChanged}) + * as well as for history (undo/redo) purposes + * @type Array + */ + stateProperties: fabric.Object.prototype.stateProperties.concat(additionalProps), + + /** + * List of properties to consider when checking if cache needs refresh + * @type Array + */ + cacheProperties: fabric.Object.prototype.cacheProperties.concat(additionalProps), + + /** + * When defined, an object is rendered via stroke and this property specifies its color. + * Backwards incompatibility note: This property was named "strokeStyle" until v1.1.6 + * @type String + * @default + */ + stroke: null, + + /** + * Shadow object representing shadow of this shape. + * Backwards incompatibility note: This property was named "textShadow" (String) until v1.2.11 + * @type fabric.Shadow + * @default + */ + shadow: null, + + /** + * fabric.Path that the text should follow. + * since 4.6.0 the path will be drawn automatically. + * if you want to make the path visible, give it a stroke and strokeWidth or fill value + * if you want it to be hidden, assign visible = false to the path. + * This feature is in BETA, and SVG import/export is not yet supported. + * @type fabric.Path + * @example + * var textPath = new fabric.Text('Text on a path', { + * top: 150, + * left: 150, + * textAlign: 'center', + * charSpacing: -50, + * path: new fabric.Path('M 0 0 C 50 -100 150 -100 200 0', { + * strokeWidth: 1, + * visible: false + * }), + * pathSide: 'left', + * pathStartOffset: 0 + * }); + * @default + */ + path: null, + + /** + * Offset amount for text path starting position + * Only used when text has a path + * @type Number + * @default + */ + pathStartOffset: 0, + + /** + * Which side of the path the text should be drawn on. + * Only used when text has a path + * @type {String} 'left|right' + * @default + */ + pathSide: 'left', + + /** + * @private + */ + _fontSizeFraction: 0.222, + + /** + * @private + */ + offsets: { + underline: 0.10, + linethrough: -0.315, + overline: -0.88 + }, + + /** + * Text Line proportion to font Size (in pixels) + * @type Number + * @default + */ + _fontSizeMult: 1.13, + + /** + * additional space between characters + * expressed in thousands of em unit + * @type Number + * @default + */ + charSpacing: 0, + + /** + * Object containing character styles - top-level properties -> line numbers, + * 2nd-level properties - character numbers + * @type Object + * @default + */ + styles: null, + + /** + * Reference to a context to measure text char or couple of chars + * the cacheContext of the canvas will be used or a freshly created one if the object is not on canvas + * once created it will be referenced on fabric._measuringContext to avoid creating a canvas for every + * text object created. + * @type {CanvasRenderingContext2D} + * @default + */ + _measuringContext: null, + + /** + * Baseline shift, styles only, keep at 0 for the main text object + * @type {Number} + * @default + */ + deltaY: 0, + + /** + * WARNING: EXPERIMENTAL. NOT SUPPORTED YET + * determine the direction of the text. + * This has to be set manually together with textAlign and originX for proper + * experience. + * some interesting link for the future + * https://www.w3.org/International/questions/qa-bidi-unicode-controls + * @since 4.5.0 + * @type {String} 'ltr|rtl' + * @default + */ + direction: 'ltr', + + /** + * Array of properties that define a style unit (of 'styles'). + * @type {Array} + * @default + */ + _styleProperties: [ + 'stroke', + 'strokeWidth', + 'fill', + 'fontFamily', + 'fontSize', + 'fontWeight', + 'fontStyle', + 'underline', + 'overline', + 'linethrough', + 'deltaY', + 'textBackgroundColor', + ], + + /** + * contains characters bounding boxes + */ + __charBounds: [], + + /** + * use this size when measuring text. To avoid IE11 rounding errors + * @type {Number} + * @default + * @readonly + * @private + */ + CACHE_FONT_SIZE: 400, + + /** + * contains the min text width to avoid getting 0 + * @type {Number} + * @default + */ + MIN_TEXT_WIDTH: 2, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + initialize: function(text, options) { + this.styles = options ? (options.styles || { }) : { }; + this.text = text; + this.__skipDimension = true; + this.callSuper('initialize', options); + if (this.path) { + this.setPathInfo(); + } + this.__skipDimension = false; + this.initDimensions(); + this.setCoords(); + this.setupState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * If text has a path, it will add the extra information needed + * for path and text calculations + * @return {fabric.Text} thisArg + */ + setPathInfo: function() { + var path = this.path; + if (path) { + path.segmentsInfo = fabric.util.getPathSegmentsInfo(path.path); + } + }, + + /** + * Return a context for measurement of text string. + * if created it gets stored for reuse + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.Text} thisArg + */ + getMeasuringContext: function() { + // if we did not return we have to measure something. + if (!fabric._measuringContext) { + fabric._measuringContext = this.canvas && this.canvas.contextCache || + fabric.util.createCanvasElement().getContext('2d'); + } + return fabric._measuringContext; + }, + + /** + * @private + * Divides text into lines of text and lines of graphemes. + */ + _splitText: function() { + var newLines = this._splitTextIntoLines(this.text); + this.textLines = newLines.lines; + this._textLines = newLines.graphemeLines; + this._unwrappedTextLines = newLines._unwrappedLines; + this._text = newLines.graphemeText; + return newLines; + }, + + /** + * Initialize or update text dimensions. + * Updates this.width and this.height with the proper values. + * Does not return dimensions. + */ + initDimensions: function() { + if (this.__skipDimension) { + return; + } + this._splitText(); + this._clearCache(); + if (this.path) { + this.width = this.path.width; + this.height = this.path.height; + } + else { + this.width = this.calcTextWidth() || this.cursorWidth || this.MIN_TEXT_WIDTH; + this.height = this.calcTextHeight(); + } + if (this.textAlign.indexOf('justify') !== -1) { + // once text is measured we need to make space fatter to make justified text. + this.enlargeSpaces(); + } + this.saveState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * Enlarge space boxes and shift the others + */ + enlargeSpaces: function() { + var diffSpace, currentLineWidth, numberOfSpaces, accumulatedSpace, line, charBound, spaces; + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (this.textAlign !== 'justify' && (i === len - 1 || this.isEndOfWrapping(i))) { + continue; + } + accumulatedSpace = 0; + line = this._textLines[i]; + currentLineWidth = this.getLineWidth(i); + if (currentLineWidth < this.width && (spaces = this.textLines[i].match(this._reSpacesAndTabs))) { + numberOfSpaces = spaces.length; + diffSpace = (this.width - currentLineWidth) / numberOfSpaces; + for (var j = 0, jlen = line.length; j <= jlen; j++) { + charBound = this.__charBounds[i][j]; + if (this._reSpaceAndTab.test(line[j])) { + charBound.width += diffSpace; + charBound.kernedWidth += diffSpace; + charBound.left += accumulatedSpace; + accumulatedSpace += diffSpace; + } + else { + charBound.left += accumulatedSpace; + } + } + } + } + }, + + /** + * Detect if the text line is ended with an hard break + * text and itext do not have wrapping, return false + * @return {Boolean} + */ + isEndOfWrapping: function(lineIndex) { + return lineIndex === this._textLines.length - 1; + }, + + /** + * Detect if a line has a linebreak and so we need to account for it when moving + * and counting style. + * It return always for text and Itext. + * @return Number + */ + missingNewlineOffset: function() { + return 1; + }, + + /** + * Returns string representation of an instance + * @return {String} String representation of text object + */ + toString: function() { + return '#'; + }, + + /** + * Return the dimension and the zoom level needed to create a cache canvas + * big enough to host the object to be cached. + * @private + * @param {Object} dim.x width of object to be cached + * @param {Object} dim.y height of object to be cached + * @return {Object}.width width of canvas + * @return {Object}.height height of canvas + * @return {Object}.zoomX zoomX zoom value to unscale the canvas before drawing cache + * @return {Object}.zoomY zoomY zoom value to unscale the canvas before drawing cache + */ + _getCacheCanvasDimensions: function() { + var dims = this.callSuper('_getCacheCanvasDimensions'); + var fontSize = this.fontSize; + dims.width += fontSize * dims.zoomX; + dims.height += fontSize * dims.zoomY; + return dims; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + var path = this.path; + path && !path.isNotVisible() && path._render(ctx); + this._setTextStyles(ctx); + this._renderTextLinesBackground(ctx); + this._renderTextDecoration(ctx, 'underline'); + this._renderText(ctx); + this._renderTextDecoration(ctx, 'overline'); + this._renderTextDecoration(ctx, 'linethrough'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderText: function(ctx) { + if (this.paintFirst === 'stroke') { + this._renderTextStroke(ctx); + this._renderTextFill(ctx); + } + else { + this._renderTextFill(ctx); + this._renderTextStroke(ctx); + } + }, + + /** + * Set the font parameter of the context with the object properties or with charStyle + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Object} [charStyle] object with font style properties + * @param {String} [charStyle.fontFamily] Font Family + * @param {Number} [charStyle.fontSize] Font size in pixels. ( without px suffix ) + * @param {String} [charStyle.fontWeight] Font weight + * @param {String} [charStyle.fontStyle] Font style (italic|normal) + */ + _setTextStyles: function(ctx, charStyle, forMeasuring) { + ctx.textBaseline = 'alphabetic'; + ctx.font = this._getFontDeclaration(charStyle, forMeasuring); + }, + + /** + * calculate and return the text Width measuring each line. + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @return {Number} Maximum width of fabric.Text object + */ + calcTextWidth: function() { + var maxWidth = this.getLineWidth(0); + + for (var i = 1, len = this._textLines.length; i < len; i++) { + var currentLineWidth = this.getLineWidth(i); + if (currentLineWidth > maxWidth) { + maxWidth = currentLineWidth; + } + } + return maxWidth; + }, + + /** + * @private + * @param {String} method Method name ("fillText" or "strokeText") + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} line Text to render + * @param {Number} left Left position of text + * @param {Number} top Top position of text + * @param {Number} lineIndex Index of a line in a text + */ + _renderTextLine: function(method, ctx, line, left, top, lineIndex) { + this._renderChars(method, ctx, line, left, top, lineIndex); + }, + + /** + * Renders the text background for lines, taking care of style + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextLinesBackground: function(ctx) { + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor')) { + return; + } + var heightOfLine, + lineLeftOffset, originalFill = ctx.fillStyle, + line, lastColor, + leftOffset = this._getLeftOffset(), + lineTopOffset = this._getTopOffset(), + boxStart = 0, boxWidth = 0, charBox, currentColor, path = this.path, + drawStart; + + for (var i = 0, len = this._textLines.length; i < len; i++) { + heightOfLine = this.getHeightOfLine(i); + if (!this.textBackgroundColor && !this.styleHas('textBackgroundColor', i)) { + lineTopOffset += heightOfLine; + continue; + } + line = this._textLines[i]; + lineLeftOffset = this._getLineLeftOffset(i); + boxWidth = 0; + boxStart = 0; + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (path) { + ctx.save(); + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + ctx.fillStyle = currentColor; + currentColor && ctx.fillRect( + -charBox.width / 2, + -heightOfLine / this.lineHeight * (1 - this._fontSizeFraction), + charBox.width, + heightOfLine / this.lineHeight + ); + ctx.restore(); + } + else if (currentColor !== lastColor) { + drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = lastColor; + lastColor && ctx.fillRect( + drawStart, + lineTopOffset, + boxWidth, + heightOfLine / this.lineHeight + ); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } + else { + boxWidth += charBox.kernedWidth; + } + } + if (currentColor && !path) { + drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = currentColor; + ctx.fillRect( + drawStart, + lineTopOffset, + boxWidth, + heightOfLine / this.lineHeight + ); + } + lineTopOffset += heightOfLine; + } + ctx.fillStyle = originalFill; + // if there is text background color no + // other shadows should be casted + this._removeShadow(ctx); + }, + + /** + * @private + * @param {Object} decl style declaration for cache + * @param {String} decl.fontFamily fontFamily + * @param {String} decl.fontStyle fontStyle + * @param {String} decl.fontWeight fontWeight + * @return {Object} reference to cache + */ + getFontCache: function(decl) { + var fontFamily = decl.fontFamily.toLowerCase(); + if (!fabric.charWidthsCache[fontFamily]) { + fabric.charWidthsCache[fontFamily] = { }; + } + var cache = fabric.charWidthsCache[fontFamily], + cacheProp = decl.fontStyle.toLowerCase() + '_' + (decl.fontWeight + '').toLowerCase(); + if (!cache[cacheProp]) { + cache[cacheProp] = { }; + } + return cache[cacheProp]; + }, + + /** + * measure and return the width of a single character. + * possibly overridden to accommodate different measure logic or + * to hook some external lib for character measurement + * @private + * @param {String} _char, char to be measured + * @param {Object} charStyle style of char to be measured + * @param {String} [previousChar] previous char + * @param {Object} [prevCharStyle] style of previous char + */ + _measureChar: function(_char, charStyle, previousChar, prevCharStyle) { + // first i try to return from cache + var fontCache = this.getFontCache(charStyle), fontDeclaration = this._getFontDeclaration(charStyle), + previousFontDeclaration = this._getFontDeclaration(prevCharStyle), couple = previousChar + _char, + stylesAreEqual = fontDeclaration === previousFontDeclaration, width, coupleWidth, previousWidth, + fontMultiplier = charStyle.fontSize / this.CACHE_FONT_SIZE, kernedWidth; + + if (previousChar && fontCache[previousChar] !== undefined) { + previousWidth = fontCache[previousChar]; + } + if (fontCache[_char] !== undefined) { + kernedWidth = width = fontCache[_char]; + } + if (stylesAreEqual && fontCache[couple] !== undefined) { + coupleWidth = fontCache[couple]; + kernedWidth = coupleWidth - previousWidth; + } + if (width === undefined || previousWidth === undefined || coupleWidth === undefined) { + var ctx = this.getMeasuringContext(); + // send a TRUE to specify measuring font size CACHE_FONT_SIZE + this._setTextStyles(ctx, charStyle, true); + } + if (width === undefined) { + kernedWidth = width = ctx.measureText(_char).width; + fontCache[_char] = width; + } + if (previousWidth === undefined && stylesAreEqual && previousChar) { + previousWidth = ctx.measureText(previousChar).width; + fontCache[previousChar] = previousWidth; + } + if (stylesAreEqual && coupleWidth === undefined) { + // we can measure the kerning couple and subtract the width of the previous character + coupleWidth = ctx.measureText(couple).width; + fontCache[couple] = coupleWidth; + kernedWidth = coupleWidth - previousWidth; + } + return { width: width * fontMultiplier, kernedWidth: kernedWidth * fontMultiplier }; + }, + + /** + * Computes height of character at given position + * @param {Number} line the line index number + * @param {Number} _char the character index number + * @return {Number} fontSize of the character + */ + getHeightOfChar: function(line, _char) { + return this.getValueOfPropertyAt(line, _char, 'fontSize'); + }, + + /** + * measure a text line measuring all characters. + * @param {Number} lineIndex line number + * @return {Number} Line width + */ + measureLine: function(lineIndex) { + var lineInfo = this._measureLine(lineIndex); + if (this.charSpacing !== 0) { + lineInfo.width -= this._getWidthOfCharSpacing(); + } + if (lineInfo.width < 0) { + lineInfo.width = 0; + } + return lineInfo; + }, + + /** + * measure every grapheme of a line, populating __charBounds + * @param {Number} lineIndex + * @return {Object} object.width total width of characters + * @return {Object} object.widthOfSpaces length of chars that match this._reSpacesAndTabs + */ + _measureLine: function(lineIndex) { + var width = 0, i, grapheme, line = this._textLines[lineIndex], prevGrapheme, + graphemeInfo, numOfSpaces = 0, lineBounds = new Array(line.length), + positionInPath = 0, startingPoint, totalPathLength, path = this.path, + reverse = this.pathSide === 'right'; + + this.__charBounds[lineIndex] = lineBounds; + for (i = 0; i < line.length; i++) { + grapheme = line[i]; + graphemeInfo = this._getGraphemeBox(grapheme, lineIndex, i, prevGrapheme); + lineBounds[i] = graphemeInfo; + width += graphemeInfo.kernedWidth; + prevGrapheme = grapheme; + } + // this latest bound box represent the last character of the line + // to simplify cursor handling in interactive mode. + lineBounds[i] = { + left: graphemeInfo ? graphemeInfo.left + graphemeInfo.width : 0, + width: 0, + kernedWidth: 0, + height: this.fontSize + }; + if (path) { + totalPathLength = path.segmentsInfo[path.segmentsInfo.length - 1].length; + startingPoint = fabric.util.getPointOnPath(path.path, 0, path.segmentsInfo); + startingPoint.x += path.pathOffset.x; + startingPoint.y += path.pathOffset.y; + switch (this.textAlign) { + case 'left': + positionInPath = reverse ? (totalPathLength - width) : 0; + break; + case 'center': + positionInPath = (totalPathLength - width) / 2; + break; + case 'right': + positionInPath = reverse ? 0 : (totalPathLength - width); + break; + //todo - add support for justify + } + positionInPath += this.pathStartOffset * (reverse ? -1 : 1); + for (i = reverse ? line.length - 1 : 0; + reverse ? i >= 0 : i < line.length; + reverse ? i-- : i++) { + graphemeInfo = lineBounds[i]; + if (positionInPath > totalPathLength) { + positionInPath %= totalPathLength; + } + else if (positionInPath < 0) { + positionInPath += totalPathLength; + } + // it would probably much faster to send all the grapheme position for a line + // and calculate path position/angle at once. + this._setGraphemeOnPath(positionInPath, graphemeInfo, startingPoint); + positionInPath += graphemeInfo.kernedWidth; + } + } + return { width: width, numOfSpaces: numOfSpaces }; + }, + + /** + * Calculate the angle and the left,top position of the char that follow a path. + * It appends it to graphemeInfo to be reused later at rendering + * @private + * @param {Number} positionInPath to be measured + * @param {Object} graphemeInfo current grapheme box information + * @param {Object} startingPoint position of the point + */ + _setGraphemeOnPath: function(positionInPath, graphemeInfo, startingPoint) { + var centerPosition = positionInPath + graphemeInfo.kernedWidth / 2, + path = this.path; + + // we are at currentPositionOnPath. we want to know what point on the path is. + var info = fabric.util.getPointOnPath(path.path, centerPosition, path.segmentsInfo); + graphemeInfo.renderLeft = info.x - startingPoint.x; + graphemeInfo.renderTop = info.y - startingPoint.y; + graphemeInfo.angle = info.angle + (this.pathSide === 'right' ? Math.PI : 0); + }, + + /** + * Measure and return the info of a single grapheme. + * needs the the info of previous graphemes already filled + * @private + * @param {String} grapheme to be measured + * @param {Number} lineIndex index of the line where the char is + * @param {Number} charIndex position in the line + * @param {String} [prevGrapheme] character preceding the one to be measured + */ + _getGraphemeBox: function(grapheme, lineIndex, charIndex, prevGrapheme, skipLeft) { + var style = this.getCompleteStyleDeclaration(lineIndex, charIndex), + prevStyle = prevGrapheme ? this.getCompleteStyleDeclaration(lineIndex, charIndex - 1) : { }, + info = this._measureChar(grapheme, style, prevGrapheme, prevStyle), + kernedWidth = info.kernedWidth, + width = info.width, charSpacing; + + if (this.charSpacing !== 0) { + charSpacing = this._getWidthOfCharSpacing(); + width += charSpacing; + kernedWidth += charSpacing; + } + + var box = { + width: width, + left: 0, + height: style.fontSize, + kernedWidth: kernedWidth, + deltaY: style.deltaY, + }; + if (charIndex > 0 && !skipLeft) { + var previousBox = this.__charBounds[lineIndex][charIndex - 1]; + box.left = previousBox.left + previousBox.width + info.kernedWidth - info.width; + } + return box; + }, + + /** + * Calculate height of line at 'lineIndex' + * @param {Number} lineIndex index of line to calculate + * @return {Number} + */ + getHeightOfLine: function(lineIndex) { + if (this.__lineHeights[lineIndex]) { + return this.__lineHeights[lineIndex]; + } + + var line = this._textLines[lineIndex], + // char 0 is measured before the line cycle because it nneds to char + // emptylines + maxHeight = this.getHeightOfChar(lineIndex, 0); + for (var i = 1, len = line.length; i < len; i++) { + maxHeight = Math.max(this.getHeightOfChar(lineIndex, i), maxHeight); + } + + return this.__lineHeights[lineIndex] = maxHeight * this.lineHeight * this._fontSizeMult; + }, + + /** + * Calculate text box height + */ + calcTextHeight: function() { + var lineHeight, height = 0; + for (var i = 0, len = this._textLines.length; i < len; i++) { + lineHeight = this.getHeightOfLine(i); + height += (i === len - 1 ? lineHeight / this.lineHeight : lineHeight); + } + return height; + }, + + /** + * @private + * @return {Number} Left offset + */ + _getLeftOffset: function() { + return this.direction === 'ltr' ? -this.width / 2 : this.width / 2; + }, + + /** + * @private + * @return {Number} Top offset + */ + _getTopOffset: function() { + return -this.height / 2; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {String} method Method name ("fillText" or "strokeText") + */ + _renderTextCommon: function(ctx, method) { + ctx.save(); + var lineHeights = 0, left = this._getLeftOffset(), top = this._getTopOffset(); + for (var i = 0, len = this._textLines.length; i < len; i++) { + var heightOfLine = this.getHeightOfLine(i), + maxHeight = heightOfLine / this.lineHeight, + leftOffset = this._getLineLeftOffset(i); + this._renderTextLine( + method, + ctx, + this._textLines[i], + left + leftOffset, + top + lineHeights + maxHeight, + i + ); + lineHeights += heightOfLine; + } + ctx.restore(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextFill: function(ctx) { + if (!this.fill && !this.styleHas('fill')) { + return; + } + + this._renderTextCommon(ctx, 'fillText'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextStroke: function(ctx) { + if ((!this.stroke || this.strokeWidth === 0) && this.isEmptyStyles()) { + return; + } + + if (this.shadow && !this.shadow.affectStroke) { + this._removeShadow(ctx); + } + + ctx.save(); + this._setLineDash(ctx, this.strokeDashArray); + ctx.beginPath(); + this._renderTextCommon(ctx, 'strokeText'); + ctx.closePath(); + ctx.restore(); + }, + + /** + * @private + * @param {String} method fillText or strokeText. + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Array} line Content of the line, splitted in an array by grapheme + * @param {Number} left + * @param {Number} top + * @param {Number} lineIndex + */ + _renderChars: function(method, ctx, line, left, top, lineIndex) { + // set proper line offset + var lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, + boxWidth = 0, + timeToRender, + path = this.path, + shortCut = !isJustify && this.charSpacing === 0 && this.isEmptyStyles(lineIndex) && !path, + isLtr = this.direction === 'ltr', sign = this.direction === 'ltr' ? 1 : -1, + drawingLeft; + + ctx.save(); + top -= lineHeight * this._fontSizeFraction / this.lineHeight; + if (shortCut) { + // render all the line in one pass without checking + // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex); + ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); + ctx.direction = isLtr ? 'ltr' : 'rtl'; + ctx.textAlign = isLtr ? 'left' : 'right'; + this._renderChar(method, ctx, lineIndex, 0, line.join(''), left, top, lineHeight); + ctx.restore(); + return; + } + for (var i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing || path; + charsToRender += line[i]; + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + left += sign * (charBox.kernedWidth - charBox.width); + boxWidth += charBox.width; + } + else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + // if we have charSpacing, we render char by char + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChanged(actualStyle, nextStyle); + } + if (timeToRender) { + if (path) { + ctx.save(); + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + this._renderChar(method, ctx, lineIndex, i, charsToRender, -boxWidth / 2, 0, lineHeight); + ctx.restore(); + } + else { + drawingLeft = left; + ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); + ctx.direction = isLtr ? 'ltr' : 'rtl'; + ctx.textAlign = isLtr ? 'left' : 'right'; + this._renderChar(method, ctx, lineIndex, i, charsToRender, drawingLeft, top, lineHeight); + } + charsToRender = ''; + actualStyle = nextStyle; + left += sign * boxWidth; + boxWidth = 0; + } + } + ctx.restore(); + }, + + /** + * This function try to patch the missing gradientTransform on canvas gradients. + * transforming a context to transform the gradient, is going to transform the stroke too. + * we want to transform the gradient but not the stroke operation, so we create + * a transformed gradient on a pattern and then we use the pattern instead of the gradient. + * this method has drawbacks: is slow, is in low resolution, needs a patch for when the size + * is limited. + * @private + * @param {fabric.Gradient} filler a fabric gradient instance + * @return {CanvasPattern} a pattern to use as fill/stroke style + */ + _applyPatternGradientTransformText: function(filler) { + var pCanvas = fabric.util.createCanvasElement(), pCtx, + // TODO: verify compatibility with strokeUniform + width = this.width + this.strokeWidth, height = this.height + this.strokeWidth; + pCanvas.width = width; + pCanvas.height = height; + pCtx = pCanvas.getContext('2d'); + pCtx.beginPath(); pCtx.moveTo(0, 0); pCtx.lineTo(width, 0); pCtx.lineTo(width, height); + pCtx.lineTo(0, height); pCtx.closePath(); + pCtx.translate(width / 2, height / 2); + pCtx.fillStyle = filler.toLive(pCtx); + this._applyPatternGradientTransform(pCtx, filler); + pCtx.fill(); + return pCtx.createPattern(pCanvas, 'no-repeat'); + }, + + handleFiller: function(ctx, property, filler) { + var offsetX, offsetY; + if (filler.toLive) { + if (filler.gradientUnits === 'percentage' || filler.gradientTransform || filler.patternTransform) { + // need to transform gradient in a pattern. + // this is a slow process. If you are hitting this codepath, and the object + // is not using caching, you should consider switching it on. + // we need a canvas as big as the current object caching canvas. + offsetX = -this.width / 2; + offsetY = -this.height / 2; + ctx.translate(offsetX, offsetY); + ctx[property] = this._applyPatternGradientTransformText(filler); + return { offsetX: offsetX, offsetY: offsetY }; + } + else { + // is a simple gradient or pattern + ctx[property] = filler.toLive(ctx, this); + return this._applyPatternGradientTransform(ctx, filler); + } + } + else { + // is a color + ctx[property] = filler; + } + return { offsetX: 0, offsetY: 0 }; + }, + + _setStrokeStyles: function(ctx, decl) { + ctx.lineWidth = decl.strokeWidth; + ctx.lineCap = this.strokeLineCap; + ctx.lineDashOffset = this.strokeDashOffset; + ctx.lineJoin = this.strokeLineJoin; + ctx.miterLimit = this.strokeMiterLimit; + return this.handleFiller(ctx, 'strokeStyle', decl.stroke); + }, + + _setFillStyles: function(ctx, decl) { + return this.handleFiller(ctx, 'fillStyle', decl.fill); + }, + + /** + * @private + * @param {String} method + * @param {CanvasRenderingContext2D} ctx Context to render on + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {String} _char + * @param {Number} left Left coordinate + * @param {Number} top Top coordinate + * @param {Number} lineHeight Height of the line + */ + _renderChar: function(method, ctx, lineIndex, charIndex, _char, left, top) { + var decl = this._getStyleDeclaration(lineIndex, charIndex), + fullDecl = this.getCompleteStyleDeclaration(lineIndex, charIndex), + shouldFill = method === 'fillText' && fullDecl.fill, + shouldStroke = method === 'strokeText' && fullDecl.stroke && fullDecl.strokeWidth, + fillOffsets, strokeOffsets; + + if (!shouldStroke && !shouldFill) { + return; + } + ctx.save(); + + shouldFill && (fillOffsets = this._setFillStyles(ctx, fullDecl)); + shouldStroke && (strokeOffsets = this._setStrokeStyles(ctx, fullDecl)); + + ctx.font = this._getFontDeclaration(fullDecl); + + + if (decl && decl.textBackgroundColor) { + this._removeShadow(ctx); + } + if (decl && decl.deltaY) { + top += decl.deltaY; + } + shouldFill && ctx.fillText(_char, left - fillOffsets.offsetX, top - fillOffsets.offsetY); + shouldStroke && ctx.strokeText(_char, left - strokeOffsets.offsetX, top - strokeOffsets.offsetY); + ctx.restore(); + }, + + /** + * Turns the character into a 'superior figure' (i.e. 'superscript') + * @param {Number} start selection start + * @param {Number} end selection end + * @returns {fabric.Text} thisArg + * @chainable + */ + setSuperscript: function(start, end) { + return this._setScript(start, end, this.superscript); + }, + + /** + * Turns the character into an 'inferior figure' (i.e. 'subscript') + * @param {Number} start selection start + * @param {Number} end selection end + * @returns {fabric.Text} thisArg + * @chainable + */ + setSubscript: function(start, end) { + return this._setScript(start, end, this.subscript); + }, + + /** + * Applies 'schema' at given position + * @private + * @param {Number} start selection start + * @param {Number} end selection end + * @param {Number} schema + * @returns {fabric.Text} thisArg + * @chainable + */ + _setScript: function(start, end, schema) { + var loc = this.get2DCursorLocation(start, true), + fontSize = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'fontSize'), + dy = this.getValueOfPropertyAt(loc.lineIndex, loc.charIndex, 'deltaY'), + style = { fontSize: fontSize * schema.size, deltaY: dy + fontSize * schema.baseline }; + this.setSelectionStyles(style, start, end); + return this; + }, + + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChanged: function(prevStyle, thisStyle) { + return prevStyle.fill !== thisStyle.fill || + prevStyle.stroke !== thisStyle.stroke || + prevStyle.strokeWidth !== thisStyle.strokeWidth || + prevStyle.fontSize !== thisStyle.fontSize || + prevStyle.fontFamily !== thisStyle.fontFamily || + prevStyle.fontWeight !== thisStyle.fontWeight || + prevStyle.fontStyle !== thisStyle.fontStyle || + prevStyle.deltaY !== thisStyle.deltaY; + }, + + /** + * @private + * @param {Object} prevStyle + * @param {Object} thisStyle + */ + _hasStyleChangedForSvg: function(prevStyle, thisStyle) { + return this._hasStyleChanged(prevStyle, thisStyle) || + prevStyle.overline !== thisStyle.overline || + prevStyle.underline !== thisStyle.underline || + prevStyle.linethrough !== thisStyle.linethrough; + }, + + /** + * @private + * @param {Number} lineIndex index text line + * @return {Number} Line left offset + */ + _getLineLeftOffset: function(lineIndex) { + var lineWidth = this.getLineWidth(lineIndex), + lineDiff = this.width - lineWidth, textAlign = this.textAlign, direction = this.direction, + isEndOfWrapping, leftOffset = 0, isEndOfWrapping = this.isEndOfWrapping(lineIndex); + if (textAlign === 'justify' + || (textAlign === 'justify-center' && !isEndOfWrapping) + || (textAlign === 'justify-right' && !isEndOfWrapping) + || (textAlign === 'justify-left' && !isEndOfWrapping) + ) { + return 0; + } + if (textAlign === 'center') { + leftOffset = lineDiff / 2; + } + if (textAlign === 'right') { + leftOffset = lineDiff; + } + if (textAlign === 'justify-center') { + leftOffset = lineDiff / 2; + } + if (textAlign === 'justify-right') { + leftOffset = lineDiff; + } + if (direction === 'rtl') { + leftOffset -= lineDiff; + } + return leftOffset; + }, + + /** + * @private + */ + _clearCache: function() { + this.__lineWidths = []; + this.__lineHeights = []; + this.__charBounds = []; + }, + + /** + * @private + */ + _shouldClearDimensionCache: function() { + var shouldClear = this._forceClearCache; + shouldClear || (shouldClear = this.hasStateChanged('_dimensionAffectingProps')); + if (shouldClear) { + this.dirty = true; + this._forceClearCache = false; + } + return shouldClear; + }, + + /** + * Measure a single line given its index. Used to calculate the initial + * text bounding box. The values are calculated and stored in __lineWidths cache. + * @private + * @param {Number} lineIndex line number + * @return {Number} Line width + */ + getLineWidth: function(lineIndex) { + if (this.__lineWidths[lineIndex]) { + return this.__lineWidths[lineIndex]; + } + + var width, line = this._textLines[lineIndex], lineInfo; + + if (line === '') { + width = 0; + } + else { + lineInfo = this.measureLine(lineIndex); + width = lineInfo.width; + } + this.__lineWidths[lineIndex] = width; + return width; + }, + + _getWidthOfCharSpacing: function() { + if (this.charSpacing !== 0) { + return this.fontSize * this.charSpacing / 1000; + } + return 0; + }, + + /** + * Retrieves the value of property at given character position + * @param {Number} lineIndex the line number + * @param {Number} charIndex the character number + * @param {String} property the property name + * @returns the value of 'property' + */ + getValueOfPropertyAt: function(lineIndex, charIndex, property) { + var charStyle = this._getStyleDeclaration(lineIndex, charIndex); + if (charStyle && typeof charStyle[property] !== 'undefined') { + return charStyle[property]; + } + return this[property]; + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _renderTextDecoration: function(ctx, type) { + if (!this[type] && !this.styleHas(type)) { + return; + } + var heightOfLine, size, _size, + lineLeftOffset, dy, _dy, + line, lastDecoration, + leftOffset = this._getLeftOffset(), + topOffset = this._getTopOffset(), top, + boxStart, boxWidth, charBox, currentDecoration, + maxHeight, currentFill, lastFill, path = this.path, + charSpacing = this._getWidthOfCharSpacing(), + offsetY = this.offsets[type]; + + for (var i = 0, len = this._textLines.length; i < len; i++) { + heightOfLine = this.getHeightOfLine(i); + if (!this[type] && !this.styleHas(type, i)) { + topOffset += heightOfLine; + continue; + } + line = this._textLines[i]; + maxHeight = heightOfLine / this.lineHeight; + lineLeftOffset = this._getLineLeftOffset(i); + boxStart = 0; + boxWidth = 0; + lastDecoration = this.getValueOfPropertyAt(i, 0, type); + lastFill = this.getValueOfPropertyAt(i, 0, 'fill'); + top = topOffset + maxHeight * (1 - this._fontSizeFraction); + size = this.getHeightOfChar(i, 0); + dy = this.getValueOfPropertyAt(i, 0, 'deltaY'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentDecoration = this.getValueOfPropertyAt(i, j, type); + currentFill = this.getValueOfPropertyAt(i, j, 'fill'); + _size = this.getHeightOfChar(i, j); + _dy = this.getValueOfPropertyAt(i, j, 'deltaY'); + if (path && currentDecoration && currentFill) { + ctx.save(); + ctx.fillStyle = lastFill; + ctx.translate(charBox.renderLeft, charBox.renderTop); + ctx.rotate(charBox.angle); + ctx.fillRect( + -charBox.kernedWidth / 2, + offsetY * _size + _dy, + charBox.kernedWidth, + this.fontSize / 15 + ); + ctx.restore(); + } + else if ( + (currentDecoration !== lastDecoration || currentFill !== lastFill || _size !== size || _dy !== dy) + && boxWidth > 0 + ) { + var drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + if (lastDecoration && lastFill) { + ctx.fillStyle = lastFill; + ctx.fillRect( + drawStart, + top + offsetY * size + dy, + boxWidth, + this.fontSize / 15 + ); + } + boxStart = charBox.left; + boxWidth = charBox.width; + lastDecoration = currentDecoration; + lastFill = currentFill; + size = _size; + dy = _dy; + } + else { + boxWidth += charBox.kernedWidth; + } + } + var drawStart = leftOffset + lineLeftOffset + boxStart; + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - boxWidth; + } + ctx.fillStyle = currentFill; + currentDecoration && currentFill && ctx.fillRect( + drawStart, + top + offsetY * size + dy, + boxWidth - charSpacing, + this.fontSize / 15 + ); + topOffset += heightOfLine; + } + // if there is text background color no + // other shadows should be casted + this._removeShadow(ctx); + }, + + /** + * return font declaration string for canvas context + * @param {Object} [styleObject] object + * @returns {String} font declaration formatted for canvas context. + */ + _getFontDeclaration: function(styleObject, forMeasuring) { + var style = styleObject || this, family = this.fontFamily, + fontIsGeneric = fabric.Text.genericFonts.indexOf(family.toLowerCase()) > -1; + var fontFamily = family === undefined || + family.indexOf('\'') > -1 || family.indexOf(',') > -1 || + family.indexOf('"') > -1 || fontIsGeneric + ? style.fontFamily : '"' + style.fontFamily + '"'; + return [ + // node-canvas needs "weight style", while browsers need "style weight" + // verify if this can be fixed in JSDOM + (fabric.isLikelyNode ? style.fontWeight : style.fontStyle), + (fabric.isLikelyNode ? style.fontStyle : style.fontWeight), + forMeasuring ? this.CACHE_FONT_SIZE + 'px' : style.fontSize + 'px', + fontFamily + ].join(' '); + }, + + /** + * Renders text instance on a specified context + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + // do not render if object is not visible + if (!this.visible) { + return; + } + if (this.canvas && this.canvas.skipOffscreen && !this.group && !this.isOnScreen()) { + return; + } + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + } + this.callSuper('render', ctx); + }, + + /** + * Returns the text as an array of lines. + * @param {String} text text to split + * @returns {Array} Lines in the text + */ + _splitTextIntoLines: function(text) { + var lines = text.split(this._reNewline), + newLines = new Array(lines.length), + newLine = ['\n'], + newText = []; + for (var i = 0; i < lines.length; i++) { + newLines[i] = fabric.util.string.graphemeSplit(lines[i]); + newText = newText.concat(newLines[i], newLine); + } + newText.pop(); + return { _unwrappedLines: newLines, lines: lines, graphemeText: newText, graphemeLines: newLines }; + }, + + /** + * Returns object representation of an instance + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} Object representation of an instance + */ + toObject: function(propertiesToInclude) { + var allProperties = additionalProps.concat(propertiesToInclude); + var obj = this.callSuper('toObject', allProperties); + // styles will be overridden with a properly cloned structure + obj.styles = clone(this.styles, true); + if (obj.path) { + obj.path = this.path.toObject(); + } + return obj; + }, + + /** + * Sets property to a given value. When changing position/dimension -related properties (left, top, scale, angle, etc.) `set` does not update position of object's borders/controls. If you need to update those, call `setCoords()`. + * @param {String|Object} key Property name or object (if object, iterate over the object properties) + * @param {Object|Function} value Property value (if function, the value is passed into it and its return value is used as a new one) + * @return {fabric.Object} thisArg + * @chainable + */ + set: function(key, value) { + this.callSuper('set', key, value); + var needsDims = false; + var isAddingPath = false; + if (typeof key === 'object') { + for (var _key in key) { + if (_key === 'path') { + this.setPathInfo(); + } + needsDims = needsDims || this._dimensionAffectingProps.indexOf(_key) !== -1; + isAddingPath = isAddingPath || _key === 'path'; + } + } + else { + needsDims = this._dimensionAffectingProps.indexOf(key) !== -1; + isAddingPath = key === 'path'; + } + if (isAddingPath) { + this.setPathInfo(); + } + if (needsDims) { + this.initDimensions(); + this.setCoords(); + } + return this; + }, + + /** + * Returns complexity of an instance + * @return {Number} complexity + */ + complexity: function() { + return 1; + } + }); + + /* _FROM_SVG_START_ */ + /** + * List of attribute names to account for when parsing SVG element (used by {@link fabric.Text.fromElement}) + * @static + * @memberOf fabric.Text + * @see: http://www.w3.org/TR/SVG/text.html#TextElement + */ + fabric.Text.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat( + 'x y dx dy font-family font-style font-weight font-size letter-spacing text-decoration text-anchor'.split(' ')); + + /** + * Default SVG font size + * @static + * @memberOf fabric.Text + */ + fabric.Text.DEFAULT_SVG_FONT_SIZE = 16; + + /** + * Returns fabric.Text instance from an SVG element (not yet implemented) + * @static + * @memberOf fabric.Text + * @param {SVGElement} element Element to parse + * @param {Function} callback callback function invoked after parsing + * @param {Object} [options] Options object + */ + fabric.Text.fromElement = function(element, callback, options) { + if (!element) { + return callback(null); + } + + var parsedAttributes = fabric.parseAttributes(element, fabric.Text.ATTRIBUTE_NAMES), + parsedAnchor = parsedAttributes.textAnchor || 'left'; + options = fabric.util.object.extend((options ? clone(options) : { }), parsedAttributes); + + options.top = options.top || 0; + options.left = options.left || 0; + if (parsedAttributes.textDecoration) { + var textDecoration = parsedAttributes.textDecoration; + if (textDecoration.indexOf('underline') !== -1) { + options.underline = true; + } + if (textDecoration.indexOf('overline') !== -1) { + options.overline = true; + } + if (textDecoration.indexOf('line-through') !== -1) { + options.linethrough = true; + } + delete options.textDecoration; + } + if ('dx' in parsedAttributes) { + options.left += parsedAttributes.dx; + } + if ('dy' in parsedAttributes) { + options.top += parsedAttributes.dy; + } + if (!('fontSize' in options)) { + options.fontSize = fabric.Text.DEFAULT_SVG_FONT_SIZE; + } + + var textContent = ''; + + // The XML is not properly parsed in IE9 so a workaround to get + // textContent is through firstChild.data. Another workaround would be + // to convert XML loaded from a file to be converted using DOMParser (same way loadSVGFromString() does) + if (!('textContent' in element)) { + if ('firstChild' in element && element.firstChild !== null) { + if ('data' in element.firstChild && element.firstChild.data !== null) { + textContent = element.firstChild.data; + } + } + } + else { + textContent = element.textContent; + } + + textContent = textContent.replace(/^\s+|\s+$|\n+/g, '').replace(/\s+/g, ' '); + var originalStrokeWidth = options.strokeWidth; + options.strokeWidth = 0; + + var text = new fabric.Text(textContent, options), + textHeightScaleFactor = text.getScaledHeight() / text.height, + lineHeightDiff = (text.height + text.strokeWidth) * text.lineHeight - text.height, + scaledDiff = lineHeightDiff * textHeightScaleFactor, + textHeight = text.getScaledHeight() + scaledDiff, + offX = 0; + /* + Adjust positioning: + x/y attributes in SVG correspond to the bottom-left corner of text bounding box + fabric output by default at top, left. + */ + if (parsedAnchor === 'center') { + offX = text.getScaledWidth() / 2; + } + if (parsedAnchor === 'right') { + offX = text.getScaledWidth(); + } + text.set({ + left: text.left - offX, + top: text.top - (textHeight - text.fontSize * (0.07 + text._fontSizeFraction)) / text.lineHeight, + strokeWidth: typeof originalStrokeWidth !== 'undefined' ? originalStrokeWidth : 1, + }); + callback(text); + }; + /* _FROM_SVG_END_ */ + + /** + * Returns fabric.Text instance from an object representation + * @static + * @memberOf fabric.Text + * @param {Object} object plain js Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Text instance is created + */ + fabric.Text.fromObject = function(object, callback) { + var objectCopy = clone(object), path = object.path; + delete objectCopy.path; + return fabric.Object._fromObject('Text', objectCopy, function(textInstance) { + if (path) { + fabric.Object._fromObject('Path', path, function(pathInstance) { + textInstance.set('path', pathInstance); + callback(textInstance); + }, 'path'); + } + else { + callback(textInstance); + } + }, 'text'); + }; + + fabric.Text.genericFonts = ['sans-serif', 'serif', 'cursive', 'fantasy', 'monospace']; + + fabric.util.createAccessors && fabric.util.createAccessors(fabric.Text); + +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { + /** + * Returns true if object has no styling or no styling in a line + * @param {Number} lineIndex , lineIndex is on wrapped lines. + * @return {Boolean} + */ + isEmptyStyles: function(lineIndex) { + if (!this.styles) { + return true; + } + if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { + return true; + } + var obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + // eslint-disable-next-line no-unused-vars + for (var p3 in obj[p1][p2]) { + return false; + } + } + } + return true; + }, + + /** + * Returns true if object has a style property or has it ina specified line + * This function is used to detect if a text will use a particular property or not. + * @param {String} property to check for + * @param {Number} lineIndex to check the style on + * @return {Boolean} + */ + styleHas: function(property, lineIndex) { + if (!this.styles || !property || property === '') { + return false; + } + if (typeof lineIndex !== 'undefined' && !this.styles[lineIndex]) { + return false; + } + var obj = typeof lineIndex === 'undefined' ? this.styles : { 0: this.styles[lineIndex] }; + // eslint-disable-next-line + for (var p1 in obj) { + // eslint-disable-next-line + for (var p2 in obj[p1]) { + if (typeof obj[p1][p2][property] !== 'undefined') { + return true; + } + } + } + return false; + }, + + /** + * Check if characters in a text have a value for a property + * whose value matches the textbox's value for that property. If so, + * the character-level property is deleted. If the character + * has no other properties, then it is also deleted. Finally, + * if the line containing that character has no other characters + * then it also is deleted. + * + * @param {string} property The property to compare between characters and text. + */ + cleanStyle: function(property) { + if (!this.styles || !property || property === '') { + return false; + } + var obj = this.styles, stylesCount = 0, letterCount, stylePropertyValue, + allStyleObjectPropertiesMatch = true, graphemeCount = 0, styleObject; + // eslint-disable-next-line + for (var p1 in obj) { + letterCount = 0; + // eslint-disable-next-line + for (var p2 in obj[p1]) { + var styleObject = obj[p1][p2], + stylePropertyHasBeenSet = styleObject.hasOwnProperty(property); + + stylesCount++; + + if (stylePropertyHasBeenSet) { + if (!stylePropertyValue) { + stylePropertyValue = styleObject[property]; + } + else if (styleObject[property] !== stylePropertyValue) { + allStyleObjectPropertiesMatch = false; + } + + if (styleObject[property] === this[property]) { + delete styleObject[property]; + } + } + else { + allStyleObjectPropertiesMatch = false; + } + + if (Object.keys(styleObject).length !== 0) { + letterCount++; + } + else { + delete obj[p1][p2]; + } + } + + if (letterCount === 0) { + delete obj[p1]; + } + } + // if every grapheme has the same style set then + // delete those styles and set it on the parent + for (var i = 0; i < this._textLines.length; i++) { + graphemeCount += this._textLines[i].length; + } + if (allStyleObjectPropertiesMatch && stylesCount === graphemeCount) { + this[property] = stylePropertyValue; + this.removeStyle(property); + } + }, + + /** + * Remove a style property or properties from all individual character styles + * in a text object. Deletes the character style object if it contains no other style + * props. Deletes a line style object if it contains no other character styles. + * + * @param {String} props The property to remove from character styles. + */ + removeStyle: function(property) { + if (!this.styles || !property || property === '') { + return; + } + var obj = this.styles, line, lineNum, charNum; + for (lineNum in obj) { + line = obj[lineNum]; + for (charNum in line) { + delete line[charNum][property]; + if (Object.keys(line[charNum]).length === 0) { + delete line[charNum]; + } + } + if (Object.keys(line).length === 0) { + delete obj[lineNum]; + } + } + }, + + /** + * @private + */ + _extendStyles: function(index, styles) { + var loc = this.get2DCursorLocation(index); + + if (!this._getLineStyle(loc.lineIndex)) { + this._setLineStyle(loc.lineIndex); + } + + if (!this._getStyleDeclaration(loc.lineIndex, loc.charIndex)) { + this._setStyleDeclaration(loc.lineIndex, loc.charIndex, {}); + } + + fabric.util.object.extend(this._getStyleDeclaration(loc.lineIndex, loc.charIndex), styles); + }, + + /** + * Returns 2d representation (lineIndex and charIndex) of cursor (or selection start) + * @param {Number} [selectionStart] Optional index. When not given, current selectionStart is used. + * @param {Boolean} [skipWrapping] consider the location for unwrapped lines. useful to manage styles. + */ + get2DCursorLocation: function(selectionStart, skipWrapping) { + if (typeof selectionStart === 'undefined') { + selectionStart = this.selectionStart; + } + var lines = skipWrapping ? this._unwrappedTextLines : this._textLines, + len = lines.length; + for (var i = 0; i < len; i++) { + if (selectionStart <= lines[i].length) { + return { + lineIndex: i, + charIndex: selectionStart + }; + } + selectionStart -= lines[i].length + this.missingNewlineOffset(i); + } + return { + lineIndex: i - 1, + charIndex: lines[i - 1].length < selectionStart ? lines[i - 1].length : selectionStart + }; + }, + + /** + * Gets style of a current selection/cursor (at the start position) + * if startIndex or endIndex are not provided, selectionStart or selectionEnd will be used. + * @param {Number} [startIndex] Start index to get styles at + * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 + * @param {Boolean} [complete] get full style or not + * @return {Array} styles an array with one, zero or more Style objects + */ + getSelectionStyles: function(startIndex, endIndex, complete) { + if (typeof startIndex === 'undefined') { + startIndex = this.selectionStart || 0; + } + if (typeof endIndex === 'undefined') { + endIndex = this.selectionEnd || startIndex; + } + var styles = []; + for (var i = startIndex; i < endIndex; i++) { + styles.push(this.getStyleAtPosition(i, complete)); + } + return styles; + }, + + /** + * Gets style of a current selection/cursor position + * @param {Number} position to get styles at + * @param {Boolean} [complete] full style if true + * @return {Object} style Style object at a specified index + * @private + */ + getStyleAtPosition: function(position, complete) { + var loc = this.get2DCursorLocation(position), + style = complete ? this.getCompleteStyleDeclaration(loc.lineIndex, loc.charIndex) : + this._getStyleDeclaration(loc.lineIndex, loc.charIndex); + return style || {}; + }, + + /** + * Sets style of a current selection, if no selection exist, do not set anything. + * @param {Object} [styles] Styles object + * @param {Number} [startIndex] Start index to get styles at + * @param {Number} [endIndex] End index to get styles at, if not specified selectionEnd or startIndex + 1 + * @return {fabric.IText} thisArg + * @chainable + */ + setSelectionStyles: function(styles, startIndex, endIndex) { + if (typeof startIndex === 'undefined') { + startIndex = this.selectionStart || 0; + } + if (typeof endIndex === 'undefined') { + endIndex = this.selectionEnd || startIndex; + } + for (var i = startIndex; i < endIndex; i++) { + this._extendStyles(i, styles); + } + /* not included in _extendStyles to avoid clearing cache more than once */ + this._forceClearCache = true; + return this; + }, + + /** + * get the reference, not a clone, of the style object for a given character + * @param {Number} lineIndex + * @param {Number} charIndex + * @return {Object} style object + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + var lineStyle = this.styles && this.styles[lineIndex]; + if (!lineStyle) { + return null; + } + return lineStyle[charIndex]; + }, + + /** + * return a new object that contains all the style property for a character + * the object returned is newly created + * @param {Number} lineIndex of the line where the character is + * @param {Number} charIndex position of the character on the line + * @return {Object} style object + */ + getCompleteStyleDeclaration: function(lineIndex, charIndex) { + var style = this._getStyleDeclaration(lineIndex, charIndex) || { }, + styleObject = { }, prop; + for (var i = 0; i < this._styleProperties.length; i++) { + prop = this._styleProperties[i]; + styleObject[prop] = typeof style[prop] === 'undefined' ? this[prop] : style[prop]; + } + return styleObject; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {Object} style + * @private + */ + _setStyleDeclaration: function(lineIndex, charIndex, style) { + this.styles[lineIndex][charIndex] = style; + }, + + /** + * + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _deleteStyleDeclaration: function(lineIndex, charIndex) { + delete this.styles[lineIndex][charIndex]; + }, + + /** + * @param {Number} lineIndex + * @return {Boolean} if the line exists or not + * @private + */ + _getLineStyle: function(lineIndex) { + return !!this.styles[lineIndex]; + }, + + /** + * Set the line style to an empty object so that is initialized + * @param {Number} lineIndex + * @private + */ + _setLineStyle: function(lineIndex) { + this.styles[lineIndex] = {}; + }, + + /** + * @param {Number} lineIndex + * @private + */ + _deleteLineStyle: function(lineIndex) { + delete this.styles[lineIndex]; + } + }); +})(); + + +(function() { + + function parseDecoration(object) { + if (object.textDecoration) { + object.textDecoration.indexOf('underline') > -1 && (object.underline = true); + object.textDecoration.indexOf('line-through') > -1 && (object.linethrough = true); + object.textDecoration.indexOf('overline') > -1 && (object.overline = true); + delete object.textDecoration; + } + } + + /** + * IText class (introduced in v1.4) Events are also fired with "text:" + * prefix when observing canvas. + * @class fabric.IText + * @extends fabric.Text + * @mixes fabric.Observable + * + * @fires changed + * @fires selection:changed + * @fires editing:entered + * @fires editing:exited + * + * @return {fabric.IText} thisArg + * @see {@link fabric.IText#initialize} for constructor definition + * + *

Supported key combinations:

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

Supported mouse/touch combination

+ *
+   *   Position cursor:                click/touch
+   *   Create selection:               click/touch & drag
+   *   Create selection:               click & shift + click
+   *   Select word:                    double click
+   *   Select line:                    triple click
+   * 
+ */ + fabric.IText = fabric.util.createClass(fabric.Text, fabric.Observable, /** @lends fabric.IText.prototype */ { + + /** + * Type of an object + * @type String + * @default + */ + type: 'i-text', + + /** + * Index where text selection starts (or where cursor is when there is no selection) + * @type Number + * @default + */ + selectionStart: 0, + + /** + * Index where text selection ends + * @type Number + * @default + */ + selectionEnd: 0, + + /** + * Color of text selection + * @type String + * @default + */ + selectionColor: 'rgba(17,119,255,0.3)', + + /** + * Indicates whether text is in editing mode + * @type Boolean + * @default + */ + isEditing: false, + + /** + * Indicates whether a text can be edited + * @type Boolean + * @default + */ + editable: true, + + /** + * Border color of text object while it's in editing mode + * @type String + * @default + */ + editingBorderColor: 'rgba(102,153,255,0.25)', + + /** + * Width of cursor (in px) + * @type Number + * @default + */ + cursorWidth: 2, + + /** + * Color of text cursor color in editing mode. + * if not set (default) will take color from the text. + * if set to a color value that fabric can understand, it will + * be used instead of the color of the text at the current position. + * @type String + * @default + */ + cursorColor: '', + + /** + * Delay between cursor blink (in ms) + * @type Number + * @default + */ + cursorDelay: 1000, + + /** + * Duration of cursor fadein (in ms) + * @type Number + * @default + */ + cursorDuration: 600, + + /** + * Indicates whether internal text char widths can be cached + * @type Boolean + * @default + */ + caching: true, + + /** + * DOM container to append the hiddenTextarea. + * An alternative to attaching to the document.body. + * Useful to reduce laggish redraw of the full document.body tree and + * also with modals event capturing that won't let the textarea take focus. + * @type HTMLElement + * @default + */ + hiddenTextareaContainer: null, + + /** + * @private + */ + _reSpace: /\s|\n/, + + /** + * @private + */ + _currentCursorOpacity: 0, + + /** + * @private + */ + _selectionDirection: null, + + /** + * @private + */ + _abortCursorAnimation: false, + + /** + * @private + */ + __widthOfSpace: [], + + /** + * Helps determining when the text is in composition, so that the cursor + * rendering is altered. + */ + inCompositionMode: false, + + /** + * Constructor + * @param {String} text Text string + * @param {Object} [options] Options object + * @return {fabric.IText} thisArg + */ + initialize: function(text, options) { + this.callSuper('initialize', text, options); + this.initBehavior(); + }, + + /** + * Sets selection start (left boundary of a selection) + * @param {Number} index Index to set selection start to + */ + setSelectionStart: function(index) { + index = Math.max(index, 0); + this._updateAndFire('selectionStart', index); + }, + + /** + * Sets selection end (right boundary of a selection) + * @param {Number} index Index to set selection end to + */ + setSelectionEnd: function(index) { + index = Math.min(index, this.text.length); + this._updateAndFire('selectionEnd', index); + }, + + /** + * @private + * @param {String} property 'selectionStart' or 'selectionEnd' + * @param {Number} index new position of property + */ + _updateAndFire: function(property, index) { + if (this[property] !== index) { + this._fireSelectionChanged(); + this[property] = index; + } + this._updateTextarea(); + }, + + /** + * Fires the even of selection changed + * @private + */ + _fireSelectionChanged: function() { + this.fire('selection:changed'); + this.canvas && this.canvas.fire('text:selection:changed', { target: this }); + }, + + /** + * Initialize text dimensions. Render all text on given context + * or on a offscreen canvas to get the text width with measureText. + * Updates this.width and this.height with the proper values. + * Does not return dimensions. + * @private + */ + initDimensions: function() { + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this.callSuper('initDimensions'); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + render: function(ctx) { + this.clearContextTop(); + this.callSuper('render', ctx); + // clear the cursorOffsetCache, so we ensure to calculate once per renderCursor + // the correct position but not at every cursor animation. + this.cursorOffsetCache = { }; + this.renderCursorOrSelection(); + }, + + /** + * @private + * @param {CanvasRenderingContext2D} ctx Context to render on + */ + _render: function(ctx) { + this.callSuper('_render', ctx); + }, + + /** + * Prepare and clean the contextTop + */ + clearContextTop: function(skipRestore) { + if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { + return; + } + var ctx = this.canvas.contextTop, v = this.canvas.viewportTransform; + ctx.save(); + ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]); + this.transform(ctx); + this._clearTextArea(ctx); + skipRestore || ctx.restore(); + }, + /** + * Renders cursor or selection (depending on what exists) + * it does on the contextTop. If contextTop is not available, do nothing. + */ + renderCursorOrSelection: function() { + if (!this.isEditing || !this.canvas || !this.canvas.contextTop) { + return; + } + var boundaries = this._getCursorBoundaries(), + ctx = this.canvas.contextTop; + this.clearContextTop(true); + if (this.selectionStart === this.selectionEnd) { + this.renderCursor(boundaries, ctx); + } + else { + this.renderSelection(boundaries, ctx); + } + ctx.restore(); + }, + + _clearTextArea: function(ctx) { + // we add 4 pixel, to be sure to do not leave any pixel out + var width = this.width + 4, height = this.height + 4; + ctx.clearRect(-width / 2, -height / 2, width, height); + }, + + /** + * Returns cursor boundaries (left, top, leftOffset, topOffset) + * @private + * @param {Array} chars Array of characters + * @param {String} typeOfBoundaries + */ + _getCursorBoundaries: function(position) { + + // left/top are left/top of entire text box + // leftOffset/topOffset are offset from that left/top point of a text box + + if (typeof position === 'undefined') { + position = this.selectionStart; + } + + var left = this._getLeftOffset(), + top = this._getTopOffset(), + offsets = this._getCursorBoundariesOffsets(position); + return { + left: left, + top: top, + leftOffset: offsets.left, + topOffset: offsets.top + }; + }, + + /** + * @private + */ + _getCursorBoundariesOffsets: function(position) { + if (this.cursorOffsetCache && 'top' in this.cursorOffsetCache) { + return this.cursorOffsetCache; + } + var lineLeftOffset, + lineIndex, + charIndex, + topOffset = 0, + leftOffset = 0, + boundaries, + cursorPosition = this.get2DCursorLocation(position); + charIndex = cursorPosition.charIndex; + lineIndex = cursorPosition.lineIndex; + for (var i = 0; i < lineIndex; i++) { + topOffset += this.getHeightOfLine(i); + } + lineLeftOffset = this._getLineLeftOffset(lineIndex); + var bound = this.__charBounds[lineIndex][charIndex]; + bound && (leftOffset = bound.left); + if (this.charSpacing !== 0 && charIndex === this._textLines[lineIndex].length) { + leftOffset -= this._getWidthOfCharSpacing(); + } + boundaries = { + top: topOffset, + left: lineLeftOffset + (leftOffset > 0 ? leftOffset : 0), + }; + if (this.direction === 'rtl') { + boundaries.left *= -1; + } + this.cursorOffsetCache = boundaries; + return this.cursorOffsetCache; + }, + + /** + * Renders cursor + * @param {Object} boundaries + * @param {CanvasRenderingContext2D} ctx transformed context to draw on + */ + renderCursor: function(boundaries, ctx) { + var cursorLocation = this.get2DCursorLocation(), + lineIndex = cursorLocation.lineIndex, + charIndex = cursorLocation.charIndex > 0 ? cursorLocation.charIndex - 1 : 0, + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize'), + multiplier = this.scaleX * this.canvas.getZoom(), + cursorWidth = this.cursorWidth / multiplier, + topOffset = boundaries.topOffset, + dy = this.getValueOfPropertyAt(lineIndex, charIndex, 'deltaY'); + topOffset += (1 - this._fontSizeFraction) * this.getHeightOfLine(lineIndex) / this.lineHeight + - charHeight * (1 - this._fontSizeFraction); + + if (this.inCompositionMode) { + this.renderSelection(boundaries, ctx); + } + ctx.fillStyle = this.cursorColor || this.getValueOfPropertyAt(lineIndex, charIndex, 'fill'); + ctx.globalAlpha = this.__isMousedown ? 1 : this._currentCursorOpacity; + ctx.fillRect( + boundaries.left + boundaries.leftOffset - cursorWidth / 2, + topOffset + boundaries.top + dy, + cursorWidth, + charHeight); + }, + + /** + * Renders text selection + * @param {Object} boundaries Object with left/top/leftOffset/topOffset + * @param {CanvasRenderingContext2D} ctx transformed context to draw on + */ + renderSelection: function(boundaries, ctx) { + + var selectionStart = this.inCompositionMode ? this.hiddenTextarea.selectionStart : this.selectionStart, + selectionEnd = this.inCompositionMode ? this.hiddenTextarea.selectionEnd : this.selectionEnd, + isJustify = this.textAlign.indexOf('justify') !== -1, + start = this.get2DCursorLocation(selectionStart), + end = this.get2DCursorLocation(selectionEnd), + startLine = start.lineIndex, + endLine = end.lineIndex, + startChar = start.charIndex < 0 ? 0 : start.charIndex, + endChar = end.charIndex < 0 ? 0 : end.charIndex; + + for (var i = startLine; i <= endLine; i++) { + var lineOffset = this._getLineLeftOffset(i) || 0, + lineHeight = this.getHeightOfLine(i), + realLineHeight = 0, boxStart = 0, boxEnd = 0; + + if (i === startLine) { + boxStart = this.__charBounds[startLine][startChar].left; + } + if (i >= startLine && i < endLine) { + boxEnd = isJustify && !this.isEndOfWrapping(i) ? this.width : this.getLineWidth(i) || 5; // WTF is this 5? + } + else if (i === endLine) { + if (endChar === 0) { + boxEnd = this.__charBounds[endLine][endChar].left; + } + else { + var charSpacing = this._getWidthOfCharSpacing(); + boxEnd = this.__charBounds[endLine][endChar - 1].left + + this.__charBounds[endLine][endChar - 1].width - charSpacing; + } + } + realLineHeight = lineHeight; + if (this.lineHeight < 1 || (i === endLine && this.lineHeight > 1)) { + lineHeight /= this.lineHeight; + } + var drawStart = boundaries.left + lineOffset + boxStart, + drawWidth = boxEnd - boxStart, + drawHeight = lineHeight, extraTop = 0; + if (this.inCompositionMode) { + ctx.fillStyle = this.compositionColor || 'black'; + drawHeight = 1; + extraTop = lineHeight; + } + else { + ctx.fillStyle = this.selectionColor; + } + if (this.direction === 'rtl') { + drawStart = this.width - drawStart - drawWidth; + } + ctx.fillRect( + drawStart, + boundaries.top + boundaries.topOffset + extraTop, + drawWidth, + drawHeight); + boundaries.topOffset += realLineHeight; + } + }, + + /** + * High level function to know the height of the cursor. + * the currentChar is the one that precedes the cursor + * Returns fontSize of char at the current cursor + * Unused from the library, is for the end user + * @return {Number} Character font size + */ + getCurrentCharFontSize: function() { + var cp = this._getCurrentCharIndex(); + return this.getValueOfPropertyAt(cp.l, cp.c, 'fontSize'); + }, + + /** + * High level function to know the color of the cursor. + * the currentChar is the one that precedes the cursor + * Returns color (fill) of char at the current cursor + * if the text object has a pattern or gradient for filler, it will return that. + * Unused by the library, is for the end user + * @return {String | fabric.Gradient | fabric.Pattern} Character color (fill) + */ + getCurrentCharColor: function() { + var cp = this._getCurrentCharIndex(); + return this.getValueOfPropertyAt(cp.l, cp.c, 'fill'); + }, + + /** + * Returns the cursor position for the getCurrent.. functions + * @private + */ + _getCurrentCharIndex: function() { + var cursorPosition = this.get2DCursorLocation(this.selectionStart, true), + charIndex = cursorPosition.charIndex > 0 ? cursorPosition.charIndex - 1 : 0; + return { l: cursorPosition.lineIndex, c: charIndex }; + } + }); + + /** + * Returns fabric.IText instance from an object representation + * @static + * @memberOf fabric.IText + * @param {Object} object Object to create an instance from + * @param {function} [callback] invoked with new instance as argument + */ + fabric.IText.fromObject = function(object, callback) { + parseDecoration(object); + if (object.styles) { + for (var i in object.styles) { + for (var j in object.styles[i]) { + parseDecoration(object.styles[i][j]); + } + } + } + fabric.Object._fromObject('IText', object, callback, 'text'); + }; +})(); + + +(function() { + + var clone = fabric.util.object.clone; + + fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + + /** + * Initializes all the interactive behavior of IText + */ + initBehavior: function() { + this.initAddedHandler(); + this.initRemovedHandler(); + this.initCursorSelectionHandlers(); + this.initDoubleClickSimulation(); + this.mouseMoveHandler = this.mouseMoveHandler.bind(this); + }, + + onDeselect: function() { + this.isEditing && this.exitEditing(); + this.selected = false; + }, + + /** + * Initializes "added" event handler + */ + initAddedHandler: function() { + var _this = this; + this.on('added', function() { + var canvas = _this.canvas; + if (canvas) { + if (!canvas._hasITextHandlers) { + canvas._hasITextHandlers = true; + _this._initCanvasHandlers(canvas); + } + canvas._iTextInstances = canvas._iTextInstances || []; + canvas._iTextInstances.push(_this); + } + }); + }, + + initRemovedHandler: function() { + var _this = this; + this.on('removed', function() { + var canvas = _this.canvas; + if (canvas) { + canvas._iTextInstances = canvas._iTextInstances || []; + fabric.util.removeFromArray(canvas._iTextInstances, _this); + if (canvas._iTextInstances.length === 0) { + canvas._hasITextHandlers = false; + _this._removeCanvasHandlers(canvas); + } + } + }); + }, + + /** + * register canvas event to manage exiting on other instances + * @private + */ + _initCanvasHandlers: function(canvas) { + canvas._mouseUpITextHandler = function() { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { + obj.__isMousedown = false; + }); + } + }; + canvas.on('mouse:up', canvas._mouseUpITextHandler); + }, + + /** + * remove canvas event to manage exiting on other instances + * @private + */ + _removeCanvasHandlers: function(canvas) { + canvas.off('mouse:up', canvas._mouseUpITextHandler); + }, + + /** + * @private + */ + _tick: function() { + this._currentTickState = this._animateCursor(this, 1, this.cursorDuration, '_onTickComplete'); + }, + + /** + * @private + */ + _animateCursor: function(obj, targetOpacity, duration, completeMethod) { + + var tickState; + + tickState = { + isAborted: false, + abort: function() { + this.isAborted = true; + }, + }; + + obj.animate('_currentCursorOpacity', targetOpacity, { + duration: duration, + onComplete: function() { + if (!tickState.isAborted) { + obj[completeMethod](); + } + }, + onChange: function() { + // we do not want to animate a selection, only cursor + if (obj.canvas && obj.selectionStart === obj.selectionEnd) { + obj.renderCursorOrSelection(); + } + }, + abort: function() { + return tickState.isAborted; + } + }); + return tickState; + }, + + /** + * @private + */ + _onTickComplete: function() { + + var _this = this; + + if (this._cursorTimeout1) { + clearTimeout(this._cursorTimeout1); + } + this._cursorTimeout1 = setTimeout(function() { + _this._currentTickCompleteState = _this._animateCursor(_this, 0, this.cursorDuration / 2, '_tick'); + }, 100); + }, + + /** + * Initializes delayed cursor + */ + initDelayedCursor: function(restart) { + var _this = this, + delay = restart ? 0 : this.cursorDelay; + + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + this._cursorTimeout2 = setTimeout(function() { + _this._tick(); + }, delay); + }, + + /** + * Aborts cursor animation and clears all timeouts + */ + abortCursorAnimation: function() { + var shouldClear = this._currentTickState || this._currentTickCompleteState, + canvas = this.canvas; + this._currentTickState && this._currentTickState.abort(); + this._currentTickCompleteState && this._currentTickCompleteState.abort(); + + clearTimeout(this._cursorTimeout1); + clearTimeout(this._cursorTimeout2); + + this._currentCursorOpacity = 0; + // to clear just itext area we need to transform the context + // it may not be worth it + if (shouldClear && canvas) { + canvas.clearContext(canvas.contextTop || canvas.contextContainer); + } + + }, + + /** + * Selects entire text + * @return {fabric.IText} thisArg + * @chainable + */ + selectAll: function() { + this.selectionStart = 0; + this.selectionEnd = this._text.length; + this._fireSelectionChanged(); + this._updateTextarea(); + return this; + }, + + /** + * Returns selected text + * @return {String} + */ + getSelectedText: function() { + return this._text.slice(this.selectionStart, this.selectionEnd).join(''); + }, + + /** + * Find new selection index representing start of current word according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findWordBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + + // remove space before cursor first + if (this._reSpace.test(this._text[index])) { + while (this._reSpace.test(this._text[index])) { + offset++; + index--; + } + } + while (/\S/.test(this._text[index]) && index > -1) { + offset++; + index--; + } + + return startFrom - offset; + }, + + /** + * Find new selection index representing end of current word according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findWordBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + + // remove space after cursor first + if (this._reSpace.test(this._text[index])) { + while (this._reSpace.test(this._text[index])) { + offset++; + index++; + } + } + while (/\S/.test(this._text[index]) && index < this._text.length) { + offset++; + index++; + } + + return startFrom + offset; + }, + + /** + * Find new selection index representing start of current line according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findLineBoundaryLeft: function(startFrom) { + var offset = 0, index = startFrom - 1; + + while (!/\n/.test(this._text[index]) && index > -1) { + offset++; + index--; + } + + return startFrom - offset; + }, + + /** + * Find new selection index representing end of current line according to current selection index + * @param {Number} startFrom Current selection index + * @return {Number} New selection index + */ + findLineBoundaryRight: function(startFrom) { + var offset = 0, index = startFrom; + + while (!/\n/.test(this._text[index]) && index < this._text.length) { + offset++; + index++; + } + + return startFrom + offset; + }, + + /** + * Finds index corresponding to beginning or end of a word + * @param {Number} selectionStart Index of a character + * @param {Number} direction 1 or -1 + * @return {Number} Index of the beginning or end of a word + */ + searchWordBoundary: function(selectionStart, direction) { + var text = this._text, + index = this._reSpace.test(text[selectionStart]) ? selectionStart - 1 : selectionStart, + _char = text[index], + // wrong + reNonWord = fabric.reNonWord; + + while (!reNonWord.test(_char) && index > 0 && index < text.length) { + index += direction; + _char = text[index]; + } + if (reNonWord.test(_char)) { + index += direction === 1 ? 0 : 1; + } + return index; + }, + + /** + * Selects a word based on the index + * @param {Number} selectionStart Index of a character + */ + selectWord: function(selectionStart) { + selectionStart = selectionStart || this.selectionStart; + var newSelectionStart = this.searchWordBoundary(selectionStart, -1), /* search backwards */ + newSelectionEnd = this.searchWordBoundary(selectionStart, 1); /* search forward */ + + this.selectionStart = newSelectionStart; + this.selectionEnd = newSelectionEnd; + this._fireSelectionChanged(); + this._updateTextarea(); + this.renderCursorOrSelection(); + }, + + /** + * Selects a line based on the index + * @param {Number} selectionStart Index of a character + * @return {fabric.IText} thisArg + * @chainable + */ + selectLine: function(selectionStart) { + selectionStart = selectionStart || this.selectionStart; + var newSelectionStart = this.findLineBoundaryLeft(selectionStart), + newSelectionEnd = this.findLineBoundaryRight(selectionStart); + + this.selectionStart = newSelectionStart; + this.selectionEnd = newSelectionEnd; + this._fireSelectionChanged(); + this._updateTextarea(); + return this; + }, + + /** + * Enters editing state + * @return {fabric.IText} thisArg + * @chainable + */ + enterEditing: function(e) { + if (this.isEditing || !this.editable) { + return; + } + + if (this.canvas) { + this.canvas.calcOffset(); + this.exitEditingOnOthers(this.canvas); + } + + this.isEditing = true; + + this.initHiddenTextarea(e); + this.hiddenTextarea.focus(); + this.hiddenTextarea.value = this.text; + this._updateTextarea(); + this._saveEditingProps(); + this._setEditingProps(); + this._textBeforeEdit = this.text; + + this._tick(); + this.fire('editing:entered'); + this._fireSelectionChanged(); + if (!this.canvas) { + return this; + } + this.canvas.fire('text:editing:entered', { target: this }); + this.initMouseMoveHandler(); + this.canvas.requestRenderAll(); + return this; + }, + + exitEditingOnOthers: function(canvas) { + if (canvas._iTextInstances) { + canvas._iTextInstances.forEach(function(obj) { + obj.selected = false; + if (obj.isEditing) { + obj.exitEditing(); + } + }); + } + }, + + /** + * Initializes "mousemove" event handler + */ + initMouseMoveHandler: function() { + this.canvas.on('mouse:move', this.mouseMoveHandler); + }, + + /** + * @private + */ + mouseMoveHandler: function(options) { + if (!this.__isMousedown || !this.isEditing) { + return; + } + + var newSelectionStart = this.getSelectionStartFromPointer(options.e), + currentStart = this.selectionStart, + currentEnd = this.selectionEnd; + if ( + (newSelectionStart !== this.__selectionStartOnMouseDown || currentStart === currentEnd) + && + (currentStart === newSelectionStart || currentEnd === newSelectionStart) + ) { + return; + } + if (newSelectionStart > this.__selectionStartOnMouseDown) { + this.selectionStart = this.__selectionStartOnMouseDown; + this.selectionEnd = newSelectionStart; + } + else { + this.selectionStart = newSelectionStart; + this.selectionEnd = this.__selectionStartOnMouseDown; + } + if (this.selectionStart !== currentStart || this.selectionEnd !== currentEnd) { + this.restartCursorIfNeeded(); + this._fireSelectionChanged(); + this._updateTextarea(); + this.renderCursorOrSelection(); + } + }, + + /** + * @private + */ + _setEditingProps: function() { + this.hoverCursor = 'text'; + + if (this.canvas) { + this.canvas.defaultCursor = this.canvas.moveCursor = 'text'; + } + + this.borderColor = this.editingBorderColor; + this.hasControls = this.selectable = false; + this.lockMovementX = this.lockMovementY = true; + }, + + /** + * convert from textarea to grapheme indexes + */ + fromStringToGraphemeSelection: function(start, end, text) { + var smallerTextStart = text.slice(0, start), + graphemeStart = fabric.util.string.graphemeSplit(smallerTextStart).length; + if (start === end) { + return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; + } + var smallerTextEnd = text.slice(start, end), + graphemeEnd = fabric.util.string.graphemeSplit(smallerTextEnd).length; + return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; + }, + + /** + * convert from fabric to textarea values + */ + fromGraphemeToStringSelection: function(start, end, _text) { + var smallerTextStart = _text.slice(0, start), + graphemeStart = smallerTextStart.join('').length; + if (start === end) { + return { selectionStart: graphemeStart, selectionEnd: graphemeStart }; + } + var smallerTextEnd = _text.slice(start, end), + graphemeEnd = smallerTextEnd.join('').length; + return { selectionStart: graphemeStart, selectionEnd: graphemeStart + graphemeEnd }; + }, + + /** + * @private + */ + _updateTextarea: function() { + this.cursorOffsetCache = { }; + if (!this.hiddenTextarea) { + return; + } + if (!this.inCompositionMode) { + var newSelection = this.fromGraphemeToStringSelection(this.selectionStart, this.selectionEnd, this._text); + this.hiddenTextarea.selectionStart = newSelection.selectionStart; + this.hiddenTextarea.selectionEnd = newSelection.selectionEnd; + } + this.updateTextareaPosition(); + }, + + /** + * @private + */ + updateFromTextArea: function() { + if (!this.hiddenTextarea) { + return; + } + this.cursorOffsetCache = { }; + this.text = this.hiddenTextarea.value; + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + var newSelection = this.fromStringToGraphemeSelection( + this.hiddenTextarea.selectionStart, this.hiddenTextarea.selectionEnd, this.hiddenTextarea.value); + this.selectionEnd = this.selectionStart = newSelection.selectionEnd; + if (!this.inCompositionMode) { + this.selectionStart = newSelection.selectionStart; + } + this.updateTextareaPosition(); + }, + + /** + * @private + */ + updateTextareaPosition: function() { + if (this.selectionStart === this.selectionEnd) { + var style = this._calcTextareaPosition(); + this.hiddenTextarea.style.left = style.left; + this.hiddenTextarea.style.top = style.top; + } + }, + + /** + * @private + * @return {Object} style contains style for hiddenTextarea + */ + _calcTextareaPosition: function() { + if (!this.canvas) { + return { x: 1, y: 1 }; + } + var desiredPosition = this.inCompositionMode ? this.compositionStart : this.selectionStart, + boundaries = this._getCursorBoundaries(desiredPosition), + cursorLocation = this.get2DCursorLocation(desiredPosition), + lineIndex = cursorLocation.lineIndex, + charIndex = cursorLocation.charIndex, + charHeight = this.getValueOfPropertyAt(lineIndex, charIndex, 'fontSize') * this.lineHeight, + leftOffset = boundaries.leftOffset, + m = this.calcTransformMatrix(), + p = { + x: boundaries.left + leftOffset, + y: boundaries.top + boundaries.topOffset + charHeight + }, + retinaScaling = this.canvas.getRetinaScaling(), + upperCanvas = this.canvas.upperCanvasEl, + upperCanvasWidth = upperCanvas.width / retinaScaling, + upperCanvasHeight = upperCanvas.height / retinaScaling, + maxWidth = upperCanvasWidth - charHeight, + maxHeight = upperCanvasHeight - charHeight, + scaleX = upperCanvas.clientWidth / upperCanvasWidth, + scaleY = upperCanvas.clientHeight / upperCanvasHeight; + + p = fabric.util.transformPoint(p, m); + p = fabric.util.transformPoint(p, this.canvas.viewportTransform); + p.x *= scaleX; + p.y *= scaleY; + if (p.x < 0) { + p.x = 0; + } + if (p.x > maxWidth) { + p.x = maxWidth; + } + if (p.y < 0) { + p.y = 0; + } + if (p.y > maxHeight) { + p.y = maxHeight; + } + + // add canvas offset on document + p.x += this.canvas._offset.left; + p.y += this.canvas._offset.top; + + return { left: p.x + 'px', top: p.y + 'px', fontSize: charHeight + 'px', charHeight: charHeight }; + }, + + /** + * @private + */ + _saveEditingProps: function() { + this._savedProps = { + hasControls: this.hasControls, + borderColor: this.borderColor, + lockMovementX: this.lockMovementX, + lockMovementY: this.lockMovementY, + hoverCursor: this.hoverCursor, + selectable: this.selectable, + defaultCursor: this.canvas && this.canvas.defaultCursor, + moveCursor: this.canvas && this.canvas.moveCursor + }; + }, + + /** + * @private + */ + _restoreEditingProps: function() { + if (!this._savedProps) { + return; + } + + this.hoverCursor = this._savedProps.hoverCursor; + this.hasControls = this._savedProps.hasControls; + this.borderColor = this._savedProps.borderColor; + this.selectable = this._savedProps.selectable; + this.lockMovementX = this._savedProps.lockMovementX; + this.lockMovementY = this._savedProps.lockMovementY; + + if (this.canvas) { + this.canvas.defaultCursor = this._savedProps.defaultCursor; + this.canvas.moveCursor = this._savedProps.moveCursor; + } + }, + + /** + * Exits from editing state + * @return {fabric.IText} thisArg + * @chainable + */ + exitEditing: function() { + var isTextChanged = (this._textBeforeEdit !== this.text); + var hiddenTextarea = this.hiddenTextarea; + this.selected = false; + this.isEditing = false; + + this.selectionEnd = this.selectionStart; + + if (hiddenTextarea) { + hiddenTextarea.blur && hiddenTextarea.blur(); + hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea); + } + this.hiddenTextarea = null; + this.abortCursorAnimation(); + this._restoreEditingProps(); + this._currentCursorOpacity = 0; + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this.fire('editing:exited'); + isTextChanged && this.fire('modified'); + if (this.canvas) { + this.canvas.off('mouse:move', this.mouseMoveHandler); + this.canvas.fire('text:editing:exited', { target: this }); + isTextChanged && this.canvas.fire('object:modified', { target: this }); + } + return this; + }, + + /** + * @private + */ + _removeExtraneousStyles: function() { + for (var prop in this.styles) { + if (!this._textLines[prop]) { + delete this.styles[prop]; + } + } + }, + + /** + * remove and reflow a style block from start to end. + * @param {Number} start linear start position for removal (included in removal) + * @param {Number} end linear end position for removal ( excluded from removal ) + */ + removeStyleFromTo: function(start, end) { + var cursorStart = this.get2DCursorLocation(start, true), + cursorEnd = this.get2DCursorLocation(end, true), + lineStart = cursorStart.lineIndex, + charStart = cursorStart.charIndex, + lineEnd = cursorEnd.lineIndex, + charEnd = cursorEnd.charIndex, + i, styleObj; + if (lineStart !== lineEnd) { + // step1 remove the trailing of lineStart + if (this.styles[lineStart]) { + for (i = charStart; i < this._unwrappedTextLines[lineStart].length; i++) { + delete this.styles[lineStart][i]; + } + } + // step2 move the trailing of lineEnd to lineStart if needed + if (this.styles[lineEnd]) { + for (i = charEnd; i < this._unwrappedTextLines[lineEnd].length; i++) { + styleObj = this.styles[lineEnd][i]; + if (styleObj) { + this.styles[lineStart] || (this.styles[lineStart] = { }); + this.styles[lineStart][charStart + i - charEnd] = styleObj; + } + } + } + // step3 detects lines will be completely removed. + for (i = lineStart + 1; i <= lineEnd; i++) { + delete this.styles[i]; + } + // step4 shift remaining lines. + this.shiftLineStyles(lineEnd, lineStart - lineEnd); + } + else { + // remove and shift left on the same line + if (this.styles[lineStart]) { + styleObj = this.styles[lineStart]; + var diff = charEnd - charStart, numericChar, _char; + for (i = charStart; i < charEnd; i++) { + delete styleObj[i]; + } + for (_char in this.styles[lineStart]) { + numericChar = parseInt(_char, 10); + if (numericChar >= charEnd) { + styleObj[numericChar - diff] = styleObj[_char]; + delete styleObj[_char]; + } + } + } + } + }, + + /** + * Shifts line styles up or down + * @param {Number} lineIndex Index of a line + * @param {Number} offset Can any number? + */ + shiftLineStyles: function(lineIndex, offset) { + // shift all line styles by offset upward or downward + // do not clone deep. we need new array, not new style objects + var clonedStyles = clone(this.styles); + for (var line in this.styles) { + var numericLine = parseInt(line, 10); + if (numericLine > lineIndex) { + this.styles[numericLine + offset] = clonedStyles[numericLine]; + if (!clonedStyles[numericLine - offset]) { + delete this.styles[numericLine]; + } + } + } + }, + + restartCursorIfNeeded: function() { + if (!this._currentTickState || this._currentTickState.isAborted + || !this._currentTickCompleteState || this._currentTickCompleteState.isAborted + ) { + this.initDelayedCursor(); + } + }, + + /** + * Handle insertion of more consecutive style lines for when one or more + * newlines gets added to the text. Since current style needs to be shifted + * first we shift the current style of the number lines needed, then we add + * new lines from the last to the first. + * @param {Number} lineIndex Index of a line + * @param {Number} charIndex Index of a char + * @param {Number} qty number of lines to add + * @param {Array} copiedStyle Array of objects styles + */ + insertNewlineStyleObject: function(lineIndex, charIndex, qty, copiedStyle) { + var currentCharStyle, + newLineStyles = {}, + somethingAdded = false, + isEndOfLine = this._unwrappedTextLines[lineIndex].length === charIndex; + + qty || (qty = 1); + this.shiftLineStyles(lineIndex, qty); + if (this.styles[lineIndex]) { + currentCharStyle = this.styles[lineIndex][charIndex === 0 ? charIndex : charIndex - 1]; + } + // we clone styles of all chars + // after cursor onto the current line + for (var index in this.styles[lineIndex]) { + var numIndex = parseInt(index, 10); + if (numIndex >= charIndex) { + somethingAdded = true; + newLineStyles[numIndex - charIndex] = this.styles[lineIndex][index]; + // remove lines from the previous line since they're on a new line now + if (!(isEndOfLine && charIndex === 0)) { + delete this.styles[lineIndex][index]; + } + } + } + var styleCarriedOver = false; + if (somethingAdded && !isEndOfLine) { + // if is end of line, the extra style we copied + // is probably not something we want + this.styles[lineIndex + qty] = newLineStyles; + styleCarriedOver = true; + } + if (styleCarriedOver) { + // skip the last line of since we already prepared it. + qty--; + } + // for the all the lines or all the other lines + // we clone current char style onto the next (otherwise empty) line + while (qty > 0) { + if (copiedStyle && copiedStyle[qty - 1]) { + this.styles[lineIndex + qty] = { 0: clone(copiedStyle[qty - 1]) }; + } + else if (currentCharStyle) { + this.styles[lineIndex + qty] = { 0: clone(currentCharStyle) }; + } + else { + delete this.styles[lineIndex + qty]; + } + qty--; + } + this._forceClearCache = true; + }, + + /** + * Inserts style object for a given line/char index + * @param {Number} lineIndex Index of a line + * @param {Number} charIndex Index of a char + * @param {Number} quantity number Style object to insert, if given + * @param {Array} copiedStyle array of style objects + */ + insertCharStyleObject: function(lineIndex, charIndex, quantity, copiedStyle) { + if (!this.styles) { + this.styles = {}; + } + var currentLineStyles = this.styles[lineIndex], + currentLineStylesCloned = currentLineStyles ? clone(currentLineStyles) : {}; + + quantity || (quantity = 1); + // shift all char styles by quantity forward + // 0,1,2,3 -> (charIndex=2) -> 0,1,3,4 -> (insert 2) -> 0,1,2,3,4 + for (var index in currentLineStylesCloned) { + var numericIndex = parseInt(index, 10); + if (numericIndex >= charIndex) { + currentLineStyles[numericIndex + quantity] = currentLineStylesCloned[numericIndex]; + // only delete the style if there was nothing moved there + if (!currentLineStylesCloned[numericIndex - quantity]) { + delete currentLineStyles[numericIndex]; + } + } + } + this._forceClearCache = true; + if (copiedStyle) { + while (quantity--) { + if (!Object.keys(copiedStyle[quantity]).length) { + continue; + } + if (!this.styles[lineIndex]) { + this.styles[lineIndex] = {}; + } + this.styles[lineIndex][charIndex + quantity] = clone(copiedStyle[quantity]); + } + return; + } + if (!currentLineStyles) { + return; + } + var newStyle = currentLineStyles[charIndex ? charIndex - 1 : 1]; + while (newStyle && quantity--) { + this.styles[lineIndex][charIndex + quantity] = clone(newStyle); + } + }, + + /** + * Inserts style object(s) + * @param {Array} insertedText Characters at the location where style is inserted + * @param {Number} start cursor index for inserting style + * @param {Array} [copiedStyle] array of style objects to insert. + */ + insertNewStyleBlock: function(insertedText, start, copiedStyle) { + var cursorLoc = this.get2DCursorLocation(start, true), + addedLines = [0], linesLength = 0; + // get an array of how many char per lines are being added. + for (var i = 0; i < insertedText.length; i++) { + if (insertedText[i] === '\n') { + linesLength++; + addedLines[linesLength] = 0; + } + else { + addedLines[linesLength]++; + } + } + // for the first line copy the style from the current char position. + if (addedLines[0] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex, cursorLoc.charIndex, addedLines[0], copiedStyle); + copiedStyle = copiedStyle && copiedStyle.slice(addedLines[0] + 1); + } + linesLength && this.insertNewlineStyleObject( + cursorLoc.lineIndex, cursorLoc.charIndex + addedLines[0], linesLength); + for (var i = 1; i < linesLength; i++) { + if (addedLines[i] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + } + else if (copiedStyle) { + this.styles[cursorLoc.lineIndex + i][0] = copiedStyle[0]; + } + copiedStyle = copiedStyle && copiedStyle.slice(addedLines[i] + 1); + } + // we use i outside the loop to get it like linesLength + if (addedLines[i] > 0) { + this.insertCharStyleObject(cursorLoc.lineIndex + i, 0, addedLines[i], copiedStyle); + } + }, + + /** + * Set the selectionStart and selectionEnd according to the new position of cursor + * mimic the key - mouse navigation when shift is pressed. + */ + setSelectionStartEndWithShift: function(start, end, newSelection) { + if (newSelection <= start) { + if (end === start) { + this._selectionDirection = 'left'; + } + else if (this._selectionDirection === 'right') { + this._selectionDirection = 'left'; + this.selectionEnd = start; + } + this.selectionStart = newSelection; + } + else if (newSelection > start && newSelection < end) { + if (this._selectionDirection === 'right') { + this.selectionEnd = newSelection; + } + else { + this.selectionStart = newSelection; + } + } + else { + // newSelection is > selection start and end + if (end === start) { + this._selectionDirection = 'right'; + } + else if (this._selectionDirection === 'left') { + this._selectionDirection = 'right'; + this.selectionStart = end; + } + this.selectionEnd = newSelection; + } + }, + + setSelectionInBoundaries: function() { + var length = this.text.length; + if (this.selectionStart > length) { + this.selectionStart = length; + } + else if (this.selectionStart < 0) { + this.selectionStart = 0; + } + if (this.selectionEnd > length) { + this.selectionEnd = length; + } + else if (this.selectionEnd < 0) { + this.selectionEnd = 0; + } + } + }); +})(); + + +fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + /** + * Initializes "dbclick" event handler + */ + initDoubleClickSimulation: function() { + + // for double click + this.__lastClickTime = +new Date(); + + // for triple click + this.__lastLastClickTime = +new Date(); + + this.__lastPointer = { }; + + this.on('mousedown', this.onMouseDown); + }, + + /** + * Default event handler to simulate triple click + * @private + */ + onMouseDown: function(options) { + if (!this.canvas) { + return; + } + this.__newClickTime = +new Date(); + var newPointer = options.pointer; + if (this.isTripleClick(newPointer)) { + this.fire('tripleclick', options); + this._stopEvent(options.e); + } + this.__lastLastClickTime = this.__lastClickTime; + this.__lastClickTime = this.__newClickTime; + this.__lastPointer = newPointer; + this.__lastIsEditing = this.isEditing; + this.__lastSelected = this.selected; + }, + + isTripleClick: function(newPointer) { + return this.__newClickTime - this.__lastClickTime < 500 && + this.__lastClickTime - this.__lastLastClickTime < 500 && + this.__lastPointer.x === newPointer.x && + this.__lastPointer.y === newPointer.y; + }, + + /** + * @private + */ + _stopEvent: function(e) { + e.preventDefault && e.preventDefault(); + e.stopPropagation && e.stopPropagation(); + }, + + /** + * Initializes event handlers related to cursor or selection + */ + initCursorSelectionHandlers: function() { + this.initMousedownHandler(); + this.initMouseupHandler(); + this.initClicks(); + }, + + /** + * Default handler for double click, select a word + */ + doubleClickHandler: function(options) { + if (!this.isEditing) { + return; + } + this.selectWord(this.getSelectionStartFromPointer(options.e)); + }, + + /** + * Default handler for triple click, select a line + */ + tripleClickHandler: function(options) { + if (!this.isEditing) { + return; + } + this.selectLine(this.getSelectionStartFromPointer(options.e)); + }, + + /** + * Initializes double and triple click event handlers + */ + initClicks: function() { + this.on('mousedblclick', this.doubleClickHandler); + this.on('tripleclick', this.tripleClickHandler); + }, + + /** + * Default event handler for the basic functionalities needed on _mouseDown + * can be overridden to do something different. + * Scope of this implementation is: find the click position, set selectionStart + * find selectionEnd, initialize the drawing of either cursor or selection area + * initializing a mousedDown on a text area will cancel fabricjs knowledge of + * current compositionMode. It will be set to false. + */ + _mouseDownHandler: function(options) { + if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { + return; + } + + this.__isMousedown = true; + + if (this.selected) { + this.inCompositionMode = false; + this.setCursorByClick(options.e); + } + + if (this.isEditing) { + this.__selectionStartOnMouseDown = this.selectionStart; + if (this.selectionStart === this.selectionEnd) { + this.abortCursorAnimation(); + } + this.renderCursorOrSelection(); + } + }, + + /** + * Default event handler for the basic functionalities needed on mousedown:before + * can be overridden to do something different. + * Scope of this implementation is: verify the object is already selected when mousing down + */ + _mouseDownHandlerBefore: function(options) { + if (!this.canvas || !this.editable || (options.e.button && options.e.button !== 1)) { + return; + } + // we want to avoid that an object that was selected and then becomes unselectable, + // may trigger editing mode in some way. + this.selected = this === this.canvas._activeObject; + }, + + /** + * Initializes "mousedown" event handler + */ + initMousedownHandler: function() { + this.on('mousedown', this._mouseDownHandler); + this.on('mousedown:before', this._mouseDownHandlerBefore); + }, + + /** + * Initializes "mouseup" event handler + */ + initMouseupHandler: function() { + this.on('mouseup', this.mouseUpHandler); + }, + + /** + * standard handler for mouse up, overridable + * @private + */ + mouseUpHandler: function(options) { + this.__isMousedown = false; + if (!this.editable || this.group || + (options.transform && options.transform.actionPerformed) || + (options.e.button && options.e.button !== 1)) { + return; + } + + if (this.canvas) { + var currentActive = this.canvas._activeObject; + if (currentActive && currentActive !== this) { + // avoid running this logic when there is an active object + // this because is possible with shift click and fast clicks, + // to rapidly deselect and reselect this object and trigger an enterEdit + return; + } + } + + if (this.__lastSelected && !this.__corner) { + this.selected = false; + this.__lastSelected = false; + this.enterEditing(options.e); + if (this.selectionStart === this.selectionEnd) { + this.initDelayedCursor(true); + } + else { + this.renderCursorOrSelection(); + } + } + else { + this.selected = true; + } + }, + + /** + * Changes cursor location in a text depending on passed pointer (x/y) object + * @param {Event} e Event object + */ + setCursorByClick: function(e) { + var newSelection = this.getSelectionStartFromPointer(e), + start = this.selectionStart, end = this.selectionEnd; + if (e.shiftKey) { + this.setSelectionStartEndWithShift(start, end, newSelection); + } + else { + this.selectionStart = newSelection; + this.selectionEnd = newSelection; + } + if (this.isEditing) { + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Returns index of a character corresponding to where an object was clicked + * @param {Event} e Event object + * @return {Number} Index of a character + */ + getSelectionStartFromPointer: function(e) { + var mouseOffset = this.getLocalPointer(e), + prevWidth = 0, + width = 0, + height = 0, + charIndex = 0, + lineIndex = 0, + lineLeftOffset, + line; + for (var i = 0, len = this._textLines.length; i < len; i++) { + if (height <= mouseOffset.y) { + height += this.getHeightOfLine(i) * this.scaleY; + lineIndex = i; + if (i > 0) { + charIndex += this._textLines[i - 1].length + this.missingNewlineOffset(i - 1); + } + } + else { + break; + } + } + lineLeftOffset = this._getLineLeftOffset(lineIndex); + width = lineLeftOffset * this.scaleX; + line = this._textLines[lineIndex]; + // handling of RTL: in order to get things work correctly, + // we assume RTL writing is mirrored compared to LTR writing. + // so in position detection we mirror the X offset, and when is time + // of rendering it, we mirror it again. + if (this.direction === 'rtl') { + mouseOffset.x = this.width * this.scaleX - mouseOffset.x + width; + } + for (var j = 0, jlen = line.length; j < jlen; j++) { + prevWidth = width; + // i removed something about flipX here, check. + width += this.__charBounds[lineIndex][j].kernedWidth * this.scaleX; + if (width <= mouseOffset.x) { + charIndex++; + } + else { + break; + } + } + return this._getNewSelectionStartFromOffset(mouseOffset, prevWidth, width, charIndex, jlen); + }, + + /** + * @private + */ + _getNewSelectionStartFromOffset: function(mouseOffset, prevWidth, width, index, jlen) { + // we need Math.abs because when width is after the last char, the offset is given as 1, while is 0 + var distanceBtwLastCharAndCursor = mouseOffset.x - prevWidth, + distanceBtwNextCharAndCursor = width - mouseOffset.x, + offset = distanceBtwNextCharAndCursor > distanceBtwLastCharAndCursor || + distanceBtwNextCharAndCursor < 0 ? 0 : 1, + newSelectionStart = index + offset; + // if object is horizontally flipped, mirror cursor location from the end + if (this.flipX) { + newSelectionStart = jlen - newSelectionStart; + } + + if (newSelectionStart > this._text.length) { + newSelectionStart = this._text.length; + } + + return newSelectionStart; + } +}); + + +fabric.util.object.extend(fabric.IText.prototype, /** @lends fabric.IText.prototype */ { + + /** + * Initializes hidden textarea (needed to bring up keyboard in iOS) + */ + initHiddenTextarea: function() { + this.hiddenTextarea = fabric.document.createElement('textarea'); + this.hiddenTextarea.setAttribute('autocapitalize', 'off'); + this.hiddenTextarea.setAttribute('autocorrect', 'off'); + this.hiddenTextarea.setAttribute('autocomplete', 'off'); + this.hiddenTextarea.setAttribute('spellcheck', 'false'); + this.hiddenTextarea.setAttribute('data-fabric-hiddentextarea', ''); + this.hiddenTextarea.setAttribute('wrap', 'off'); + var style = this._calcTextareaPosition(); + // line-height: 1px; was removed from the style to fix this: + // https://bugs.chromium.org/p/chromium/issues/detail?id=870966 + this.hiddenTextarea.style.cssText = 'position: absolute; top: ' + style.top + + '; left: ' + style.left + '; z-index: -999; opacity: 0; width: 1px; height: 1px; font-size: 1px;' + + ' paddingーtop: ' + style.fontSize + ';'; + + if (this.hiddenTextareaContainer) { + this.hiddenTextareaContainer.appendChild(this.hiddenTextarea); + } + else { + fabric.document.body.appendChild(this.hiddenTextarea); + } + + fabric.util.addListener(this.hiddenTextarea, 'keydown', this.onKeyDown.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'keyup', this.onKeyUp.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'input', this.onInput.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'copy', this.copy.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'cut', this.copy.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'paste', this.paste.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionstart', this.onCompositionStart.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionupdate', this.onCompositionUpdate.bind(this)); + fabric.util.addListener(this.hiddenTextarea, 'compositionend', this.onCompositionEnd.bind(this)); + + if (!this._clickHandlerInitialized && this.canvas) { + fabric.util.addListener(this.canvas.upperCanvasEl, 'click', this.onClick.bind(this)); + this._clickHandlerInitialized = true; + } + }, + + /** + * For functionalities on keyDown + * Map a special key to a function of the instance/prototype + * If you need different behaviour for ESC or TAB or arrows, you have to change + * this map setting the name of a function that you build on the fabric.Itext or + * your prototype. + * the map change will affect all Instances unless you need for only some text Instances + * in that case you have to clone this object and assign your Instance. + * this.keysMap = fabric.util.object.clone(this.keysMap); + * The function must be in fabric.Itext.prototype.myFunction And will receive event as args[0] + */ + keysMap: { + 9: 'exitEditing', + 27: 'exitEditing', + 33: 'moveCursorUp', + 34: 'moveCursorDown', + 35: 'moveCursorRight', + 36: 'moveCursorLeft', + 37: 'moveCursorLeft', + 38: 'moveCursorUp', + 39: 'moveCursorRight', + 40: 'moveCursorDown', + }, + + keysMapRtl: { + 9: 'exitEditing', + 27: 'exitEditing', + 33: 'moveCursorUp', + 34: 'moveCursorDown', + 35: 'moveCursorLeft', + 36: 'moveCursorRight', + 37: 'moveCursorRight', + 38: 'moveCursorUp', + 39: 'moveCursorLeft', + 40: 'moveCursorDown', + }, + + /** + * For functionalities on keyUp + ctrl || cmd + */ + ctrlKeysMapUp: { + 67: 'copy', + 88: 'cut' + }, + + /** + * For functionalities on keyDown + ctrl || cmd + */ + ctrlKeysMapDown: { + 65: 'selectAll' + }, + + onClick: function() { + // No need to trigger click event here, focus is enough to have the keyboard appear on Android + this.hiddenTextarea && this.hiddenTextarea.focus(); + }, + + /** + * Handles keydown event + * only used for arrows and combination of modifier keys. + * @param {Event} e Event object + */ + onKeyDown: function(e) { + if (!this.isEditing) { + return; + } + var keyMap = this.direction === 'rtl' ? this.keysMapRtl : this.keysMap; + if (e.keyCode in keyMap) { + this[keyMap[e.keyCode]](e); + } + else if ((e.keyCode in this.ctrlKeysMapDown) && (e.ctrlKey || e.metaKey)) { + this[this.ctrlKeysMapDown[e.keyCode]](e); + } + else { + return; + } + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.keyCode >= 33 && e.keyCode <= 40) { + // if i press an arrow key just update selection + this.inCompositionMode = false; + this.clearContextTop(); + this.renderCursorOrSelection(); + } + else { + this.canvas && this.canvas.requestRenderAll(); + } + }, + + /** + * Handles keyup event + * We handle KeyUp because ie11 and edge have difficulties copy/pasting + * if a copy/cut event fired, keyup is dismissed + * @param {Event} e Event object + */ + onKeyUp: function(e) { + if (!this.isEditing || this._copyDone || this.inCompositionMode) { + this._copyDone = false; + return; + } + if ((e.keyCode in this.ctrlKeysMapUp) && (e.ctrlKey || e.metaKey)) { + this[this.ctrlKeysMapUp[e.keyCode]](e); + } + else { + return; + } + e.stopImmediatePropagation(); + e.preventDefault(); + this.canvas && this.canvas.requestRenderAll(); + }, + + /** + * Handles onInput event + * @param {Event} e Event object + */ + onInput: function(e) { + var fromPaste = this.fromPaste; + this.fromPaste = false; + e && e.stopPropagation(); + if (!this.isEditing) { + return; + } + // decisions about style changes. + var nextText = this._splitTextIntoLines(this.hiddenTextarea.value).graphemeText, + charCount = this._text.length, + nextCharCount = nextText.length, + removedText, insertedText, + charDiff = nextCharCount - charCount, + selectionStart = this.selectionStart, selectionEnd = this.selectionEnd, + selection = selectionStart !== selectionEnd, + copiedStyle, removeFrom, removeTo; + if (this.hiddenTextarea.value === '') { + this.styles = { }; + this.updateFromTextArea(); + this.fire('changed'); + if (this.canvas) { + this.canvas.fire('text:changed', { target: this }); + this.canvas.requestRenderAll(); + } + return; + } + + var textareaSelection = this.fromStringToGraphemeSelection( + this.hiddenTextarea.selectionStart, + this.hiddenTextarea.selectionEnd, + this.hiddenTextarea.value + ); + var backDelete = selectionStart > textareaSelection.selectionStart; + + if (selection) { + removedText = this._text.slice(selectionStart, selectionEnd); + charDiff += selectionEnd - selectionStart; + } + else if (nextCharCount < charCount) { + if (backDelete) { + removedText = this._text.slice(selectionEnd + charDiff, selectionEnd); + } + else { + removedText = this._text.slice(selectionStart, selectionStart - charDiff); + } + } + insertedText = nextText.slice(textareaSelection.selectionEnd - charDiff, textareaSelection.selectionEnd); + if (removedText && removedText.length) { + if (insertedText.length) { + // let's copy some style before deleting. + // we want to copy the style before the cursor OR the style at the cursor if selection + // is bigger than 0. + copiedStyle = this.getSelectionStyles(selectionStart, selectionStart + 1, false); + // now duplicate the style one for each inserted text. + copiedStyle = insertedText.map(function() { + // this return an array of references, but that is fine since we are + // copying the style later. + return copiedStyle[0]; + }); + } + if (selection) { + removeFrom = selectionStart; + removeTo = selectionEnd; + } + else if (backDelete) { + // detect differences between forwardDelete and backDelete + removeFrom = selectionEnd - removedText.length; + removeTo = selectionEnd; + } + else { + removeFrom = selectionEnd; + removeTo = selectionEnd + removedText.length; + } + this.removeStyleFromTo(removeFrom, removeTo); + } + if (insertedText.length) { + if (fromPaste && insertedText.join('') === fabric.copiedText && !fabric.disableStyleCopyPaste) { + copiedStyle = fabric.copiedTextStyle; + } + this.insertNewStyleBlock(insertedText, selectionStart, copiedStyle); + } + this.updateFromTextArea(); + this.fire('changed'); + if (this.canvas) { + this.canvas.fire('text:changed', { target: this }); + this.canvas.requestRenderAll(); + } + }, + /** + * Composition start + */ + onCompositionStart: function() { + this.inCompositionMode = true; + }, + + /** + * Composition end + */ + onCompositionEnd: function() { + this.inCompositionMode = false; + }, + + // /** + // * Composition update + // */ + onCompositionUpdate: function(e) { + this.compositionStart = e.target.selectionStart; + this.compositionEnd = e.target.selectionEnd; + this.updateTextareaPosition(); + }, + + /** + * Copies selected text + * @param {Event} e Event object + */ + copy: function() { + if (this.selectionStart === this.selectionEnd) { + //do not cut-copy if no selection + return; + } + + fabric.copiedText = this.getSelectedText(); + if (!fabric.disableStyleCopyPaste) { + fabric.copiedTextStyle = this.getSelectionStyles(this.selectionStart, this.selectionEnd, true); + } + else { + fabric.copiedTextStyle = null; + } + this._copyDone = true; + }, + + /** + * Pastes text + * @param {Event} e Event object + */ + paste: function() { + this.fromPaste = true; + }, + + /** + * @private + * @param {Event} e Event object + * @return {Object} Clipboard data object + */ + _getClipboardData: function(e) { + return (e && e.clipboardData) || fabric.window.clipboardData; + }, + + /** + * Finds the width in pixels before the cursor on the same line + * @private + * @param {Number} lineIndex + * @param {Number} charIndex + * @return {Number} widthBeforeCursor width before cursor + */ + _getWidthBeforeCursor: function(lineIndex, charIndex) { + var widthBeforeCursor = this._getLineLeftOffset(lineIndex), bound; + + if (charIndex > 0) { + bound = this.__charBounds[lineIndex][charIndex - 1]; + widthBeforeCursor += bound.left + bound.width; + } + return widthBeforeCursor; + }, + + /** + * Gets start offset of a selection + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + getDownCursorOffset: function(e, isRight) { + var selectionProp = this._getSelectionForOffset(e, isRight), + cursorLocation = this.get2DCursorLocation(selectionProp), + lineIndex = cursorLocation.lineIndex; + // if on last line, down cursor goes to end of line + if (lineIndex === this._textLines.length - 1 || e.metaKey || e.keyCode === 34) { + // move to the end of a text + return this._text.length - selectionProp; + } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex + 1, widthBeforeCursor), + textAfterCursor = this._textLines[lineIndex].slice(charIndex); + return textAfterCursor.length + indexOnOtherLine + 1 + this.missingNewlineOffset(lineIndex); + }, + + /** + * private + * Helps finding if the offset should be counted from Start or End + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + _getSelectionForOffset: function(e, isRight) { + if (e.shiftKey && this.selectionStart !== this.selectionEnd && isRight) { + return this.selectionEnd; + } + else { + return this.selectionStart; + } + }, + + /** + * @param {Event} e Event object + * @param {Boolean} isRight + * @return {Number} + */ + getUpCursorOffset: function(e, isRight) { + var selectionProp = this._getSelectionForOffset(e, isRight), + cursorLocation = this.get2DCursorLocation(selectionProp), + lineIndex = cursorLocation.lineIndex; + if (lineIndex === 0 || e.metaKey || e.keyCode === 33) { + // if on first line, up cursor goes to start of line + return -selectionProp; + } + var charIndex = cursorLocation.charIndex, + widthBeforeCursor = this._getWidthBeforeCursor(lineIndex, charIndex), + indexOnOtherLine = this._getIndexOnLine(lineIndex - 1, widthBeforeCursor), + textBeforeCursor = this._textLines[lineIndex].slice(0, charIndex), + missingNewlineOffset = this.missingNewlineOffset(lineIndex - 1); + // return a negative offset + return -this._textLines[lineIndex - 1].length + + indexOnOtherLine - textBeforeCursor.length + (1 - missingNewlineOffset); + }, + + /** + * for a given width it founds the matching character. + * @private + */ + _getIndexOnLine: function(lineIndex, width) { + + var line = this._textLines[lineIndex], + lineLeftOffset = this._getLineLeftOffset(lineIndex), + widthOfCharsOnLine = lineLeftOffset, + indexOnLine = 0, charWidth, foundMatch; + + for (var j = 0, jlen = line.length; j < jlen; j++) { + charWidth = this.__charBounds[lineIndex][j].width; + widthOfCharsOnLine += charWidth; + if (widthOfCharsOnLine > width) { + foundMatch = true; + var leftEdge = widthOfCharsOnLine - charWidth, + rightEdge = widthOfCharsOnLine, + offsetFromLeftEdge = Math.abs(leftEdge - width), + offsetFromRightEdge = Math.abs(rightEdge - width); + + indexOnLine = offsetFromRightEdge < offsetFromLeftEdge ? j : (j - 1); + break; + } + } + + // reached end + if (!foundMatch) { + indexOnLine = line.length - 1; + } + + return indexOnLine; + }, + + + /** + * Moves cursor down + * @param {Event} e Event object + */ + moveCursorDown: function(e) { + if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { + return; + } + this._moveCursorUpOrDown('Down', e); + }, + + /** + * Moves cursor up + * @param {Event} e Event object + */ + moveCursorUp: function(e) { + if (this.selectionStart === 0 && this.selectionEnd === 0) { + return; + } + this._moveCursorUpOrDown('Up', e); + }, + + /** + * Moves cursor up or down, fires the events + * @param {String} direction 'Up' or 'Down' + * @param {Event} e Event object + */ + _moveCursorUpOrDown: function(direction, e) { + // getUpCursorOffset + // getDownCursorOffset + var action = 'get' + direction + 'CursorOffset', + offset = this[action](e, this._selectionDirection === 'right'); + if (e.shiftKey) { + this.moveCursorWithShift(offset); + } + else { + this.moveCursorWithoutShift(offset); + } + if (offset !== 0) { + this.setSelectionInBoundaries(); + this.abortCursorAnimation(); + this._currentCursorOpacity = 1; + this.initDelayedCursor(); + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Moves cursor with shift + * @param {Number} offset + */ + moveCursorWithShift: function(offset) { + var newSelection = this._selectionDirection === 'left' + ? this.selectionStart + offset + : this.selectionEnd + offset; + this.setSelectionStartEndWithShift(this.selectionStart, this.selectionEnd, newSelection); + return offset !== 0; + }, + + /** + * Moves cursor up without shift + * @param {Number} offset + */ + moveCursorWithoutShift: function(offset) { + if (offset < 0) { + this.selectionStart += offset; + this.selectionEnd = this.selectionStart; + } + else { + this.selectionEnd += offset; + this.selectionStart = this.selectionEnd; + } + return offset !== 0; + }, + + /** + * Moves cursor left + * @param {Event} e Event object + */ + moveCursorLeft: function(e) { + if (this.selectionStart === 0 && this.selectionEnd === 0) { + return; + } + this._moveCursorLeftOrRight('Left', e); + }, + + /** + * @private + * @return {Boolean} true if a change happened + */ + _move: function(e, prop, direction) { + var newValue; + if (e.altKey) { + newValue = this['findWordBoundary' + direction](this[prop]); + } + else if (e.metaKey || e.keyCode === 35 || e.keyCode === 36 ) { + newValue = this['findLineBoundary' + direction](this[prop]); + } + else { + this[prop] += direction === 'Left' ? -1 : 1; + return true; + } + if (typeof newValue !== undefined && this[prop] !== newValue) { + this[prop] = newValue; + return true; + } + }, + + /** + * @private + */ + _moveLeft: function(e, prop) { + return this._move(e, prop, 'Left'); + }, + + /** + * @private + */ + _moveRight: function(e, prop) { + return this._move(e, prop, 'Right'); + }, + + /** + * Moves cursor left without keeping selection + * @param {Event} e + */ + moveCursorLeftWithoutShift: function(e) { + var change = true; + this._selectionDirection = 'left'; + + // only move cursor when there is no selection, + // otherwise we discard it, and leave cursor on same place + if (this.selectionEnd === this.selectionStart && this.selectionStart !== 0) { + change = this._moveLeft(e, 'selectionStart'); + + } + this.selectionEnd = this.selectionStart; + return change; + }, + + /** + * Moves cursor left while keeping selection + * @param {Event} e + */ + moveCursorLeftWithShift: function(e) { + if (this._selectionDirection === 'right' && this.selectionStart !== this.selectionEnd) { + return this._moveLeft(e, 'selectionEnd'); + } + else if (this.selectionStart !== 0){ + this._selectionDirection = 'left'; + return this._moveLeft(e, 'selectionStart'); + } + }, + + /** + * Moves cursor right + * @param {Event} e Event object + */ + moveCursorRight: function(e) { + if (this.selectionStart >= this._text.length && this.selectionEnd >= this._text.length) { + return; + } + this._moveCursorLeftOrRight('Right', e); + }, + + /** + * Moves cursor right or Left, fires event + * @param {String} direction 'Left', 'Right' + * @param {Event} e Event object + */ + _moveCursorLeftOrRight: function(direction, e) { + var actionName = 'moveCursor' + direction + 'With'; + this._currentCursorOpacity = 1; + + if (e.shiftKey) { + actionName += 'Shift'; + } + else { + actionName += 'outShift'; + } + if (this[actionName](e)) { + this.abortCursorAnimation(); + this.initDelayedCursor(); + this._fireSelectionChanged(); + this._updateTextarea(); + } + }, + + /** + * Moves cursor right while keeping selection + * @param {Event} e + */ + moveCursorRightWithShift: function(e) { + if (this._selectionDirection === 'left' && this.selectionStart !== this.selectionEnd) { + return this._moveRight(e, 'selectionStart'); + } + else if (this.selectionEnd !== this._text.length) { + this._selectionDirection = 'right'; + return this._moveRight(e, 'selectionEnd'); + } + }, + + /** + * Moves cursor right without keeping selection + * @param {Event} e Event object + */ + moveCursorRightWithoutShift: function(e) { + var changed = true; + this._selectionDirection = 'right'; + + if (this.selectionStart === this.selectionEnd) { + changed = this._moveRight(e, 'selectionStart'); + this.selectionEnd = this.selectionStart; + } + else { + this.selectionStart = this.selectionEnd; + } + return changed; + }, + + /** + * Removes characters from start/end + * start/end ar per grapheme position in _text array. + * + * @param {Number} start + * @param {Number} end default to start + 1 + */ + removeChars: function(start, end) { + if (typeof end === 'undefined') { + end = start + 1; + } + this.removeStyleFromTo(start, end); + this._text.splice(start, end - start); + this.text = this._text.join(''); + this.set('dirty', true); + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this._removeExtraneousStyles(); + }, + + /** + * insert characters at start position, before start position. + * start equal 1 it means the text get inserted between actual grapheme 0 and 1 + * if style array is provided, it must be as the same length of text in graphemes + * if end is provided and is bigger than start, old text is replaced. + * start/end ar per grapheme position in _text array. + * + * @param {String} text text to insert + * @param {Array} style array of style objects + * @param {Number} start + * @param {Number} end default to start + 1 + */ + insertChars: function(text, style, start, end) { + if (typeof end === 'undefined') { + end = start; + } + if (end > start) { + this.removeStyleFromTo(start, end); + } + var graphemes = fabric.util.string.graphemeSplit(text); + this.insertNewStyleBlock(graphemes, start, style); + this._text = [].concat(this._text.slice(0, start), graphemes, this._text.slice(end)); + this.text = this._text.join(''); + this.set('dirty', true); + if (this._shouldClearDimensionCache()) { + this.initDimensions(); + this.setCoords(); + } + this._removeExtraneousStyles(); + }, + +}); + + +/* _TO_SVG_START_ */ +(function() { + var toFixed = fabric.util.toFixed, + multipleSpacesRegex = / +/g; + + fabric.util.object.extend(fabric.Text.prototype, /** @lends fabric.Text.prototype */ { + + /** + * Returns SVG representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + _toSVG: function() { + var offsets = this._getSVGLeftTopOffsets(), + textAndBg = this._getSVGTextAndBg(offsets.textTop, offsets.textLeft); + return this._wrapSVGTextAndBg(textAndBg); + }, + + /** + * Returns svg representation of an instance + * @param {Function} [reviver] Method for further parsing of svg representation. + * @return {String} svg representation of an instance + */ + toSVG: function(reviver) { + return this._createBaseSVGMarkup( + this._toSVG(), + { reviver: reviver, noStyle: true, withShadow: true } + ); + }, + + /** + * @private + */ + _getSVGLeftTopOffsets: function() { + return { + textLeft: -this.width / 2, + textTop: -this.height / 2, + lineTop: this.getHeightOfLine(0) + }; + }, + + /** + * @private + */ + _wrapSVGTextAndBg: function(textAndBg) { + var noShadow = true, + textDecoration = this.getSvgTextDecoration(this); + return [ + textAndBg.textBgRects.join(''), + '\t\t', + textAndBg.textSpans.join(''), + '\n' + ]; + }, + + /** + * @private + * @param {Number} textTopOffset Text top offset + * @param {Number} textLeftOffset Text left offset + * @return {Object} + */ + _getSVGTextAndBg: function(textTopOffset, textLeftOffset) { + var textSpans = [], + textBgRects = [], + height = textTopOffset, lineOffset; + // bounding-box background + this._setSVGBg(textBgRects); + + // text and text-background + for (var i = 0, len = this._textLines.length; i < len; i++) { + lineOffset = this._getLineLeftOffset(i); + if (this.textBackgroundColor || this.styleHas('textBackgroundColor', i)) { + this._setSVGTextLineBg(textBgRects, i, textLeftOffset + lineOffset, height); + } + this._setSVGTextLineText(textSpans, i, textLeftOffset + lineOffset, height); + height += this.getHeightOfLine(i); + } + + return { + textSpans: textSpans, + textBgRects: textBgRects + }; + }, + + /** + * @private + */ + _createTextCharSpan: function(_char, styleDecl, left, top) { + var shouldUseWhitespace = _char !== _char.trim() || _char.match(multipleSpacesRegex), + styleProps = this.getSvgSpanStyles(styleDecl, shouldUseWhitespace), + fillStyles = styleProps ? 'style="' + styleProps + '"' : '', + dy = styleDecl.deltaY, dySpan = '', + NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + if (dy) { + dySpan = ' dy="' + toFixed(dy, NUM_FRACTION_DIGITS) + '" '; + } + return [ + '', + fabric.util.string.escapeXml(_char), + '' + ].join(''); + }, + + _setSVGTextLineText: function(textSpans, lineIndex, textLeftOffset, textTopOffset) { + // set proper line offset + var lineHeight = this.getHeightOfLine(lineIndex), + isJustify = this.textAlign.indexOf('justify') !== -1, + actualStyle, + nextStyle, + charsToRender = '', + charBox, style, + boxWidth = 0, + line = this._textLines[lineIndex], + timeToRender; + + textTopOffset += lineHeight * (1 - this._fontSizeFraction) / this.lineHeight; + for (var i = 0, len = line.length - 1; i <= len; i++) { + timeToRender = i === len || this.charSpacing; + charsToRender += line[i]; + charBox = this.__charBounds[lineIndex][i]; + if (boxWidth === 0) { + textLeftOffset += charBox.kernedWidth - charBox.width; + boxWidth += charBox.width; + } + else { + boxWidth += charBox.kernedWidth; + } + if (isJustify && !timeToRender) { + if (this._reSpaceAndTab.test(line[i])) { + timeToRender = true; + } + } + if (!timeToRender) { + // if we have charSpacing, we render char by char + actualStyle = actualStyle || this.getCompleteStyleDeclaration(lineIndex, i); + nextStyle = this.getCompleteStyleDeclaration(lineIndex, i + 1); + timeToRender = this._hasStyleChangedForSvg(actualStyle, nextStyle); + } + if (timeToRender) { + style = this._getStyleDeclaration(lineIndex, i) || { }; + textSpans.push(this._createTextCharSpan(charsToRender, style, textLeftOffset, textTopOffset)); + charsToRender = ''; + actualStyle = nextStyle; + textLeftOffset += boxWidth; + boxWidth = 0; + } + } + }, + + _pushTextBgRect: function(textBgRects, color, left, top, width, height) { + var NUM_FRACTION_DIGITS = fabric.Object.NUM_FRACTION_DIGITS; + textBgRects.push( + '\t\t\n'); + }, + + _setSVGTextLineBg: function(textBgRects, i, leftOffset, textTopOffset) { + var line = this._textLines[i], + heightOfLine = this.getHeightOfLine(i) / this.lineHeight, + boxWidth = 0, + boxStart = 0, + charBox, currentColor, + lastColor = this.getValueOfPropertyAt(i, 0, 'textBackgroundColor'); + for (var j = 0, jlen = line.length; j < jlen; j++) { + charBox = this.__charBounds[i][j]; + currentColor = this.getValueOfPropertyAt(i, j, 'textBackgroundColor'); + if (currentColor !== lastColor) { + lastColor && this._pushTextBgRect(textBgRects, lastColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + boxStart = charBox.left; + boxWidth = charBox.width; + lastColor = currentColor; + } + else { + boxWidth += charBox.kernedWidth; + } + } + currentColor && this._pushTextBgRect(textBgRects, currentColor, leftOffset + boxStart, + textTopOffset, boxWidth, heightOfLine); + }, + + /** + * Adobe Illustrator (at least CS5) is unable to render rgba()-based fill values + * we work around it by "moving" alpha channel into opacity attribute and setting fill's alpha to 1 + * + * @private + * @param {*} value + * @return {String} + */ + _getFillAttributes: function(value) { + var fillColor = (value && typeof value === 'string') ? new fabric.Color(value) : ''; + if (!fillColor || !fillColor.getSource() || fillColor.getAlpha() === 1) { + return 'fill="' + value + '"'; + } + return 'opacity="' + fillColor.getAlpha() + '" fill="' + fillColor.setAlpha(1).toRgb() + '"'; + }, + + /** + * @private + */ + _getSVGLineTopOffset: function(lineIndex) { + var lineTopOffset = 0, lastHeight = 0; + for (var j = 0; j < lineIndex; j++) { + lineTopOffset += this.getHeightOfLine(j); + } + lastHeight = this.getHeightOfLine(j); + return { + lineTop: lineTopOffset, + offset: (this._fontSizeMult - this._fontSizeFraction) * lastHeight / (this.lineHeight * this._fontSizeMult) + }; + }, + + /** + * Returns styles-string for svg-export + * @param {Boolean} skipShadow a boolean to skip shadow filter output + * @return {String} + */ + getSvgStyles: function(skipShadow) { + var svgStyle = fabric.Object.prototype.getSvgStyles.call(this, skipShadow); + return svgStyle + ' white-space: pre;'; + }, + }); +})(); +/* _TO_SVG_END_ */ + + +(function(global) { + + 'use strict'; + + var fabric = global.fabric || (global.fabric = {}); + + /** + * Textbox class, based on IText, allows the user to resize the text rectangle + * and wraps lines automatically. Textboxes have their Y scaling locked, the + * user can only change width. Height is adjusted automatically based on the + * wrapping of lines. + * @class fabric.Textbox + * @extends fabric.IText + * @mixes fabric.Observable + * @return {fabric.Textbox} thisArg + * @see {@link fabric.Textbox#initialize} for constructor definition + */ + fabric.Textbox = fabric.util.createClass(fabric.IText, fabric.Observable, { + + /** + * Type of an object + * @type String + * @default + */ + type: 'textbox', + + /** + * Minimum width of textbox, in pixels. + * @type Number + * @default + */ + minWidth: 20, + + /** + * Minimum calculated width of a textbox, in pixels. + * fixed to 2 so that an empty textbox cannot go to 0 + * and is still selectable without text. + * @type Number + * @default + */ + dynamicMinWidth: 2, + + /** + * Cached array of text wrapping. + * @type Array + */ + __cachedLines: null, + + /** + * Override standard Object class values + */ + lockScalingFlip: true, + + /** + * Override standard Object class values + * Textbox needs this on false + */ + noScaleCache: false, + + /** + * Properties which when set cause object to change dimensions + * @type Object + * @private + */ + _dimensionAffectingProps: fabric.Text.prototype._dimensionAffectingProps.concat('width'), + + /** + * Use this regular expression to split strings in breakable lines + * @private + */ + _wordJoiners: /[ \t\r]/, + + /** + * Use this boolean property in order to split strings that have no white space concept. + * this is a cheap way to help with chinese/japanese + * @type Boolean + * @since 2.6.0 + */ + splitByGrapheme: false, + + /** + * Unlike superclass's version of this function, Textbox does not update + * its width. + * @private + * @override + */ + initDimensions: function() { + if (this.__skipDimension) { + return; + } + this.isEditing && this.initDelayedCursor(); + this.clearContextTop(); + this._clearCache(); + // clear dynamicMinWidth as it will be different after we re-wrap line + this.dynamicMinWidth = 0; + // wrap lines + this._styleMap = this._generateStyleMap(this._splitText()); + // if after wrapping, the width is smaller than dynamicMinWidth, change the width and re-wrap + if (this.dynamicMinWidth > this.width) { + this._set('width', this.dynamicMinWidth); + } + if (this.textAlign.indexOf('justify') !== -1) { + // once text is measured we need to make space fatter to make justified text. + this.enlargeSpaces(); + } + // clear cache and re-calculate height + this.height = this.calcTextHeight(); + this.saveState({ propertySet: '_dimensionAffectingProps' }); + }, + + /** + * Generate an object that translates the style object so that it is + * broken up by visual lines (new lines and automatic wrapping). + * The original text styles object is broken up by actual lines (new lines only), + * which is only sufficient for Text / IText + * @private + */ + _generateStyleMap: function(textInfo) { + var realLineCount = 0, + realLineCharCount = 0, + charCount = 0, + map = {}; + + for (var i = 0; i < textInfo.graphemeLines.length; i++) { + if (textInfo.graphemeText[charCount] === '\n' && i > 0) { + realLineCharCount = 0; + charCount++; + realLineCount++; + } + else if (!this.splitByGrapheme && this._reSpaceAndTab.test(textInfo.graphemeText[charCount]) && i > 0) { + // this case deals with space's that are removed from end of lines when wrapping + realLineCharCount++; + charCount++; + } + + map[i] = { line: realLineCount, offset: realLineCharCount }; + + charCount += textInfo.graphemeLines[i].length; + realLineCharCount += textInfo.graphemeLines[i].length; + } + + return map; + }, + + /** + * Returns true if object has a style property or has it on a specified line + * @param {Number} lineIndex + * @return {Boolean} + */ + styleHas: function(property, lineIndex) { + if (this._styleMap && !this.isWrapping) { + var map = this._styleMap[lineIndex]; + if (map) { + lineIndex = map.line; + } + } + return fabric.Text.prototype.styleHas.call(this, property, lineIndex); + }, + + /** + * Returns true if object has no styling or no styling in a line + * @param {Number} lineIndex , lineIndex is on wrapped lines. + * @return {Boolean} + */ + isEmptyStyles: function(lineIndex) { + if (!this.styles) { + return true; + } + var offset = 0, nextLineIndex = lineIndex + 1, nextOffset, obj, shouldLimit = false, + map = this._styleMap[lineIndex], mapNextLine = this._styleMap[lineIndex + 1]; + if (map) { + lineIndex = map.line; + offset = map.offset; + } + if (mapNextLine) { + nextLineIndex = mapNextLine.line; + shouldLimit = nextLineIndex === lineIndex; + nextOffset = mapNextLine.offset; + } + obj = typeof lineIndex === 'undefined' ? this.styles : { line: this.styles[lineIndex] }; + for (var p1 in obj) { + for (var p2 in obj[p1]) { + if (p2 >= offset && (!shouldLimit || p2 < nextOffset)) { + // eslint-disable-next-line no-unused-vars + for (var p3 in obj[p1][p2]) { + return false; + } + } + } + } + return true; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _getStyleDeclaration: function(lineIndex, charIndex) { + if (this._styleMap && !this.isWrapping) { + var map = this._styleMap[lineIndex]; + if (!map) { + return null; + } + lineIndex = map.line; + charIndex = map.offset + charIndex; + } + return this.callSuper('_getStyleDeclaration', lineIndex, charIndex); + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @param {Object} style + * @private + */ + _setStyleDeclaration: function(lineIndex, charIndex, style) { + var map = this._styleMap[lineIndex]; + lineIndex = map.line; + charIndex = map.offset + charIndex; + + this.styles[lineIndex][charIndex] = style; + }, + + /** + * @param {Number} lineIndex + * @param {Number} charIndex + * @private + */ + _deleteStyleDeclaration: function(lineIndex, charIndex) { + var map = this._styleMap[lineIndex]; + lineIndex = map.line; + charIndex = map.offset + charIndex; + delete this.styles[lineIndex][charIndex]; + }, + + /** + * probably broken need a fix + * Returns the real style line that correspond to the wrapped lineIndex line + * Used just to verify if the line does exist or not. + * @param {Number} lineIndex + * @returns {Boolean} if the line exists or not + * @private + */ + _getLineStyle: function(lineIndex) { + var map = this._styleMap[lineIndex]; + return !!this.styles[map.line]; + }, + + /** + * Set the line style to an empty object so that is initialized + * @param {Number} lineIndex + * @param {Object} style + * @private + */ + _setLineStyle: function(lineIndex) { + var map = this._styleMap[lineIndex]; + this.styles[map.line] = {}; + }, + + /** + * Wraps text using the 'width' property of Textbox. First this function + * splits text on newlines, so we preserve newlines entered by the user. + * Then it wraps each line using the width of the Textbox by calling + * _wrapLine(). + * @param {Array} lines The string array of text that is split into lines + * @param {Number} desiredWidth width you want to wrap to + * @returns {Array} Array of lines + */ + _wrapText: function(lines, desiredWidth) { + var wrapped = [], i; + this.isWrapping = true; + for (i = 0; i < lines.length; i++) { + wrapped = wrapped.concat(this._wrapLine(lines[i], i, desiredWidth)); + } + this.isWrapping = false; + return wrapped; + }, + + /** + * Helper function to measure a string of text, given its lineIndex and charIndex offset + * it gets called when charBounds are not available yet. + * @param {CanvasRenderingContext2D} ctx + * @param {String} text + * @param {number} lineIndex + * @param {number} charOffset + * @returns {number} + * @private + */ + _measureWord: function(word, lineIndex, charOffset) { + var width = 0, prevGrapheme, skipLeft = true; + charOffset = charOffset || 0; + for (var i = 0, len = word.length; i < len; i++) { + var box = this._getGraphemeBox(word[i], lineIndex, i + charOffset, prevGrapheme, skipLeft); + width += box.kernedWidth; + prevGrapheme = word[i]; + } + return width; + }, + + /** + * Wraps a line of text using the width of the Textbox and a context. + * @param {Array} line The grapheme array that represent the line + * @param {Number} lineIndex + * @param {Number} desiredWidth width you want to wrap the line to + * @param {Number} reservedSpace space to remove from wrapping for custom functionalities + * @returns {Array} Array of line(s) into which the given text is wrapped + * to. + */ + _wrapLine: function(_line, lineIndex, desiredWidth, reservedSpace) { + var lineWidth = 0, + splitByGrapheme = this.splitByGrapheme, + graphemeLines = [], + line = [], + // spaces in different languages? + words = splitByGrapheme ? fabric.util.string.graphemeSplit(_line) : _line.split(this._wordJoiners), + word = '', + offset = 0, + infix = splitByGrapheme ? '' : ' ', + wordWidth = 0, + infixWidth = 0, + largestWordWidth = 0, + lineJustStarted = true, + additionalSpace = this._getWidthOfCharSpacing(), + reservedSpace = reservedSpace || 0; + // fix a difference between split and graphemeSplit + if (words.length === 0) { + words.push([]); + } + desiredWidth -= reservedSpace; + for (var i = 0; i < words.length; i++) { + // if using splitByGrapheme words are already in graphemes. + word = splitByGrapheme ? words[i] : fabric.util.string.graphemeSplit(words[i]); + wordWidth = this._measureWord(word, lineIndex, offset); + offset += word.length; + + lineWidth += infixWidth + wordWidth - additionalSpace; + if (lineWidth > desiredWidth && !lineJustStarted) { + graphemeLines.push(line); + line = []; + lineWidth = wordWidth; + lineJustStarted = true; + } + else { + lineWidth += additionalSpace; + } + + if (!lineJustStarted && !splitByGrapheme) { + line.push(infix); + } + line = line.concat(word); + + infixWidth = splitByGrapheme ? 0 : this._measureWord([infix], lineIndex, offset); + offset++; + lineJustStarted = false; + // keep track of largest word + if (wordWidth > largestWordWidth) { + largestWordWidth = wordWidth; + } + } + + i && graphemeLines.push(line); + + if (largestWordWidth + reservedSpace > this.dynamicMinWidth) { + this.dynamicMinWidth = largestWordWidth - additionalSpace + reservedSpace; + } + return graphemeLines; + }, + + /** + * Detect if the text line is ended with an hard break + * text and itext do not have wrapping, return false + * @param {Number} lineIndex text to split + * @return {Boolean} + */ + isEndOfWrapping: function(lineIndex) { + if (!this._styleMap[lineIndex + 1]) { + // is last line, return true; + return true; + } + if (this._styleMap[lineIndex + 1].line !== this._styleMap[lineIndex].line) { + // this is last line before a line break, return true; + return true; + } + return false; + }, + + /** + * Detect if a line has a linebreak and so we need to account for it when moving + * and counting style. + * @return Number + */ + missingNewlineOffset: function(lineIndex) { + if (this.splitByGrapheme) { + return this.isEndOfWrapping(lineIndex) ? 1 : 0; + } + return 1; + }, + + /** + * Gets lines of text to render in the Textbox. This function calculates + * text wrapping on the fly every time it is called. + * @param {String} text text to split + * @returns {Array} Array of lines in the Textbox. + * @override + */ + _splitTextIntoLines: function(text) { + var newText = fabric.Text.prototype._splitTextIntoLines.call(this, text), + graphemeLines = this._wrapText(newText.lines, this.width), + lines = new Array(graphemeLines.length); + for (var i = 0; i < graphemeLines.length; i++) { + lines[i] = graphemeLines[i].join(''); + } + newText.lines = lines; + newText.graphemeLines = graphemeLines; + return newText; + }, + + getMinWidth: function() { + return Math.max(this.minWidth, this.dynamicMinWidth); + }, + + _removeExtraneousStyles: function() { + var linesToKeep = {}; + for (var prop in this._styleMap) { + if (this._textLines[prop]) { + linesToKeep[this._styleMap[prop].line] = 1; + } + } + for (var prop in this.styles) { + if (!linesToKeep[prop]) { + delete this.styles[prop]; + } + } + }, + + /** + * Returns object representation of an instance + * @method toObject + * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output + * @return {Object} object representation of an instance + */ + toObject: function(propertiesToInclude) { + return this.callSuper('toObject', ['minWidth', 'splitByGrapheme'].concat(propertiesToInclude)); + } + }); + + /** + * Returns fabric.Textbox instance from an object representation + * @static + * @memberOf fabric.Textbox + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when an fabric.Textbox instance is created + */ + fabric.Textbox.fromObject = function(object, callback) { + return fabric.Object._fromObject('Textbox', object, callback, 'text'); + }; +})(typeof exports !== 'undefined' ? exports : this); + + +(function() { + + var controlsUtils = fabric.controlsUtils, + scaleSkewStyleHandler = controlsUtils.scaleSkewCursorStyleHandler, + scaleStyleHandler = controlsUtils.scaleCursorStyleHandler, + scalingEqually = controlsUtils.scalingEqually, + scalingYOrSkewingX = controlsUtils.scalingYOrSkewingX, + scalingXOrSkewingY = controlsUtils.scalingXOrSkewingY, + scaleOrSkewActionName = controlsUtils.scaleOrSkewActionName, + objectControls = fabric.Object.prototype.controls; + + objectControls.ml = new fabric.Control({ + x: -0.5, + y: 0, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingXOrSkewingY, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mr = new fabric.Control({ + x: 0.5, + y: 0, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingXOrSkewingY, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mb = new fabric.Control({ + x: 0, + y: 0.5, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingYOrSkewingX, + getActionName: scaleOrSkewActionName, + }); + + objectControls.mt = new fabric.Control({ + x: 0, + y: -0.5, + cursorStyleHandler: scaleSkewStyleHandler, + actionHandler: scalingYOrSkewingX, + getActionName: scaleOrSkewActionName, + }); + + objectControls.tl = new fabric.Control({ + x: -0.5, + y: -0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.tr = new fabric.Control({ + x: 0.5, + y: -0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.bl = new fabric.Control({ + x: -0.5, + y: 0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.br = new fabric.Control({ + x: 0.5, + y: 0.5, + cursorStyleHandler: scaleStyleHandler, + actionHandler: scalingEqually + }); + + objectControls.mtr = new fabric.Control({ + x: 0, + y: -0.5, + actionHandler: controlsUtils.rotationWithSnapping, + cursorStyleHandler: controlsUtils.rotationStyleHandler, + offsetY: -40, + withConnection: true, + actionName: 'rotate', + }); + + if (fabric.Textbox) { + // this is breaking the prototype inheritance, no time / ideas to fix it. + // is important to document that if you want to have all objects to have a + // specific custom control, you have to add it to Object prototype and to Textbox + // prototype. The controls are shared as references. So changes to control `tr` + // can still apply to all objects if needed. + var textBoxControls = fabric.Textbox.prototype.controls = { }; + + textBoxControls.mtr = objectControls.mtr; + textBoxControls.tr = objectControls.tr; + textBoxControls.br = objectControls.br; + textBoxControls.tl = objectControls.tl; + textBoxControls.bl = objectControls.bl; + textBoxControls.mt = objectControls.mt; + textBoxControls.mb = objectControls.mb; + + textBoxControls.mr = new fabric.Control({ + x: 0.5, + y: 0, + actionHandler: controlsUtils.changeWidth, + cursorStyleHandler: scaleSkewStyleHandler, + actionName: 'resizing', + }); + + textBoxControls.ml = new fabric.Control({ + x: -0.5, + y: 0, + actionHandler: controlsUtils.changeWidth, + cursorStyleHandler: scaleSkewStyleHandler, + actionName: 'resizing', + }); + } +})(); + diff --git a/public/js/guideLines.js b/public/js/guideLines.js new file mode 100644 index 0000000..91080b2 --- /dev/null +++ b/public/js/guideLines.js @@ -0,0 +1,219 @@ +function initAligningGuidelines(canvas) { + + var ctx = canvas.getSelectionContext(), + //aligningLineOffset = 0, + //aligningLineMargin = 0, + //aligningLineWidth = 1, + //aligningLineColor = 'rgb(0,0,0)', + aligningLineOffset = 4, + aligningLineMargin = 5,//吸引 + aligningLineWidth = 6, + aligningLineColor = '#6637FF', + viewportTransform, + zoom = 1; + + function drawVerticalLine(coords) { + drawLine( + coords.x + 0.5, + coords.y1 > coords.y2 ? coords.y2 : coords.y1, + coords.x + 0.5, + coords.y2 > coords.y1 ? coords.y2 : coords.y1); + } + + function drawHorizontalLine(coords) { + drawLine( + coords.x1 > coords.x2 ? coords.x2 : coords.x1, + coords.y + 0.5, + coords.x2 > coords.x1 ? coords.x2 : coords.x1, + coords.y + 0.5); + } + + function drawLine(x1, y1, x2, y2) { + ctx.save(); + ctx.lineWidth = aligningLineWidth; + ctx.strokeStyle = aligningLineColor; + ctx.beginPath(); + let width = canvas.getWidth(); + let height = canvas.getHeight(); + ctx.moveTo(width / 2 - ( ( width / 2 - (x1) ) * zoom ) , height / 2 - ( ( height / 2 - (y1) ) * zoom ));//哪个点散射出来 + ctx.lineTo(width / 2 - ( ( width / 2 - (x2) ) * zoom ), height / 2 - ( ( height / 2 - (y2) ) * zoom ));//结束到哪个点 + //修复中心放大后的位置偏移 2021-10-24 + ctx.stroke(); + ctx.restore(); + } + + function isInRange(value1, value2) { + value1 = Math.round(value1); + value2 = Math.round(value2); + for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) { + if (i === value2) { + return true; + } + } + return false; + } + + var verticalLines = [], + horizontalLines = []; + + canvas.on('mouse:down', function () { + viewportTransform = canvas.viewportTransform; + zoom = canvas.getZoom(); + }); + + canvas.on('object:moving', function(e) { + + var activeObject = e.target, + canvasObjects = canvas.getObjects(), + activeObjectCenter = activeObject.getCenterPoint(), + activeObjectLeft = activeObjectCenter.x, + activeObjectTop = activeObjectCenter.y, + activeObjectBoundingRect = activeObject.getBoundingRect(), + activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3], + activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0], + horizontalInTheRange = false, + verticalInTheRange = false, + transform = canvas._currentTransform; + + if (!transform) return; + + // It should be trivial to DRY this up by encapsulating (repeating) creation of x1, x2, y1, and y2 into functions, + // but we're not doing it here for perf. reasons -- as this a function that's invoked on every mouse move + + for (var i = canvasObjects.length; i--; ) { + + if (canvasObjects[i] === activeObject) continue; + + var objectCenter = canvasObjects[i].getCenterPoint(), + objectLeft = objectCenter.x, + objectTop = objectCenter.y, + objectBoundingRect = canvasObjects[i].getBoundingRect(), + objectHeight = objectBoundingRect.height / viewportTransform[3], + objectWidth = objectBoundingRect.width / viewportTransform[0]; + + // snap by the horizontal center line + if (isInRange(objectLeft, activeObjectLeft)) { + verticalInTheRange = true; + verticalLines.push({ + x: objectLeft, + y1: (objectTop < activeObjectTop) + ? (objectTop - objectHeight / 2 - aligningLineOffset) + : (objectTop + objectHeight / 2 + aligningLineOffset), + y2: (activeObjectTop > objectTop) + ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) + : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft, activeObjectTop), 'center', 'center'); + } + + // snap by the left edge + if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) { + verticalInTheRange = true; + verticalLines.push({ + x: objectLeft - objectWidth / 2, + y1: (objectTop < activeObjectTop) + ? (objectTop - objectHeight / 2 - aligningLineOffset) + : (objectTop + objectHeight / 2 + aligningLineOffset), + y2: (activeObjectTop > objectTop) + ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) + : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop), 'center', 'center'); + } + + // snap by the right edge + if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) { + verticalInTheRange = true; + verticalLines.push({ + x: objectLeft + objectWidth / 2, + y1: (objectTop < activeObjectTop) + ? (objectTop - objectHeight / 2 - aligningLineOffset) + : (objectTop + objectHeight / 2 + aligningLineOffset), + y2: (activeObjectTop > objectTop) + ? (activeObjectTop + activeObjectHeight / 2 + aligningLineOffset) + : (activeObjectTop - activeObjectHeight / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop), 'center', 'center'); + } + + // snap by the vertical center line + if (isInRange(objectTop, activeObjectTop)) { + horizontalInTheRange = true; + horizontalLines.push({ + y: objectTop, + x1: (objectLeft < activeObjectLeft) + ? (objectLeft - objectWidth / 2 - aligningLineOffset) + : (objectLeft + objectWidth / 2 + aligningLineOffset), + x2: (activeObjectLeft > objectLeft) + ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) + : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop), 'center', 'center'); + } + + // snap by the top edge + if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) { + horizontalInTheRange = true; + horizontalLines.push({ + y: objectTop - objectHeight / 2, + x1: (objectLeft < activeObjectLeft) + ? (objectLeft - objectWidth / 2 - aligningLineOffset) + : (objectLeft + objectWidth / 2 + aligningLineOffset), + x2: (activeObjectLeft > objectLeft) + ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) + : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2), 'center', 'center'); + } + + // snap by the bottom edge + if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) { + horizontalInTheRange = true; + horizontalLines.push({ + y: objectTop + objectHeight / 2, + x1: (objectLeft < activeObjectLeft) + ? (objectLeft - objectWidth / 2 - aligningLineOffset) + : (objectLeft + objectWidth / 2 + aligningLineOffset), + x2: (activeObjectLeft > objectLeft) + ? (activeObjectLeft + activeObjectWidth / 2 + aligningLineOffset) + : (activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset) + }); + activeObject.setPositionByOrigin(new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2), 'center', 'center'); + } + } + + if (!horizontalInTheRange) { + horizontalLines.length = 0; + } + + if (!verticalInTheRange) { + verticalLines.length = 0; + } + }); + + canvas.on('before:render', function() { + + if(canvas&&canvas.contextTop) + { + canvas.clearContext(canvas.contextTop); + } + + + }); + + canvas.on('after:render', function() { + for (var i = verticalLines.length; i--; ) { + drawVerticalLine(verticalLines[i]); + } + for (var i = horizontalLines.length; i--; ) { + drawHorizontalLine(horizontalLines[i]); + } + verticalLines.length = horizontalLines.length = 0; + }); + + canvas.on('mouse:up', function() { + verticalLines.length = horizontalLines.length = 0; + canvas.renderAll(); + }); +} + diff --git a/yarn.lock b/yarn.lock index b82ecf9..f2ecfcb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,40 +4,41 @@ "7zip-bin@~5.1.1": version "5.1.1" - resolved "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" + resolved "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.1.1.tgz" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== "@develar/schema-utils@~2.6.5": version "2.6.5" - resolved "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6" + resolved "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz" integrity sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig== dependencies: ajv "^6.12.0" ajv-keywords "^3.4.1" -"@electron/get@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" - integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== +"@electron/get@^1.13.0", "@electron/get@^1.6.0": + version "1.14.1" + resolved "https://registry.npmmirror.com/@electron/get/-/get-1.14.1.tgz" + integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw== dependencies: debug "^4.1.1" env-paths "^2.2.0" fs-extra "^8.1.0" - got "^11.8.5" + got "^9.6.0" progress "^2.0.3" semver "^6.2.0" sumchecker "^3.0.1" optionalDependencies: global-agent "^3.0.0" + global-tunnel-ng "^2.7.1" "@electron/remote@^2.0.8": version "2.0.8" resolved "https://registry.npmmirror.com/@electron/remote/-/remote-2.0.8.tgz" integrity sha512-P10v3+iFCIvEPeYzTWWGwwHmqWnjoh8RYnbtZAb3RlQefy4guagzIwcWtfftABIfm6JJTNQf4WPSKWZOpLmHXw== -"@electron/universal@1.2.1": +"@electron/universal@1.2.1", "@electron/universal@^1.2.1": version "1.2.1" - resolved "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339" + resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz" integrity sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ== dependencies: "@malept/cross-spawn-promise" "^1.1.0" @@ -48,18 +49,6 @@ minimatch "^3.0.4" plist "^3.0.4" -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" resolved "https://registry.npmmirror.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz" @@ -69,7 +58,7 @@ "@malept/flatpak-bundler@^0.4.0": version "0.4.0" - resolved "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz#e8a32c30a95d20c2b1bb635cc580981a06389858" + resolved "https://registry.npmmirror.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz" integrity sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q== dependencies: debug "^4.1.1" @@ -77,120 +66,87 @@ lodash "^4.17.15" tmp-promise "^3.0.2" -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-0.14.0.tgz" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== dependencies: - defer-to-connect "^2.0.0" + defer-to-connect "^1.0.1" "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmmirror.com/@tootallnate/once/-/once-2.0.0.tgz" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - "@types/debug@^4.1.6": - version "4.1.12" - resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" - integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + version "4.1.7" + resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.7.tgz" + integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== dependencies: "@types/ms" "*" "@types/fs-extra@^9.0.11": version "9.0.13" - resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + resolved "https://registry.npmmirror.com/@types/fs-extra/-/fs-extra-9.0.13.tgz" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== dependencies: "@types/node" "*" "@types/glob@^7.1.1": version "7.2.0" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + resolved "https://registry.npmmirror.com/@types/glob/-/glob-7.2.0.tgz" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" "@types/node" "*" -"@types/http-cache-semantics@*": - version "4.0.4" - resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" - integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== - -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - dependencies: - "@types/node" "*" - "@types/minimatch@*": - version "6.0.0" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-6.0.0.tgz#4d207b1cc941367bdcd195a3a781a7e4fc3b1e03" - integrity sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA== - dependencies: - minimatch "*" + version "5.1.2" + resolved "https://registry.npmmirror.com/@types/minimatch/-/minimatch-5.1.2.tgz" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/ms@*": - version "2.1.0" - resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" - integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + version "0.7.31" + resolved "https://registry.npmmirror.com/@types/ms/-/ms-0.7.31.tgz" + integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": version "18.7.18" resolved "https://registry.npmmirror.com/@types/node/-/node-18.7.18.tgz" integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== -"@types/node@^18.11.18": - version "18.19.130" - resolved "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz#da4c6324793a79defb7a62cba3947ec5add00d59" - integrity sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg== - dependencies: - undici-types "~5.26.4" +"@types/node@^14.6.2": + version "14.18.29" + resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.29.tgz" + integrity sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A== "@types/plist@^3.0.1": - version "3.0.5" - resolved "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz#9a0c49c0f9886c8c8696a7904dd703f6284036e0" - integrity sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA== + version "3.0.2" + resolved "https://registry.npmmirror.com/@types/plist/-/plist-3.0.2.tgz" + integrity sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw== dependencies: "@types/node" "*" xmlbuilder ">=11.0.1" -"@types/responselike@^1.0.0": - version "1.0.3" - resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" - integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== - dependencies: - "@types/node" "*" - "@types/verror@^1.10.3": - version "1.10.11" - resolved "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb" - integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== + version "1.10.6" + resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.6.tgz" + integrity sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ== "@types/yargs-parser@*": - version "21.0.3" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" - integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + version "21.0.0" + resolved "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.1": - version "17.0.34" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.34.tgz#1c2f9635b71d5401827373a01ce2e8a7670ea839" - integrity sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A== + version "17.0.12" + resolved "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.12.tgz" + integrity sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ== dependencies: "@types/yargs-parser" "*" @@ -201,11 +157,6 @@ dependencies: "@types/node" "*" -"@xmldom/xmldom@^0.8.8": - version "0.8.11" - resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608" - integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw== - agent-base@6: version "6.0.2" resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz" @@ -215,12 +166,12 @@ agent-base@6: ajv-keywords@^3.4.1: version "3.5.2" - resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.10.0, ajv@^6.12.0: version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -228,6 +179,13 @@ ajv@^6.10.0, ajv@^6.12.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz" @@ -242,13 +200,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: app-builder-bin@4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0" + resolved "https://registry.npmmirror.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz" integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA== -app-builder-lib@23.6.0: - version "23.6.0" - resolved "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.6.0.tgz#03cade02838c077db99d86212d61c5fc1d6da1a8" - integrity sha512-dQYDuqm/rmy8GSCE6Xl/3ShJg6Ab4bZJMT8KaTKGzT436gl1DN4REP3FCWfXoh75qGTJ+u+WsdnnpO9Jl8nyMA== +app-builder-lib@23.3.3: + version "23.3.3" + resolved "https://registry.npmmirror.com/app-builder-lib/-/app-builder-lib-23.3.3.tgz" + integrity sha512-m0+M53+HYMzqKxwNQZT143K7WwXEGUy9LY31l8dJphXx2P/FQod615mVbxHyqbDCG4J5bHdWm21qZ0e2DVY6CQ== dependencies: "7zip-bin" "~5.1.1" "@develar/schema-utils" "~2.6.5" @@ -256,13 +214,13 @@ app-builder-lib@23.6.0: "@malept/flatpak-bundler" "^0.4.0" async-exit-hook "^2.0.1" bluebird-lst "^1.0.9" - builder-util "23.6.0" - builder-util-runtime "9.1.1" + builder-util "23.3.3" + builder-util-runtime "9.0.3" chromium-pickle-js "^0.2.0" debug "^4.3.4" ejs "^3.1.7" electron-osx-sign "^0.6.0" - electron-publish "23.6.0" + electron-publish "23.3.3" form-data "^4.0.0" fs-extra "^10.1.0" hosted-git-info "^4.1.0" @@ -279,12 +237,12 @@ app-builder-lib@23.6.0: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== asar@^3.1.0: version "3.2.0" - resolved "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221" + resolved "https://registry.npmmirror.com/asar/-/asar-3.2.0.tgz" integrity sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg== dependencies: chromium-pickle-js "^0.2.0" @@ -296,7 +254,7 @@ asar@^3.1.0: assert-plus@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + resolved "https://registry.npmmirror.com/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== astral-regex@^2.0.0: @@ -306,22 +264,17 @@ astral-regex@^2.0.0: async-exit-hook@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" + resolved "https://registry.npmmirror.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz" integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== -async@^3.2.2: +async@^3.2.2, async@^3.2.3: version "3.2.4" resolved "https://registry.npmmirror.com/async/-/async-3.2.4.tgz" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== -async@^3.2.6: - version "3.2.6" - resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== at-least-node@^1.0.0: @@ -329,6 +282,11 @@ at-least-node@^1.0.0: resolved "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +author-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/author-regex/-/author-regex-1.0.0.tgz" + integrity sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz" @@ -341,14 +299,14 @@ base64-js@^1.3.1, base64-js@^1.5.1: bluebird-lst@^1.0.9: version "1.0.9" - resolved "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz#a64a0e4365658b9ab5fe875eb9dfb694189bb41c" + resolved "https://registry.npmmirror.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz" integrity sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw== dependencies: bluebird "^3.5.5" -bluebird@^3.5.0, bluebird@^3.5.5: +bluebird@^3.1.1, bluebird@^3.5.0, bluebird@^3.5.5: version "3.7.2" - resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== boolean@^3.0.1: @@ -356,6 +314,20 @@ boolean@^3.0.1: resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.npmmirror.com/boxen/-/boxen-5.1.2.tgz" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz" @@ -373,12 +345,12 @@ brace-expansion@^2.0.1: buffer-alloc-unsafe@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + resolved "https://registry.npmmirror.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz" integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== buffer-alloc@^1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + resolved "https://registry.npmmirror.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz" integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== dependencies: buffer-alloc-unsafe "^1.1.0" @@ -391,12 +363,12 @@ buffer-crc32@~0.2.3: buffer-equal@1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz" integrity sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ== buffer-fill@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + resolved "https://registry.npmmirror.com/buffer-fill/-/buffer-fill-1.0.0.tgz" integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== buffer-from@^1.0.0: @@ -406,7 +378,7 @@ buffer-from@^1.0.0: buffer@^5.1.0: version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -420,25 +392,25 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" -builder-util-runtime@9.1.1: - version "9.1.1" - resolved "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz#2da7b34e78a64ad14ccd070d6eed4662d893bd60" - integrity sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw== +builder-util-runtime@9.0.3: + version "9.0.3" + resolved "https://registry.npmmirror.com/builder-util-runtime/-/builder-util-runtime-9.0.3.tgz" + integrity sha512-SfG2wnyjpUbbdtpnqDpWwklujofC6GarGpvdWrEkg9p5AD/xJmTF2buTNaqs3qtsNBEVQDDjZz9xc2GGpVyMfA== dependencies: debug "^4.3.4" sax "^1.2.4" -builder-util@23.6.0: - version "23.6.0" - resolved "https://registry.npmjs.org/builder-util/-/builder-util-23.6.0.tgz#1880ec6da7da3fd6fa19b8bd71df7f39e8d17dd9" - integrity sha512-QiQHweYsh8o+U/KNCZFSvISRnvRctb8m/2rB2I1JdByzvNKxPeFLlHFRPQRXab6aYeXc18j9LpsDLJ3sGQmWTQ== +builder-util@23.3.3: + version "23.3.3" + resolved "https://registry.npmmirror.com/builder-util/-/builder-util-23.3.3.tgz" + integrity sha512-MJZlUiq2PY5hjYv9+XNaoYdsITqvLgRDoHSFg/4nzpInbNxNjLQOolL04Zsyp+hgfcbFvMC4h0KkR1CMPHLWbA== dependencies: "7zip-bin" "~5.1.1" "@types/debug" "^4.1.6" "@types/fs-extra" "^9.0.11" app-builder-bin "4.0.0" bluebird-lst "^1.0.9" - builder-util-runtime "9.1.1" + builder-util-runtime "9.0.3" chalk "^4.1.1" cross-spawn "^7.0.3" debug "^4.3.4" @@ -456,35 +428,27 @@ bytenode@^1.3.4: resolved "https://registry.npmmirror.com/bytenode/-/bytenode-1.3.6.tgz" integrity sha512-t/e1psHP/r9ipj136i9FUOznv/9el1DcY7mB8nBM1FPDqYTvQY73J8uVwRPciWdKZzZrS5yxEyzNEEnB835Fyg== -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" - integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-6.1.0.tgz" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== dependencies: clone-response "^1.0.2" get-stream "^5.1.0" http-cache-semantics "^4.0.0" - keyv "^4.0.0" + keyv "^3.0.0" lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -chalk@^4.1.1: +chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -497,13 +461,23 @@ chownr@^2.0.0: chromium-pickle-js@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" + resolved "https://registry.npmmirror.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz" integrity sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/ci-info/-/ci-info-2.0.0.tgz" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + version "3.4.0" + resolved "https://registry.npmmirror.com/ci-info/-/ci-info-3.4.0.tgz" + integrity sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug== + +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-2.2.1.tgz" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== cli-truncate@^2.1.0: version "2.1.0" @@ -513,13 +487,13 @@ cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.1" + strip-ansi "^6.0.0" wrap-ansi "^7.0.0" clone-response@^1.0.2: @@ -543,19 +517,19 @@ color-name@~1.1.4: colors@1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + resolved "https://registry.npmmirror.com/colors/-/colors-1.0.3.tgz" integrity sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw== combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@2.9.0: version "2.9.0" - resolved "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + resolved "https://registry.npmmirror.com/commander/-/commander-2.9.0.tgz" integrity sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== dependencies: graceful-readlink ">= 1.0.0" @@ -575,6 +549,36 @@ concat-map@0.0.1: resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concat-stream@^1.6.2: + version "1.6.2" + resolved "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.npmmirror.com/config-chain/-/config-chain-1.1.13.tgz" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/configstore/-/configstore-5.0.1.tgz" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + copy-anything@^2.0.1: version "2.0.6" resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz" @@ -584,17 +588,31 @@ copy-anything@^2.0.1: core-util-is@1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + crc@^3.8.0: version "3.8.0" - resolved "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + resolved "https://registry.npmmirror.com/crc/-/crc-3.8.0.tgz" integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== dependencies: buffer "^5.1.0" -cross-spawn@^7.0.1: +cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz" + integrity sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw== + dependencies: + "@malept/cross-spawn-promise" "^1.1.0" + is-wsl "^2.2.0" + which "^2.0.2" + +cross-spawn@^7.0.1, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -603,47 +621,48 @@ cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^2.2.0, debug@^2.6.8: +debug@^2.2.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.7" resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-3.3.0.tgz" + integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA== dependencies: - mimic-response "^3.1.0" + mimic-response "^1.0.0" -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== define-properties@^1.1.3: version "1.1.4" @@ -655,7 +674,7 @@ define-properties@^1.1.3: delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== detect-node@^2.0.4: @@ -665,7 +684,7 @@ detect-node@^2.0.4: dir-compare@^2.4.0: version "2.4.0" - resolved "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" + resolved "https://registry.npmmirror.com/dir-compare/-/dir-compare-2.4.0.tgz" integrity sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA== dependencies: buffer-equal "1.0.0" @@ -673,14 +692,14 @@ dir-compare@^2.4.0: commander "2.9.0" minimatch "3.0.4" -dmg-builder@23.6.0: - version "23.6.0" - resolved "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.6.0.tgz#d39d3871bce996f16c07d2cafe922d6ecbb2a948" - integrity sha512-jFZvY1JohyHarIAlTbfQOk+HnceGjjAdFjVn3n8xlDWKsYNqbO4muca6qXEZTfGXeQMG7TYim6CeS5XKSfSsGA== +dmg-builder@23.3.3: + version "23.3.3" + resolved "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-23.3.3.tgz" + integrity sha512-ECwAjt+ZWyOvddrkDx1xRD6IVUCZb5SV6vSMHZd+Va3G2sUXHrnglR1cGDKRF4oYRQm8SYVrpLZKbi8npyDcAQ== dependencies: - app-builder-lib "23.6.0" - builder-util "23.6.0" - builder-util-runtime "9.1.1" + app-builder-lib "23.3.3" + builder-util "23.3.3" + builder-util-runtime "9.0.3" fs-extra "^10.0.0" iconv-lite "^0.6.2" js-yaml "^4.1.0" @@ -689,7 +708,7 @@ dmg-builder@23.6.0: dmg-license@^1.0.11: version "1.0.11" - resolved "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a" + resolved "https://registry.npmmirror.com/dmg-license/-/dmg-license-1.0.11.tgz" integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== dependencies: "@types/plist" "^3.0.1" @@ -701,24 +720,27 @@ dmg-license@^1.0.11: smart-buffer "^4.0.2" verror "^1.10.0" +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + dotenv-expand@^5.1.0: version "5.1.0" - resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== dotenv@^9.0.2: version "9.0.2" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" + resolved "https://registry.npmmirror.com/dotenv/-/dotenv-9.0.2.tgz" integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" +duplexer3@^0.1.4: + version "0.1.5" + resolved "https://registry.npmmirror.com/duplexer3/-/duplexer3-0.1.5.tgz" + integrity sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA== duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.2" @@ -726,33 +748,53 @@ duplexer@^0.1.1, duplexer@~0.1.1: integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== ejs@^3.1.7: - version "3.1.10" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" - integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + version "3.1.8" + resolved "https://registry.npmmirror.com/ejs/-/ejs-3.1.8.tgz" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: jake "^10.8.5" electron-builder@^23.3.3: - version "23.6.0" - resolved "https://registry.npmjs.org/electron-builder/-/electron-builder-23.6.0.tgz#c79050cbdce90ed96c5feb67c34e9e0a21b5331b" - integrity sha512-y8D4zO+HXGCNxFBV/JlyhFnoQ0Y0K7/sFH+XwIbj47pqaW8S6PGYQbjoObolKBR1ddQFPt4rwp4CnwMJrW3HAw== + version "23.3.3" + resolved "https://registry.npmmirror.com/electron-builder/-/electron-builder-23.3.3.tgz" + integrity sha512-mFYYdhoFPKevP6y5uaaF3dusmB2OtQ/HnwwpyOePeU7QDS0SEIAUokQsHUanAiJAZcBqtY7iyLBgX18QybdFFw== dependencies: "@types/yargs" "^17.0.1" - app-builder-lib "23.6.0" - builder-util "23.6.0" - builder-util-runtime "9.1.1" + app-builder-lib "23.3.3" + builder-util "23.3.3" + builder-util-runtime "9.0.3" chalk "^4.1.1" - dmg-builder "23.6.0" + dmg-builder "23.3.3" fs-extra "^10.0.0" is-ci "^3.0.0" lazy-val "^1.0.5" read-config-file "6.2.0" - simple-update-notifier "^1.0.7" - yargs "^17.5.1" + update-notifier "^5.1.0" + yargs "^17.0.1" + +electron-notarize@^1.1.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/electron-notarize/-/electron-notarize-1.2.1.tgz" + integrity sha512-u/ECWhIrhkSQpZM4cJzVZ5TsmkaqrRo5LDC/KMbGF0sPkm53Ng59+M0zp8QVaql0obfJy9vlVT+4iOkAi2UDlA== + dependencies: + debug "^4.1.1" + fs-extra "^9.0.1" + +electron-osx-sign@^0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz" + integrity sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ== + dependencies: + bluebird "^3.5.0" + compare-version "^0.1.2" + debug "^2.6.8" + isbinaryfile "^3.0.2" + minimist "^1.2.0" + plist "^3.0.1" electron-osx-sign@^0.6.0: version "0.6.0" - resolved "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8" + resolved "https://registry.npmmirror.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz" integrity sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg== dependencies: bluebird "^3.5.0" @@ -762,40 +804,63 @@ electron-osx-sign@^0.6.0: minimist "^1.2.0" plist "^3.0.1" -electron-publish@23.6.0: - version "23.6.0" - resolved "https://registry.npmjs.org/electron-publish/-/electron-publish-23.6.0.tgz#ac9b469e0b07752eb89357dd660e5fb10b3d1ce9" - integrity sha512-jPj3y+eIZQJF/+t5SLvsI5eS4mazCbNYqatv5JihbqOstIM13k0d1Z3vAWntvtt13Itl61SO6seicWdioOU5dg== +electron-packager@^15.5.2: + version "15.5.2" + resolved "https://registry.npmmirror.com/electron-packager/-/electron-packager-15.5.2.tgz#9864796b8af63aa87b43151d0b6990ba7093e6b8" + integrity sha512-8zUdkSONn0jomu/efqoxApGzgqIb56ooMs671HeB/BXTPnWcWvqpEY08g16PL6ok2ymA5zPj8vmUszLrq99F0Q== + dependencies: + "@electron/get" "^1.6.0" + "@electron/universal" "^1.2.1" + asar "^3.1.0" + cross-spawn-windows-exe "^1.2.0" + debug "^4.0.1" + electron-notarize "^1.1.1" + electron-osx-sign "^0.5.0" + extract-zip "^2.0.0" + filenamify "^4.1.0" + fs-extra "^10.1.0" + galactus "^0.2.1" + get-package-info "^1.0.0" + junk "^3.1.0" + parse-author "^2.0.0" + plist "^3.0.0" + rcedit "^3.0.1" + resolve "^1.1.6" + semver "^7.1.3" + yargs-parser "^20.2.9" + +electron-publish@23.3.3: + version "23.3.3" + resolved "https://registry.npmmirror.com/electron-publish/-/electron-publish-23.3.3.tgz" + integrity sha512-1dX17eE5xVXedTxjC+gjsP74oC0+sIHgqysp0ryTlF9+yfQUyXjBk6kcK+zhtBA2SsHMSglDtM+JPxDD/WpPTQ== dependencies: "@types/fs-extra" "^9.0.11" - builder-util "23.6.0" - builder-util-runtime "9.1.1" + builder-util "23.3.3" + builder-util-runtime "9.0.3" chalk "^4.1.1" fs-extra "^10.0.0" lazy-val "^1.0.5" mime "^2.5.2" -electron-squirrel-startup@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz#c9171568d724884c7a2b03760bfeedcf921c63ab" - integrity sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA== +electron@^15.2.0: + version "15.5.7" + resolved "https://registry.npmmirror.com/electron/-/electron-15.5.7.tgz" + integrity sha512-n4mVlxoMc4eYx07wWFWGficL+iOMz5xZEf5dBtE/wwLm0fQpYVyW4AlknMFG9F8Css0MM0JSwNMOyRg5e1vDtg== dependencies: - debug "^2.2.0" - -electron@^25.0.0: - version "25.9.8" - resolved "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz#7c125ccbddad02861736275b0d4a387c59a91469" - integrity sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg== - dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" - extract-zip "^2.0.1" + "@electron/get" "^1.13.0" + "@types/node" "^14.6.2" + extract-zip "^1.0.3" emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +encodeurl@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz" @@ -815,32 +880,12 @@ errno@^0.1.1: dependencies: prr "~1.0.1" -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" - integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== +error-ex@^1.2.0: + version "1.3.2" + resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: - es-errors "^1.3.0" - -es-set-tostringtag@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" - integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== - dependencies: - es-errors "^1.3.0" - get-intrinsic "^1.2.6" - has-tostringtag "^1.0.2" - hasown "^2.0.2" + is-arrayish "^0.2.1" es6-error@^4.1.1: version "4.1.1" @@ -852,6 +897,16 @@ escalade@^3.1.1: resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/escape-goat/-/escape-goat-2.1.1.tgz" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" @@ -870,7 +925,17 @@ event-stream@^4.0.1: stream-combiner "^0.2.2" through "^2.3.8" -extract-zip@^2.0.1: +extract-zip@^1.0.3: + version "1.7.0" + resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-1.7.0.tgz" + integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== + dependencies: + concat-stream "^1.6.2" + debug "^2.6.9" + mkdirp "^0.5.4" + yauzl "^2.10.0" + +extract-zip@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -883,17 +948,17 @@ extract-zip@^2.0.1: extsprintf@^1.2.0: version "1.4.1" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== fast-deep-equal@^3.1.1: version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fd-slicer@~1.1.0: @@ -903,27 +968,54 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -filelist@^1.0.4: +filelist@^1.0.1: version "1.0.4" - resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + resolved "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz" integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: minimatch "^5.0.1" +filename-reserved-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz" + integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ== + +filenamify@^4.1.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/filenamify/-/filenamify-4.3.0.tgz" + integrity sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg== + dependencies: + filename-reserved-regex "^2.0.0" + strip-outer "^1.0.1" + trim-repeated "^1.0.0" + +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-2.1.0.tgz" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^2.0.0" + +flora-colossus@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/flora-colossus/-/flora-colossus-1.0.1.tgz" + integrity sha512-d+9na7t9FyH8gBJoNDSi28mE4NgQVGGvxQ4aHtFRetjyh5SXjuus+V5EZaxFmFdXVemSOrx0lsgEl/ZMjnOWJA== + dependencies: + debug "^4.1.1" + fs-extra "^7.0.0" + font-list@^1.4.2: version "1.4.5" resolved "https://registry.npmmirror.com/font-list/-/font-list-1.4.5.tgz" integrity sha512-mLi7Sb4iitgk3mJsTZZDbixssHLoJB4/onWGlQOvTSPW8YPBDQNaMTGOdZ92axQI4UNWwPxV5FmCBOEo1Us7lg== form-data@^4.0.0: - version "4.0.4" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz#784cdcce0669a9d68e94d11ac4eea98088edd2c4" - integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow== + version "4.0.0" + resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" mime-types "^2.1.12" from@^0.1.7: @@ -940,6 +1032,24 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-4.0.3.tgz" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz" @@ -976,10 +1086,14 @@ function-bind@^1.1.1: resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +galactus@^0.2.1: + version "0.2.1" + resolved "https://registry.npmmirror.com/galactus/-/galactus-0.2.1.tgz" + integrity sha512-mDc8EQJKtxjp9PMYS3PbpjjbX3oXhBTxoGaPahw620XZBIHJ4+nvw5KN/tRtmmSDR9dypstGNvqQ3C29QGoGHQ== + dependencies: + debug "^3.1.0" + flora-colossus "^1.0.0" + fs-extra "^4.0.0" get-caller-file@^2.0.5: version "2.0.5" @@ -995,29 +1109,22 @@ get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.3" -get-intrinsic@^1.2.6: - version "1.3.0" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== +get-package-info@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/get-package-info/-/get-package-info-1.0.0.tgz" + integrity sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw== dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" + bluebird "^3.1.1" + debug "^2.2.0" + lodash.get "^4.0.0" + read-pkg-up "^2.0.0" -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" + pump "^3.0.0" get-stream@^5.1.0: version "5.2.0" @@ -1026,7 +1133,7 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" -glob@^7.1.6: +glob@^7.1.3, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1050,6 +1157,23 @@ global-agent@^3.0.0: semver "^7.3.2" serialize-error "^7.0.1" +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/global-dirs/-/global-dirs-3.0.0.tgz" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + +global-tunnel-ng@^2.7.1: + version "2.7.1" + resolved "https://registry.npmmirror.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz" + integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== + dependencies: + encodeurl "^1.0.2" + lodash "^4.17.10" + npm-conf "^1.1.3" + tunnel "^0.0.6" + globalthis@^1.0.1: version "1.0.3" resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz" @@ -1057,27 +1181,22 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -got@^11.8.5: - version "11.8.6" - resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== +got@^9.6.0: + version "9.6.0" + resolved "https://registry.npmmirror.com/got/-/got-9.6.0.tgz" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.10" @@ -1086,7 +1205,7 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: "graceful-readlink@>= 1.0.0": version "1.0.1" - resolved "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + resolved "https://registry.npmmirror.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz" integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== has-flag@^4.0.0: @@ -1106,17 +1225,10 @@ has-symbols@^1.0.3: resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/has-yarn/-/has-yarn-2.1.0.tgz" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== has@^1.0.3: version "1.0.3" @@ -1125,16 +1237,14 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz" integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: lru-cache "^6.0.0" @@ -1153,14 +1263,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" @@ -1171,7 +1273,7 @@ https-proxy-agent@^5.0.0: iconv-corefoundation@^1.1.7: version "1.1.7" - resolved "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" + resolved "https://registry.npmmirror.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz" integrity sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== dependencies: cli-truncate "^2.1.0" @@ -1194,6 +1296,16 @@ image-size@~0.5.0: resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz" integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/import-lazy/-/import-lazy-2.1.0.tgz" + integrity sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz" @@ -1202,38 +1314,117 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/ini/-/ini-2.0.0.tgz" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/is-ci/-/is-ci-2.0.0.tgz" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + is-ci@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + resolved "https://registry.npmmirror.com/is-ci/-/is-ci-3.0.1.tgz" integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== dependencies: ci-info "^3.2.0" +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.10.0.tgz" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/is-npm/-/is-npm-5.0.0.tgz" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/is-obj/-/is-obj-2.0.0.tgz" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/is-typedarray/-/is-typedarray-1.0.0.tgz" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-what@^3.14.1: version "3.14.1" resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz" integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.npmmirror.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isbinaryfile@^3.0.2: version "3.0.3" - resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" + resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz" integrity sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw== dependencies: buffer-alloc "^1.2.0" isbinaryfile@^4.0.10: version "4.0.10" - resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" + resolved "https://registry.npmmirror.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz" integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== isexe@^2.0.0: @@ -1242,13 +1433,14 @@ isexe@^2.0.0: integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== jake@^10.8.5: - version "10.9.4" - resolved "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6" - integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA== + version "10.8.5" + resolved "https://registry.npmmirror.com/jake/-/jake-10.8.5.tgz" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "^3.2.6" - filelist "^1.0.4" - picocolors "^1.1.1" + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.1" + minimatch "^3.0.4" jr-qrcode@^1.1.4: version "1.1.4" @@ -1257,7 +1449,7 @@ jr-qrcode@^1.1.4: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -1267,14 +1459,14 @@ jsbarcode@^3.11.5: resolved "https://registry.npmmirror.com/jsbarcode/-/jsbarcode-3.11.5.tgz" integrity sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.0.tgz" + integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stringify-safe@^5.0.1: @@ -1283,9 +1475,9 @@ json-stringify-safe@^5.0.1: integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json5@^2.2.0: - version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + version "2.2.1" + resolved "https://registry.npmmirror.com/json5/-/json5-2.2.1.tgz" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^4.0.0: version "4.0.0" @@ -1303,21 +1495,33 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -keyv@^4.0.0: - version "4.5.4" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== +junk@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/junk/-/junk-3.1.0.tgz" + integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/keyv/-/keyv-3.1.0.tgz" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== dependencies: - json-buffer "3.0.1" + json-buffer "3.0.0" klona@^2.0.4: version "2.0.5" resolved "https://registry.npmmirror.com/klona/-/klona-2.0.5.tgz" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/latest-version/-/latest-version-5.1.0.tgz" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + lazy-val@^1.0.4, lazy-val@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" + resolved "https://registry.npmmirror.com/lazy-val/-/lazy-val-1.0.5.tgz" integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== less-loader@^10.2.0: @@ -1344,16 +1548,44 @@ less@^4.1.2: needle "^3.1.0" source-map "~0.6.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/load-json-file/-/load-json-file-2.0.0.tgz" + integrity sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ== + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/locate-path/-/locate-path-2.0.0.tgz" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.npmmirror.com/lodash.assign/-/lodash.assign-4.2.0.tgz" integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== -lodash@^4.17.15: +lodash.get@^4.0.0: + version "4.4.2" + resolved "https://registry.npmmirror.com/lodash.get/-/lodash.get-4.4.2.tgz" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash@^4.17.10, lodash@^4.17.15: version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz" @@ -1374,6 +1606,13 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + map-stream@0.0.7: version "0.0.7" resolved "https://registry.npmmirror.com/map-stream/-/map-stream-0.0.7.tgz" @@ -1386,19 +1625,14 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - mime-db@1.52.0: version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12: version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" @@ -1410,41 +1644,29 @@ mime@^1.4.1: mime@^2.5.2: version "2.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + resolved "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mimic-response@^1.0.0: +mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - mingo@^6.1.0: version "6.1.0" resolved "https://registry.npmmirror.com/mingo/-/mingo-6.1.0.tgz" integrity sha512-OShdwTMBzDabOxkfxX1a7ENFP0tp9KGIJgRabuyamCdm6qDxcjoQPhlkXyxgUqI5s1tKsDKiRWKZtu1Nap4/Xw== -minimatch@*: - version "10.1.1" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" - integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - minimatch@3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" @@ -1456,9 +1678,9 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== minipass@^3.0.0: @@ -1476,6 +1698,13 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" +mkdirp@^0.5.4: + version "0.5.6" + resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + mkdirp@^1.0.3: version "1.0.4" resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz" @@ -1507,13 +1736,31 @@ needle@^3.1.0: node-addon-api@^1.6.3: version "1.7.2" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-1.7.2.tgz" integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-4.5.1.tgz" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +npm-conf@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/npm-conf/-/npm-conf-1.1.3.tgz" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" object-keys@^1.1.1: version "1.1.1" @@ -1527,16 +1774,64 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-1.1.0.tgz" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/p-limit/-/p-limit-1.3.0.tgz" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/p-locate/-/p-locate-2.0.0.tgz" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/p-try/-/p-try-1.0.0.tgz" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.npmmirror.com/package-json/-/package-json-6.5.0.tgz" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +parse-author@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/parse-author/-/parse-author-2.0.0.tgz" + integrity sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw== + dependencies: + author-regex "^1.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/parse-json/-/parse-json-2.2.0.tgz" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" parse-node-version@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz" integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/path-exists/-/path-exists-3.0.0.tgz" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz" @@ -1547,6 +1842,18 @@ path-key@^3.1.0: resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/path-type/-/path-type-2.0.0.tgz" + integrity sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ== + dependencies: + pify "^2.0.0" + pause-stream@^0.0.11: version "0.0.11" resolved "https://registry.npmmirror.com/pause-stream/-/pause-stream-0.0.11.tgz" @@ -1559,26 +1866,22 @@ pend@~1.2.0: resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pify/-/pify-3.0.0.tgz" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pify@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -plist@^3.0.1: - version "3.1.0" - resolved "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" - integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== - dependencies: - "@xmldom/xmldom" "^0.8.8" - base64-js "^1.5.1" - xmlbuilder "^15.1.1" - -plist@^3.0.4: +plist@^3.0.0, plist@^3.0.1, plist@^3.0.4: version "3.0.6" resolved "https://registry.npmmirror.com/plist/-/plist-3.0.6.tgz" integrity sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA== @@ -1586,11 +1889,26 @@ plist@^3.0.4: base64-js "^1.5.1" xmlbuilder "^15.1.1" +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/prepend-http/-/prepend-http-2.0.0.tgz" + integrity sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + progress@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.npmmirror.com/proto-list/-/proto-list-1.2.4.tgz" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + prr@~1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz" @@ -1605,18 +1923,37 @@ pump@^3.0.0: once "^1.3.1" punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + version "2.1.1" + resolved "https://registry.npmmirror.com/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/pupa/-/pupa-2.1.1.tgz" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +rc@1.2.8, rc@^1.2.8: + version "1.2.8" + resolved "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +rcedit@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/rcedit/-/rcedit-3.0.1.tgz" + integrity sha512-XM0Jv40/y4hVAqj/MO70o/IWs4uOsaSoo2mLyk3klFDW+SStLnCtzuQu+1OBTIMGlM8CvaK9ftlYCp6DJ+cMsw== + dependencies: + cross-spawn-windows-exe "^1.1.0" read-config-file@6.2.0: version "6.2.0" - resolved "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz#71536072330bcd62ba814f91458b12add9fc7ade" + resolved "https://registry.npmmirror.com/read-config-file/-/read-config-file-6.2.0.tgz" integrity sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg== dependencies: dotenv "^9.0.2" @@ -1625,22 +1962,77 @@ read-config-file@6.2.0: json5 "^2.2.0" lazy-val "^1.0.4" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz" + integrity sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w== + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-2.0.0.tgz" + integrity sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA== + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.2.2: + version "2.3.7" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +registry-auth-token@^4.0.0: + version "4.2.2" + resolved "https://registry.npmmirror.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz" + integrity sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg== + dependencies: + rc "1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/registry-url/-/registry-url-5.1.0.tgz" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== - -responselike@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== +resolve@^1.1.6, resolve@^1.10.0: + version "1.22.1" + resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== dependencies: - lowercase-keys "^2.0.0" + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/responselike/-/responselike-1.0.2.tgz" + integrity sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ== + dependencies: + lowercase-keys "^1.0.0" + +rimraf@^3.0.0: + version "3.0.2" + resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" roarr@^2.15.3: version "2.15.4" @@ -1654,6 +2046,11 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -1661,7 +2058,7 @@ roarr@^2.15.3: sanitize-filename@^1.6.3: version "1.6.3" - resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + resolved "https://registry.npmmirror.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz" integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== dependencies: truncate-utf8-bytes "^1.0.0" @@ -1686,33 +2083,30 @@ semver-compare@^1.0.0: resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz" integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== -semver@^5.6.0: +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/semver-diff/-/semver-diff-3.1.1.tgz" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.1" resolved "https://registry.npmmirror.com/semver/-/semver-5.7.1.tgz" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.2.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.0.tgz" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2: +semver@^7.1.3, semver@^7.3.2, semver@^7.3.4, semver@^7.3.7: version "7.3.7" resolved "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -semver@^7.3.7: - version "7.7.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" - integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== - -semver@~7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz" @@ -1732,12 +2126,10 @@ shebang-regex@^3.0.0: resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -simple-update-notifier@^1.0.7: - version "1.1.0" - resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" - integrity sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg== - dependencies: - semver "~7.0.0" +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== slice-ansi@^3.0.0: version "3.0.0" @@ -1750,12 +2142,12 @@ slice-ansi@^3.0.0: smart-buffer@^4.0.2: version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== source-map-support@^0.5.19: version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -1766,6 +2158,32 @@ source-map@^0.6.0, source-map@~0.6.0: resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.1.1.tgz" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.12" + resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz" + integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + split@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/split/-/split-1.0.1.tgz" @@ -1780,7 +2198,7 @@ sprintf-js@^1.1.2: stat-mode@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" + resolved "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== stream-combiner@^0.2.2: @@ -1791,7 +2209,7 @@ stream-combiner@^0.2.2: duplexer "~0.1.1" through "~2.3.4" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1800,6 +2218,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -1807,6 +2232,23 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +strip-outer@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/strip-outer/-/strip-outer-1.0.1.tgz" + integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== + dependencies: + escape-string-regexp "^1.0.2" + sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz" @@ -1821,6 +2263,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + tar@^6.1.11: version "6.1.11" resolved "https://registry.npmmirror.com/tar/-/tar-6.1.11.tgz" @@ -1835,7 +2282,7 @@ tar@^6.1.11: temp-file@^3.4.0: version "3.4.0" - resolved "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7" + resolved "https://registry.npmmirror.com/temp-file/-/temp-file-3.4.0.tgz" integrity sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg== dependencies: async-exit-hook "^2.0.1" @@ -1848,19 +2295,33 @@ through@2, through@^2.3.8, through@~2.3, through@~2.3.4: tmp-promise@^3.0.2: version "3.0.3" - resolved "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" + resolved "https://registry.npmmirror.com/tmp-promise/-/tmp-promise-3.0.3.tgz" integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== dependencies: tmp "^0.2.0" tmp@^0.2.0: - version "0.2.5" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== + version "0.2.1" + resolved "https://registry.npmmirror.com/tmp/-/tmp-0.2.1.tgz" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/trim-repeated/-/trim-repeated-1.0.0.tgz" + integrity sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg== + dependencies: + escape-string-regexp "^1.0.2" truncate-utf8-bytes@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + resolved "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz" integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ== dependencies: utf8-byte-length "^1.0.1" @@ -1870,15 +2331,39 @@ tslib@^2.3.0: resolved "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.npmmirror.com/tunnel/-/tunnel-0.0.6.tgz" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + type-fest@^0.13.1: version "0.13.1" resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.npmmirror.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/unique-string/-/unique-string-2.0.0.tgz" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" universalify@^0.1.0: version "0.1.2" @@ -1890,34 +2375,81 @@ universalify@^2.0.0: resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/update-notifier/-/update-notifier-5.1.0.tgz" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz" + integrity sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ== + dependencies: + prepend-http "^2.0.0" + utf8-byte-length@^1.0.1: - version "1.0.5" - resolved "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" - integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== + version "1.0.4" + resolved "https://registry.npmmirror.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz" + integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" verror@^1.10.0: version "1.10.1" - resolved "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" + resolved "https://registry.npmmirror.com/verror/-/verror-1.10.1.tgz" integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" extsprintf "^1.2.0" -which@^2.0.1: +which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/widest-line/-/widest-line-3.1.0.tgz" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz" @@ -1932,6 +2464,21 @@ wrappy@1: resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz" @@ -1947,23 +2494,28 @@ yallist@^4.0.0: resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.1.1: +yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.0: version "21.1.1" resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.5.1: - version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== +yargs@^17.0.1: + version "17.5.1" + resolved "https://registry.npmmirror.com/yargs/-/yargs-17.5.1.tgz" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== dependencies: - cliui "^8.0.1" + cliui "^7.0.2" escalade "^3.1.1" get-caller-file "^2.0.5" require-directory "^2.1.1" string-width "^4.2.3" y18n "^5.0.5" - yargs-parser "^21.1.1" + yargs-parser "^21.0.0" yauzl@^2.10.0: version "2.10.0"

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 diff --git a/public/images/bg_2.png b/public/images/bg_2.png index a742d8e2ce9c029fa1df40e73eaa2a6310daffb1..e5918d3d203676d87b1a4d7f7dbf52568b77d919 100644 GIT binary patch literal 9422 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 diff --git a/public/images/f1_objects.png b/public/images/f1_objects.png new file mode 100644 index 0000000000000000000000000000000000000000..f06c2123030949aceb2bee48cf7e132913e0d947 GIT binary patch literal 164515 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 E0d>|B1poj5 literal 0 HcmV?d00001 diff --git a/public/images/front_bg1_1.png b/public/images/front_bg1_1.png index d0565abd7d53f258a2b952f78cd31918aac27d87..1098c5cadd373b4258222d64e33e2e496fa36a8a 100644 GIT binary patch literal 74148 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 literal 133002 zcmY&=2~-nT`+n3~KWnRPwNjzL*G18yD9R!r(AJ`#vWa9t)+j1MSjB(_fwb1zY9np6 zfFhtG+e}CT7zn{Y)EcU6RUGz!iii%Ihye`*{_mZjzw@7SJb041ckX@P=Y8JoPJZ3t zvGs%Z4Bw;CXdi6*-}gIdwD)?@zyG`opICZb`5FGqkN)4TSQ^cL3;H)}$=S`{(q_-w zwC($E>4z^2=;CGjxrxKm&g0?O>@U8E3vc~p?p&{rT!QB%3|oi&wo3I?o|&25iW;%m zt*_1p4=Bb@FaN0WKeP837}bBo5zF#*Wt?h{k=xP}6X%7VyC`!QZrc1~ASKX5cQFe6)65gRdC-76{`HD~lMRg9k`;v!bNMtO|YK02$K zPW0?}ypcQBp4XqhMjMi3>426=v#{RhkN)~pmT{JZ#rqxdZy&8#^L*I3!pq-LA@KHU z3|q2uZU1mz!{o`06=7(YO4Fs+ARIhGt})9g{7I)wU%mK_4E>cH zt!J<}XE`9GBbv7M?~3Ct9j#0V{_g3Mjn(zpH2L!hZS9ovf>bG2_k#Y^)xnb2Q9l`< zsb2XsYoo)`_bQzumK^pJCS85Xot|EZCQSW&Mk+j3fymN9{PZZI#yVpR5S6 z&Wo73DpTDR+6uP^%y~69aApi7g^71Hd!P8-a>81X@23GG?n9-e|U3O7t zulAkZ)>BO_T=}t=3jK$_CZx)%q|$?1-`2>fPwI{xYnNh~Ha(60>AzI$DDz8CuaNmC z+!)B8Z6aLFdV0CH^H#4KkH1?oWs)({mvwg~Id#>(N;@(zIj^)kd0oQbjWr|Av(XNk z?e#m59V&}j5-KKCC4|5<+r#u>}e0!=L z7c8H?B_2GyYUQ<%6pZq z)yb>QVN2R=)>P>B#wE2cZCEI2wGdn$vex!yyen#3IUJ}sU%4c?`B%Yos&PBuPFB4ZWPDP^ zO6=RS@C+Ai-e*K_(>|-pBF-vp)^u2WRylP(dCE-p;=bzmZ9$DK>?1tOoO}1jq3=TD zAC7Y-;oGwoRaYi5RmmCdM)P12cAGb)d$wKMCHdWfR$OorN$0@4OjR=y{c2&%Lt9p z@s13u?d7&vy3W!OpO>d%cWZ819QpClHa}}?n%_-EyHA~Bx4{}Ruu^^{;UsNL=(P_pN*>OmNiSPMCB-}$d>?>ca|v7dW;abzQB38&i5VQE@KwR3_o zU%I#>>!z9) zE`Y@;?_B&1{gv1Z2C7TEt}0x(qgz%z+n)A(s;PbB=g&EU8-pxE@5r^SXhQRjJWKa+ zTSFIhhBW4jNsF}SjThQqRX(pR?J+0PurpyANarx7)&M=|o{bt0b*om7?deMJ$T?KGV`^7=!?Q~dt36QdK!V~beV$YtKwhXyM8^?7sGE*lNY8wWu zX>9vZ;sdl0KbxtnYFW{FQ|przHFc+R@E%O#=lnIAE5mh}0sLVfX)W}+Q?V~6y3#YJ zAokX&Bg4x;i+4{YnfPT&3Gdl7zk8n;Ei z;+>Rr`tU00u)(6p4Gxj_(toNf?CJ24+2xHqC5P!osb8$5Ngh8j^ga{?KE$H4ptSGS zwfIC$J^2rLsx*5mBY%y{4z#Ry^*b!$w1C>8npQsOD)73xYv7vdYGrWy#=)_4^6~OJ z^J%N2SVNbaVxx%k%hl=?!yP*kad4+=8%}$tt~7&bl;?BwnCcy9c5yAAuvyU&;d{+7 z-%A#-vgWg0#(tUAd$-Nv6YP#HpN}^S8ci289TLlKPuN(k=wF+4apD$f;F`Jw$>$S2 z_sGKIBF{!d`3*Wx=(Gl-Ri%cDBMl6qs?R@KLz8gpShkTzZ7MXEvnxS&)g7+S}V@QKZouphK^l*wH{>(QN8z2bHa{I+EzZsdN2m}Rhf*G?Kgs7wU#JKkeM zcw}4~zM%?9yJ=vYM@0vdhI#|84j0>`Mn-j{uPEEKcmkV6}`Vxz>*}fXcTP-J7 z5J?e}ou!^qkcva4J!WYa|1h>O#x?b0w3)d(C{;Df9pwi`;-T2j;nEK4_bN^~8>z3H?|7V8ec-5x?_}H4zn8nthvAYsJu7i;ALWjcrwL{j#k) zioE0jE4zkRgV$~+c!*DjFNt*SUfDgxp1%Q>Vbv0PHa4ndW#3T8tz?z`We`iF%?c9P zlhB@$gfgsH)Lsczts9VqR!{o$_F#?s!i;Fj)RJdS^CE)=rMDAo=s{6O7f$#CQ4?Xh zd$-oRO2Ba6+|2?PzLe<@sy%uLjALQen^crUW%sSTU z1FJkqgfuzlXaYNjpOEX`HCno5WNfjZkCwH@%b$MvOLf(u5eNf5k8Fp%fi%LuX{B9AaK>ki!cdMw#+Zl@>H%mT@=`6s+=pR~?P+*!;!yc6Q_*Xd=cvY!G@Afa~?YL>U{KUb8gUUR_)B1?NLh=z=7Q-n?Gr)Q&P(R@xXy;Bh%8f+I|++>s; z;%wtkb?wky7{&isIiv+<~Myey9n{c<)< zSz;YD*dqI5Ffw4W?^z@L1k<$ew8&<4#P=`-`#nlt5_y1B-gmi&JQe5{WK5_K&`VS8 z{|gqq!+|-^=43^@I8yDBZ58uf&e?|Y^ z$07-jQ-Kb+cCV4GuDa~;GgxMXJd~TQTKx5pf57g$oDi$X(+*cH9mi$MJy8rAZmHkJ zi-G$q)m^c11Jc|5H8v}{lVsZOxeb5jLDXg=-JJ7#<@04zOEaz|`5T?8Fy)*@K`{T5 z?M0VWK z;M+~eJ~CXW@u~U8Z|@rCg#WuXD=A?&_ps(VxtTBs;h+d=Ly$Ueq+;5Mm@1Xdt+93* zueSB9f}#9)*22b$AU6ROE9f?=SDV`_?3 ze|Iiob_bz_(Uf|6;6DrZHr}Bwx18I*^i*KSzv+i7pC4$yiEAhwrQesUu5=y?sfobA zHcdOy&reK|O~hQi0x7=a=f)A_@2Yn?6BsPtm+ghbRwJWlra})NYWoM{G^+StH)ECK zwcTb;ow9SoIzc{cj7D?Stnv@l^ZI;GVAo#Fp0M zNkA@u?KpqUkZSyQPlmy2?naowr=Ic{?Aq2`IV*Vf&~+m6RNzEtaMMI0u@&2pw#gV~ zNFAW7OTFqGugJ;EElGD}md_oDOGKz^7D|qp%P69e4Tw5a&&ua9I@a(8(*cAE!oV=) zS70!ouu)>r$aZ0uSl)KIr2${a>ABxIy!Hr?0?lWx{_&#&evT5a8|sj|+Sda2fpHs~ zl88Nky4t$}!&{vb%=oagT!YWls$r|G7Dv{jftRR(4_F!Eg+dc6hcSO7_7jFi1c-eX z84b6j-HaqY7yMc=j>YYI**-!zApf+0+7V6oAqDv7wPoU=u5|u{GHl#MtdsCOH6qnl zoujEXh5!y`aR4`soCP^iOW@1)Mnl`rt8ncB?$E~}urmHWcM7*ZXX3|ob^pQ9}1*duokJWun3D*no%x&{C}+ntKL*lRizmYQeZ5^6*{ zaeITwCI?)+8f}o@+hc3konhiLSb0EqfeaQ5yAO*zd|&K^jY;08%w5tv(AN4*QZ$z> zeNMc(K{g58AICGhv%dhPE7!-X!J~Xj7*{@x$2_GY4#dcrD`?sC^<$U%i+TUkNW!L7 zU$XFqh@K_+<8(8Rk3ga9<;p#Kw)}GpH*aXRrEj%5SrO%K39&ez@joOlKJoZn7<&`b zEiK9pRET+gVCVz6DNyE<>>p@d`NZj}JW4uxxn(H~#H&hQBKN|s)r%{)a<`G$gCUT} zRRDMqacR{92%6k>*N}mN;fV1Fw|@ho_vW0PXeK*cO+SEvW$#niWL{Pn8dWU9BNZ@` zbQIKgZpLF%oj}L5H1-LQ(30s&Ld^Zy@I2TdiyY z$}@|y84B^p;d_giA)ZJ@{B4kR=Z^%8R1u%-@9(pXymvHKBO+g}x`7ryn-W-FNia7W zu|er57K<;kwqbO8XWDH;7Mpq|6Ptj9Yg|;8U6dV=c#+pLke{&;#`+wjY-)9G1fD=o z8MvHcq>yN{RZpHdVBQpblqV$|%>z|^W_;1ovQ3^*Zg&#BHGiny2|&9dDSP&LuUu@> zl+Hdxw6*_C#tPi;mDA1K7K1MNpA3-1O`}_#9gNsgO}2HG?1fep?}>(nQA1mO$HJCG zn>tJ+UN08RmqiJy%3l`4*FL$T?v=exnEdr+O^;!2XG?t&K$*XWidgt-o?(J#PRAmFf``YZXY;REo;zyoQQ{|S}V^6O(xk@JZh z`O{!0=MGiP(JZTP5h7bZt7N4d=-kUY;nMF6cvN8aanFK5F_Mp|B>j2+z}?mhN8O~u zmo-t=dG9K!v}5m7z(BUEYGP#&>#;QT2G%MruN)KAo{>C?l>DXP~leqJDjf z{6J9uPopPN39amWhxPV~6j5A8QDzVzp?9F~b<_7?J?si9Yi1WG;%3a0@J%J9Hj(yK zWxOEn&{wz76Q6|#sl{G(0Y}e}jkqmSjGeyl)iU^^_B4fGme_N%`>;PLqCBdW^ZcPS zSQ9>YgZx)@wR8OOugH6FsK+SVOQLf46Hmj++uC0f^9-AgI<0d+G-uHUI~Igi%xi4G zle`p#JQwa`D?~&-VHCvi?nuF}j-G>;Q<^rFSW5fciEXZ?fC?J(I8ILq>ztH>h=cJo zhM05q5VDM13Zcw=E)m7_EHt4Y>4w8} zl&EIV%}jH|&8)V|S~80Qe2m-Ef1DU1uWkDinQVJ;ghmN_OJFSMD5-+W%=@04+X4U=Z%0wrJjR zBWJBCYtYHhp`7QJ^HcxbH26|-REQ@*B#`(&>CJH66;eu=5sqDgiWM8(Vv^!*1nAB$J6LR7gj8VFVa#GRFp5I-kN{NXGBt| z^aH{SOJfcfG-Rzn)Gj$b=;J8kg>n1BBFmD#*8~w6>(_t_V(+5B>60`T3Vaa&Hag*= zAywaB5PL30O7!`{HUdQBGic-IP{i8|S?SCh#F0Sg9G38*O#6Lm3P) zHYiFKdEH5jr49-%!;I>l~(qTZTMVNy;^>2OcwQ|Zqv z*7UB#*gxs;F^B5TD4VSTDqc4fe%7{r)Bc)ARWC8W z>2H98kAkv@GNFTY-UBCDdt}*|fShKCF^^12xe}FsA@92$`&;h&Iem=b1Dz`;Z~uy< zJX0V^>-HdmjWYbo)4?1Rm0mY=K0SAThbPipBSdn)VBeuR*zq-wg2A`8FhVE?!sb$; zx@=%9GJLONRr|iOxbHM2WVK-mALYwF7NP)0g|>(!OQlU%3&br!87l!T%99k~OO8Cj z=fPULaxDY$$i+Z6QR0SdinEltr4gFnRqm?zEF_*lg=FMv_5#W(B(o_U`rES#vvxX` zw#SymZPBztP|!+khURZle&FmuFf%JZhbo>ISYhAdUL+n%N<2wYKYBIGF;9LYe9(wU zwmK=xcR!01!gg)X_4-GrBUPo+Qmn;RbWHXU(C34Xso-w1)2}i=Yx7ymvp)|p=eGgFyjYC)n zz8e~5+MY+S5LvDn$MxDbLEGa?!vY{@8OJKEO|Hu>X`_P|Kif=bZ{I`4wA#5JOR$X$ zs|qm^=N}cuc#lpTbOhM@9Mcco-$!l}xaSx(G&dY@Qzq`#X!H^leq|d0am9DirtrZI zpc!+liQ*B-WBs^C_FzL%3=mCKfTE!I+e0J;R2uWDIm1(w)uuAa_ACr+KCJzBQ65TO zB!{SR#84Sgil1P1)HN@}r3v`bFZh%%VgLOqUgT8W>ccN3pHjDtLP|Lh{{G=@}rq zX)oP@nNdwrtQ*f_1{^6H%Otxo7Bk-yfq{LBqBG{PetLCBYik>~EY9D^Hm*U>k~HEI z+>DXDW6;{qA(t20^Db%XhFFeWOIZW6M29(JCihfZO_DBa(@4dU$DhN_lm|;h@?^~b z=}{^j#miaK{>GMGz_$EaDgp2beS&Sq`VQT7&nUOqF5Busfs*eU)xBla`0j{nx3y=8 z>7p)HL^@?b`hb{uau9=>!owj)Scz6lx*WMcJ!<=*O!6Gjga?Fhf{^M0xLuS=EF2Lc z*%ckRye*mu!a9W0Ee_nFxMrVtp6Y>wf9t^VES!>nQH}MG-S{*s(}r4O5EUi)h(ruW zoadYKyC3&Z@Yi=T@KLjEh&PqZS0gmPkSVxdeX|E1cu!_Gv6VulY%k(><-t)SBAcFq zm1PtqUe)H4H;JG1(2^W_f+g697ONx5;sP})75DO5N&yejD66Zz^{t@(uVY3TYYga7 zoN&=+cDqqjM$3~%1@9cXiM>|Ux0@dDx7EHngs72f1+cgGV<)U^yvMSUfm|MGy*^B7 z2kh$UyF@9LPiLhU8FvBUDtw*Yp$N_{qLPa|NuuTzX-5Q>N*Ch>jOksOR%q)GJXWjm z?!v5HgO+{{B5gH+S-o+hy4o)&a)=o!rcX_&vdPyS$Gf7G(t)+ad|GKcVnncEyj8?CW@Kh46(2NzPw^F=gh1}oj1 z{~j6>PK|kT9$zS$%4<(9i#w>f0eC$_2^iv-<{@I3)hL7@+Bi^`2vvf|$bsuqZQ0c) z*gR%(fSOkpw+F(-WH5RvNTFGjX5!tUZnf&*vN*b?lgwO&97o!Z$R9uN7vd&?Iv4J# zwsudfkF~b2;XJZLDD@$m!ylaWO<{#MF{!*zaeL4vI z(FHyBp?A7ja~8CcRVo#;rgta`>ChOHdds{c@>yeh{~BXp<~C@5tYWMzPYO9 zFz>$k!5+vwe@8omVrn&U_+^(A$O6yuQ&h;K^#GRm*RfIxrb^lq%EoSzuZda+lMsMc z&p{88#0~CnEV7(Ge!f&1Y?#uXkRVNZGp-0Xxwo>nD-2i!%6w_`J~zZ>>D1$3Aq>x= zscZ7)@WFqeFci-+n$ep1Eq@K46k~}Y9A_+xrMPMzPs!sQBuDmOtB7|{V?c2CLv3Ih z=Xq|2MZhvDo?sKqS%{DC z9Gb==!NigP$kN=w zNI)<2aCb@;+Hh~jDlJZG;W@12AO%p1o;-ZaVR^PIdWYaqkpL-cV(ytM0ID*|oP8Y2 z3QYoZTxm=AV839&RFu1ZeW~sKnu{uHxnEs#RnuV}VYCzE&S!j!;sRzBH)8d}uEQw0 zOJ+s6=||+1iFgMz1fPsX4AnY^LLAI%^t zKvSR+Xl&0FCFW`~$jlioNjrvMA#@v$bgNQF#Z9SIIfH@OTRV^(ln2Y*dqP3}2z|e7 z**3m;@jlp&#h1wReRd7eb-~;R1Cup^ZL+oEhK#RKN0b(QE}Z3<3*rH&cruc`5)UHd zRz5*}B>rE!t4UKVp`pPObgm2Gj?pinG5j@qY9QKn#^bV!5@p(2)!)c-Eu%KnJNSg? zW-Ssv!MyS0!YQeJqMUMHN~XkHGtdU7$u>jvB??>XX#lW$#h~zWq#f6Gkar0=Sw|ix zM)4rGbode?4s9Hs<*ke4-U>_9{7epGJu46``BP;spFokfj;py6mO)-4s{vmg>3TAg zyc7lMj%YfFI5<~kj!y^Z*f)^venG)u(?FnZpXRr)36+b61$Z!a*843sFM*lt?RY6> z@w6Rr3QsQZptzB;3%7`TauMz zdWtga`9;;(SM>y=~8S$rJQgVl7a;0VBjyuOc5soG`0x<-;LnL_t^A;q5K9 zl!|aFTMa+bBAhDY9SkrAo6mOXopHJEl5#SP_hnmhwg>|~5 zqe@j;w++!`xOjP7)TXh)2tiF%Vk}ZVTni%o9m{zKtXY}&58r5Uh4=_z$vg`gl)uv= zEGuZx+F|;zrjO{$dk@9CwCCoXRlGp%zewnIgoBwYd}@Ayy5FW;JXK-Oj)Sh>+SuzT z{igXWGokA?b4%M^hhWZkE`mk2hWFMc2wROX;5+N zLzreZqWGjd9>*yA9f*tl-y3a*@1qL`y3#)$!Blg}SP(VS5X}8kYV6X3ftqf@ z95ZK?7Y>1w@M?9$pe5!rc|ige2Gtc58C-n?9f~wTMNHK^g>(xgP&E_ z`*Fi1KE66wp%tP+;-&=_Z-%=ut4>>LsvfpcQG~mxBHr&oKYB3T}_~nS)`AH>6Erfkr^B z?NVN(h9sX5%Ya@|3Qrf#u`Lp;z))ruBV-RNZ2jj2hGrZh?M!# z@C?=5o_Kn^_z5Hti!i4h0qX*+&`|!q-XF;`1g;l`kB-UR&!tO~uxL0clw0wdLZa1B zfG$=#J}}uslo}cQMhW5-5gDL!<)*d0X5r45lNH)p`pN5{HKBC^%k+YA0;`4r?YZ+` z0JK0~xm^UU(u zG=354U+{?o!;pJbHa1kZuY8zvKu;|+PNM=U4L~iXp_^{kDZwn5e{sF3GV_5J5)MZQ zz8Km@Y{30;f1!9>a^LB`%xR)drD;#F5_MNyjB!NX^7xSmFiqwUT*4cVXH@Sh5(oM_ zUbPR8Lq-GrB)_1c66a|N+H*Pb_6vS zj5m0hpn*`&T!Sa$L+c%!F$m+mz30MqYHQi(Sy(?ks=(>)%10(aj@RTIy(Pl&$27y1 z7^q^268cr#E?as_7IG#2`zcctu5(}}`zne@HkQU>$pt2VeXLjO_7MX7&M4jiHAqj+ z`=2;mma`E_LG45@?6j_9STYv5&LP=VVBy) z-TZnNQO$_Y&NKyP)V})u2z7t!i=my0i*jOMGKVm=%6p0$^wLNYzI$%&U#M+?*5eR? zgst28H}Dx4S%g!UYzhhcL8Yx|(KM%rX9>pZO6_I!=2#*ak7tb+0Cq$sI}`WJ2%Drh z`KN%645A{FNKkOJSGh3IM;#hBRR|@FoP0e)D))|s`fsS}8JJuw z;HZMmfBhlE+;@mD;Jkx_R=jPdKag8n;T zY!ws`R@kr{MLZUFT=nHYfj*8`wUbol2s_$xx-c*>;F3Io`%TWpggxAWpP=4yQE#D= zR5hkeQPGcKo;D_HwK)c zzNry%&dutG` zHKoRImqns3mKygB(j{mlDeEp63Usv4Lxq-lBHKpf*Ig82Jh5NU z-!6{y?|T<+4oB{QUwc5r3xhy%Az??v;x+UdcN9*-WpOtilGHw1QN%FBwe}VE@{z~3 z;EGqrj(S5|g}ofsJdMrS-*KxQ>(^i0|SDxI9Fq6@9 z5=a_Ow8bR*!wH%A8O9KQ!Zqh?s8nSoH7;~GrNO6 z%^0+GNRHLaBRo<3p)3hO@kiQT5tMG#f^D=AdUEs1M^gu8f*gr-@(OPscRROci{1Dt zq7lzQK1F$OKN@Aww?g1Bz36;q@H z@n1m1bo-6!_dU1+QGGr{BP=|hm3@9a7+x*Kd6mpY&L&@uXykBFJ`)Nb5d6WldTTru z=&QbB+4?A#$Y$odrO&7Ff2|j1AQXG^d^A<01@W3veD}FGV^}q|@5v09^XL;S&-9{X zE|Q4bKd>sk^jSNR%pA|iOvWsijdR}Y`>)>pgex{Dq;D9{^3p{k-{y7$xQj2{CyM#} zxj8toRp5Rd^7T#(puiFI_j^Y9M^#7lXeTxpw)B8!{n8C2<>L_9dH7IsCd^fkwJUSo z46J;NMP3RC?>W#TxVR|9_59_*pbAKS-8D7DU3kQNV)OnJGm`aBf>$M4x`sPt7Sm>e z{+AF?>{$UEA&dm_4Mad-K#}rs;cSXglwZg9LJ`)it|6r$YiD98+CVsiy5FONHC-9= zLsT`HKqzpt88=MxYr0J=HfrguZjpH;f8Qh5jh)E_GsMrepgnLSgA~;$KvqPLO|;~_ zPk9p>r>@+{Q`}J@58&?5{0=94Pf(n)V1`rt?tFsxF{`@|&%R*`#f|K9NVjnPI}3u$ z40=DFES|#bWA8vx8NQ5K+rsezryc5ixr7&}c?x`^r!E?%{T7U4onX%XnjB2b1igX> z_TwW)G4Bu&&ekg)G%$jMSv!p#1HPgj@qpEu&pX1+0JPm9Q=sX73hbkIHxln1ZZw>; zxJY(FrfJ4-&YD@&bMwp{ip%zu_7Zu7Mn?7%p`YP}{=2WC-UVEKLGji!HHA*Rxd(W_fRw7A`*)WMhjLw)==_3^Xc-+mmnd;3u7hUEtEe#HjhZuXH1$hw9W@9)cdNLjhE zJuXLew$q`ZA?Po(i`tV&vb8mz6UBlrM4|A1xV|CEF^7=tAi$D+NF*=gLfvjAz8L7V z(i75yN8$F%$9v)YY$!4!C0fLEj6H4USfAie;3(GvT?b&m^7u-qpC;l9uxfD@J#!;W zg)X*>U7?psA+eX(U*!Fd`@JRyIbEUS9az6>NC#91So8#XkZ56Hmw!>Vz8pZ4tS=HT z#KBz_w^#D^NCPy51J1xPsGH8ujTt595MS=^oA!4s(LS6B9{9wgpH%C9hk|V9F+nduyNM2q zGRyUGiZBpd48yoprqtXc+lX_E_fvYIF{{m)({xA0c_B1&e?T%?Rr*^2l2K z5hXQAYCwfHg|sXkUBvJ#OxgtO#Am3GUe@$og3#Bc^9**v)*^f+@}PO!Rq(=tyB}Rq zIjPbNC&06Z&qAi_pHxYoW;({iP5nULCOl_iKw7k=hzVA8IBp6f7hz2(d6pQWqJh6t zyfDyPebF9=IN%oX^Qz5yQThK^L6+mmm}p00InQ7741F%#JKCS;0b$Sj2tTL_zaYOR z{(u9ji`YZ7FkOrDQMhIoLm|Iw;d-hS}lGwKPbK%o-Y;cQ{sPpV5B+O{2OYKWKQ0aK#<5 zAgjEds)&2Q$>{Q{*=*z;Bi@n1-5nzHLzrdCm2EDor8%U&@u~zs!82D)bx@iNu8|*N0`Ccj|&Me zfezfLjip$yfEN7`z*W@sudHLjRdC9EIP;BY-f;?2;Pw}_0&*L1gb2YuKy3n_KvX`k zk?+@>CZ9n!v6_)mkhFfr_W1L4LLP&A3wmBA#1F()ya{z2BuDiDFZ+lrS@R-noXjT{ zezGb^NJf^{Hdy{O|}}_EwV?HmTN<0pK#`29BRhaSP zYAl=XS>UuDKIJdp12~c_4QJ`P$(!KucN0!;cf4x&z(Dn9&8;bRSBsrX)jypBVEm1$ z1Cl#x33OuqC2xQW%V$s_5G7G-`=XJvH%75z@WKKF?g5#bAn@cDh3V5Yzs4_N2bJ{h zA`?bp(i`+~ZVqC$%$BILhu{oiDEidzS14B~!wwyX0=zq{$q(D>?blX9iG8T z!5cUO^JdXjCm={~kN%5XLkwGOuV@sT1Z8fTu}WH5x;NfJ@374}dpISm8VqG%J{-%O z^wrZGl&XRh744K zeJcCUv6xx>-pBE{r}atMRx1p<_OG9EOuxvJc7!gX!vfUV+S+`u|eB0J*ED*UT?Tcw^{rlyW$ zyp_tn1UQ%kl>3zYoQOvRDqMr+-)Vn^7a-+m>Q%>oU+X!;wtU9JCqxHs1nGK8QJ3?+ z|3YUjJ;)X+W$ZQS;;AYyjnje!pN|(Mp$AhJQ?MA&V~a(4J7Vp5+V7>iA#G9`qBCmY z5tS^+X6OMvx)|u8SG)<-*@PV&kptc>Cq?kq*dj)VJ9uKBUVYI^xPO8bRWv3{x-Xma z`pjFvt&w?aRHQF4Lfph|A~q2Ez6<*DtBtZXnJ5j>I1@>4;3lzh+BMkGn%`bp9HsdU znKs@`f$ey}CEhMg8gW$92tKB1#fl$ z21Kz_GxmlkzJY^s`=qX#T=Ej+0BAYry%c5Y!D*5pL_~|BfmM|g$d9^w9_I~i`zp7hoxTmRlha11>YI?H+AOA|FC5D6ATHw_)J zmTeMs$`W2}BaXwVi)8;y+c%n!z0=2t!ac-8Ok?_BP@=>>0cqkry{}>CLfPUp`7`k= zu<1>-VCqWi`c{zdG~|_-&x_{PK~w?x0+{iGlEZs7=OA!=t_nV?udPE$zilCBAU3f| z{;lSj%IQ-$w@qbzHaCxICdq7k5KFUJ@hI!<7G%G{VG+N8B6*_b->^Qi8rPt-kNLYk zm^HsO1SD0-fXvTN5&p)_NP0Kr^1P&pIloQ(LS8|#Hb?#W=+I6%F9eDVCy32@zV?va#-DB6aT~*m_u1oj_R7ql5rUv?ni#I>MbVhjk{VGIS7lL$DWZR2vw;H#MBQy z4)@k^G}oXX?~K*JVSyPIrInRzz*|_A%y^**eUOM9!iy@Adk}`OEhJ`HAZNK_Hv{`OW{08(smn;gpyfVGjMe+o0DLJR@ z*c`rfFcr!ucea&6F}NR&$668-`M`YCh5z$8$j6+qm9Yq`az@6u4N?tddvrb-L6{J> z(CrG!c;kD8-`R5B|ED5`y9WZPGv4pcrj=DuQFoab60@i%GHoUrpoM=BghzI_ zrgh{LdyZd4D)m!SyY=m$uN6*-&WooC;au_9Of`?j{>ves7tB4bTD*Fg6;R~$Zvg5f zv(5ERPGap}!E=RLcA74qDbBFcXJU}5ra;Gc@Za=5CnV%!>LIA1D0H&LAVHPcU*fSf zuiqllFs_meAP78)aiT71HVyxqf|%cZHN0qO#M;U#cmD{AB6n$@H_AhAGxP}4vv$rEq5m=-qBVcgnUQ+Y5rXt*39AlwomQu3&tH4P_ntXXj6 ziO21Fvrqo1jnzC?X#ls6tRjZsEh&l#Eb{eCkXG}TVNmPvu1s$Lp&BJN=;8FafLYuNcXPbPEWVIir*GCcwm;>Kb=u)7uN^UDy$#BX%vDFBVd-3#l$-?l$GB z^qoWIg*WLpq=MI*s(8Md#2{!eh1tMU6?{GUYf%!3yca=^i$+R;-vv0Q%a$hA0A;F{{BEksHTUUBL1K@ zdzZpM+S6IYZs5ooY|c#E2TCmuIt04wDb`~P7U3pVXdNG;7KXCqEepDZ+*e zZ)`(yrGYAAv_r{{%hOb#P^sDtJXC601ZKcU-c?_pG!-LFLX|soDZgrF4T~D^p+I$| zoX0&d_TemcTe_Z4*?nX^kqEyBRKtpT3w4SArmH-6X)t&LiRuav=^nJtO#{0WVz~=g zq>?N}7D-*HnH}gLj+e2Yy4)V6E#FXC!POwmYj9)cg0=si)K{oz$~>o*R?o@p!O0_| z#1xVU;+H)Dr@#P()`wdI6~(u+N{4O6K5=eu4VfHPReecY_XqMB>2=xkwS{HkK&GSI zp7T~O+oK~jd8DCW8@xExAadFO6h$4$U_Yy8f%)%=o60A$8U55D0JLVIRX;v%^X^oq zLLLHix|#SH;%?>n4ODLE;|E!Nu{@}!;r{<8l6#I>)LSX~ zMkm3^;5&pdoRbWpXQOfh^>ZbXULY5y4m!1-`>UhUX9`m^NppCCQ|royU8QoCG$DAW zNa0Fr{Za4i@Y+wX7C1@NP-qgQ;G)C{UCQ(063Dt2!YXU%At(!_jyAR)6!|L3ipU{_gY)7C20m# zRZpm>5LY5&Q+^#?Np1jRGx$Gaqorm?(c=#caJnSg@ZjpjG}&i>!v`N95KMDs+MmLt{(jzctiP~Es|kc+hW|74wVqGiC?C}n{bow`fw6BNNRW{ z#xzhTvFAY%S+WPQ=f2T2Xb_J=-y?+8kBFPPqMsFsyE+b+j0NBmEbzLh`X&4x*&3YT zpDvgZj`Y49$NJt<;)9ZjZ1Qi?iBRJ>^90ifCDrD)J16f$?$XErT7xW7ikS5P{J|n3_kDi$ZGvsl;lCSCJXex8_Fvv6ybk@ zUinQ@a2TdoWT5V%y-fS!4QOd)r!!cl1)`=_14X1_KiFMvjl-gz=kF z0F5bLYdhvreXrF_=N8<(geH3jwitbTVF6?dyArXH|D~F~)g-%r;#+RM%IR5_AhF58 z0~DNU2|hx0cwFq{JIGX&g312hGEy6E6OM)xxs?pl(1D!Uw2X9Uhwy`5_z@fkiw#(w zSi@UVQ{Jn*2+0otB>?zg2UZXg_G$c9}p8E&dH_n zAa10l5J*;e5WP8c(_iCB1hM+@5a#rzH+~Raf|ZazL56BVSP)y0Z>U9X{JsrQ3#XhL za1%ep+c=I^+xQ0*x8~93FQ4{9L*q2-mFVlhjKYS3GQAuNv`^m(JGC6rIbj^u zNetNJ3W_Kn;MXQTZK*nDVH9i#c8(HQN+^#2X(cB2_nUS*4F1sjcr*Y@X-k}|dDC3fR z0-P;ruY;yI#Qy(G-xK z5UeUwpTyeDAWfiG7YunDqcBK+Su{+6XHsJ+OrUb)Rr~`Hlnzxghod&#dHfUn zQbF-skNtopcY!p9BtqbY3vXmaX(zw}%-c+e6PO>XI|@2;Z=D`ZW;KR^dd2pbJf8aq zkoTYji8@yv*K?}P9T&ri0_O{FB5x?WRjsArHz-Em7o9_%5v<+2l`*e(!7}|qmD@*e zs*;AGdfx46J$7ss+Xc?RvU4kTaeaEcmS-0xZAkfAUwnes$~?jcCX({xk-^S~hY@k@ zM-N`!mOpZ<-w;aU38-g{Uaf?SUpstOH8s4|XyUqQ)WD)S%9BW1+haN)sm7VZ;vrV1 z-CJpuo!wwGp+xnz>Gatzpi}UoNIEe)7%N9{=6?L$_)7peOyq z2D^meM(a%`oJ?-wqFU!H&aYXC%^G=EOWg+8fJtW=FP8h7WWdiI#GhC?vs+15RmaPu z=lJ<9(RHFidvB$NDS$>S#unc~uw}hi=9RylKmpSxOg6SP9eE-`HyqPD?_U zuj`-x&fIQO|C;b38tLVchL6EY*ROw%X7QV)1+DaH%lWEY%hlM#(xCEuWM4*rvvVU2h_4QV~tGHWy{{78SDiW ztv>QUO(#^gy@;UG^)s?yZ~4qSQ7n&zUU)BgZ1X7^TY#SbXc3lQJJtF2d)`jD;L+ne^NJJna@-<7apb^9G}oIT zrh3UvXsYGpeLkeXSt;+&gi-8=7odM;>^m3$<(tsGAJS^C!@YvColr;CbXk5Um(nF1nG`IME22m%TMnNRwpAu8;UbB2I0u8D)V zXFftDM`i+o1ChA)*(K)y2lE1rGv|k63{X*!;Xs9fjO+LMkZd2nzx|0H-^c5|-#xAw z{J!lLZ!>TE14{R}mE?FqJ^2Ai=c#KUu-!kuv!kofB-O)@r}m*ni2&AifI!z5n@~qg z==W^9Wgo5nwCPbfB&O{V5fqK`O#oXrEC5>@IIe9a>pfUR<4gRx*NalaS){nj0l&qJ z$cNaSx&Vbw3;_y#B=!(dFL3Rn^{$H-{^#o^xeGdj++=%mc_d@2^0%31eBijzs_Wm% zOx~kU@l!Q7E1luXd$9z>;5TN3k!ApavJ{#vqr)86J*o>wpoaev zKQf1d+H+i;k@X#LC1Lc4ny+4M&4mQ2F{!sk<&7F)G+=;1z-trg0zG&b1mecU|I}7O z(d8El#5h5~Z?)u#y~>7jy9mvWKw6cZ=aB_>mW%Q4^?$wwfO(Gt5yB5-Mz;J!6yJ!c zpu&|VUnN1U#%45cTD3%0gEs@_o_;)Kfo22j&8x7TA=0}3?`^k4{>mCP^$UmxkOQFm zCwXQ>Q52wm{4U%*_(c`E0Yg5SZR%aC8<-*I0+(jmHNa#_Zao{a zVthyi=c*}{T6@Wj3*UI7p?`voEao!f5B zDdoPJ?RQ`Dh`VC@r?g0V*w_Z}ei0+n+ta7zzc}rdX}NA$Z@6?Rqi25tk?cY}*`cmu z#z1-BgPeF!6cwf{uNdm*d6^6TDcFJ3Z__X65V9PpC3-`3GPbBMEc$KK*Gd_D$<47e zQ-9g}j?)I&TxWb-gd%sDI1NbT!Hso&Cs#xQmXIQ~*kgX;;L19pAR8ZKxy1zm?8_Pf z_9l^G%HIPUAwIV%vEwz6#FR0xlX|(s_R8~Y7a%2bcR;A{@*H~un}swI6Lu^K9)jPE zT4bDUM@-u0*=&yyt|i9%CQ#JFfXqf3x*+(G^@)>yxT8IGBcP?Pq5dCY zyimD3cfB2o6c&G(PN9eyxtC|l3Aj8{4X}R?1L$V(vofF?1wtM3Ac|jq>KV$8udHE! z0hTG(1}ZTK^ku;Of(^aGVV9T;zSrU2dfP}fLp9f3fQ)}Xr_M{^!ZHVmLWhf`cPfGS z$n4Z&-M05%$KnWPC1~MI@)WzZc$5+s1Pkm2@n>3Ok3^ElC0=A>H;uj|jQbDCFQCg% zeWZt2qys`i0z$$x7a=v0>M5dv;Vu8_HwK0D&o4YE1LCVJDDkf19>9c8AqXFe85yLB zS8cSj#QR^ZYnL=JVLWsSqB34+31~n=xLwya;DM%r6A*OIb~Pyp@s9JME{=(XwM{=9 zYg%qvV!Q{+T)vFp_q9(fUnoI6UoMA2nZ+Nc2;AM^Xz%$3r!mQx17N`EXtK88RRk}U z6XF?HSrcCO?h>I|-!OCu62h+`Ct^DmG47GFh%(Z;A)MepOoOrLMPs-9cUO_TJ|usQq+O< z;RSggckI(q?9;XlNF&KT%fWN1ArH!K7k>hogG*ewEvF`qaUeBtndwJ99?aV>1yBl8 zjfT4N*Q&YSHMjTxqFXY0m~|+Q3g{ncFwm#cW&G$q>G*C?(m}74G$H=}cW44x%QZ4| z?NH8k04f6ms)25?mn+P2xDu`ETK9VI2j9dBRU}3gRQBehB^WkC0Gs2Xe29hcpqROS zy?XcAkH!L`(-tN{RM@fG^nj#lIh<68OvlIswv{y=P&pFG9#>m;1bu(3<5(a!0y_Bx zTiFj=nbrnlklM(5U6aT|pz2>A!|W0(8)v+eB=HG#x~{D^!DOUZlEv7J})C$ZtVyh7op( zuk4a_p)~QCmvJ}9%>OXHPJA5!B$&6=p%e29B~CHS&TWe2gv?dgfqg2Xzk~9i3HgCV zYJYtOW!N6ZP@%n8ku|xjZektv0B~Y+(go<4Jmuub?B@<^g}pv5b?c?Ra;@$80VdC9 z3q=HiWeB=fpK3O{8^R$RD2o4|nhwN!w&z1WD;JtGeAza@vk!j2E>^dflvYydI#rx% zq{c)q`Wj$;F8u%FX5~peaT(hM|PZ9`3#l4AlDvhk?#MeUANEPd`h`pi!a@fs6*7Xn|fMQ@nzw3ce zQFqu2`+Lt3dxZ&})yoa(ExPW^sXfxMoI9uQha!TCjIi- zp8-__@k;D2KRVM?@BVDbo_o3ab#)6t+)(|%;i0&&_br0aag1Yj+n-Q$rh`9m6UgZG zv3#Fn`NnyaPa5LPt!L_h@)poX$@V^q>18Obq2Tp*g#CjPFlq1;l*N{?SAm2w{Q^X6 z3VjANWGZAF+t#TW%QSCQHt$hiHDoU$C12|^8Rez6H3aI5XWbJwM=oUT5!PF(@k}`Z z#ho0ey+@qLCzeq*0=bNg>;v=49!ejQNJC(nzdk^EZ=jMb7lXv|S`;E{Fi@+yMYb^g zRapl!GdXI_MOan4lBrXA^C^M30)YPKuvUkJz5M0!O|O?Sw`&cQT3+fX5B>2wN#8Q2 z0z68ALr%v8QPR~>GgL}s zKv7Z=?3L*VAaox)0XZxk*`&&Y#dkJChV3%SQL90z`vF_cC*Iz@DIX#ske^6+D%QJiO1A@ml zNQaP+(RCBtQ-9)&b=;?mU4ZZ{^@CUuh~@r3zT6J21?p*j2dHQNpcsZA?1Sd@r)mLC z7#Y)se%+%$XRFvQdsi-NUJhTLjck$^gTK&*_@}7h*=25mw1Or#b`@0w{0T-h<%qE- z!Mq2u4gLz{G`d`EqY{Er!LMT<;D0iz|gN4#p7-i3d0AOQiL-J_>YW z?Gl81$5Z5&3+HaxXH9-Yn9U^8Jeax9f0NUla-Y}iW!zB9F($o^BtmKuz$4p_CV@t1 zHh_zpR5&}%x}DJ??$zAeV00WEf_Ml->39xca8naOan82qK>0zFtMeW55}6Zb#d{$d z&_kXf^8;(Lv;`B3DyBKHi8#jPxrhpFVN!rv=!k=k1h0L#p2Bj2o-I%aOhb?a=5v={ zB|<;PFCgElG-gS2;eO&+323SUsf|_}^Rp_9)eN6I1K(P*V~(k*DC`ip8=?Sz`jET? z+^PnU;px3M*$XbgC6F6%R7(NsAgiyuY0J)c>3snTA&fM7usLmu zYc3R!Pa>)AGxp+p5RFPlb5M5(Wo&eH)|gr5kq0;tB_U)iP!$(^?0xaui~M8VzaG`f zT3Nl{&AvE#PQ&MC#?Q#Zo$_UXc8&w6 zeCjGdd(#6s>dtN=IkV@FsslIT8!#I5V&E7Uq`s&PkW!G~0{Zs~f)Q;BN*0_Tls;A5 zs_|R|4@gj6Jp&c;lZzID;MBT?6>GZpDMJCD6825posL6M5h<<|nj>ZljkUzzV3=Cd zmCx>{so1COh~646Td)mM*dD$0HAj?lau#So>pP2zG==^(v;L9qY?!vbqC|FLL-kmV?sK%Y%mefd9C^| z3}@A64{b}KFDb;#(6&J7saRf36r-VQ9dm!Hfqv#j1d7+<*i){N#g)lw--hb>%tOQl zPAsgUE#hbY@ua_@2 zqUcTy9IeH{S!M&H9=%o3cVX|dRq}0S=2{`HV5z&##VgrrOPvt(TN~LMLd8%R%Tv7s z+Bo~r!pwXG8YC{OupTchjbx^-n%%FgM;6#2Aa;Uu#oq$c`*jCOin2#Lw%zH4K?!Pc zOUOQ&A@2g}GRii4k^}7g;L~U3wuA>ouMqg7A)06SPpc^qv+3FJtjSOPw<-3n2Lt)R z6%0dQ87u^;{v%-_k;!U>NM%A!C|H&FWH);QU}N@A3`2(H{^03#%0EE&YcOPf#~&Hn z=GoZGx4ohQUy*4t0C%E6biuP1sxKL1@>z{Fm0W&Ubj_Bd`fml9(E;CThXJ0f+Je?T z%5k&*V)yIXAL!VRjF+T7{-exQ7@Z#r=S-mgm`UJOD^V)@r&|Wr~Za(-p~;@vf(roPXowrj6yxU z6SBY=J;HKSyK&5@^25f5j1RL8_7>XGj=e941&O@p$7s0sd33o(7*TsJ^_M$N{|EGu z;cgnU0PNVHO08{kgp(x6x3bXpxJ#Ty?TgEYs9V}grhFInAr_qpx?%)F4kOqwWN%!9 zn>Dw{Y|uw+J2IbJx3m0h%Gyey*suEg#r_d*w!S*-NT6Q7CJ4wVjgcI9V&N0c^u3B% z`II$VO5(YoentxUOMy%RQ&}FP%qa{Fi-*DQS9gdl!VLAh-qWM4eH4$>M5-++LQrxU zJBHXFBt>>YVngsAVA5KO6-w4_i@Wy}M;4T~rg&Z?C`~L>wRf$fo&$~u`m>41Kfd;n z!v-}$!ko{h?8&ccw<*Yz25!quw*d-MN)L-lZOezxe;Y$|2hI4JHU!^CgsAomd6#(g zmiz2T0ElN|oBuUtGe-#bWANWo%d9RB_pFFb{v<7O!gh<56c128Mi0qIq^q5k&IxaO zbwHZw8xaQnN(>}qrjnDFtw{sIh_3xXSz2hh;M}SfSIn!}jxc90KvK7Y+AB=f<`i9k zIAU+M!>*G_h2aZJ#A|-96@r8AWIlhA-wG)MQ(d@?a-s-SG@n$T{t2|_{qrk3x*rB* zJ37szI~r#%H~kR%>_ktYv!0@T;%;82o|-0@x#gh}-ji#Ep>Yl&$8`knlUNNaIVP6I3-rTsQ>rky1?eQ*!`=K`FgR23O?Zs9A2!&9m$T#ono{gp51j zND5Zxcqzzn*P!C2Pa{bvj-e>pro8+BOjO+_I?H#~)#Ka2euG+^AtaCL{8uT;!BN zA9EKSgHQF=HLNiEh34Ak7Bio(fiUI!jidzOD}03DI&G7*2?rn4h+1dKkx#4up(p*30H?>3F5?;=78cFWh}!A}v0 z*I|X*T)0Lg9aKu?!%nqX`ZkIn0vj`T%^u{kW46Ve80tf(GzwMRyuHv?Fq~b)KPUI1Uzz!i?!a3_G9*CUO4gs#@Uchc zc_fK%zp!5!zTBN@2sVdcHxxl0Kxx@#k8?D-g5cM=4}|@4whz13bqK%TMTN2`0U3~QZY0Rx0;g5-p7cx}Z986#Ig ztBSRj!R@0S6t4{Se7>|g`h*5J0;@h*d2M$|^dZJDi1jbP`xlhwctP;^IO9jgsD{Ea z?Lzsw#XR!d_l*1PYpFkD-iY{ZEz(|6KR4tMOQR(TgB?0hLRm}Ff_!oI@*wLZ`q619 z)`sBx294TdD(HtLx-_1YT*z z^8Gh`Jt$^+!Jv1z(bReI>nuCyr?7a*`XL$`?__68{SH{TX+R1k54}*%&)1mg1nnV# zd^LIm<+~Pv(Pe$QNhnws!JZcd{5&iA<y{Zbn4pWfU)JKOmM9!273H>M1{G=&on!W)1us!BLJB(*D>G% zPSR>gH05oZVh4)CqZg*JXgDbBgGn$ggG)lZFskkH0+h`5R7W3$ekB=>1k#N??TZ4& z#%~nP>6te|kZ6HfGfIR*)8;BVN&QvM|0;eph1%i+4tmC8Pco(Yqw;-_G0(>}Xz< z8t5>1&n;J363_dB5i=Y69 zh4=t@xHld3$aKu$&U4*L=500lEz=*m6=~AtbMB04kOUndD9==nH$x@n3^aWV>8Onk zs70uS7L*Yv;Kv-`pp?ZccXaWi;}{W=D3yywq!LCROCcy62}LztVEhaJT-R`Bnm#R$ zqP3-I3@E~yyCqJwm%*=8oMG+|0PTklTtxevHV&J(u{04T0>ys}0~fo+dh{pZX2H!9kuaEjfyRezAqgw%bxs?_&p5B7nRMBl=DP)BjY&8Wj~vP z_~{7wA0UHH#pLao>b;U9(`AU!&{t|>(G;OU*e3$fJuY3n%<>i>*FJ-vQ#LvK6i3V# z<4RTI6+`|uinC0^Qh>1K5I`9GmYZz+rl&>qH7<-fC@_ZNEvELOQ|Hk>4@F_!XiE6% zrQ`)6)1wSrNp}WS()xDhGGL%*!dQvnG<&Z`ixXzsHzJPiG4UixAAExmvSrR8h}iI= zDU5lIUZFO`US8aDA7m;93B*N#25K-A3e%yH8LXKl0h)QJ6Uq@4hkLo0vm-w0VK8Yxl*nU%V1y}h+o zdJh)ZqJ&iOQn{^sP-o_9CxDQtZv(|UTqp_Xvn*eko_SMf-fD)9XU&_>&S)Ua+&}ui z!rs4i(fVzQbIfI!xX5fIO>qvCjJn5ZvWo=s&_1Fu$um+h6$(;dKMj+QrbR|%ljAkN z#?%%%ezatR6Dm8rAOTQ~Zf2(?x14%1U~U#vhZdGSw6m_J-i82wSKxkVX&%6l)Mw$n@>kHy8R4{d#VN)hfGydRJw)1suEK%My1y}d za{neYJS)OUZ0io}uk)f9$|Fi3&lHYh|NU}%)IOW{IG!Kf*tL$ba}i%JJZe?{l>|)n zkQ+6Q1R-742SrAkAt>yU*dhLjZlK@MxuUJwaf?Q_Q?L1CPks+xbT+ABQ2Ybk5IzaD znLDa{QdlX^? z{L|tHEX88_TM=N(J42(p&11a!%0p{nmG`f27MOc9oKe!#z%i~uj9hcUvR{I6SYCXd zZAs0^iUi4a{VrX=>SXOnWpc$(qb3ajdbW*}<`omaM>79Tmo8?e_Rd#I`7p!b0p0*1 zI6^gaEx8kkDCq<3KDfx(nV~jp2Uo6Z6~{qcmkI5cA422TsltKNHhc#VGlLm7g#rbM z?N73^X3=caZYg2#_6t;!J?Xq4D^Gf>5~FklfTvdVjih=q6o-eduQIp?c=2`c5Z0xu zJIr&F=|_7!N^A>iz5!?crA9YF`J9*2Oc9^hS<(u@_Hlm*gPIrRbV1P|P90O@MOPrq zyi`$pui~)184myRaqIxS zihJd0(x@Qa<_wJ3ze;d3_F}os?ZI35Ova0Oxj!&fJ#5FZO%8tUVqdM#bIMDd@qaMP?;1hd^H1R~p7+>QCm_Ub6`Ss1_Hb4RU2XkO# zDxch0Qil`m)ZMe+!%($p*!pJx-3)wqHu9o8wl+>``XN1h+VQ7 zvwPxy;1^y+O>{!k8o=PmpO6!ehNjTJ;Tn_R(>M-)j%sz}gpyd>)is6$s?f-_5xk8n zwnshQZC($z*t>KK4J(SjbBbXbXHOD0VGzNVf;kQPuHI~(XB^1orOS%H7X~o`m1XGi ztO(NAaMu-{yyzR!@mw4yK40F?S$}nM_ElgS{Y!Iq&^Qklrl!DH@j)Ed%G9tmryby6 z)|$f07&NTkO-#dP>Hs5_PtI;a_i9xx?MY~68<9-BV(Oxl=T2=lUj@b*Rz|F#;GKAa z{*zmUEGScuWKEI<_klp5z~=^}St}4c4`v1c4|3xxRV{^ct4%j&(2qobr5mr9cGkF_ z1mX66&ipaO^gB*=rW7|2C8pK&{83k9_qm4;)-0Wh8AbmArbW!j4=+Oh5@vsyx9YG5 zI&(t3;*L?5Qd9&;iyTtsRSad5HtGbOI=ZgG_u`xn^dMf+7zd!s_acyU66EX@u1KUC zX86MVv|Je0N8^js@Deb16jse7NDq0Ql{uu(O!GaV27_e(xrPIn!Ai8ynB-K;@zkmm zzmFX~4~AHC4-~II46)~>d=P_76EdDc-Y|u)$w&8j80ZKD=TrrNc*9# zQw(R!wG+!)t+YOOQ)rr9AG-EY-KV&1l`kg?Jv$1rBHRSqJm7-QJVp7owZrRFLpV)u z`UgTb;YT1L-k8&4odh>HYYcBryr}>wp}ENV{&Golp!Kh&?*M|<)D!Kih8Ldk-C0%| zV~_fYg2cLa^0TQbWGJQOREmFclSNlPJRe#h??X3f(#ZtV${6Anerqw0$bv|Wb>^ye z)6?~k*Qmoq0+}h3zZQx*E@Vx*3)_djP+luu z3Ap|iUX;28x=d4;K@fC<-&|iYw9#5oLw#s4P+A;cRpZAvq2yd&>RNzF2QbB~F>BJJ z-cGo}`qrX-nn30pKqiYvNumIpI5|;EgV`U=fo-vp9opbUuld^tGHQHHK8-*vx3N-G zPac;>UjGI=5g01@FyAo+A1)vq^sEJ-1V|&|VuN%!CUCuN&g=-z{0R!X`)VqvcYEg z0@r@wfqVPWQNw}KFV=f_*4sjGLc@RrGg%pu2AX`mg>Xg3Z43Zi@i9>Q$#XS6D2mVk zfZ*8Di*ynfTs+H$e=uvtnEqdBAwU72;ZHuLf@<9b_IgX0jg5oi%5cMsC=hLCXgv;l=35Q~N#hi<^5v(iAs{>c4D>RzqI9zB zwrMz!$$e12Y(uCsKbTCf*eX(sB7S`QKpS(xge(J+?OZu7}4O zv>^hfkJBjBnQO%XdJ#(bf^NtuZY35osBB%0NZ9sJC*Fv27O3{Js{wkGDS>VB1h5!C z10;?Q{tW@(P8=WvU-XfTiJ%~wAFS#6%51>G8Eg^RwomI1DPQ0u3@3ktWCCs#uXAFv z+EqvYfVQ}>{h zB?js zQ|(onmEsQ7Z_(B`8_`|pGC*eQa#C@on)q{ReSl5Go(g|u%herW4*?MA_N5SbOvN2O zy@i%N=|c{bp1=M*FxU~sg+fGFO9W^nXtXq8x$H``Hv@xCJ%GU(`%u{h!{L6$uLN~6 z5yxuD3!!1!9w4nQe%r}*sGN+QldoFtjvagPXE2h5-letKKs8&}TaMR#J^cReceNue z;39D1(wSq*El#zijXEKxavxZjl?|>zXJ8tEJ+cL7{!&+Z6xed5TnIkR0YsDFdL29K zK$GK^BAnl1t@w+?t~Zjs<#+=&yXy(B5O^c;)hehSSUMBI@6HqU0*o(I^pxA2#^6Na zZ1cPwtUxu+8&g}g$MM>)Gm%cUmIke`((O2CCC=sZh;g_q2Ep=pr+gKP!ZRsbAQoK# z1p=1TBKgmSl81VH2Xl?OVHzsu3bm`Xh&J%r5zEHn?I8Na74@E;`L@;3^IiXpT*Z#( zC)3WF^y@5M4~iU%j)+TZU6myeFUdm_@UYNbCo8v^IU)IC%5FD}5(s1{HG#lp|9Oo? zM#yMJvLpP-y!8&&XMBOdHq7w>*BRUpZ%k>cff0UetgyG+CYv`f-`2G*{X&y4d146T zL|=8r1fGxBQ5ldB(JlY`v^(Vo*zGSO4D7Qb+aQ={v$Pa*$H{!~Q1>&q@^PPV${y@H zAE++Nz^_y}eO#g&-09Ki`+f%9@ZnnYOIlketpe$|NFX+IiDTCshDwEru61jm ztRScyf>oV4Cb>8&FdA`3S9Ro=be8*^)&VVHr1%Z`L-pA-l0QzRQr~$7HCw7_d5)8v zbr(1xrRO4^CXwk2IO7m<<=sxT73})IynfdKv=|L{!gOE;50e9t!RsSX7cu0qdh zRV}SY5M1(-mHX6^Bs`rC)>bnkcs_Y6NKwzY1dWYMXd0Puva8IB2)5G=I=TxK{s-|Q zX_;+B^j)07Pw&~EH5mj|B|TJ6_@vM5$JJAOb~w&c5LY|JRKkp-h_zie-m+-$6~S=a zCbSXB(A9WNOjyH&A72tJ?)qp=3uZQ~xqHB==vqQajdO!KR!eA3^@dDgTwzT_1=RLw zLt3drvK&%qCWx1VwYuzq5Pu8m4}$9{_F+Nqo&^H!XlivL(-W@R?(|tIngOZMCW6;l za}nOf5g}(e*dJT;u*w>W=mbtncodVZ6*8We{qZnuZIFE(H_gzE2tu6t3x>46{xQ25 zSSDuz)GmY&n(bgIy?PX~?#CIoaDaLhka}EN>sb-7u(Id2uSaDk56W%Zu(ug;85@FG z$0soCaSN0P%|RY-@FLJ3zaQ+PoP(n1BvUHGc*iMAC!0kN9$k>uc0%(D@D75n*KR{M3Ur zgMYxu&Ke`muJx|QULvqimlh)gmBm;o9yqvoB3L4RFctf7#zu$kT;jDus6k|9;|CCj z1obmOMZn^&0LwN=F-=FKaLSMx!y?bMfVXR-c>Dmm6?|#NUY=#DW6DyTT`pMf)lq)7 zb`K->`sQXsACJlgT{Ithj(MtivD$kiP__(ND;@>bq|&_aem3a6rm{f0=HtRN zl9W@F5Grl-EDG47l>wQzjQjhP#o#I6g8PKCPiCBEgjtg(HZ6D0+<-*^Gxd{5dtGz+ zlblLT*%*Pzs0wGeF)E(2*FK(sGo9eK>yFYC(eY&ZyO~wE+;U9C+y> zOm6zN`ViwAnDuxTU4YV^rG=Qw=E=%oaOs^82&x}l1M7Snbqr!5U1`aE; z!GcNKHfOTbdI>L}JWS;sDCQluryhUD^14BWG20P5oLB~)t1=lwHVOrMe!F{f!H_or zs>s11euK>iuW!nVh>Rgkt&`oLHV9{5NJkt={U(O$Fbg64(KWdVC|n1N|GmH}Jk0MY z;7L;NlK88(F~`*L07CP*UbZu2-$uL28-3oXPOg>3hpnCpz~8-Ndu>1hS^4w1Pm-Tl zjJ~Y#VjNbofO0!=adNS&8*}JNz@xmon`UssSM~&ElzIo7&e}r!)zhp2P!c2XNswrgEaP zFs_ft_BN5-fSxVS^_x%=sEV1pv()4S(=!h`Q)1$Nl!T;xCIGLL*>d7*WtKb~razD2 zdG1V^4_MoBbeB`D{hCX#*+5!{SMDD;+79~P7{bqs6c>Yw z7?|?*gRd}I96}U9ZqBF`K=r(c#&5&LObFKZsNk?zSQ%~@dDXK<60`>n^KWy6=aY>*Y8;l;hRuRyE8PLOJ2Z2(0vjM9b2;7Fmhss1{00D z)WQgYMPJhR^|>vBH(q)_dt`eVY%8v9AWqeo=I3Wc#Kw@cbVH7wHW)DDIeNsz1;XqP z91ac-St(wz=T9Dc+sGfwOJYTlQ?C9bGE%20ii82$odrl6ImWe$*JoFr$QZI1{Xun| zC>z3g5rhqKwb7%}ag+J-g^$PY`pRyHE9EXuMcjmH)eW42phS~P93&_Iqg;y)C3XmS z0Lh(;klcoo$C)c2Pp4FLm5=+J0HmYH-(7%X z9l$Z(^lgCS)&C!k9>OAyXg=QhlJ?Dnb7u#bV2G8=i*edt>}<;p7^?D@jHa!iCf9CX zUpc92mR~#fiH5z-*z%(rq~l|t(dMvP0jj+RAIdilfO2leVP5<#UNUrr0I??a^GWOk zJQ4bJUv!Fr0w}%2bb*;i11lkB;^vuIC6cD80fk}Ftqdd$y0yqHpR`sFT8z%laA7V> z_CLCjxsWEB(Jiasjfob#-h1ZMJ@M(if-j3d(x(1N5g&gR&FPX@{j%dr)z0tkXFmS! zz=@qX20mv+otL{lq?vXJ{#EYdw~k2ohACAA#29Q{ifBbol` zBXv;*_HL0ddVb=?ShC^&TKwUI^1TeNin^Mk$}{u<&3@U#?Jx)z9<^bIrWqwU`ND@2 z&jV}a-Y%MSA@jmBZFr6_`c#_14(;sg00sXMxh!#V#@J|ng+|?xc-|qD_9!pLf0bqQ z0l}9+4W3b>e;>Rx_r0=%p0C+b`vDjAH&UINGO&3!d!J$Clc>ih9r9~VG=HrAM*Neo zG*6$REZ%xrF#LJA&qk`tIvCleIt1B^NBHAwAKR@|!o;1=QU8XNn+>Igk-pR1z{8@C zmxh1Bk`yO1o>y1J?L- zy}?f-9oO_L+2e(2dh9v}ua9_VTZB#heBubZvrrR3A<(YdAeL)Yr|WG`tl^!8PkK7g zY8@kRBl;{uG@&W#FPE^_GV5WO7oaUH7y#5adJoWc@>h*-DM-Iol;lRkGuH#< zqBta(PCn$K#u&lpN7YG(6Zb!AreU||wdlai2}gKTFy8!)y%ZGBgNlR3UznudDifwk2Z`rpr_80E;YApUZ_~5#$i}!BusqBePf@&4l=wi=~@-^^b?B@v+0dfBa-PW_+3UKzS}(}2x>HD zwFn2K2jfK2Jlj&0Oi0(@X!WB+7&vW0oN;wrXVWSLDJERsb?{D{GmeDC4Kr!-ULE^e zTFIAuom?7>J2w=Lcjp(XS0RwD41GVeVbK2em8|9z7FAL69gMlU0;N_P2O2c2N6J(H zygb?YYomWL-3kXDMtIY=Ezamd&+?g!a+^R1vp_5 zRDG5W!r~V_Bb&dZK9-~Rq-@(NC=4#KrQP4AId&kuR&K20PS@k{3;@u;4)nzcWX4Yw zK2vfU>cRM0sl~f}d_J{rL~Ne*f;m@fALa>Jt+>{?I=#w0p19)s^1^ zw+m`O4^-4#kyfcD@h7(WVM_i};%zQ8^_tkU%7OA9MIWqSC=2lfSuoB@oPX4T4sY;3 zg2G;%K^5cLL(m?+^bbusG8%aaSICF`Dq=r1*8easm<>dWs z$Zr6jfmRGYOAqH^=Sm(Lka*XUBMo;nvH!&EQftW2vTgBE(!HL@k`s+H|L$6(ip_*VpBA~9dhb93eTmHl` zsi~tsIXCpFU0=v!Qv}HT#*R2!@122~TQO-H-zw|n7gd|%Qr%P$5(E=r2VKf_*SW4Rta)9 za~G*}5qdm#;uznoc#;pYPGc*IKgTc#H7EN~t%j5`3!w7p_1jrn_T!-!#Vhu*jhnr^ z^)%<0^*~4t%rhd!z=W%yYMzTn#^&qh_yw(t{PVw?UP2ZnD3wv{<-brZGf^)*!4_NLqxq0vyhP68?P03PL{`1bY;8M|ie&JohTd}S$??iI5y z-?&k1S>5C1dz`L*v&b;By_apPmbg&EF>BCC)P}7C=~sO)46b!2$MlYpo!FK*e+*s# zG$HdtpE~!2J~nO2u^44!&+VT06>ln4M3?Fc&* z9t}w%=bBixSom3v&dxWU%(;lSK_Wb)?YW$qw#;4WcD}OdmcNAjH<t}dUDrKMziVpbw-6(Q58eKC$9IMyGFi? zelg%x+wHiGe{l+DO3JN94t+J?brxgH^dciyZ%+I{#f27)ha#4i*VPE5CD`~7KvG(Z zu=81+WctlbMt)Cp@a;~GN%@1$?|x>IPR2U})YvkI0*g_rMS7dA-UY)Cv{0{Zn7zI9 z;{=Z&+6W6Ch})84ALqO$)t9K6Jz>4^R+HJ++J)G#+TYRP&NSTDDZ5c~Z5 zTxjhWxivNIf)Mgq(5SiiU;;I&s}gQ<(AXm`>i)K-pIe0WPy5h6qkV8vfnezv{FHnE zHq!rMVE#(C}H%!?0b?05rVc} z=rqlykC53)^ax(= zctHQ`gjpv!a_x?$r+?5B>OP%jym6ylUUsahwN|$+%rTM{cG#4cMRIi6b$BO>dEucL zDm8Dzn3vB68VuXq`jcY3H78h>-EQ`lCQrtW{W zXm$NK$mZK{_SDgp7xB`R^Tl3LF0YOV#w1t<1C*w0x^o~r;Jg=$1g>p@x#EwnHw-$GPeBjdQaJCw(=^US^Bs$kD#Cyxi(R4r(#l zf-DC!PcQ9~#$=!tsQ$a#Bzz$;elV+y)Fh0|ZtWzJQ!~{C?+|XPEzJ#W6!pYvk&z0w z2GPjLx}!K(1yqYsQMeOq4$&M+G?=R;b>5zQ%s$qfa+9=1eghINV)-@tfGM{^Xc*a1 z6Hmim)Lnir(aR6*ZG6unv4XO=O+0=Zr17F8Dg!P`#jLi1itw9jA6ElC!T?8&ZlSD> zpgleAbqJmTNRVyc@6Qcx5bdtuMTg>d(tc740i58qsw`TA+oplOINSd1cvA}?;?skkr&RTyRk8a?v(e>oo{< z!5?7QP&!6op;hadRdF7_H(X5Jhs|I)wxX&KMd70wFQR63hH5Q|oR??>JR6X;B3mSfGlmCjByJ4%{ ziTW^rKFMmrfqV@uH;k+-eZ|EKcvBm)YiPEEmsJm)OoK;DmVV49Lz^X0jfY%JFE0YK z!5W%Rbf70~1)fzNtbOJZ#{~nBTN5PP?jBMR)G$}@|4}f{?D*4|km3+~xaw86zl3GP z+o8rd3zpFNWV2+tDZh~NF;b8mlQbOLgr_cpo}g(t`-^|5>j}`+p}BHtu&suzDLEZv zePRzOhOD1zuK!`bw-fG-~9sNYiq_=)$u=uVs;^3sWG zUrj5>p!*}g^xNX3F2NdgFC5PRE1;P>VKEvYnNEM>AhPE4t9_t%+!sY*OO+?%P=yne zr}pA8D|jaY0ewn>p$edWvi&~`Z4fak>QofG_}|nLX%u~V1UbK_wX~ab!f@*TaBL$k zqD1KOC|mzj0Vehe_}9qEr1?p_wIZnYvu}5Sb0%Z4T0s@m^^TLiH! zBYz3^ev7aw_|E)1p4v$e(Ho;ZJ=5nW`y$)FARS=xbHBqI^jsVH{ZI)=szOqr;nbvL zI`vJMw5Tpu7y_{zFXc<|cC z(oZaZIXS$O(dW|CjENYd&}@j0@3W%pSu8%{GoP{s=vqBZMo` zXD~qYkaNGs(*##ooaYELv}JdW3|7dR7LSmItAAmBHpK%-{A|dR03_;Yw=^{`1XEGB zMQ2ZH)+1XCA7P3PD~>@QPl~(|MI}(asU`Qbi<=pkaC}eZ)_e4N2rSr#FD1a#P+897>EIE-mu_ zqlI|m|D3{(X6u|ab^pKVG_Z(^8pT3+bA z4jtMn(7Fx&8XQw&^+_?LB>!MUjLI&%m5Et&|{^BI6h z^^;22n>4R}mkvAqd+{CZTflT~{+L`P1O4cDsca98jT!s`F?YT#VEJP1#h!VH>(P(a;ENoc*M+C=AG60f6hP38TMF{ zz39Ca_kyNyEQ#R7zj?l^T$s(%LyU3lH@0j((Ulw%YgOX#J1#at7@vWpl@~R&iq$87#SqV^gNz}1cxtKvGPM3Sj{Oh3)L{gED_u~cYH9O z`dL?5jQ}jFf|^*)$3CFdzXJ$q;~^xV*P-o>z3uILja#}F@2U5O;dsHX!_l7J{C-nG z^30pN--F#Zf;fF67$9jb^0jYrJRXYCoRJt0E7i zPKUuK4YK4I*~$3rIMD)CA5YM3Nn>N$>L04{J_yw%?wD=p4ifXp!3O)7K3uvzti@p) zD?b>624hHd0Cd^cO7z7K|BtJ0frmP4AD?}Dt8Ghm-*$BocHcxLRLT^g&0BU7D!Jt{ z+FZ(IBzI#_ZS0oVMv;U>iNT0m#~5WJPSl^PK1YJm;J*{0x$ZM|q4cODV2Wvgxa4py4h8RH*+SR77Zw{w%L2>{}%|(8s>+y{EL!I2a_bje>XN7mwn!yx7rCL zewr-X)ybl`4_su=b`}3kZx;W{6tGh|I8iNK|8si_pp($bKw-}R0nNLC-DS(lGGu?t zb6@of(cmcpP3!_9#6Z&CYx>ismKUxOs1hpSkxt11nrCiwHOsN_h74#e;uCv5+Eqm#v@MHyGM zAV=aMygs??(e&y!Qb&*joAoDPiYO6H$=z^IiK{c zUiG%7#b&(UItB~JiubR`HK{QHq_ihX;v}iOvwcv=WmJ!6(jY2A^O%Nj) zBye5X9HO;>3**$jp>dPgOpB6^pdDke&&BUUvrV%$VkPN_j@n)40#FrYr(f2}JTnxy z=rg5Kr0$gCGT@3tp!}IK`mhQ+r+#>z@so-xj!cK32hpShL2{|-$M_WkadIhTXb2xc z<+_upq9KZzXn+L&cMtFP?v7h=>ocA$@nA1Dqs4tUHZI%_ipdL#oy#Q5sk0|YSgGH5 zR)oiRoRE|iCtGrlo_xXxD_pC7vsS)W3Tm${NMr-P0OA?-sA$_3aUm8rK{EK`$?g&U z(Da3l*t0x0_lD4F#OUe!Q-hROlt;=mj+ql4@9D(m5k#zN%k^e2ELFh|{D1wSe5r^{ zTe1Fd8HBLat9*ZJ`g1*UeR)vKh&n-7KW8EUKO$y`sc0>BVl8&@=y>e#S_01>n+Yya zaVC_C13*dXL(YbtP6Ru<@74mNVm-J)I3n%SQ)`!Iv&v{!aN%g5bp*1_!Sjc~lDa6% zrG@*GWc9OP4=V-7s>4O*sfYdbomFUR=Fkgu*2Hm*@DJxTu1w?x(xeO48Bq@6nUpwY zEf_pUzC12rj+-+I%$HitMYWy6+RoU80b`Db3!yOgKHk>ykSLEHk-p0aVyJ9a7QM@! zYH1l1)*VRw9-UG5IyBa6jR4ubQLH(*KcLR6w9dOpv{jY*^g6ocrHYC8{ss6`H*-6P zc{}Jz>iR>Ye^w1e@1$+7s|oAPb^G^&3m(FM$mWGi@F|8Z7G^8vKgWaX6bZ_f9#i8hqS5u7d1_urO>t1mB6(c?g zoYAaG@~8=|M&!8TGZhu~9%`7-TE#V}n!>r->h)eFD6%P;+TU)n;DhGgzmPn6|3XS& zH7ndz{33vO%b@OAocu$J$v6N<L(me~44$iz%h`vu{aSQcTkrQ{*Jb?9eg@JO?r3 zxiada@ESOokC6T;0j!erXxJ}JDYqn}?^(zu4CkN119z~pdrvi*)roHeo#$5#;xr^h9<4%L~vY%8DFGy;JD~&g7>!tjsoYYOnryVqLd=JyIF}i>q+f)2vFI& z09RI>_#P! z-RKcHOizhttduVV-+}9B;z*9zv z)}_&-pAv4@%Fj`2fn{+BO8@v{qG@ol5owEsZH?ST!%@SY5c36}xdd>@R|MEh zCME}&c?hBzTwB_#p7d>&fTS~!NL7D9RGL=ew{ zLn|}rToU>A9po-N=kKkY#=+j5^;m$B_}rap@-&Eo#(y0wt81BO=#%8< zg7NcvTSC(*%qJA4u#~l|R(+9FQnf|*r<3l%uYVxSZ_}E*9TaH^#he82tebFBkEDwJ zJ_PGTh-zJdD1x<@xF9K41#U1BZJhJ521cZfFbEvwHFGP@6b|S49nV3U0quU@U8JKpoA)%=gP!@wJ>@&d=aP5}uFURzMkM)AKzPft1gX zNdy*-=FZ<^r5q(SAREs+VjZ&@6l08o zLrTyj89_Nlz||(wYA4lar52LWm#ejyD>N4y`J(vMp`&-KlGWCyz4tYr%kWs5O&86k zQ;Q8_XKsNZxZtAUjZX4ICy*@8$GWYqgyLa*B-*V_T|aztL<^t>Pzjf=&*wpmf@>Oc z>#T%-a{4Ol`1NxgEK#h8o2!aBMGo)B2uS!!;2?g;GY zW0CneR`L1oUQ0G|b#A#gOL?4071ysc8)~W%bQ-@BbNDLTneu^a6n3mcSe9r}zuICx zSL4c?pYm>8zc&hm-miwyFV{v7ul4A1qKWAwb8v6iA;3P{^L|4#b5S;<3rjd_nG<$; zclc(J3%C@FhBfmJ4no9o+XY&JfNu6;AkPo53Fn0=Gnc(w!sc{D)LMsx+*)QGb$((% zTb>=7Z$>W^`N zu}PV`eKv~{z*QsUv#a%o0+A^cY7`|NxO-Vdevp~T)fCnH(nJ$h5!?-STaDCfP9=M@cZ8qVMZ+LYHdFMkEu5m#zCIQmia znxX<<7e|5fl@CZOUFimtsrTDrg--ZU!LsdYjnJ<9{qcq5n9q7d7>8cQBi-+Q81i&( zVrhNgAFp?hXc}cH!jWZVxKn1}cp2dvzeH%inMrCpd2@gyjVthS!6v}~ev<^(K?B&#m?Kvfm*;MI4*%;!07Plju;9%Bi3 zCFkiI)dUFk%6zt-F?%{}bt%w2YRF0A0(Q+eWc%MdP{XO9io zglOuBKQQmQvG>=Kw+Ew5Ebb^G5&@-BaFx1Ni7mOx99R_5gQi2>6yO9Lb926di{lhh zB~$?~P!MUQn;m*aW#w{@y2tA5{^>f{FDjs3PhDK>(6YN(MU9dqnK@Qx4j&i$;P(f- zT>g*nqaQcb*1WcwHkU=^0{f?`ngXtf1c&$y4zfxksN-f-DUdq(sdlBgcO@Sa7&mDC zky)?@lyS$Qo~Yhm-C~{t5AI}vB4QR$pHHgU04C*IFU-w{O7wwn9i zDMe1bCbiaaL`8NEa=Xgm-?t?Lxf^Apwb+$dCoL>U&RoBV#j`W3RE?Riq>6LowcAn- zm-|^ZabFi~@v!K-oq=?bR_n4^bpLhDq8DzMTw7XKlRH7N7mnPZA#$vEO9V+)WRlN` zU$K5-ja|&Is?pQeGc#R(6AXQ&H}p9jJ7n##{3(0!H{H;osPT4l0#4{l=eAfQn(~C4ZK<`e=a}2a zbM5Wz!mdQK;!c8W4q+_7zh^9S<)2l^AO)d>Jy#2CoU6i3)EYc{y<=o3)Wo9pYX%%U z{vJ*aPDL{%r9#hFAR7_@&nB+HVzJ30fMsHFN%O-&ZeIykRW_{({2vRpz)aj-!AMEn z;(^U(Pe&sXex3Q=EVRqSTEjjc=9y-Qj(E~Jtl3j=7B$=NN(z3c!Y)SKBV`vHeqy%p z;Q}+X@n&U)m@9bcfp0;6fI@0o^440TS!;T`9tIdadC46nNj+6tH^DgqQ5NQ@~WR$Ax4Dr-H*z zcUITh_{NQHgKzvrtO)XOFpL=bRYI%C6O3sAVHuI^F2u=N14#b$RF zV2!71qq=VZOC=J}RwDzUg3o?u%!%&i{s8nVy4mns= zy);oH+&L&}a4`DiEstIgJnsN*U%dZwh6i4FZ6ozrbv-W-dh`wySU% z*vY3l2SB)}$o5jCyM(7EqbXxu(;1WXll2A0-9SQ=R{DmW2bCM?Hz>Oy`R&#N^~rzM zhtc=8Xb#S0%a@Z)#1p~xUj8BYN_Ziw2%Y`v`CPbt=g}EdxY8KKn3TEEnz9M%utpHZ^G!sX9d< ze41w=8zHH^NA)H3WB;=_(sKA|N!36Rr&ML|Ra_L)3pOw7H#Y*(?%7&fwTp=@UxA{- zptB6@eQSh`h^|;^l#oIneRO?2XUQ<;JaBerYYI_mS1X1jLz)YJCeD90jH~6m#q(JTcYpiuk%_Z;QDs_?TOK;RZXC zLP(lfvPY{OY>L>IB3Xw+dp!~8CVdDDb;G}2tJNh*YFE}F=GX2gs~rWTWLDXE%T7Er z)o)>$wx+!O;Rf;E-UV6VoEwI*!JTo#Yk^~INq??F&QaZpM(Pa%h%Rj5@wLIvZ|oZB zc=bWC04K_Gqiuy#eofEO6c=AWCuXC$KnANd&MtgRJe*%)A?}6|Bfm&}Lb#AQc>%)Z z%!hCp0Jw^h@S~xMz;)w^;7D+X$yceZ`Brk7AfivgW)CN7nFF|f7@lrY6%>Q;3Vegk zI9bGJPuFp7z+&0>&$zw_GeBMHm-bbE>r}WFoM{miWUhLEF=ys!`xb-hxIB0CWS}ZpaR7+yo7G=f;i^zPZZW zb-!_annJcc5Yn+uG>5P`SLS1b1~E3|?Zu+#(KL@YJ_>O_ zVV8h=7lS{XPZVAS@w3(0vEmOID91|hOSC}#akc4umnAs8&;w|DB<1|>HTb?spW1cw zZm{o90f@f$Jk(zWXo6N$B8W++%wMhOd`x|)>w>1_1JEAy6`&{4W(BRzYxP8p0_pq83-_)eR>Dc7cNS?p-2hSE92x*_ zz7wk(2TVj6M*b|S6Hy^aj8Vl7WDY}f=64Xi*8+uub#_9*$WzG4o+^WNVpvr(GC$+f z|GvEj{m^5?LQyl|8580Ep(av#eZ9#uQGCV z>hk~4xkXG)O)#JG8GOHw|6R{gt$saXJp>e56nmWGU4B)9qf3BOjf?I*eZlu1A9q0s zJ|udn8V=q#G}$rnmxNYd;F3&*YVH*ja?750Gu3UBw(|uSCo_`xmLEyNlFOjk9RPxQ zTMntA7+x^C#}mZ(iYRia|Dxb~yMRayEZT1m#7o~}7rF5GTz1|1wGPeFDMj|lGkEF@ zwIRC8Yu_YvK@6k%-wlKh23jSrY>cU5g%5hnmZu9BE#z}>0j_WH=#1EWU=!@kzr8q# zatzu-u4c* zz;1bsuaFK@BIv5@IE=A~LoKz!D*`ak-3|t8abAp!llIC6+-V`10)Y8FS(O5s`SqNG2S0K6Vly0MiZ8)^C-*4?dwt}649g32i z_$MJJgjIeyD^w4cMdXB~{oA1N8|YO}v3#@WS66E~gH13C2@E{!$N8iMZsKLB|HNxt znm#B;xol!VTgiZL;%$Q2oG`krl~%|}U&6-~8o_^kx6SGwo(Qpf37wRelnFI#f2U=7w0)#(hCfout03=z&X!TP0@DWPl-^kAVM$ zcm{r`2Lx#yn~aI$4k=p|Bt~Kuuf|V|d3ajCtx2>SuXVkQSUMjz#ho!Ld~g_q(eP4r zHsTM5>wvl(;23}4f<%yJq<9k4!NsBL5u3H~*G*wY{PR7R6pVPw+wqmSr^rg$A((%) zkyaDN?Yo`%P5iM+kU8;K{IpT@jJ=fAUm$xiEcM*yu?wQXvCtdr7g(ztzsA_rN!~l( zk}B^X(PY?Xi+go8nC*_Q^xT&*f49KAvc*g`Q(oNgmruRbBNs)M&jyf&2Z1$5TgIruOWQPhX4NFEL>Z(uDzas(Xg&xD?hnP{mq z2t4E%s0cO;g4u#$;~hS|2h8ghHZkVr8S<_s(Plg@V2l9)ZamQ6kP8ZoKq4->U(ci4 zQd8XZ+~}*ab%%jQWZ$OiH4|nk0zq4JdTc3devx?8Y}5&v79geB>CP^*vkd z3L@j=7$J!wk}qljA>g^WmO&M;^gzhd39Dg`n_QAunGh z0~aP0vSvpli(AKK@r}VMr{%)%1Z0pwz`Z2fA`iy2Ih;Ex^PN)izsM>wB4Gz&B|JQf zi)CvFLJJ`DNzz#j!3VB~TwDdTOOfk=`jn~tIK}#wxo9m+N7=crRrq9^de+k|oLU5B zP3x&M?LmByMo>51fYNJ(c?5O#@))qb6N^T`4*ek~IJBpIySOZ|9nU#;a?DujUqv=3 zHOK@`QwR>Kmc|Fw1j}GfKChbHfUcjk$-0s`n6!0G{Jk-jAlfN{I+rB@`8YS>5IA^0 zrA2{_Z;TkzW={f(R02PV(v86ek6e2G`R!yy_#^WI1>BL18N^Wnee3yQV3`Bbpl=89 zG=QVnYE$* zg!IsnSEPGZ3Ppo_PA;QuYx&Ak|KRX%8|psY)Qj!}g&<(_p=wcua-sJvG;!>TvPiVT z(hnaDAGQS!+y*7hrwC6>F7`AaX5oQ}Sb7Mlb(@y(nQT}~9z1mzRlJubGNtwO0iEDK!<6-b|_<|Jf7nw5=sQb-DupDtyj$c8`` zpL{OzAMxJjK|-3_9u#Gb=hynuZ>tYg7bEE+&oSoY;Yf&)T5o2EV()e1x%m|OPT+NCMuyv7=PA5&>0=)oMjGV z{z6F3U~r3;MYtxa#<4{sE=E58aC|?YR$D-8kcHy~`{%tPio)3+3_=!{-5Im33>1e$#D}{i`)U8W(m;NmlFH-qxT2d471f zkH7KD{GZ!_8$2Z>X-yEy`8!AjFucNF93fSaJsf$a3N|}#?0ODi)nDPz31me~l>0y} zZN16FqmGy+b!dbEs}+?Q03tBlHgtV1G+97NueJKnIK-55X2brhTW6ZhM*Yx`$US+{ zC&J!AVamvk@&QXV{vitGSpgFuh*|(7LBhF6o8H`kUvKIRxWxMj*n&50c8+N7&l>ND zfb~rA4uWb@A~43YCClT`quv4-BH(#mvUwfUoFI8yv>IK{Mwn^=%zia)YGXja<_MA> z4S2bv%uR?2rH|xu$Ry3V4=v%KdME-cK-pz}2wSqyk`mQ6)kM1#$%ZTF8F+|X_c#}A zTD=#$r~jFK(@GjR)PYBd>W2_I4=<30oOr=4rmR9E_H@G7nDH#d8pM3vVwXO%q@@u& zto$>!sKGt!{;}=w1OIjgxX&24hGgz^Gp^ap(}Y$Zm7(Yg2=$VMgTS@HPrk#J{=T-c zXg?awu*S+}*PG3EfrGWv_t&dJ304*!6J?@0aKB02wb^XiZzD{LdIm0Ka?}9p5PTm- z`2bd$gP84}0@=Q-;IRx?J>pRPOvX@P*7+PZyJN+e>3%3D$N`FDOdRVaXK4-<$RgXQN(e}S!cH^8BB%~|eK^@`%TvU7(Y)yaWd0Rtm2 zuLO~{AN+F4FtYpBgOfTAB9i98E86gDHHOpEke6?3DCp%kPl6mQ`rA&y$HiL{9pkXV z3AS9y2aaY^<|+_cF=c-?fcZ8v$+X-t@wNhOuZ2BemtA6lCa+#6gh66H20~wv(u>3jjLs>58K8LJS z82^4C^ih2%I{UT;&SjaYL1Gt*TM8_wO{x|#kSCV6f*KR)NKcNrWcrSZAP1SUxuSUh z(X6PzDHVFOK-u^&D z?CzU;x$S%lL80G1w0w?B%5j8FS?!INuoGwVs-hvctWY3iIM;o1SYja;$~(Qc+tc0N zuW8X>=vvJDQB+ZcEd=$`NZ@9z=liZ)cc(yC_bY*`$^J(ChMuUOZz2;SwF}PbmSAZO z8>x%0!K1Rxr3>cKzY3q-*@nofJ*CAB=7f57%xZL15?+?31HtojIrJhrhQ1+t@h|9$ z1B)d79`~u027QBk*3ehWykVFCEyDPFL47$a-v&#D17OK0TGeahlpbe;Do{ER81Zf- znX4+FNP*0yHzZ%aZZVP8qTy8O-Xxg{lcE(qEX`$e#LvbfyO8qqVojR6^gWm_o)40f zOePaz|215W+D^O&4B?8OB#4kYHpi;9PeXowTZ3hNItY=rd-duA%ZpR8shJc32&zUh z_oK>gZjBmI7x|{dEl)M+o<(oki$oh-asLIH6=%|!qu3ay_cA~FT;;G2nidtee7_|r zYcN%lYWwHQAX zNEP;782>8X!IxW7&4;iL22|M41B()GZ$Xra{!Ha0Gf08B5_%<4n(gU5SRp1AwH#h} z4$FZ?o`UbEAktxwr#~^Sf7%>z4&EB+jqpv`xvk7f3$&B;MQ#}d+(!J5m5Ab)G^?(Y z=O$_n9Libt0`@)+V$eY{X%{!}9CS5qAzuQySa3)fB3+DG#wzXudRyvVq+@$>myAdF z?t$*^_YtR|@kIC_M+VH}0*ko}NI3t5o)u&rXlsBdWUK<3D=xy41asaHBm82+KE8QR zl-Y$AJwWZ@9DF2Ss?B&sLHbGQmS=5re710PbXl^$@B5?JX-bhEo zFG?Jmybw!^XiI~%z)myKRLB5uWIcC>?7Zr`LwnWutk zPsF*FSjc|c5WTof6Oj`YnzG-mLP3)IY)MdL9Q0c~KRf@quT*gkfnKKu$+QFH9Or!x z=e2t6gF2uBST%NdaSxza(y*D>07O3ES0nAwFC=b*SC@cAvr1vnG`=kl6w*ksQgm!y zUdkaKK(0q%vW_NGr|p$Y2dNK{5j`cVx@g1~7R+5+c?)8z@yw!>^N|(7rs~Q*W0SL1 zqV7k3!pgiMuIN9j2oNg`oc6wtFC_CCn}M_QTsgY{iBp_2?bPB|Kv9sS$1*GeBH754 zqB4aS$*`cEGS(U7B&`u+Dy2sY5V*S!lk3>sEn9o~wU&Mf31Q5#v{0Vkr(*xld?O_>+QmB&Af+g%I+B4?;w;;w)*Rhcm$cyg7uMd*0SYgvLbVuYIn$cYB#E} z@*t=LtZOk7xE#0?DOb!uG8W1w*w}fvZjK0dR?21djYfIaN-}BEL731g za&-`G0(1fXF^0K4QB?ov*#WRH2kNl2v~dLK=6w!vF2cc-7_UUF*u^)&sCe!b7#0Rk zMuCwQ5NUxJKT!{amqfKsb;%`36h^2=%|`_tU8K2azyR$+K?>iFtwK5eZe`9ntr7Y0ssrUfJsIJ#t)%(Kl| zCEe0yawQM84eV3MzBp9BnFNrD9F_cpOl=5$Z#TDeLUvLl~ecmW2;Ww0B1$ zxd64dT2AWFT!Y!l6hMU^PC{J_?KYC}{(9sv&9ZthsKlcum2tM2RRj(^Ef@Fw`vyW+ z@3x+jo}*)z>DU8kH2TU`lxGM`-fxT(I#sB~l?)SLwkH1bh^q>9z>Jc>=N$mDp|~nL zcG&yY7PxZp?ZN`O80Ow6>=DfizF(oLx>$nXlw?!cVv$fqFbCS8c59^{nD-ms5?w|6 z_~2~~-fkSNwih{MBva7EkvYiOSa}$zLxbo}16G$bE>6m*P!(V`jDJODDWkZM!*fU$ zd$F5EnYYvsnWH~*Us3HbIK&T!1FOA(RcE&5pYLvoy*7N|IIutgn;Je4`ibhq8^GMD zvQUq*iwJ3aa7#QZB1ON-kuTL?Q1L58i@=9km~K!JNDzQkfl#^)@?O-*`pey);G?oy zP5VH?CyZ_wsQCzo^#2b^dtlHYMmNW;3Q?K6(V1Yga`=S062SK0>l~SIt?L}{SjKh< z=0rh}a)fwdDz;ZMC|N0PdW?h@Nvl$cSGp zk6YlIww2a(Xf5J({yg%_FFSI-{=<6XsWrQP)46$;T(wS=cVrv+j5c0hzY9?3Ca=|+p{IPNDxJ<`CQF82A&W7=0d=Kb z%$>rpmyZyi;1SvwGt1Xr8Z9t)IQR`}&sp=CRy)T`LB|%^8)Q-cEQ+qoEQxIa#6TG9|wWvM+S&tu4uK=~#x~+@7r4aY19R(b^t&237{;oz{ z+r_Z8T>IVXLkKAv1{@{Eogk)2xwNOXQ8qaxt5C-ugTPkI@`jve;qsl8T*xt~x6aw; zXyMf21^ffLJi6i-fe`ccQ7Aa3UnSd=&w!M4)`g*&|UJSLQHwEA5wmR zx;y7gVAV@*lhT9Hg=D++(Fo)oZ%i<@Cy6P`?`U3AAD-VBL7^-I!7oeGRu<=Y+4l#8 zZ~&C|?qYIgC@mNqY2pKMg~+RqH@0SbaYkEHLtf$8u3*-u-mDQ2f^_V%A8Om%Q;_3?nWK?X@kzmH+b8FxdUd;y%3A?bSZ~{ z8;~{wOwqH}!jFz%@H+h=Sz&#xt3g36oZ&0;>6fZL;0cDq_!@!@j*oM6poO0hsXG~t zI9>?RQlCd`!uip^w4-|NSosXRxb&m~g}PyrX{;XvGVOkhP&e>Z{Jx@Co)0U?jhaNk z5Dp-ki5i@QeE<}<}NnseS3XPox6W3_ zx$+()!yxXS7e(2;X1WRsumm~$Tkr&B>3fuTfjF82GRdz_qvD78IkKf3QhS~~B^kc< zym;1Z(C=&h7TpnpP36b}pG`hlKK$ZR&#Wj>^Bm;8HVv_u;R}`uIs#z7E%+uKb!QlEwP zC6kGd*XKTgw2|VY#n*-%Kd;t$sBME8aySTuYvaY!!T@wBZo;Fv1Q&DV4I;z#=(O&| z)Wy;FgOa4laOvexKL+CItt&0^^+#f2jeCMUi5ml7bRUlz8BRJa92j zJL>d*+ZS-TTi(@V7AtUkU66&D?W?Bh1INSzvfyR#U z5&C*aN}iVk?~e*Gv#KEdFuVhGB##rWF(cMa{pL8F_a>F1>_9rj{Yes~+ix{|vZrR~ zt``*~IgO1L21v-n+due|VZaVk?%~Hk!kp`}i+o*$IL5)v5#6VrART>9wk1|;5ken4 zcyI#f)-7l4?d^wR=ez0rL2DnR#Y#0g<3L)l4U%o&Wa6sRkH!qew(W(ydb@N`_*Vm_ zYS$)6exCWH=qmcR%<;E1HbQmN$4CHo&-X(Q^P{RKdn!O#1YG?6HK3MoJH;j^yH~al za<+dz_!6~kpr=q6h(1vgCvn?cUH3DxBir+0{UJ=UF}Os0(~qoDy&<=KP_#GhDBIzF z{c?`p2#(3Kg&(hCu(hKcKn7xHVWAo>OVYPN7UBsIRnu+Nv!@KfHlCNyHU*(i)V-}q zku?x}4SLd!=3fEU@1&mk1Cnv}C22D8UtoHj=^iR&Vt^}w!@ zj#_EA0%72Aa20CL`%p^5!N;D^F^j)b${8#@)2xtco)-h|d8Isk`m}!X%{FU*+T-`P zrI_rpv9SS+?8iVdE$ix!3B#KH@bo6Zp^{EnQJ323W9rsp@YSZ5YVT9TaPNI8kSF#o+v8KX6_l5gdO8D)|k1BG_hyQjLi&h4McB zSo2-YtppQj3^wkVjle+r{2mM ze&Mh=;&y7VA{@D@g}uFUXBaSM4MPEM(ZHgQt0ZbT@?-=BYi;WyitB(dvX*f!!!cI9 z;--$4p&Oouu19&^jg+bnoeFHoEkMSfsPtPbQDE58D_z@A6OD6Qe|X{%RDq{I0n_Gf zP4EcO%g>Lo4R*CIsVE4_IYk_6g`iQpUqB~!{I+HTg2g}9Y|t5{O9WV+rt>}|i}rOz zenaWL%@Jpa9oqWa{{`7A=`z`xczwNj4FV33J2Q2}L$LGX_tUgBCXFHCX*~S|MZ&uJ zV9bIw5pH0-{5`m#6EuE~^_neg++dR=QVY8$^<0iNt3^6q&pgId9~bU~FX zq^BKo<>|D;nL!Ov#3Vaj9ZV#}Up+0vp{G2)(CkNz``XZOQ%Zy58GwGbMt1#7=U?3p zbRYnC50JKJLWpaGS!qYl!zahf8QWWdcy%*bjoNcUBj(Ld^fB%U1u32{8shyn*4vP_ z1C#cH0;)5C5521Y1N8omfkP#lV&Rb-v;=!3!_CUEt%%XJ?H4*F z>%&8@>*9|0N|1}3bX#R0ZaldaUm$?cw8zoT3O*55-59r?R}-Kr$(_(7qrI%APBrY%vT5ZnQV2dgUmY=b8iL zhluQ*-y{CB_JHADDAvq9<^~v!nG!YR!l6B9b{CR2WTpn3V!NE%fy8=#c@Y7{=sGP{ zskLI`x|sZ=K@qxJcP)%~(vCW0>Q}5nvuyauHQ0#@*Y;|4ZCKRO0D@S*lm(W~2AL zy=MV49wFuz7VgA%+&|6ZyC@0zln|%8UpM0v`;u~I`Ta&NpGl9tLM7b5F?1CxDzyi# zjCD6-*)9u*S3~0LGR14R4&epvfM^X7m#C@i9v$f^D@Ae;CD(XaKeE`hd&n;?*nx*aSpqXTUB zH8fOAMXg5`j>k*W?ukHOT#wq69iC}U9f*l~xe2rdnvlav4zexxAow23-2|)0Hx*p)dxvJ;j!g)F`5z8p!cPMRN~q0>=>J^Xu8?k9n7dMg)4x}A_Aj6*n}SE0 zy@9c!2b{-8RxrQSd|N$TdqZk~J7TgO$vX!@bW&4(GW8J9?+g1r7vC&M&C&aP+U`%) z^w`q*)+Z3V<)WuvDu@@qzxFeL7j^tvR;m}nJwiWg1H3^#d}Ot_dCO{(obt z>;aOt{8cp=m=1)lcK_i@?qbb&r>t3XxPL=}bz>VkKTw4@jUOnH_hX<3*XMt9#T zVg)Maeaafl8;5`SBmDX{j*@Tez8>}HPG}}4s2-~RBZ3mBojd>AqYV)P2kGCVzk!sb zuyv0J^f91SmI5pL>jM7;4D9&kp1^(ls_kG#DAfLE$@!!t$_oW;9j^wnEdY$W|2~dk zs6^UOIc!n>r9~LA9@ydrUMJ+;Yn48*x3Tz}+xv@w_?B&@CoSKZd$QXpN9Sl%x} zKCE?Y(GN94==&Q=@?nn$ACHCt$-u!lNjR8%s@vV`;O!UR4t9s$L=b*;-13zkXA$y18P5~LDHGWRCDjQ zD?y=^zx87Npj?ykotW|r@WGMm5B7boy;_r*)&|+PE8bE2zZ1Hp-5+yH{{&?CR2*Zk za$SCQXeLx$MAGBUm6%_FbJJC0%wDct&%c|EMAJT!UjU>qy_F*s*$|6OaZGRsH z3(vQLID`>+pzVSds&0X!mN};BDo2eDO4ZKT_xjsIs7KGkGG`TMxpY~BRrFS_;-x(8 zey<)IL=L6jMn{!irvximXsrM5M0Ma$A)(R;b!6M%#k|`^z&C_L^&w=1pi*FC>$nr> zyZ!k%ErToP5!C_;5kVsq0exsM(9joSSmti~YZWpqAzwjJ5vdAlX7HMhZCKk3n_v9p zzn@`rG9Vq-w7P2<43sH2_V`&K9&agV;3UN2wxiWw3_pMJfxmI+vu54a1a(d^;JuPT zVcVi|W!r9kS$=`dFGN6(WXN!_li_AYxym8F00%70>ng@OAe&(8+&hyLcJ-t0b}A$h zOZ*31utlAdk+>iEVkLR~Tam_T$o#W-wz30-QYi$3|Mq1yzkFH)eT_#mx3w%9z|JlO z^KDb?o0_1^V9H0A~5%h{u36u&Mh8kaiNdigl zWP&L4ZRgT}kcXox{>6JJfK&cu{smN(e_^@nQ2QH%3@mNn;VI=r{I;Zq>Rj5T0Ts`q zuk>w*_p-7O78*3M>6F%9yslh}lLZPkQ}o(prC9m@`xk+ct*z%+tXPGCfq|m-WaZa) zkXXPGHt4^vukSrjfMV~Z+0S$zYsEBgKLe+6Iq!$EMfq=k+6)vAg@|}iKD)S|tMi3R zFTTF{!;@`*t*rh1{U@P@L8(|H*5xBXn(kXY)R7VH!tmoK|FhKo*2IN{1q6U6d3kvQ zZZ7|`px&7B7f+rj*2|?lf56T0e;V{@yVUhoA6kLB9oSz!Zc&mL$RTp6UpVc*jBNdlQV0O(uRq=i8*S-%>~(MU z8=!|c0QbjMUVsnxKY%M4k)>2E^v4Q*3X@#Evla#u<14Fizyv__ibzct_J5l^n0PCD zm%`Ne&cT~GbIcy;>S+PdYdFycfZd&^-D1>LPU2cnhHU$oasOnD=eMn9X) zEQV+>SDI-+>T1Ng7=Ez@&?(xj^-*&Q?X@&F?v$yTt`IoSz+oSCN<5Y>weH%khKf&k z&$B$HdQYhXn*nz*XTFS;Nc3tzG-dQNuH`WWzf&t9mXW0j(mX*-)QsMd1>^-(Zzp_x zeMM{JN{w*pUNB=PaxmnhVEtdpAD5KqgRPFvi1{)5;X=w2@BrhOAMOs-Pvd|q8o-Y` zg&2WH^6)z_W1X2!aPi28y>=OZCP(6u?0{)yqXe!lkqFO&*o~M)b?&qs3Ni077a~vf zYEn`fi>aMq}@X}YG*uw;^lW7Lthd9m~XFvQ*BU8_0Goz?OF++ z0*we~JtNmdL!lBXL0=-J&T8y~R{sX2%d>`WO9aJq z6_DU7uK-8EA=4Zwwe3Q0faY|F>T0Nlm4l?-R1qjz=Qq@Ul^C{%5$mkc~V(<0F#y* zgW@ZG>I>XmZaDQygi>?jfMK}dZ*9JSstH^$clwx_Qf}A$0hfoRSf1lIO;bm!T!Zwt z0ygwzl{&@TgsSC{pbr}MBVT+yoE>y_h6R1DxI*I~B?eyx{;6$qf%qBIXI&mk{NWgq zb=Y6^2S(H8{fm!<1?Anx)$etzKutTT>7To6 zpj{0$)jE4LJJi0iP_a-EVC)QqAZ@JMj^Tr|z;=7o!<0e11Nh~jXb2xv$?dTO6G9QH zeIi?Cpwqhh%`&|w=`-llj z9-yGp#_&NI%VA-!J*NEMt?o+qscwxh3==~1<(R5}fxGzR6<4BLh>(6G4~6Ot&Af^C zCstj-K-?Vv?GIt)lS8fL;*Q1fLkRFglwFw8n&nxjw~5;6*l?H1$)2OIvD8V3OIJp( zlJz#g;K<^HV>7i}VFG{m7);OKw){my#m~8lAWHWE%)oeCMk)~hNM!$ts@eQ0pU&(o zZ5}l5K?vl>lh4Td?U?LBL4fm@pqX|n?~h5~8AY*5t@yw2Odt)$%L=2btskWBCJB<_ zvadsQPGst(IyC%i3+fS>WRKl3~v^5LNP4VC?$%TSU#(!`BM;YR|ReGtGg8f|ENG zp}hP$6HE&ro=hvvsC+;@o_C``iTM>!3Z;z!aeiGe3=*w$fB(=cnBd#=B?MVOWTt6v z#D<=RA@PeKG(TF^TWSYePtb}YBqxytNd4vpgmY7?0fuNHXUmaz`5EfmuIwS#T~|Y z5jjpzFSPBm&KGoiE^B5F#n=(pm%BTL&&3?nHW;w!0{99(Qgy-;G zL=@5g8^kZzv{NVF@h|s>p*FAF-LPo4o^7zViHbgLC!N+DO2fampgeyGrO0abRdW9Y zUx|Z$B@lFgax}NcDuee!+c8(VZ)}7%P|`kDsfEE##2WGZtx#O$|1imPhP)mc1&?S3 zwnRO0&A;{a^&#En{m0tc8d8{_=o1kzAD9FP7ATW+-)3ZZ#R5axpkd-M;7ky>|8&dX z1b9Gfv%BInj96IPFb9^y|CU40B95w#XF)B!=Y-p3*%EzBOPNi%ssplLw#`}dPKi)n z@A+1CUwd3WtU5U{u>;mQ44askTp1F?VR^D#MS$_UXCMm{ypKPsOfsEt%gFx>F?k!! zJOwfNiWS+U67}g1U1I+BN-m7dp;jd;Jd%|!be60^4C43-)ViBEhI$-^X9B&M_^U>> z4WLtnVchs}$)5NS9DD^8_%^1NY&y|z&Q3`VrySTI9m;IA%z`GH=l8WgVp;;LhrXzw zq*EbdZLKBMOro|1M&_(YhyJp^w!u6V*nc^NWN5R3d_uM^(e0i#?U0BG6T^2nOsu?a zpx&f)WF?RQFxvkA(?@PJJ0e!jaYCRuTg^>kE3C9V*KA73a20_V06jisZ=bB6Nl@Px zG*l1QcM6gA-X_aYU=$8*^_mF4SDjYl{O=X2P~p!$0U!eBva{-u+F1v@c# zKdj6Cr~lanJ2*WWL}b!IK)GM_@SjNZeg!HCm2!CY7nu*IH#9U}~oSYC_hrg&hZ`OeCP}jZl zdc{?C1${J|kZRK$zA`7wWniu>xcDOc{H^c9*yze7lEMZ%UQ^^NIUCmW0Awl?cPX-M z3-7cws#($gJ{=FCnD6=se#rn^P!C{C&d9q|k#T;#LMVx@2I8KSP^uuN>w<1=r|qqW zpxTx8@}fT#X{&Bbf~&jdy@S~x$m!;I@P33JuVVCLu5|SK0t8nespBw!`iB*rrQw}B zksz7p_s@{bF34iom42B58spfOVWi7&#|Hvi_{jA-ps#=(_E7th4NnY*iGOJ?4A|(1 zF21yn-)xCgb9sj1by}A&vlThA7a)oC=yVt*`JhGXr)#Gn9LKM>3c~Cm_raIhictN8Snd&383DZa{ziW4Ub$sp$pc$BpR_Ob5Xt<=Niku#r`{~uS^ z0oUZU_OafU_ByE7RzyU!MsTnM1=)J7Vnje?&!8;X0tRFRXl>OhioQ{htwliydvCBR z7&b!KLuEw55->y(0^f7q*lYUv`(1C%o4n_o=RD*8jPsnaem|6V-O}jYtgUuaT+tBI z!)#1G7Xak=P^NMt%5ikOdG8dl*$_)w`c(HjaQCgTf~2L#y;_=u6aPkU{KlM-Pf16~ zc@F(^a{j`S9Fgf1Z5J!L?easMVvPx!YSFequGl3R^-2$P^e6mGGOI@+!^@%_3UUOi z4=2RjABjcWGFi}$xMjaiaJ%K2i{IZDXEE~9;?icpT)o$wH*K9KN5gjSI!)gJtrRe~pbeEPnX!owBNr$fR zOgC^?y9G(mC+Q@fqoC|4z)RyjzJV50>0R{56;4=tXXnCy>%M|&5^HPg(79o0^KxCe z&m5f2EnHJJR1ze?ndNFYwGN|+>D(A-I*m1(PuIsWPO-!XFv4-IFFmhvyAy z2XH>VH}{;dFS~r9*Ct@95OMPJpNrkE+$UC%KJ%jq+dViw!ulXt?QvUs+W7+> z4OX&(BCNV{M7w}0$*^^|C)yL zh_#fAs=qht@{VeK!H{USm!A&dULpP=OKw=*G7_bi55MG zSi8$ra?%7n`1t+>A@x*(#z9VV?0r1M2v+h@QBl!k0e>MzH!Ktj*-I~8qle>{%vu>& z0|2$$ff);mvKOXxh9q!BcFzG5h=Dg zTq*ax!)BtM`vqm27S{5_sw4u5$1-nkYdZtqR5AhEKUbI?TG-1E{k!fj*NH+o&(`cT zsiovqeHLJ?_*lmtC^Jr&2UbQ*wgVxCByi|u=yQajeRyrXeF05oSiy#W8!B(5v$NyG zJ{LPWoWqi2=zB=Zz6dCuUaa4fY7&>~v@UD}ZM*#m&od-xYyRy-nPEeT0vz0Y>{O#> zRDf6DJ9|tVuaC>SsaUof>1izE)Lf<@C4E9QGu!>31m_Q~o4^bDuwNI1-*-;D>NFDm z?y}0Eqy$I1XSi1FFsba7HLFfeg_6W8g1X$QPbaba=C*-O3Zwx8QEcA%n_sY z$3H$ue?S<19fpult5G#? z%G-}m?%Gx8t+>=8*h~vkMk?j9L4Xd_P0w=67Q=B%V!ypne=eD@$Y}|Yfj9+n?!9zo4jo^KO#CYh?h7@jl#fq_HTMYR6Mu z#u{UYud!a9CL+^weHiwVVA#7{?w`z3Y4|jJZWCeAAB$gFY)LIFECfDySZrl&?Kj8N zB~67Y7IFg6Z?8tD8}-pZ6&(+l|NOhRZ{vuGp&y=!`ms1w=4i3{si8B$f$#}+WSl@! z6dXc1NaGenW{)?E$3~Qo{=HIpqVGv;i78(F;P*A+n?P-4WWs-p=|Xc&H_kmm%MTHV zmulwGZ!^Z5nfr!y%t*zXKj0FxwqQm!X8vP4+yJvYtcwhD=j$+Nz#7OMPo+8nUK%d2`KYZAH=AfC}KSy|@I&)Ehv z<+td>5oS)z^$DD=_s=E?eKI{e=oyTe5C~1emdhtGLj{Bxk}2i(xFj{D>MqieV2OVi zHww+$`_7I!5+Lx=Tl2*kU3y)Ym|$Qa%!{2mrL0bWw+b%;t*Hq%#q_BMY<eMgJzPJ|s@OeYEtfhZn zX7SiH-A_l5hII@5bVVA4q6{!6qK>sHeiFp}9F5C;H80g_EWvriEn|n{72)%ml%{W+ag61-$WW}_HR%wH-w3{NJVGmsOCIs3S{v{@I z+M-yjzR_rINIgt9q~ zo7J|E7NHqdNJX=1wa|%q%ue5z-RP>jNCaXixg{)6lSX1`*@jU5EB%?J2h)NI#B_&N z88{@aJhOh%`Hmt-k6Imr;Ai-b`BFkswl?`kQ2bUDg&C`q%b9V{q^ARe( z^q1n%2CGGqU+oen#jCU&O77*KaGPm>Z;2eZE&il4nW9x_H?@Ur0vxKxnlM}a1HPO` z0Yoe*0Q`#=oNC>DX75+6sBA*$C%B^+|Mz2g%`@%7`UGzUY2CtzFyP&oDY;6LGR6HGk+n|$H zcSiDwbN7u6=?MY-$aF{81PuD+>JGXMd z#AGak<_*+FX{5!I<60t0??Tx7Nc1MPfqMjfYRL9N4hem8i!mc`vT^~{=c8KHWS zTjvciMFKr6KE&5~kc^@hO1_O16In}jbXoIq(upYw@;<|BP1!CyFyeO^hG+?ghcyq< z8JU@xfi1Ijsg;$L5cj!o8B~AZ%y+VnQQSGpaEgyt{nr+mQl=m&85_a-lVHFXw3Wzd zy*ol7Mh#u$vlnZ-s_ityL=IGVhwvv*D3a~flh6wPSg)SagQwj;$O^-$uJQH3eI}|BB4d4!hU%kYX3UI957Hnedvm#Gds~4Pn z74f(`(nc^M^i-s-CS3rm)|j2$&XvHqhypm(;!6Os(EmF3 zdKDHn<<6L{ezH)7CRy_eGDOmovrj)}MeyEji$wVI;TirRx^!P(pI?9ZP{J<#sC&(; zMV=30yYzk02avZVEIME-g5W~$ zgPcs_rpF69y_TvQ(41F$9F`r{IZaAsU&7-^>!$u7%;>)k9FuSviD)-c1ZrK)R58@8 z49cE7#a}Hi*gU0g0hCp2i#+-Iz}W9bP*EgHl{=YtMi)QcqO_ENjlB3{qwE+r&Z{1^ za04z_yN@#z7PbJ5nDZU!7-7*1Gy35+^4DtKT-KnE5RA`GwCQ9Q?irSWryE(aw*5(t zXnt$m9jHsY=pP42vo~zWO$Q}3{XND{i73ex6c#!SRdSl;)rR5;#k<=Id&&!P#i?W=$=43ddzRm*G?-g9afsZqN4Y>~-~8~Sv-keAyr zsE8ED=9u)V$?zy28r5K87RO)5Z{$;#Dj@DuS2bZ9_~sW;#K-5+&TQpn1w26ksnEoc zh`;Wl)o!+dP^?{~9)n%@xyK2LkMYeb$2*_$sdhMeJVzDjM~K9wvzpizk$- z(Xag*cUtT}tYbOa2(s&K4z5@-wV?mf@U6vYFu8!DHV3WD-HjYck@P<0HgmI%;JPR7 zTNpKbhoD}vCcIRw@VnB*Bx9o{OP551HPY=`DN7vxRqj**3 zMhI=byeaOIt-bIHE@DX{sL84;5v@OO|6Lzql&ocpl}i!-_ie2eQL@t~GJE>&Wwn)~ z7e;LJBo5vkq4Q>{b!W839uyQ;znMuCdH5ErFjGoT+V)4Eq*K191mNU2H7$+pBwcUy zgAcV(5m<9In;bXjgv1l-CX+@Ih>85+uf<0`s|BSuxLPm@TXMj(;(B?5YeLuUK!fhA zFJiNw6;cj+!2&u>fesYLxk}4iTdiG-<{6`{ZYk-Z0CgkltBDW9) z9^t^$qe8#Vg;kV2!(LrC>(EP;lqrYk>l&iu8_swK8RNA9@PeLSzh2`zvO&)gLS#hW zqi!>P=(`bySFCC?Yo|V!*Ts%V5w|5mCV9Q?WKKa*QB?E4L@GNWL-|}xQ_hdsY=B)e z4i1}7LDobm$W*(hRC^o402XfgJRr_a3w<1Zm$p|3G;rhb=JiJ!<;3i6D%GFph~2+DVVqUO~9*j;1VUwa#lx-ZP} z2ub>wB3#g8n)V%JcoDozMAa&Li!!vP7PvwNG9{<5eB)qggn7buK=-MvY(YXOV|q1F!MZ9W5YewO_8<)` z?5BPC&Q0}iuSrEYHAL;AI}ry-0xbBvPL)lD&Cg6JcIq#sX-goAnDk;5+H_lpFugh= zy7dl}9X!1xBXYSJ?kBoetvhl@&LU?!ar)#Gg_h}oi{#b2ZWdGh+aj$Dyy1ji_rO%} zBJQ*tzvhBf?4dtL39-S8?;lxKT=oIodIw|+PC#snh0vP}{drm?N!wTzukL={=+7n_ zex8oog*Nnc5j`>+TTLS8Fg=OXwq%Zl`Aq>LTz5QT{A~KDPA| zO3#A2&enbtj^?(@aCwP2&&UpW(yP6P9=WDgfb{!z(lx35*hp$+pgpO+gtsfu38PW_ zyLp1_PrXf&Vc}w^!Gk@aMLKMnzISCNriZ zv5+i#9J_8g%D9=(*C)F0Btnx`HQoLB_d(zwv}k@NFUrjTA0B_`7sj86zqmAu^Dl|8 z7)?lUGDZt)6-l?7a@OD{#d6w_G;(t6F6eNHzH5$txv&G(vb*ao*cc5DAQ5gWkrJcm;T&o zx3B{-o!9D+=qZ&I?oyYblMp{fCEu<;CT?c|^IJFh=NiRE>)RUXd*3bZqPGbwzx|Yf zex^9@bdco>1Fji39lVJNLrRDins;(v9>Lqd_vPGZgCD5Kkvx4xfL`IpFZDc$Ml*W_ z52z#c9fPDGzUW5ui{pjqV!>gNdfJgya9;&2yN@$Fi$2ehqsLKgP{~Wh>^kpPqtUiz zXhtJ*4GImC&>2e3x=40+8RMKM)8HXn{zVOwKH%AV*)k0BAgsZfyv z!oQ^k7WP1GYtRMUzN$T52z`B4ASmn7S34#m%y8da%5$-n!0HtW zpysuDa9w8w`elB|;vAt=xsW4v%m%HJn@pnm#MOSBB6b<%$q|jQSz<5OA6KRpFFz5g zPtEm00Cub~zoj|uwpJdj;p!hl@xfk$J|EivrRzidp<-8IwK;m%VG_H1V_z4&q(ufI z{tqtQ8bg{peAy~$q=~5D;qKnhH^l8kQgFZdUjWOH6eJHV6s2DeKIb+gE=Z$&>?$I5 z5|tGxTMhfVl)YXbydgl!;lC7U(XFZRLlD*)j?`8i;*P25yL|QVp?BDXwfb_mL%#25 zJyHjKXM?NJY{Hj&A5{(Zb!`-sSt)JBIZPeJeXZ4uT3fdmTVCAPa+3 z82U6l&d^|ra?Ys~N4gP*pPyHrzAZ3EB9yL}5fXLD91G!S4$*Vr)Mp}GEe1nwHfhn} zdEBOSmCq$W2Fdl>2Z{%M^|69ofh5dN zAl&x zUg>-27MD0#%o&7Y130{R3_l7Q{;bg0O}m@dUiXvN>uT^3`;AEf)677$SbI;~sF8<+ zNFBJzC9o-!H_D=A?S;jLmT=Q)c`H@CG3cdx0@E`NPN*($ltDi^{(E#sS8$LP>uUiU zv|gV+<$D+lMu2>)Z|jl8mn~s-usq6#2GY_`*S20djh;MS2)MTTY%l;7;G<_|Y|_kB z3z2JGn_0E`Vrpn2`dB2$Jrr9_waGpI@lHxns?RE$!9c4^!ocdCK%}*tqdO`ROv&BJ0`< z$(=cxUrW=yJ;va4*pm?siHp=yMuB|nQp!Rvf91wi93mgElBK^UOvuDo3{6v)4Qp+q zdb^I@GRX7PZNZt3Kf8ie?4-Y@WmPEi8Xw%%^s26em6nwbFZAWNL`cB)xncpw1>2`w zM9f^#P~`7^C&NcjyphYrRvkhy43Up@GuVp?l5$I*gH)--!pZ zt9Z`S{iVdY3vSt6)$&_#Pp~b6RXI2?v~k_f5}ZVC*}OZi_r4$oGd z%4imsvp5l7TF25&E> zlGCq(9$<2(rpTYH1*}!G7aaWoKx~`7`7?s&$ai^8uwGZQh-PX3&^F4knxIw;X**l! z(T#`SdV8%j#A~jjibZrySYVgtP)t-*6rohTuxqHHUIvH5rWL#Ck1vt!c}_4nqWQj0 zyFRP1(MEmW)7XqGeI-7~ZDGi}8p0LD9(%(#oQDnU39IJ&qOktJM}CK00Q0^+&P477 zev~PWNx$Qp`Vy51fwwO5+|nHjBl?D5QE^@s`-7))^%M@REd7-$vZe29W>Qk+LibL! zfy)k9!Q%xDo?=?%G~(Bqi^go*8HwxEbtV$U|ITj^Nx-$tH|;G{C|jYd5m#tL5Iu6G zq55&5smt1jYLJtd&xT%m2+GUZlqeCW_4C5qvhxvnlx$2XUFRUOHuyO`CUWatRmZ?$vSz;Rfee-zL{q zp}essXRcADW#p)A;sX>1DjV;FUKsn$aA>c-e7!yT%?HQT8I8mb~g-8?YM+<81v9 z+vSL4eX4JEFn(lXTBR3S+TopnmXu1rhlYnXQ+-IC!)Nd}F7Zj2HZ{Jht?h1hyzj|{ z#Qi(LiK$zoz25%k()rt(d7KCdlq-FVN-~%4I7Sk}_Qv6&#m(5F5o{i*$NuXuM0&1% zQ25Ngx6U2Gie(p>1bwgSsiH|UzQ4n2qxXUVJ9W&uXV|E&VtS3KlzjmZ(`stDft zQ*1mEw}p|#CfCSZJa9|msmd;6OzW3CzARAf zcEO`6vI+OV5)n#e3cJd^Qx>?m?b~^T;a%yl-AJR=sef<3zUZe4d>uCZNh5a%E3uB_ zDB33xJb70!WZKUXV*W7EN@Nd_rf!IUc#t44ZX8U}BoubxDaOr2J2R5Y`VpdNS%mfP z!IqJFNMj#%m^d+4Nl?LT20J^Wkao7q?ei)fd3SS--f9cYXqylp^ugjgx@;<{&Q~y+ zB2NYHUxx?H@1|{0?fahU6Cm*hr)15=18Vd~M4da1Z78!)t^Mf@3hp^Q`}J$s`3yX+ zf`Yc_0jiuO=It)*$~|fvj2HZe-*xNa1~CsO7PvMZhGV%8Mq&sJ6Ma*wgM-lOHy5Q4 z7JY%S=(UpG@y1nL#@66|A6GXw;KuDluug_Ny3t&kQOHlAp)$Od12Lmrfq&ZImSppP z9DvK90Gm$SB-Zfj=u6$0{ywH|p2ABTFk!{vQV@Df4&HBM7Mk z*K+t_K9gp5f8)@~vex1V6{wNiL^aZhK_5&)S3}cd{Y4#iNzOkDjC|YZ6E?f~7oA!A zC?AsQw@f@*bUa6kPAA&kANdQ_J+-8zjy=(wiCDgJ>l{$KCHLlcJX+fC*>puP=njE2-UsY95myAeiKN6QPavl6R<#OI{^$iM@1s9a; zN6IE&>*KQ(tC*oMex6n*4o3A(v}IsD=r<1^f@+e2SeB@uzOm+wrfy;OZ!7=BMiHIX zYrP4tSbVip#XZVytCEs)Q|x^;=|D&Haeq*QJhAKEv^I6=(FHv4vU_F}UnUgY{-Wcc ztV+~(J2teADku3V6%7@B_29um-+pWC06M5D8~WkO;)_(B`vz(Q1s4xI+#nB@CN*SS zf3dl^IspR0gz-~z4TP0M$I!Nm=;umW8A*;rw`$F}JCDiRjdVci^)KVC1-!k&FU%g@r8#*Ar76;XlB!vgZl~+t8_Ju`mhMVj;QHDKJkf`9 zd9ddJF5x#z=x^@%z|jD1S>N^q+^yeYYx+P_gFboe=BCk&(kqw!L6a^`rs^61YI=LQ z<{|^MY!d%tYENLD)chZjVOlpg$)Ch}(kc_T-szpdu~+L8R{o&IW?AVB<>^eeDemry z1TF9g*(c}52}U3~SCBiV!go?^KxIncaQ!~K>iIE{00j6Pxeazv$bl2n`DBl2-S3^K zqn!=Ld;EwjTQOWJDN2Sq-Bh1cvDY{a>QXkD0$=I z@6i8G=6Bfz&O2y4a$%3#c@A+dE zZ_Sdk0CM{?cytj47@ssB8!|ng)Z%W(>%~f;-xmrSj3|ceKox&vV{1%$*WyX+>nFar zZd&bnGVc)m1tHGq`_ODv3#WcU;BLAZ(f@?9`-em%6C%em(ug|Lk@p1Xy#x(HlEPN{ znZ7j>cIF2*E0rkl8%T4%w}k1K+k^&^je{}BK)7ImFMq{fKHQ^FxNTx*8rw?LX5K!& zAZyDgZ|+D4UhuM+J_f;iLiQN`$lS|vV1kxFy6g|}3O z?B+)0=_xjuyIlvj91$|GNC#|y8lUJmTG(Yn-5Gi4r*_gp-qOc{_Sc=AW~w`KI*~(+ z{;iC7=&s(@yYAVgGHLfOww#z6TGUNhVB_B4;h}Y0$9~vT@)**Sgg_oTjZm_jGpe&xvJTrl>~I+OifXLE}t-Ta*D%3 zgs7=`5N8!hW`ZQG_EKsn0sU;4cLcjd)zZFfgag{--W1|s^hgrsagRWiHEKpZ+6Kek z{ehbLv|T*h1n1d&Spn5?(#vl|QP~@Lz8%WSMmh*aQS~@)?Ccon2o)`sqhBTViVhRM z$p@w1B@=UZq>!i}*`JE^V)z;F|6;>f`~$}c0`xy1`@V=9yeCHa^t{Jqh;sAll{Qwd zdlsJTT3S%6rq|!!M~j1HClGXrug}UM=qD24kxle<#U_j=^4H%Od#2$t;E8ra7st$} z_kE;j()Eq^jSl8O_+*I{UER3Mn~JVn=vQ$`BAWqlZbwIe6DI#h`(h#5IA=XV%I)BThh4qN$p~ZSR4Wa`+b{y-Rka@#B_E-Bz}d ztcOC2Y)+yTZ9ZscNAjMSeH!YvMdg8u(E`!e6pN!)_+CsxKBLI{XJ*9ZJ%xY7$H@?( zsmOYrkvkPL{F#;^>7Wa>5fURqoBsM16-VjXT|BbP>@c2ZuJEeCM41F=$Bd++J$ie7 z6_N(~k7fG{mswTBtZf;x5|hv{g>dsM@3)l~pC0R?f$pg)(BA^Q@4YroB|xWvCTL%# ziY{PAEf3_j9>7p-E>sv3Qu&|iV${6tIOSP}P##*X|JsUxZ%&REwu?>nr39Z|>o1{3 zoF?WOW9EcXrw>WE5LxJ#KQ^nU1Y_%{y#@WN?Kou>yl#j26bRUJC$dQgwy#xdh*oSt zT2@zks$gjp(=Ri6Mvs=9Fe*Sg*khqLc;@_D`$HuQvF>y2?S>7Cq095=;1tic zYnQq^S)jlPcvYkDT-W8 znnB^FNZG|+d!4q^_k6@o_!8aPS1oK=N=2Tk$Tv06&1hGk3wD&6VLq@Qz@1+sa9D9A zf2s$r#ymNC<;X1D^~Pi}`rg<@k0@SQ z{Q2ma+5si{pM*@JjEtrFCnJ=k6IuIk9d&EsNG8#aYIQ~pno%K1#CHTW4Mb-P8o#A1 zzowB&I^LB8$<$5c6n%7B>=VCPlN z?wk$?NHMG7+wxt!ez}f@s;N;|php_>I%&o07O3mM@(vTLVn;NII*0xq>`^9GvA^*A zv<@+m#~9vZ?Q!=gX@$ELnhP=?UJ=8aYB@RHb9Hs4qhR@(W?fzJ_^{Az!IQZ#8Vab* zP0diXY<}8_##7o|b>E7_-k8QV7iB8C$$&V3q=%np5Z zg}AH;3;mI3mVws_iYWaVL7Y1U;dxcVxsUd`Z(*%eM>A{+tI(oL9px0DS=A`zxw{QL zCfj3_vplEwe*SO|OGVHMf1btXw4gRvsL|GcBqVCL!4&`~wN?6VuE7rqUYKw?(2jq{ z395!XdtbgJPH$mFpcfuKQ!Z1gLXYnVqFwRY-B1GD8wF8?#qm~cdGuWARA#i}abO#u zt~}9?vl`lCl{0UY22WK6p!QQ=?cXyFp|1vH=uurFLJA<@h1?xdL@#}i;nBW{^IZH@ z5zaG)P-st*37j~s_4ag>kM8{uePY%9k>{Tmclb*8p+eWvY{NVf7X4&_eBM^nqrwMi zsmWYsVvl=vWgas^BIbbW*Ivij0s*_g!M0|QKG zJm&%zTGsEgTdbc{r3BeQ7zg=cE^{OFquZhek+Jux9F0O-UOLrj?bpx*(iuEW)YGeD zUCTP*>yEjf-S@`sz9YRg1&0z_Uj3NHHd#@nCtZ8@-L*Vun2HK@43zg2j^oco+}#HV z<-2-z(rK&-e|L?G&<6@iZ30@*|KKKO%$CG`jT%5AjBr&+f%qEC8=ORDW@*l#7^L5X zeDMez2&DIy`X$Zm$fx$hQ6?_L`CYEL6NZ^zD7I4Q0i&>W9Wp~tBEIingkbfyVvOk^P{DUo5yr3*XX625RF1sI8=h5>|tEp30JC=tRuH$t^B1(s%2&I26w-0m> zG@qC?52P*U+hR>fuC$guJTG0!YglGE4%gmlK<$IkG@z5JvS)7r>H^dQjFFGJ8MW^* z&Kr%gb_TP1g-1GgeUqYHyq+}WfRTd59dvdRH6KX4yfoVwr=|#}JsOVAGnsJzKv30p z#RJ-R9sRN330;+miKS-3Uh`M!xJ3|so-k@M^uDDYyWLOax3%d9}SbYR?cp|7_$@6z~dIZ}}v>?{hP zLpC=L(ok}wDlPpeIXZ;W^WKlA=3i+0Q)|G}_R|Q}GROXiS%yV!(F!T^#N27@8nuzA zTwDc381nAHKOwGzz?rSv@BNTyQ8qEs=y2-)gScszrR9*9WN)efUEUufSU*-Z4QS}7 zFNKz+TJ#h)IiI^w_Pg@>-(09#F};p`brXG&vVjEpm9+cb(uR2tO%CI)7FSZGtEBO> z{FAv0cPw025sBvz=l&TCiYI7c71>PvPeLv{td3%?T9 zbiJ#_aku@UxI{-AEJorl2+Vy1VJqEHs63sxl2oZF*0r*2atS2C@{bdQUw`ZV^F+Y~ z+Gy9_WH$*OMD0COxem@6&KfEQt@~nJO|6}ekRKEsr5#Hv+*|PNcmFXtzb4P(KmYmZ z+p|5_hoqBuOMjg(tZGnaQWPWW$fbkcSuNy*=y`1pWo2F?n<`-C9IdK*XZNAi*6a?5 zOVAuN7;s6gwkNYDEBaE5aJLXW6sgHtyxUXyAE$m!Q?sGJ$v?BF64ooZNfc)# zezn|Ykc7h~!Asm1cIMAh*6n`R*$syqfA|pT!Nm)A1#hv*zjdbDSdHQ)+4Nj_T1>3* z%gbxt^}c^p6=o7%$UZ|V^|GI_}3d5nf8s+1X z7gK*x|MDIStpGRHqr?FS;@*-UHcL>}oY^njbm->TbpHH3gQQ;P`LnhB+(&zaetJNX z-+o_VI8jS;rfS^y%YzRC)B81tw3T1XhLSo*y zv8MgD`V!fe=5lXg(w)MmaNt;Bk&ecZfv06p%L>ZQv72&zzD)Ch1J8_3GCOi~E}k0@eDQ%_fEOzvvFFWCUQK!u#Vqao`MRqqUtVHA==srLC?BTb zXeXG4nD8Vk%IhXWIITaZ0QIz;`~~H6f?wyv+^QP4x#!c>sVDnj(wI_Oc8+|Byxffk z2L)egd=z((eX80D=I`bazi3%onXW4-4%&LNVnZn&@pLWu;m}Hr%HHa-zV6*$zSS5b?a7J>%~9X;t~2Dm56v((L03nZ zljfb1NkOkujHYq|_aUAUZqW6~kQ1Na2hEDCr4!ziOIcDLaoxz5*_r>?uGO$zY3sAR zdOOq-*3845N9V}SF#J2Kx7t#Q z{-#+~SB1i`ohh(FG2XFV@RpodRWqNV_N|+I+(R2m$n1MLLSxjp^}fHGc;LWGnX%G{ z@>zrOu7AvXZezZmS3`;WMDj+Pay~8FMATF*tLlL83^d<=VEx6KW99Uy2aE+uaBi?{ z8CK@Sv0pgVVx4YV2bp5zX9cXP0J`Ro=FCB5+z%DZP}3_}m%9Mud0r9iGqhiGM*IIg zjR=1C>iaGy#H&qHB)B*;N8`LdZm97W*q2I~*_zm@mXr3YHW$Wmoci~r2OJz-`a^gh zyCpattjMZzbp_y%Xy6dnb9kw=U<%=ki`hMkw;%2CfA-UTpOv;(cs;z}+2n&;%F}=B zJUV%!klprOR$nN?JpD@gPUir@WAt}a`#HujhEqQ8M~7h#uMJT?*%^jj_1ulZESggp z`e&LNH*2ioy7kR7wV6T8$tAs`Lh3VTK$ZKa?CrI=v`tT7#gi2+!n5!w4{mARDC*qp zgmz;x77U_E7wvY2!WPLdlnD2{_%rz&*_CWTe%XDf%kvCAldR6NvMD@yiO;ykw=}?* z^wC!S@P^_|sS(!ZM(c)x?eXZLU`0hOl`hObR;x@#9g`a#ba50ixJ6BWKSH$d9|&bc zr%RgXnTki;!krqy0GrAz$)RK-n8x~CAOEJG$l2K_F`DOFe%rB6)es3)jTS2yuAOz8 zQCEi-VhRSaU(^4HYr+1V<;*CqtL-7*gi6t=EH6!Kyr+=hI~MCDD_e^T55puXl)Vgw zM$&ofh4r{uY0KnN`J+7xHND*T|0w$9D z4beWj#tEiUR{U0BO*#2Q+GFp#M8>Z_`nd0JC^7X;NYGUm)|9~$Rr&LtM~x>R9g`nk z9+))24=sJ3sD~t_wb+Z>5pM;m%D$!lDfc}244g(J^W zo{px8nTVR{svg4AEP{6hkJZI;AEp+E!>ap%^nK3%kMu^K9}gqaSLVEO>Mw3?PTDH0 zO0^5yR^!CaMZ{m|KLYZ=PnR?&Z4>6ifr}RGm#nya-V=x6$(UnywUq@y0qrZ-cx&N2 z%HMx#rb~?q2RlSLO*R5NMw z9$~Tf{|a`=RL0q zSs5&Sz;mX;7~zA|LYhLx`fM-r1Z~ykPKy+${(PEW?D8+6moK7&VxRw1^@$y1Yw7l2 zKlr$pLQRr)OaiCxqY$R_@8!ENj(=^AB z%WQ`5CO=*8$+~Q3Ds_h9c_CK5z@wprL^I6lr191XGuudNEH^>6_-A1IDqwp_7v`0J zK)UD0v*&LC-NQQpnso;SJk>FyRB5CmzWIA0A|m@dlUTp9UI~;tnP2KS5zr@CINp{_*79FGo-!shdxKkh7ur$*k)l76K!tZ z2)itU?{XvvwJA@nX-in32Oi)Tyy5;ONAeMJAo(&Gbbz(DWweiC=Ac=o3*J~pFkkz7 zqN;4uq0aw+?GHp6Cq6-Ji_kdsWY3R>hDyeb*LWb?I^}nY9m8rc*jP6tY*g%@Oqx@2 z0i`yu`g47X>Xa3S6RjyFWoO8d!Uoo?DN^l6AKlj&3|5K;OH2^!72e~b-qo!)}_=2WGa`9rVL6X&1IAyDM($=K!i*>%O zC?TY}?9-&ct&%!D;NefQqU9jGd6H~T9ui(`)BuHcCEeJ1?kBIz&3q^W@Vw9#J}EGQEp#M@7>D_#W!5WC*QJB(oSWG%q^)c6 z%7Tqa^YLa6uUpfM0X7UwhI|kPX$=!Q^5@xLH6EJo9bRTnhI|r+4f>Pk^5@r~r?{nT zu40I%OPbcHHsOa;1XRg6=w+Q?3yug8IZFPudjaX^!;RO=AA~)|sJ7a@)8A#D*idXn zi?C*w^(;OV_6nm~uDCGW^{#fi;`k&((oEcJf^pYC<<B5NeskTb!hGrLUgbk(s@Gp!iMxXT~Ag&z{H`|5zTv$*own>s+zC*EM8I!jCK7+;T^@S!u4%X8Q~QRmHGNDt($e+qEMW}uAQD6IL`yan`=t`C+T2(8kb zt~hLGSD9ut!ScsT3;Og(`;M>#jtv}*3ySvke0j43t}%Lh zFn|88umCnz>oqo;iy7>fMFRLhYz+INoU{7Yr7N28%mB9DzNL}~-Oj{mRNCrv(e*;s zLMeOM$9QON|f~ugrYonVENwM}oTC~W z^&HoAoHwjahD&X2j2NKtg%}p!KzF;?*7z^z!hTRIr|gLF(Af9oZpaH%f+%NpONaPs!^3bFADpS_q@{2fqu56Bhj@SX4$A8cP^i zZ)Syq&v%EVYFW!y02%H|jg`44mNu;`%=#BN-YnB9-DCYTk0OGbRAuf-R|><62Jd8} z#dJ``XOO;VOzkssF(+kuowd~wCgAs?CZwm}_F5;Xs5URIbXq@NnKu@8ts;D@ zQ*=f}&%UTUUSHBReLsI5>g&F!od`s~bf8~1PQQsFMh_T~|F18Sf$LifB6t*rrNeNe zYy-R~K7}|cX{LKncv*(Lw3Kb5<9puO0bewEp4$6Q`Xv;%C513jj514bzUZ4uR{@71 z&cu8zG&}hFPman$cGYPqtAVOCBl^Ldi1O0>*p&NXp0QS6yfBNebr~H=OQ&y?#GS3J z^vZsapsOO}EYvjjyf>&i-<~Sir+_%tBnxB7FaT};Jqq0LN}DiE%Jw=vP{bVpo@i2@ zCi5KGgDKKaMTLP;&H%8w_gp2cN!F>~c0R9XQAHU2=6u?wrb*H{@bXs_Cb8eY)ilA} z_RPRzZm+P^_Px8qTEkdo$fwWO)y9NH`2;jAGBndy)2{tvEZc2^9no5Qj`iFA8Bso= zni3^lEX)=+CtRGF4QiAdD?@0rZVq41^13W-YI{KGivv?*)_%W#H4HFtjA0F~hu%L1 zU~FS_P($2rl$4*62;fwl_g`)l#*%W0w?ao6uBBKgA-%+a^?hsq(&LqrYXz|W`Q2Yx^o75-c4_E>L1|T=vgcg))7U` z#}(%kuclZW#)nE=Omb_HEi;{#70qZhVcxp3lzrW$o#$PG^?R5Z;ZfAPeJ_S>WR7AD#LuIj?&be-qvf|AAo_rao}iL;)hwJMiIb*i)>htUrav zQYHUP4lOj3vb8;=+I&7gqMW0UF=#Vo&i^Qk8oEYv4Jzx3A73wr+YO3qlUkZ(*9wIV zE)FCf(p(8?Pwn1?@BB3+hU>GcvNeK*;h+1ZKJck9@Ez1@Yb`^f;7w}jT_)~~VPRLI zMAhg5OS4M#Sik(Eh;p)>hGx^Kd{N0{+Ug}Q$Q@Q)! zF8K+o`VOiK^QNe?g%wWs)X%duOBEB+z8UG7YV6I`CJ;;Af1v3<;8|*CJ+)IU*A2A) z5?Qq`)DHBok~`M#c}^mLw{)7l3>I&UabK9I`<$y;`QQ-`#0K5+fQU{XbA}eF$`@G~ z76K~ajQN_BZNqxTF9uZWOZp~W=Fc;aU;RQ+v)3?X{o_|H{P8Il$C)zC%uV&o#cv5I ziHz&dY4dScts~PEKy(8wYX|JaO}x_17_kDbBEZTf52-|CJu0 z=t21X53q?hV4wu2{)E|`j5NmYj3(MXVUQ5jvCIer>O`8&NJ+Xt5@SGO#xpipCoiZ~ zh+Asrf-V}9W}k!9ehaUfugjko7smQgJ;#k^jX;O)K;+4|jp*?2);g1a1RUjJ;>?q> zs!XL!DXte{e}o-lhNJ}<6neAsg<(PilbOzD6~a)PVW`6Wi%avuywl*E$^E^DAF20^ zUNlo^m2-G^uZw3^G(gJ%CBO5J;_4{4I(f117x z+%@lINy+0LC!4U@LYgN0>i`RODJwk1w7&(SMOL8IDq2j!tfg$Uy@Wz1_Osem8c(_9 zKllX1D^G6@Q`TIe3weJv%Aa2LEY=-0&UlLOhFSZvfnTehNkEp4aF9OdVQ3DQ2hp1| zC?b>rH!(WcoT!*8Hme6!Ie$?A45G zb+wiTQ9gBx7s+nzYu;=Srt?kaT?6eGoRsUD5z5mRFy$~;AvkNlfln1o8HY$_0LJHO z6XpRKI{Sr?U(R+f5+#Fs4fOK_Oj!Y@OsVAFbju8?7v`RKmrok6d~kwEJ4pmoNT&~y zY)u9#%0h(HtbMc8X-W~8Lu+HPyGP`PZ*w>Xiofnm*Ao*$``|7dWUp7#xW4vrgr>CJ z0nM<$e@xlqq%DW| zhtyN(%HVSXnQ*$&#Ssdf(-m#Uo#8vvTFF^Dw(x#)Y#uD0?GK#TMvYY!HmZx z8A;94Mp8mE-t3mF_X#+;c^5pNSYXt}+8xp}B}oefdAiT3J#Eg;B1sK(>~m~}g^`k; z4qAcmxe61RshyUl_h|u$90f!Uel{7>D=d898YLuzDINGa4#nidnki|uPB9tq2EAM=p`*S&Y#%_uv2dOW{sF7a{Dy2kw<{7|x-| z4J$;cq;HI~yJ)m6_6whOpVS@d5DU?}QL^TkM8Ir?y}#F~Gyi*u$W718G;s^I=*}xS zJR}qL0aMlq#!|-G4}<1~{FoZMvM}<0$zJ3>RvZkb34P=FO;3VIbj~l z{Xe$8Jg%uT>p#{}rbC74+bV9rsBec_5pV%22z2UL>o^gWY(OBP6p>woKn(_B9mNVl zTNNk~aH#?A4M`M%ums`;wrtvX*#lM-%0)oM3mOdk&W(^{-p?<8!u>q=dCq>m=RD^M z9jWgpu7U^HiHIj#pakFhwW$SjwPxpWG#9Gkh>KTaJ3h#YwQAo%o?qzY4gErayFl{v?hdOb}VzK7S~TI@Ln{Sn3p zPTFXVY(jRSm(jFTagdBvA8^EqnXrxvPo>%~p>DDM z#Z3cOg@<_uAQc1NQ_U;w;m$VP=Y{k!jsis!$O7C%&}sJaKB%b@g~y4@-=f_d@!?UbZ_297Yly? z0S>uc;WO3dli!(~U-e1SS3U(}XFrV1{&C~z3$3p6^~>-mlYp2-kDz+gB7^TW-Eh&H zyAj9&zXK}H!$c-hFFY4FgZ~G}J3$tTc zTbZ;%h845oZjZBpXOY>*0N>q)CeaGtquwfq%y4HpLjEHP9?$@R6&J?faz}uC&n2N>$2!6RXJj!b|f& z?S7fBduaa@<_ciXNANABQ*}t&LSv#N40(x`j@LO86Rde;$SOZGA@dB-Ncf)-113Vr zJ=|00cCqS06`cp!+|#A7vnQwUy4L^0l#RfY>DCWpnWx}&aR(sB{~k9()h22!yp!0D z&&S$Hn+9sV15B2O71J?SQ?S8AQ|!}H+}$}jt4Du@5&5|pQqN9W{d6tvW8xK6H(@Uf z9-SA->xCVC3OgE#!x~WmnzF?tqp`o1f<0(R0{gJ;myM&i8`uYEZfN|kl%4zg%KIr^kw|^#jvH|s>9u{x|I!`emL0hg~7H7kH3hc>Z&I}eSbR}dwAmMo9uHJ zzn)uzqKKZN)wxxBC6$`_CjY9asdB4!E9aZS2l77yL{;rx&Bq?vzb8&_q+?^Pqpd~% z#n{6Y3|)1nZVT08D?<-kHXYNU-b`22hNf#aQ4^;+n~n#i)fXTe>FoG4egCp-C}?%P z=JEWEEsL)LS9ny<v^@iMBeY9?>BL*I>!4l@;(5pVhwBeAH(wCZ@my<7{OV7Lo52;MWF zRsUorA({A5T(D?ZF}Ed(6EW0=dQsoco=FQ~OMgjpc?GJLV4`YE2mZj%z&vnwT4?6! zTQyhH8=bdQ^Fzu~2-MX0eOcG_pfD-ql!86KQNb3uGYjc`MbUsdni%A#HEUp>?XqOK zn*a{?BfPNL46O^}&wo8=y4Ig(YTOijA6U!0Q4u-NfSiH8sRkMZ*v44Iec&P91O0aS zRDs(o8=!VmHInF}{9$X9i3RA`3C^)K@9b&^kvzSTcla}P&4d-=&&)Q354%S%076ei z%@}qN!EY^v2e(}0a{#%_HUW`@dC$CaPbtQBy`MeIU6vj2{2{&wdyPe*jV9rmFsd{K z0>g296~-Zn1#3ya3Fi4h;%aVszy$swpZ6!Pi>Fsc{<_hj8vg;~&he zA5sy1qAIs2cK@n)n!#k)#)Rv!niN){Ud{)jE;#k?rdtsd3+Yp z1-lyAS0CcYg2w_FB)LSs#uh7Xt$;k;&nFuLucbFKKD`iTJvo2{BTQ3#s~x|OtwUQG z4GbtP-ns;m$57Pp2cAw3zpgY8it9O|eh`Ni1F+B-NIue8=Z3n8wL`e0X#g_zyaQj1 zRbnH0N-bkKg`n&oOgaPx+Gmuf(36c>Kx{@=o%WwR6pzKAB4Cg?A*%Z~aSaUIh);b; z8p|NFN|Hkpqkyxsr+}nld=I&bCf6W?XaIjP7MW3=T zN;6DTuPAPSAyOi3l>Exs*iMI(y?MR@4)Mwadt^~FWALka0m}G;OR^qRxj1w`M({_V zumOlw)3mEWx;oySqNmWU86HYE!PiT(vK<_{ha4e%_h;69YT}L2CsrbDMCfLslk2Y! zfv+Xe2!v!mvde@6+yyIX(ES$RQ5Ckzs{A+{vKvL~V))yVOhZ@6i9#2m&GX)a;A2t2 z%d+#H32`cBg?2Mc({$lS3CEzhrXKT(#$`9k!F=!j^rBnk(CHD52_~#MG-X8_OeGLQkv+Z#2;$dAbGaN!Q4$5{Zn9VXSw-=&l_r>g{>Ca5BauOyF+Ob&K=6 z{K3zq!diBk61OtI?z(Yv6SLQ$GO09f=QBt_Spl5@{P%WzDp?KvO@{)Bo8p2Sp(fRF z)mMCmkL5_pm6MHs%Zgn#R1X!^RhT!;&P1tI@4S94?(J;P1w*rPMJgQ(%@4IKkI;k} zjS1?=`n1l~z2MAhhxxGS>&HZGu`&BRF7Zc9LLB>VQ=N%ssLh_5B7k#Y@IuH0D)vwI zNnhSFC~Zd*pJ?jTRRH6hLf8X0%+WZcA1PR2tg{PWPn1#xrE!QA1S2IstqR$_wN^V0 z$?Fti>IS(M)~q~V%_aWlgjI0A5Y}pFMAW&xf14RH$-J5hQ@S2(axdY(erUf3wq8p{ z*l_1Vgrg>c;3f7QvGBb_$&o5O?f)(5fftDR~+-x$57`T$YX#9+B=P5b_KChW-v z_7s{*3uU@WZT9I1lL++b1v6$0*>>VDh>BdP*iSylL8lv^PFJ5BHMHNU!dbF>=js^- zY-Mq>`!0xsK{a5?Y!IQE>H5}>m*3q49L`DuB`ysg)?DE&yh07K&+Ay$!BumnL8R$FRQK>oS|vUO60LM;7?=r z=)UoZnSE|8hC<1*Z>h=7MxL*rdR%1|o^1RUsJ^-~iuZ-jcW=y)OjE5{Qnd}jq;4!0 z>jaug!Q`#pQpo=Y)G{O{eD3h8>waK4b?d`eUr<2b&8e=+Uu^s|h5RWK#fdzg>r;)Oh5- zW>P)mmb4VlPtBkHKt@p9R!ucOi6|3?y=XNl)u~1mJK?U!m%IY8=Tdt>+U&1=fVOW| zzCV*~QfTg=&Ri*;A-aGTFgeIKzJXy|JW&GZ!8ay}K*EhlGN?yJX?e~r7Ehrk)4r+{ z9b^t;DW)bOVEnwew|yLTe*=_y*-AgK1PcB^qTVE%?r55i))hM-(aeCYKvuw*?T@9Q zedjd=0VD4A?-~dz>Z~Xpr2C8wKMLB(O@m<4K9DB34(y#VC5AH$oK)cH`0-<Pe1Lq9+qovGO=AtooNy>$`u#oou}i?o6AXTYg{_p+76ZZ-d<|F5F!df^_-s_pEo zlOgP^W^2@v-X~4IpnYYcYqScPC%c56C~aA~!mK=le69hRp^q>pYY z2Nr$)tHK`W9!hUu)|s@*sSy6Zc!grm_HoeKsH-HX99fV`mqh`ZrF&`!zPX_*0|n3@ zb*Z>@!LEjl4vkNFn162X-I-w%oka({T#1U_A6Gl<0$bDz|pS3Iz z*)7uWDp&v<`6k$w1nwE}_eeAzUr2x6BkpZ82Y2!dNW$V-Nr)tVHIam@?qH-&Qp*x> zc+cVd&2av|I7p{;Pp6;a7h?AsBq6KoD4F8VY}n3b2sb*Qk7j6v%m&&FKhkhXNO*;a zLHu2avgvOkKkQgWf;p;|%1|N${|#D=D@}Zg{kdQy(^*HnNTQSO*8LVN`1of$1S618 zlaiJ^GGFtcmG65>A^B^gLXryxT-5t)OmqFLp^-+_3omHFS76r2czylRJP5<+C{Zo%ligeO|KXngr_VdEk)4*9wpjaCUL`(E+5p2R)A!n4l7oeC1k;- z@ZYfr=v=NS$TJY)Y5pzLyQyUdO=g|DaA8MiJZ0(QGR$A(Nl6Yip~Z1xnCc;^07H2S ze99-)KD@4{)7!9A6Xc7vxsM~@Ce%d`KOFVt0-`0kmPvCg0y*Q9n3z+!Qyb!S0Ou(G z2BgmkVKX#~&`hRlNpiWi)O7bZKM-jd1+q~VsnYL3?nIx&@OB6>P>W?bhWwEG{>pv2 z>8I1_Ajh%8U5b0D$P}%C*)H1+iZpjfu2dJz>%)K6+q_$3*Cu8BcL!{0|D>(NbE|^^ zuUGYGr}6ovQyN6wu>I(3?ozA27c$8cOkeOb0aduu5@!OPh{FBzJU1_Ddbd$=uxuNe z3B8{AG-bRo-4wol@gH#@^vSr>tiXiE-Q4KF9(jZ>E1eo+rlzMEvM48}MR7jBL4Kzc z&u0AF>yMAUxQKf~(n}y8m>B#V^HyZkKacGpi2N|eQ7?{fDtAE_L^o#1ur;ZNb0zNd zLO^&dnPihiT)mUS5t$nGDw@d4i!cel(@Po33+NP0|Il67TPYlZ>011M4zVHs-h%?T zASxgg$|=>p|E6=bLR;3Un?$#nsP9tfHR}X0l={Av^giS@Y%p_PzSd0e=s`x4DYPae zcr8|q6hf_FJOUaT-^qzdEbiq9s;m5Y+PnJ8*m}0PKL!RNWq7JO@7suwrB*J9e4-}2 z4^MgGYyfTS>ja2bBu@2k+|KSe8^TsZyTSzlNOQ`HjVdLVBv%&OL0vxsYj3DO`dXZB zUOliELID{qvB+ViiIn)S^cB>&N%&we@p>`&Gqy$@c_8$WO2>B}3TCb2K( zU+-HbKyJ{%t0?x|XU)=mO-z``Z}|fyT0zgIL+rj~^CJ5{H!9@Ap1wLp84%r#YeJFr zai0;XR9#WuZdk7qDEBnW2W_Aw9?*_to-tv6$N#hz+4u`;APn#afB#7Dn3l9EMdA*} znB1?tLQMou87IOoQ->9k#k*JLE?klx#O@H-F&s-QB>Aq!@{}qtDcQ#szq0nx&FIo} z^Q3`#YTQ%T@Z9|=PfT1>(&G&n2d;aL?t%U*3w!8PkcZZ?bW`9_n5pj>;|#f7^t}fu zh&BS%BNXxFqry)Hb!y{Norx29`Pr)-o}S}Hu{$Ehr1>n(RS5eb zCZE%Pib#77w?f8|9ZV7tON(kww~PGFjGh8be%72kcVqmla)#o@7rcYSL;N&JTv1OyYlbdfovNSBOukrE^Gzgi3UYe| z{_Vvu?oL+y@shZI5D{R?4}9{(p!`k8plO=wOq~BPHYiOJ(SI|`t)$AEcLZ+gEixe+ z4Py8trX79eNaTks2fgyYs0{X5n>d39Qf&hbC%p{in;`x_ zj?FV!Z{1U&*hcL~NZ_@NCGgtvPp|N4>VJ*WvF}W51d4kxGQ&Rk-Seupt>aVq5wc1v zd=k>7pQuN^bm>qA76uh>B?7W~^vTytt;$<>Vlyx$rPsisgqwaoF+)NeibBT$Sg;Z&#;w#!_vg4JYh=vY)!<+f;^E zfbI!+_{S5=tSJl?hmoRrd1Io-7=#KJKkE_K{oFm^5#!^%bZm&3X^iv>QzyRB)iAeV8|hH^F;(OX0nM ze6GO!h&tq$338LAxCF|5m^r_J3*AK?GMfFj=lx@@$BioOW#E=>wY_Z8U! zCHK=a3X31a1sET8q%mQ=Ic|gff>j{UerTJ!knw#GdujhMYTaol9ya`!jF|r+6i?sx zg!ijc#YY#DZ@*Q8@@c#VvE{7_6PIqEGsE_{1{XVDiFs^ps@}n(&Hvmw$1}OzC|fD> zla2Z+q1%!ba3VHHGD&MiCn9pw`VvjJG4E;j1J^DYKlzlxi3G>qOFTpT%hs@T7TMP(Z)sK)pL^2f;9>WVgvBq?}E4`#i3=)$zaKb>7?j8J*NYO5e~b zY#7~zx-&yf=+X3DH#p#SnT?+kd>{81yx1|OC~%+kgUxnh&W3Q~srrFkRWbox7B|_x z?JtkPlw*7+xP2yEDl#U;3#ZQEYq8fuqf4RJg$2XiZ`EGh$t8{#^t?w;h)&7O_v=Hnxaur!h9 zo|*rZ8Sd?~7EEm>j$0u5_m=URJ#&)&zwdRKvci0z|16bGo;PwosYG1nR>e?-zeC9= zo6HsGUM#0ZGX?Un#gi9(?;NiTU23)D7KiB=trhCd{3}!cwI=e(tX5B|?!vKL?dPDp z6jFJEYD)S0ct;2f_b|~5|EEKkBYlu*3Gs67Lg*77a1Oe-wJ7YP$=`4(W@kd@^1CCb z8bCDLTsoVz6zF`l=Bn0Sv@&#>Z_GaSS!1Jpe?9YOCVts1I$dqNEQb{kw z1cp;oOm8E8hD1IHdji|3)|(B04hyx_@Qq!KXh1&~n0#7zC$1eo8(s$yR5xrVAv=i8 z`gaVP&m7_C!gwkZj@W#9fxpwo@{iXbTc7)ZGd>P!%Y8sFj$(Z#wW_D$P)4StMPj3V znSJf(f>JWale;Eg=eIvi9>X6S^Ch9?Supk3krk6ZfSXTz3-66AWYqbTFdd~9CXr{y zVl$>=7_NJ>8%TYRB=zq~lz$LR`m33SUcreWza7e2mvA-4GA zXUPh?tA7fbw<)f9Q(S(-)r_B3y?!<|&?L|m3`;XshzwSd{aq+?5)x~V8tD93bnvu& z(-&5hUB72wKVeyLC&9no*#F^mONzY<<54`2(uM&_``NVEpr}s^wP^T7zAx|cRl}GT z)f2^Db#3THEoa%Io4HP<^~at2(}5o`4Cr3o;$pB;uhu%2l%u<4Hp~DQ7c!}L3jBeatytd+BV02$_%*1lejxn0K6?r=4GXmuYUb+!Y-}P)=zVYYsd|qDnp2y zcgAKmZ)3B3(qn^04#yI0aKEGxId0saT)Lj-SRCDTefaS2i)ZTQkJm4?YRhkg$cH1F zD6;V~?y#+_ftlnya@a<5eQaLa_s2W`c!yiLl4Ttjg^V&FCNdnQEw7tlE0OwJ_go*G z*S6HP|7I7=(K!`9jMK3t+al)9!4-5~;`7vhoureLaV9A&*_HpN!{in@`Z^Fm%Ab~EBW!M;HA)+k}GDF)Uo4|mLz>sJ? zL;h76pq^311X$CoH<%pw{CsGrr(zl;4rF27jXMf?@_B#VFOMc2k>nsFLnO{#9Oc31 zQ-%9anh&24#Rds{i!w?kB)cUEU_$&1=NON<$lt|-9vrbCBfvuPL3I6b;>k6919qG` zb8F_Y3G@EGwK6O-E{LcG5>`Fh&t?@&6lX}`6AMc1A038UsyY6EA@2#OhC3zU2Nua6 z^`x5*t2cnbO`i7+uk4A*gS-40vaqh-YwST}viG+sx{25Inu)EM^Wt2MOZhIhxVgNq|x-jvOU)RBfd&y z*A>vw_nkoTj_yKJnH+SYXqp>oF!hpf@9^c1l1su8gMrxRVZ9l+sT)t|!n^TX!Epv5 z#9o)+>EUR2pIEs_-cq^D>Xml`E!9PL=^(3&Fj9s^FP8>(UzZ&YI-yWq`pMbhS@7;K z?d=w8M8s%VR5EuCA=Y0BveW=qrfflk8EJYgDXWZA)3xo=30d!Eqtu+}t_a&Pu7v%p z*%l$B-?^huc~5;wgr@b?U9ZAahO75WN328%o1I6(--|t3@Nhr-;R}1{MM$EL6^$|j zHX5(uEX<+Dprby@%C73F$OG@o@h0|Z^EdC=(JcWJ5!AWyOo*c>q{BUgq6As(CXnsZ zAQ0>y0+0%Zmn5O6WvNjK*6kPJ*Kt2gN_Bnw&Kp;U`UTRv-AD>jUSxqrI~mWyxJ{t# zE+hN%e_G{mZ~?j8PwP=NT1}V9Y)p=vr5Q%dz&4E11Gd6@oAnQ-i54pQ!nF(fV@=rN z>md!Z8!r)S(qX~l@LZ(S%r!tcqXBX~HBo@aaLbuf#v{JR`CTT`WP0$$BCFjIrWTKczIxa{j?{B;(lB7}P?VTncygZ{9OBBq;EF z`iQ?k+~scd+dSqmkm`s>(by2S1WU&5n~<**vIigHSa>yL3tLn*hc-DJq#mi%CX^+C zSs}3P+tTI~;Nh>Dsv+tn$sYWGjO(?;wsTIH5 zbjNQS4Ow$c_;u86AdBk6>K*a25R7Z1BkTm_2)4BtWM1F3uFo+4jAgAZ|*wMV@jm{ zx)7oZj^+0|`S$g+24_wB)F=R8mPR}AS=3%I5z?XkJxaa#hyZhY^vXmVq(XVQp4;Oe z8Mpe!Sgy-@&H?`LxR_iNtSEO&56a)6wmUT;%UHf_Zl|iM38-&VW4?h;b+>{eYK{eA zTiFt!eR)qxE%;ylHlo>B6qW3%+n2W|pioW!oYbhE?W!a|t~+ zp!1rWvdUjA%A1rqtXTFZ2^GXCt1$!{qZLGHj}QE0`qV-gm!mTY47zti}%5o*gt*2jUQ!O3M&YF`` zB|snkhSK^4jC;%_Gb27De%3w$o{{SjH>{y_xgIKUA3*#y{L9VA_<3?0-7dnNDx2RS zt~y}UqqwIF0R!x2$Rsw-*B7!9(_@dmUoPzp&|Go5<|eO-G`6qCgJ`>izijAPz4J`J&V=6kFaWEpjp0m= zojcvmIpANfg?PtBXH}JRoU?<|Jv-RAHL5{xAu*Qg4J@~L^A%AJIM)o*8tezF6_hUB z1;9(G8A-~htc&UT z>Bv?7m*AqfARCOdD8xtPS^4q;PMFBXaT}IWI@H|g4wt$_^Y@Ulf!=-%4WS;e&&!DU zj)imPOOEthbdwHUtFU9nEEmmpA_czdO$6}_?*TLHZ?fhBDJO5QMhf9v?Vq@GCwRB0 z1f7ICOc_NMQj^d|^`$ws0-R$hn#2%+w!X<(ba_4{`B$-+bKb%4jrOe3w7)lTHu<{U zmcMOdbI*}4_8akstNHshGX9{^9)6)n-8} z$Vl{$G)UOVBqctE3}!aZQyd{FRO$=B*$l43EU-zVp_BhlIkLM1nypzQ_g|g}5z-sL zH-`h!;f4gYj-@mSGuAy`Ku!9gm2UY9DC8ZGH*9nm&@C~Z2w=A%TaY)|^j-g;^TG8M zue&-{3fH-Sv=6A5xt>qrKP%fsOyFrogAC)W&~|z$bolH90!zKlAC^b*{a@7M)|d|2 z1-FGG9YBG{AO8pj(JD9jIe*kBNLhE|awq=Akyy0+)hcIAy+Tz}DetQ5qUi#OKaBig zZ>#h7sMSW7anzoF;zl<~VvlZl`2>Fx-WOhh-@a2YCqy}p-Lx3Veym@wzHDAOtj7-_ zdcqsUoV(GUh8RMK{xLwU&Rh?LMcGon`3IfXlgSrb;Lot5yVJ&yQKpV-Ji913O{{59 zqH1J{VFj9{$ORp0Douk1hwWY7h-WpD1?KobkkXpK?PWuLA?@F0oO146tfH(xanSNz7bi-;J zYy&kWwogX-0@ZJEpmhwvr6JzJ$&!ii)umW7J|C##}c!dZ}8;EF(v*5RInRrFqoUpKL zN^u3q&n>JzTzzk~FywkrJVmv!o91z$NP6B*qXyJr%t8yMRx_SJPW?2 zgvf8vu*~NKP=6hn#Y%iQXfMa(de_CFGrpllXOrV- zS(5J?;5QPG2XF)Ty?q%8V?si0EP-xfT{fvNQ=+J(C89mocYk z-nNFr%kei>U?;ygb<$(+hxz9U&V)F z+0SrYDh@3KN5T^5>BK1X<^8*C2k~6&{Y}7TBTYjz7oa{9%==t@Wj{#)*75Y%dCBD~ z;C2eMFBB)p9Bd4UzkFkPiJe6(Xbc>nyqmuXKfnsA_LTr}}N zobWF1rH|fu^BzfpbuUYB4Ym!5LdIN`$#ZJLE#dnSp_o{_YP12s2}v|il{xwbIx-*P z$~f>; zC^=;ii}0q6mUmrw6fd$Ygs{RIK~9@oUHjj$KmC?QD^PC`o!J!??26*uvS`MvHoXCV z!dhO6-xV`IJyI3+ebVp7B@5z9fss*2Frq_AA0e3s?A#SPjGq-Vx18TQ!j4*QkS3?A z5fs+nSA1bck*pFSN>OyZrrb?3RL`D$t?-BaRQD56qJ%|Bp(oAdUxQp`wH?PivF&M` z0&r}zk6jofE_>XXrcN!SWZi9AZpHmYmjoQWfq9@~%tB*))rJ2jyY)5-nO(T zQGJC!BV{icCUZypa!xx?SW|&}toH@5q9mRkhE1~iVFF8_MiTa)ss8};BShq)B08j7 zn-|5px-2p{n5?em+!Kn5u&QNH2qmQ~0s<`6uN_rxSB4h~2F&V+`yUgRc-Co_vXC=0@Lye&r;vs@kt$xfd<678sm78Xzchi!;OuPBM$) zX=&BZVQB|{1!5BE0+^q!jB%hF6dB$--CW>0n za+w8sWdB@tmE%0}=TXh|bscMfC-hrzV&3cLoSeyGXl0laYSx1M_Umh2Jx{Bpus#Bu zlY+J|(L|bMF{I|qLgRqA6NkGV)lso%%nXkDObsf+C>)20635Vh+=W%47KVU~(X|RI zMRw)7{K*dBj)yPE0|XWvSpNH{??@o@V00J>ePtz0De)`HXXz0ouQZT9JYhef6L-hj z?#T-jL>t&F?@5qmNXnrBljI3S`aSt*q95;A_!O>!xbls`(W(x0mc#A9-Owx}gPk4> z(fg#1mFCd4k_uD&+!?mE2E$-q)=eKnq8UoDzBIX-B0UQWzsYe{e*0Ac^il_=ab`6@ z_&O2AJ<>Rt#TG@$pI-m7uo-01i32>`H?l|Y)$`%wM(K?JBkzuOGYc8jbW(@poj|RD z)RSRHm3P1AE_Cgd; zlJ^rogNC3WBXukDnbr^DM^k3R(47!>0AeVo_FBI@KaZj=RunyNtHFsYHm3M{T?K1eVZ=?4scFGgw?a30*HvvM?qQ@t!IQO^8nuO6iZKu_s+>bWhh2_b(ndm>gs!Wl z2@V?G|4eP;k$Wu%`Q^gN4Aty?Jf(3-s4_8Zq2b- zP2yU27sCzp1enP;v#jGC)ka9!S)$!Y^j?T|(B43ksIaXd3Uy_+qZTF8B+}9tq&Eyt zuI*7G`~yT8z;2y}<8yTi9~iW$#+|WSFxn1;z#iz|R_;sG!yV%VkYyJd!VOgy3_Q!j z90ALoVv>;&x09p*OuZy431l0s3VGHj^V*J+hy-hc6&+dwB9}kTzZV}o7x^7*1{Aq(^KISNyt?<`T#JAa#FLw1Cih4! z1ds7Nql7#4l}j=Z7Y65mTXx++NFCQT0yA`5WJ4c(V`ZQ0acRhME|`Ca003AsL@l!d zU)2ini0Tw+DGNL`n{O-SU8VdeXJe2u9>P!}U4=t$1li*8Zzyfc$Sn+ChNj*ahS_G_ zQSSG1;WRUvtVAAES5E2BI7s-NGC;|-2@tyT_5zS`hniyOoy&%L(?5v2;_(ShZcE=h zRv~E-hkIW`=5h?eNfF)7;Jt;|Z~;%c4USGAUgtFld6rCWWpmfX*i&`_KZ_YIrcYr;r@O zbYJen+et_bhCu$8H?hPF-AiIavxXQ`h(X*ZEJ%nZiuE<_3Ut!A(b#;p2RigQl!j6v z)Z&&wJpb^;c^|<={%3&r+{AT8seR&6t{uEHX6vKmL$MA8Ye?~G^o5bw9;gT6$gnj| zQd#%MUTl|DUCPZ&fhEQ32E2XUq5VWjctyAjrrMe)!fTJ8NtM<1RFWHN16WX}mo9Kyeu z;^{u1=)kwIPtkB5_BpS>P;+A?Sri5O2cX*FJ1LZdSIF0X(z`cm?+9(Advm?+m13?W z!up>n0AH&^2v=+duszjQl0Jyrm8yiYV72X#d^+V3Y#mE zcr=LYf5Hj?Q07L1jvl0*XKCbJ(y;HRrtwWiYqjaApR%|M;U^F~0C3HTfRKajW5;j} zU;fzU`+v--C)(`*ICEejKbklTs78|q2vXKfe^kU+AzD}&N-Hp`(G1M~y^NNiDbnc+ zWU-7A*9vF4a_Doom>vil9m47iL4eSSRjULB7*mQdV_gyek^0U2c-B)E%yn_oX zXHBZa!kymWx+s>j&-%wz4wPM_XkJNR6T2`M>am_lhVktxetDc%)%%kiq#@*_x$1v5 z)-T|0S=pT#eb=MNtrX_Z&tW%hmjjSs?fuh=6AFs`7VJi#Qf76NJP3;R(KeT69cuI@8p$l!D(X4TG%Vr4$c3tAod=-Q~$?}(H>CMQDzYYUL zv~3+w{R?Q24CD2I2eU#-7f#Ib+sPI>18actb!eT9-+WaSdG-Ti2&j12^2f=h;X2yc zk6O7ZPZN%M#DJ>=`_6U#v_#7&izpj>G&yEHr)sM3muKQ`$^}w1h;L%l zC5j|QwX-HGsmmZ7j5A(iJNmo(WcjZ01(azBBPr-FKVgHY5)A+d%g2z$$d|vV!(}jS zb_dfD0Dp=X#Bi$cI0o?xK(&R?U2>-W$=sRleGao*)QBx3vnbF5yl?U;a@qS^(q<;m zgraDeFhA##h3Crmc*wN3z-k}Geg{?6$GmdCJbx%|)^)Ko2tSV3dH3Vll^M4s{8%Ch zo6K#=$Rv;Jb&JfeDeS9Lj2+DOlEChlzy3gGv~Qo6pWhFciBlOOM1Y)ukU_N=0u-)I zWMBNcQ37>kScdTz%-zz7Xw0HoIm3W3w5!xD3Ph9rE--E2jtdMHg)!N5GiT+g+13hS zSrCe!1Y5lVLi#Cqn`%F~ZvLADXpGqp{RRS2;Lh2o3sV0oH>B%|b`qb)Qo(_$21su7 zLP?~uDul;ZuiIH`m;x<~czgmrFf#!$jsVRB(;-A5NE9A^ISkm%bf(z*^HGLLO^eyk zj8<$`t|i9d-^tG;iVAdhNiuozw?ENvdv43&7^Xlm^rjuOvXV5yTc`e27RTcgjo{St4AVjn zUP%U3pK7EID&3v)X5LY;^=A~64$(idqgP14b!?hLvxNmwB1q6$UGv6!>xO!)C<9p_ z91%AX5hZdNUE!*`QLZ)s#M7d@+?KeQp7`2oruwiWrO6q1(spW$35r-OWm@;WOkFX% z-Tslz_>3f_I8HNP)N*(pceGgo`o7JfBm;7kC<#m@3&`6fpXD}JnZ$)l^j(-a3Kz}6 z+Y3J;Pv;lst1etC3m9RI_y_C}aJe76U0lT{I&coCLnzHV%&mQa&>8YUVHy5d%*=hd zZ-lPfLF5|@}C(w7(~K4SF}My5e^k|3b#^^Dg)wx@&#o)=!?3C zld$mHABZxBLJ4CG@`nbn$HK*2Sek!Lz>9>hB|QdP)opN!Z6^!RUYbUx-4D1^7Z9Gc zmfWvvq7;DGYy1?zBF79OG|){&K0uOaR&Ws8WuSiR?gAZ%KbYtTCyk-vf@~g$;?IRn(H(Hb8qpZ5n zRSeUK?3}$z%9qW}baUDV2Q?TiJtXnj#dNVMm zOv1&7E{lI3QKB)lsm;o;4=JxmP9MB8x<`3Mdj($uwk+GQXM34H-Hw(hsqKj0t-T78 zB|jCl6!KfNrvdP$Ezkh0M!OBT1fSZ*vSiNPa{=L|F5B~x|nChInYzSL7h9p<= z6U#;ny9kjBU{0iHn|G9(okt8JGu;w2rzXrk4s(XJo;Fj>4_8-yOp*Tt{`?_+n8^z)+oAq=(h8vJ>5mt@ z)v19Sq~|#+ZairNmm?t)YeLDUEJ{uc6zgQ=(u`u8eF8%`u3B6jO>~79g!@28g#yS& zRt)Do%&iVp+lpO_dzm8vMmw&0QhgW@UMgIAH9y(w4u*LE&_s&;>28J{tOpkhg}C%qZV zjZ*?xP;oRnk7f-(%-f@J-72;gSM5|E9%#Xsq|7bfNK*MD;ut8381OY4mTrfdIwzv* zM%H4xz!qB3G7-Hfwk;=@=4V6ex(EqC0^l3Yg6>Ci;32oN83|?1L_8lXq^JC}X%~fc zNMF8`ms_)S5oL5jj9#GGNExsNO1M! zv-1p$lC(r>Z%w`wdtaIrCHvz^jIl7c%&nzW%o7TVd^Nf56ZRfW;n0a55tx-JgY9bj z7G%>lrYqj&aDjMyCxyEJioWx>+XL9pvc6qy=VOV~cE|h7rWYzYQHZ6X>#gcMYoHMo zwj8QoI)Gu#gbrAyrml)kWZ}OZ1*Io{sy7M-aS1O@^YSXwj*G{?g!SLrer>{MFml5U z%JeskomfI_bRi^J4%4rrVQ$PxQgJg#W&Ea_wu4zA@r3%F5gC;G!Cco!LImN)d}NYz z6g)^9DU4KqW{eb?{mR0=AGVw=mON~Tf(eP5OGB3mJkVD1@Lf*uf%hU9^gaPwXa?BR z%#gn^&VdhpKjwbCR1rW@?-M;muvf`M6{xKezL}PWs=xSR)|TG#DB%Z{ZHH3^y%mI+ zJLE31cL8A3S4=i}W+NXgnTeSti1v9wf43Lvfad$@wzpW!;J>W^QlM;VJm8 z&pdr(NAXgV)ic#%?PrjtCGe_Y6i$L+?I+i-4zZt2PzV3h4ZXM&l!P%P$(#erALj4T z)VNjhZ^QfaKkk;THBcL1KP!QUIb=@EE{66`O4IQ)z}j3Rwa1d`a5rkoNsf!dEcC*9 zX9ZGMGF@E7fDGyfY?7px2q1dOKtl+`q~=Hf+{{WW&WKn(JS(*D`QBWIfy4g>#r+PO zK^?4@Euci!>dn+cyra^srYe^BJ-nbMf3|OHpt%ISEOPk$HJ^8o_W~^RLm1k*K=_&% z#x}qt7a47eK1O5i3_ zQEq*J3zUn4q+ouU-k!E>3_2U4f7{%ok#!+MmWgj4=JI@trp4eQw-evORblZDdnqbs z@avTEvERVfq2QS^z~l5Iw(mWiJ!wP zZUA4)@*~RxlwG4|0BimQTTep#&|&v>^TQ9ep@}YwV!frK(6vgDY=P1Yg>|NusVuMZ z<;fs_t%BC8koncKXZ6UTr!b+IT!P;do5$B~+V~d;5%m^PbtvfwG_;T%%*W7}Ib{+$ zKRp3Qjm9hyJEr4Gc&)u7X>V;4ih4WZeMaq2Guq&sMHH3|xtx+%A%-?Jy)6A*FAunH=3l(y4p2$NY+Jj(EpYm?!2 z>}>Xx6K9V3pxMx_(R=Tu_*Zi>7+OaPWLvf^Er$K3Dz0po+Aa=0FaN20R)z=e^AIM@ z+xN}u6ne^@fUTbZF%Q2z?8iw=GK3e%ht!pWbD)9nbu0>@ixjIsQyr(@qewPEh$64m zTpT*8iUNT&qm3Er+-P3{HRk7H#mq!b5o7*|#%z9*y<_!p=M>U^DJ-V}GbMqvD7q4LQ)qvk)9|HZ1MrzOB~&w0X^v!wFIf(IS9y1r^Mi>rn14QNP)IRQ zecHV_uRw#i)w#*}W3U33-bq?h7O$mfv<<3SB}?ky!!NU$So-rnlH;S{R_o+@vGarl zq;sY?G$ewXbOp_uqIq%hSOFUi8^4)_jiWRE0TIuqQfYn5!3Icnza@G2kz%+$?x!>D#B{-<^!X` zssP5%$4Ve`u(g=gYJg(8urz4ihlqimSu`E|^@kKEFMSOPa^MRv*q{xY5(T!#_0ADp zfk)GoSG&YRhjO9ao;=b3;M0_YJRJkRd5l8Hehf`CC`WTeFmVFn?LlaR(RX(Y{HEV~ zkM7F#=lxY5s1XIRnE^eI1f6dm8b^kc3l+j zXwxwpB4)!-`IM4r1r+7>dWf|xpf}uFy|1Q3+?kIs;T9;|65o?YMtJ&P@?%%Tl~Ly_ zK>1(u9*~reItaU$(=wv@g4P{ye?Qb1X9#~u#-;#04P_>_!fsfrNde}*D^L!Vc~(GQ zk%d(1pSLf-WmXP^L>R;4GJiL31SUh*VoWm4iD=S;C31%KJ`WUd&?MsF)(3C5!hO;s z;R|>L_!MSKcO?CjWG5-Uhq}u7+aUIX>uE6Han={8`0@Y%GObWY7v=ruO46P>N06}; zm+F4GDv8uR>q44$@sc?gFBf}U8EQUV$I7E=)^CGtFC&w4VMuu~R3*>^xVHjHNZAnY zv$va(e<dg4FFOwq-7jWqmLTH;6P+ z?S6=QI>3^vw7-7^@A=5O{pIotrAXfazV*Frb1kV(lQv!Pqw&?^xT0#(>`DFx29uCAGwCHZOEak!s>wUgF}oLgwbYZG!y(3dk+qvURxgGK9hHVcN4VZeWFT zn4XhuvJzZSHlBo?L9$6Z-bPw%?)B=;9v{CZaDTW$dTVYkas+N?)+m$J+a2L%!kc#} zPPg?kS(V7(O^WA@5|A6VqUFmw-nttVUP@PQ`E5mR%Q=e}w@|kT*o{yGw4^`z4)b#H z_;L$;h`PxD605?Wt)s5#sb==crhn#>;iiy8hyb^n6q~KB+b1Z6OsVLo$6LCmm&Fh- z@Do(4%9TCwSIsFZL=Js(V8gbQRz;{OlPBOC4x7;R^&hjh`LcSDA zXvJm@9Mr~Fd3et9HHNj~=P)YbNVAma6QhTJQGmZ;jr4ZEth(sx>Tz&OIsXe@2?PxK zpM7P7bKZRG?s$in1LC;Vq@>YBq{)eUSd9gxamIW!)m0qcKHh!RY&f1(Un2TKVJUSe zhU)lNWno}(R0Ax1JgEpzC<^q^?Sn3h`}*Ws{|dzy6D5IN@he6TL+6DL$wl^JXtbvc zG#6tru7Zu)dLU99S~4g42hT-e@hx#*gT5$m2f)E8ar*&=pfp_^W5`ekWYh;rrIOuY zRqNm4(L!vb9sPi81X4iLza>JQFM@)0mOi!-MhuKXC=i9jfRB;Qxi;0}!v`R4(qf^A z8C~B?pjZgq-IS*4O(=l9XQ5Xa!|G`MnX_r~!@AC@p_PLelvmoBiOpb3a^j$gnbP)S z1EV#X|NZ!L=;< zIqKKVJ47vUq^lxPG72lAC5$f|{|&&lv|P^+fo_gj!}kbLOIRQc?%4R2QF|l*Op$ER z`zcD};0?@NWb+dGeg-$q#pV*rZbm8{L>A@0dDKdwM6@(vAj!)UcJuyOc2(Pq@5MgC zs$q<2$Xn@@_^v>_WPx;rLEDWC{b(c7Bk^dDj37$EVX({#G z3Lzo+qEz=vh0z>fH!gK#Yw7|04E8|^8>52hf@gYds7xllXtX!e%+!%+XC=?h@S z8=+z^N%;cG8I-nC8N*Q)&DN9-t-9|*l~J`VvkD-@z#sk&b}E9N3L)blDI`n7 z^p=*(`{py4eff7NVD6yD%mHxnU!jP0X~?AqC?UbDrotRBSvdwIv`F6ohP^qrAz64yqlV~za z7sC6J(5p839e72Rca?;n2&u6r)iTFMv-yFbZ|)4|uJ7?6vl}`w6yAz2q7JU5Q0%YW zSwK-78IvVf^LK(1+(MnN;VgwfhnxHvPzD5@M7jkV>OCw1b(5WW?~F#s8CV#E3b7(Ej4E zYhKFA`|&o1j_?M_b4$+xt0^vhpTnsvA&?10cR(;ZyP<=Nq9lQIU3eKeTJ0Wwjnt9x zuLi=|6e0Rp2Np$%^hs=dqkMrUHHE(hZNu_hh4ACi5A7N1jx)T63=0x+O)V55=Un9Wo zz)y&UUF5TgCb)LnhonNMUL;^%5v0Xa?C%ZGF^FHqW{gxoVJhZ`dvHtiUTYtve2CqD zjykv!;yy|{>7poEEk~&pk9rkQ3i)0SP3T{62GElY(SGnr7v=e2;M=><-$=2~TZ2Hm zuZzcBw;Dd3cX`%ksHze!i^os7y5wrFTIUnZgYd6nB;IKPU1)Z#yw5Dfr~x zc|+6G!4EAX+$z5LT>aCX*W};Za^Grn*>F3AWZf_-$5AqUeJJRy`Gqf{M+1BM(c|#B z=J&Bs(u_;!dY!Wu#cVzu>q?)Lj`~7k#3hc;Vl=9*G6B&h_K$UxPKQFFAVU9|jo|Wy zPk6q>DA<($*VC8BHFb1~e zPF>s5NL#9)Y+8Ysg#^S92$DpsAcCX{mn8v$qND}_MGQ;A@}7HR-~7dg-sKzr%l$}g~isLrZ=^Iw5|oySvN%646n94JhQNWbj3m7W!al#&1kt?5LegR|T{bhhnP*Q@TM^&JeThoe$K!th znhzj(TpiaOQo`dg*a-5Ilz%Qjq0*i{M?$J|kt38W<5t1ZL6FfGK1emscgw=)Wm6Fo zS)E~U>C2>B4QE8+;jRwvmH>DMuho_7JN)lY#vR68`Whfkg=#0!0u?(xh0%f`1e7?^ zfkFv?>S*v-Ua09TGBrKk8h~gWoP)&4)8hP0HUyu}xVH?KAHu*d#pRp-`vQ^a1?^DS zi<{KEVzkWnYjba$#uk#rdr^b5o##*W z1U>T-&>!}h2Cd((4}wnG=d@>#8wECRdfo68;a3^=8e%}$e$wXbXoT5>qTrMttG<~b zR*DSb^5se6!eR>D`3=g>YzieT3SYV{gclT#j1RmnRD&5ny@*=nxURDbm-L?&QXtlG z5uDZwT*rU?Gg>4wt)tq=)j;~q))u%W|IHM7qjCVg;0{a4ZG%SZ$X)yw3P%1?5HdsO zGdfh+-W1PrXekT_{QO!`n#3q93z}-3S0{`UJZlZ)j@72BaQQ1$nfrC6NK$H8j)fI1_war06hob002RFbD`GIRQoALOmYYGun*DSq+JJ<{9DThM;*7t9^)q6 z5)b0Vtr3pxhc+=`9lS~t`OBcYc^Vw=XwV9|>I$h7s8QC77w8!Jp&E#7r#ddcwc^jq z@0u7Kr-wusg*o_4F%FHr(@&7WFW=mB9~LpvY|ks=s)f>Ur~vdBi!7kznl_j)=am79 zUhe@Y@=DbIS+1Etp=#tTih^D2P|{+UPBb&gfvqIrf=L(TY0%5$8STRTlAIXHdqXSl z{RBOZ8c{-xbXam5psBkhI1h7idKqu)@*^NuAULHYbpCW5|Y5)&0@mw zU>vTm5}J5)AKU8Gf6-W5t}=;lmTm8K*tjvst61P0hUIoR+&ieRKj=OGdg-5e_@GBi z|A&#N_K-sj4mTK;PyZO=&~np&>cPwS8X0txjfAK}o+=|f5V~8GIPF&$CilZ%FG{`d|knCc3TjOf2&o zP6<#nD2}D9SvXg&dA1yN4CR-wfJNmMuLBLYl@evSk_>`$SYP!9`QrlC;h~K{aQHz_ zv3sR^6e`}6k(e0)0gF(`E~h#yr0s?-D?3nz!e@{>ivXyOWqZrS?xpTAC^${FU@KW4 z!J$xKACpQ#^gXoeFAu%=Z0L#Up%wH4^U0VgXbnATK;CwNm!IW^iY!jRDS#}NCqMe< zR7S_r){r-jhx|oP9VlZWWFmPyZ;sR*74lgqF2TuzdpYh-=G!nKBBwiyAO6uEA8QMi zfLysvwGH$zOY}_5&BLpCF2p1_o(4|hzK$JT{l8&RQ1B;j+CX-F(j{{F9EYO$yQQ%kF_Qmm&QXiwiC_ul7OFL`keNtW!Z_aQ37*&Q`)68b}%8=LZF^%_2f+~v1>EEHS2cH{Z13QM`gz_u+n|C@J zWenY`@AbD;zId=5k)Gw;Oaa5m6orB-fNqp)8lxaQF6(>Ucd8lV;}O%Of^9hzVmKR= zswU+mSZ;d)gc~r^h)S;`>Sy5K%2Qn#G6~6|>D8l4a9JfLw*)8XTYuH2X6(+)+n(N@^-iwdM9t-;#iUjVY z%rB}3ihM(fc}WYIq_vY<)|h?%$Fui7ynpRumQ|s0L*RC+NdL}{Zxo$;CRCk_vXOWk z{d<*N5l6MX{m1y-0dK1|>}-B(|Nj5}@WG1DmwaKqjG#9TrH)D1KP_3o9dk=M)zGCJ zDV!WlkM;bl)m<|>->VQ?G&bBZlpqQ``%#r64p+Oki=LkZiHW$$b(HydeJ&z_+m_%W zWKA#N5PUqIH$x++c0Gf$P-W0J)j_*H^8T|Z$6jWX8nT-@&%%u)mPcur$jMKf6heL1 zki9UdVgarfbJGJeJm~Xxz&Xw;dVLp;c{$=Wu7QIY2|HY3yqHHmbn- zdLJ8aOD|L}16`u`pHEJLAJh-*XB}y`@k&llfmH6^gb|F*R2`(ZG^~Ug!nlyqvoi`b zjCcMnI;Ibslyl6VxRV$E$Ml~qiJhzG)}iCzJYrP|x1yZeyS={n3fN1$zWNFJ^TQEb z%E9ok^`w4Y%2&|TKNnd$8H1g7p(1r?j&?J*o?uCkg?rW;p3Nyi{>nZ>g^4ES7*6RQ zLJbXi-_;~9h8JSjj13ewIl6yd1Ie2`+(*bF-Tz*CX8TklA8Vr)sMFZS5x?i@m9@Oro8^k7)$OKhD-{lTe$`l9WuePjD`yX{Gi+`uBz9@Fn1 zq(cs-Ifzm+VD_}wNq8RKOzBe81DxmQ=0dNbMUJ~HzHz1lD&O{Kx-oN7ek<|V&@L_8 zT0>C87DhD$VO-j8;?g}AQVAEhM1jP-FeH9t8KOuDg+tOEG6($Ood}Q4B9J@=Dueyf};3?Mb5%nci;;ANGoio*JZhAT}c z`;5eKULgPC)re4RZad=fLur2-tu($0K{2v3Hsuw@rxz{X0iSCRrIH-^84d0?m0=AM zgHEeT7y$eRt6{J1E z>;F(`uRVaOJ5_V1K@tTe#Iwi*>w~UX?wGp_%scqhoak;!Zq1A-Y~Ce{r%V>JCJRG{ zyWpS{KMoTPDx1#rg8EctSicPJS2+h4741NdOwvG{0ZTgIFb9wdPAm}MY-kYCmQkn% z`z!JEHrU`s-AQn`mlCdv=oeQ#S2^O1F1zbUk9cnAqtpPnKDJ29LY7*ec^d*Bru21A9`FJTlw zfVlM#ESH>O4z7WF#w)|9HWM#wc|Q&v3|i-3hnnwHY{|}TBcQhs)fOe5T_5~;X z&i0~bdnRd^Sdo9V37!{fzt8QC;ADsm&9uooa7kvC$OnCBI5G!}K8V`-(-xoP6DMYq zoiX&}oB5J5@$-K_*C+Ad@-&`HI}buvWFE#e=@gHGpYNtEHr@UylfRIvfAI!$x9mx( zfY6jrHdtNnz+QbGF|&vKhPNJTIN;)cmF+sQn7;Ash+PKE%MSZ#vNEx* zu9YVfEZmmFz2S=A_(yl;BWD(|chpU=?$NehP~Bfo)v z*!o6fE8m{tE%-rL?HbJwx{oJuJFqX}_F&^2sm;_cM{WSJ2p4V|ypc<9DBbhflpy1} zfu$wpDpPrCt``O_S?f0fJj3~zJ+PZB2XqW+s7)b=;wb@`*toXZAPN^d^#CK-_|3+% z#_Jdd`kb)&eE|nI=CwU7BSIp$CwoNEZyxgaZV#9g(_D`r8rx}iojt~#!C?%=F2#iF ze}~f+z5%o-r`?jpee~jx2u#|hDul%V%>(Jd9_g^PpIBoKGVO#t;+e?HkW~HN-}oev?Hc&E z%+3Bax+H2>7a>Tnkjfn2tK+ed3Q9z{N1g!q8U8IQazsckN5S^hA02Gmz43Qr*ICR7 zq;e{FHfpn{3xlOoBJF)7<^zZpWD&l`9uf9K1-^{1emkeAi@aj|EUh(6g4zaINX&+_ z##E##SLw?22AvMyQ3CjpRrV=La-P}-5*}*)*fvgj&Gt(}MQ_&Beh`LXFlBpn;I$Ej zT$gqZ7%kLyFy`udZlqA4@2t*VqyW+`7VZPc0KXL!-j?p)7;g47gJ-y*UHk4+(9x^^ zLC1+v6hl$0jM5J*JPC4L|NcW%iL$J#1rYi3*m^!r!C9zHNoMK`4KVBBFVQu12^zre zml4;yaMA?;`!%#l>|4+iTDm@x_1;*zooL+2?x~b7p#`E4lYlL1G~82YK(tf2N=u$M ze*jOvYT|BX*m3Z60TUE^b%okY2t^1A(Qo8mU~}BkQ=P)t_9Z70k;d0+EBLOa6N=6PI1J1w>A;h3|wB|D0i_@ zd{~PSvSq`9FIIP)}$37 zCHRQqNK7I3K_x&8oT~M-%8d=;E>xoGi>vL$=pOzuU)#-N6Ro)cP*Ua1^>Q_BjN}_* z9koBEJxxP~wT}>6Qb`eNbI57`MMv6luAV4UcIEm69zS~`zzS1F&ViP9!2tUNy~P|ed0m?gS`G;6c8rF^#d-o~971nil&7x`J|`Yqftbfqa#_E0~Oz>Ox( zM2a#q*oX&^t)X>b%GR|LBTL;Wa^&8-&BPI?Aog~0C{K$w?H=1ArfG$)DLx`!QJH%c zWIZFW&ErdKv@PDgB-xWLOA;g-k{tlwz&82J9IURCu%TgH4A<4A{evQG|HIXoxC2Qs zN)I{lpRB#Q-<$r-72~@x-~o59oq>c{=f>p}bvjf~4$c$wk1%MCYrtgT1>B#wj71$g z1Sn_q!9EHh+zUlHkmYkz6~(0aJnh9?WgQDQ=Eut(MzvbbLN{C-@h4C>0R_L_nUgL{ zIiT-G+!C}vC&CnAonL|@7M(!>NIaqtF{H3>7~)yrmqXw#QFf5?Q#FW_{Z~pX7ikVX?|=cpy2rzB1*&P-Aru8?)>=TVGZw$y+}yRBs6d( zCo%jS4K|H;`+CZWNaaKGAeuEVljh$#-)52$@&_4}@s#10$XKqj8KO+~_yQ9(pS*`r zvwjG!3H7GvTsZSF`aHy&RA7QoIro+{B~4$#yAXgbkbXAK+YRH-kJ=HJ-RT$*@9 zf8NJzdeS7;y_i1qY!~x0q~I(3vsd>7WL`~-ctcQ+p<*PHn<24yP~lz=7F{cEB?f@< zpEjVt64wFySzY|E+8K^vxwR!{fEd1G64Us^zA+@k%60wX1N?jhPsR(pDJV)uiL*Hu zBhI5Fe0FZ+n*2^_c*7qMgKIbK@jb;Vg5ym%p6|{H4{i`xG zV&&&WOy|VMk2oXuO^QJdh1)K|=XW!6_Uf)`>8wq)wkAO4UBu@4q?R7p(rk$rmvlPt z26}1-^>rpWKnj1?JCOdYlo@;r(dxTBgem~DcU-`7OIsPK*XsRola z5^vtHdaEyrAkOb}5KuZGqF~aEF3iHn>Q%7~Elu}tE%!&fpP0RnmPu%9Fh8tSO@X2W zOT2k8H}emeY!0$E;cws}^$|ANe4)|Fp?Dsj-EqQ^PXYYMh{h<7Yi;$k(!5tpSig${ z_3>2}jq{_}L@*nJnhf<6DMNoK=h<3Y+SwE8gW;YRP;((`wzt|a(WG_v{(`WG&kl)m zEZBqY?f%$aPR_NS?P(f zzqLC9*;N6IwJ_HDq`QSs8=|}dWm`FXcvj)`N zXJcuoejbWrc-EG~=p-*`dYazuiz1@5j5!+{admtS{@sefllW!14j$E*SjeyK0q(MtzThA(UEK%3ah&81d6#e<8uf6>uz&wx( zm?wwqdVxGwIt7frb;egsPJNF|6E4Gc?n(ok2=Uh^u(D6D4CGM48>%F6Q>djQ=f=gs zMGFbsNVb;k44iaPqv0;1Gp3267a1ViK~4@Ib>;t+a2NtNW7?~AC(%7|bMqMF*rI}L zEQzJF8Tw%;CbQRcSiv{dn9W$}f|6*vNb?wJ?7lb2b;W0@^9Rug))tKJR}i|LC;&;d za;=kA$9HUoFJgW`wHSB7`Eo}Zap;d{rxmUapGvP=JOLHW68-JM`bU?*O$|bVcogs6 zb`wf%vBYTV?@<#UKf&h0jv2#EaxH&ROr{t5MZw#)d8ER1C;oTS!$d}umrYHNca z9q`rV_2?pTHBky}6&NQ|4(?Kc;dz)lOfsu1Axyu6zG#PZ``ZXj?At=ZhU&{73B5S^ zvvxHrZj5wPjk+MC?hLPN&Ox~vUk3(DM=-Z#h5+lgY9T>3CC~R4AdT*g*yWF&R^C3 z>9cJ*9J!S6at(|th#A+u3s>3LgzCIF5yXaylyv>~w2m+dtblqSLknt@F5I)uqAeYt zZMnmdF>!df1sQgG4IGp!YozYd`vrT=3p<6J0afjl*SfkZ;LT(eVg8m}aI!#aZ)NFb zyN$3&gu;!HTW$6*sNeViYrI@>li^KR{NKhbW4ZQ@+iT#E{Bz8^lLyzq)iWBXkMl=@ zV%|LmA=$M#D6|yRFOv7Spv36M^9)<62*9%OG2iU$A*ZAUJ3U-1@uCH7TT!8a%7Jj8jE2IPMSj3G`nhi_K+&PjH*mC%vq>^%?(_B+`ixxYj zAqNYyjF)=FC?3^gdjXDz6osxD9*E^;L_>9uF1Oh)N)4bS=T<{4CB)KmI^OCF5`Qe8 z*P2GeG7mWU!8o?h|4m0Dusf1S5DCveu2D#~!_wD|5|LckrF@C0fjaQK(tsy1)v;(c zH%H;V@({L!+=2^ z_ocqJ>HdpmyfH}UapOL9jLqA+151V^b`hpy(!32-+OZI$Lq#Ah%{wKD+0s@IGkRmY z0(1zqzp}F1gZK<*(Re8GE{hWcH$q%tCcw)4s5x3+EA{uq`2IsQ^(^9H?Yc=Sl+cFz zQHlBj!*`tP@jd##Kuh-`Phk|A|5@n4aF{v4hyDe)o_Cj%a_Dcz*@mNnG1`<=BqrH% zT^^zFXB>G0wsvZB1@B@-ahRu6qUe;eSbOX2@r!-6aX{M2zIDB3YOm`laUxZxRN*XD z6z(eurkf^L`MzoWkCK|3z8)zI}2GXe9bht@U;yzCN{^ zIl@V|g!Z=MHwQw(ccEY6sFyIyC@VZxtlhv`sSe_feIXj>YUg1;*o$;W^{;~ke7^p1 z?e>2rkd54i@~QN)?Xy$i$OT>u_9c#g6a%fw*B80PJ^A10_ppu}woJS34$z_hHS9ys z_rx{^oKtFsg4i)j##mixmr16S{vhB{( z9+(V}+Zn3-4C4*thGcB|$7yHc(^E;z0h0tcV?j*Za1=WKO+;^H{MqI4r* zjZK8kRW$yS_BrUnpZ*9WCJxm34kZ|FSe0~4Ur{Cd)+2V5bxwYhTuE{n`LmNqPf7QQcasaprkrlnes zY6eur;PO|S-r>;&-lxOc3ASXNn`n`%jof-ZAt?H7D1yqexb0bEaVP!M#vkgtI~RDo z927K*>02q134Z6otAl_BcPy_yd%U zeidSLaa|%F=Xvc*uaz9G(V;|a{T{RSDhDsKvhFsP3hNowi-jMthov(2B$UkZCmwv5 z!1L!5iKQ49OZvX#VJonYnOH@6kmWX;GP9Y`m+zbHD`% zRS&1yY&jQ8j{DN97wcJn=^9r@tMd=V!qf5yNJpy@a?7Vx8 zxvuG6oh`q;B>DpA_A)Uo{*_LDF4ntiW>?Aya9pllNkn6ne0NN6!v44%c>X z_s;3-tZnztaF>eRhVKAk>_L3F3SxJ=;A=<=92aZzKe{J27ciFU78Df56?_(L=IFRN2Q+Xi{Tb-^L@JYS1jOHvy+`R(4*d>ZT{58Qe&M~*()-` z$@M*OTO-s@$eT2hdt$evAw!d@#!giZQGLZU6W45$1w(VmT9R4_+;{!ZMGmU>(??$R9ANA4d~ADp|B_;OV$T1waRnMsC`!|67B@5RmE8M?xDD!UQbfrG7xfpM}X?y?St*t8nM zF)%JCw2hxkp`x{Ihe@;!-U?-&L?=Q>8fcH#)O_3q=?s4A#Lp>jLo#K}@w0T>zYgQ3 zD|PFpk*3?KbA!5QM6^ST)-F*6rnQL1e(|aNZbR6KFlILWVsCKKV>6Az*^}OobUBa* zhDB_SHos;e?Bq19-=#TSR^e@#e#zK8n2qS5qT!zg4kR0xivz!?iq~LiF*M zvnQEHMbSr#mf-r*My$V58AUU7ntY%CBq<39hra#}WO%CIhj-vG+W;`N)%=&FqGleJQi5KTnhN@d$ zmoEB6J^mD4@g|>vs~qXOWs!JgjkGV>qd6Qg``f`^0gm`N|DvTh##Ok@!K%dL;+0(k z^~zX}<_yt&h0V$mZ-@N7KrDzO7Gi=Xf8u_#;$B@dF^SVfPv~oc$XYcZuEt*Bcolvq zKasR=V_fRE)+<*t&7JBQuHA}|B#eKijMdQ}z+0#~lkVp?ECs*jt7er zU#*PtXr=@gb>&|(_O=XJayMU=2H>ccO7=uXrhoQzRjR9(vc=F(Iyz78Q zoWvRK`^f%TX5y>4wmWRFGI3k{Fu}!bR#d>sjQ%x|+G^x{PT8)RWT^q$w{>7QG zFGEppjrtiD&&E-sMpa!OoE+bte8;cb`_Mm1pTX!2H&cC>#Em#+q9{5#^d0J2*fIvp zR1R-5EE~m1{KWd=5*0bU%;MI3aA?q-hBWvM`bH5J?|=;9@tYTq#lQb__m{K{pEA?g zWdeazdHBoe%}_og-!Q_U4> z%CT&3;&yD)!X2gO`20ZIP@4L@b7(zdP~$>SAKzYBuS>l9M6^o&zFY16O5N+kyr$H~ z)!|JuN3(MgOB==UJNC!JB$rWPV9666LGs zMlWkUw!V{~U~GZgLBntTFMTQh=yLo8lCRSOOFHyizgt&>NUUdO{Ui%NYSa(TqS1XE z5ocN(F*{T=|I3&KoI~u7eXfzI5AU0fp|4kZ)>{lOBgZgQhielz97|<$lv^3kH9}5% z^jy89L9UzidxG7+)AkF7QP5@Vy!J1jUq*kZS#xP?=TzOV>Z&Stan*MQv7JKJYj&k* zNAg0E&p)fEb*cu7+YNh|^(~1Pw3SV(SBXn@sfTyM`QuM*QnULb6HZyQQ~%JPin%YcG~KIG}qnl=Aqq`orz7T)I}zrEDzE9D-QJ2`a* zult2Izh+J_F50;%Ej9OEKn*6!k4$oxwZ57ZdE)u*YjoAiUz3jcU+Mcbgh6uIU9T2M zRc7PG-DcX1B$_(;%I%e_>YbO|zTmP@;Z`~ktDxKEh>Mis={0lG09IyXH`iS5sSlKxHi>b`;UgW@x?=~qp%Pt-3ZA4nA7Ov^gVo}=47&I1dT z11iU=edLS{apHf{QXan8kgIWh@!`M=K+z<-98Ii7fAZAyt)fL9E@UcZBu-ahr=qiq zgJc7bsTr)n(PY-Pg*y8BClO`?4!dM&n#Iqt$zL>SQPzu?^z09|$9gC4x;+y3SJ)|O zcfj1dHE$(%rM+rdNX|-yOje}(qhOJAdhOF|+K*JdYXl6p-ZjN3@YCjixdZ`i`=_#P z6NWjTzot~V%?WnKcAkrRPKIsLm3R}zQT07S*w|KH?^hiU@hG)ecV|TG{_c#nRdl#&Ah`^2P@X{XX(U zRdi>EXnc4Pb(6ptHd%Y%r9@?Scv;r0Pj9FO2Gr*{HP(^*iz6uEbss2P$4(FAvplW7 z|7R(;A?ewy7pqeD7VAshqt#CWL-SXpKkcm9C=?DX%XGos*=Twv&J~e-bf#`)$k*mQ z?KRuZoqRf5W4!$E2;$7jf@>2AEcsZI4|V!#%k~1R4Fl(>akaCYL&xm|(+TQHz hH1y%DE3=DlU2A+!?b^6!8vV!KuLJ)l*iAkC{{X*Fky8Kw diff --git a/public/images/front_bg1_2.png b/public/images/front_bg1_2.png index dfc5e5936f8fc59b9588ad5e145370d94822ee5d..f5e60846f831db8e6c81dd31b537af0caf533d7b 100644 GIT binary patch literal 13983 zcmeHtcU05awlIzgqoB;_fLK7p2s1(;^coO*1~Cpu7f48GQiAki2$E4oLC`Q#L;=T9 z1VnmKN{pz0R0Tw8MCpRmfFy*x{X@rl-&^;-@2+pH?~iY-aV<%5&OZC>v-{a+Up#UA z$cFXX)(Z&VktkluL^8$6SvlCt2cU(742*((h$IhZe_1DIS9fo$++;aZPS%}_m9x?` z#hCgWaCUP)8tUt89(w#FDb$0cOO`V-lr;#}0|Z{q{zTbeFHdhjyMCQ4tF5c7s;x>=({>>`Daoo~)YMckIx4DK zXf-uGbxl1DRoO59EmM)^1A_&WXdG(LIL&qGO;b-(_rC#C$nGv7{|Tt6sUFVT z&!6Z`a>gCR$^lWz?(Ss3h;b&W>u90Xi5eudhLe^KT1SZ-fwXsXG{ zl1X|l6kjhQD2uxn(bZYS$J>A zIk!h|{-F$HBA5nqk_IF1tzbe74$cld{zBUq6E=4~^W~eTyX=Zx=n+YXIb!8Vh#oov zH$M-%{|njvXI%WeKFG}(kp9~f{k4rB#l=5}=<96k3VQIL>A%XqJAofD@PDUYOP8!l zCTTgLHPv<0(HbOmRkSX~Sp)5?rcPFKcG6T=)g}E!*#A5Fzc85OM)Y=d21l!iK>kx``fD-7JLK!vSPMVCMr&tpken~X;>~yE-$JbS#vL?18GK`+m+EQ1KpkF; zn>~T;+%$OjJ=w|pzbSv5 zN1Kb2`z}gF1L7wWb^;)#F+G z_YQSMw7~l&ePQCcpF&stin}y?BiDUxEtSn)ycykeLw7SI7!iwo<{Tu`c>HR&6!N*2 zh5Y@oB6n=nb>i&Zk9{b6iMUlTgi}-3#Oj-KzKag{MZq)FTKtv)1y! zDjAbkDUwI%3q@m3oe+{W*uYA{=~DS)ZC27n2TdZ+S;MGMPHU`N%N!vu#5@T9^cxs( z%Upb!E-_3wjY%q9Rb93Mgf88!TbgTZt{HZo<;JMyyg(2ZouIZ>SE2YR+5rpNvpuGw z!W>ppID*sbM4T`m_k65-jm9DKNMpX`ulXo?PWVO?%uTtNw-#>BtW@6V@me>f_z6L$ z?kNx!9N#1Yvl=csIDDbEskJ}mp3(j@7|vx$7>$_E&i%x`N-o+OXY2WVJ^dx>HXu~R`@V?D$ z+WT%C^WsIUwex{(vTmtZB8VQFsb4?TmynkvA*|1cv9m`|nACS>NWCuIWPRn?8v3&J z=dpHc$uo9Oc#^&+_`h6A2K7BPavm5Gk+99gONGAHW@J5`g?cYqiI_l(EkY^I1<_r{ z+G)`Z!qo2(0TME|<0mxFR&N6(9MjJ%4iynDDp402BVU7sMWZ=(FNLWNNx~s}EI)3uVl;Gka6W_7bx&r$iF( z5a=e8T)lcR>w^2Q1fNxJbT!)IZKXzxAy326z!>t$32gaQEBfuz2&B^-%tfD7!sK(R5iF}YR(k&I-?jB}+Wu`8vDkK33r?=oqf?a=Ji*G6?Y9XkKV;n?rR z^p5dWUdM84jxk=g$+7w`9fk->_&#vs22O_L#*aVGaN3@*4yxzwk~;OcZ_|4Dc(I@t zR24v6QO=zhqKCW>U=s>0cYG_n+2xYr|Dn|@)pImzKDr}GH~PtE|4wNv2bm>q}+lDevd zH|N9(0}w_<5+!PF3+#A2lkY($>lA1FSfsJ-TVa#xV1C+4LNa-}qqf%CcE<)t9`m`W zYb)K&l-?L(TFi|cDSSKoS;MFxHFJRuZ*icvJP2>80q62b9ZMNn$e8!9te%|n6YZRv zqDEhjIn)V4-d!xn!$x0R+btp-Y95xgpT@WxEfX_1l295>;yr$RwateJPbdkgMB_TmI2$Rb#Poojp#b%MgPpgyFn* zA;6PTBpK>TcV9za-}a$7m|^5l+xlIJxY%6V0oH+#l%NZ@)@sQw)=#Wny_q9{1_o^U zg|;~qQ}-6MyvpUK73SV^A4W3B=k{fpGAbe6sCd|W{o0fL2`Id^7x2@>gx@&Qk0Noj zGq;#x+zPol-_m}!<+~r{AL`OJv)jATf{P$0SslKVp1PNmUw=ZzO#3m?7S{i^gL zgJI>!+#6RlcXg?e%`7?fj)h>|$ObmYW{CiGkZq3d*v1V#U8*3aJaoz%=a$=#s=jO> zajS~eaPc;` z4|2N4uP`?T9)N{oqjfJ5TI4x5_m+e5#oU}!g^eGXgFG~UE6I6pgdltBnib|w#Y~65)1JSQq^l{gM%qis***<3#fU#D4 z9`9*0G87eQ9NjLc_Qb7b5qOXHIgW*G>6z-Mc|Nl~`S$r`g>0Ey_S|e4zRnx}1{XF`GFlwoL{KM-Kz_=N!1bAp}r7`MtQ)Mil~d7Q!i z>2C^RywRiCp@8e{jf-H#mXvMc!j-0J-*mZ>R#s_pGWL*ryG*tjTEa>+PJX+Ai@oSj zL`mjkHE4p;hWKpD9PdxEtn~AUdz9~F*DkN6P-FQ$TPNP`KQp(b{@qWIT0VZIo?nuk z-A`Xbe?9iITI*X?>?||M@Qi8v;ajIwWV%mszC~P$e{lPl=7yapsUk}XqdHobCGKF$ z{@wUqAC2FCDeDN3nUGOuvBj}x$~e;4BjC<7U$k%F99BfhnJHAkoZ73VREE;D!E_8%; zCZvixz^uiJSDfS^_XE+Hsw|Oo)i{GQ7(8<-|LlB+;guA_rQ4_Gmxr@uE{4pfeQcz4 z3s?qu!}}O?i#Q-Tj{s=(z>Hx#vcV+Y*Q@00(gBOOqH$>>&g^)K?(YK})WpNJ{bL>v z>bRUilJ?Ht!VGi=L=9!nNY>iN7eedt?3_}U?h-Ew&z0wJRyAs)^y%4yz}WMLF%cX6 z`2iiy>?&%=R5=^Hd`t<*ylxER8rs^4e0BAA@ViAVa+QBNlUL5HPe9wv%+#w4x7Ec{ zxa!{n_uPb8LV73gc)a1+#lgj$w8@1_M!YerDmmm<7A-HePlU@755KZg@Tms&M`A&X z7=o^o&+{7_s5=$L=ZxBxDrXE2sqt^`9E@tlT5;TfxV8I%h_=czr10Tr$775oPj6+M zZ}kT4fvZPk>YmcYEp=f7pL5@drLX2crz{=bC}WUBq0Xl0o_TkW@#o}gJHPjrwTGr_ zWb_xXV3p@Li75;1eB?>x8s_U+3C8eQGR`)7KI5;Bjq@|cQ{35ot2{a$G>Y$}EtwJt zldX7HV3&r`@2PvW>yZ;CxT)G=1tsC~vm z1{Z>KupQj_X|)XdY$x8n*F1KvF~;8DJqm+LB;C__9>t>&qxwh_pPtCXIqty zKSE4+7wCZ=&-$6B$LkxszWp-UWyIOIifKlOkOXdf}JH^ zb}~yT%(~lQ^8}E$(C{5QjJ;tK|U;XcPoAeqKbC*dDRDM=g&0G5&{AYvc?kP-W z@6S_g!E1^$D9muDYg*&AoqRXLux_u1Krw9Nx?rs1KXPK;knhhRv!hi~c^UlgAZZNQ zET-3@ouOrc{busr%bgVO(M2QbY@=&tNi+q=lnvVoT?>eqEH8Ig81ySn{>ZvAtHOkXj3pd{rDy zh2g1+<}>e}Uetbe9#pa7C)wWse;$N|EfJ&|{2&C4EE!UAXmNp}VCvy;6Ez%QFF3Jm zsHJ_^FZcE?a^F}R2Jt33&rTE%FOQG!mP7=D15f4K`Z{Nlt3J$=V}9{HE|Yzg!&M8f zSoFnyaaz)4EOzm!g5fSm#UHDITa0=GQu5}HyRtdM^Oc|8h4dcx0arZ8V+)p3Eai9T z`l8}KcE(dxs*>0{cQmwjoSMCR4NPOjmZA`?s^VVDMP*`_r??*;)bkhEX=%&VM&$Of z_RdFMc9svU&p6~0K=KdC6J6s}?x-JI|Lnl!%j)8wSZR%o&U`MXQLqL4v0fxWNP56i ztZezw2SkCGqrC!i1^kesAcYpcVY}9ig$w7ihrE2tu3$lTBSbsEu?V^Kyu5v+Fh4`V zJZUxg5wc= zT7q{-y}VP$oFi5LYPm!C#cuo#&kveAsdF!TrWc!s#jED-thoFcMKJjlp2%GLRBym( zGk?e_Oly20p*S;M*qmBk+`rq(A;eq#=`0q?dOrPdOY4C&>Z-mW_ZT>@$#MZEXS#o2 z!6;u@oVk?QGyB-Y>)`;G^I=xNZ+9Go#mNr>KKO=uE1QQcxC9E8XANsQ6=ctHhf>%@ zuLz*@VGAD{sqDq$t-}!P^`amWAh~5vOMqiL=?WS0PG4TsWOKWqK)bw(Q?)}sJnK#e zkK-ud81Ng^qx*v1zQl$hAz^Sv@j)$Y?8luOt(aPxwM2@Y^O%v7#nw}m;8f6B4{kIx z>4*2teAuDmBb#U+X!A(hyWzqHFs_&PHY*$-fY_LLWMRAxvAyQra9+y%VL*|fpa{++ zA&&N=wm-EL@$=iQjt}|S(bUDqbz2HNb{P7ix*t?(0h^qsT^;!^teQUjv<^{dIQXT> zW+t}(9B;C;3qkc0D}6PUSnpvG_qe?5{q1JRq6VX4(2FN z(JJQ!SYwli#bXHKLvxwti*t?I!*e~SKJuD^3Lq+YR?T-Ul`{F=7oLE&tVp!s$b*rr z&^1l19IRp?<*w%N$8yeV&+d|(Ps=?$r(|g3`Ll-2;JFbOJLI_hadN1D#WI*{IncNR zo;gMcTG(GDZ8*!c)141LJii0SKe?(lGV9^NTe|M=0cplH-=5q(4HVzt{o$%1b zYA5PXRumrEjHoP1$9yR`JF#;D0eMBL@sB?%k@8)v|%S7Dwt#_Q-n))C0|!>5K1 zCGXG=)lk5vdd}BE?KBD$vSHdhheqDns2Y$LpePnAgS`&U08E7(D~-J#*Or!$2dM8m zREDyKZw61XmKK?Uh13|3$G5^aUDyq`kZ6N2Hc7uw*+|c)%z2nLax2PraWHXMy|jn1 zT4dwE^X-&|$+OeZArmdTQY7GxXG(0X-Z+=DCAWS)TO5*F=F|7Ny-B1RN3YIB*M-Su z+K%leX5I93a8`xBgD=hsrfcj1O_9))|0I1i781}eu}-?-rcEL<^pjhC6Vs*fx<92S z=>WyXbc$cIvlK$RklQxMYxtv;9*5|A+&(ruI=k49A9^!wj@A``&x%J9nSy}6-4Y%5 zZQ{36DpZ+9I!W2N5AW{j0MDDS6BbMBhwr6@PIegtEty-y?eAS~tz3-fJ!q~UOii%y}Bc>o&j%L=fAiP0Vz)a6T0U;IFz%(HdyJp1u@O~?GykzO^lxu;& zpVGb?t)E*JZV~SDNgWv%6EJg!uUQH5ojbU1EQ%%eu(?Cp(RSj;Z|Gb_GDsZbgVFTG zghs%rv{MUPA!M*Pt_X=wp`*Rs;E(lf_-@~8*A%UWT>bneKh;(v>9m5|s#)HLdky8{)KhW@zokr!&4>svfHncVy z#P6KwP6W5>0uFq!_L|iY;?=9DJT)+%_S+!QEkclJ*M1E9h!bq7x`ME>dkFgz5p)ff_J9X{WRAm0RF4WG2Dt|GZschA(^XlqFS;xPLR1vEM0Jd!Zq)Hx#u!3CTzl4v*F zMUtF5Cn$zDo4qRk82bgmcMvo3oz=W&tsu01vp04`7BUIOgcVx>a(^Ld9A-oa zHv3s=gwUN^r2=sGLLovDlDnX6$eT@L_Dvy|GN}r$Yt}9O;gff(E#hFElB0diM(rx* zbwb`#>PP`+Ua3sT^OSBfdZ*w-I|%@8XtUCbT^Mudp*7Sr>)w@{W)KH%?O*34!y>b3 z90gM;`kl930eDE`Ad4{IJlsPZO~$?IeybWk%Y0MjJ2OGM6ieC(g}!t-`&_}e>&hjz z1*%cWs1L}+_{OAa&}1LA%wqOLrSn|Tuhkaf$R4dc@u-cOXRpBhT|8sjLYQZV5ixm; zchGm>eCu@v>NxQh%?O-deOMC*E=7l6KpN*HVTOGEM48FiJ z=5UIsLHj4{0qK!isO!a9Vc~LdrHx(I#~`2GRU;}qp_*#j?*4S76aTTBS$y&KiSs+C5kzPho#Gh1FP52=cVwkTHn5xs4=72KKbD8{b8p@a72)Yg>hc6za`c*^CBjaXZTiQ5y1*Hwu zxWii>b@tmJas)ldH{c_mwHHb;)Hi9BqO{3l)se9<0Pow^36)EG<};lw$lM2d4M zh}iJs$G*z2p^v@gVew06?rWv5p=UBNmC~n_>aJyQweLJ@fBdQ#;FWMzrxhDhnq-7c_E9@)gHOLqFMUM> zBCKb|^3%18+sC5p&TL>2F1`Vkm3j#i5snC^4k$m@73oN#B3TTxIAMLWXkq%bjR;zX z?|k4qO5Od8($O{aJ+4Sq{&aKWj$U024O{l~CP3iq2?%I?!Q1xJvPs5rQc5`FA;R3z zVN)qm#8R^3*nI~tEPJ*tImaFM)Pl_sdnxoF|EXO}-H@7xWRirCOm>!`xu76pdC0f> zl>ZPge7PTK1!0Im_r-kiY1y9|E-K;}uGfL423ckAFX+|C@w_fUxK9wgX_o-(44oD*WAa0!;YZT>umQ<^6>K6aOE( z3#8Ml5y?x1+%7}x3}ftYpDTmzEoh-T$Ut9qfY>2)?--s@cUaul1OfZfZ1V)#!IqH) zDcEmev}IEz&Se|cZ5N9muVsh?oZ~OAX|1K#p{Sxd8xicxsv`H+>g2XSYXoINRbP?& zdDg-E5au9Dqd=OJib7i7Jm=nApd0h$>|Irm<^?4iYG&m$JMX-q=h+LsTldu;ukO~ln^70lh3&8svzmd zIJB0|9g4c0zcR5`n-aj9a%wJqBOwgLNgsbz7-GKg)~Olt2_dByZj9+?50sBy|4wWS z-jH(=5o*&L$GX?fW#EEVY3z|YX4)L9kX$RLeG=Jbn-y&}O{s1Q$#5MDEu~PS9^FUupRm-c$SE=5voY`g4yMxe)jN?{Yy&QVdw-M+x*#NB-eX{vgofg`jN_Fe5t z6|Us`hTtvQ$KSwHV4U?m9U54WJnTJ$UWa3FXQCh_)iOq;`}$r@3|QDb62h9nyZ+Eh)zEGf{XR-+ zarWLmMQyB7o8nQ*Y2LPD-q6?E)ARv|-ly=UBC|XrJWLSGcnRH!B}V7+aumgevaJ~X zZ8DWQJ+V9$*p2X0Hak4;_Ofbf*U*Ki?4&yd1RqN(h2GCiyp4OF#dQXleG*7_CJwzx z4BB;R!N9{m3eFX*iDcOzN|`ZVKWduu2ht%NYo!I{Zlv*5)0oD8-vrl+iFtb6iwoCX z>(u#B4w^ zNMD`eJ=;R+hXHTi4|A1+vky>qW}<82Gb>dMtDvXotcjjfg$4EDVan<4d*<7iSb-ma zTCbNk52N#XhAk#>E%##ALme8j%8HCJL}(Frjv!|GSF_+unI08x&=f;Kt-fsMJZ{J zjwPf!cZprjvy0#J;>?_x|EvGZnfbk7*nOTmuKViyihcFKK%3?Y`xOEL0vfQ+U1I_Q z;zI%gqL&n8z?0dpUp#?-l)gHa0R#kMe7HZtkYZ2}0l~u^@LhG&knAl?Xu8wVlkpP> zGGN=-V@F@EX`uIw>ov+)5(-L^t2!E;k8b%{vme(-OHZS6OFnE{ zZAY%TAFhTYZ$~`dat{-2+(0qtK&7E0i!FWc!ymx;!SEu5+k}~ju*V!bUgoz>t6hXu zLi*J9#AnE@DXeL&iL$9a5uSE{AOz!--)>>A3xI(+nPLhO1fPmrS14cvl|&rSl4u@d zl8d{Wh8}ZqZkYvn=rkp+TGCoZd8+X%jaqx|9jbHs@kwzDDM^y?pu4yg;O|XHJ|cF} zXCxLn4CoTVZfF&>4S|AI63`KE{cMKl>N^b>-aWtWOTH@??S!C07(&gXdl6#s=^!13 zJr$d;*Kw;18Ii<4Tsjcah?4w#r8eDu=|pNyVGcxuT-st@H<3;kx^k-as5_Y1BbW?8&F}WYk|Kf$jYgj_p@R`k_xE8KN3uws-6}+-5hv3wb9_34an)cn zSqHa=DbdsTjMJ=k*t5X3XD216tWf)Y#0~+7T$$wj(HuiOW{=xl5RX?6 zsrapBBCPkPYNA^Ru)J-yp=o1!IA9J9Xy)AJp3W)Gtq4{4LY5qP zb{%7)=+Q0+BoUTw4)SiC4v$m#8_~krlP#fmk8SK730=uYkXD16t8vi~@R!nynrLK) z$SQ7Yu%c~)5baw#4M`(zg;ziK4P;j1gq)W_H0NZ#paF$J!4%vmw>KU{p#0TMy8`*R z?bvtNyU(It+Ph&~gpT__FV2?m%%o^}drl$VYpX?LaJ-*$M~=s5fR6a$wy(%;7NU^# z00%pN&PTFchxm<6?9H|w3|-<1N21{B*)3ZkwCtQ6LGZj(I1VECO+CgC!rJJC!?Uq= zv^P5HGGB-JU|sqRw78kYXkfpyFEiwp=uEvV;zP z(4hgtII$-|1$keAe1n+Mq+FxjKtDOwV=jhC@?sgyh$n7Q7Yo#eX=)nceU34r>(YC( zzef;3gr?rrX7V^W0CT;^V=1hLY?o$EXpV~MY&~(+RlS7>ePfqwH;zer4NsM#(i%qe zm9{Pa76Q{ZhxfFvoU*s|oRcz1wz$NT-NHjN>#?AEDtPO77_zTp7g&^UM>P%hqnqUm zK{n#I7~Z;mZ@{O}BJ3j9@-&z{k{Qj~1`R>YMP8!rNQ1VKI)dDS-1=Z!JS2EPyfFPY zgb;mmCqTIwKR?sBkV(CTt@c&0?D>2P7)nPd($Vr`A{4)Qm|$UgF@y~*;k!Mbr>qRn z`2B8;AFO)=Pr9LRt_1zYo23_>^Lg^ht7$|oM3n?ex5U2Txf9$p{7(KGmL?vI#=bJV zPZD`0Tifr}tf9p6r4pgM@j1($2V*stjeCo^?%RfDiA-3KQNi#0C*13?%4e3^kaLHT zSNV1%*`%A~K?~GPc$lT{QH8v+PsI{5CGW>QixT=wKmJ@&95-+1{#B?76ZxVIfhYky z*8bw${V`9QR(OmpjzNktiitV%({n&n7Pf>hq8~Duper2)@Pv>V4vc=yRCPE;SZN+j z7_V|j4%KHp#4}&74BO|Nh0O-(XlJ5BBa%oC*JE!_{F}8~Qt?@9$HXGgVZqet%A)j~ z%Y#ez|Duj0M_*?`4XiL#9a#3iz@HfYMxfBb5=mf1*~Wc(h3wx^CVuHZWp1x5S4yra z*}g<;&wnO&F;^eoBN8P!HYRBjoW2B6`suBTY#stL{mlZyb|fAVLp@CGNCGLo%i~>@ zTE#CQ%=_=W|KEAAuhW4XqYlH9{x>f{%AOs^CH7NZO8@&XaJQr6AJUc>+L8Zz-+6a{ z{QvE^`!5%F0{*?+Pp3F_y(G3NH=}wU+rMRh()*5}D>weFEa3RRA=?FbVwO7I@F8#d z9}F+m@myy7s_ySUl>5E7{2(R$|5@Ds|G*_555ou)CBg9BjHw;|_VJAZoDR$+)u2!g zMA`gGM}^%|RPP%|)5&}var`Dv7BAuYnXG(yO^XKbU&^8!5PN~QX2KE6Z}J5CBS-E7 zvxoi)zr(tKhrf+!4pK^7BJXc#pUXDdawuM@s=GB`9I9K~>f+OqtlS-@!h~auDvMmF zh~s0WQr4s5#{>PoDefu#(@}yMc-ePjceHGG@oq`ht)AQ{SY@R?W&7mbgA<)~=&KKv z>_^g>>LAUB9@RIN8)j_j?%`nI{O@VQy+32_P2BNsTJ!Us*u(y~%c=%Ut zXz!Ar`ZY7^b}dGrWY$CJyRf&-*eLJ%E!ui62OQH3^$e{A)~7<3>aH$?-1gv^*jzN~4uiduMb^c?h7!$<~aN{{C75dMLPw|NRahN>r zR5D(fy<~ZfYtE^nD!m`_9RKaCE1N}Aan`e9;Xf%=7{9{NyU^wAf1g%M=vAFF)x zxWgWCZX)M@`um|41}wE&qL%9KT4%ObZGBb`N!5{f}wRdzM z_jCi)7XPqpEl8<}+n|*7Fj=%)3c%0rqrAF_O9w%bwpS}A(dI(Ptio2a#^Dc-_$503 z1OU&=K%vAUW1nQ#6O+|JufB>pbPWxo|LV&Pd)^6C;l%N%aEf!`kC8E(e&IrF?LUOD z6#`fqc{*0*zLgOOJ4FE(0k~*44zV}{U!tSPWB|b0`lR94<({gwz{w`=KA6*`;&#pk zmedCM-S+5H?Dzn>5)*Y5Pzys&-L>2A`-eQ`mGBBYxnRO|cQDbed$Ov(c=*mbviI`X zy%xChSRYXKDWjANu{PAFCjU9vq>4T2tCmx|oN5A0^=<1=_5Rb6a49jFdjNGl9rVeq zcd#9nT;B*%@H)9t(X=Ps*N4+SLrgdtpw~U*H_oYCYaRi(?7uWVMMJK*Hy;!Iwnh;v;(n+0Fh0>A zdyl2RE+Z@kOY*eA#=x$6@{+ijyjHzrX;ElCYu`PAZ_5wyW+T zZ&tmwbS_q`hl2T2Tc7Pm+$-v^CjZV&J?ws`4!uuTwLL^P=-!9@ALLb9WfOnj(b@Ff zFqMb2_OYs=}MIZ5kjkjwM_G@$)B24ZyqpzXb+5L<9oPDFK5@XkFSjDgByOSUu)W=uP8}!z zMalm287`tkeQXzA>;JsbC3+EpqGEmm1uyp zJ^C1ZMywX}kuIB`vXGo^|GWy)_QuQS?R&x07XiYn;YB?1G^}J~P$vZYW8Tjp=aE-_ z+0x*HA%Uhs1BA0gTG362(LE#&0WFadwEdN;Z|+J@R!ek(_=N=B`AMFnGDKC98^X?h zXyR~VIu4our6U%(WjirJ{_U3SJmEN@jL@^$+w?es%T)0*ZE*^*?*z{I#}61nZ4imk zEVoX*Sp?}(mS#os>(c%9V{f(uW%PUo3@Zc>#&cG4g$ROspAwB4pHFpuA5I3|Z0 z#hZuY&Igt@NwGhA3i_OF-x@^fOZJ%pEwjt^SZ-Hl*Bo&zemmSQG*?<#Igu{vZ{hSs z3dqTIHsVb%VU0RS;TrJ@u^jP=(Rm#=iS2vz6IJrff|uco_-M#)mkacnTszYI77>K) z^Ben7tmvHC99cZunILpb$ayPKxLe8|_Q|>`wW%Q(&fg&iK%19Iig=D#xs^I%9({xu zgSy;`a_zOHF~FbNsh%l(m5!j2FmeCb|J;yZjKG`#M9NJN)njxj(cZbctE?I^B0GJy z0_hnT*!819*YlL|uQE%*9ejOlqOnN#K4}Gn102O6Cq1%<; zC7u(Gk2)6@G7GvGM0X(ld~GH5Ks_luEpNhG_5}fViu(XeeaaX>yE`V7I=(}OW!@#} z>%`x_W;d<_H+joBmuNcztadzjKKzzJ8;HMKs(wbS9k=0+C2Xr>_Jma$OTwc|90IPRYx8 zM3_?mfy4j-t_#KiGA!?|;AP~bD7lIQ%{2$fuN}tP8D$TrYA_rU6UX2@fa2qK$Er#XCjU@-Ht=4(L2#L2oPxn@enH?hM3qoeAvaUW=b35j2dV$XGY7n z@vMs=kcBT#h2A=`O?~%PsZb(AF0FD171=}jgu1`N+Vs+-*B_T3FvfZHJ=WgT4x6Idk+r@ILNFh=&Vyxunj0OHP+m48Q3-DWGPhtutaYgDx zNYC$~`BwC!xCPs8+JRz=q2XeA*Tq@~kiN=HU|P0wax(_&MQA~FZu6J`tv@H2d?5-2j0d1q7&P{y zB<%`b9y0^Nt2}ojO@g1V6Xd{EU9fQpG zn^O&Ac^PmIbMpyp7dv^_x<|BL|vwV$2R&BZm9* zRt1@S-h#}J^5OIIpG8qY|Z+w^vDG33&P240(GdJxE6%aAIp| z(a2k=i*n(LH*gg9?uO$%ivL6-D=S$)Djm^5@ z9CKwiG`{vi4xxh35huq>b~)2p(UL)RmUpr7pNk`R9di zispG~zVrJt)MGWnc2q^CFU9Keg5t#d`zK}Up9aaAA1wABp!ZJ2rh+sDH;nc8ssih= zqVWRgGbs5kNdy2usd*-%3Q;B5&H?8?_p2Hl?|w37*uRutX_U=xS4?rbb|p;)kuoEC zaWv>`{nZysKW3A?`z1gwV6;``kGlgs$3WNhye5tsmqvrqa;*j3}Rt`8ZM2#%6jF=1)$dmKsS_enp%?sX@kKs@MKv-VAc zRu4y0dBo!jRk24+i6c~5qISND$dHOY=-|!B6w8KlY*>62PmlzL{GKkr#lkYs5-3aO z!G?J4U$~sM*0HSO)*Xr9m}XpVg*1vq-uIjM^!KM5lw9}IoY(+cV$ftOZl$cHam(O3 zwFJ|_b`GCIL_~;dx>6Hh6+sFBWv2sr;$+7<;*v+wp`*u}&O^^oh`k87+Y zG%bDQJTMu>GW2rdnQL&?;lozXh?!#AY27^#w;kzZ6W1F6Co+Odys;{wsTZ|oJ4zmV z@L|7m zlipB8dEcda_B>^jtnkUB!1mKl%^d!&J@(ybcGze5ZS!@0%23r%J!BbW3IPwRS5?rD$9)1W-`GAn{qlAk6&ua%wI|x@ zgwMkc%+;E%B{-p~4zx3fR!kZRZUQp3CJ`5hV&Wi;qJZ&(S=lefv#Cx=ENHW78+H7~ zj+K6~PJDOj<_CGr^32ENjJMQ+V3IB0p)zjpj&*)e;(JXJ`%eSrTuzpeN+|=yjKdR+ zeGAm_UczDxI9EKZP!l#qQNW;aHGll3s)J#j&z(25SFxtWOnPx?#*Ot~c4q97?(c6Z zs|l9S)ylUAK4E7!ZB?bcgXB5Weu9x%|8vgcdq}zcDwjhj$2SKAsvCs`GEq@-q%0 zC6esxtDXoj)XJaQP`@7+75`ypyPS67G<*po}j*K=ovfdOQ$92a^2bA?CYYGI|6Z-m5ay*mKA^5+5)XvL)fzK2iff zE3%CbHGn`sN6o5pe=8J0q+13h4Qcf8OPjnI0n&%e@tS)t^B;NHmz7eCg!HRsBAp%cI9yUw}{mW0XKhYQmWEPEnYw`e#%gD9qSe2?3>vHq; zI(yW+tPt9l%qVt-boZ^Q9GW=3yiuVI?F&~8|7&i3lW#RGmOf6n;GQ(kUo{tINuQ)x zVn=S6xgu2OsuX+9$)=~6V7F3HflYQ4DBOujd1)lvG@+UJ$y05x!=iY@l~ZPmi}c*XP=6!?W|=< z^3&4ds*ot$jXZO4KYiF+ZtZD<054cWa_zq3IZJ?!uH>+Mt4!`F7oR2n-Gy7`GxiuB~T95EG9wo`wuch{Q=cX_JJ<5wKEhsABBB&tLvNCxp0 zP)V@RwPm4`qt~L`1W4ezx+<`}(?`tBEq=0PEHH@&GwKZ{f{Dznp0m$YsO}PhSK)td zOrgZ)*Yv3-K%?(79qHjFAVnhp)FGJ3uL$|yv|Db>N;w8`dn3!du?ljUpeK_MCP^)O z?hLu~fnF7S2YL>Xffy%tbbUK%g=YTC1@?vV2l=Ehwu4fcSKDRYCYdNq5=Lek zFkBZb*0g%XFFQVF*~eI!+q)=kCNFzl;9>fDpa@X~uP9e*hcE-xbUb1A@pvfzFHP4l%EUpIPGMC4>ykFo*9 zo!?@X9Vi5718~!0AgId9+nwK%YYdjU@SjXlC zl8;wSZfBsEd;)3Fm3W74Z}ap@rDr#r$D2XBgrXlNbO3z(M{m@Te1NwzEKEhF2;=tL zD^Gh75;>-9z|(0d@!-h`T$Su}-O(}-ax9|b#HxuoYLFGyv3P{b=jDx7do2P%hj9MUw+XH|D-S7z6N?@r}jnmhCbu+NWx1x zZev{1)a`}js>^>1-aqN%Ef=__D@yn>>+^n|uvjBnpHm?Z_I6Z`{`{v>f)kU8ANhG- zU?&sX^&6Ed>t&Uy3oD~7$LvbM+u6$uICKg##@MdEO%Shtd9*QhfObf1S_x>!hp8PT zSBtsbs;xXj4)pQY=pit$?Pq%H5Tpph`}2AP&-om=b=X?5=S+P{`}nw$s>@&8RZ{U9 zkTB3C`jtMseYf87`c>*Px2qi&mSDbW-RqQ;Crr;Vlhq!Ub?(e9y9q7m+eyhzcXQLDIR8_+e&nyZ6Jx>#-7R(#QR(XQETz zy_6PKE)xkI_5=O&ylS~CGLr+n9-_7qoup-qX1zXFSK$EneI&h3lN5KwXKQCM<+g&- z@tsv?uhWlyLzP#jYo8Fm{vtVQ6Cp{p++Q-`zSq7{d9ZH?QFQC)#o_uGfGL_^EmfOV zCf~=G+dP}C)_;q(PpSOJ!aR(B*|CZHFQ){MxYtM&Y3Auw5d5 zGP?dZo5$$xbMYvK=%6xEgj1SLdvME{O>V^bUROBIQ3qGS)sYThieKrHQmGR1L?nYfxtBfj`0ZGDr&&8X6mzs(dQ zboN&e)V1836*|{^#>&M-8sD%c-g54ri^ZboB5?^R_?urql7#*`|62^7T5(>MDr@b* zSGf#i>`Xqh*RH&xI9gz%XIKDOru;;ZS4S*ZJNE>{qc+`IxvUi;@vfqspcG?L%jw z2g`f=!-g$<({>Tq(2C|$cQP`v%kbV#cm7T>y<1KFH`ay1mBWDHdmQP=nv`wVG;dV` zUV06q!@hpQ%51e+aOVN-0r~fUt?Lg8?1@cVD}c5yQOYM``xwQ8#9he10p&7N1bhH{0!=wI^l&xq-* z{9x-oEq!S6RrC~|fRoW&{};{XHykC6%he!FTK9#M2zjM4f^$5i?!JU#t)8C{KcF0d zm0(TwClzt*Qqr2g^OoWl`nWZWF^Y@>N%U5`)8X;gYepenIk@Fa=HX}Dl)UiwPy?3C z-aC>z8ds9d5;Bz~)uO)`v}3>M$Z@pz?=tZO%{GPtvfYcaeq>%b#>UDj-=>XEK1~5y z%oJ==zeP5M3S#r1Q!~C@Q5q0%3}d*#@A$V`v4PHXys-&fGF%MjFFAL}to`oo>$^M9 zVX!<|U#yqrzx2@jOW>L@K}F-buy@NME%5)N31RzzeMZA5wF*@j7Eq|6BV9^|*fSQg(mC~337#e~(0*u0$_#-stGel-lJWA?UXi51( z_AtBY!L_YV2Gn3bp8EGQcYDl*x$K+Tm$#@wmwO4@74_YdeaTfCQIm?%a>$5e9?WiB z2Zq($HdG)>&S!bAgWF-IjV0h{#QXp)*V2W`4T~44tdCE$380CIiP`SJAc8fk>T8*MwipF_6^RCv z_}{jbJo3jFk4mnvohaM|)a@8xrnq>MZN?X;d^s^Rx#yDnCu`IGp*aE9ys!7t!k3?% z=Qm1373aLaPpuZqA6KNuy|UNdCmn)rdmeF?0`ney-=ZoR4p~Xq%ul1l^x%Ena*ibNur%m{QV}EO{;dFuENGQ zGQBu|GLhRFov(7EK~C{$_ixNtJo)y}1*H==7EHZ&_(GuZTb+K%@RNL6-dN2k0fUkC zk+RU!wGu*$uYp2#kX`lfp0hnTjR_!QqAp;?Po?kKbE>-ik7cVHJLK^(N13nN3y?UY zlbHi#m-Anq#-99y28F|1?DRuIH|RagMO}{G!A`1;06$ep9pt3(GM0Yr7dv0M`kk$K z&so)W*Tw7AMcr!5M9C}%1c0s~rD7QxLmHNiER2}j&QBloDb1T~i#Ke34L-*L<}egSs7kw~whVRu^)EM-+r28t}~74ab>dP~8bB-eHWN1_uWj z<4-8ZY#=Hgeey9EsBXLEVY%7P=NqG3r_;ZfgMc%`op}Jeo)8*{j7a3?y+0u$WN!O( zM6zW$gzp^ZT;f73l@EH9oR3jE$bOut9aT29 zi(~f11F)nlAvC!-=A&W*st#B$v~#uDdtsubRe^)o6JD5T!2ULI&vCwep~g+zfO8O22M$CuJD|yU&#BJ*T zn-4t***jUGi}XZdF*_j@L1k@#b0zrXFCG=_I8Tj8X3AzVlqZ3O)(^X;KU%JUSQlW{ z;9gS%gO-RM=o8Jbj(;xuQTFI#W4%`r*O{W}kXW_j{yXmYiKCLds8g=06~OVp9wSeb z1Ow?JFP_BB<@9jlgc!U8=r0R`Ap(ajm{!B@x-xR~=8{c?yaPu}ir1fg3jQYUWzOV1 zXz}hBs?>wlcIF7ogm9(TbAz7JqGZ(X-<_Ql9-|dYCo8#1fyS8n1aZ~v@YUe$>8^?# zr8?gk#XH{PySe?4T}_^MW|ar$0sZ=u>A0M0FDF@K;&XytgLaFC)_X2}iz}5A{m)BU zbZ;xE)g%w~@@*I7dB4@YYGFLlD!T&v?6`ZsdH;`uxTmXnPpRom7efrH8Kt5M>Arow zp4NP~VT33jSieWQpA)5W*sp@(97FmNM;?|`=F7_^=T>U_MF2CxtP2caY1!nP%wcGC2z;X2{$Ey(2T+Bu&;Sf zMS}e&GBh_Q^9Hun?4Fx6k8a0{RLq^Q*vcoU=3TEZDy#TYg-N|Y^{Vmdm zFJP$32An+dtf!;7qsy9Q$5PXBT6fF@uOb4rT%su?3x+EsBtEB68E&kc5hqMEmk_0F z{>KwJ{2r)x_zsgE{Zz15p>og>etk9;kX6mN4H{b7{WT7szZ{9G?tgF!uqs_0DIz)4 zE2ZJ2+I_9sQsOfWt3cwBZQ}hnDyYu$Z%XZMR92r9%kxwUz3M~bbi>@^n75}|^2)}_ z7dNZ=qxx5{i#ynDzk8UcV|M+|P7Z*3#LqM&$1$+>&?dl-_w%QEOGiwu_3#u`T0y-l zJBkW_KemPZcB5hx=E$FU4y;Iba(a3s!||FgYWuG9s`X9jO`jRYL9Y0)etaR683a zr#MT!?9usua4=fZCKz*M9eFgea}ok5_q83!MJ>-txS>iLpm7#gu%~t|6XF+AhRS=t zO6hPpz9Cq!dsg(*Aq|-6uv$r$w0}OhhKe66@0>}``%CQ;*)h!7b0TMOT|>TY>6l<8 zCtM6*wi`H`WmSF+au^5cJU!Z&Xbe4|+zCb21qilWgtur{jVyeh@>V+f0e}n6+!O8O ze#QHs)T~R1e4w(ti@NwsEHd$4hE zEFwB7Twdn|*Y`>7faK$LY)_{*w?3r zRntiFv?SUN`z$22!kVoTf}E0E?({9LnM+%h!>rhEE(xRw=7v0bSZ21j=TYedFuHZr z$x_68f}P`7Uk`5D=Qa%tgw7_DY|#Nmm=Bk26vkf3bMN-PTafUHY6F9z*Z1Ob8Kv zy#viB@=Iz={=7A(oEzmpiVP`ef7D=Q3Wo*4?W25sliKuBKl}ZB5gc)zQIZ#guk;x5 z18&XHK;Q)$iuoMAql*12UUnXGP-pG${CmB?rpNG(hO63Phpa4760wav>toT1h1ZdK zCuL*Ov*XITWh%O|Pzf-HDd6@I;WVq~Y~#s;0XkY*|C6P(0O0QcfkEN#s|tp=XsO%i zS9+J16tM)6vYlUHEOtEeDKt=iFj#?j=R=;mkB>_yxN9M*p6!=t-k)?HfOD7*5<$n~ z1&N7o-t0p#aTkD--BgV}f3JU8w7(V!7}@(2lqpyKh8mQZ^l-4au&^Ht1NGQ>NpEi= z_^0I{wE_J~bkXG74Ld47R23C}6QgE2Dx=o)tRuPvB6dnkZlb#IdR7?lYf@nS6f7zV zUs@hHjg5_IH;gE*Z=S8L-r6$r$kfK00a)XAgG{YQHQr9Z^{mgLYWZaz=;9d_Ypz^k z`J)7RY_m2k?8rO#AD4VZf<-lJexpb$C8 z+lSuU?UMa@KJuPf`E*Far+FlQ*vqKQ_-@lf-0@4vsZZUmMe{quzB9J(herG*Lbt}r z`48@M#Wqsr&~61iSALIPTq`E{TU4Mf+hs8gYNuJ@nu7_Hh0fItpOl%KOy-b{=>B4{ z#*tV*j{f@YvBr}GKX%f1r{mYOFellH+UvUs=3dLwZCUvv1tXpwjqjRIiiCX?$d`quCd&nA}y!g zYFvgz0R2yjmanZEy6U=v{6-Y0#=nXbkE}~PNc^=*hl}cF-UhA;Lc%&P7cKU?u|=~d;nX8!n#_=Xwbqrlh%17vqb2};wr zFj@c117GCu-hu*34`0Bb_011=Z^tGvu55FkUb~la+c_emHIM=-qb4=k$I89B2$v?- zJJl{2<}HELzbNRUzy2b2z^lq%J?5#1^lS)_Z@u!zUB%)Q9}N4gzgc1^Tu>6ev4M$< z;pL3)RH*rjMWCm2^Re58%a&SJ8E_DsHc7UD`P%5C?79zaYO+Z+Wj#e0Rz=lNYRAwN zY%_xyjrUC_-Wg?qn}hH0cji)7UNBIL^GfquiRFh8_XMIRtk;)Mvar;lTz&Y+;qm|7l zzGxn<;3CGoR+e%lR%LMhE;RPkjd|#sqIml_c^mfyKr)54uV(^?XFov<8jpXpKL5qa z#=#YdLS?_JC3n>_zAB}`N?Lz^@gqbZnxxrO+1D6~+4KtR+Lt$}{4W1cVc@-m8!m#^ z3kCv@+VwNAuqG|zjN=c6aDE$Z2Tr1we%m?zJW#T>PiJdD9wm20)Z*LA7;@UA`|Glf z1*G421+TvR^2W>5mM~p@S*l$}E@83bcHi&xxCQYI)p%wxoR-LD)&MFlUE6fZXU%*5 z7*17nSixi4V&w?}0$!*Re<$XEjxe3AK8k~==Q>f-(Mg~7S)Q60>)V&)Jokb&P8C~q zS*S_QPqYc3mQ2pcIXaUOu7y84p=6)FD*(E1K{-)r|pfbt`a*SE_34 z14^h>Zim=|3?OA_LV^jJi->nZP0?LEqbH(b5no2|_YG-lFfIg)44it3Icv@7iU`CrQL8U5A$PkO0OOGH*Rt-xnG za3{%rl#wT2UI1!9`?1{T=Qmc}yTWI8YZ{!Ue{$dww(Q9EFeEcgUt?Q!w zL9oQfeWWUC$@X25T=H>+_Y=jfzQ+D2C`b-_#>y9(o9x7P6qX~JXh}UUsEHYQO|F%7 zB3&snp*TcWE!EGQFyotJx(*-%xaxDq1Q;)hPh}&e4JY2Ob_X+`yR&~pO2(aCZ8ty zR3!i7Oy#R+A>{E~tikP`D%kUcYer=wfZ1T=)zYe>-?5YESwYL0ICtRca7Z?L1R|&PCW6`Q}H|Ct;`Bsn)dqc&7=X0UC!&}^I^Q~`mjYEB} zyIG%3wx54%$g}KxfMoBpJG?pK9`lK#)4uIfp5F~-sUhKSKY=T!Wm^nr(_!Nc=Ff0; zQqge8!?tf>Y#|imqnBg|7=OkOuGAg+J!BrWzpw#RNgj@!okrJj94dUy1v;yo=aiVb zcaePEH+>SrVKC?2>L1WaL7l3pyvV0^L))T%--xVR2c2q>!5M~tyB2AjbLrzB z(-m=cE+3pfDnqXiAE4Kk$zoE(s%3gc54N7KE|6gKqKJIW_J<3zI-a*2r* z6F=giw@o#v;P&sMQrj0`cl_QyHsE&zEEc|I{0@a|rA1XIE(bEZ4Tyv$?w2^58&{0TAXH`G^JzmQPX%q; zHacRbYQFR?<@ZC)g^npMH0Q|X$|O!uowt=Rf!|w>M(2%?@xFF)4o2`pOpMdPzI4{> z-g9(`5a^TG<25+%H9FEEe!<}Qs7cc6`~$tl$6T<@SxC~rZ14Gx?2`lcUlF6*8FBU; zzgx(fNvlrmOaPJ0+`C4QcRhCL7g{Mxt})a%3i8xXC%Yhz9?C-~P8dwo|9~@9uJmK| z6(&u$IBzS`y(%;-f3XnD^&B^j!hzzp5Bq-r>3| zA%*fJ{UQKQ!HZ@UxzL;v)aMQSU|KOtnLL@um8TN{DyxExY789y8SBJT-Me5A@-L@eQxf+tX@E5}Y-MDpp|Bn!@X3|Yf ztv!^N;I3DE@@5Mq?8qNqZX9i2iYZ30M0+82I?|=TLO|747%oXVGR(X!Ay#E~3L#E# zK|2o0Fr;@VGA{k>f8-97i+Sx>N71#-AR%gg50JMJCx|%cL3AkVv7&_sYBO+b79xKD1EQ(9w68dKF{gz++nB54Ot~5d+}oSfQAe+RnJuQmxVC+0p}ak z!+3$cWU+Eni)MRLpf)oy_>Xq{5~@X;VK?nfqMOiX)(;Cuhl;9{ZFTXug5(x+WC^-q zM&&K}Z(QAG>its!+;)U*7e03PzR;XGATEfNDU$dpt54qsJ8~TEd`n!wZdD^7_xvoz zW9~ovQz9f{UK!OQ-gPT=9;C$Fk8jM#WVmK@Ulanh7uecS`|`U%@{X1DkEbME3?Rjt zEt04Nd&4~tk@vgt4l!Ua@Q`B`c| zu*Gj6?EAHF_gZk~+D)Ly68`RX8sP;Y8pT;4c%Uq(pQsY&Il$9$4d5j`1cc^1V2*pv zJ3f-u#J?Js4C62TvyLD|^c!I|m^@ZbZtxD4bTh@Fv4_g)*Wm zL(amW;)HS!?qcMGXa_wOjqx#sOueh|k^5!PG9hohhV+HG25~~<%B`Gqe#$e{)?$`j zOJW!X?X3^=!`K=~N?%-W8+w&Tj)fqO5C;f5;711>Sn=sZnMVRlUMkcP1hrSt^lh8x zv~v)`?ROTvm}VPRIHOVx(BmO{f}VnqHIq$KM?5*lj3CV7KR{^FLZ5j$Z>4T`?AhV& zn(*Je3Y+B`e&r{12t}MHcXSXf7Y!jh7r2LN@;PI!ItUM4n6KnPZ7Ks@P)-ZvfjqnP zzGQ7~Xmg50Jbq~Hc$Si=gP3(y4c>NA8HJ;nrS?!M)`QCT2AvCA3H zxW~Z++I=OL_V;qxt5BwNLx$rCbR~;1$(ab#)aShS7PiPG`P5M!F`&YEd!pAaYVqx& z;sn;04D;tD)8Ono3->P>3tI1uJVk3jp7O<`HFiUmrapH{x8*n7vxg7Y5%5+DIdT9nb2)xWn<<1i|c+Z znc2Q<99}E>v;LGjd{-5yUo?cv`zSx3RJ*g6#m0qiSTD*b2XaQ^#3+AX$6x7r&R93p z0}AMuK_l*_9q*6TR{y$x1&e@Dug`Yo=b#MFBtbR{zr@cOcfQaQ0Vy@^rm+wBL@IZ+ zRgU&fLmaM$gu8!~YES^LRErpm%SS>Eu?@gwAE=+)^cisUe-U+)l6Sxrw%6HYS7=qv zea3;~%u?--)$ED0E2){*N`wbjzw@D{DAL5byX5Bn~l$6gu?Os@i_Pv0wE5XEVxqKpFL^8L=HV2%T6 z7!X#BrFX5FQjXZMF%=fe$0?IPk5Y6~w72ULoQKo%+<>g}$6@b-U6tUs>_<4g9tOBg zySq)ngWff-PuM!GaN$AzCT%O=p-S-JS|HF@&h;GF*rH@u$axLdi;gSGlZh*GbMHxu z1~z>98sDim>uu}EdPaHB# z{qOa3*wSeVcIAtgvKAWQcyg_1f^i7|JNtqGmzh^&Fre+@Cc}u%NwEQM!5u%h8mfR= zWWA82=Qz+%$txM;p8Zn5h`r;7jFy#g_+E(@LKTQR%Pt%-QP(nFo{CE2&&~`uqj5V} zVX!^6_YU^1O9nqU1if7ee~Lo|ZZYnSpa$i@ej9!_pdsJ&3hQCj1NeK~gFrl1-TKyp z6qO_TZHn5@KRFHb8@pxhwz^ztM|IjltzGzPr2z`xyb z_B_nTlQl(+0@vEOyBdjlP&N{-!g-Xg4(b?je+sApZY?_Q>m(P7_;AZZovXCmtn(Zw zq+YpqPj^1cgR-%cb_~jY7eT?hR~ZY?NoJTI-1-A^7P?93VCz#gOTuJyfmbjWQCBbV zAqnSy;&`Fjyq7Oo8T@rRP^a8>g@bOkyAvCEiB)1?1#IM5$R_mc!`Ko zyR-O(eB0qd6~D}SF+X^`C0HC8v8%cs+1@zYZ9g0iboQc`V^CipJGPgDfMm>gO)wqu z{1nsP_~y6OFvAFL$k>hxa#rPXx7TnPr=8pxW$Y0-EVM~nPhb1t&c@Ij0$785`NDv;pC4*z&BBB4@3xx-5|J{q@DViNa zZp?omp$_9RX{yZY;E767Q3{6Rwa8GB-td1mO2NCo_~?dQdN$+F3r74}5dKW{m2=WA~?c%@#M zJUWK#2fRXt?>;jcm+JXVDWD!lQ+p#ezG%M=&5;th|9`b}K}=1wliij!RT%v5LTGfLbDo5@xDlaLKb8s3HWZ z6|5mVg9=y>C|e}U>*b~W6Z)Hfz@2yRx!=9tchC3EkzQ_ANTiBs;Nq|WOvlSUEB);K zk7{KAMa4&LHlDaYYSr;9{6B>Q6DBO}_|My^(mm4V%1|!;MC0uE;Zwvrk|C?kyoDPi zxOatlZjw*H>0Dvg5F@3(led^MK?V3D^3bGw0unvf=yoj1IJd!QlSUThf41J@VtA7OET?DalI#jgV-}Gnz zv7#vz4wVD=Yds+Gndm#j3+*_a!DcU;i51so!0Koowm28}#`)dI2O3b8vrH{vA!AmW z+SYQxXWjoIv7Cek@FONS@E<(%u%#~KA>Se|kP!UJ1l`_kjLBuF9oif`wp9P%$UVwI zh7%NVrqgW8_3mfLI#C@hm$;2kIht9eir5VI5x#85!kgmmqbIH+$Awg4DW_n^z^Vz` zg}Z>L$blpbEE`{Ifbp1!8?TO@*WG4+cwbwN6bdDfJU+J#KUJLHuSNNVVifVo`Qbq$ zdBV0>IF1Y;{^&5X4F7Q!R6|+X?Rpi#230ZFpo6cXLEq^aCKqKOd&cmstBy+o^7iR* zAn#}OI=Xm{z;h5*rL`AkNQ2j8;Ml}ok1V)o@a3sPu;ZKczy3rHrxo^Me)-EM$ziUb zD+j`-F^Hk7p=?wp2%E(t^K#?&P$e(7q*R=_-xO}~%4Ka@8+zf)J&S(WU@fyPvfC-qMlKf5oP-q!v#I-5`ZO}e_H!Y{{|@(b$eQG#iwom9Ku#QK_70+ zJ#Jbnqv>qB@rtZspI0|oQ114w)J12S5)4f(38zh&{Nsth4S;Q=evVhH9M&f}fKg4c zF;J1!9)EXv*v&w(C~)jsz?S4wCM$;+=FR9hPe|sijLIOJoKap`fu*K|{+e`zE-orP zsg>Oc6>{OfJcGt0J!j2;bjZF@rKH*%Bsr150kJ7M!+HPek?jJ7lFw3J zTBs3qGUGY`fwf90D&8CK)2C|4gv_zm;w7u7Kjd4Ra#PT)8>W-!pgVh8s(mE&8mYT* z-6Dn1RNI<0v5g-hlVp|~+o$Am<{Ua9wFI%DA#k7IU!P5INr`#d8VC;YiTb=`Vh+4V zBoNo2bVjMd#i{DC2>6C7Ub-q3IyW>`Y$Q1*`f&Q!7hsyTV(%%|5W|_6pb-?9Y2DEZ z-Y;WbV#;s?YR%W^n%D7~HkQIETtC0NN9y-T)%ySqznfMv?wI%lZLTXy%A)L5Xj?K3 zeIe;09K31)MLC}5+&(C0x(Te5#wiO=Hvv05n^j4&dC5tKA!R3s}=7$XJ{93&&KqM~a6MFj+9 zToFY{YlJ0AMiCH@9KP?vavhhiiBAGuPF|jY`h(GmP5O#$jB?nD9P_uP}Nja)>P40E3d98 zFHe9Px!E6Z^!pd6f})JP>TX3PO=UG`<=;S|-`LxD+x&k5+uLb6x_KV9fg&F`Y~$o0 z=kDsXT#~Cu&5dp@Zk~`Zc{rItN#z+ThdM#EM?US2~+ zUd3KPMp;2aT}DGm*QFl;Q zl~LAER*lQ6%fW-ofh6`zO&dEDZ#v8EkfS-+FDHi&{0|oS-)WRU z>*MSIk^UF=Bfz}e9KC&PJRR0MLF@f599-`I#=e)$(f`i8nvJ}wouZ1PjH;Tdos6=w zvZ9Q-yqda9ro=@6wugI9?NVIHge-Uo!J!Wr^*| zI8lSqY^~Zz!|CT{aP(4wM$$iL4e+9sl_fm>GdzDR=v&?Bz{F^}mn9yQ44T(C1-;r+ zJ@3KXlPQbP>zf-w1<>DZ((w-SFUg^o@(GI;qQB}!+YI;ETA`Qq>l9?sUze>W0>@_f z@xYf%V_OR}v}%KeK+ahoG?c8cE@;I=3+MJ`Wwt8 zd7opveJcv5K?E$V8HbNwnSK<_sMjU%tDkxdWTxiA5g>_ynDcDSSwjU+&`1Xzb5EZE zhXRSA-2{}R;By&{?kNmqzYI^5oc~+~WKBPUu%F$8Cvv<`n1d~Iz9O}dVzZV_%>tWN zd_ep#rxF4Q&8e9Zam4^}&(c4O1h7@LU5-VSF*%uk`fmV*Vc&(3l6^?Ba0qV0a zA%)TH30#s87e{movM*~h-qG3HKqMg2U;80RyAUypF8s(E!&z{7d12w5&M zWIhLy43)tR6_07uSxL0(yRutfC54d#G# zj2K!+3>o}o4uI}F%g zVmx?asO)PQ)W_i&NG&OkTR%!b^q}u&SMYevH#klBb?d|*8!)yV0A&CwzA8xi9&7^o+$m7Vn`O6i;7~+3mg~e^s3T5oUOdv92F52TjPP|Y zNXOW=VQgj?+r#dINY(;^LIg#`(gfuF@GFhjB)Q@$P{fGvB9K~j#&p!p_%2|@sJv_H0w&>EU$1v zsacrq`k9Drju6357badWgd}ElVO`dnVo@GTaUjFaD~b`@5}0ileHLW6JVqWe&$@zV zR-8bRFlS@q=Hdu3iN-@Rct{I-oymDLBq@c5_TY&I#t2u$1&nJeMt_ZlDBfI;v_e0M znRq9Rr2im=n2=n7UFQ!ZDd?x+AtIxEnU01A4e&%F3v9p>-Gwm4zp>zETM$Lg3aq0| z*!7ufc#xr1Oh-Oswi&67Gk-Z!t7FcooU8Z+7C=4}3BZcVi$)-`FmF%hBHn~AVF`9% z3ADE%5_GA*&`f{q!{wXs&@?ryyQqUmZf86MzjkU+ORTIqMPT#3UJkr7XgVDrkI!Fg?MnOv752#44uCU=jtJ z5YdRLmBkC1p>CS6xCU77;HR$)@&UN5f*P8;98s+}`Pmb)f}ztf zSpAB0gslK$o4x_52~;;=PJA&Z7tSHcSRz>dvp9ogu#mw(+e<9hA*>~-xnCd}umV$H z>LNxsQ!xdm6fy)y8w;@>YmRd;X4QZ?_LWp@AI*7q_8emNEG!296eNbE0Oqd`^XJQ& zjtIXl#)QLhz$y#lsZnQVKXW(byjq4_IXedFxt?zh@`wyV;%dCebx*@% ztX1f95k!|m&}Ds--O=fReODLzniv*_rGyRFy%2$BsNl(TtsU?U3z4XZz?w-(H@qAm z_7~)zSc92Mh{-?jn8FJ`XVeCaGisk2&V#8 zGxk%sGNM4ZiX;3jjt<~^88KpJgwStWiSiL(p5kgs7D*w08cV_2hc#7<89YO{R>pZW zn^;3vyrfETd}b8-B*#?aUE4oB3ri!$$-np`eg*e59T6G7)>y1i4}quWY%p$`tFm6*};n#>>LII z_vRr2pE1owbpp;Eo2&a0f>R|tf~ATis8>h&&5$8f-nR@Yll2sNH&cmVchN?~TLAJ+ zjue4R9viwJc?he47|J+<_~=IonRS&I62`0@M8QK>AaI>!AY3X4eadu#Vv{7IxF0bQ zkx20GiF8I6w1wOm5XuHBDC#8D6rf6IE3ipe@krSbkw_pm;iVjLJcLu?2_n!rNq7jC zo+k+Nf>&6^HbhZ4l%SZs1@WbT_>v^~au-7SFTgUsBm|;klN5A69EN)lrqc$LPMBV^x?bZ{6Rb6HIV@LDyp82hW08&T~oK1utnAhHlUPIGbP}U^#7a$T? zG1@V(juRv~TdzVYi*TUu628JRKqARwi))f1T|$nLlAnglr#;i57%)$)OLT&2)U!h9 z_%$A)ekM@t*$SoVLp2R+3&Bgu6eOrN&K1%)2Lsl5M4}uI`H&>Ekk@iFjBo29G{Ozg z!(dJ9AqyL)13wfYhQJp#N@y%YDZL3o9=3&yXK%o<0=gF>8i6<$v<3iQ(S?8&ra{9}_$Hb{c`L3!+q1rt=!TKgXRJjY zQbnQ*m4+0UY6Q!UB+JFk7O=Lp#Q5Hc^O9tTRB5T<9V7IFmS%&%JBqa**f{5cWP<_+iE?bM} z0;?iOQ4^`)3#paCZP2mly9pKO&LynYy|nc;0Gk>eAWIwEeNpQhaGvI|AtVi1Et)iW zIm$}(sdzH$*bMAdpzXOIH1k?NWRG^S^H_i73CJF}^x`NlhZx|q1=&Ns>it(C7%M^> zh(SkBSwX;Ahg_1mD&z_VywiY`?}&1r{3$}ytW`+j&^|2jO?^UJ^i8GdJYdmt6ldOz z!t>B%RvYHp4RI}w3m&+7EJ0>^N;r|{JVNA%I4@TS(u_ooS5KUZCa26qj@mt44pMfH z7lzRY5uQ&B<^|<{AtKc!%V=l}d_)1v%0vmHnue1P^s1{6Q|C|2;aSpCIi-n_yukee zvPt%vFU#@d(Rw7_Gpy{OWd+#CENcxteo(&{QSO!wIg0_ibuc?|Xtw&rjfBv^N!=7e zVtx|5%%)?=!$@e32g>h{iNzDJu#W@~{#7hZk{M6T=LH=_NSGxa(}~G;NEjy03Kgda zp+Sp>tPsc$LhQ_M&431j&!=vfDLCSJa1AwyCE-4KSqTA?1jOM?&zt-h?!GU?mm-Vj zt{~}VCpPke4@hLza39|kglbE6IP4tVNBK9P)Xj3V* z9E*d_Q_zaoOA)(=*#ob>Abf2IpKt_;Z?Je2x|0+VZEAzCAf`Kb5xIH;ig&s&$zQ!y z3ydKTBnML=!~w(61&}oh>JgFCr=ZaA;iMKXfr>@(&Y3d%B_3lXA!BA-L^_osofV|U zZUmqhGsqANA{bOcf-te3w@DIu298R}uu#D(53dF!hn~GC!*hI*vKDP3ooJJdD-W?^ zFaiI53`SROfevq{oeoniYpa+oLFAI9qo`cETi5M;mwQRhPBTtWIrKK zGwjB|9veXyDXkk2c?m>Z@#6;^J2Y0#`C~pNW^xWG?H`mg>S-jLNtDKP!105T%bF)f zC&+({3xS=E-aNFvD!RAfC3!5 zrV@5q)T(%aD^@1*or*K0-!W%IbgGreKUtiB~|FdD(BP#8NVGwbi6gfq;R}yA!sJ4hRC!u4)houuMzysn44>cEi zOd#%PRb1guc|#ovLt!#?1EyQcW?}~+xsIP$5P?`o&Kw|I)r(f4!D1V{)B%ue>1aUd zqkaX_#~+kF1~2-}1;Sxar_*0NpFz+ZkT;zImiuA*%_hA}{hOj4#Ru#tK^IuJl8lu4 zIW0zja}atqQ#x2Hxvj&^0;=)E@0&q)C@l`9Ux?0m< z=tekgy{A}sSepV}?YzknFCw};)zzHVfGR-V{)Vlbk{q2(uOej;-#&mmT4U~+C~ zAt>JpMN4cU)x$bp{2X=zAY1kG`AOyMb`<9Wi9az~F)>X7ZN`PSE*(_N{y_O5n?VL6 z=VEaYV4?jI%T0qxfHR$18~_J(I_eE)ix6X+>RDj07CT9UM=UW0^m2>KDJ=@4HJz1A zS)%?>%K^Y~LO6_PlS;2w^->|)Htu2}yo7mx`inq8kc~>4*`({G9M@O^+{Iq~z(O1m zBWH8$WRyP)JpyWSB;9xWN3h4gYxJceN-Dsc0r*+ zI4^4qhYNFdLJ%<=+o+fjK!yum6j*|k5KLV!P1wAFIL>E2C)RS-k>YP41@%#85=YkU01ND`eIbmk)#=N=kU?!oDH&fuhP3LVt9tmSZ5I4jbm5 zip%7nU`%Blm5EAGp+KXNk;n2c=HZc)hWyj)cuCD3iQ}I_M<*@@1%+_+MbB5Ig;QA; zu$+rHC8$OWb-V}^20gwg#5n^l4+xH!#xeW8P?VmvK%-2j(maS=GKKB}u}*(;W-);x zTV8J}D;r`JjtEmGhPvZ4mV*KqZ7ik>8EvfeSm`Nr)T5eSP)J5w_DhR7z!v8hR*51p z)xFfAaF7fZrVm^SSTCs1x(9C>FEV4=3+GXOeHVJ=@{O$;%Wy%-?&8(IK3vt96+6->u* zvPy3#5B-S3jE^c;!0lMmDI8xY)1udfWJsBnRL{jnG8{?OhXh*s{FWG^3gOGRL=+I< zs=}+OECtASd7R)n0!%o{PYU$GrWEUGK3_!U1>ryE) z@4RkP{1!;MdjzVa>v=YiPAIA6Wej{np}Q}xj3C2kPi*iMx)wyMoH2)F=uZXyX)IMJ zih2cjnX7_s>PoU-B_JoRW-7ih~% z(sIAzw+4)&d?`_(PSOf2vseIvq#%903VCv@`c>cpFb;bdtQTA65Y+3}dF=&*Fw8jp zF2aO*!9THCQ(#+>RZ_W#oTyz`y?|8%y`myoa2YAHL`yp8fXzKlw98a-;;>@KG$uM} zcx=Z@ltl1M&~F{k!o`mK1tSS!F2^^fvV)8ymMB~;IU-zI@ebXD9VSNdxUMA|?1nAe6tN}h#1JdC z*o)_;L8t(IwW=#I1n$Run_>djc$T6|1v%s?UpEc+H&<5NB)Mp^Eft_IfWp1YTu-DP z@VY2(A!{aN?3$w;?nJ;2R6>p%E`AK&o*_djg|F8dC_;ycV23Rt8Ix(@0DH)ovNvTDm7B^X^jdan;CFVfgknRy>`GBG9=6nBb^#Z#I4{Jj)J7zT~bI=*@fDotg}$M zQ?`vn?F~3S#Zf$GVIzpXNOcP*XGnKpi5DmHn8BikGgoy33D##nTadE`ihLuJhZLDPwbvQ! zg$t>CR2aE1ffaFvyy$3~b-=}!oEiH(a4{B zkhSutFtSPlm*NibmLt|&UCc<t%iH^L~%*`!da97#= zd>9!=AS&(xuX{9PEw|F0oSDJf>j6rk1nvu$5Y;|xhS_eP&3Ov3RokB=1%=hrTb}F? zIMuC^k}5;V%L_K;8>e)yTtX&hAg{+?9{^dq_uvvqfR!Jc%1a&^;~~z{sX4#`u1)m| zRVj1AJ|e<1kKEhAc43LdQdSvcVYKKB$pUayvgXHUW3N0aD3x(UTRH$be$H4=(c^3R z4om+<3z-3`q%lExY4uW|Aj>IqAw3V&d$e?c02mc6Z?=#VVXYOyUjYwsm>X58q4H!u zZ4NSA!u@Vii7b!eW}be?e#O~odL&$^ldez(tb#dG>-tD;nc`ml@Oa;YN0oV;lCn4M zGj9jXz9xyMb;y}_>|BM=0bPo`fIm58RyHb*u29f-i1d&{!XX;EbW2+c)`@jgnv@jp z4P?bb&R&{jlip6ZXc-1SAxUx;+y%mxe~NHcD>)#k+j|Um+c%rtq~?gXjKYfv9y~KM z=#+qg#Unfip-0f1Y{+a2{NhGjEiiJG_YsPU=BDS;c@Y@1;R8L>61HVIes)c5@n#hxZ_uQv@ho zF;$;V;ULzZuXCZ0?eP`>jW7?Avj_)bN@l$!2Us8b1b`g`;JY6|QM+2M0779nEaFTN zA;HdWDTAc|bUh^jQ*uahjU_KV7*e~q&Wxhg)+Y$i)<*Nb35D!Ixdy(t)5e)XE^XlT zmM%~Z9f8XeSPbWgH$_v5iiqmusrP`W`OT=>4pg>F0CC8av;8tEGwuH4Byffk)<1)QkSY9c4)3=GKKg?n#Rl)=w4KPEt(KJT{f1G1o52?tM}UQV%k%v%HmL$?+a zIZF-!r*;_-1jC(r50G|qt>t0+GKcI`^=@{z~!!)9$1wcfvuD6%%zEIIC9C?oy6_J;1pL2&2p7GwqM`RSWu!3y5 zsH+jhb5lw$ymi(%Wu+Op_k(vSt`2}dp`P3K#d4vpjes?9=+-_edw`}J6X3z*R1pzv zzJns9(ne<$Lt3c_?de^!?{bu2GN`zId8q0DNxSic3&x8bBPLYUrn;1=Rf=gU@GSs$U7 zD~fEOQ1_*51}l#gpGMp=qJW^?Io__zC;<5rveV4q62HyiIl00Dj=}JJ zCS1?u!%kDa3Y6M*<5HEvf+gMd}cH~_bQ0xdFzc>w5v9oM=D0b*He-Y+<1=;c|r>TSj=Cto;&6(^P-p&1F z>kWWQ?dGgnXj3(jH54WL6fYihLCVrBg&K-DS39i@-@&3r5IZlb=Tf;vaz%|mIqW+5 zo}ET+W&wz6w`S>|^EmD(LM17%JLM#gI66V49@Z9HimxOj3W zA=>p)S%K6*#U)ohR0t17PvZhmuJETt}$40IgcbZ&$Zav|0IW z2UzKFjjLT~wsuBi$X>X6~Oiu8sjs4W#0)(cP9UnXEVY4ELo8Xz+S7vui_M40u zS%)b~O!23)JK#0RCEYK$BJ{Q&Wum4s|ZNDua`j`V4v%6eum)fxxjTe~l+ ztmilLi&4`W%6m~|O}&NpB_Wy<)mOQ8^7Ym+fz8(QKTdK*@MsT65ucP_HY{^3p^(r6 zH6?CBc{UForxpyBcAu`vv>q60h;?4+;qSZ$zxQ z0`)c`dG>VAd5s%(2C*o1Q)cT36PNZ#AY?{^*D_SG1a9gtoT_ooW0|NPcO@{1(VgA{ z*D4AvE&yl^pSXeZA#L6X*fNbcC1FOvRD@Vf?}6b)y(i|>qsx`V)1j|672dL4rmlCB z8p@Cx6E1*N1N9!>Lb0`&UVN7-8v$K^>V3%CD!mj6gmXG+dIP$BL_Nrlr#6Xj$#lov zn{GR|af5u2Jf#9yx?hm|5A#U6-lcGWthP`|cnxpr7k=)_k{we!yxKu?2^J0f;=E z3-1=4o-vw1sR?AZM>A(n^5Vakt}$FBx7%Wv+6uC>nnJkQ(ah-8R8;?SVbBhf&m)0a zS;5&2Wm6krbOSwZNYRy_NG&4nIFr+$n#$vFFp*yw zJkC|qgL19)#8(eUG$?LoSI_0L_TX=w^%Axgz7Y~Mijd^mv3c6W)#_iIbc3u76acHk z+oXHZB%N$nk+q%+u%^!hUCtI9?WTsn$F{2}=**D&z)b&zmc`A8tUvp9pe+UWbF>tE znVO|b4Y0m???nCN&L(~;U4V+yRx?pQnS#L<%|2-^P7QEey?41wOk(vfL02CN0OYx?Yb;kCboETmxWwU&#ZUO{sm+@&~}b9WyA442#8pUlXEm09v!BE#<-?&FJO z)ClDC!O|=_>MbZNce)Rgkok`gt}U4Bu80sf=Se7(rK;SkxX#L){ZvG2a)vc`fBOqS zwnilPH(4Nt<{V3|H)os)y0G-D$LbOZPl^-PBX0}h{w_I!(&-z|1TIoZKAnY8lWZBo zuD(hkfX$50oA*pw`>u;+Npk^g-}&sgv?ylh=pS5Do;7OSJh?=ag1 zynXd^8E(DDvhcRX*W;)ei0yY?TEEzq9$UCz(gq{uL-lzI9=tZ<-*Obr>EY+DYAJj= zi{d7Wdb#=pch1et3%Bf@BSX=IYXJSKbn3I;vhWNI-_7rRYy| zG{t>X<~Hv1(GK9MiM8;mT&nngAUW?O!=2K-)7$X_ybf~Y#e^hxp4kF_rLVvF6%tX_ zH=64=ApKNlsQ+)<1}MPlD^9=s*+gJW^e6GWF2S|}?|Imi-Hc~?vNn<595vVG)-^ppecX6>Wzmi=*3E$zAlp2VR{e$Nqy8Y3&QYkoaFc zlnS>yTqS`N$cg_IC#p9N@34gT9qyYpS+VIocYe+J8dn-t3%5VN&_WBht0evM9r}v$ z+`khh`}xzqGJi4aOnai{kMD_7XrPH59{C-m-ok~(U}B5gWb@n+DdSdyu(RG?Z}!{1 z0huS>kGoq3poA{;>g!}A9||&mk#UxKqJHqVvJHyk_wvI87esl*F|kLJapW!kGnfkz zAGiIY>Ewue@aDrf7vkZRr()@+{+*GLyiqqtxq5Y8`9xzcY#wr_00`0SaBJij#O7g* zy^|A6^U7P?tu%0U(A2cT_tGgW2*>7ILtGqm<5z~iHMXG3=SeQ$)=6OW%DsyVe@n4Y z9YfMzk5L%GpwqCX)!9jm{_YJ!{P)VaA;Maimf1Ko*&BjWKXD_G6_b`_=P=p1X={Gb z^j+!g(>K`!H+#8arSJ`F;qS&!Tz=RVTGg<7h?ghkOyY#_ zhduwKj=kV_uEQ^;4^3Jbs-?BS79!iJj-n;Y5Cky`=IrJY6G2>?A zFU|gFn~47pO}YQ+%VpcyLX%TCd8y{k#}2{Qa`50U_&|S7;pDuzRvUUt+eT3c0{DGA>R49b%H0^7m4HiP>^}o{RP*_^jhMYq)Ht47E>NGC2<)7GC4S zCE>jE$@9X=3p#f`A!hxb)WIHX-RKNIFZ`{C1^xb8;-5>o8286q%ov{}=I!3U;lG9_``fqB zgTFZRRnUAfJUI#eF0*FCS7$E9`sOylZ}nVG;)tqiA^#8W~hCB*zKSq^UKd-6IFPX-S@+A+QDSJW>E zhrbp2btaWRlQ1qb)%q9wd!>Gv{|-OLRPz`7&kdg|eEx$2lkk5*Y^keIn*{N@ovPki zeBtiD!H2b1?7IHDAINzgQQ~UFq^s}Z)BmpIrv*_ZTzGnFxlaFNN0_9NDW)pF1PI$Q zQK2Z&sW&g+FQecW)!`_7<$)Pjl8H=26`OAY;`in@{q zo=Hb)XEpq+OCL5>DuALhecHm z?*5_Z-&bcXp|a*hh)`Mm^OH(}1!re1l9x=~68463ocI0Te_!hF@1G^ANL#Tt(530- zj^L5_RQ>SrkUG19vvY3kwH~&uc1rS-GS>O|K`*Z>D}U^Joz+CmF5~7nr=+5;pn+WN zvA*|1FC@kz@<*HH{CbL{dt3D;G=c|TNc4O-|2f(J>#fnQiSe#F>!6XU{HNm`nPsz$ z=SZgKo}W0ERI0b-$;6c=J@%+#QSiONp9@#JcHsKRd1jQL#frSG(LQqRYXYz@bgS91 zT_Wsl&%b^woo(s%GF>jf(8b)`{L}tJckigUT70q!_9I#JP`GzH6* zu0&<`_2Yhq)b;7E(#>5zzF(us?R$AqR7vVkdggYiZLtwYKJAnKb}!57leyc|J5T%n z+2?fS$b(PW9{Ud6ePq?o_s?E6$meo;E%W4_;ZdZ}A*8#}VblCK>A_>)Mq7i&oucKe zldRXlf8#wdLqlc3NfQvRH=%CS(f&Ns|ILNKTWRc3<;{t?J#UR8M?W7NGsJ6SpCaKf zCO3FiFRwCUd*3*#q_t~m)_1vnp)kl|q-wSN64z`SDWmwyKc%<5=3C+tac-WpZ{cf(nDX)TL)im*iw@g@Y5O}#4#7_R&_t!{dRxt(`&ao z@N!sQ&vzsHWB(q8RQlkN#`#~qa~2NsBO|+v{%Pva7^YTQnC`zvQ%fuJ?=4Fl8m=*r zo9mU_m}RUxWFIsfncP%2F;>^xC^4a~*V$S6?z-Xor``v~zvWx?*;eO6ljcUQGwLZg zZyj@Ya1jvv+TNP9HeiZ!C{NVZN_ZIHyy08>HtXmFybWj7s;((=WZbytTdBM`aK!lU z!`F8f6`Y%=WAxN9>PRrT8Y0Ekg|Q2(VQCBDElpJnaI%KacRND%BvDwAMbno z&%xUbN8h)C?Jn zrVMeTo*G*CwX@z672#eyzC6Zy0~VF@Qfy}7d<^K3`;89`v#W`EAunPpJ8b8vEOV(! z&7vmluCn?1p^Gk+WK}GGpJrUPSIN9 z1_Q^s_r)uTGP`h{X#Hm+HQN0Zc0=O>&tlS&{0|1QKd1XL*lCg4mzuuJztp*cF|KoN z4PLGWm#8&9o27<}obu)W?5aMRd-y!?xlLPPI)3W;Nq(o{XmiJw;C@HZmG1M81#yHAYy)gR~Y8S6SGXRJ5Y*Q@ulX#NgJN8G^ULTB=@pDf+b zLrM0ICL&aBS{*v_t{_`(J^qdkecm?%MWv#nYpos|^ay>f+@7eeP*oDYC(k9z@^P$g zO<2~$%P8Q6I$sF2KIfMx9vi5M;qT1X%O89xmo#x8*r?|zZQ%M@FNtkEBSA?s%Rj*5 zx36gN?OVCN^TNEwXZQyi05G!Oecs&7>F(f>OxnjJq1kYWFfWO{wDf4jGOgmIE8iVU z-VFX|e7kSvk&cD=gPW0H)gxEBn#}8l_lgJf{jJ?wth6@xjS?gJ+H|ij!#S&5JWp$_o~MMr%x9YgJuoR#%R+Pu(#e@u0RpMt`OUTMq3?GV0mH4qyjrZB7{p znR{c3@;U{mN0jGat8JtKs*(6AIsN1bssq&+W!jbJb+pnU z#zo1?kxy{(?l&gRj=`gi61|+Ufz~9qV{3hCj+l!3cRwF(HxAwp>22>ZIIli%j~0GC z+h`$cA-o>22kkLa(r0*D$1WP!OzeW4!MC*OZa({dd^?*Y^zhLmhufk`J@KFOq;k9N z>mxZ&@(alxl74t+VkCd!Eize^Tds^xwTHg=P^V8XTq5s{t~S076>0I~Tkz%k^HP$| zdDx)a{Q!0us~1WY9z%DV;q%o*rAU=wmD$j|XKk>1L&3Sq!)38IRD0B)yhakVGdc=M= z`$}X-QUhQ04jqX_8lLu6PkT!%kA>^>#Mk(g6)thJctcM)T2oV#)7W3-_3C?8>piWm z;z(oNoSu*gwU=QzS<*R&URD7a`1>w|4+IN0jVnPSgb$cJD5V9^DrH_8W%K&$W>h{r z;8*j_D&}cRanYUcS!FC9UaO8nK(M7fExGjAKy5;LjsKx%LnUsGMGY?}y1&dH=#&`2Z4d0&Pj^kt zUIB%GsXVg|X1ZRP$EyxWl-EC-c-n=rhC!?_v5&uz@L#hzHdnGJYkBds#h1C7Szb7% zZ(AKT9j~5kE2C&tv`V$jr?hUg@+P?5-(2g<;qiO1q%O87QaVOi$a$}^_K!{X@;jec z_ynSwV+Wt)=9-R| zhwt2!qqcG=T=6kao)}lHFz@)-Rxdb(TO4^)R8^cUqL0EPFIj@(F?{sBTqwi+M9p)y92r2ogkSdS!hX| zL~!2Tq{T%wrO*PTMA@{ZOP5xw-jKwvHPCd#fZS247WsH-_bmK6vZddUdlObie@JhwEM*&iPCm+PrUs z^-1b?a6s*Ff0k2fnCcoY33~Fr$1eM1bNk*| zcnme8BDc1;(`i>VJK)H9bFT@DPr50vHA`f~RVO7)Llekfb*yt?Hyvj58!Ikn{8Lqx zSvNk?`aMKE9&ryhMAv6eZIdh09dYTjc(^LY`shB_yf0@^YX3S^zKh{%;JKMC5v3~Z zEJ|tojb!11Mk!)%`!IVwqvhIJcc=b5uiSl^J^dSnN{+kv?}GK-zJJtN{@qQH#x3!; z(@UFUETc!da^0GRbYh_^s}xyp73EikZ3|*$f@fOiJtB#PV*iH%S_sY0i><&=Jow?J zSkI@dmZe>RPB&bi9Ehs|Uq7K*t;O1V$Gs;#Z$|sG^1u5HxUmCOiuN#~qaF!5Z~u+a znK$qEGiu}Q(`bhoiibwNdwtNVy~I0PTmJj^?+%^3&WnPF-zR&1SNUu|zIkEu zvw$v$G?Vcxs_6|aYo*_fJICm~RE(HJ?-lZVnT^*P*4~UQ=fS{XMxiuAUtfQ=Zc~Jq zj*qKqg6iE59w5vWrsbj0X$zYSO)aCan{|!vVXxhJDraKXKk6x8s0D1*g+*B}CwK|@ zZ{N z+zGj&pA1<>&x?13Jc6)MLM$!t9;nQy1Rd8*<(L0qcSJY6rfEqrV4>V!eJDL|M5n8$ zG`E9&K0k%8ZC9DjfJNt%-qOJSyxyKi=58gKWx7?pe*~~C676l+PS)@S{~hR11nRi` z8m|wp4M3|c=wsV3W*g4&w@k8s_2orKYenP5w~fZyml9laR_1m*xh=dfMPm1wsE!BU z?&bVwN$4G%XWW$C7+%)>Sf|G>u6BubpP4%=DZ2NcD z7;&&>))+mzLJ?N*J74jb_`)58SNqDCrW3toWya8UNj`c}O>dD7EF}eI2U|jYVhneQgf(v z(n|<<_VaF%&xA^m5koh*Ngrmsukbz}vD!U&bTFc7Z$Fmb_w-}Nj}@g0S86ahaDf(r zKA=6={UWEgGdFW$NiF={jhl8K52}phe-T7uz2!dn2`7a#XuN#07RACeD6nf(FUT=x zE!y2MaXkY!TvMZHxoO!dKU!$HLQ6FtdUklcYCZ>dZ!y+>4v*s%Qa>CAd7kF3kM_vs z_q@^m(I|n{UjE0%iS~(s)#A80km9;W)%p3_<1xSb1-I>)>|>F|Mv5AY!cfu3oL6Fz zcHw%uhwkM#!q94SnCO}q%MWy|%?s=t?M2(?8?80Qv>ZuTN`8iGNVDH?PAhB;3EI(L zdYQqKmpXr$itA6{_j+Lq$6t9>kqx8M$kU2l^esI zDpB$J+Md>$2DiuPQ>U??pN-E;+a<;{D16DFoqSu3_rGPt2}MfMtnHq@?2*X8bwy8h zrEM=+X+3J2H25RDH)-B>0~l>N0z7SAFU|*5c$c17CP%nuhF zxbAyL82+9RJRx34r0)+WmoY9H%rUzXXFn?PKCSi164%#u7Z&1dxc($xRIV^9NX@xC zC2y>JV{b*n%bdzX>Y;aR_FOP(evxA_J0DppzkA>wN47?%vqP+939vfs?^iABD^ne(-eMfArP%smP&SHF&Q_7d=Ds(?4%6SZ1K;3d?5C#uByGKe~d{ z0=hE8g%`4dci!6lU_f`I>3N7mX_#7F;$6pLgY(i}iAfC#%}W{o5PGG5BXV|mGM|)& zb`dXQyWxXRp?9-NO9Vukj)#bQE|CSdukE~b?NHa#Ud~3L=9Xy!4}Pjv(Teu37B}xS z_S|&-l^F8)HZPe4jj~+1St1{!n>e&OhNmO@epPW|oVn{$6(Q|D6IfH~pAz7#-3z(< zoT!mA@toi5?cFTLggB*0dlz>24c}r7dB&sSFog8(&5!;i(dUU8$tMb~;QKB&DKS1( zx%~66+WW0cPSHaIlwA5B)fLs6yrk8>)` zZ_oKxSJSWix*)e#k6Q__*WK_6v2~N7#{&Dk_*C?vVxc zhEgLdhB)hG?zxlDbmGq0NMx$w{3bR^+5jU$SbH+c1iO@W zYPVQMG$`P!?^e6tzskPr`4Zh>qDzP-Hr2pt!M+amjo*amj(!<+Z+|Ctbnwf8Lx+a- ztfr)FhCANoj2A*>LNT%>2;gB>mMOauASLSFJ6&R^itrO_VQ?ed@W2$KL*D22!9=cwfyv>Gt!#y#X-KAQYaI?wy zG3dLGEqB)!pDVJ7dSo!yhM^nV)Je;|2rb^4 z1lLfF{_Mt{)Xq*)flFeWDrf`Od}rOgtci4=QmPVAR{4R5aJfUNw9jybD*8t$t46gh zr|TKAW9U+?$4v)-i8X3?qk+KIXCsPURfi+(1JA!Izr9QQ28!_V6NH-X9x!1KtlSrC z-(PXxPHBQ|*z-N0t3*}FDOy_)e64~nTB-(Wb9*999n*Zb9kV|F3NC)|=9|I$!t9cu z>&OQ<2P>AXJ@LoZ>xyk>SA-V}1@)ySN#t7gs{-fgu*%lx=zf8%s4wPczb5L<8)NUv zKbrgymLq9#x!Z$tF+ZAxG!3~T6^3fO=8=P)c_W<`>cK&9rQX$}44k*w6^0g_?rm+o z2b(av)M}I&s#|}U1z4SbWrgs>C*pJUlpz?>tBbPOV&%29RvL}PO1<5w;g0%ungnhD+MVzhc7ohp|whhW>y`tiqy}a z7z=EBAyJyL{v>k15{L4X^+V+$dWo+@hRo}DHGIt3~1;-A+{mmOS*z7S4YBxlu6zt-yo&oH}o!VPa zus*!z_+2YO5uYSDd+cIY$2V<^+Un5#MPKaFO1)8=*_wm(4}5s&hJJ6?^E(s?1;?(q zsI3Sg+}T1md_$9VLfyf~pSX6A;x}ts&oj9Be=r1l*1aWFu5ULlv`+jxRX+x*Xa9S6 zEaLTLbZ986%B}1gBa&_3J3stmm*I~JRG!jL9(uAUYhOD4g08RV`v=QAV1O@CABeT< zf%QV=ca`+j0SiHdJ=|`-O@+PQiyHkEF!Jw?PK`gX(iln8W?E9=r)r7U^>SOj>w}j* zTN^4~&&*Up(V46>F$(*%mmw==`(TMvnxH0!#+SI|d9b%Jek>Y)PCIG16y2_%mk_=i zc4!N`DpKt8r%$T&rv{#>g$GTHRC=Ucc~d(L>V~%K`ju)NaC4J`0hzG|?iqWFyh@9*o{fFq z7N@w9p}O637dvvN_9rAyxn%sBLd#z#6~{XEFkFp#IuAb5tmlvPa|L)#MvE-&htHtr9sQ8_kKa+Uhh?gfM1$E{;kVYa}E;hM! zS?BmiQ--HKTWFz7S)b^xdc8kEVTq^L4Sdue)ylk8r1S4KL4%s=B1$iGkp{ zbw&9rHP#lrWQ51uD`q+3F`_+>7qIM;&H~uhTn_o2KwGg1+3af zW3#oIzUWJojMNQ0v(OG6ucxI+SLzf6uty)j-5h-KZa#j%-xuv*xKQn5)Jrt7wjS)y+>$orGUtzPdYzlbe-DVTgjMie*?4~vDOkALw*w6vz z0(w#W@54$qk?&T^Tcz2EM!~=dZVgGfX_h(BD z_yOEzRl)kWp+o+DwFAQQ$Q(M(>fy2*q$5_3Gh7W7kHKw0+0qLl&6nSAM@<_TQ6~C- zemuBcn0x5_jgD3`|U`S zuay_IzF$VJh(2L^UsE?sOlWXH>l2;)zcyU9+sby6@_Qe#Y-3L)+*BTcjUkm!W~m*P zfX-`X7pCGl7N+J{+o~J0Y~vYPQNqqdnH!g}CR2VY2ZrXlv^U&)poX}~+Ymw<@@JQ> z-gTo1)u`2W7a7-gqP$SfyqKh=HlR1iys`aCJnhQr(nTScbJ68c=0~)T=w9ml;uWz^ zy{1#-#~4>Bszt$7i3TmY=D#k2F#(IwqtWdvdmJc83w5&(mzHKj~F>Qpgd0!eATiOkBTsTwe#R zTOTsdT=a;MvFs2EhwoVas`5qpkruJ`gXXg}R}JX(ncUbGx3dTSFkGAwOdx2WEHd~^ zn)-!6<(K$6a7yPoD1W(*^F~7cBQ7;GHJXNPcl~n>x7s|5q3d!#M=PA+PnLcprD`on zh=Hd~FpO81N_~qaEJ0ql^RK%dIcm-pohP;Jb zb|pmIj~3AR$drBOQo|iji}RW2atYni40^LoZuyxoyo>o-oIy)A?s@(gKD&(dbku4* z4L?CoLZN9Nrt;}E+IKxvDm8%p7<0H(sprmG3llCx^n$pkap##JyP9p^b!mlu%MEka zqN21!_Eq@*E4V2XwRNS0#!wU57m30{2-r?-n3cIc2UqZm6Y}REnap&wq@WwFPH=g^ z=sZ?`JV}BF{_tZA5~zBF!yBjQuobQ-9?_NetM@=~5O#Z+T{X;XK2c9mTu2saMFwgV zwP{#d<_sL?SM=*?dY}e>xAG3`QnzXi)Fs0Y7>b%N<65pX-$V5F*YtF*6s!zUwa06l z$%ADurFkB_zVlX4Q&XhZ3)oeGDafH~3NUkkt*qCAYBiT5jWt$AZM`1f^nsRa=Of zt?HvPb08Ydp9z0g!l$>#McbE8_v*jRmT|CVa(?$;v&7UhU^)WF z9P2s9hpJoklF{;YYwztS+AHatRWJV!UGE(b)v@*gZ>~4`-YD|s8VjOg;sHcO6c9w( z<{HZZDM}L%u+WqiKt%c`M!jM|&(iA}MO2znMXGucSE^L$Dl5Gxz4JXYv*x|Y_kI53 za?YG{W}bPiFw&Hc$-&s{IZSK$iku(2N z996kcKJmN7lZ^-grfc;7M1bG;bU~GSb*noW-IsFd6X2{=h+(axuYa3AHIFIqa>*E_uRqP;%$>J zDY09E!0+%P4NfSD7;Vl|<>%0g{onm(#Ymk)3hm?LgQl!^Kl~{0F5fj(bVXWMq;`4B6%YNl>ehJ>Dd?x9jT7r$dyRB{OiHiRC7PSv`oM6DN?(8a2-pU_OtWNa zip}WV-EDwab~N&1tM=M^;!fcNfQx2GgIn4RxXFDiv!OHmRFJIC3KqLH%BO5+&UBN5 zk3Umn&OH|S!4VC*$_)>iow$}OXrg?sLDflitLuUBR+mB~aEjObCYt5Y6%??8px|!v zWFg1SlTn+!SqzSF;~6ltIbCf&3J~2cMKk@gty7`jV9rF_A;^!@Sz3@fxnkptM77&R z1l)4-r%y|JV$9SCofR19H3S#Hy*Z7Yr3R=F}O}@-qB#nF^d> zO|_0oX?P*_1|L3L#DgIlk zLF$XLnIr2uGXfi=a<cx@h{Iws(k!s}Bk+NR1 zXTQ+fb>+K(w&gY^$8PB!g;qNOp?SlJnDpr^x}VbZyIxrDp#plX~!)#|4~ zSh*X&nBhiS0#n4zm)W^NP%|ul35#bF?bdX9YSd1nX5^>)E4aSY6}x?iLv+ZQRf>1C zm>@}&+h)m~jGX_@lEP*iM;2>tVA8WtHO%syDPSC?OlSDYciA{>sfadR1O{roONrWd@-g*`#PfA0#50Wym zD*eSM1~!k%-IA>qyvN(@;_w^t@+TUlmllj?E|btuLt$~vU(o~~UkPaBNQ3%mcnf-6 zD%*!C<@P^*xz<52esO7fX-Y1lHfQCzMvsP;f}c+yvqm*;DsD{9C&na^6wNALI6g^{!onE?wge6hSfTmm}TS1D$-sP^F#exUxa$kH(2!a_isZ3 zPSpo^)(_d$f!A@k~dDrUi56ubh|lI_njRZD9WMuVAwqx z!|H7?g+Hmi;QGq&SH;A{l!w+ZU+t?i2ZVo+74< z{?H|<9C`jVD4**b-*+{w&ATrIF_8xP^%1VfyMFi&6>;msw%r-EhvCC4Ig-?^O&_OB zsEC~T>Ao)XApDQ40Y_szFTK6&JGXsQO5uC@iAj81b1 zO#gi&%9`94$HtNuYsmOY@f{iWPLzv zL_Tm9iI#zd25@P3BwEG_GcSv`t4`;?WqIz*Ip!*3sHZYkixPc!aL-UU-M!P)PLkeL z+~{^L=PG=QGoJ=UeXVB`JP7cXwMnF`4#|H)nM|347~^3`d#tUyA{`Sv5&iSOm}Q4t zU$#7K*@t?WNYK`t(@D^f%T$qvH0ae8qP7G)wFwk8`EiLvG_qj3q(k>0d}c30CR{W5 z7UV1I-vZ^_!9Blscu7x46(Z&X5p!0yvLm=8T*ywp~4KiVp$thG?J40{?f9w ziuVymD_((o!AF$Lf^3fN{Cv%tCW5CjetY`w|CR_nBd)Io-xxR8kzcHzugd#?98 ztE<*?7m&k;Yfovy9JXXk-py<2?K<)wWS6WDR;b*Qh@W@@zqT$}@B1@z?Vzi%#T~*F z3WO>0PakLGq#|}h*cpWs0W4rmP;9g%#P|L0S4lYfWS~M{(UAifB@hiV8mh&(a~)e@ zG7tnp$s3p@7R`VY-w8RnYCKB`yK}ue*TBFYHECTZliWFkOv>oP4yIodjQZos=UmzeT;tU$^h>szu=FC4agt0I3w~zut0>fFY2sH@k z3Kq_jfv~+husw-A7WFo5+3NqFA&^8H@P@V`+046(JSOCwQz75kH738dK#E6^)LBE# zF4D;ZLwlyXksu-lVkKI~k2v@QDhxDz+>Sc6maHhfZ={#8<6L}HF|f&rXk{16b3Ww` z4CD<3AA zQ}7AielXg>^}@{c{bE&a-U-30TRERY%~5&_=}@+gcSXjg12+yaa|Y4)kW^#Xr4F7w zAAj!9@U1hMktntT0aFZDK76<)DnX|i@VU^GvzP#6%PS&`@Avc=IQy@l z1}+$wl;vFvzXaD5Dh~n7tdx#8+Pe{TOo#`J>S6`e)SY%H4O8j4Ah8X7WAveD^!by^ zcYi9P#9{=@cXr3oC&CQ1h|ZJuw?Z$l4WZ&_d7JdpHEumNVW1#`0XMO{O`t{_<kKxq^@^RocqC1?Gb6O-aCOozQx>}58|a=Dw6zbBFZg0P=*(S}^r(D)SC>o6dtT~=feUnQf;-L;H zg&I&FM-RWGE@KX1o&O40VbmeD*A}?|8xfPJ(;JUP^mAc8$oKA|nbDXTjDnPO^y4ML*pzpBVt%&Q8PZN0o9;`E zg8+O+zbuGM70MW+r7ck7I2DFi0o*)%#33u$?i_Opi3{Ywz}LTrMXU<>_NA$L@=zy4 z-=JMPF>T4(d$ZDRYJ@S=oV^ufT@rI{3*vpnizu%np)n<(9^BjQ9wanzR$Ig!^BR9G znAZsftY6nq2!h2bcVfpM8UM}J)s<1V8+vkTRhySxWjTbA*m9`cDXn>XuAmu$q*VJs zx1adj$mcbc>L%lVw_h_q3z~br(R;3nPt=21Nfgts;yd~2*tz1B{{k-Qwt@a4ZyUMm zRH~lqV)Q{_9@6Z^Vp=1TLiu8FdX5f)`)+?^g|u#2PFGCBNaq2A=wdWyQh0eWr_)5o z&7Zm9H{PmzdBeG*SWX|ThfD-2&*I+QRBYN36b~=|qrK-LMhs$q>F(Z)rZwX(igtbh!e;~1QAW1v zCoKO}zKbPQl<6_6wuO1e!WYZ2aSm02V9&_BsP9Jlp-e^Zg;H#6N69WDig6QCJuMK% zcN>0?5Ssrr9E^y5*}e{zp{pZ4l;Kfd(IDWVF3buQ&P+;2Nk^C`8zV?+`|9TR=qoH{ z*#@=UBXT(u=1yJLb7&aT&%i76Yf2}q5m{37o#yblqR&JAo>0q=!BLSGs_HL+1yQv8 z#^Of5;{;B%;f2-()Dz%#V~tv39m97DI616l`Y_hkYfeFO1mbOEp)J={i#qo4)MbrYgheI}vRS`$gW zG_!2ahgX}@VNpT|GJog4KG*7$z0RxlFfG5C-zZZt36HTaU>wQr*t3J5*xDW(>tb+r40x^{+y9y9-!yz-vVCot+=N zn)eF^-n^YLjP>ZCPyem#b!YYQtLr{>X6H$wH5tscia=?+?Ed;4J%Y5xiW`79ECyR?2#?yph76U+@)aGkad1NR3;U5!Rvoh=-7DUXUfcx37mUmDjlBYv-1 zii)m$yEjSDj26V&mJvPn=pmWIqc`-@-b2`CL~bY`0C&!E6#Yb@c*ZeY@PrIJku0FT ze(DZYVl0H#KffzNAdZ!9k3!|oFRljvisC<}_h@53IO%UVB%ydoiwV#n1smh4{rU*H z@I;YxsSMx0!&vJ>h*_LkzQbHGb?>vt3ULY)Ap;N+_nW6%;1J>vj^!ipKvO+a@PbLs z^u!?5kqy%0;@8~#8qSVqjb6x>@V3utba9@>QRXb*IGYtNCb6g z58iEnGo9(fG@#*0%)KqaJqdnSmR|At{^2`}U>9v^j`TX~-9^Gq8X~&GZzxoA;xj+I z9%N?csgfJz4-yszj!OSls5{^xp-Ql<8j zSE-1bAAAtQ9p8o0gNgZxcm*PF9;&w9LKI;{W2O=CJ)UXFwL6ZpHB?StlY%w$k|Y3+YGq927~wB}slLJuZjDmG!@vIcu+e4`o+WLX7KvNbVm-F$mrU z5s1^GE0s|ahpH_gPwm{S9uOyzs(hOECY6R zg#Utv^65)M1tlB22Kp>b#^I~Hukyw{QcF<+fLW&ZSsl%pKH1zR*x?0r879eh*dT5T zmKgT@7&oqeiofAK;8@HHN~4y+*yjs#HdA#@1GmTCNp`? zu&`5Q?v4buwN)G_G`_Y>TxlC^8TP>kfo)zKY$}FR^KFyZmY&qrI^tFN4{2)%A7i7l zwrcWo2QDaPv;v2A!ZRErw8ArWG?j>=TR&8VMLMKJOyn=jkw@OnfDJ~gs%d#uKGYW= z7!!(v1e|%OR5{5+kKPtV$xeK0ZR5Yc-Q-q%S)+EObBTd3YRwv5feATU*dW<>=7NaQ zt2gdl*h1U??LcKrl!(&Xjda$*G!vvNSiE?`JOM`OAB_JMgBD6IG3;ObaX46QgE8V0W zjd)X*#C_*FU|EGT#3G@NI037v70nJ6(I`5}T)WcSIu0=sW7A-Br zl90heYXD(YFU5cIon{UqQ#aXus$2X*hcSS0o^@sJ`iZySwI{+oRz5L`ejPk8B#}Ee zp%5$P0$E306O(%`IW|XHZusaE2J0aVHX(8lsHideUNixP4PYGhHtU-*ThsACt?qP&`xb*J zX@jWRf0eGQiQe(-tG{2=aTZzn^-62z#L!&c(e0+;FKp8%3hMLgCuVw|YDyemuxtw$ zY@dfnh~h4X#JMsuz;tJu91%r8Ukq3p>8s}Pow6;MLkEYOp1Y!_9+btYrdQ@4CAKHPZwIa6`}`lMYODvv!h+tOtFIQ6!yXd8`Mf_fm)+u4isIVw z=#E;7sPCr>jb{>*p&Wve+l=45MGvpcm@ZkuEIa*V7>X2lGETVzSt`BiLp1aK1AJ~= z(SCgt5#?N;h^Ce}L%wUOyF}A+4@gH5chCD%5~Wg6jtu3Z>vRtWz?#0VXVX%lq98Tl ziAr(rKf1#>BJ$b5Yq!S0d*0Db*{$bP`UhO}phPI@G&JH3lmi9z<_HCkcQ}R!An;%Xyxm3Z9j6*ik<#0*pp3<< z(#MjyrsMPyeOKuwnTA#$1}{&7MXFk8&fXqJHB&QSu%|nAO5#~ClEL%a4m>gpvn5bJ zAYp+B#*WK}nMS*6P+q3Y>hEqKF%7wc(`J6|haf@%>lzUT#Ej#o1Q>7~tZi_VK1Ok-(#8u3YmB>%I>e>SVQ>u zju~mMdR?fj^Rt@k#dMvoXR7Lv$*vsYrFE#mt8$fzHvobIrytesa4BhF>4BZ><5Q1p!qgeKm7E22={tksEb z+7g}6*Q~)Ggd9Ve`Es>Km?Q4FzJnTeNJY?TKFnjA8feTOA^Lf2?f~<>i+`zkTAi^u zwpLY&SzrS4res|}nbXU+L?M?a@-iN9(h!gGDtUi73>ogKv&#dzRQnEKsXc!*_4Sh& zciTf7x;H}ms=r$XTVJ-*7>V#I_n#r}J!?4^*&r~j^fqStZ#FGMs!7&iz9k~Rx3URy zLEpM?>T>?vfS8iwQLM{!><|(kee|L?Xh~=plBn{3VKsi{l9ZPL^HQ`FF2T9bE&)Ds zllLfpF3_5jm4IKiOHO-u6f}`d(CWe`gbJB68+@!=p19J(qm-O1wq=0^ST{r}OY^_K zyuoY_SWb40!`mA(p}+2gck^V3WlTHGC`&<$^~P9njrjC^f4Mfu<)9N<@4?9;OwQM! z_v=L~%BaFl7o__xU9gB{sb}jhdcSmIwmnLXY(9hsu%Rt*Cd(r#2Ckg{r;B+2Mr#?e zn3PSe_vm?9dbxj}`O*VG=leGnc2B;W{@iX5m5ll%tA3hr_JwZ|XMf{$nt8*uoP2&L ze8+7((AR6AxBx|dD6tTM4W90zqHs;mPVTt96B;kCb^T2W1p&0W;05=iFAY3#*%pD+ zs@4&FKSo4**oAicWZrIu2Rx=&c4Q&8=^C0drvqpNoO4-rOo9#!7m8jtBZ=j?q0lrf z=X-*2jQVra@t2fJH}3-ZTGAp>wMYKGr})Ut)DOgbHE`x2qYUNG;7g|=Y`vvlw(s>5 zpl>~<@9$_v*||;Xd)wS@y!fKMHrB ztBBa?_2^f4uxMkG9NqTmrE(~iA^EkoNev-o&=c0269|Le+l0({;6lzhg+2VXD2Pc} z&g^Nw4ELu?9jqpvIzk2&>vcJJ!hY#%6qd}0GLaS=A2!MR0di?uaEEQsGP=|zyEsZL zw5cCY+mB@0(gT=7{xwl}F6>p79P$^`SIlW+{qgKBrn9P-wW`63B`4v{(|lADIsLir zLHTj`vucc0PN0Gz*?uqcFyrWx}R@ zj36`B$D#*4FYB_