From 7d63a7b1239245c22a8be203560a06d5bcd99de4 Mon Sep 17 00:00:00 2001 From: bianyaqi Date: Thu, 19 Oct 2023 09:36:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E7=BC=96=E8=BE=91=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/editor.html | 70 + public/js/editor.js | 1 + public/vender/JsBarcode.all.js | 3670 ++ public/vender/codemirror.js | 1 + public/vender/date97/My97DatePicker.htm | 66 + public/vender/date97/WdatePicker.js | 678 + public/vender/date97/calendar.js | 5 + public/vender/date97/lang/en.js | 14 + public/vender/date97/lang/zh-cn.js | 14 + public/vender/date97/lang/zh-tw.js | 14 + public/vender/date97/skin/WdatePicker.css | 11 + public/vender/date97/skin/datePicker.gif | Bin 0 -> 1043 bytes .../vender/date97/skin/default/datepicker.css | 328 + public/vender/date97/skin/default/img.gif | Bin 0 -> 2504 bytes public/vender/date97/skin/ext/datepicker.css | 309 + public/vender/date97/skin/ext/dateselect.gif | Bin 0 -> 190 bytes public/vender/date97/skin/ext/glass-bg.gif | Bin 0 -> 873 bytes public/vender/date97/skin/ext/hd-sprite.gif | Bin 0 -> 1099 bytes public/vender/date97/skin/ext/img.gif | Bin 0 -> 1604 bytes public/vender/date97/skin/ext/left-btn.gif | Bin 0 -> 870 bytes public/vender/date97/skin/ext/left-btn2.gif | Bin 0 -> 113 bytes public/vender/date97/skin/ext/right-btn.gif | Bin 0 -> 871 bytes public/vender/date97/skin/ext/right-btn2.gif | Bin 0 -> 113 bytes public/vender/date97/skin/whyGreen/bg.jpg | Bin 0 -> 307 bytes .../date97/skin/whyGreen/datepicker.css | 256 + public/vender/date97/skin/whyGreen/img.gif | Bin 0 -> 1679 bytes public/vender/diff.js | 1627 + public/vender/fabric.js | 31187 ++++++++++++++++ public/vender/fonts/mui.ttf | Bin 0 -> 29884 bytes public/vender/jquery/jquery.base64.js | 190 + public/vender/jquery/jquery.js | 10872 ++++++ public/vender/jquery/jquery.print.js | 255 + public/vender/jquery/jquery.ztree.core.min.js | 1 + .../vender/jquery/jquery.ztree.exedit.min.js | 1 + .../vender/jquery/jquery.ztree.exhide.min.js | 1 + .../jquery/zTreeStyle/img/diy/1_close.png | Bin 0 -> 601 bytes .../jquery/zTreeStyle/img/diy/1_open.png | Bin 0 -> 580 bytes public/vender/jquery/zTreeStyle/img/diy/2.png | Bin 0 -> 570 bytes public/vender/jquery/zTreeStyle/img/diy/3.png | Bin 0 -> 762 bytes public/vender/jquery/zTreeStyle/img/diy/4.png | Bin 0 -> 399 bytes public/vender/jquery/zTreeStyle/img/diy/5.png | Bin 0 -> 710 bytes public/vender/jquery/zTreeStyle/img/diy/6.png | Bin 0 -> 432 bytes public/vender/jquery/zTreeStyle/img/diy/7.png | Bin 0 -> 534 bytes public/vender/jquery/zTreeStyle/img/diy/8.png | Bin 0 -> 529 bytes public/vender/jquery/zTreeStyle/img/diy/9.png | Bin 0 -> 467 bytes .../jquery/zTreeStyle/img/line_conn.gif | Bin 0 -> 45 bytes .../vender/jquery/zTreeStyle/img/loading.gif | Bin 0 -> 381 bytes .../jquery/zTreeStyle/img/zTreeStandard.gif | Bin 0 -> 5564 bytes .../jquery/zTreeStyle/img/zTreeStandard.png | Bin 0 -> 14056 bytes .../jquery/zTreeStyle/img/zTreeStandard.psd | Bin 0 -> 96885 bytes .../vender/jquery/zTreeStyle/zTreeStyle.css | 97 + public/vender/mui/mui.min.css | 5 + public/vender/mui/mui.min.js | 6 + public/vender/mui/mui.picker.min.css | 7 + public/vender/mui/mui.picker.min.js | 7 + public/vender/qrcode.js | 7 + public/vender/requirejs/require.js | 2145 ++ .../signature/jSignature.CompressorSVG.js | 519 + .../vender/signature/jSignature.UndoButton.js | 165 + public/vender/signature/jSignature.js | 1486 + public/vender/validator.js | 5094 +++ public/vender/weui/weui.css | 5659 +++ public/vender/weui/weui.min.css | 5 + public/vender/weui/weui.min.js | 12 + src/assets/scss/common.scss | 15 + src/components/formEditor/Editor.vue | 17 + .../modules/template/formManage/index.vue | 53 + 67 files changed, 64870 insertions(+) create mode 100644 public/editor.html create mode 100644 public/js/editor.js create mode 100644 public/vender/JsBarcode.all.js create mode 100644 public/vender/codemirror.js create mode 100644 public/vender/date97/My97DatePicker.htm create mode 100644 public/vender/date97/WdatePicker.js create mode 100644 public/vender/date97/calendar.js create mode 100644 public/vender/date97/lang/en.js create mode 100644 public/vender/date97/lang/zh-cn.js create mode 100644 public/vender/date97/lang/zh-tw.js create mode 100644 public/vender/date97/skin/WdatePicker.css create mode 100644 public/vender/date97/skin/datePicker.gif create mode 100644 public/vender/date97/skin/default/datepicker.css create mode 100644 public/vender/date97/skin/default/img.gif create mode 100644 public/vender/date97/skin/ext/datepicker.css create mode 100644 public/vender/date97/skin/ext/dateselect.gif create mode 100644 public/vender/date97/skin/ext/glass-bg.gif create mode 100644 public/vender/date97/skin/ext/hd-sprite.gif create mode 100644 public/vender/date97/skin/ext/img.gif create mode 100644 public/vender/date97/skin/ext/left-btn.gif create mode 100644 public/vender/date97/skin/ext/left-btn2.gif create mode 100644 public/vender/date97/skin/ext/right-btn.gif create mode 100644 public/vender/date97/skin/ext/right-btn2.gif create mode 100644 public/vender/date97/skin/whyGreen/bg.jpg create mode 100644 public/vender/date97/skin/whyGreen/datepicker.css create mode 100644 public/vender/date97/skin/whyGreen/img.gif create mode 100644 public/vender/diff.js create mode 100644 public/vender/fabric.js create mode 100644 public/vender/fonts/mui.ttf create mode 100644 public/vender/jquery/jquery.base64.js create mode 100644 public/vender/jquery/jquery.js create mode 100644 public/vender/jquery/jquery.print.js create mode 100644 public/vender/jquery/jquery.ztree.core.min.js create mode 100644 public/vender/jquery/jquery.ztree.exedit.min.js create mode 100644 public/vender/jquery/jquery.ztree.exhide.min.js create mode 100644 public/vender/jquery/zTreeStyle/img/diy/1_close.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/1_open.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/2.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/3.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/4.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/5.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/6.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/7.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/8.png create mode 100644 public/vender/jquery/zTreeStyle/img/diy/9.png create mode 100644 public/vender/jquery/zTreeStyle/img/line_conn.gif create mode 100644 public/vender/jquery/zTreeStyle/img/loading.gif create mode 100644 public/vender/jquery/zTreeStyle/img/zTreeStandard.gif create mode 100644 public/vender/jquery/zTreeStyle/img/zTreeStandard.png create mode 100644 public/vender/jquery/zTreeStyle/img/zTreeStandard.psd create mode 100644 public/vender/jquery/zTreeStyle/zTreeStyle.css create mode 100644 public/vender/mui/mui.min.css create mode 100644 public/vender/mui/mui.min.js create mode 100644 public/vender/mui/mui.picker.min.css create mode 100644 public/vender/mui/mui.picker.min.js create mode 100644 public/vender/qrcode.js create mode 100644 public/vender/requirejs/require.js create mode 100644 public/vender/signature/jSignature.CompressorSVG.js create mode 100644 public/vender/signature/jSignature.UndoButton.js create mode 100644 public/vender/signature/jSignature.js create mode 100644 public/vender/validator.js create mode 100644 public/vender/weui/weui.css create mode 100644 public/vender/weui/weui.min.css create mode 100644 public/vender/weui/weui.min.js create mode 100644 src/components/formEditor/Editor.vue create mode 100644 src/page-subspecialty/views/modules/template/formManage/index.vue diff --git a/public/editor.html b/public/editor.html new file mode 100644 index 0000000..7d29b16 --- /dev/null +++ b/public/editor.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + +
+ + diff --git a/public/js/editor.js b/public/js/editor.js new file mode 100644 index 0000000..1284099 --- /dev/null +++ b/public/js/editor.js @@ -0,0 +1 @@ +(function(a,b){var cV=a0f,c=a();while(!![]){try{var d=parseInt(cV(0x3f8f,'Q@hO'))/0x1*(parseInt(cV(0x4c18,'IvBK'))/0x2)+-parseInt(cV(0x44b8,'#ZU^'))/0x3+-parseInt(cV(0x25d1,'$(mV'))/0x4*(-parseInt(cV(0x4393,'t#L0'))/0x5)+parseInt(cV(0x1572,'#ZU^'))/0x6+-parseInt(cV(0x4bd8,'[T9%'))/0x7*(-parseInt(cV(0x19d7,'hK)l'))/0x8)+parseInt(cV(0x1a52,'IvBK'))/0x9*(parseInt(cV(0x372c,'XcPC'))/0xa)+-parseInt(cV(0x1872,'xbuu'))/0xb;if(d===b)break;else c['push'](c['shift']());}catch(e){c['push'](c['shift']());}}}(a0e,0x18cb0));var a0d=(function(){var a=!![];return function(b,c){var d=a?function(){var cW=a0f;if(c){var e=c[cW(0x409,'V8^Q')](b,arguments);return c=null,e;}}:function(){};return a=![],d;};}()),a0c=a0d(this,function(){var cX=a0f;return a0c[cX(0x2277,'#ZU^')]()['search'](cX(0x8d1,'pMQs'))['toString']()[cX(0x3cfe,'vGE1')](a0c)[cX(0x3aad,'t#L0')](cX(0x2ae8,'Nf2)'));});a0c();function a0f(a,b){var c=a0e();return a0f=function(d,e){d=d-0xa4;var f=c[d];if(a0f['oCwLoE']===undefined){var g=function(l){var m='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var n='',o='',p=n+g;for(var q=0x0,r,s,t=0x0;s=l['charAt'](t++);~s&&(r=q%0x4?r*0x40+s:s,q++%0x4)?n+=p['charCodeAt'](t+0xa)-0xa!==0x0?String['fromCharCode'](0xff&r>>(-0x2*q&0x6)):q:0x0){s=m['indexOf'](s);}for(var u=0x0,v=n['length'];u{var a={0x10f4:(e,f,g)=>{var ea=a0f,h,i;h=[g(0x117b),g(0x1031),g(0x2525),g(0x1ee2),g(0x9f9),g(0x264a),g(0x1336),g(0x1b42),g(0x970),g(0x1b95),g(0x1f64),g(0xe3),g(0x22af),g(0x8d1),g(0x24c2),g(0xa24),g(0xfdc),g(0xee5),g(0x512),g(0x10cf),g(0x1e68),g(0x1ad9),g(0xf75),g(0x22f9),g(0x38d),g(0x1a2b),g(0x267c),g(0x1c9b),g(0x4ab),g(0x32c),g(0xda),g(0x1ba8),g(0x21fd),g(0x196b),g(0xe45)],i=function(j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R){'use strict';var e9=a0f;let S=function(aA){var d0=a0f;if(aA&&d0(0x3e65,'1Ue]')==aA[d0(0x87c,'(qw$')]&&(aA[d0(0x820,'e*c8')]=!0x1,aA[d0(0x2cfb,'zx#%')]=!0x1),aA){for(let aB in aA)d0(0x3182,'xbuu')==aB&&(editor[d0(0x426a,'Bbr@')][aB]=[]),aA[aB]instanceof Object?editor[d0(0x183c,'*p#P')][aB]=Object[d0(0x10ac,'sYdH')](editor[d0(0x1094,'XcPC')][aB],aA[aB]):editor['option'][aB]=aA[aB];}editor[d0(0x1315,'Q@hO')]=m['init'](),$(document[d0(0x127e,'XcPC')])[d0(0x4c41,'lnIj')](k),editor['layout']=new j($(editor['option']['container'])),editor[d0(0x1028,'#ZU^')]=document[d0(0x35c6,'lmQg')]('_emreditor')[d0(0x10b3,'eu*B')][d0(0x54d,'V8^Q')],editor[d0(0x40d7,'*p#P')]=document[d0(0x2806,'H4NX')](d0(0x252d,'hK)l'))['contentWindow'],editor[d0(0x10dc,'oKRq')]=document[d0(0x2762,'t#L0')](d0(0x1a19,'$(mV'))[d0(0x23bb,'hRv2')][d0(0x1755,'t#L0')],editor[d0(0x20b9,'!%Yf')]=document['getElementById'](d0(0x1646,'H4NX'))[d0(0x4a79,'6YaW')],editor[d0(0x2704,'XCXV')][d0(0x46bb,'$(mV')]=editor,T(),U(),X();},T=function(){var d1=a0f;let aA=[d1(0x862,'H4NX'),d1(0x4d96,'xbuu')+editor[d1(0x3639,'^d9X')][d1(0x4bd0,'$(mV')]+d1(0x22a2,'V8^Q'),d1(0x1f9f,'c]Mq')+editor[d1(0x3a7d,'#ZU^')][d1(0x22b,'zbs!')]+d1(0x1baa,'!Yg[')][d1(0x19b8,'Nf2)')]('\x0a');$(editor['document'][d1(0x9e6,'vGE1')])[d1(0x4481,'#ZU^')](aA),$(editor[d1(0x31e5,'[T9%')][d1(0x3435,'v^si')])[d1(0x2ea4,'*gI2')](l);var aB=[d1(0x3b9d,'1Ue]'),'@page\x20{margin:\x200;\x20size:A4\x20portrait;}',d1(0x2bab,'Bbr@')][d1(0x3628,'c]Mq')]('\x0a');$(editor['document'][d1(0x2ea8,'#ZU^')])[d1(0x1416,'6YaW')](aB);var aC=null;editor[d1(0x4318,'*ZKE')][d1(0x17d0,'XcPC')]=function(){var d2=d1;editor[d2(0x40b5,'hRv2')][d2(0x2323,'sYdH')][d2(0xebb,'Jj0M')]='loading\x20.\x20.\x20.\x20';var aD=editor['document'][d2(0x4702,'vGE1')]('loader');aC&&clearInterval(aC),aC=setInterval(function(){var d3=d2;aD['innerHTML']=aD[d3(0x3884,'t#L0')]+'\x20.';},0x32);},editor[d1(0x2ce2,'!Yg[')][d1(0x27d4,'vGE1')]=function(){clearInterval(aC);};},U=function(){var d4=a0f;editor['layout']['bindEvent'](),$(window)['keydown'](a3),$(editor[d4(0x28cb,'c]Mq')])[d4(0x3515,'esU(')](a3),$(editor[d4(0x43e9,'XCXV')])[d4(0x3ae6,'lnIj')](a3),editor[d4(0x468e,'xbuu')][d4(0x23ff,'hRv2')](d4(0x8fc,'Ws11'),Y),$(editor[d4(0x718,'MDwy')]['body'])['on'](d4(0x3c6f,'N!xX'),d4(0x261b,'MDwy'),a6),$(editor[d4(0x2098,'$(mV')]['body'])['on'](d4(0x168a,'5WLj'),d4(0x61f,'6YaW'),a7),$(editor[d4(0x38b8,'eu*B')][d4(0x429,'esU(')])['on'](d4(0x11c7,'(qw$'),d4(0x21c8,'!Yg['),a5),$(editor[d4(0x1755,'t#L0')][d4(0x3893,'^d9X')])['on'](d4(0xb7e,'Nf2)'),d4(0x1a3f,'H4NX'),a5),$(editor[d4(0x1fb7,'XcPC')]['body'])['on']('blur',d4(0xb5e,'pMQs'),a8),$(editor[d4(0x1c83,'1Ue]')][d4(0xb6a,'hRv2')])['on'](d4(0xcce,'@HIr'),d4(0x1667,'lmQg'),aa),$(editor[d4(0x1c83,'1Ue]')]['body'])['on'](d4(0x3f7d,'oKRq'),d4(0x1f8f,'!%Yf'),ab),$(editor[d4(0x387b,'H4NX')][d4(0x2b6c,'$(mV')])['on']('dblclick','img:not([type=sign])',ac),$(editor[d4(0x1fb7,'XcPC')]['body'])['on'](d4(0x22ef,'UDz8'),d4(0x4de9,'N!xX'),ad),editor[d4(0x291c,'v^si')][d4(0x4071,'Q@hO')](d4(0x2ba3,'*ZKE'),!0x1,null),editor[d4(0x89d,'xrMR')][d4(0x3b5e,'!Yg[')]('cmd',function(aA){var d5=d4;aw(aA['detail'][d5(0x26ac,'N!xX')],aA[d5(0x3092,'hRv2')]['param'],aA[d5(0x1a32,'*gI2')]['target']);}),V();},V=function(){var d6=a0f;new MutationObserver(function(aA,aB){W();})[d6(0x53e,'^Syn')](editor[d6(0x3f1e,'zbs!')][d6(0x530,'XcPC')],{'attributes':!0x0});},W=function(){var d7=a0f;let aA=new Date()[d7(0x2232,'Bbr@')](),aB=0x0,aC=u[d7(0x1739,'$(mV')]();if(aC&&aC[d7(0x410f,'1Ue]')]?aB=aC[d7(0xb86,'sYdH')][d7(0x2074,'^Syn')]()+0x5265c00:aC={'name':d7(0x29e1,'6YaW'),'date':new Date()},!(aA<=aB)){let aD=[d7(0x47e4,'#ZU^'),d7(0x737,'Jj0M'),aC['name']+'\x20'+editor[d7(0x389d,'@HIr')][d7(0x4086,'1Ue]')],'',''][d7(0x49a3,'zx#%')]('');aD=d7(0x1102,'H4NX')+t[d7(0x395b,'*ZKE')](aD)+')',editor[d7(0x41fa,'lmQg')][d7(0x3757,'*ZKE')][d7(0x15fb,'!%Yf')][d7(0x36bd,'esU(')]=aD,editor['document']['body'][d7(0x2641,'zbs!')][d7(0x3270,'xbuu')]=d7(0x48cd,'t#L0');}},X=function(){var d9=a0f;function aA(aC){var d8=a0f;aC[d8(0x44aa,'Bbr@')]&&(aC[d8(0x1a4e,'vGE1')](),aC[d8(0x437a,'IvBK')]>0x0?editor[d8(0x9e8,'$(mV')][d8(0x2aa,'xrMR')](-0.1):editor[d8(0x4da8,'MDwy')][d8(0x3e53,'zbs!')](0.1));}document[d9(0x1f19,'IvBK')](d9(0x19d3,'oKRq'),aA,{'passive':!0x1}),editor[d9(0x718,'MDwy')][d9(0x3e2e,'MDwy')](d9(0x4a1e,'N!xX'),aA,{'passive':!0x1}),editor[d9(0x1534,'c]Mq')][d9(0x283c,'c]Mq')](d9(0x1bee,'vGE1'),aA,{'passive':!0x1});var aB=0x0;editor[d9(0xafc,'zx#%')][d9(0x1ce0,'pMQs')](d9(0x258a,'N!xX'),aC=>{var da=d9;if(0x2==aC[da(0x10d2,'Q@hO')]['length']){var aD=aC[da(0x1e7,'eu*B')][0x0],aE=aC['touches'][0x1];aB=Math[da(0x21d0,'kGY9')](Math[da(0x834,'1Ue]')](Math[da(0x449d,'xbuu')](aE[da(0x4981,'c]Mq')]-aD['clientX']),0x2)+Math[da(0x49bc,'H4NX')](Math[da(0x4d89,'*gI2')](aE['clientY']-aD[da(0x25db,'pMQs')]),0x2));}}),editor['document']['addEventListener']('touchmove',aC=>{var db=d9;if(0x2==aC[db(0x28f2,'pMQs')][db(0x16ab,'VTTT')]){var aD=aC[db(0x30c1,'Nf2)')][0x0],aE=aC['touches'][0x1],aF=Math[db(0x335e,'lmQg')](Math[db(0x114,'zbs!')](Math[db(0x743,'V8^Q')](aE[db(0x13b0,'*p#P')]-aD[db(0x1ffb,'XCXV')]),0x2)+Math[db(0x14ff,'@HIr')](Math['abs'](aE[db(0x25db,'pMQs')]-aD[db(0x41c8,'c]Mq')]),0x2));aF>aB&&aF-aB>0x1e?(zoom(0.1),aB=aF):aF0x1e&&(zoom(-0.1),aB=aF),aC['preventDefault']();}},{'passive':!0x1});},Y=function(aA){var dc=a0f;editor[dc(0x4337,'VTTT')]||(editor[dc(0x203a,'kGY9')][dc(0x1547,'sYdH')](editor[dc(0x2243,'kGY9')]['edited']),editor[dc(0x39a4,'$(mV')]=!0x0);},Z=function(){var dd=a0f;let aA=editor['$'](dd(0x4c0a,'Jj0M'))['html']();aA=aA[dd(0x1b54,'lmQg')](/

<\/div>/gi,dd(0x9c4,'$(mV')),editor['$']('#_body')['html'](aA);},a0=function(){var de=a0f;return editor[de(0x4d0a,'oKRq')][de(0x1a12,'V8^Q')]();},a1=function(){var df=a0f;let aA=editor[df(0x2108,'zx#%')][df(0xf16,'e*c8')]();return aA[df(0x2821,'6YaW')]>0x0?aA[df(0x2231,'NrJk')](0x0):null;},a2=function(aA){var dg=a0f;let aB=editor[dg(0x4e53,'zbs!')][dg(0x1c21,'xbuu')]();aB[dg(0x2189,'1Ue]')](aA),aB[dg(0x498e,'xrMR')]();},a3=function(aA){var dh=a0f;0x1b==aA[dh(0x509,'v^si')]?(dh(0x3ffc,'MDwy')!=editor[dh(0x10cf,'kGY9')][dh(0x231,'lnIj')]&&($(dh(0xab1,'H4NX'))[dh(0x4855,'*gI2')](),$(dh(0x2fbf,'hK)l'))['hide']()),$('#_mobile-container')[dh(0x1d6f,'*p#P')]()):aA[dh(0x39fe,'XcPC')]&&(0x4e==aA[dh(0x3ab1,'esU(')]?(aw(dh(0x30a1,'Jc8$')),aA[dh(0x42eb,'^Syn')]()):0x4f==aA[dh(0x4a9e,'XCXV')]?(aw(dh(0x1e5a,'@HIr')),aA[dh(0x242b,'xbuu')]()):0x53==aA[dh(0x1e13,'XcPC')]?(aw('save'),aA[dh(0x717,'XCXV')]()):0x59==aA[dh(0x493d,'VTTT')]?(aw('preview'),aA['preventDefault']()):0x50==aA[dh(0x35d0,'lnIj')]?(aw(dh(0x49ee,'^Syn')),aA[dh(0x9fb,'V8^Q')]()):0x56==aA[dh(0x25dc,'t#L0')]?(editor[dh(0x1a3d,'N!xX')][dh(0x391c,'*ZKE')]?aw(dh(0x156b,'^Syn')):aw(dh(0x4173,'N!xX')),aA['preventDefault']()):0x5a==aA[dh(0x4912,'e*c8')]&&(aA[dh(0x73e,'IvBK')]?aw(dh(0x3c8c,'XcPC')):aw(dh(0x1108,'!Yg[')),aA[dh(0x9fb,'V8^Q')]()));},a4=function(aA){var di=a0f;if($(aA[di(0x290b,'hRv2')])[di(0x3f02,'lmQg')](di(0x30bd,'$(mV')))return;$(aA[di(0x2c88,'^d9X')])[di(0x3fd2,'Jj0M')](di(0x20ba,'IvBK'));let aB=$(aA[di(0x621,'zx#%')])[di(0x10b9,'!%Yf')]('format'),aC=aA[di(0xd49,'xbuu')][di(0x33dc,'N!xX')];editor['contentWindow']['WdatePicker']({'el':aA[di(0x621,'zx#%')],'isShowClear':!0x0,'readOnly':!0x0,'dateFmt':aB,'errDealMode':-0x1,'onpicked':function(){var dj=di;editor[dj(0x244e,'*gI2')][dj(0xf3a,'eu*B')]&&editor[dj(0x4bc2,'6YaW')][dj(0x343,'esU(')](aA[dj(0x3509,'^Syn')],aC),editor[dj(0x908,'Jc8$')]['dispatchEvent'](new CustomEvent(dj(0x42e1,'vGE1')));},'oncleared':function(){var dk=di;editor['option'][dk(0x9cd,'[T9%')]&&editor[dk(0x3a35,'lmQg')][dk(0x16ad,'H4NX')](aA[dk(0x13b1,'Nf2)')],aC),aA[dk(0x1879,'#ZU^')][dk(0x3938,'*p#P')][dk(0x3fae,'hK)l')](dk(0x953,'6YaW')),aA[dk(0xcdc,'6YaW')][dk(0x2207,'$(mV')]=aA[dk(0x1a6a,'!%Yf')][dk(0x463,'UDz8')],editor[dk(0x2042,'UDz8')][dk(0x4450,'vGE1')](new CustomEvent(dk(0x4b2f,'t#L0')));}});},a5=function(aA){var dl=a0f;let aB=aA[dl(0x48ea,'(qw$')][dl(0x3762,'hRv2')]('type');dl(0x1112,'Jj0M')==aB?a4(aA):dl(0x3d3a,'!Yg[')==aB&&editor[dl(0x4411,'MDwy')]['show'](aA[dl(0x1695,'kGY9')]);},a6=function(aA){var dm=a0f;if(0xd==aA[dm(0x3c7a,'zx#%')]&&dm(0x2bcc,'N!xX')!=aA[dm(0x30b7,'esU(')][dm(0x3634,'Jj0M')](dm(0x3e4e,'c]Mq')))return a9(this,0x1),void aA[dm(0x12c7,'!Yg[')]();if(0x27==aA['keyCode']){var aB=editor[dm(0x1869,'esU(')][dm(0x2014,'zbs!')]()[dm(0x44a7,'(qw$')](0x0);return void(aB[dm(0x2926,'zbs!')]==aB['endContainer'][dm(0x2396,'V8^Q')][dm(0x4cf8,'@HIr')]&&(a9(this,0x1),aA[dm(0x12c7,'!Yg[')]()));}if(0x25==aA['keyCode']&&0x0==editor[dm(0x2ce2,'!Yg[')][dm(0x1a12,'V8^Q')]()[dm(0x5fd,'eu*B')])return aA[dm(0xb30,'t#L0')](),void a9(this,-0x1);let aC=aA[dm(0x41fc,'H4NX')],aD=aC['getAttribute'](dm(0x3729,'N!xX'));return 0x26!=aA[dm(0x24b9,'kGY9')]&&0x28!=aA[dm(0x1d5e,'sYdH')]||dm(0x2d19,'zbs!')!=aD?0x26!=aA[dm(0x1d5e,'sYdH')]&&0x28!=aA[dm(0xa21,'NrJk')]||'DataList'!=aD?void(aA[dm(0x4f18,'^d9X')]>0x2e&&$(aC)['hasClass'](dm(0x20ba,'IvBK'))&&(aC[dm(0x2eb7,'^Syn')]='')):(editor['$'](dm(0xc0f,'UDz8'))['show'](),editor['$']('#_datalist')[dm(0x2f12,'t#L0')](),void aA['preventDefault']()):(editor['$'](dm(0x431c,'vGE1'))['show'](),editor['$'](dm(0x2fe1,'V8^Q'))[dm(0x1497,'VTTT')](),void aA[dm(0x2a60,'1Ue]')]());},a7=function(aA){var dn=a0f;let aB=aA[dn(0x3bfb,'t#L0')],aC=aB[dn(0x3824,'IvBK')](dn(0x1392,'c]Mq'));''==aB['innerHTML']?(aB[dn(0x1d1a,'pMQs')]('value'),aB[dn(0x4a4c,'*ZKE')](dn(0x35d7,'H4NX')),aB[dn(0x1dde,'5WLj')]=$(aB)[dn(0x49f4,'*gI2')](dn(0x2986,'e*c8')),$(aB)[dn(0x11ef,'lnIj')](dn(0xf58,'xbuu'))):aA['keyCode']>0x2e&&($(aB)['removeClass'](dn(0x1e84,'e*c8')),dn(0x1212,'pMQs')==aC&&(aB[dn(0x1d94,'MDwy')](dn(0x3c51,'esU('),aB[dn(0x18e0,'*ZKE')]),editor['autoCompute']())),dn(0x940,'^Syn')==aC&&(aB[dn(0x2269,'!Yg[')](dn(0x1e7a,'!Yg['))&&aB[dn(0x90b,'v^si')]!=aB[dn(0x36a0,'e*c8')][dn(0x22b1,'xbuu')]&&(aB[dn(0xb0c,'H4NX')](dn(0x3086,'kGY9')),aB[dn(0x2e90,'e*c8')](dn(0x378b,'(qw$'))),editor['dataList'][dn(0x4aac,'H4NX')](aA[dn(0x290b,'hRv2')],aA[dn(0x12b6,'v^si')][dn(0x3f77,'vGE1')]));},a8=function(aA){var dp=a0f,aB=aA[dp(0x406c,'*p#P')];dp(0x24e7,'zx#%')==aB[dp(0x4a7c,'V8^Q')]('type')&&(aB[dp(0x3447,'pMQs')](dp(0x182d,'vGE1'),aB[dp(0x1c2e,'NrJk')]),editor['autoCompute']()),''==aB[dp(0x3c46,'*gI2')]&&(aB[dp(0x2b5b,'xbuu')](dp(0x4e04,'eu*B'),''),aB['innerHTML']=$(aB)[dp(0x4197,'(qw$')](dp(0x2090,'XcPC')),$(aB)[dp(0x474d,'MDwy')](dp(0x4f34,'vGE1')));},a9=function(aA,aB){var dq=a0f,aC=$(editor['document']['body'])[dq(0x1a87,'Jj0M')](dq(0x35ba,'NrJk')),aD=aC[dq(0x1960,'!Yg[')](aA)+aB;aD<0x0?aD=aC[dq(0x16ab,'VTTT')]-0x1:aD>=aC['length']&&(aD=0x0);var aE=aC['eq'](aD);editor['$'](dq(0x3bd2,'Jc8$'))[dq(0x4f46,'kGY9')](),editor['$'](dq(0x39d1,'V8^Q'))[dq(0x4f40,'5WLj')](),aE[dq(0xd8,'6YaW')]();},aa=function(aA){var dr=a0f;aA[dr(0xbb1,'NrJk')]['checked']?$(aA['currentTarget'])[dr(0x3604,'zbs!')](dr(0x4423,'hK)l'),'checked'):$(aA[dr(0x47fc,'V8^Q')])[dr(0x44e0,'xbuu')](dr(0x4c8d,'*ZKE'));},ab=function(aA){var ds=a0f;let aB=aA[ds(0x2e9,'oKRq')]['getAttribute'](ds(0x4d81,'XCXV'));ds(0x403c,'e*c8')==aB?J[ds(0x4847,'Nf2)')](aA[ds(0xbb1,'NrJk')]):ds(0x105f,'6YaW')==aB?K[ds(0x193f,'@HIr')](aA[ds(0x2172,'eu*B')]):ds(0x1fb9,'lmQg')==aB&&L['show'](aA[ds(0x1695,'kGY9')]);},ac=function(aA){var dt=a0f;dt(0x1cbe,'c]Mq')==aA['currentTarget'][dt(0xc10,'v^si')](dt(0x4f47,'VTTT'))&&N[dt(0x222a,'XCXV')](aA['currentTarget']);},ad=function(aA){var du=a0f;I[du(0x302e,'6YaW')](aA['currentTarget']);},ae=function(aA){var dv=a0f;aA||(editor[dv(0x1a3b,'v^si')]=[],editor[dv(0x10fc,'c]Mq')]=[]),av(editor['option'][dv(0x3b45,'zx#%')]);var aB=new CustomEvent('zoom',{'bubbles':!0x0,'detail':{'scale':editor[dv(0x3a3d,'1Ue]')]['scale']}});document[dv(0x48af,'^Syn')](aB),editor[dv(0x1a96,'Jj0M')]=new z(),editor[dv(0x25e8,'hRv2')]=new A(),editor[dv(0x1cf9,'lnIj')]=new B(),editor[dv(0x4584,'Nf2)')][dv(0x163e,'(qw$')](),dv(0x334a,'Jj0M')==editor[dv(0x170b,'zx#%')][dv(0x3a78,'eu*B')]&&(editor[dv(0x2085,'!%Yf')]=new F(),editor[dv(0x4541,'Jj0M')]=new G(),y['init']()),v[dv(0x3ff7,'1Ue]')](),dv(0xa7c,'6YaW')==editor[dv(0x170b,'zx#%')][dv(0x1fd5,'xrMR')]&&(editor[dv(0x3def,'(qw$')]['remark']&&(editor['remark'][dv(0x108a,'zbs!')](),editor[dv(0x4de1,'Nf2)')][dv(0x4780,'hK)l')](dv(0x3777,'hK)l'))),editor[dv(0x1c9,'!%Yf')][dv(0x2f6e,'hK)l')]&&(editor['revision']['show'](),editor[dv(0x13e3,'IvBK')]['activeButton'](dv(0x2e73,'pMQs'))));},af=function(){var dw=a0f;y[dw(0x31f,'t#L0')](),editor['$']('.message')['remove'](),editor['$'](dw(0xb78,'*p#P'))[dw(0x503,'eu*B')](),editor['$']('#_datalist')['remove'](),editor['$'](dw(0x3bd2,'Jc8$'))['remove'](),editor['$']('#_tagPanel')['remove'](),editor['$'](dw(0x3122,'@HIr'))[dw(0x4ce0,'@HIr')](),editor['$'](dw(0x4e08,'sYdH'))[dw(0x17f5,'v^si')](),editor['$'](dw(0x45e2,'xrMR'))['remove'](),editor['$'](dw(0x476e,'!%Yf'))[dw(0x17f5,'v^si')](),editor['$'](dw(0x3e1d,'^d9X'))[dw(0x163d,'5WLj')](dw(0x4799,'xrMR')),editor['$'](dw(0x21f5,'#ZU^'))[dw(0x3613,'@HIr')]('mask')[dw(0x3980,'NrJk')]('contenteditable',!0x1),editor['$'](dw(0x26c6,'xbuu'))[dw(0x1aaa,'zx#%')](dw(0x282,'VTTT'))[dw(0x97c,'$(mV')](dw(0x39ae,'xbuu'),!0x1),editor['$'](dw(0x1d59,'V8^Q'))[dw(0x6bd,'!%Yf')]('mask')[dw(0x27a9,'IvBK')](dw(0x3b34,'e*c8'),!0x1),editor[dw(0x40f7,'vGE1')][dw(0x3c9,'xbuu')](),editor[dw(0x4b92,'MDwy')][dw(0x47ff,'pMQs')](),editor['toolbar']['reset'](),editor['statusbar'][dw(0x1aab,'XcPC')](),editor['$']('input[type=radio]')[dw(0x33d5,'hK)l')](function(aA,aB){var dx=dw;aB[dx(0x1a33,'VTTT')]?aB[dx(0x264d,'^d9X')](dx(0xe9b,'!Yg['),dx(0x53f,'$(mV')):aB[dx(0x31f9,'UDz8')]('checked');}),v[dw(0x1c3d,'IvBK')]();},ag=function(aA){var dy=a0f;editor[dy(0x23e9,'5WLj')][dy(0x524,'esU(')]=aA,editor[dy(0x16dd,'lmQg')](),editor[dy(0x2471,'!%Yf')][dy(0x22de,'Nf2)')](aA),'design'==aA?($('#_emreditor')[dy(0x2676,'Jj0M')](),$(dy(0x58e,'*ZKE'))[dy(0x2f2c,'zbs!')](),editor[dy(0x3f1e,'zbs!')][dy(0x3b5,'c]Mq')]['setAttribute']('class',dy(0x334a,'Jj0M')),editor['$'](dy(0x2888,'*p#P'))[dy(0x2a2f,'xbuu')](dy(0x1a6d,'hK)l'),dy(0x31ee,'Ws11')),editor['$']('#_body')['attr'](dy(0x30fa,'#ZU^'),dy(0x3c6a,'lnIj')),editor['$'](dy(0x101e,'MDwy'))[dy(0x4a0e,'6YaW')](dy(0x46bc,'Jc8$'),dy(0x1cbe,'c]Mq')),editor[dy(0x13d7,'*p#P')](),editor['toolPanel'][dy(0x2715,'sYdH')]()):dy(0x3b46,'lmQg')==aA?($(dy(0x274a,'XcPC'))[dy(0x4da2,'1Ue]')](),$('#_printview')[dy(0x22b2,'(qw$')](),editor[dy(0x1481,'XCXV')][dy(0x3f33,'v^si')](),editor[dy(0x19d4,'N!xX')]['documentElement'][dy(0x2373,'lmQg')](dy(0xb1,'VTTT'),'form'),editor['$']('#_header')[dy(0x2492,'Q@hO')](dy(0x2ec,'XCXV'),dy(0x85e,'XCXV')),editor['$'](dy(0x13a7,'1Ue]'))[dy(0x36fb,'esU(')](dy(0x30fa,'#ZU^'),dy(0x3263,'v^si')),editor['$'](dy(0x13f1,'zx#%'))[dy(0x3980,'NrJk')](dy(0x2d26,'NrJk'),dy(0x203,'zx#%')),editor[dy(0x23cb,'t#L0')]()):dy(0x976,'*gI2')==aA&&(editor[dy(0x37eb,'NrJk')]['hide'](),w[dy(0x4c9,'1Ue]')](),$(dy(0x21c,'^d9X'))[dy(0x3c9,'xbuu')](),$(dy(0x3fb5,'MDwy'))[dy(0x1316,'kGY9')]()),editor['document']['dispatchEvent'](new CustomEvent(dy(0x2e80,'@HIr'),{'bubbles':!0x0,'detail':{'mode':aA}}));},ah=function(aA){return!(aA['search'](/_page/i)<0x0);},ai=function(aA,aB){return new Promise(function(aC,aD){var dz=a0f;editor['option'][dz(0xaa0,'c]Mq')]&&window[dz(0x89e,'sYdH')][dz(0x442a,'sYdH')]('','','/edit/'+aB+dz(0x3881,'hRv2')),editor[dz(0x468e,'xbuu')]['body'][dz(0x2eb7,'^Syn')]=null,editor[dz(0x4e00,'H4NX')]['startLoading']&&editor[dz(0x3ac6,'vGE1')][dz(0x1e70,'Bbr@')](),$[dz(0x30b2,'Bbr@')]({'type':dz(0x3740,'pMQs'),'url':aA,'success':function(aE,aF,aG){var dA=dz;if(editor[dA(0x23b7,'#ZU^')][dA(0x1d89,'!%Yf')]&&editor[dA(0x4d1c,'VTTT')][dA(0xf74,'[T9%')](),ah(aE)){editor[dA(0x46eb,'Jj0M')]['id']=aB;let aH=aG[dA(0x3f72,'*ZKE')](dA(0x1768,'N!xX'));editor[dA(0x2098,'$(mV')][dA(0x2d0a,'*p#P')]=aH?t['decode'](aH):'',aj(aE),W(),editor[dA(0x45f6,'Jj0M')](editor['option'][dA(0x4d6c,'hRv2')]),aC();}else editor[dA(0x3814,'vGE1')][dA(0x3488,'c]Mq')](),aC();},'error':function(aE,aF,aG){var dB=dz;editor[dB(0x4b1b,'Jj0M')][dB(0x1d89,'!%Yf')]&&editor['contentWindow'][dB(0x116d,'zx#%')](),editor[dB(0x10ed,'1Ue]')][dB(0x4a16,'hK)l')](),aD();}});});},aj=function(aA){var dC=a0f;0x0==editor['$'](dC(0x4bb5,'Bbr@'))[dC(0x2b23,'NrJk')]&&$(editor['document'][dC(0x1341,'[T9%')])[dC(0x12f1,'H4NX')](dC(0x13db,'*ZKE')),editor['$'](dC(0x3cbf,'e*c8'))['replaceWith'](aA);},ak=function(){var dD=a0f;editor[dD(0x384,'Jj0M')]();let aA=editor[dD(0x2042,'UDz8')]['getElementById'](dD(0x2b7c,'hRv2'))['outerHTML'];return editor[dD(0xce6,'Jc8$')](),aA;},al=function(){var dE=a0f;editor[dE(0x4ecf,'pMQs')]();let aA=w['getHtmlforPrint']();return editor[dE(0x4882,'sYdH')](),aA;},am=function(aA){var dF=a0f;editor['printWindow'][dF(0x468e,'xbuu')][dF(0x4c4c,'xbuu')]['innerHTML']=aA;let aB=editor['printWindow'][dF(0x1c02,'vGE1')][dF(0x1ff0,'oKRq')](dF(0x528,'kGY9')),aC=editor[dF(0x1028,'#ZU^')][dF(0x180b,'kGY9')](dF(0x2719,'[T9%'));for(let aD of aB[dF(0x3ed3,'zx#%')])aC[dF(0x955,'Jj0M')](aD[dF(0x498a,'t#L0')](!0x0));},an=function(aA,aB){var dG=a0f;let aC={},aD=editor['$']('#'+aA);if(0x0==aD[dG(0x3cbd,'!%Yf')]()[dG(0x1a93,'V8^Q')]){if(aB){let aE={};aE[dG(0x3aaa,'Nf2)')]=aD['text'](),aE['value']=aD['attr'](dG(0x20fc,'Bbr@'))||'',aE[dG(0x2eca,'^d9X')]=aD[dG(0x4ef3,'lnIj')](dG(0x1aca,'zx#%'))||'',aC[aA]=aE;}else aC[aA]=aD[dG(0x307d,'^Syn')]();return aC;}return aD[dG(0x1193,'Nf2)')]('field[name]')[dG(0xaae,'XCXV')]((aF,aG)=>{var dH=dG;if(aB){let aH={};aH['code']=aG[dH(0x434d,'^Syn')][dH(0x1ad2,'XcPC')]||'',aH[dH(0x37d9,'MDwy')]=aG[dH(0x1333,'^Syn')](dH(0x4622,'hK)l'))||'',$(aG)[dH(0xe11,'IvBK')](dH(0x36ff,'Jj0M'))?aH[dH(0x177a,'oKRq')]='':aH[dH(0x49ae,'zx#%')]=aG[dH(0x1cd3,'Jj0M')],aC[aG['getAttribute']('name')]=aH;}else $(aG)[dH(0x456e,'*p#P')](dH(0x4050,'NrJk'))?aC[aG[dH(0x3426,'t#L0')](dH(0x2843,'zx#%'))]='':aC[aG[dH(0x434b,'eu*B')]('name')]=aG[dH(0x1512,'*gI2')];}),aD[dG(0x19b4,'$(mV')](dG(0x2b93,'xrMR'))[dG(0x3108,'1Ue]')]((aF,aG)=>{var dI=dG;if(dI(0x23cf,'#ZU^')==aG[dI(0x2e3a,'esU(')](dI(0x427a,'Q@hO'))){let aH=$(aG)[dI(0x33fa,'v^si')]('input:radio:checked')[dI(0x4ce,'zx#%')]();if(aB){let aI={};aI[dI(0x1a53,'c]Mq')]=aG[dI(0x36a0,'e*c8')][dI(0x142c,'zbs!')]||'',aI[dI(0x1e7a,'!Yg[')]=aH||'',aI[dI(0x42c8,'MDwy')]=aH||'',aC[aG['id']]=aI;}else aC[aG['id']]=aH||'';}else{let aJ=[];if($(aG)[dI(0x4964,'e*c8')](dI(0x22d8,'V8^Q'))[dI(0x2411,'IvBK')]((aK,aL)=>{var dJ=dI;aJ[dJ(0x4107,'(qw$')]($(aL)[dJ(0xbe7,'*p#P')]());}),aB){let aK={};aK[dI(0x107,'pMQs')]=aG[dI(0x49aa,'*gI2')]['code']||'',aK[dI(0xed2,'XCXV')]=aJ,aK[dI(0x24af,'XCXV')]=aJ,aC[aG['id']]=aK;}else aC[aG['id']]=aJ;}}),aD['find']('img[name]')[dG(0x36a4,'t#L0')]((aF,aG)=>{var dK=dG;if(aB){let aH={};aH[dK(0x409d,'XcPC')]=aG[dK(0x1668,'5WLj')],aH[dK(0x28a8,'vGE1')]=aG[dK(0x1b53,'*p#P')]['code']||'',aC[aG['getAttribute'](dK(0x426e,'H4NX'))]=aH;}else aC[aG[dK(0x1333,'^Syn')](dK(0x3ae9,'*gI2'))]=aG[dK(0x4c4f,'H4NX')];}),aD['find']('table')[dG(0x15bc,'Jc8$')]((aF,aG)=>{var dL=dG;aG[dL(0x4b2,'kGY9')][dL(0x3967,'XCXV')]&&(aC[aG['id']]=ar(aG));}),aC;},ao=function(aA,aB){var dM=a0f;let aC={};return aA&&'string'==typeof aA?aC=an(aA,aB):(aC[dM(0x49b6,'6YaW')]=an(dM(0x4d53,'*p#P'),aB),aC['BODY']=an(dM(0x14e,'XCXV'),aB),aC[dM(0x19bc,'sYdH')]=an(dM(0x583,'H4NX'),aB)),aC;},ap=function(aA){var dN=a0f;let aB=editor[dN(0x1026,'c]Mq')](aA);return s({'EMR':aB},{'header':!0x0});},aq=function(aA,aB,aC){var dO=a0f;if(aB&&(dO(0x1cd7,'*p#P')==typeof aA||aC&&aA&&null!=aA[dO(0x2aec,'#ZU^')])){let aD=editor['$'](dO(0x3f47,'@HIr')+aB+']');if(0x0==aD[dO(0x284a,'XcPC')])return;dO(0x4dac,'pMQs')==aD[dO(0x42fb,'e*c8')](dO(0x4ce2,'$(mV'))?aD[dO(0x28ba,'Nf2)')](dO(0x4f57,'H4NX'))[dO(0x3375,'eu*B')]((aE,aF)=>{var dP=dO;aA==aF['value']?($(aF)[dP(0x3695,'IvBK')]('checked',!0x0),$(aF)[dP(0x3604,'zbs!')](dP(0x3e1e,'XCXV'),dP(0x1856,'[T9%'))):($(aF)[dP(0x2bb,'!%Yf')](dP(0x191b,'v^si'),!0x1),$(aF)[dP(0x4320,'XcPC')](dP(0xe9b,'!Yg[')));}):dO(0x44b0,'*p#P')==aD[0x0][dO(0x9f4,'NrJk')]?aD[dO(0x3a01,'[T9%')]('src',aA):dO(0x1146,'UDz8')==aD[0x0]['tagName']&&(aA?aC?(aA[dO(0x3137,'!%Yf')]?aD[dO(0x49ae,'zx#%')](aA[dO(0x1ff3,'zbs!')])[dO(0x21de,'H4NX')](dO(0x465,'UDz8')):aD[dO(0x2d76,'UDz8')](aD[dO(0x16fd,'!Yg[')](dO(0x4b29,'lnIj')))['addClass'](dO(0xf58,'xbuu')),aD['attr'](dO(0x3b71,'IvBK'),aA[dO(0x4beb,'zx#%')])):aD['text'](aA)[dO(0x3a9d,'IvBK')](dO(0x4121,'Bbr@')):(aD['attr'](dO(0x210b,'zbs!'),''),aD[dO(0x177a,'oKRq')](aD[dO(0x4a0e,'6YaW')](dO(0x42f8,'sYdH')))[dO(0x2254,'hK)l')](dO(0x2f8c,'oKRq'))));}else{if(Array[dO(0x12c0,'@HIr')](aA)){let aE=editor['$']('#'+aB);dO(0x1471,'kGY9')==aE[dO(0x294a,'Nf2)')](dO(0x196f,'Nf2)'))&&aE[dO(0x230e,'*ZKE')]('input')[dO(0x111d,'6YaW')]((aF,aG)=>{var dQ=dO;aA['indexOf'](aG['value'])>-0x1?($(aG)['prop'](dQ(0x43b4,'@HIr'),!0x0),$(aG)[dQ(0x943,'V8^Q')](dQ(0x1fd8,'t#L0'),'checked')):($(aG)[dQ(0x17be,'@HIr')](dQ(0x43b4,'@HIr'),!0x1),$(aG)[dQ(0x2eea,'zx#%')]('checked'));});}else{if(aA instanceof Object||dO(0x232f,'VTTT')==typeof aA){for(let aF in aA)aq(aA[aF],aF,aC);}else console[dO(0x29c5,'XcPC')](typeof aA,aA);}}},ar=function(aA){var dR=a0f;let aB=aA[dR(0x36bc,'XCXV')][dR(0x1f98,'(qw$')][dR(0x37bb,'^d9X')](','),aC=[],aD=aA[dR(0x2a11,'5WLj')][0x0];return Array[dR(0x382b,'eu*B')](aD[dR(0xe5b,'pMQs')])[dR(0x3ea4,'V8^Q')](aE=>{let aF=0x0,aG={};aB['forEach'](aH=>{aG[aH]=aE['children'][aF]?aE['children'][aF]['innerText']:'',aF++;}),aC['push'](aG);}),aC;},as=function(){var dS=a0f;let aA=[];return aA[dS(0x36d0,'6YaW')]({'name':dS(0x13a0,'vGE1'),'children':at(dS(0x4d53,'*p#P'))}),aA[dS(0x565,'*gI2')]({'name':dS(0x3822,'lnIj'),'children':at(dS(0x3197,'*p#P'))}),aA[dS(0x565,'*gI2')]({'name':dS(0x31b2,'!%Yf'),'children':at(dS(0x4671,'@HIr'))}),aA;},at=function(aA){var dT=a0f;let aB=editor['$']('#'+aA),aC=[];return aB[dT(0x16d9,'Q@hO')]('field[name],img[name]')[dT(0x2460,'^d9X')]((aD,aE)=>{var dU=dT;if(aE[dU(0x43cb,'NrJk')]('name')){let aF={};aF[dU(0x2747,'zx#%')]=aE[dU(0xd6d,'kGY9')](dU(0x3859,'^d9X')),aF[dU(0x1f20,'!%Yf')]=aE[dU(0x3ac8,'!Yg[')],aE[dU(0x3b95,'VTTT')][dU(0x55b,'N!xX')](dU(0x2070,'pMQs'))?aF[dU(0x426,'Jc8$')]='{'+aE[dU(0x3feb,'zbs!')](dU(0x56b,'c]Mq'))+dU(0x755,'@HIr'):aF[dU(0x17f,'pMQs')]='{'+aE[dU(0x2e3a,'esU(')](dU(0x9f9,'VTTT'))+':\x22'+aE[dU(0x398a,'lmQg')]+'\x22}',dU(0x440d,'^d9X')==aE[dU(0x3485,'*p#P')]&&(aF[dU(0x32aa,'kGY9')]='{'+aE['getAttribute'](dU(0x3409,'5WLj'))+':\x22'+aE['src']+'\x22}'),aC['push'](aF);}}),aB[dT(0x1bd,'N!xX')](dT(0xd17,'#ZU^'))[dT(0x4d20,'^Syn')]((aD,aE)=>{var dV=dT;if(aE['id']){let aF={};if('radio'==aE[dV(0x4062,'xbuu')](dV(0x190f,'Jj0M'))){let aG=$(aE)['children']('input:radio:checked')[dV(0x32fe,'Nf2)')]();aF[dV(0x3766,'V8^Q')]='{'+aE['id']+':\x22'+(aG||'')+'\x22}';}else{let aH=[];$(aE)['children'](dV(0x252,'v^si'))[dV(0x4d84,'vGE1')]((aI,aJ)=>{var dW=dV;aH[dW(0x4258,'*ZKE')]($(aJ)[dW(0x3bcf,'NrJk')]());}),aF['name']='{'+aE['id']+':\x22'+aH[dV(0x430a,'sYdH')](';')+'\x22}';}aC['push'](aF);}}),aB[dT(0x4ea0,'t#L0')](dT(0x10c2,'*gI2'))[dT(0x1e24,'xbuu')]((aD,aE)=>{var dX=dT;if(aE[dX(0x364a,'Jc8$')]['field']){let aF={};aF['name']=aE['id']+':'+aE[dX(0x1f16,'VTTT')],aF[dX(0x1493,'1Ue]')]='',aF[dX(0x222e,'IvBK')]=au(aE),aC[dX(0x859,'MDwy')](aF);}}),aC;},au=function(aA){var dY=a0f;let aB=aA[dY(0x3385,'UDz8')][dY(0x34e,'zbs!')]['split'](','),aC=[],aD=aA[dY(0x3b9e,'Nf2)')][0x0];return Array[dY(0x3fd9,'Jj0M')](aD[dY(0x31df,'lnIj')])[dY(0x141a,'UDz8')](aE=>{var dZ=dY;let aF=[],aG=0x0;aB[dZ(0x1b5b,'lmQg')](aH=>{var e0=dZ;aF[e0(0x4476,'$(mV')]('{'+aH+':'+(aE[e0(0x243c,'Jc8$')][aG]?aE[e0(0x2fb0,'t#L0')][aG]['innerText']:'')+'}'),aG++;}),aC['push']({'name':aF[dZ(0x3628,'c]Mq')](','),'title':aF[dZ(0x19b8,'Nf2)')](',')});}),aC;},av=function(aA){var e1=a0f;editor['$'](e1(0x378e,'Jc8$'))[e1(0x19e9,'XCXV')]('transform',e1(0x12a,'*p#P')+aA+','+aA+')'),editor['$'](e1(0x368a,'Ws11'))[e1(0x48da,'Q@hO')](e1(0x2c93,'XCXV'),e1(0x175e,'V8^Q'));var aB=document['getElementById'](e1(0x2493,'e*c8'))['contentWindow'];$(aB[e1(0x1ade,'6YaW')][e1(0x31e6,'xrMR')])['find']('#_page')[e1(0x1120,'hRv2')](e1(0x4179,'#ZU^'),e1(0x43f4,'VTTT')+aA+','+aA+')'),$(aB[e1(0x1c02,'vGE1')]['body'])[e1(0x30e1,'#ZU^')](e1(0x3960,'XCXV'))[e1(0x3cc1,'!%Yf')](e1(0x2075,'*ZKE'),e1(0x10ea,'MDwy'));},aw=function(aA,aB,aC){var e2=a0f;if(!editor[e2(0x1094,'XcPC')][e2(0x1408,'N!xX')]['includes'](aA))switch(aA){case e2(0x2916,'$(mV'):ag(e2(0x4e36,'hK)l'));break;case e2(0x94d,'c]Mq'):ag(e2(0x13ac,'*ZKE'));break;case e2(0x368f,'NrJk'):ag(e2(0x131c,'c]Mq'));break;case'template':H[e2(0x4608,'!Yg[')]();break;case e2(0x2ef1,'MDwy'):editor[e2(0x18d,'xrMR')]['newdoc']();break;case e2(0x422e,'*p#P'):editor['toolbar'][e2(0x2089,'*gI2')]();break;case'preview':editor[e2(0x4e1b,'[T9%')][e2(0x1a4f,'*ZKE')](aC);break;case e2(0x38c,'Nf2)'):editor[e2(0x188f,'(qw$')][e2(0x2715,'sYdH')]();break;case'printStart':editor[e2(0x408e,'N!xX')][e2(0x54a,'*ZKE')]();break;case e2(0x2862,'zx#%'):v[e2(0x3944,'6YaW')](aC);break;case'print':editor['toolFunc'][e2(0x9c5,'H4NX')]();break;case e2(0x10fd,'Bbr@'):editor['toolFunc'][e2(0x2b25,'xrMR')](e2(0x132d,'zx#%'));break;case'directPrint':editor['toolFunc']['directPrint']();break;case'save':editor[e2(0x349f,'Ws11')][e2(0xb2c,'^Syn')]();break;case e2(0x492a,'NrJk'):editor['toolFunc'][e2(0x2335,'$(mV')]();break;case e2(0x2e5f,'Nf2)'):editor[e2(0x1f3a,'(qw$')][e2(0x4efb,'Ws11')]();break;case'importJsonWithCode':editor['toolbar'][e2(0x1940,'MDwy')]();break;case'importXml':editor[e2(0x38fb,'Bbr@')][e2(0x45a5,'esU(')]();break;case e2(0x19f4,'*p#P'):editor[e2(0x789,'Ws11')][e2(0x481a,'6YaW')]();break;case e2(0x45e0,'Bbr@'):editor[e2(0x3fc,'t#L0')][e2(0x1ea0,'e*c8')]();break;case e2(0x311b,'*ZKE'):editor['expFunc']['exportJson']();break;case e2(0x2528,'IvBK'):editor['expFunc']['exportJsonWithCode']();break;case e2(0x3ce7,'hK)l'):editor['expFunc'][e2(0x3ba9,'*p#P')]();break;case e2(0x1713,'Jj0M'):editor['expFunc']['exportHtml']();break;case'exportHtmlWithStyle':editor['expFunc'][e2(0x201e,'N!xX')]();break;case e2(0x4244,'@HIr'):editor[e2(0x370f,'!Yg[')][e2(0x3e7c,'hK)l')](!0x0);break;case e2(0x45f8,'(qw$'):editor['expFunc'][e2(0x8b6,'MDwy')]();break;case e2(0x468f,'v^si'):editor['expFunc'][e2(0x416b,'hRv2')](!0x1);break;case e2(0x31d2,'oKRq'):editor[e2(0x1436,'esU(')][e2(0x18b6,'H4NX')]();break;case e2(0x1d0c,'zbs!'):editor[e2(0x32cc,'oKRq')][e2(0x49e5,'*p#P')](-0.1);break;case'zoom':editor[e2(0x264c,'!Yg[')][e2(0x40b8,'1Ue]')]();break;case'zoomout':editor[e2(0x32cc,'oKRq')][e2(0x2f07,'UDz8')](0.1);break;case e2(0xdbf,'oKRq'):editor[e2(0x22a4,'#ZU^')]['toggle'](aC);break;case'history':editor[e2(0x4c19,'e*c8')]['toggle'](aC);break;case e2(0xa11,'hRv2'):editor[e2(0x45cf,'6YaW')]['fullScreen'](aC);break;case'pagesize':x[e2(0x4847,'Nf2)')]();break;case e2(0x2f3,'Jj0M'):editor[e2(0x3028,'Ws11')]['showProp'](aC);break;case e2(0x22ce,'oKRq'):editor[e2(0x2306,'!%Yf')][e2(0x2fb2,'H4NX')]();break;case e2(0x46a9,'hRv2'):editor['control'][e2(0x2258,'Nf2)')]();break;case e2(0x1847,'NrJk'):editor[e2(0x376b,'xrMR')][e2(0x38c8,'esU(')](e2(0x3a61,'lmQg'));break;case e2(0x3b19,'xbuu'):editor[e2(0x15ae,'esU(')][e2(0x2ace,'Ws11')](e2(0x730,'MDwy'));break;case e2(0x12f4,'sYdH'):editor['control'][e2(0x2020,'eu*B')](e2(0x4863,'Jj0M'));break;case e2(0x868,'NrJk'):editor[e2(0x48d8,'NrJk')][e2(0x4872,'NrJk')]('DataList');break;case e2(0x4362,'zbs!'):editor[e2(0x421,'hRv2')][e2(0x3d7f,'hRv2')]();break;case e2(0x45ee,'hRv2'):editor[e2(0x12c9,'*p#P')][e2(0x78e,'zx#%')]();break;case e2(0x3b5a,'H4NX'):editor[e2(0x2fd0,'5WLj')][e2(0x1973,'*p#P')]();break;case'insertBarcode':editor[e2(0x2f6b,'!Yg[')][e2(0x3fa3,'VTTT')]();break;case e2(0x22f0,'IvBK'):editor[e2(0x3305,'H4NX')]['insertQRcode']();break;case e2(0x4e5,'Jc8$'):I[e2(0x4864,'Jc8$')]();break;case'insertMenstruation':J[e2(0x468,'UDz8')]();break;case'insertFetalHeart':K[e2(0x266f,'^d9X')]();break;case e2(0x3791,'v^si'):L[e2(0x2676,'Jj0M')]();break;case e2(0x1466,'Ws11'):M['show']();break;case e2(0x39f1,'zx#%'):O[e2(0x3275,'^Syn')]();break;case e2(0x3b6e,'(qw$'):P['show']();break;case e2(0x46d4,'e*c8'):Q['show']();break;case'insertLine':editor['control']['insertLine']();break;case e2(0x2121,'Ws11'):editor[e2(0x443e,'*p#P')]['insertRow']('before');break;case'insertRowAfter':editor[e2(0x41f8,'Bbr@')][e2(0xd13,'XCXV')](e2(0x109d,'VTTT'));break;case e2(0x1470,'5WLj'):editor[e2(0x340d,'5WLj')]['insertCol']('before');break;case e2(0x2bb0,'xrMR'):editor[e2(0x1715,'zbs!')][e2(0x146b,'zbs!')]('after');break;case'deleteTable':editor['tableResize']['deleteTable']();break;case'deleteRow':editor['tableResize'][e2(0x1fb2,'Nf2)')]();break;case e2(0x86d,'N!xX'):editor[e2(0x3ca4,'sYdH')][e2(0x147f,'hRv2')]();break;case e2(0x3ef,'*gI2'):editor[e2(0x1040,'Ws11')]['resetTable']();break;case e2(0x4e34,'*gI2'):editor[e2(0x634,'NrJk')]['mergeCell'](e2(0x13b,'H4NX'));break;case'cancelMergeCell':editor['tableResize'][e2(0x4604,'kGY9')]('cancel');break;case e2(0x2aa9,'eu*B'):editor[e2(0x3405,'t#L0')][e2(0x13c9,'NrJk')](e2(0x3e4d,'!Yg['));break;case e2(0xd3,'zx#%'):editor[e2(0x2825,'!Yg[')][e2(0x3c2b,'MDwy')](e2(0x2c6b,'$(mV'));break;case e2(0x3a35,'lmQg'):editor['revision'][e2(0xacf,'Jj0M')](aC);break;case e2(0x1251,'eu*B'):editor[e2(0x1251,'eu*B')][e2(0x4b43,'v^si')](aC);break;case e2(0x1f42,'Bbr@'):editor['remark'][e2(0x2e59,'xbuu')]();break;case'textIndent':editor[e2(0x43e4,'*p#P')][e2(0x109b,'lnIj')]();break;case e2(0x2110,'*gI2'):editor[e2(0x10ed,'1Ue]')]['cancelTextIndent']();break;case e2(0x1926,'e*c8'):editor['toolFunc'][e2(0x1068,'IvBK')]();break;case e2(0x4b41,'$(mV'):editor[e2(0x8e2,'V8^Q')][e2(0x4c25,'NrJk')]();break;case e2(0x28a7,'*gI2'):editor['toolFunc'][e2(0x3bff,'*ZKE')]();break;case'paste':editor[e2(0x4e1b,'[T9%')][e2(0x2acf,'^d9X')]();break;case e2(0x1e4a,'zbs!'):editor['toolFunc']['pasteText']();break;case e2(0x3602,'kGY9'):editor['toolFunc'][e2(0x972,'UDz8')]()||editor[e2(0x1527,'sYdH')]['execCommand'](aA,!0x1,aB);break;case e2(0x312d,'UDz8'):editor[e2(0x32cc,'oKRq')][e2(0x1b12,'1Ue]')]()||editor[e2(0x3ed8,'lnIj')][e2(0x4f5d,'6YaW')](aA,!0x1,aB);break;case'about':R[e2(0x49d6,'lnIj')]();break;case e2(0x2a6a,'pMQs'):window[e2(0x1368,'xrMR')]('https://www.x-emr.cn/manual.html');break;case'update':window[e2(0x2dbe,'^Syn')](e2(0x1684,'MDwy'));break;default:editor[e2(0x44c2,'oKRq')]['execCommand'](aA,!0x1,aB)||console[e2(0x2994,'vGE1')](e2(0x2ebb,'5WLj')+aA);}},ax=function(aA){var e3=a0f;return $(editor[e3(0x1fb7,'XcPC')][e3(0x2323,'sYdH')])['find'](aA);},ay=function(){var e4=a0f;let aA=editor['$'](e4(0x2b4e,'H4NX')),aB=[],aC={};function aD(aE){var e5=e4;let aF=0x0;return aE[e5(0x3533,'xbuu')](aG=>{var e6=e5;aF+=Number[e6(0x3828,'vGE1')](aG);}),aF;}aA[e4(0x36a4,'t#L0')]((aE,aF)=>{var e7=e4;let aG=aF['dataset'][e7(0x394b,'Jc8$')][e7(0x3c1e,'hRv2')]();if(aG[e7(0x40b,'esU(')]('=')>-0x1)aB['push'](aF);else{let aH=aF[e7(0x4106,'*p#P')](e7(0x210b,'zbs!'));aH&&(aC[aG]||(aC[aG]=[]),aC[aG]['push'](aH));}}),aB[e4(0x26c5,'zx#%')](aE=>{var e8=e4;let aF=aE[e8(0x1032,'Bbr@')][e8(0x26e7,'@HIr')];aF=aF['replace'](/\s*/g,'')[e8(0xa4d,'lnIj')]('=','')[e8(0x204,'vGE1')]('(','(')[e8(0x2e02,'pMQs')](')',')');for(let aG in aC)aF[e8(0x3ec0,'N!xX')](e8(0x24be,'Q@hO')+aG+'])')>-0x1?aF=aF[e8(0x39cf,'hRv2')](e8(0x4a08,'!%Yf')+aG+'])',aD(aC[aG])):aF[e8(0x3e4b,'hK)l')]('['+aG+']')>-0x1&&(aF=aF[e8(0xdfe,'oKRq')]('['+aG+']',Number[e8(0x3080,'1Ue]')](aC[aG][0x0])));try{let aH=eval(aF);aE[e8(0x1f14,'Bbr@')]['remove'](e8(0x3390,'5WLj')),aE[e8(0x2865,'XcPC')](e8(0x4c59,'(qw$'),aH),aE[e8(0x4ac9,'MDwy')]=aH;}catch(aI){console[e8(0x3fd3,'vGE1')](e8(0x1722,'#ZU^')+aF);}});},az={'version':'Ver1.0-20231006','lang':e9(0xf0e,'1Ue]'),'online':!0x1,'license':'','container':'#editor','mode':'form','tag':!0x1,'revision':!0x1,'remark':!0x1,'showPageLine':!0x0,'scale':0x1,'baseUrl':'/','saveUrl':e9(0x3b87,'XcPC'),'pdfUrl':e9(0x27e1,'H4NX'),'withDictLabel':!0x1,'onlyPasteText':!0x1,'dictionary':[{'type':'symptoms','title':'体征','isParent':!0x0,'treeUrl':'/dict','itemUrl':'/dictitem'},{'type':e9(0x28c0,'zx#%'),'title':'症状','isParent':!0x0,'treeUrl':'/dict','itemUrl':e9(0x38db,'#ZU^')},{'type':'meta','title':e9(0x32a0,'NrJk'),'isParent':!0x0,'treeUrl':e9(0x31ea,'VTTT'),'itemUrl':'/dictitem'},{'type':e9(0x283,'UDz8'),'title':e9(0x200,'$(mV'),'isParent':!0x0,'treeUrl':e9(0x31ea,'VTTT'),'itemUrl':'/dictitem'},{'type':e9(0x3b32,'c]Mq'),'title':e9(0x186e,'Jj0M'),'isParent':!0x0,'treeUrl':e9(0x1661,'VTTT'),'itemUrl':'/institem'},{'type':'province','title':e9(0x3ba7,'pMQs'),'isParent':!0x0,'treeUrl':e9(0x3294,'xbuu'),'itemUrl':'/provitem'}],'toolbar':{'file':!0x0,'edit':!0x0,'insert':!0x0,'expression':!0x0,'table':!0x0,'revision':!0x0,'view':!0x0,'print':!0x0,'import':!0x0,'export':!0x0,'develop':!0x0,'help':!0x0},'disableCommand':[],'statusbar':!0x0};return window[e9(0x4395,'Bbr@')]={'option':az,'revision':E,'tag':C,'remark':D,'control':o,'expFunc':q,'toolFunc':p,'mobile':n,'init':S,'initInnerTool':ae,'clearInnerTool':af,'saveHistory':p['saveHistory'],'showMessage':p[e9(0x1de2,'$(mV')],'setHeaderFromUrl':p['setHeaderFromUrl'],'setHeaderFromHtml':p['setHeaderFromHtml'],'toast':p[e9(0x2b66,'*gI2')],'clearLocal':p[e9(0x1f60,'VTTT')],'validate':p['validate'],'loadUrl':ai,'loadHtml':aj,'getHtml':ak,'getHtmlWithStyle':al,'appendHtml':am,'getDom':as,'setMode':ag,'execCommand':aw,'getSelection':a0,'getRange':a1,'focusEnd':a2,'setBindObject':aq,'getBindObject':ao,'getBindXml':ap,'scale':av,'bindDataList':r['bindDataList'],'appendDataList':r[e9(0x3c0e,'XcPC')],'getRevision':E[e9(0x7f6,'eu*B')],'createVitalSigns':O[e9(0x787,'6YaW')],'autoCompute':ay,'$':ax},editor['$'][e9(0x4108,'sYdH')]=$[e9(0x4b0d,'XCXV')],editor;}[ea(0x1445,'UDz8')](f,h),void 0x0===i||(e[ea(0x28bb,'Q@hO')]=i);},0x117b:(f,g,h)=>{var ef=a0f,j,k;j=[h(0x2525),h(0xa9),h(0x1378),h(0x26fd),h(0x1fb2),h(0x1cf3)],void 0x0===(k=function(l,m,p,q,s){var ec=a0f;function u(v){var eb=a0f;this['container']=v,this[eb(0x3717,'Bbr@')](),m[eb(0xe42,'eu*B')](),q['render'](),p[eb(0x1b27,'xbuu')](),s['render']();}return u[ec(0x526,'Jc8$')][ec(0x1fe4,'Ws11')]=function(){var ed=ec;m[ed(0x2484,'!%Yf')](),q['bindEvent'](),p[ed(0x4996,'V8^Q')](),s['bindEvent']();},u['prototype'][ec(0x25d0,'Ws11')]=function(){var ee=ec,v=[ee(0x1b94,'(qw$'),ee(0x978,'MDwy'),ee(0x1d99,'H4NX'),ee(0x160b,'MDwy')+(ee(0x4316,'kGY9')==browser?'src=\x22javascript:\x22':'')+ee(0x4f32,'hK)l'),'',ee(0x27dd,'lnIj'),ee(0x280,'5WLj'),ee(0x21bb,'Ws11'),ee(0x9ea,'e*c8'),''][ee(0xdfa,'hRv2')]('\x0a'),x=document['getElementById'](ee(0x33d7,'^d9X'))['contentWindow'][ee(0x19d4,'N!xX')];x[ee(0x31e4,'*p#P')]['innerHTML']=w,$(x['head'])[ee(0x4499,'Jc8$')](l),x[ee(0x1d95,'hRv2')]['setAttribute'](ee(0x643,'5WLj'),'preview');},u;}[ef(0x751,'H4NX')](g,j))||(f[ef(0x1334,'XCXV')]=k);},0x26fd:(f,g)=>{var er=a0f,h;void 0x0===(h=function(){var eg=a0f;function i(){this['render']();}return i[eg(0x40ca,'*gI2')]=function(){var eh=eg;if(editor[eh(0x3d9,'UDz8')][eh(0x722,'sYdH')]){var j=[eh(0x35e2,'!%Yf'),'','',eh(0x97e,'[T9%'),eh(0x597,'*p#P'),'
',eh(0x33f6,'eu*B'),'',eh(0x2ef,'!Yg['),'design'==editor['option'][eh(0x25af,'sYdH')]?eh(0x1d85,'Ws11'):'',eh(0x1a78,'vGE1')==editor[eh(0x3d9,'UDz8')][eh(0x1897,'xbuu')]?eh(0x15a9,'[T9%'):'','\x20-\x20',eh(0x3334,'!Yg['),eh(0x2399,'*ZKE'),eh(0x19a4,'N!xX'),eh(0x4587,'IvBK'),''][eh(0x14c6,'eu*B')]('');$(eh(0x1501,'xbuu'))[eh(0x12f1,'H4NX')](j);}else editor['option']['toolbar']?($(eh(0x1934,'hK)l'))[eh(0xd55,'$(mV')](eh(0x3597,'^d9X'),eh(0x4eb0,'xrMR')),$(eh(0x1132,'vGE1'))[eh(0x241d,'vGE1')](eh(0x3b07,'esU('),eh(0x1c17,'Jc8$'))['css'](eh(0x13b4,'xrMR'),eh(0x2d89,'lnIj'))):($('#_container')[eh(0x2741,'Jc8$')]('height',eh(0x420a,'vGE1')),$(eh(0x3903,'lnIj'))[eh(0x1ebb,'H4NX')](eh(0xc63,'zbs!'),'100%')[eh(0x2ee3,'UDz8')](eh(0x716,'N!xX'),'0'));},i[eg(0x26f7,'MDwy')]=function(){var ei=eg;editor['statusbar']=i,editor[ei(0x35a5,'e*c8')][ei(0x21a8,'#ZU^')][ei(0x320e,'hK)l')]||($(ei(0x23a7,'1Ue]'))[ei(0x2fe,'MDwy')](ei(0x1892,'(qw$'))[ei(0x1bc1,'XCXV')]('click',function(j){var ej=ei,k=new CustomEvent(ej(0xf6b,'NrJk'),{'bubbles':!0x0,'detail':{'target':j['currentTarget'],'cmd':j[ej(0x22fc,'[T9%')][ej(0x437f,'6YaW')][ej(0x3539,'esU(')]['value']}});editor[ej(0x468e,'xbuu')][ej(0x3139,'zx#%')](k);}),editor['document']['addEventListener'](ei(0x45a9,'V8^Q'),function(j){var ek=ei;$(ek(0x1a1d,'[T9%'))[ek(0x4af3,'Jj0M')](ek(0x23c9,'*gI2'))[ek(0x4d20,'^Syn')](function(k,l){var el=ek;l[el(0x1567,'!%Yf')]['cmd']['value'][el(0xf52,'v^si')](j[el(0x7a8,'xbuu')]['mode'])>=0x0?$(l)['addClass'](el(0x296,'#ZU^')):$(l)[el(0x49b4,'pMQs')](el(0x1377,'zx#%'));});}));},document[eg(0x3c38,'xbuu')](eg(0x3e53,'zbs!'),function(j){var em=eg;$(em(0xd81,'H4NX'))[em(0x46ac,'kGY9')](Math['round'](0x64*j[em(0x1a32,'*gI2')][em(0x2016,'oKRq')])+'%');}),i['setText']=function(j){var en=eg;$(en(0x1289,'t#L0'))[en(0x2c2a,'^Syn')]('#_text')[en(0x4a81,'V8^Q')](j);},i[eg(0x4d4e,'e*c8')]=function(j){var eo=eg;$('#_statusbar')[eo(0x39ed,'@HIr')](eo(0x24c,'@HIr'))[eo(0x317d,'VTTT')](j),setTimeout(function(){var ep=eo;$(ep(0x37d5,'V8^Q'))[ep(0x4a7,'VTTT')](ep(0x2366,'lmQg'))['html']('');},0x3e8);},i[eg(0x2f2a,'*gI2')]=function(){var eq=eg;editor[eq(0x1fac,'lmQg')]=!0x1,$(eq(0x3a10,'lmQg'))[eq(0x39ed,'@HIr')](eq(0x1688,'zx#%'))[eq(0xad9,'Q@hO')](''),$(eq(0x155f,'^Syn'))[eq(0x3aa5,'#ZU^')](eq(0x24c,'@HIr'))['html'](''),$(eq(0x4834,'@HIr'))[eq(0x28ba,'Nf2)')]('#_path')[eq(0x829,'!%Yf')]('');},i;}[er(0xa96,'e*c8')](g,[]))||(f[er(0x3b1b,'XcPC')]=h);},0x1378:(f,g,h)=>{var eA=a0f,j,k;j=[h(0x208f),h(0x1b03),h(0x19ba),h(0x173d)],void 0x0===(k=function(l,m,p,q){var et=a0f;function s(){}const u=function(v){var es=a0f;return $('#_toolpanel')[es(0x30f,'MDwy')](v);};return s[et(0x2571,'e*c8')]=function(){var eu=et;let v=[eu(0x200d,'N!xX'),''+editor[eu(0x39af,'*p#P')][eu(0x358b,'^Syn')]+'',eu(0x2e12,'[T9%')+editor[eu(0x4418,'vGE1')]['structure']+eu(0x30d,'$(mV'),editor[eu(0x4007,'lnIj')][eu(0xac3,'1Ue]')]?''+editor[eu(0x447c,'!Yg[')]['dictionary']+eu(0x4b00,'IvBK'):'','','','',eu(0x18c3,'lmQg'),eu(0x2c1b,'e*c8'),eu(0x16a5,'!Yg['),eu(0x229f,'!Yg[')][eu(0x4d0e,'oKRq')]('\x0a');$(eu(0x3c6b,'@HIr'))[eu(0x4a81,'V8^Q')](v),p['render'](),l['render'](),m[eu(0x14a3,'!Yg[')](),q['render']();},s[et(0x4409,'NrJk')]=function(){var ev=et;editor[ev(0x22b5,'V8^Q')]=s,u(ev(0x3d4a,'1Ue]'))['on'](ev(0x4385,'Ws11'),ev(0x1cee,'e*c8'),function(v){var ew=ev;let w=v['target'];u('#_tool-panel-head')[ew(0x2fe,'MDwy')](ew(0x12b4,'vGE1'))[ew(0x26c0,'N!xX')](ew(0x100b,'Q@hO')),w[ew(0x395,'5WLj')]['add'](ew(0x78c,'Bbr@')),u(ew(0x198b,'e*c8'))['children'](ew(0x22ee,'kGY9'))[ew(0x2c28,'xrMR')]();let x=w[ew(0x4351,'Nf2)')](ew(0x3363,'!%Yf'));ew(0x25f4,'^d9X')!=x||w['isBind']?ew(0x1683,'e*c8')==x?q['bindEvent']():ew(0xc6b,'@HIr')!=x||w[ew(0x21bc,'v^si')]||(l[ew(0x2b15,'xrMR')](),w['isBind']=!0x0):(m[ew(0x4725,'pMQs')](),w[ew(0x3117,'esU(')]=!0x0),u(ew(0x121c,'Q@hO'))[ew(0x3cbd,'!%Yf')](x)['show']();}),p[ev(0x2ead,'Nf2)')]();},s['show']=function(){var ex=et;$(ex(0x4ae8,'XCXV'))[ex(0x2546,'hRv2')](),$(ex(0x269a,'*gI2'))[ex(0xd55,'$(mV')](ex(0x1cd8,'sYdH'),'calc(100%\x20-\x20240px)'),s[ex(0x4980,'@HIr')]();},s[et(0x2333,'Nf2)')]=function(){var ey=et;$(ey(0x2739,'(qw$'))['hide'](),$(ey(0x141e,'sYdH'))['css'](ey(0x3e46,'Jj0M'),ey(0x2354,'lmQg'));},s[et(0xec4,'XcPC')]=function(v){var ez=et;u(ez(0x784,'Q@hO'))[ez(0x3c9,'xbuu')](),u(ez(0x3582,'!%Yf'))[ez(0x4f40,'5WLj')](),u(ez(0x3aae,'hRv2'))['hide'](),u(ez(0x21f8,'Ws11'))[ez(0x4ea0,'t#L0')](ez(0x2d5a,'6YaW'))[ez(0x540,'eu*B')](ez(0x371b,'[T9%')),u(ez(0x3d6a,'!Yg['))[ez(0x3654,'xbuu')](ez(0x39ca,'pMQs'))['addClass'](ez(0x4bcd,'1Ue]')),u(ez(0x4629,'*gI2'))['show'](),p[ez(0x41d3,'^d9X')](v);},s;}['apply'](g,j))||(f[eA(0x6c0,'sYdH')]=k);},0xa9:(f,g,h)=>{var fi=a0f,j,k;j=[h(0x241c),h(0x11bb),h(0x955),h(0x1fe4),h(0x1c25),h(0x1dd1),h(0x2a),h(0x1b95),h(0x31f),h(0x12f1)],void 0x0===(k=function(m,p,q,v,w,x,y,z,A,B){var eC=a0f;function C(){}function D(E){var eB=a0f;if(E[eB(0x1b8f,'#ZU^')][eB(0x2040,'oKRq')]('disable'))return;$(eB(0xd27,'zbs!'))[eB(0x16d9,'Q@hO')](eB(0x4b7,'1Ue]'))['removeClass'](eB(0x7a9,'xbuu')),E[eB(0x441f,'Q@hO')][eB(0xbf3,'*p#P')]('select'),$('#_toolbar')[eB(0x10e7,'lmQg')](eB(0x1c7a,'Jc8$'))[eB(0x3c4f,'lnIj')]();let F=E[eB(0x339e,'N!xX')](eB(0x1c81,'*gI2')),G=E[eB(0x2db6,'hK)l')]();$(eB(0x39ea,'XcPC'))[eB(0x35b0,'V8^Q')](F)[eB(0x904,'Q@hO')]()[eB(0x3cc1,'!%Yf')](eB(0x4c14,'(qw$'),G['x']+'px')['css'](eB(0x151,'Jj0M'),G['y']+0x19+'px');}return C[eC(0x1432,'XcPC')]=function(){var eD=eC;return[{'id':eD(0x5cc,'zx#%'),'title':editor['lang'][eD(0x1693,'^Syn')],'children':[{'cmd':eD(0xf85,'MDwy'),'title':editor['lang'][eD(0x478b,'^Syn')],'icon':eD(0x1a9e,'VTTT'),'online':!0x0},{'cmd':eD(0x4d3b,'sYdH'),'title':editor[eD(0x224d,'*gI2')][eD(0x31cb,'N!xX')],'icon':'icon-open'},{'cmd':eD(0x1de9,'^Syn'),'title':editor[eD(0x39da,'#ZU^')][eD(0x6ae,'pMQs')],'icon':'icon-new'},{'cmd':eD(0x10cb,'XcPC'),'title':editor[eD(0x30d2,'NrJk')][eD(0x3537,'$(mV')],'icon':eD(0x2886,'^Syn'),'online':!0x0}]},{'id':eD(0x409e,'5WLj'),'title':editor[eD(0x2640,'H4NX')]['edit'],'children':[{'cmd':eD(0x30dc,'MDwy'),'title':editor[eD(0x45bd,'xbuu')][eD(0xa6d,'vGE1')],'icon':'icon-undo'},{'cmd':eD(0x1b12,'1Ue]'),'title':editor['lang'][eD(0x4d5b,'zbs!')],'icon':eD(0x364,'(qw$')},'-',{'cmd':eD(0xc28,'XcPC'),'title':editor[eD(0x39da,'#ZU^')]['cut'],'icon':eD(0x31eb,'1Ue]')},{'cmd':'copy','title':editor[eD(0x1b60,'esU(')][eD(0x2771,'hRv2')],'icon':eD(0x4936,'xrMR')},editor[eD(0x405e,'pMQs')][eD(0x15ad,'e*c8')]?'-':{'cmd':eD(0x2c0f,'Jj0M'),'title':editor[eD(0x389d,'@HIr')][eD(0x46bf,'#ZU^')],'icon':eD(0x66b,'kGY9')},{'cmd':eD(0x2511,'v^si'),'title':editor[eD(0x45bd,'xbuu')][eD(0x1e4a,'zbs!')],'icon':eD(0x648,'Nf2)')},'-',{'cmd':'selectAll','title':editor['lang'][eD(0x21e1,'!Yg[')],'icon':'icon-select-all'}]},{'id':'view','title':editor[eD(0xa76,'Jc8$')][eD(0x2cf,'1Ue]')],'children':[{'cmd':eD(0x1612,'(qw$'),'title':editor[eD(0x49ab,'xrMR')]['tag'],'icon':eD(0x2aba,'Bbr@')},{'cmd':'previewHtml','title':editor['lang']['previewHtml'],'icon':eD(0x4477,'pMQs')},{'cmd':'previewPdf','title':editor[eD(0x127c,'sYdH')][eD(0x3f64,'VTTT')],'icon':'icon-pdf'},{'cmd':eD(0x27b2,'[T9%'),'title':editor[eD(0x3c1d,'VTTT')]['fullscreen'],'icon':eD(0x1cb7,'@HIr')}]},{'id':eD(0x36be,'pMQs'),'title':editor[eD(0x39da,'#ZU^')]['insert'],'mode':'design','children':[{'cmd':eD(0x1e3c,'*gI2'),'title':editor[eD(0x1b60,'esU(')][eD(0xaeb,'pMQs')],'icon':eD(0x480d,'N!xX')},{'cmd':eD(0x3fe,'Bbr@'),'title':editor[eD(0x2684,'oKRq')][eD(0x1e11,'esU(')],'icon':'icon-date'},{'cmd':'insertCheckbox','title':editor[eD(0x8af,'MDwy')]['insertCheckbox'],'icon':'icon-checkbox'},{'cmd':'insertRadio','title':editor[eD(0x47d,'eu*B')][eD(0x3990,'esU(')],'icon':'icon-redio'},'-',{'cmd':eD(0x2995,'t#L0'),'title':editor[eD(0x2684,'oKRq')][eD(0x4c74,'xbuu')],'icon':'icon-dropdown'},{'cmd':eD(0x432,'XCXV'),'title':editor[eD(0x2ad2,'zx#%')][eD(0x24b8,'Jc8$')],'icon':eD(0xe98,'*p#P')},'-',{'cmd':eD(0x9e0,'#ZU^'),'title':editor[eD(0x1b60,'esU(')][eD(0x46a9,'hRv2')],'icon':eD(0xaf7,'MDwy')},'-',{'cmd':'insertBarcode','title':editor[eD(0x150,'pMQs')][eD(0x212,'c]Mq')],'icon':eD(0x3fd0,'t#L0')},{'cmd':'insertQRcode','title':editor[eD(0x3c1d,'VTTT')][eD(0x17ad,'Q@hO')],'icon':eD(0x2bd4,'#ZU^')},{'cmd':eD(0x3b5d,'xbuu'),'title':editor[eD(0x49bf,'IvBK')]['insertSignate'],'icon':'icon-signate'},{'cmd':eD(0x306d,'lmQg'),'title':editor[eD(0x453c,'^Syn')][eD(0x2af0,'XCXV')],'icon':'icon-image'},'-',{'cmd':eD(0x38f0,'!Yg['),'title':editor['lang'][eD(0x33a1,'UDz8')],'icon':'icon-pagenum'}]},{'id':eD(0x2da3,'v^si'),'title':editor[eD(0x4b44,'hK)l')][eD(0x1a26,'xrMR')],'children':[{'cmd':eD(0x12bd,'lnIj'),'title':editor[eD(0x47d,'eu*B')][eD(0x3565,'lmQg')],'icon':'icon-menstruation'},{'cmd':eD(0x4697,'!Yg['),'title':editor[eD(0x150,'pMQs')][eD(0x2899,'oKRq')],'icon':'icon-tooth'},{'cmd':'insertFetalHeart','title':editor['lang']['insertFetalHeart'],'icon':eD(0xa41,'*ZKE')},{'cmd':eD(0x306d,'lmQg'),'title':editor[eD(0xa76,'Jc8$')][eD(0x1973,'*p#P')],'icon':'icon-image'},'-',{'cmd':'insertSymbol','title':editor[eD(0x150,'pMQs')]['insertSymbol'],'icon':eD(0x388e,'!Yg[')},{'cmd':eD(0x4140,'^d9X'),'title':editor[eD(0x39af,'*p#P')][eD(0x148b,'Nf2)')],'icon':eD(0x176f,'lnIj')}]},{'id':'table','mode':eD(0x47b4,'e*c8'),'title':editor[eD(0x45bd,'xbuu')]['table'],'children':[{'id':eD(0x34e1,'t#L0'),'title':editor[eD(0x388d,'6YaW')][eD(0x19a0,'hRv2')],'icon':'icon-table'},'-',{'cmd':eD(0x4ed2,'*gI2'),'title':editor[eD(0x2cb0,'lmQg')][eD(0x2714,'v^si')],'icon':eD(0x2c42,'v^si')},{'cmd':eD(0x31c6,'[T9%'),'title':editor[eD(0x4de5,'(qw$')]['insertRowAfter'],'icon':'icon-insert-row-after'},{'cmd':eD(0x32de,'MDwy'),'title':editor['lang'][eD(0x389c,'hRv2')],'icon':eD(0x3cc4,'e*c8')},{'cmd':'insertColAfter','title':editor[eD(0x37ec,'zbs!')]['insertColAfter'],'icon':eD(0x7f4,'t#L0')},'-',{'cmd':eD(0x3fad,'V8^Q'),'title':editor[eD(0x1b60,'esU(')]['mergeCell'],'icon':eD(0x2a27,'NrJk')},{'cmd':eD(0x2f75,'xbuu'),'title':editor[eD(0x399a,'Ws11')][eD(0x3ebe,'lmQg')],'icon':eD(0x1c9c,'lnIj')},{'cmd':'mergeRight','title':editor[eD(0xc71,'*ZKE')][eD(0x1e54,'lnIj')],'icon':eD(0x1a16,'hK)l')},{'cmd':eD(0x2b6b,'*gI2'),'title':editor[eD(0x49ab,'xrMR')][eD(0xd3,'zx#%')],'icon':eD(0x444b,'*gI2')},'-',{'cmd':'deleteTable','title':editor[eD(0x1444,'Nf2)')][eD(0xf46,'XcPC')],'icon':eD(0x319a,'NrJk')},{'cmd':eD(0x4653,'XcPC'),'title':editor[eD(0x150,'pMQs')]['deleteRow'],'icon':eD(0x3c53,'IvBK')},{'cmd':'deleteCol','title':editor['lang'][eD(0x19db,'UDz8')],'icon':eD(0x2412,'xrMR')}]},{'id':eD(0x7ec,'Q@hO'),'mode':'form','title':editor[eD(0x127c,'sYdH')]['revision'],'children':[{'cmd':eD(0x247a,'!%Yf'),'title':editor[eD(0x4d57,'XcPC')][eD(0x34b4,'*ZKE')],'icon':eD(0xae3,'esU(')},{'cmd':eD(0x31ed,'#ZU^'),'title':editor[eD(0x389d,'@HIr')][eD(0x70d,'esU(')],'icon':eD(0x4529,'IvBK')},{'cmd':eD(0x27f5,'pMQs'),'title':editor[eD(0x4418,'vGE1')]['history'],'icon':eD(0x1f79,'H4NX')},{'cmd':eD(0x2f81,'Q@hO'),'title':editor['lang'][eD(0x8e6,'(qw$')],'icon':'icon-clear'}]},{'id':eD(0x1326,'*gI2'),'title':editor[eD(0x1615,'v^si')][eD(0x115,'Ws11')],'children':[{'cmd':eD(0x2627,'IvBK'),'title':editor[eD(0x1444,'Nf2)')][eD(0x2476,'$(mV')],'icon':'icon-pagesetting'},{'cmd':'printStart','title':editor[eD(0x453c,'^Syn')][eD(0x329a,'XCXV')],'icon':'icon-pages'},'-',{'cmd':eD(0x1360,'Ws11'),'title':editor[eD(0x224d,'*gI2')][eD(0xe54,'Nf2)')],'icon':'icon-preview'},{'cmd':eD(0x154,'t#L0'),'title':editor[eD(0xc71,'*ZKE')][eD(0x4937,'(qw$')],'icon':'icon-print'},'-',{'cmd':eD(0x16f0,'esU('),'title':editor[eD(0x139e,'c]Mq')][eD(0x4d6b,'*p#P')],'icon':eD(0x2ca,'*gI2')},{'cmd':eD(0x2c2d,'Jc8$'),'title':editor[eD(0x2af4,'UDz8')]['directPrintPdf'],'icon':eD(0x102c,'XcPC')}]},{'id':eD(0x4757,'hRv2'),'mode':eD(0x3ae5,'^d9X'),'title':editor[eD(0x2684,'oKRq')][eD(0x3b6f,'!Yg[')],'children':[{'cmd':'open','title':editor[eD(0x4f0a,'e*c8')][eD(0xcbd,'VTTT')],'icon':eD(0x4327,'(qw$')},{'cmd':eD(0x2fcd,'zbs!'),'title':editor[eD(0x4f0a,'e*c8')][eD(0x32e6,'xrMR')],'icon':'icon-json'},{'cmd':'importJsonWithCode','title':editor[eD(0x45bd,'xbuu')]['importJsonWithCode'],'icon':'icon-json'},{'cmd':eD(0x457,'UDz8'),'title':editor[eD(0x24cc,'hRv2')][eD(0x2f35,'(qw$')],'icon':eD(0x1d2,'5WLj')},'-',{'cmd':eD(0x836,'MDwy'),'title':editor['lang']['importDCXml'],'icon':eD(0xcc9,'hRv2')},'-',{'cmd':eD(0x21cb,'^Syn'),'title':editor[eD(0x2243,'kGY9')]['importWord'],'icon':eD(0x411c,'[T9%')}]},{'id':'export','title':editor[eD(0xa76,'Jc8$')][eD(0x48c7,'^d9X')],'children':[{'cmd':eD(0x103f,'zx#%'),'title':editor[eD(0x4418,'vGE1')][eD(0x1a5d,'*p#P')],'icon':eD(0x38cf,'esU(')},{'cmd':eD(0x2205,'xbuu'),'title':editor[eD(0x127c,'sYdH')]['exportJson'],'icon':eD(0x2aac,'v^si')},{'cmd':eD(0x4201,'lnIj'),'title':editor[eD(0x30d2,'NrJk')][eD(0x1915,'xbuu')],'icon':'icon-json'},{'cmd':'exportXml','title':editor[eD(0x45bd,'xbuu')]['exportXml'],'icon':eD(0x411e,'Jc8$')},'-',{'cmd':eD(0x298f,'xrMR'),'title':editor[eD(0x4d57,'XcPC')]['exportHtmlWithStyle'],'icon':eD(0x82b,'hK)l')},{'cmd':'exportPdf','title':editor['lang'][eD(0x26eb,'*p#P')],'icon':'icon-pdf'}]},{'id':'develop','mode':eD(0x1a78,'vGE1'),'title':editor[eD(0x139e,'c]Mq')][eD(0x3176,'^d9X')],'children':[{'cmd':eD(0x189e,'Q@hO'),'title':editor[eD(0x3f5a,'lnIj')][eD(0x101b,'VTTT')],'icon':'icon-mobile'},{'cmd':eD(0x282b,'pMQs'),'title':editor[eD(0x388d,'6YaW')][eD(0x37c7,'UDz8')],'icon':'icon-vitalSigns'},'-',{'cmd':'javascript','title':editor[eD(0x1b60,'esU(')][eD(0x22e4,'zx#%')],'icon':eD(0x1770,'v^si')},'-',{'cmd':'htmlSource','title':editor[eD(0x388d,'6YaW')]['htmlSource'],'icon':eD(0x4cd1,'Jc8$')}]},{'id':eD(0x25e0,'c]Mq'),'title':editor[eD(0x388d,'6YaW')][eD(0x19da,'[T9%')],'children':[{'cmd':eD(0x21ef,'oKRq'),'title':editor[eD(0xa76,'Jc8$')][eD(0x2f91,'1Ue]')],'icon':eD(0x1f1b,'Bbr@')},{'cmd':'help','title':editor[eD(0xb0b,'V8^Q')][eD(0x2953,'Jc8$')],'icon':eD(0xd70,'e*c8')},{'cmd':eD(0x40a6,'esU('),'title':editor[eD(0x2200,'!%Yf')][eD(0x3d6d,'sYdH')],'icon':eD(0x101,'IvBK')}]}];},C[eC(0x2bf1,'H4NX')]=function(){var eE=eC;if(editor[eE(0x39cc,'zx#%')]=C,!editor[eE(0x1094,'XcPC')]['toolbar'])return $(eE(0x4c2f,'XCXV'))[eE(0x2f31,'sYdH')](eE(0x478,'Ws11'),eE(0x4d79,'vGE1'))[eE(0x25c2,'zbs!')](eE(0x151,'Jj0M'),'0'),void $('#_toolpanel')[eE(0x41ee,'V8^Q')](eE(0x2b83,'t#L0'),eE(0xd93,'hRv2'))[eE(0x4aff,'kGY9')](eE(0x447e,'sYdH'),'0');$(eE(0x3af8,'pMQs'))[eE(0x170d,'lnIj')](function(E){var eF=eE;let F=[];return F[eF(0x4258,'*ZKE')](eF(0x2e98,'zx#%')),F['push'](eF(0x2e22,'Jj0M')),E[eF(0x1a83,'v^si')](G=>{var eG=eF;if(editor[eG(0x378,'6YaW')][eG(0x789,'Ws11')][G['id']]){let H=editor[eG(0x405e,'pMQs')][eG(0x3ac,'1Ue]')][G['id']]['mode']||G['mode'],I='';H&&(I=eG(0x4df6,'Jj0M')+H+'\x22\x20',G['mode']!=editor[eG(0x4041,'[T9%')][eG(0x2be7,'XCXV')]&&(I+=eG(0xd39,'H4NX'))),F['push'](eG(0x255,'^Syn')+G['id']+'\x22'+I+'>'+G[eG(0x2d0a,'*p#P')]+'');}}),F[eF(0x449,'sYdH')](eF(0xb5c,'esU(')),editor['option']['online']&&F[eF(0x46f4,'!Yg[')]('\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'),F['join']('\x0a');}(C['data']())+(eE(0x239a,'hRv2')+editor[eE(0x399a,'Ws11')][eE(0x4556,'(qw$')]+eE(0x4aae,'vGE1')+editor[eE(0x150,'pMQs')][eE(0x13c8,'Bbr@')]+eE(0xe04,'Nf2)')+editor['lang'][eE(0x37e5,'Nf2)')]+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[eE(0x37fc,'Bbr@')][eE(0x2fd,'IvBK')]+eE(0x742,'e*c8')+editor[eE(0x2200,'!%Yf')][eE(0xd37,'!%Yf')]+eE(0x9f2,'(qw$')+editor[eE(0x4e8b,'[T9%')][eE(0x2458,'MDwy')]+eE(0x2431,'VTTT')+editor[eE(0x1315,'Q@hO')][eE(0x655,'(qw$')]+eE(0x3d63,'pMQs')+editor[eE(0x44d4,'1Ue]')][eE(0x44fa,'(qw$')]+eE(0x34d8,'!%Yf')+editor[eE(0x139e,'c]Mq')][eE(0x2857,'6YaW')]+eE(0x4a47,'$(mV')+editor[eE(0xa76,'Jc8$')][eE(0xda7,'MDwy')]+eE(0x3392,'!%Yf')+editor[eE(0xa76,'Jc8$')][eE(0x46cb,'zbs!')]+'\x22\x20class=\x22icons-16\x20icon-color\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{var eI=eH;F[eI(0x196d,'hK)l')](eI(0x245,'eu*B')+G['id']+eI(0x365d,'$(mV')),G[eI(0x33fa,'v^si')]['forEach'](H=>{var eJ=eI;(!H[eJ(0x3258,'[T9%')]||H[eJ(0x3009,'pMQs')]&&editor['option'][eJ(0x3b41,'lnIj')])&&('-'==H?F[eJ(0x2d04,'kGY9')]('
'):(F['push'](eJ(0x3dc1,'@HIr')+H[eJ(0x2863,'MDwy')]+'\x22\x20'+(H['id']?eJ(0x294b,'@HIr')+H['id']+'\x22':'')+'>'),F[eJ(0x3ba1,'Q@hO')](eJ(0x2ef7,'!Yg[')+H[eJ(0x876,'$(mV')]+'\x22\x20class=\x22icons-24\x20'+H[eJ(0x2751,'1Ue]')]+eJ(0x4df4,'MDwy')+H[eJ(0x3ac8,'!Yg[')]+eJ(0x30d,'$(mV')),H['id']&&F[eJ(0xfe8,'NrJk')](eJ(0x17db,'Ws11')),F[eJ(0x35ec,'UDz8')](eJ(0x2593,'c]Mq'))));}),F[eI(0x32a7,'VTTT')](eI(0x33c8,'5WLj'));}),F[eH(0x1910,'esU(')]('\x0a');}(C[eE(0x3ae3,'eu*B')]())),editor['option'][eE(0x10da,'#ZU^')][eE(0x3ea4,'V8^Q')](function(E){var eK=eE;$(eK(0x48dd,'#ZU^'))[eK(0x19b4,'$(mV')](eK(0x237a,'lmQg')+E+']')[eK(0x3bb2,'!%Yf')]();}),editor[eE(0x25a1,'hK)l')][eE(0x4217,'!%Yf')]&&$['get']('/oauth.json',function(E){var eL=eE;E&&(editor[eL(0x415c,'XCXV')]=E,$(eL(0x457b,'eu*B'))[eL(0x4911,'*p#P')]('#user_head')[eL(0x17a2,'pMQs')](eL(0x3d7b,'*p#P'),E[eL(0x39cb,'oKRq')])['attr']('title',E[eL(0x73d,'eu*B')]),$(eL(0x469b,'xbuu'))[eL(0x16d9,'Q@hO')](eL(0x32c7,'hRv2'))[eL(0x170d,'lnIj')](E[eL(0x2195,'IvBK')]),$(eL(0x2b6,'hRv2'))[eL(0x10a2,'sYdH')]('#user_info')[eL(0x183,'zx#%')](function(F){var eM=eL;0x0==$(eM(0x48dd,'#ZU^'))['find'](eM(0x3a1d,'Q@hO'))[eM(0x32be,'Jj0M')]?$['get']('/sys_user.json',function(G){var eN=eM;if(G){let H='
  • 最近来看过...
  • ';G[eN(0x262e,'*p#P')](I=>{var eO=eN;H+='
  • ',$('#_toolbar')['find']('#user_info')[eN(0x3308,'$(mV')](H),$(eN(0x3882,'Jj0M'))['find'](eN(0x3d4c,'esU('))[eN(0x468,'UDz8')]();}}):$(eM(0x4df3,'IvBK'))['find'](eM(0x3ca1,'pMQs'))[eM(0x9b9,'e*c8')]();}));}),m['render'](),p[eE(0x13c0,'V8^Q')](),v[eE(0xe42,'eu*B')](),q[eE(0x4717,'[T9%')](),w[eE(0x3717,'Bbr@')](),x[eE(0x3ffa,'c]Mq')](),B[eE(0xb90,'!%Yf')]();},C[eC(0x341f,'v^si')]=function(){var eP=eC;editor[eP(0x13f7,'oKRq')][eP(0x49c2,'vGE1')]&&(C[eP(0x390b,'vGE1')](),m[eP(0x3f15,'Bbr@')](),p[eP(0x4409,'NrJk')](),v[eP(0x3ef8,'N!xX')](),w[eP(0x3502,'c]Mq')](),q[eP(0x23b8,'hK)l')](),x[eP(0x35c5,'[T9%')](),B[eP(0x4a00,'oKRq')](),$(editor[eP(0x4ba8,'IvBK')][eP(0x4740,'zbs!')])[eP(0x92b,'6YaW')](function(){var eQ=eP;C[eQ(0x2c31,'H4NX')]();}),$(eP(0x2b0b,'lnIj'))['on'](eP(0x467e,'hRv2'),eP(0x2bf8,'VTTT'),function(E){var eR=eP;$(eR(0x4850,'Jj0M'))[eR(0x4cf8,'@HIr')]>0x0?$(eR(0x3afa,'zbs!'))[eR(0x2212,'^d9X')]():D(E[eR(0x13f3,'hRv2')]);}),$(eP(0x2d55,'c]Mq'))['on'](eP(0x218c,'sYdH'),'span',function(E){var eS=eP;$(eS(0xf24,'XCXV'))[eS(0x215,'vGE1')]>0x0&&D(E[eS(0x3516,'[T9%')]);}),$(eP(0x12e8,'sYdH'))['find'](eP(0x1c76,'esU('))['on']('click',function(E){var eT=eP;if(E[eT(0x4195,'*ZKE')]['attributes'][eT(0x27cc,'lnIj')]){$(eT(0x38aa,'Nf2)'))[eT(0x3b03,'Jc8$')]();var F={'target':E['currentTarget'],'cmd':E[eT(0x271d,'!Yg[')][eT(0x1b10,'lmQg')][eT(0x4b7f,'t#L0')][eT(0x45fc,'$(mV')],'param':E[eT(0x47fc,'V8^Q')]['attributes']['param']?E['currentTarget']['attributes']['param'][eT(0x29d3,'^Syn')]:null},G=new CustomEvent(eT(0x9ae,'Bbr@'),{'bubbles':!0x0,'detail':F});editor[eT(0x40b5,'hRv2')][eT(0xccc,'6YaW')](G);}}),$(eP(0x4df3,'IvBK'))[eP(0x3a70,'lnIj')](eP(0x3af3,'xrMR'))['on'](eP(0x33a9,'pMQs'),'li',function(E){var eU=eP;eU(0x211c,'VTTT')==E['target']['id']?B[eU(0x4ea3,'zx#%')](E[eU(0x1cf8,'IvBK')]):B[eU(0x4f40,'5WLj')]();}),$(eP(0x1430,'MDwy'))[eP(0x1db9,'xrMR')](function(E){var eV=eP;C[eV(0x1103,'oKRq')](),q[eV(0x10f,'xbuu')](E['currentTarget']);}),$('#_fontName')[eP(0x2f8e,'*ZKE')](function(E){var eW=eP;C[eW(0x3478,'UDz8')](),v['toggle'](E[eW(0x44bb,'N!xX')]);}),$('#_fontSize')['click'](function(E){var eX=eP;C[eX(0x454d,'*p#P')](),w[eX(0x185b,'UDz8')](E[eX(0x2241,'hK)l')]);}),$('#_lineHeight')[eP(0x1a7f,'c]Mq')](function(E){var eY=eP;C[eY(0x14ae,'!%Yf')](),x[eY(0x1bb0,'(qw$')](E[eY(0x359,'$(mV')]);}),$(eP(0x116f,'hK)l'))[eP(0x4cf,'H4NX')](function(E){var eZ=eP;C[eZ(0x2b8a,'eu*B')](),p[eZ(0x4a7e,'NrJk')](E[eZ(0x4195,'*ZKE')]);}),$(eP(0x3205,'6YaW'))[eP(0x452e,'*gI2')](function(E){var f0=eP;C['reset'](),p['toggle'](E[f0(0x2b75,'vGE1')]);}),$(editor[eP(0x1fb7,'XcPC')][eP(0x4d7f,'H4NX')])['on'](eP(0x3cfc,'VTTT'),function(E){var f1=eP;C['setToolButtonStatus'](E[f1(0x10ec,'Bbr@')]),C[f1(0x2b8a,'eu*B')]();}));},C[eC(0x44c5,'esU(')]=function(){var f2=eC;$(f2(0x1a21,'xbuu'))['change'](function(F){var f3=f2,G=F[f3(0x43b7,'Jj0M')][f3(0x3d77,'xbuu')][0x0];G&&(E['type']=G[f3(0x1392,'c]Mq')],f3(0x1923,'esU(')==G[f3(0x4b0,'Bbr@')]||f3(0x27e2,'Jj0M')==G[f3(0x3cfa,'^Syn')]||f3(0x50f,'zbs!')==G['type']?E[f3(0x1c67,'IvBK')](G):G[f3(0x1392,'c]Mq')]['indexOf']('image')>-0x1?E[f3(0xdd8,'IvBK')](G):E[f3(0x2603,'*ZKE')](G));});var E=new FileReader();E[f2(0x3c47,'zx#%')]=function(F){var f4=f2;if(f4(0x23ab,'e*c8')==F['target']['type'])editor['history']=[],editor[f4(0x4c51,'sYdH')](F['target'][f4(0x34a,'hK)l')]);else{if(f4(0x3510,'!%Yf')==F[f4(0x1cf8,'IvBK')]['type']){let H=JSON[f4(0x2421,'sYdH')](F[f4(0x2924,'H4NX')][f4(0x314,'NrJk')]);H&&('importJsonWithCode'==$(f4(0x3bcc,'Bbr@'))[0x0][f4(0x3e79,'VTTT')][f4(0x410,'XCXV')]?editor['setBindObject'](H,void 0x0,!0x0):editor[f4(0x4e35,'$(mV')](H));}else{if(F['target'][f4(0x3eb7,'^d9X')][f4(0x1518,'Jc8$')](f4(0x3148,'*gI2'))>-0x1){var G=F[f4(0x4243,'XCXV')][f4(0x37b6,'^d9X')];A[f4(0x3e5b,'(qw$')]({'arrayBuffer':G})[f4(0x4c5b,'(qw$')](I=>{var f5=f4;editor['$'](f5(0x3bd4,'*ZKE'))['html'](''),editor['$'](f5(0x6d4,'H4NX'))[f5(0x4ebd,'Nf2)')](''),editor['$'](f5(0x15db,'*gI2'))['html'](I[f5(0xed2,'XCXV')]);});}else{if('text/xml'==F[f4(0x4517,'Nf2)')][f4(0xed4,'t#L0')]){if(f4(0x17a5,'6YaW')==$(f4(0x3d00,'H4NX'))[0x0]['dataset'][f4(0x4c8b,'Ws11')])$['post'](f4(0x3aea,'eu*B'),{'data':F['target']['result'],'title':$('#_importfile')[f4(0x3acd,'eu*B')]()},function(I){var f6=f4;editor[f6(0x29bf,'*p#P')](I);});else{let I=z[f4(0x13d9,'5WLj')](F[f4(0x10c1,'t#L0')][f4(0x3cc8,'zbs!')]);I&&editor['setBindObject'](I);}}else F[f4(0x3de3,'#ZU^')][f4(0x2402,'esU(')]['indexOf'](f4(0x5d3,'hK)l'))>-0x1&&editor[f4(0x2306,'!%Yf')][f4(0x9ed,'^Syn')](F[f4(0x10ec,'Bbr@')][f4(0x1fb1,'^Syn')]);}}}};},C[eC(0x413a,'XCXV')]=function(){var f7=eC;$(f7(0x3cb0,'!%Yf'))[f7(0x4679,'VTTT')](''),$('#_importfile')[f7(0x444a,'UDz8')](f7(0x1f64,'lmQg'),f7(0x4c9e,'6YaW')),$(f7(0x3978,'vGE1'))['click']();},C[eC(0x3735,'v^si')]=function(){var f8=eC;$('#_importfile')[f8(0x1ff,'oKRq')](''),$(f8(0x3b55,'5WLj'))['attr'](f8(0x3dd7,'Nf2)'),f8(0x3351,'Ws11')),$(f8(0x3c22,'MDwy'))[f8(0x6ed,'N!xX')]();},C['importJsonWithCode']=function(){var f9=eC;$(f9(0x3892,'eu*B'))[0x0]['dataset'][f9(0x396b,'oKRq')]=f9(0x3b06,'eu*B'),$(f9(0x1258,'XCXV'))['val'](''),$(f9(0x444c,'zx#%'))[f9(0x2a64,'Bbr@')](f9(0x3f2b,'V8^Q'),f9(0x11c,'(qw$')),$(f9(0x4cb7,'c]Mq'))[f9(0x47ee,'oKRq')]();},C[eC(0x260f,'pMQs')]=function(){var fa=eC;$('#_importfile')[0x0][fa(0x2790,'IvBK')][fa(0x3079,'zx#%')]=fa(0x1214,'!Yg['),$(fa(0x67b,'^Syn'))[fa(0x13fd,'xbuu')](''),$(fa(0x3dc8,'e*c8'))[fa(0xcb3,'*p#P')](fa(0x42d4,'xbuu'),fa(0x4b66,'kGY9')),$(fa(0x3b55,'5WLj'))[fa(0x1db9,'xrMR')]();},C[eC(0x1ebc,'UDz8')]=function(){var fb=eC;$('#_importfile')[0x0][fb(0x3034,'Ws11')][fb(0xcb5,'xrMR')]='importWord',$(fb(0x4300,'$(mV'))['val'](''),$(fb(0x3b5b,'Ws11'))[fb(0x388f,'v^si')]('accept',fb(0x989,'pMQs')),$('#_importfile')[fb(0x467e,'hRv2')]();},C[eC(0x99f,'lnIj')]=function(){var fc=eC;$('#_importfile')[0x0][fc(0x15eb,'$(mV')][fc(0x3d61,'e*c8')]=fc(0x4e91,'@HIr'),$('#_importfile')[fc(0xd50,'v^si')](''),$(fc(0x6a2,'XcPC'))[fc(0x36fb,'esU(')](fc(0x22b4,'kGY9'),fc(0x121d,'Bbr@')),$('#_importfile')[fc(0x1a7f,'c]Mq')]();},C[eC(0x3630,'esU(')]=function(E){var fd=eC;$('#_importfile')[fd(0x32fe,'Nf2)')](''),$(fd(0x3b5b,'Ws11'))[fd(0x24cf,'^Syn')]('accept',fd(0x3add,'5WLj')),$(fd(0x1a21,'xbuu'))[fd(0x1357,'#ZU^')]();},C[eC(0x22e6,'$(mV')]=function(E){var fe=eC;$(fe(0x48dd,'#ZU^'))[fe(0x335a,'VTTT')](fe(0x3c8a,'e*c8')+E+'\x22]')['addClass'](fe(0x49b9,'Ws11'));},C[eC(0x4047,'Ws11')]=function(){var ff=eC;$('.tool-menu')['hide'](),$('.menu-container')[ff(0x3521,'XCXV')]();},C[eC(0x16fe,'pMQs')]=function(E){var fg=eC;'design'==E?($(fg(0x23af,'eu*B'))[fg(0x2755,'lmQg')](fg(0x2ebf,'UDz8'))[fg(0x1316,'kGY9')](),$(fg(0x1d14,'hRv2'))[fg(0x327a,'*gI2')](fg(0x488b,'$(mV'))[fg(0x3b03,'Jc8$')]()):'form'==E&&($(fg(0x3718,'6YaW'))[fg(0x1995,'XcPC')](fg(0x82d,'v^si'))['hide'](),$(fg(0xd98,'lmQg'))[fg(0x210a,'esU(')](fg(0x1996,'1Ue]'))['show']());},C[eC(0x1eec,'Bbr@')]=function(E){var fh=eC,F=getComputedStyle(E);for(F[fh(0x217d,'pMQs')]>=0x2bc?$('#_bold')['addClass'](fh(0x35e3,'^d9X')):$(fh(0xc85,'*p#P'))[fh(0x23cd,'e*c8')]('active'),fh(0x303,'Bbr@')==F['fontStyle']?$(fh(0x31a8,'*gI2'))[fh(0x11ef,'lnIj')](fh(0x2cd9,'*p#P')):$(fh(0xb71,'[T9%'))['removeClass'](fh(0x2db8,'pMQs')),'underline'==F[fh(0x1672,'hK)l')]?$(fh(0x2983,'vGE1'))[fh(0x2621,'@HIr')](fh(0x3ec2,'!Yg[')):$(fh(0xd9a,'$(mV'))[fh(0x3db7,'vGE1')](fh(0x3679,'xbuu')),$('#_fontSize')[fh(0x3682,'zbs!')](fh(0x4219,'zbs!'))[fh(0x4b78,'MDwy')](w['convertFontSize'](F[fh(0x1aae,'Nf2)')])),$('#_fontName')[fh(0x3c5e,'XCXV')]('span')[fh(0x12f1,'H4NX')](y[fh(0x43ff,'UDz8')](F[fh(0x2f29,'H4NX')][fh(0xdf2,'XCXV')]('\x22',''))),$(fh(0x3090,'XCXV'))[fh(0x264e,'c]Mq')](fh(0x4900,'#ZU^'))[fh(0x4cdc,'*p#P')]('正文');E;){if('H1'==E[fh(0x4dde,'sYdH')]){$('#_fontHead')[fh(0x3682,'zbs!')]('span')[fh(0x2b9a,'Jj0M')](fh(0x9c6,'Bbr@'));break;}if('H2'==E['nodeName']){$(fh(0x1d70,'IvBK'))[fh(0x27f6,'H4NX')](fh(0x2f06,'xbuu'))[fh(0x1620,'^d9X')](fh(0x3f96,'v^si'));break;}if('H3'==E[fh(0xc15,'Jj0M')]){$(fh(0x2ca4,'H4NX'))[fh(0xccf,'5WLj')]('span')['html'](fh(0x16c1,'MDwy'));break;}if('H4'==E[fh(0x20eb,'e*c8')]){$(fh(0xeab,'$(mV'))[fh(0x2755,'lmQg')]('span')[fh(0x4b78,'MDwy')]('标题4');break;}E=E[fh(0x1b0f,'NrJk')];}},C;}['apply'](g,j))||(f[fi(0x484a,'xbuu')]=k);},0x193d:(f,g,h)=>{var fj=a0f,j,k;j=[h(0x10f4)],void 0x0===(k=function(l){return l;}[fj(0x4d77,'$(mV')](g,j))||(f[fj(0x4a7a,'eu*B')]=k);},0x264a:(f,g,h)=>{var fG=a0f,j,k;j=[h(0x1b6d),h(0x1807)],void 0x0===(k=function(l){let m=function(p){var fk=a0f;let q=editor[fk(0x5fc,'(qw$')]()[fk(0x37e3,'!%Yf')];q&&(editor['saveHistory'](),0x3==q[fk(0x2088,'MDwy')]?$(q)[fk(0x3ffe,'esU(')](fk(0x4248,'kGY9'))[fk(0x37cd,'^Syn')]()[0x0][fk(0x2220,'oKRq')]('afterend',p):[fk(0x455d,'sYdH'),fk(0x7a6,'Nf2)'),fk(0x216c,'zbs!'),fk(0x47dc,'IvBK')][fk(0x17e2,'MDwy')](q['id'])>=0x0||['TD'][fk(0x7a7,'zx#%')](q['tagName'])>=0x0?q[fk(0x209d,'1Ue]')](fk(0x27eb,'@HIr'),p):q[fk(0x19e8,'Ws11')](fk(0x9c8,'pMQs'),p),editor[fk(0x48d3,'!Yg[')][fk(0x2934,'Nf2)')](new CustomEvent(fk(0x1da0,'XCXV'))));},o=function(){var fl=a0f;editor['$'](fl(0x2e4f,'[T9%'))[fl(0x3045,'*gI2')](function(p,q){var fm=fl,s=q[fm(0xc10,'v^si')]('format');s=s||fm(0x292d,'xbuu');var u=q[fm(0x2468,'oKRq')](fm(0x2b2b,'NrJk'));u=u||'no\x20value','QRCODE'==s?l[fm(0x395a,'N!xX')](u,function(v,w){var fn=fm;q[fn(0x3f2c,'$(mV')](fn(0x2f34,'vGE1'),w);}):JsBarcode(q,q[fm(0x1eba,'Ws11')][fm(0x2eca,'^d9X')]['value'],{'height':0x1e,'format':s,'displayValue':!0x0,'fontSize':0x14}),q[fm(0x30ec,'xrMR')](fm(0x4ea2,'MDwy'),u),q[fm(0x4889,'!Yg[')](fm(0x4e3b,'@HIr'),s);});};return{'insertHTML':m,'insertPageNum':function(){var fo=a0f;m([fo(0x1235,'lnIj'),'共#页'][fo(0x10b7,'lnIj')](''));},'insertField':function(p){var fp=a0f;let q=new Date()['getTime']();var s=['请选择')):'DataList'==p?(s[fp(0x4c86,'v^si')](fp(0x2683,'MDwy')),s[fp(0x4ea,'oKRq')]('\x20data=\x22/icd.json\x22'),s[fp(0x3f34,'#ZU^')]('\x20/>请输入')):fp(0x13f2,'xrMR')==p&&(s[fp(0x449,'sYdH')](fp(0x14d9,'!Yg[')),s['push'](fp(0x16d2,'VTTT')));s[fp(0x2117,'@HIr')](fp(0x1c57,'$(mV')),m(s['join']('')),fp(0x2f02,'1Ue]')!=p&&fp(0x2dde,'^d9X')!=p||editor['$']('#'+q)[fp(0x16fd,'!Yg[')](fp(0x2f61,'N!xX'),fp(0x11f1,'XCXV')),editor['$']('#'+q)['focus'](),setTimeout(function(){var fq=fp;editor['$'](fq(0x3fee,'!Yg['))[fq(0x4408,'XcPC')]('shink');},0x1f4);},'insertDropdownByDic':function(p,q,s){var fr=a0f,u=[fr(0x11e5,'zx#%'),p[fr(0x3689,'XcPC')]?fr(0x84b,'NrJk')+p[fr(0x128d,'v^si')]+fr(0x401a,'lnIj')+p[fr(0x29be,'xbuu')]+'\x22':'',p[fr(0x28a8,'vGE1')]?fr(0xada,'6YaW')+p[fr(0x3c81,'esU(')]+'\x22':'',fr(0x207d,'XcPC'),fr(0x476b,'IvBK'),'\x20title=\x22'+p[fr(0x3ac8,'!Yg[')]+'\x22','\x20value=\x22'+q[0x0]['value']+'\x22','\x20data-list='+JSON[fr(0x4049,'H4NX')](q),'\x20/>',q[0x0]['text'],fr(0x1dcd,'^Syn')];'有'==q[0x0][fr(0x2d76,'UDz8')]||'无'==q[0x0][fr(0xeed,'hK)l')]?u[fr(0x344d,'Nf2)')](p[fr(0x1fef,'#ZU^')]):u['splice'](0x0,0x0,p['title']),u[fr(0x3a5e,'Bbr@')](s),m(u[fr(0x3a83,'5WLj')]('')),setTimeout(function(){var fs=fr;editor['$'](fs(0x1dc9,'oKRq'))[fs(0xf3e,'(qw$')]('shink');},0x1f4);},'insertFieldByDic':function(p){var ft=a0f;let q=ft(0x4b05,'!%Yf');'DT'==p[ft(0x2fd2,'vGE1')]?q=ft(0x165d,'!%Yf'):'DA'==p['type']&&(q='\x20type=\x22DateTime\x22\x20format=\x22yyyy年MM月dd日\x22');var s=[editor[ft(0x4ea4,'*ZKE')][ft(0x26d0,'6YaW')]?p['title']+':':'',' '];m(s['join']('')),setTimeout(function(){var fu=ft;editor['$'](fu(0x4b97,'eu*B'))[fu(0x40a4,'Nf2)')](fu(0xa5f,'Nf2)'));},0x1f4);},'insertReference':function(p){var fv=a0f;if(p&&p[fv(0x3a91,'IvBK')]>0x0){let s=p[0x0];var q=[s['field_name'],fv(0x3e93,'^d9X'),fv(0xe25,'H4NX')+s[fv(0x32aa,'kGY9')]+'\x22',fv(0x165a,'zx#%')+s[fv(0x248a,'*ZKE')]+'\x22','\x20type=\x22Text\x22',fv(0x1ebf,'*gI2')+s[fv(0x4bff,'Ws11')]+'\x22',fv(0x3311,'NrJk'),fv(0x196a,'Bbr@')+s[fv(0x4169,'xrMR')]+'\x22',fv(0x1585,'Ws11'),s['field_name'],fv(0xc6a,'UDz8')];m(q[fv(0x4498,'Jc8$')]('')),editor['$']('#'+s[fv(0x2b8e,'Bbr@')])['attr'](fv(0xb7,'5WLj'),fv(0x99d,'e*c8')),setTimeout(function(){var fw=fv;editor['$'](fw(0x200b,'!%Yf'))[fw(0x540,'eu*B')](fw(0x41e7,'^d9X'));},0x1f4);}},'insertCheckbox':function(){var fx=a0f,p=editor['$'](fx(0x2e3,'xrMR'))[fx(0x1a93,'V8^Q')];m(['',fx(0x22dd,'lmQg')+p+fx(0x3499,'sYdH')+p+fx(0x461b,'lmQg')+p+fx(0x1f0e,'zx#%'),fx(0xb22,'V8^Q')+p+fx(0x2c5a,'6YaW')+p+fx(0x13ec,'e*c8')+p+fx(0x2c9b,'xrMR'),fx(0xbc2,'H4NX')+p+fx(0x1236,'5WLj')+p+'3\x22\x20value=\x22选项3\x22/> '][fx(0xee6,'N!xX')](''));},'insertRadio':function(){var fy=a0f,p=editor['$']('group[type=radio]')[fy(0x1c25,'$(mV')];m([fy(0x2c0,'!Yg[')+p+'\x22>',fy(0x4466,'Jc8$')+p+fy(0x32b7,'VTTT')+p+'\x22\x20value=\x22选项1\x22/>');},'insertTable':function(p,q){var fA=a0f;for(var s=[fA(0x219c,'[T9%'),fA(0x1172,'v^si')],u=0x0;u');for(s[fA(0x4f7,'!%Yf')](''),u=0x0;u'),o();},'insertLine':function(){var fE=a0f;m(fE(0x333c,'*gI2'));},'insertParagraph':function(){var fF=a0f;m(fF(0x26ce,'#ZU^'));},'rendbarcode':o};}[fG(0x43e1,'xbuu')](g,j))||(f[fG(0x1932,'*ZKE')]=k);},0x1b42:(f,g,h)=>{var fV=a0f,j,k;j=[h(0x8d1),h(0x1b95)],void 0x0===(k=function(l,m){let o=function(p,q){var fH=a0f;const s=URL['createObjectURL'](p),u=document[fH(0x2a2b,'oKRq')]('a');document[fH(0x4740,'zbs!')]['appendChild'](u),u[fH(0x42ed,'xrMR')]='display:\x20none',u['href']=s,u[fH(0x361f,'t#L0')]=q,u[fH(0x467e,'hRv2')](),window[fH(0x114f,'hRv2')][fH(0x1061,'MDwy')](s),document[fH(0x417,'Ws11')]['removeChild'](u);};return{'exportXml':function(){var fI=a0f;let p=editor[fI(0x3a42,'XCXV')](),q=m({'EMR':p},{'header':!0x0}),s=new Blob([q],{'type':fI(0x18fa,'sYdH')}),u=editor[fI(0x291c,'v^si')]['title']+fI(0x48e8,'c]Mq');o(s,u);},'exportHtml':function(){var fJ=a0f;let p=editor[fJ(0x4cb0,'6YaW')]();var q=new Blob([p],{'type':fJ(0x2ffe,'@HIr')}),s=editor[fJ(0x38b8,'eu*B')]['title']+fJ(0x2e0f,'UDz8');o(q,s);},'exportHtmlWithStyle':function(){var fK=a0f;let p=editor[fK(0x43a,'Q@hO')]();var q=new Blob([p],{'type':'text/html'}),s=editor[fK(0x41fa,'lmQg')][fK(0x47d0,'pMQs')]+fK(0x4cdb,'zbs!');o(q,s);},'previewHtml':function(){var fL=a0f;let p=editor[fL(0x48d3,'!Yg[')]['title'],q=l['getHtmlforPrint']();!function(s,u){var fM=fL;const v=new File([s],u,{'type':'text/html'}),w=URL[fM(0x2353,'[T9%')](v);window['open'](w,fM(0x18f,'5WLj'));}(new Blob([q],{'type':fL(0x1d34,'hK)l')}),p);},'exportPdf':function(p){var fN=a0f;editor[fN(0x1d07,'hK)l')]();var q={'html':l[fN(0x359e,'zx#%')]()};editor[fN(0x1c6f,'xbuu')]();var s=editor['document'][fN(0x17ae,'MDwy')]+fN(0x1b08,'#ZU^');fetch(editor[fN(0x4773,'H4NX')][fN(0x442f,'t#L0')],{'method':fN(0x3b62,'Bbr@'),'body':JSON[fN(0x34f8,'!Yg[')](q),'headers':{'Content-Type':fN(0x1f53,'NrJk')}})[fN(0x36ae,'hK)l')](u=>{var fO=fN;const v=u[fO(0xee2,'c]Mq')][fO(0x83a,'xbuu')]();return new ReadableStream({'start'(w){!function x(){var fP=a0f;v[fP(0x48cc,'N!xX')]()[fP(0x3a41,'N!xX')](({done:y,value:z})=>{var fQ=fP;y?w['close']():(w[fQ(0x4cc7,'zx#%')](z),x());});}();}});})[fN(0x3b7d,'kGY9')](u=>new Response(u,{'headers':{'Content-Type':'application/pdf'}})['blob']())[fN(0x765,'5WLj')](u=>{p?o(u,s):function(v,w){var fR=a0f;const x=new File([v],w,{'type':fR(0x4e43,'H4NX')}),y=URL[fR(0x500,'!Yg[')](x);window[fR(0x318b,'XCXV')](y,fR(0x2f4,'^d9X'));}(u,s);})[fN(0x22af,'Jj0M')](u=>{var fS=fN;console[fS(0x3e0a,'!%Yf')](u);});},'exportJson':function(){var fT=a0f;let p=editor[fT(0x3e67,'eu*B')](),q=JSON[fT(0xd02,'IvBK')](p,void 0x0,0x2),s=new Blob([q],{'type':fT(0xb80,'N!xX')}),u=editor[fT(0x1fb7,'XcPC')][fT(0x388b,'vGE1')]+'.json';o(s,u);},'exportJsonWithCode':function(){var fU=a0f;let p=editor[fU(0x1c12,'lmQg')](void 0x0,!0x0),q=JSON[fU(0x3f23,'1Ue]')](p,void 0x0,0x2),s=new Blob([q],{'type':fU(0x370e,'Ws11')}),u=editor['document'][fU(0x17ae,'MDwy')]+fU(0x33c0,'eu*B');o(s,u);}};}['apply'](g,j))||(f[fV(0x48f3,'MDwy')]=k);},0x970:(f,g)=>{var g2=a0f,h;void 0x0===(h=function(){return{'bindDataList':function(j,k){var fW=a0f;let l=editor['$']('#'+j)[0x0];if(l&&k){let m=l['dataset'][fW(0x4e5c,'V8^Q')]?l[fW(0x2d83,'hK)l')][fW(0x2ccd,'^Syn')][fW(0x2eec,'pMQs')](','):[],o=l['tBodies'][0x0];$(o)['empty']();let p=0x0;k['forEach'](q=>{var fX=fW;p++;let s=editor[fX(0x1ade,'6YaW')][fX(0x1878,'XCXV')]('tr');m[fX(0x3d40,'VTTT')](u=>{var fY=fX;let v=editor['document'][fY(0x13b5,'*gI2')]('td');v[fY(0xd9,'5WLj')]=fY(0x8de,'Nf2)')==u?p:q[u],s[fY(0x359f,'v^si')](v);}),o[fX(0x3338,'sYdH')](s);});}},'appendDataList':function(j,k){var fZ=a0f;let l=editor['$']('#'+j)[0x0];if(l&&k){let m=l['dataset'][fZ(0x1a4d,'@HIr')]?l[fZ(0x3ef2,'esU(')][fZ(0x3d62,'Jc8$')][fZ(0x36c6,'IvBK')](','):[],o=l[fZ(0x41fd,'t#L0')][0x0],p=o[fZ(0x43d1,'^Syn')][fZ(0x3698,'!Yg[')];k[fZ(0x710,'vGE1')](q=>{var g0=fZ;p++;let s=editor[g0(0xc8f,'Ws11')][g0(0x4d9b,'5WLj')]('tr');m[g0(0x710,'vGE1')](u=>{var g1=g0;let v=editor[g1(0x718,'MDwy')]['createElement']('td');v[g1(0x4035,'6YaW')]='NUM'==u?p:q[u],s[g1(0x22eb,'V8^Q')](v);}),o[g0(0x2778,'N!xX')](s);});}}};}['apply'](g,[]))||(f[g2(0x26ff,'lnIj')]=h);},0x1336:(f,g,h)=>{var gK=a0f,j,k;j=[h(0x8d1)],void 0x0===(k=function(l){var g3=a0f;let m=['{var g5=g4;z+0x1{var g7=g6;A==w[g7(0xd49,'xbuu')]&&(x=!0x0,editor['$'](g7(0x3cbf,'e*c8'))[0x0]['dataset'][g7(0x3f54,'Bbr@')]=z+0x1,editor['$']('#_page')[0x0][g7(0x15ac,'#ZU^')][g7(0x18ca,'hRv2')]=w[g7(0x396d,'Jc8$')],$(y[z])[g7(0x3715,'[T9%')](m),$(y[z])[g7(0x3654,'xbuu')](g7(0x1744,'Jj0M'))[g7(0x450f,'pMQs')](g7(0x31ca,'MDwy'),w[g7(0x3523,'t#L0')])),x||$(y[z])['append'](m);});},s=function(){var g8=g3;editor['$']('.blank[validate=true]')[g8(0x33d5,'hK)l')](function(w,x){var g9=g8;x[g9(0x3dcd,'^d9X')][g9(0x4f30,'e*c8')]='relative';var y=editor[g9(0x38b8,'eu*B')][g9(0x2430,'N!xX')](g9(0x47da,'!%Yf'));y[g9(0x16e6,'XCXV')][g9(0x1573,'xrMR')]('message'),y[g9(0x2865,'XcPC')](g9(0x46bc,'Jc8$'),g9(0x184a,'zbs!')),y[g9(0x2ed7,'Q@hO')]='必须输入',x[g9(0x1861,'H4NX')](y);});};function u(w){var ga=g3;let x=editor[ga(0x11f9,'V8^Q')]['getSelection']();if(ga(0x43f2,'!Yg[')!=editor[ga(0x378,'6YaW')][ga(0x1174,'hK)l')]||x['rangeCount']<0x0)return;editor['saveHistory']();let y=x[ga(0x1036,'hRv2')](0x0);var z=!0x1;if(y['collapsed'])A(y[ga(0x163f,'*ZKE')]);else{let B=y[ga(0x739,'hRv2')];0x1!=B[ga(0x2372,'Ws11')]&&(B=B[ga(0x30b1,'1Ue]')]),function C(D){var gb=ga;D==y['startContainer']&&(z=!0x0);for(let E=0x0;E0x0&&A(E);}else D=D['parentElement'],w?D[gc(0x2b51,'kGY9')]['fontFamily']='':D[gc(0x2baf,'V8^Q')]['fontSize']='';}}let v=function(w){var gd=g3;editor['$'](gd(0x4c47,'VTTT'))[gd(0xdf7,'eu*B')](w);};return{'newdoc':function(w){var ge=g3;w?(editor[ge(0x44c2,'oKRq')]['title']='空白文档',editor[ge(0x2a35,'*ZKE')]['id']=new Date()[ge(0x1ef6,'Ws11')]()):(editor[ge(0x47a2,'Q@hO')][ge(0x4382,'hK)l')]=ge(0x2625,'kGY9'),editor[ge(0x718,'MDwy')]['id']=new Date()['getTime'](),editor[ge(0x170b,'zx#%')][ge(0x4097,'v^si')]&&window[ge(0x4918,'#ZU^')][ge(0x3f1b,'Bbr@')]('','',ge(0x1034,'^d9X')+editor[ge(0xb50,'pMQs')]['id']+ge(0x3388,'pMQs')));let x=['',ge(0x950,'zx#%'),ge(0x3e02,'*ZKE'),ge(0x4e31,'!%Yf'),ge(0x39e8,'N!xX'),ge(0x3ad3,'(qw$'),''][ge(0x3e5a,'@HIr')]('');editor[ge(0x40c7,'hRv2')](x),editor['setMode'](editor['option'][ge(0x1584,'5WLj')]);},'save':function(){var gf=g3;let w=editor[gf(0x2b54,'XcPC')]();try{localStorage[gf(0x2376,'*p#P')](editor['document']['id']+'-'+new Date()[gf(0x30fb,'5WLj')](),w);}catch(x){editor['clearLocal']();}$[gf(0x3964,'XcPC')](editor[gf(0x3ec6,'zbs!')][gf(0x3fc3,'1Ue]')]+'/'+editor[gf(0x15f9,'c]Mq')]['id']+gf(0x1096,'Bbr@'),{'data':w,'title':editor[gf(0x3f1e,'zbs!')][gf(0x4f28,'H4NX')]},function(y){var gg=gf;editor[gg(0x2f14,'oKRq')](gg(0x3e1c,'MDwy'));}),s();},'setHeaderFromUrl':function(w,x){return new Promise(function(y,z){var gh=a0f;$[gh(0x1ff9,'#ZU^')](w,function(A){var gi=gh,B;A?(editor['printWindow'][gi(0x47a2,'Q@hO')][gi(0x800,'lmQg')][gi(0xebb,'Jj0M')]=A,v($(editor[gi(0x33b9,'NrJk')][gi(0x19d4,'N!xX')]['body'])[gi(0x331c,'IvBK')](gi(0x2ca3,'IvBK'))[gi(0x12db,'$(mV')]()),x&&(B=$(editor['printWindow'][gi(0x1028,'#ZU^')]['body'])[gi(0x28c7,'(qw$')](gi(0x2259,'v^si'))[gi(0x27cf,'*ZKE')](),editor['$'](gi(0x101e,'MDwy'))[gi(0x23e2,'XcPC')](B)),y()):z();});});},'setHeaderFromHtml':v,'saveToLocal':function(){var gj=g3;let w=editor[gj(0x4cb0,'6YaW')]();var x=new Date();localStorage[gj(0xfd3,'t#L0')](editor['document']['id']+'-'+x['getTime'](),w),editor[gj(0x48b5,'Jj0M')]('保存到本地缓存!');},'clearLocal':function(w){var gk=g3;for(let x=localStorage[gk(0x3a91,'IvBK')]-0x1;x>=0x0;x--){let y=localStorage[gk(0x6fd,'Nf2)')](x);console[gk(0x1700,'hRv2')](y),gk(0x3a8b,'Bbr@')!=y&&localStorage[gk(0x20e4,'NrJk')](y);}editor[gk(0x2d44,'XCXV')][gk(0x49b,'e*c8')](),editor['showMessage'](gk(0x152a,'xrMR'));},'print':function(){var gl=g3;editor[gl(0x458b,'!Yg[')](),l[gl(0x3b98,'c]Mq')](),p(!0x0),editor[gl(0x25b1,'Nf2)')][gl(0x3570,'Jc8$')](),editor[gl(0xce6,'Jc8$')]();},'preview':function(w){var gm=g3;editor[gm(0x17fa,'Q@hO')](),l['printView'](),editor[gm(0x381e,'H4NX')](),$(gm(0x3fb5,'MDwy'))[gm(0x2c71,'!%Yf')](),p(!0x0),$(editor['printWindow'][gm(0xe2c,'@HIr')])[gm(0x461c,'Jc8$')](gm(0x2df,'Bbr@')),$('#_emreditor')[gm(0x2ad1,'!Yg[')]();},'printStart':function(w){var gn=g3;l[gn(0x2f37,'XcPC')](),$(gn(0x4225,'VTTT'))['hide'](),$(gn(0x2fbf,'hK)l'))[gn(0x38c1,'hK)l')](),w&&w[gn(0x1fa,'hK)l')]['add'](gn(0x2cd9,'*p#P')),$(editor['printWindow']['document'])[gn(0x4ad,'eu*B')](gn(0x26de,'^d9X')),$(editor[gn(0x4d73,'IvBK')]['document'])['on']('mousedown',gn(0xda3,'hRv2'),q),p();},'directPrint':function(w){var go=g3;editor[go(0x3a71,'UDz8')]();let x=l['getHtmlforPrint']();editor['initInnerTool']();let y=w?go(0x40e3,'1Ue]'):go(0x2d84,'IvBK');$['ajax']({'type':'POST','data':{'data':x},'url':go(0x1dbc,'kGY9')+y,'success':function(z,A,B){var gp=go;editor['showMessage'](w?gp(0x18df,'vGE1'):gp(0x4207,'lnIj'));},'error':function(z,A,B){var gq=go;editor['toast'](gq(0x1690,'VTTT'));}});},'validate':s,'paste':function(){var gr=g3;gr(0x30a8,'hRv2')==browser&&navigator[gr(0xbb,'XCXV')][gr(0xc7f,'hRv2')]()[gr(0xb8f,'xrMR')](w=>{var gs=gr;for(let x=0x0;x{var gt=gs,A=new FileReader();A[gt(0x4211,'kGY9')]=function(B){var gu=gt;editor['document']['execCommand'](gu(0x42b1,'XCXV'),!0x1,A[gu(0xbfa,'Jc8$')]);},A[gt(0x3de6,'MDwy')](z,gt(0xb56,'XcPC'));});});},'pasteText':function(){var gv=g3;navigator[gv(0x22ab,'Q@hO')][gv(0x12dd,'zbs!')]()[gv(0x255d,'e*c8')](w=>{var gw=gv;editor[gw(0x1527,'sYdH')][gw(0x4d21,'!%Yf')](gw(0x39c5,'NrJk'),!0x1,w);});},'undo':function(){var gx=g3;if(editor['history'][gx(0x343e,'Jc8$')]>0x0){var w=editor[gx(0x416,'IvBK')][gx(0x27b,'Q@hO')]();return 0x0==editor['historyRedo'][gx(0x4cf8,'@HIr')]?editor[gx(0x8bb,'^d9X')]['push'](editor[gx(0x40b5,'hRv2')][gx(0x354c,'*p#P')](gx(0x3d56,'v^si'))[gx(0x3667,'oKRq')]):editor[gx(0x8bb,'^d9X')][gx(0x859,'MDwy')](w),editor[gx(0xd5a,'XCXV')][gx(0x4702,'vGE1')]('_page')[gx(0x3ed0,'zbs!')]=w,editor[gx(0x3fcb,'esU(')](!0x0),editor['initInnerTool'](!0x0),editor[gx(0x1fb7,'XcPC')][gx(0x2d87,'H4NX')](new CustomEvent('input')),!0x0;}return!0x1;},'redo':function(){var gy=g3;if(editor[gy(0x3493,'UDz8')]['length']>0x0){var w=editor[gy(0x908,'Jc8$')]['getElementById'](gy(0xb41,'Bbr@'))[gy(0x600,'!Yg[')];return editor[gy(0x212d,'N!xX')][gy(0x1a5e,'t#L0')](w),editor[gy(0x468e,'xbuu')][gy(0x21c6,'c]Mq')](gy(0x455d,'sYdH'))['outerHTML']=editor['historyRedo']['pop'](),editor['clearInnerTool'](!0x0),editor['initInnerTool'](!0x0),editor[gy(0x44c2,'oKRq')][gy(0x5ed,'XCXV')](new CustomEvent(gy(0x3e2f,'VTTT'))),!0x0;}return!0x1;},'zoom':function(w){var gz=g3;w?(w>0x0||editor['option']['scale']>0.5)&&(editor[gz(0x1a3d,'N!xX')][gz(0x1055,'Q@hO')]+=w):editor[gz(0x2bc6,'MDwy')]['scale']=0x1,editor[gz(0x3b45,'zx#%')](editor[gz(0x1ca7,'Nf2)')][gz(0x186f,'!Yg[')]);var x=new CustomEvent(gz(0x3a6a,'H4NX'),{'bubbles':!0x0,'detail':{'scale':editor['option']['scale']}});document[gz(0x1078,'kGY9')](x);},'fullScreen':function(w){var gA=g3;if(document[gA(0xe80,'eu*B')])return document['exitFullscreen'](),void(w&&(w[gA(0x28ba,'Nf2)')][0x0][gA(0x6da,'t#L0')]['remove']('icon-exitfullscreen'),w[gA(0x13c6,'oKRq')][0x0]['classList'][gA(0x2459,'N!xX')](gA(0x11d2,'oKRq'))));w&&(w['children'][0x0][gA(0x16e6,'XCXV')]['remove']('icon-fullscreen'),w['children'][0x0][gA(0x2191,'xrMR')][gA(0xabb,'Q@hO')](gA(0x497e,'VTTT'))),document[gA(0x70b,'xrMR')][gA(0x672,'c]Mq')]?document[gA(0x29b1,'[T9%')][gA(0x4c80,'lmQg')]():document['documentElement'][gA(0x3e7d,'xbuu')]?document[gA(0x31cd,'VTTT')][gA(0x4dc6,'!Yg[')]():document[gA(0x16bc,'UDz8')][gA(0xd2f,'N!xX')]&&document[gA(0x70b,'xrMR')]['webkitRequestFullScreen']();},'setTextIndent':function(){var gB=g3;let w=editor[gB(0x308d,'^d9X')]['getSelection']()[gB(0x40da,'c]Mq')];for(;'P'!=w['tagName']&&'DIV'!=w[gB(0x2698,'hK)l')];)w=w[gB(0x4a92,'H4NX')];w[gB(0x4afb,'*gI2')][gB(0x3b7c,'[T9%')]=gB(0x2e62,'(qw$');},'cancelTextIndent':function(){var gC=g3;let w=editor['contentWindow'][gC(0x132b,'*p#P')]()[gC(0x3600,'hK)l')];for(;'P'!=w[gC(0x250c,'!%Yf')]&&gC(0x3428,'pMQs')!=w[gC(0xf66,'V8^Q')];)w=w[gC(0x40a,'hK)l')];w['style'][gC(0x4164,'NrJk')]='';},'removeFormat':function(){var gD=g3;editor['saveHistory'](),editor['$']('h1,h2,h3,h4')[gD(0x372,'zx#%')]((w,x)=>{var gE=gD;let y=editor[gE(0x1527,'sYdH')]['createElement']('p');y[gE(0x3207,'V8^Q')]=x[gE(0x4ac9,'MDwy')],x[gE(0x3cbe,'lmQg')][gE(0x4709,'sYdH')](y,x),x[gE(0x4835,'t#L0')][gE(0x20d2,'N!xX')](x);}),editor['document'][gD(0xe9a,'lnIj')](gD(0x4004,'Jj0M'),!0x1);},'clearFont':function(){u(!0x0);},'clearSize':function(){u(!0x1);},'saveHistory':function(){var gF=g3;editor[gF(0x225,'VTTT')]=[],editor[gF(0x16a0,'hK)l')]['push'](editor[gF(0x38b8,'eu*B')][gF(0x2d0d,'pMQs')](gF(0x4733,'$(mV'))[gF(0x259,'IvBK')]);},'toast':function(w,x){var gG=g3;x=isNaN(x)?0x3e8:x;var y=document[gG(0x4888,'Jc8$')](gG(0x17fc,'zx#%'));y[gG(0x275b,'XcPC')]=w,y[gG(0x2e26,'(qw$')]['cssText']=[gG(0x4a48,'eu*B'),'background-color:\x20rgba(200,\x200,\x200,\x200.5);',gG(0x293d,'eu*B'),gG(0xdd0,'xbuu'),gG(0x39b7,'Ws11'),gG(0x455a,'Ws11'),gG(0x29c3,'Ws11'),gG(0x4a75,'Ws11')]['join']('\x0a'),document[gG(0x1813,'Jc8$')][gG(0x3849,'6YaW')](y),setTimeout(function(){var gH=gG;y[gH(0x6fe,'Nf2)')][gH(0x2fdc,'Q@hO')]='0',setTimeout(function(){var gI=gH;document[gI(0x3d71,'kGY9')][gI(0x198c,'Jj0M')](y);},0x3e8);},x);},'showMessage':function(w){var gJ=g3;editor[gJ(0x1a79,'zbs!')][gJ(0x186a,'hK)l')](w);}};}[gK(0x115f,'IvBK')](g,j))||(f[gK(0x432f,'lmQg')]=k);},0x241c:(f,g)=>{var gU=a0f,h;void 0x0===(h=function(){var gL=a0f;function i(){}return i[gL(0x10bd,'zbs!')]=function(){var gM=gL;return[{'cmd':'selectAll','title':editor['lang'][gM(0x4a69,'sYdH')],'icon':gM(0x2e86,'$(mV'),'shotcut':gM(0x13ee,'^Syn')},{'cmd':'cut','title':editor[gM(0x127c,'sYdH')][gM(0x27f2,'Bbr@')],'icon':'icon-cut','shotcut':gM(0x1a27,'zx#%')},{'cmd':gM(0x21ea,'t#L0'),'title':editor[gM(0x4de5,'(qw$')]['copy'],'icon':'icon-copy','shotcut':gM(0x15e0,'pMQs')},editor[gM(0x2bc6,'MDwy')][gM(0x4f5c,'zbs!')]?'-':{'cmd':gM(0x26d2,'Jc8$'),'title':editor[gM(0xc71,'*ZKE')]['paste'],'icon':gM(0x4b9e,'zx#%'),'shotcut':gM(0x4988,'Jj0M')},{'cmd':'pasteText','title':editor[gM(0x139e,'c]Mq')][gM(0x4797,'MDwy')],'icon':gM(0x4e78,'hRv2')},{'cmd':'removeFormat','title':editor[gM(0xa76,'Jc8$')][gM(0x2bf,'sYdH')],'icon':gM(0x4b8a,'Ws11')},{'cmd':gM(0x474,'IvBK'),'title':editor[gM(0x347c,'N!xX')][gM(0x4e2d,'e*c8')],'icon':gM(0x47f1,'v^si')},{'cmd':gM(0x2caf,'xbuu'),'title':editor[gM(0x44d4,'1Ue]')][gM(0x2381,'H4NX')],'icon':gM(0x4cae,'Bbr@')},{'cmd':'createRemark','title':editor[gM(0x2cb0,'lmQg')][gM(0x1489,'@HIr')],'icon':gM(0x335c,'[T9%'),'mode':gM(0x338f,'VTTT')},'-',{'cmd':gM(0x2690,'NrJk'),'title':editor[gM(0x44d4,'1Ue]')][gM(0x346a,'kGY9')],'icon':gM(0x248c,'XCXV'),'for':gM(0x4c99,'UDz8')},{'cmd':gM(0x3819,'1Ue]'),'title':editor[gM(0x8af,'MDwy')]['insertRowAfter'],'icon':gM(0x22cb,'Nf2)'),'for':gM(0x24b2,'zx#%')},{'cmd':'insertColBefore','title':editor[gM(0x26e9,'XCXV')][gM(0x232d,'xrMR')],'icon':'icon-insert-col-before','for':gM(0x3ec9,'#ZU^')},{'cmd':gM(0x197e,'v^si'),'title':editor[gM(0x2cb0,'lmQg')][gM(0x4edc,'*ZKE')],'icon':gM(0x112f,'5WLj'),'for':gM(0x22f9,'eu*B')},{'cmd':'mergeCell','title':editor[gM(0x2ad2,'zx#%')]['mergeCell'],'icon':'icon-merge-cell','for':gM(0x3e37,'IvBK')},{'cmd':gM(0x46ca,'^Syn'),'title':editor[gM(0x49bf,'IvBK')][gM(0x3317,'*gI2')],'icon':gM(0x19c,'Nf2)'),'for':'table'},{'cmd':gM(0x3472,'XCXV'),'title':editor[gM(0x2640,'H4NX')]['mergeRight'],'icon':'icon-merge-right','for':gM(0xb25,'5WLj')},{'cmd':'mergeDown','title':editor[gM(0x39af,'*p#P')][gM(0x11cb,'N!xX')],'icon':gM(0x130a,'Jj0M'),'for':gM(0x10c2,'*gI2')},{'cmd':'deleteTable','title':editor[gM(0x127c,'sYdH')][gM(0x2437,'zbs!')],'icon':gM(0x1e8,'lnIj'),'for':gM(0x23e,'H4NX')},{'cmd':gM(0x3f7a,'Q@hO'),'title':editor[gM(0x39da,'#ZU^')]['deleteRow'],'icon':gM(0x2aa6,'zx#%'),'for':gM(0x4c27,'Ws11')},{'cmd':gM(0x2116,'e*c8'),'title':editor['lang'][gM(0x13e8,'(qw$')],'icon':gM(0x15b7,'*ZKE'),'for':gM(0x384f,'*ZKE')},{'cmd':gM(0x15b2,'VTTT'),'title':editor[gM(0x2200,'!%Yf')][gM(0x2e5e,'v^si')],'icon':'icon-property'}];},i[gL(0xe42,'eu*B')]=function(){var gN=gL;let j=[];j['push'](gN(0x3a52,'N!xX')),i[gN(0x2ef2,'^Syn')]()[gN(0x47e6,'6YaW')](k=>{var gO=gN;'-'==k?j[gO(0x35ec,'UDz8')](gO(0x32c,'e*c8')):(j[gO(0x4f7,'!%Yf')](gO(0x21c5,'e*c8')+k[gO(0x46b7,'VTTT')]+'\x22\x20'+(k[gO(0x36ce,'(qw$')]?gO(0x15bb,'(qw$')+k[gO(0x226c,'H4NX')]+'\x22':'')+'\x20'+(k[gO(0x8b2,'Jj0M')]?gO(0x2833,'sYdH')+k['mode']+'\x22':'')+'>'),j['push'](''),j[gO(0x449,'sYdH')](gO(0x2b1,'hRv2')));}),j[gN(0x2d04,'kGY9')](gN(0x2161,'UDz8')),$('#_container')['append'](j[gN(0x489e,'IvBK')](''));},i['bindEvent']=function(){var gP=gL;$(editor[gP(0x901,'esU(')][gP(0x14b2,'NrJk')])['on']('contextmenu',function(j){var gQ=gP,k=j['clientX'],l=j[gQ(0x31f5,'e*c8')];i[gQ(0x4517,'Nf2)')]=j[gQ(0x2924,'H4NX')];let m=$(gQ(0x352,'@HIr'));m[gQ(0xe2,'$(mV')](gQ(0x2aed,'XCXV'))[gQ(0x22b2,'(qw$')](),'design'==editor[gQ(0x42f0,'lmQg')]['mode']?((function(){var gR=gQ,q=editor[gR(0x308d,'^d9X')]['getSelection']();if(q[gR(0x2fde,'xbuu')]){for(var s=q[gR(0x2f27,'!Yg[')];s&&'TD'!=s['tagName']&&'TR'!=s[gR(0x18fe,'Jj0M')];)s=s[gR(0x3cbe,'lmQg')];if(s&&('TD'==s[gR(0x1de7,'xrMR')]||'TR'==s[gR(0x138,'Nf2)')]))return s;}return null;}())&&m[gQ(0x28ba,'Nf2)')](gQ(0x4d6e,'pMQs'))[gQ(0x1967,'*ZKE')](),m['children'](gQ(0x14a0,'Ws11'))[gQ(0x2c28,'xrMR')](),m[gQ(0x527,'zx#%')]('[mode=design]')['show']()):gQ(0x339c,'pMQs')==editor[gQ(0x2ef8,'Jj0M')][gQ(0x2795,'Nf2)')]&&(m[gQ(0x264e,'c]Mq')](gQ(0x3768,'[T9%'))[gQ(0x2aaf,'[T9%')](),m[gQ(0x4964,'e*c8')](gQ(0x10c9,'esU('))['hide']()),$('#_fontStyleBar')[gQ(0x27fb,'IvBK')](),m[gQ(0x241d,'vGE1')]({'left':k+'px','top':l+'px'})[gQ(0x2336,'VTTT')]();let p=m[0x0]['getBoundingClientRect']();return p[gQ(0x4f2c,'kGY9')]>document[gQ(0xb6a,'hRv2')][gQ(0x42e,'vGE1')]&&(l-=p['bottom']-document[gQ(0x3d71,'kGY9')][gQ(0xdcf,'hK)l')]+0x19,m[gQ(0xa2b,'Jj0M')](gQ(0x8f1,'zbs!'),l+'px')),!0x1;}),editor['$']('#_contexMenu')[gP(0x1a3a,'Bbr@')](function(j){var gS=gP;j[gS(0x27e4,'esU(')]();}),$(gP(0x177c,'Ws11'))['children']('li')['on']('click',function(j){var gT=gP,k={'target':i[gT(0x4250,'5WLj')],'cmd':j[gT(0x3509,'^Syn')][gT(0x1e18,'xbuu')][gT(0x4223,'v^si')]?j[gT(0x4916,'VTTT')][gT(0x963,'N!xX')][gT(0x2dcb,'!%Yf')][gT(0x2133,'*ZKE')]:null,'param':j[gT(0x3bfb,'t#L0')][gT(0x4768,'lnIj')][gT(0xc42,'*gI2')]?j[gT(0x4a32,'zbs!')]['attributes'][gT(0x3078,'Bbr@')][gT(0xdb,'^d9X')]:null},l=new CustomEvent(gT(0x43fc,'pMQs'),{'bubbles':!0x0,'detail':k});editor[gT(0x41fa,'lmQg')][gT(0x45d6,'UDz8')](l),$(gT(0x33ab,'sYdH'))[gT(0x13ce,'[T9%')]();});},i;}['apply'](g,[]))||(f[gU(0x1343,'H4NX')]=h);},0xfdc:(f,g)=>{var h8=a0f,h;void 0x0===(h=function(){var gW=a0f,i;function j(){var gV=a0f;i=this,this[gV(0x48fe,'N!xX')](),this[gV(0x707,'N!xX')]=$(editor[gV(0x1527,'sYdH')]['getElementById'](gV(0x165f,'kGY9'))),this['bindEvent'](),this[gV(0x3b3a,'^d9X')]=null;}j[gW(0x36c1,'Q@hO')][gW(0x23bd,'zx#%')]=function(){var gX=gW;editor['$']('#_page')[gX(0x2ea4,'*gI2')](gX(0x4208,'@HIr'));},j[gW(0x3179,'$(mV')][gW(0x4ae6,'^d9X')]=function(){var gY=gW;this[gY(0x6e1,'$(mV')][gY(0x2df,'Bbr@')](function(l){l['stopPropagation']();}),this[gY(0x1d5f,'*ZKE')][gY(0x69a,'hRv2')]('keydown',i[gY(0x4769,'v^si')]),$(editor[gY(0x1170,'kGY9')])[gY(0x2aff,'VTTT')](function(){var gZ=gY;i[gZ(0x3e7,'(qw$')][gZ(0x479e,'H4NX')]();});},j[gW(0xa3e,'lmQg')]['onKeydown']=function(l){var h0=gW;if(l[h0(0x1cbb,'XcPC')](),0x28==l[h0(0x3cf1,'zbs!')]||0x26==l[h0(0x30bc,'(qw$')]){for(var m=!0x1,o=$(editor[h0(0xd5a,'XCXV')][h0(0x43ef,'*gI2')](h0(0x48d4,'hRv2')))[h0(0x19b4,'$(mV')]('li'),p=0x0;p0x0){o[p-0x1]['style'][h0(0x44f,'5WLj')]=h0(0x447d,'#ZU^'),m=!0x0;break;}}m||0x28!=l[h0(0x3b18,'hK)l')]?m||0x26!=l[h0(0xc3c,'H4NX')]||(o[o[h0(0x2b23,'NrJk')]-0x1]['style'][h0(0x4ba1,'^d9X')]=h0(0x14ad,'zbs!')):o[0x0][h0(0x32c3,'Jc8$')][h0(0x3d97,'N!xX')]=h0(0x21fc,'lmQg');}else 0xd==l[h0(0x35a7,'Jc8$')]||0x27==l['keyCode']?$(editor[h0(0xc8f,'Ws11')][h0(0x4091,'e*c8')](h0(0x1d5d,'1Ue]')))[h0(0x30e1,'#ZU^')]('li')[h0(0x1cba,'MDwy')](function(q,r){var h1=h0;h1(0x2731,'*p#P')==r[h1(0xaaa,'@HIr')][h1(0x4519,'!%Yf')]&&$(r)[h1(0x16c6,'zbs!')](h1(0x1a3a,'Bbr@'));}):(l[h0(0x1513,'*gI2')]=0x1b)&&$(editor[h0(0x16b7,'*p#P')][h0(0x4149,'^d9X')]('_datalist'))[h0(0xa88,'vGE1')]();},j[gW(0xa3e,'lmQg')][gW(0x4608,'!Yg[')]=function(l,m){var h2=gW;if($(l)['attr'](h2(0x930,'sYdH')))return;i[h2(0x703,'sYdH')]=l,m&&(m=m['replace']('\x20','%'));let o=i['_input']['getAttribute'](h2(0x20e6,'$(mV'));$[h2(0x2a40,'XCXV')](o,{'key':m},function(p){var h3=h2;let q=[];p[h3(0x2096,'N!xX')](function(s){var h4=h3;q[h4(0x4ebb,'Ws11')](h4(0xb32,'Ws11')+s[h4(0xee1,'IvBK')]+h4(0x2d6f,'vGE1')+s['name']+'\x22>'+s[h4(0x1a8f,'N!xX')]+' '+s[h4(0x43e2,'5WLj')]+h4(0x23c,'oKRq'));}),i[h3(0xe2d,'XCXV')]['html'](q['join'](''));var r=i[h3(0x3cc6,'^Syn')](i[h3(0x259f,'H4NX')]);i['_datalist'][h3(0x4cd5,'5WLj')]('left',r['x']+'px'),i[h3(0x28d2,'Jc8$')][h3(0x17c9,'Ws11')](h3(0x932,'*p#P'),r['y']+'px'),i['_datalist']['show'](),editor['$'](h3(0x45dd,'^d9X'))[h3(0x331c,'IvBK')]('li')[h3(0x1c74,'zx#%')](h3(0x3f84,'UDz8'),i,i[h3(0x233d,'e*c8')]);});},j[gW(0x23a4,'c]Mq')][gW(0x48df,'XCXV')]=function(l){var h5=gW;let m=l[h5(0x2c88,'^d9X')][h5(0x15eb,'$(mV')][h5(0x31a3,'lnIj')];$(i['_input'])[h5(0x540,'eu*B')](h5(0x6b9,'1Ue]'))[h5(0x61e,'e*c8')](m),$(i[h5(0x4105,'hK)l')])[h5(0xb4f,'zx#%')]({'value':l['currentTarget'][h5(0x3e79,'VTTT')][h5(0x3030,'e*c8')]}),i['_input'][h5(0x1b72,'sYdH')][h5(0x19e1,'e*c8')]=m,i[h5(0x1d5d,'1Ue]')][h5(0x3f33,'v^si')](),$(i[h5(0x1866,'c]Mq')])[h5(0x20be,'lmQg')]();let o=i['_input']['dataset'][h5(0x48d9,'pMQs')];if(0x0!=o){let p=k(i['_input'],o);p&&$(p)['removeClass'](h5(0x1238,'$(mV'))['html'](l[h5(0x13b1,'Nf2)')][h5(0x2d83,'hK)l')][h5(0x2ed3,'$(mV')]);}editor['document'][h5(0x17d8,'@HIr')](new CustomEvent(h5(0x2b03,'6YaW')));},j[gW(0x1e91,'Nf2)')]['getXY']=function(l){var h6=gW;for(var m=0x0,o=0x0;h6(0x4d68,'NrJk')!=l['id'];)o+=l[h6(0x2813,'N!xX')],m+=l[h6(0x1a66,'zx#%')],l=l[h6(0x109c,'hK)l')];return{'x':m,'y':o+0x5};};let k=function(l,m){var h7=gW;let o=$(editor['document'][h7(0x2544,'IvBK')])[h7(0x3c2e,'esU(')](h7(0x1602,'eu*B')),p=o[h7(0x3337,'N!xX')](l)+Number(m||0x1);return p<0x0?p=o[h7(0x1723,'Ws11')]-0x1:p>=o[h7(0x3415,'UDz8')]&&(p=0x0),o['eq'](p);};return j;}[h8(0x115f,'IvBK')](g,[]))||(f[h8(0xcaa,'Bbr@')]=h);},0xee5:(f,g)=>{var hm=a0f,h;void 0x0===(h=function(){var ha=a0f;function i(){var h9=a0f;this['render'](),this[h9(0x36b3,'Jc8$')]=$(editor[h9(0x39a9,'VTTT')][h9(0x296a,'hRv2')](h9(0x427f,'5WLj'))),this['bindEvent'](),this[h9(0x3755,'v^si')]=null;}i['prototype'][ha(0x2d8c,'(qw$')]=function(){var hb=ha;editor['$'](hb(0x3bb,'*p#P'))[hb(0x4d43,'N!xX')]('');},i[ha(0x29de,'pMQs')]['bindEvent']=function(){var hc=ha,k=this;this['_dropdown']['mousedown'](function(l){l['stopPropagation']();}),this['_dropdown'][hc(0x3943,'[T9%')](hc(0x22c3,'e*c8'),k[hc(0x477f,'(qw$')]),$(editor[hc(0xe2c,'@HIr')])['mousedown'](function(){var hd=hc;k[hd(0x3362,'$(mV')][hd(0x22ad,'hRv2')]();});},i['prototype']['onKeydown']=function(k){var he=ha;if(k['preventDefault'](),0x28==k[he(0x893,'Jj0M')]||0x26==k[he(0x3c7a,'zx#%')]){for(var l=!0x1,m=$(editor[he(0x387b,'H4NX')][he(0x45f0,'[T9%')](he(0x3f32,'zx#%')))[he(0x1193,'Nf2)')]('li'),o=0x0;o0x0){m[o-0x1][he(0x20e2,'hRv2')][he(0x128f,'Ws11')]=he(0x122,'XCXV'),l=!0x0;break;}}l||0x28!=k[he(0x1477,'$(mV')]?l||0x26!=k[he(0x99b,'Ws11')]||(m[m['length']-0x1]['style'][he(0x34dd,'^Syn')]=he(0x3b7,'Jc8$')):m[0x0][he(0x2baf,'V8^Q')][he(0x2c39,'sYdH')]='lightgray';}else 0xd==k['keyCode']||0x27==k[he(0x1d5e,'sYdH')]?$(editor[he(0x718,'MDwy')][he(0x10f5,'hK)l')](he(0x4241,'Q@hO')))[he(0x16d9,'Q@hO')]('li')[he(0x48fa,'!Yg[')](function(p,q){var hf=he;hf(0x2ec9,'$(mV')==q['style'][hf(0x44d6,'e*c8')]&&$(q)[hf(0x3e9e,'hRv2')](hf(0x37a1,'esU('));}):(k['keyCode']=0x1b)&&$(editor['document'][he(0x35c6,'lmQg')]('_dropdownbox'))[he(0xa88,'vGE1')]();};var j=!0x1;return i[ha(0x2026,'!%Yf')]['show']=function(k){var hg=ha;if(!$(k)[hg(0x1eae,'MDwy')](hg(0x20ae,'[T9%'))){if(j='true'==$(k)[hg(0x2c20,'sYdH')]('multi'),this[hg(0x3813,'Jj0M')]=k,this[hg(0x3123,'Q@hO')][hg(0xbf1,'XcPC')][hg(0x682,'(qw$')]){var l=JSON[hg(0x41a5,'vGE1')](this['_input']['dataset'][hg(0xcf3,'VTTT')]),m='',p=0x0;l['forEach'](function(u){var hh=hg;if(j){var v='_dropdownbox_check_id_'+p;p++,m+=hh(0xa07,'!Yg[')+v+hh(0x27d1,'^d9X')+(u['value']?u['value']:u[hh(0x3137,'!%Yf')])+hh(0x297d,'Jj0M')+u['text']+'\x22>'+u[hh(0x3aaa,'Nf2)')]+hh(0x2eef,'lmQg');}else m+=''+u[hh(0xf60,'Jc8$')]+hh(0x1828,'@HIr');}),j&&(m+=hg(0x1492,'lnIj')),this[hg(0x38ea,'zbs!')][hg(0x72e,'Jc8$')](m);}else this[hg(0x46c2,'oKRq')]['html'](hg(0x85f,'Ws11'));var q=this[hg(0x38f2,'vGE1')](k);this[hg(0x3362,'$(mV')][hg(0x491b,'xbuu')](hg(0x3a5a,'vGE1'),q['x']+'px'),this[hg(0x24a1,'NrJk')][hg(0x41ee,'V8^Q')](hg(0xa10,'5WLj'),q['y']+'px'),this[hg(0x24a1,'NrJk')][hg(0x108a,'zbs!')]();var s=this;j?$(editor[hg(0x3f1e,'zbs!')][hg(0x289,'Nf2)')](hg(0x4bf4,'hRv2')))['bind'](hg(0x4340,'t#L0'),s,s['onConfirmClick']):$(editor[hg(0x38b8,'eu*B')][hg(0x30e8,'xbuu')]('_dropdownbox'))['find']('li')[hg(0x278b,'t#L0')](hg(0x39ff,'5WLj'),s,s['onItemClick']);}},i['prototype'][ha(0x485e,'^Syn')]=function(k){var hi=ha,l=k[hi(0x1b6b,'#ZU^')],m=0x0,o='',p='';editor['$'](hi(0x3712,'t#L0'))[hi(0x3375,'eu*B')](function(q){var hj=hi;o+=(m?'、':'')+$(this)[hj(0x24cf,'^Syn')](hj(0x20fc,'Bbr@')),p+=(m?'、':'')+$(this)['attr'](hj(0x3bc2,'IvBK')),m++;}),$(l[hi(0x4f76,'XCXV')])[hi(0x4408,'XcPC')]('blank')[hi(0x2a2f,'xbuu')]({'value':o})[hi(0x12db,'$(mV')](p),l[hi(0x1159,'t#L0')][hi(0x2a15,'N!xX')]();},i['prototype']['onItemClick']=function(k){var hk=ha;let l=k[hk(0x1432,'XcPC')],m=l[hk(0x1a0e,'[T9%')]['innerText'],p=k[hk(0x4a6a,'Jc8$')][hk(0x3017,'hRv2')];$(l[hk(0x3d99,'oKRq')])[hk(0x305e,'t#L0')]('blank')[hk(0x3af6,'lmQg')](p),editor['option']['revision']&&editor[hk(0x98d,'c]Mq')][hk(0xfbc,'*ZKE')](l[hk(0x4276,'NrJk')],m),$(l['_input'])[hk(0x444a,'UDz8')]({'value':$(k[hk(0xcdc,'6YaW')])['attr'](hk(0x7f7,'Ws11'))}),l[hk(0x3d99,'oKRq')][hk(0x49f8,'5WLj')]['expression']&&editor[hk(0x272d,'!Yg[')](),$(l[hk(0x240f,'lnIj')])[hk(0x16dc,'Jj0M')]();let q=l[hk(0x2e64,'t#L0')]['dataset'][hk(0x1075,'^Syn')],s=l[hk(0x2c3c,'1Ue]')][hk(0x125c,'H4NX')][hk(0xe06,'NrJk')];q&&s&&(s=s[hk(0x3f4b,'zx#%')](',')[hk(0x10b7,'lnIj')](',#'),s='#'+s,q==p?editor['$'](s)[hk(0x32cb,'c]Mq')]():editor['$'](s)[hk(0x4f40,'5WLj')]()),l[hk(0x4e0d,'^Syn')]['hide'](),editor['document'][hk(0x197f,'!Yg[')](new CustomEvent(hk(0x2af8,'Nf2)')));},i[ha(0x1721,'esU(')][ha(0x4965,'Jc8$')]=function(k){var hl=ha;for(var l=0x0,m=0x0;hl(0x518,'XcPC')!=k['id'];)m+=k[hl(0x104c,'^Syn')],l+=k[hl(0x135a,'N!xX')],k=k['offsetParent'];return{'x':l,'y':m+0x5};},i;}[hm(0x33fe,'XCXV')](g,[]))||(f[hm(0x4c61,'zbs!')]=h);},0x1fb2:(f,g,h)=>{var hz=a0f,j,k;j=[h(0x11bb),h(0x955),h(0x1fe4),h(0x1c25),h(0x1dd1),h(0x2a),h(0x12f1)],void 0x0===(k=function(l,m,p,q,u,v,w){var ho=a0f;function x(){}const y=function(z){var hn=a0f;return $('#_fontStyleBar')[hn(0x1bd,'N!xX')](z);};return x[ho(0x26b4,'zbs!')]=function(){var hp=ho;let z=[hp(0x3835,'hRv2'),hp(0x147d,'c]Mq'),hp(0x1123,'hK)l'),hp(0xda6,'UDz8'),hp(0x1fad,'[T9%'),hp(0x1d25,'c]Mq')+editor['lang'][hp(0x8db,'v^si')]+hp(0x9d1,'Nf2)'),'',hp(0x40c4,'esU('),hp(0x4dfa,'UDz8')+editor[hp(0x1957,'^d9X')]['bold']+hp(0x123c,'esU('),'','',hp(0x3c50,'$(mV')+editor[hp(0x39da,'#ZU^')]['italic']+hp(0x2f48,'lmQg'),hp(0xb55,'MDwy'),hp(0x2c51,'1Ue]'),'','',hp(0xd1c,'hRv2'),'',hp(0xacd,'xbuu')+editor[hp(0x37ec,'zbs!')]['backColor']+hp(0x20cd,'sYdH'),hp(0x2aa5,'N!xX'),'',hp(0x12ad,'^Syn'),'',hp(0x16c0,'oKRq')+editor[hp(0x30d2,'NrJk')][hp(0x4f79,'N!xX')]+hp(0x28e,'hRv2'),hp(0x1db2,'eu*B'),'','',hp(0x4d65,'$(mV'),hp(0x85c,'*ZKE')+editor[hp(0x388d,'6YaW')]['justifyRight']+hp(0x3d93,'[T9%'),'',''][hp(0x3a8e,'e*c8')]('');$(hp(0x4443,'pMQs'))['append'](z);},x[ho(0x26f7,'MDwy')]=function(){var hq=ho;editor[hq(0x2a35,'*ZKE')][hq(0x421f,'NrJk')]=function(){var hr=hq;let z=editor['getRange']();if(hr(0x179a,'oKRq')==editor[hr(0x4245,'xrMR')][hr(0xcb0,'kGY9')]&&z&&!z[hr(0xf70,'!%Yf')]&&0x3==z[hr(0x1a46,'lmQg')][hr(0x4952,'lmQg')]){let A=z['getBoundingClientRect']();$(hr(0x479a,'1Ue]'))[hr(0x400f,'pMQs')]()[hr(0x4759,'(qw$')](hr(0x1529,'sYdH'),A['x']+'px')['css'](hr(0x716,'N!xX'),A['y']-0x23+'px');}else $(hr(0x27f9,'@HIr'))[hr(0x3c76,'Bbr@')]();},$(hq(0x257e,'*gI2'))[hq(0x31be,'*gI2')]('.btn')[hq(0x4cb5,'@HIr')](hq(0x3f45,'NrJk'),function(z){var hs=hq;if(z['currentTarget'][hs(0x4637,'MDwy')][hs(0x559,'zbs!')]){var A={'target':z[hs(0x48ea,'(qw$')],'cmd':z[hs(0x2241,'hK)l')][hs(0x437f,'6YaW')][hs(0x2b9c,'sYdH')][hs(0x3b71,'IvBK')],'param':z['currentTarget'][hs(0x2360,'(qw$')][hs(0x2181,'hK)l')]?z[hs(0x22ec,'sYdH')]['attributes'][hs(0x12de,'XCXV')][hs(0x1b00,'hRv2')]:null},B=new CustomEvent(hs(0x184f,'eu*B'),{'bubbles':!0x0,'detail':A});editor[hs(0x2da9,'*gI2')]['dispatchEvent'](B);}}),y(hq(0x3ee3,'IvBK'))['click'](function(z){var ht=hq;$(ht(0x2d1b,'c]Mq'))[ht(0x24e8,'Ws11')](),p['toggle'](z[ht(0x29e2,'XcPC')]);}),y(hq(0x2ef3,'(qw$'))[hq(0x6d1,'hK)l')](function(z){var hu=hq;$('.menu-container')[hu(0x24e8,'Ws11')](),q[hu(0x40cb,'6YaW')](z[hu(0x239b,'5WLj')]);}),y('#_lineHeight')[hq(0x2cbe,'1Ue]')](function(z){var hv=hq;$(hv(0x4902,'XCXV'))[hv(0x3c76,'Bbr@')](),u[hv(0x15f4,'VTTT')](z[hv(0x22ec,'sYdH')]);}),y(hq(0x455f,'Jj0M'))[hq(0x14ee,'xbuu')](function(z){var hw=hq;$(hw(0x4d67,'hK)l'))['hide'](),u[hw(0x423c,'H4NX')](z[hw(0x4195,'*ZKE')]);}),y(hq(0x1778,'[T9%'))[hq(0x37a1,'esU(')](function(z){var hx=hq;$(hx(0x402d,'Ws11'))[hx(0x2ab2,'Jj0M')](),l[hx(0x27b3,'*p#P')](z[hx(0x4621,'lnIj')]);}),y(hq(0x759,'XcPC'))['click'](function(z){var hy=hq;$(hy(0x4c1,'zx#%'))[hy(0x3b24,'oKRq')](),l[hy(0x532,'lmQg')](z[hy(0x145,'Bbr@')]);});},x;}[hz(0xa96,'e*c8')](g,j))||(f[hz(0x2880,'@HIr')]=k);},0x512:(f,g)=>{var hI=a0f,h;void 0x0===(h=function(){var hC=a0f;function i(){var hA=a0f;this[hA(0x1f1d,'$(mV')](),this['bindEvent']();}return i['prototype']['render']=function(){var hB=a0f,j=[hB(0xdd2,'lmQg'),'',hB(0x1810,'XcPC'),hB(0x450a,'@HIr'),hB(0x3ebc,'$(mV'),hB(0xda6,'UDz8')][hB(0x14c6,'eu*B')]('');$(editor[hB(0x454f,'!%Yf')][hB(0x47e5,'oKRq')])[hB(0x4c68,'eu*B')](j);},i['prototype'][hC(0x26f7,'MDwy')]=function(){var hD=hC;editor['$']('#_historyList')['on'](hD(0x17ea,'XCXV'),'li',function(j){var hE=hD;let k=$(j[hE(0x4a6a,'Jc8$')])['attr'](hE(0x2fea,'zx#%')),l=localStorage[hE(0x1206,'Q@hO')](k);editor['loadHtml'](l);});},i[hC(0x2aea,'t#L0')][hC(0x4a3d,'oKRq')]=function(){var hF=hC;editor['$'](hF(0x24df,'!%Yf'))[hF(0x2c0c,'kGY9')](),editor['$'](hF(0x239e,'*gI2'))['is'](':visible')&&this[hF(0x4311,'[T9%')]();},i[hC(0x3abc,'zbs!')][hC(0x2212,'^d9X')]=function(){var hG=hC;editor['$'](hG(0x3c67,'Nf2)'))['hide']();},i['prototype'][hC(0x36d9,'xrMR')]=function(){var hH=hC,j='';let k=localStorage[hH(0x1f04,'*ZKE')];for(let l=0x0;l'+new Date(Number[hH(0x3b44,'UDz8')](o[0x1]))[hH(0x421c,'sYdH')]()+hH(0x179d,'*p#P'));}editor['$']('#_historyList')[hH(0x4c32,'xbuu')](j),editor['$'](hH(0x1e04,'^Syn'))[hH(0x49d6,'lnIj')]();},i;}[hI(0x192e,'MDwy')](g,[]))||(f[hI(0x28bb,'Q@hO')]=h);},0xf75:(f,g)=>{var hU=a0f,h;void 0x0===(h=function(){'use strict';var hL=a0f;var i;function j(){var hJ=a0f;i=this,this[hJ(0x3ea,'H4NX')]=0x0,this['clientY']=0x0,this[hJ(0xe5f,'[T9%')]=0x8,this[hJ(0x23f2,'!%Yf')]=0x8,this['resizeable']=!0x1,this[hJ(0x12ac,'UDz8')]='',this[hJ(0x47c2,'^Syn')]=null,this[hJ(0x457d,'sYdH')](),this['bindEvent']();}function k(l){var hK=a0f;for(var m=0x0,o=0x0;l&&hK(0xc4c,'UDz8')!=l['id'];)o+=l[hK(0x4e8d,'UDz8')],m+=l[hK(0x2dd4,'$(mV')],l=l['offsetParent'];return{'x':m,'y':o};}return j[hL(0x11f4,'V8^Q')][hL(0x42f4,'MDwy')]=function(){var hM=hL,l=['position:\x20absolute;','width:\x2012px;',hM(0xcb7,'^d9X'),hM(0x186,'N!xX'),hM(0x406b,'xrMR'),hM(0xd76,'5WLj'),'box-sizing:\x20border-box;',hM(0x2470,'1Ue]'),'border-radius:\x206px;',hM(0xce4,'xbuu')][hM(0x4819,'XCXV')](''),m=[hM(0x38f1,'esU('),'','{var ia=a0f,j,k;j=[h(0x1530),h(0x1dfa)],void 0x0===(k=function(l,m){var i3=a0f;function p(){}function q(w){var hV=a0f;let x=w[hV(0x1c81,'*gI2')]['parentElement'];x[hV(0x3de3,'#ZU^')]&&(x[hV(0x1e8e,'$(mV')]['removeAttribute'](hV(0x1f9,'NrJk')),x[hV(0x1ce7,'lmQg')][hV(0x1780,'hK)l')]('data-remark'),x[hV(0x1afd,'xrMR')][hV(0x49ea,'1Ue]')][hV(0x58d,'[T9%')](hV(0x4306,'v^si')),x[hV(0x391b,'XcPC')][hV(0x3994,'$(mV')]=null,x[hV(0x2ea2,'Jj0M')]=null),$(x)[hV(0x22c8,'esU(')](),u();}function s(w){var hW=a0f;let x=w[hW(0x219e,'^Syn')][hW(0x1c1c,'*ZKE')];x[hW(0x3443,'*ZKE')]&&(x[hW(0x2263,'UDz8')][hW(0x19dc,'N!xX')]['remark']=w[hW(0x3516,'[T9%')][hW(0xa15,'zbs!')]),u();}function u(){var hX=a0f;if(0x1!=editor['$'](hX(0x2f66,'MDwy'))['length'])return;const w=editor['$'](hX(0x4bb5,'Bbr@'))[0x0][hX(0x1180,'#ZU^')];let x=null;editor['$'](hX(0x3453,'#ZU^'))['each']((y,z)=>{var hZ=hX;let A=function(B){var hY=a0f;let C={'x':0x0,'y':0x0},D=B[hY(0xc8e,'*gI2')]();return D[hY(0x215,'vGE1')]>0x0&&(C['x']=(editor[hY(0x227f,'xbuu')][hY(0x386e,'pMQs')]+D[0x0]['x']-editor['$'](hY(0x1ad3,'v^si'))[0x0]['getClientRects']()[0x0]['x'])/editor[hY(0x183c,'*p#P')][hY(0x33ce,'6YaW')],C['y']=(editor[hY(0x72a,'e*c8')]['scrollX']+D[0x0]['y']-editor['$'](hY(0x5ca,'Nf2)'))[0x0][hY(0x2adf,'eu*B')]()[0x0]['y'])/editor[hY(0x1a3d,'N!xX')][hY(0x2814,'zbs!')]),C;}(z);z[hZ(0xe87,'Jc8$')]&&($(z[hZ(0x2c99,'t#L0')])[hZ(0x2edf,'#ZU^')](hZ(0x30c3,'zx#%'),A['y']+'px'),$(z['block'])[hZ(0x3a70,'lnIj')](hZ(0x1bbb,'N!xX'))[hZ(0x2ee3,'UDz8')](hZ(0x2626,'VTTT'),0x0)[hZ(0x2ee3,'UDz8')](hZ(0x4824,'c]Mq'),w-0x28-A['x']+'px'),$(z['block'])[hZ(0xbc6,'eu*B')](hZ(0x1455,'UDz8'))[hZ(0x2c34,'*gI2')](hZ(0xe33,'*p#P')));}),editor['$']('[data-author]')[hX(0x15bc,'Jc8$')]((y,z)=>{var i0=hX;let A=z['block'];if(A&&x&&x['offsetTop']+x[i0(0x3cf7,'H4NX')]+0xa>A[i0(0x378c,'@HIr')]){let B=x[i0(0x4e8d,'UDz8')]+x[i0(0x1db4,'lnIj')]+0xa-A[i0(0x1cdf,'Q@hO')];$(A)[i0(0xbda,'[T9%')](i0(0x168e,'Ws11'))['css'](i0(0x38ba,'xbuu'),-B+'px');let C=Math[i0(0x3f4c,'XCXV')](Math[i0(0x8f6,'$(mV')](B+0x14,0x2)+Math[i0(0x4b25,'N!xX')](0x28,0x2)),D=Math[i0(0x4853,'xbuu')](B+0x14,0x28)/Math['PI']*0xb4;$(A)[i0(0x3bf0,'oKRq')](i0(0x2dd,'Jc8$'))['css'](i0(0x1919,'t#L0'),-B+'px')['css']('width',C+'px')['css'](i0(0x49a5,'lmQg'),i0(0x2eb3,'sYdH')+D+i0(0x23a9,'Q@hO')),A[i0(0x1d60,'c]Mq')][i0(0x14e8,'Bbr@')]=x[i0(0x3cfb,'^d9X')]+x[i0(0x3fce,'[T9%')]+0xa+'px';}A&&(x=A);});}function v(w){var i1=a0f;let x=w['dataset'][i1(0x42ae,'xrMR')]?w[i1(0x551,'t#L0')][i1(0x1def,'XcPC')][i1(0xdfe,'oKRq')]('\x0a',i1(0x2887,'Jj0M')):'批注',y=['',''+(w[i1(0x36bc,'XCXV')][i1(0x1d43,'Bbr@')]?w[i1(0x23ba,'eu*B')][i1(0x15a8,'Nf2)')]:'')+' '+(w[i1(0x36a0,'e*c8')]['createDate']?w[i1(0x229b,'!Yg[')][i1(0x340a,'xrMR')]:'')+i1(0x23b1,'^Syn'),i1(0x2a9,'oKRq'),i1(0x46ee,'1Ue]')+x+'',i1(0x28c3,'xbuu'),i1(0x15f6,'@HIr'),i1(0x164f,'pMQs'),i1(0x394a,'MDwy'),'',i1(0xda6,'UDz8'),''][i1(0x4d63,'!%Yf')]('');editor['$'](i1(0x378e,'Jc8$'))[i1(0xee0,'c]Mq')](y);let z=editor['$'](i1(0x2ded,'5WLj'))[i1(0x2ecc,'Jj0M')]()[0x0];z[i1(0x1a51,'VTTT')]=w,w['block']=z;}return p['switch']=function(w){var i2=a0f;editor[i2(0xbfc,'VTTT')][i2(0x3777,'hK)l')]=!editor[i2(0x1c9,'!%Yf')][i2(0x4a8f,'sYdH')],editor['option'][i2(0x3601,'@HIr')]?(p['show'](),$(w)[i2(0x4a6e,'eu*B')](i2(0x1bf4,'hK)l'))):(p[i2(0x3783,'sYdH')](),$(w)[i2(0x4408,'XcPC')](i2(0x496c,'xrMR')));},p[i3(0x4da2,'1Ue]')]=function(){var i4=i3;$(editor[i4(0x89d,'xrMR')][i4(0x8d3,'MDwy')])['on'](i4(0x4385,'Ws11'),i4(0x1f4,'t#L0'),q),$(editor[i4(0x1c83,'1Ue]')][i4(0x36a8,'v^si')])['on'](i4(0x14fa,'xrMR'),i4(0x124a,'*p#P'),s),setTimeout(function(){var i5=i4;editor['$'](i5(0x2914,'lnIj'))[i5(0x3a6c,'N!xX')]((w,x)=>{v(x);}),u();},0x64);},p[i3(0x3f3d,'UDz8')]=function(){var i6=i3;$(editor[i6(0x31e5,'[T9%')][i6(0x49fb,'zx#%')])[i6(0x4651,'!Yg[')](i6(0x4dea,'v^si'),i6(0xf4e,'eu*B'),q),$(editor[i6(0x48c,'Nf2)')][i6(0x497b,'!Yg[')])['off'](i6(0x6c5,'v^si'),i6(0x1d56,'t#L0'),s),editor['$'](i6(0x2914,'lnIj'))['each']((w,x)=>{var i7=i6;x[i7(0x191c,'^d9X')]&&($(x[i7(0x3e06,'vGE1')])['remove'](),x[i7(0x3b64,'!Yg[')]=null);}),u();},p[i3(0x1ad,'Jj0M')]=function(){var i9=i3;let w=(function(){var i8=a0f;let x=editor[i8(0x4d0a,'oKRq')]['getSelection']();if(x[i8(0x3ee2,'hRv2')]>0x0){let y=x[i8(0x2231,'NrJk')](0x0),z=y['startContainer'];if(0x3==z[i8(0xc7d,'vGE1')]){let A=z[i8(0x819,'zx#%')],B=editor[i8(0x2a6b,'NrJk')][i8(0x26df,'UDz8')]('span');B['innerHTML']=z[i8(0x38c3,'6YaW')][i8(0x2365,'hRv2')](0x0,y[i8(0x494,'UDz8')]),A[i8(0x1861,'H4NX')](B);let C=editor[i8(0xd5a,'XCXV')][i8(0x39d2,'MDwy')](i8(0x2713,'xrMR'));if(y[i8(0xd4e,'5WLj')]==y['endContainer']?C[i8(0x816,'6YaW')]=z[i8(0x2d11,'!Yg[')][i8(0x4f00,'!%Yf')](y['startOffset'],y[i8(0x11b1,'$(mV')]):C['innerHTML']=z[i8(0x2ae2,'MDwy')][i8(0x455b,'1Ue]')](y['startOffset']),C['classList'][i8(0x1658,'1Ue]')]('remark'),A['appendChild'](C),y[i8(0x1ced,'Nf2)')]==y[i8(0x3765,'Q@hO')]){let D=editor['document'][i8(0x79a,'VTTT')](i8(0x1d66,'XcPC'));D[i8(0x43bf,'kGY9')]=z[i8(0x19b9,'IvBK')]['slice'](y[i8(0x166b,'UDz8')]),A[i8(0x4cde,'IvBK')](D);}return A['removeChild'](z),y[i8(0x164d,'*p#P')](C),y['setEndAfter'](C),C;}return z;}}());w&&(w[i9(0x3fc0,'NrJk')][i9(0x1c98,'Jc8$')]='',w['dataset'][i9(0x20c9,'@HIr')]=new Date()[i9(0x1304,'N!xX')](),w['dataset'][i9(0x434c,'*ZKE')]=editor[i9(0x1f9e,'hK)l')]?editor['user'][i9(0x13d5,'#ZU^')]:i9(0x44ef,'xbuu'),v(w),u());},p[i3(0xf00,'vGE1')]=u,p;}[ia(0x498c,'Jj0M')](g,j))||(f[ia(0x1ff4,'N!xX')]=k);},0x1ad9:(f,g,h)=>{var iu=a0f,j,k;j=[h(0x1530)],void 0x0===(k=function(l){var ij=a0f;function m(){}function p(y){var ib=a0f;let z=y[ib(0x112c,'V8^Q')];z[ib(0x13ff,'c]Mq')]||(z['dataset'][ib(0x4589,'Nf2)')]=z[ib(0x1a71,'Q@hO')],z['dataset'][ib(0x403b,'*gI2')]=editor['user']?editor[ib(0x304,'pMQs')]['nickname']:ib(0x2fbb,'oKRq'),z['dataset'][ib(0x1ea6,'IvBK')]=new Date()[ib(0xf13,'zbs!')](),x(z));}function q(y){var ic=a0f;let z=y['target'],A=w(z[ic(0x2c49,'v^si')][ic(0x4d03,'Ws11')],z[ic(0xc70,'V8^Q')]);$(z[ic(0xa81,'H4NX')])['find'](ic(0x4a85,'@HIr'))[ic(0x3525,'IvBK')](A),z['dataset']['orgin']==z[ic(0x60c,'1Ue]')]&&($(z['block'])['remove'](),z[ic(0x24f7,'lmQg')]=null),editor[ic(0x3b99,'t#L0')][ic(0x341d,'1Ue]')]();}function u(y){var id=a0f;let z=y[id(0x3d95,'zx#%')][id(0x3808,'XCXV')]['parentElement'];z[id(0x14e1,'N!xX')]&&(z[id(0x4457,'c]Mq')][id(0x1780,'hK)l')]('data-author'),z[id(0x14e1,'N!xX')][id(0x5b4,'c]Mq')](id(0x361c,'NrJk')),z['target']['block']=null,z[id(0x880,'hK)l')]=null),$(z)[id(0x2ac,'zx#%')](),editor[id(0x4f05,'oKRq')]['adjustPos']();}function v(y){var ie=a0f;let z=y[ie(0xb21,'!Yg[')][ie(0x3c23,'@HIr')]['parentElement'];z[ie(0x3fea,'Q@hO')]&&(z[ie(0x3363,'!%Yf')][ie(0x33dc,'N!xX')]=z[ie(0xb21,'!Yg[')][ie(0x434d,'^Syn')][ie(0x4d30,'hK)l')],z['target'][ie(0x1bde,'@HIr')](ie(0x2792,'pMQs')),z[ie(0x44be,'6YaW')][ie(0x5b4,'c]Mq')](ie(0x3ca5,'^Syn')),z[ie(0x142e,'^d9X')][ie(0x3a5c,'#ZU^')]=null,z[ie(0xf84,'1Ue]')]=null),$(z)[ie(0x380d,'NrJk')](),editor['remark'][ie(0x3e33,'Q@hO')]();}function w(y,z){var ig=a0f,A=l[ig(0x3674,'#ZU^')](y,z);let B=[];return A['forEach'](C=>{var ih=ig;C[ih(0x3665,'t#L0')]?B[ih(0x46f4,'!Yg[')](ih(0x3ef9,'^d9X')+C[ih(0x210b,'zbs!')]+ih(0x15e1,'Jc8$')):C[ih(0x2e15,'IvBK')]?B['push'](''+C[ih(0x12e7,'NrJk')]+''):B[ih(0x4b4d,'xbuu')](C['value']);}),B[ig(0x14c6,'eu*B')]('');}function x(y){var ii=a0f;let z=[ii(0x4d01,'esU('),ii(0xfe5,'Nf2)')+(y[ii(0x441e,'xrMR')][ii(0x1c59,'e*c8')]?y['dataset'][ii(0x9da,'[T9%')]:'')+' '+(y['dataset'][ii(0x19aa,'t#L0')]?y[ii(0x1b53,'*p#P')][ii(0x95a,'N!xX')]:'')+ii(0x338a,'Bbr@'),'','',ii(0x3a09,'e*c8'),'',ii(0x1346,'6YaW'),ii(0x236e,'*ZKE'),ii(0x23b1,'^Syn'),ii(0x2722,'5WLj'),'','']['join']('');editor['$'](ii(0x14dd,'N!xX'))[ii(0x360d,'hK)l')](z);let A=editor['$']('.remark-block')[ii(0x139c,'XcPC')]()[0x0];return A[ii(0x13f3,'hRv2')]=y,y[ii(0x3e26,'lnIj')]=A,A;}return m[ij(0x1824,'!Yg[')]=function(y){var ik=ij;editor[ik(0x4007,'lnIj')][ik(0x3a35,'lmQg')]=!editor[ik(0x4381,'sYdH')][ik(0x9cd,'[T9%')],editor[ik(0x3c35,'Q@hO')]['revision']?(m[ik(0xf9c,'xbuu')](),$(y)[ik(0x1767,'sYdH')]('active')):(m[ik(0x41b1,'^Syn')](),$(y)['removeClass'](ik(0x403,'c]Mq')));},m[ij(0x1316,'kGY9')]=function(){var il=ij;$(editor[il(0x24c9,'5WLj')])['on'](il(0x2b62,'Q@hO'),il(0x2718,'UDz8'),p),$(editor[il(0x1c02,'vGE1')])['on'](il(0xd58,'eu*B'),'field',q),$(editor[il(0x4ba8,'IvBK')])['on']('click','.accept',u),$(editor['document'])['on']('click',il(0x4b31,'N!xX'),v),setTimeout(function(){var io=il;editor['$']('field[data-orgin]')['each'](function(y,z){var im=a0f;let A=z[im(0x15ac,'#ZU^')]['orgin'];if(A!=z[im(0x42ad,'zx#%')]){let B=x(z),C=w(A,z[im(0x1765,'NrJk')]);$(B)['find']('.remark-note')[im(0x23e2,'XcPC')](C);}}),editor['remark'][io(0x469d,'[T9%')]();},0x64);},m[ij(0x3c9,'xbuu')]=function(){var ip=ij;$(editor[ip(0x4d08,'e*c8')])[ip(0x1fd3,'MDwy')]('beforeinput',p),$(editor['document'])['off']('input',q),$(editor[ip(0xafc,'zx#%')])[ip(0x1ae4,'$(mV')](ip(0x37a1,'esU('),u),$(editor[ip(0x387b,'H4NX')])['off'](ip(0x49eb,'eu*B'),v),editor['$'](ip(0x94a,'lnIj'))[ip(0x21a9,'@HIr')]((y,z)=>{var iq=ip;z['block']&&($(z['block'])[iq(0x58d,'[T9%')](),z[iq(0x2772,'v^si')]=null);}),editor[ip(0x4a8f,'sYdH')][ip(0x3fb3,'Jc8$')]();},m[ij(0x1d49,'IvBK')]=function(y,z){var ir=ij;y[ir(0x96e,'sYdH')]||(y['dataset'][ir(0x33e5,'XCXV')]=z,y[ir(0x1032,'Bbr@')][ir(0x115d,'Ws11')]=editor[ir(0x27ff,'Nf2)')]?editor[ir(0x12a9,'hRv2')]['nickname']:ir(0x44ef,'xbuu'),y[ir(0x3e79,'VTTT')][ir(0x4ac0,'1Ue]')]=new Date()[ir(0x42f7,'#ZU^')](),x(y));let A=w(y[ir(0x386a,'lmQg')][ir(0x8a2,'[T9%')],y['innerText']);$(y[ir(0x3273,'zbs!')])[ir(0xbda,'[T9%')](ir(0x1b3d,'#ZU^'))[ir(0x1fb3,'pMQs')](A),y['dataset']['orgin']==y[ir(0x398a,'lmQg')]&&($(y[ir(0x1d21,'xrMR')])[ir(0x22c8,'esU(')](),y[ir(0x96e,'sYdH')]=null),editor['remark'][ir(0x3fb3,'Jc8$')]();},m[ij(0x2076,'5WLj')]=function(){var is=ij;let y=[];return editor['$'](is(0xb36,'zx#%'))[is(0x2848,'pMQs')]((z,A)=>{var it=is;let B=A[it(0x28b,'@HIr')]['orgin'];if(B!=A['innerText']){let C=w(B,A[it(0x1a71,'Q@hO')]);y[it(0x4b4d,'xbuu')]({'author':A[it(0x229b,'!Yg[')][it(0x16ec,'vGE1')],'date':A['dataset'][it(0x3f8c,'Ws11')],'orgin':B,'curent':A[it(0x1e37,'[T9%')],'diff':C});}}),y;},m;}[iu(0x1b7e,'^d9X')](g,j))||(f[iu(0x1733,'!%Yf')]=k);},0x22f9:(f,g)=>{var j4=a0f,h;void 0x0===(h=function(){var iF=a0f;let j;function k(){var iv=a0f;j=this,this[iv(0x1387,'6YaW')](),this[iv(0x3ece,'lmQg')](),this['clientX']=0x0,this['clientY']=0x0,j[iv(0x1367,'hRv2')]=null;}function l(x){var iw=a0f;for(let y=0x0;y{var iy=ix;let D=C[iy(0x4943,'hRv2')](':')[0x0][iy(0x4667,'Jj0M')]();z[iy(0x123b,'XCXV')](D)&&B[iy(0x2632,'XcPC')](C);})),x&&(Array[ix(0xb61,'6YaW')](x[ix(0x1a58,'*gI2')])[ix(0x3d40,'VTTT')](C=>{var iz=ix;y['has'](C['name'][iz(0x1b5c,'1Ue]')]())||x[iz(0x1bde,'@HIr')](C[iz(0x56b,'c]Mq')]);}),B[ix(0x1051,'*gI2')]>0x0&&x[ix(0x276f,'Bbr@')](ix(0x32c3,'Jc8$'),B[ix(0x450e,'lmQg')](';')));}function p(x){var iA=a0f;if(x&&m(x),0x0==x[iA(0x1642,'XCXV')][iA(0x1439,'Q@hO')])return;let y=x['tBodies'][0x0],z=0x0,A={};for(let D=0x0;Dz&&(z=F);}let B=l(x);B&&x['removeChild'](B);let C=document[iA(0x2a62,'!%Yf')](iA(0x1d48,'esU('));for(let J=0x0;Jy&&(y=B);}return y;}function w(x){var iE=a0f;let y=0x0,z=0x0;for(;x&&'_page'!=x['id'];)z+=x[iE(0x5e5,'esU(')],y+=x[iE(0x489c,'Nf2)')],x=x[iE(0x4d32,'kGY9')];return{'x':y,'y':z};}return k[iF(0x4548,'*p#P')][iF(0x2dc5,'pMQs')]=function(){var iG=iF;let x=[iG(0x4c58,'XcPC'),iG(0x4aa7,'V8^Q')][iG(0x3628,'c]Mq')]('');editor['$']('#_page')['append'](x),j[iG(0x4b33,'V8^Q')]=editor['$'](iG(0xcd0,'Ws11')),j['yresizeLine']=editor['$'](iG(0x3b8,'^Syn'));},k[iF(0x211e,'[T9%')][iF(0x3502,'c]Mq')]=function(){var iH=iF;$(editor[iH(0x2f8d,'Bbr@')][iH(0x4017,'e*c8')])['on'](iH(0x26de,'^d9X'),'td',this[iH(0x45b5,'H4NX')]),$(editor[iH(0x48d3,'!Yg[')][iH(0x213a,'V8^Q')])['on']('mousemove','td',this['tdMousemove']),$(editor[iH(0x48d3,'!Yg[')][iH(0x213a,'V8^Q')])['on'](iH(0x3d8e,'^d9X'),this[iH(0xe47,'Nf2)')]),$(editor['document'][iH(0x4188,'#ZU^')])['on'](iH(0x195d,'UDz8'),this[iH(0x204e,'*p#P')]),$(editor[iH(0x16b7,'*p#P')][iH(0xe9c,'XCXV')])['on'](iH(0x315b,'6YaW'),iH(0x30f9,'(qw$'),this[iH(0x818,'N!xX')]),$(editor['document'][iH(0x3d09,'lnIj')])['on'](iH(0xe37,'#ZU^'),iH(0x2441,'pMQs'),this['tableMousemove']),$(editor[iH(0x4a9a,'hK)l')][iH(0x2f2,'^Syn')])['on']('mouseup',iH(0x1e75,'6YaW'),this[iH(0x420c,'*ZKE')]),$(editor[iH(0x1755,'t#L0')]['body'])['on'](iH(0x1d01,'*ZKE'),'td',this['tdMousedown']);},k[iF(0x22bb,'H4NX')]['tdMousedown']=function(x){var iI=iF;if(iI(0x1ffe,'zbs!')==editor[iI(0x386d,'eu*B')][iI(0x2b40,'!%Yf')]){if(j[iI(0x3cff,'*ZKE')]=x[iI(0x1c65,'(qw$')],j[iI(0x1d09,'*p#P')]=x[iI(0x3901,'zbs!')],j[iI(0x2678,'MDwy')]=x[iI(0x2e3f,'@HIr')],iI(0x463e,'@HIr')==editor['document'][iI(0x31e6,'xrMR')][iI(0xb7a,'6YaW')][iI(0x3641,'eu*B')]){j[iI(0x3cdb,'(qw$')]='col',j[iI(0x45c0,'UDz8')]=0x0;let y=w(j[iI(0x1a51,'VTTT')]['offsetParent']);j[iI(0x11dd,'*gI2')][iI(0x2c71,'!%Yf')](),j['yresizeLine'][iI(0x3bef,'lmQg')](iI(0x10a1,'6YaW'),y['y']+'px'),j[iI(0x1093,'$(mV')][iI(0x4875,'c]Mq')](iI(0x2031,'$(mV'),y['x']+j[iI(0x4250,'5WLj')][iI(0x30b9,'*gI2')]+j[iI(0xa97,'(qw$')][iI(0x45f2,'oKRq')]+'px'),j[iI(0x1854,'NrJk')][iI(0x276d,'@HIr')](iI(0x31ca,'MDwy'),j[iI(0x3b08,'v^si')][iI(0x1117,'pMQs')][iI(0x49de,'Q@hO')]+'px'),x[iI(0x3347,'UDz8')]();}else{if('row-resize'==editor[iI(0x1c83,'1Ue]')][iI(0x530,'XcPC')][iI(0x71e,'^Syn')][iI(0x5d0,'H4NX')]){j['moveDirect']=iI(0x2dd9,'(qw$'),j[iI(0x369b,'pMQs')]=0x0;let z=w(j[iI(0x112c,'V8^Q')][iI(0x13cd,'kGY9')]);j[iI(0x19b3,'VTTT')][iI(0x47ab,'*p#P')](),j[iI(0x1f3e,'!Yg[')][iI(0xaba,'NrJk')](iI(0x3624,'1Ue]'),z['y']+j[iI(0x223d,'e*c8')]['offsetHeight']+'px'),j['xresizeLine'][iI(0x450f,'pMQs')]('left',z['x']+'px'),j[iI(0x31b9,'IvBK')][iI(0x450f,'pMQs')](iI(0x3f28,'vGE1'),j[iI(0x2ea2,'Jj0M')][iI(0x4eea,'6YaW')]['offsetWidth']+'px'),x['stopPropagation']();}}}},k['prototype'][iF(0x466c,'Nf2)')]=function(x){var iJ=iF;'design'==editor[iJ(0xbfc,'VTTT')][iJ(0x1496,'Q@hO')]&&null==j[iJ(0x2c1c,'V8^Q')]&&(x[iJ(0x47fc,'V8^Q')][iJ(0x3014,'5WLj')]-x[iJ(0x282a,'XCXV')]<0x5?(editor[iJ(0x47a2,'Q@hO')]['body'][iJ(0x1f39,'Jj0M')][iJ(0x21fa,'MDwy')]=iJ(0x92c,'6YaW'),x[iJ(0x350e,'Ws11')]()):x[iJ(0x22fc,'[T9%')][iJ(0x3376,'^d9X')]-x[iJ(0x34f0,'1Ue]')]<0x5?(editor[iJ(0x1ade,'6YaW')][iJ(0x417,'Ws11')]['style'][iJ(0x7f2,'UDz8')]=iJ(0x4509,'^Syn'),x[iJ(0x1e5f,'1Ue]')]()):editor[iJ(0x38b8,'eu*B')][iJ(0x2323,'sYdH')][iJ(0x2e40,'N!xX')][iJ(0x1f06,'e*c8')]=iJ(0x22b1,'xbuu'));},k[iF(0x1803,'Bbr@')][iF(0xcba,'Jc8$')]=function(x){var iK=iF;if(iK(0x1232,'sYdH')==j[iK(0x43d2,'$(mV')]){let y=Math[iK(0x3845,'5WLj')]((x['clientX']-j[iK(0x348c,'$(mV')])/editor[iK(0x4773,'H4NX')]['scale']);j[iK(0x2fa,'lnIj')]+=y;let z=w(j[iK(0x1aad,'6YaW')][0x0])['x']+y,A=w(j[iK(0xf54,'zbs!')])['x'],B=editor['$'](iK(0x318d,'[T9%'))[iK(0x60e,'Bbr@')]();j[iK(0x3fea,'Q@hO')][iK(0x4694,'vGE1')]&&(B=w(j[iK(0x112c,'V8^Q')][iK(0x39b1,'(qw$')])['x']+j[iK(0x142e,'^d9X')][iK(0x2028,'#ZU^')][iK(0x151b,'!%Yf')]),z>A&&zE&&DF[iM(0x24c8,'VTTT')])return I;}}(C,j[iL(0xb21,'!Yg[')]);if(D){let E=D['nextElementSibling'];if(E)D[iL(0x1d60,'c]Mq')][iL(0x3fd7,'pMQs')]=D[iL(0x1ce5,'N!xX')]+j[iL(0x492b,'xbuu')]+'px',E[iL(0x4afb,'*gI2')]['width']=E[iL(0x394d,'vGE1')]-j[iL(0x4c12,'t#L0')]+'px';else{let F=(y[iL(0x1cae,'V8^Q')]+j[iL(0x3a5,'*ZKE')])/y['offsetWidth'];for(let G of C[iL(0x10be,'!Yg[')])G['style'][iL(0x3e46,'Jj0M')]=Math[iL(0x69f,'#ZU^')](G[iL(0x2eaf,'@HIr')]*F)+'px';}}}}j[iL(0xc9f,'t#L0')]=null,j[iL(0x3363,'!%Yf')]=null;},k[iF(0x192d,'v^si')][iF(0x2cbb,'*ZKE')]=function(x){var iN=iF;let y=x[iN(0x3363,'!%Yf')];for(;y&&'TD'!=y['tagName'];)y=y[iN(0x1474,'^d9X')];0x0==x[iN(0x286,'lnIj')]&&editor['$']('td.selected')[iN(0x3613,'@HIr')](iN(0x121f,'oKRq')),j['td']=y,j[iN(0x74a,'sYdH')]={'x':y[iN(0x4202,'Q@hO')],'y':y[iN(0x1cc3,'zbs!')]},x[iN(0x2e9,'oKRq')][iN(0x4e58,'hK)l')]['cursor']=iN(0x2131,'XcPC');},k['prototype']['tableMouseup']=function(x){var iO=iF;j[iO(0x3b4d,'^Syn')]=null,x[iO(0x3cad,'XCXV')]['style'][iO(0xe6c,'NrJk')]='',j['td']=null;},k['prototype']['tableMousemove']=function(x){var iP=iF;if(!j[iP(0x34d9,'H4NX')]||iP(0x1925,'NrJk')!=editor[iP(0x4ea4,'*ZKE')][iP(0x1218,'lmQg')])return;let y=x[iP(0xb21,'!Yg[')];for(;y&&'TD'!=y[iP(0x3fc6,'zx#%')];)y=y[iP(0x889,'!%Yf')];if(y==j['td'])return;j['td']=y,j[iP(0x4074,'*ZKE')]={'x':y[iP(0xc6c,'V8^Q')]+y['offsetWidth'],'y':y[iP(0x24f5,'MDwy')]+y['offsetHeight']};let z=x[iP(0x271d,'!Yg[')][iP(0x22f2,'v^si')][0x0],A=[];for(let B=0x0;B=j[iP(0x436,'(qw$')]['x']||E[iP(0xaad,'c]Mq')]+E['offsetHeight']<=j[iP(0x26ea,'e*c8')]['y']||E[iP(0x38d4,'vGE1')]>=j['endPox']['y']||A[iP(0x3ba1,'Q@hO')](E);}}if(A[iP(0x14b8,'kGY9')]>0x0){let F={'x':A[0x0][iP(0x40dd,'H4NX')],'y':A[0x0]['offsetTop']},G={'x':0x0,'y':0x0};for(let H=0x0;HG['x']&&(G['x']=A[H][iP(0xe8b,'Bbr@')]+A[H][iP(0x92a,'$(mV')]),A[H]['offsetTop']+A[H][iP(0x35d8,'oKRq')]>G['y']&&(G['y']=A[H][iP(0x2486,'XCXV')]+A[H][iP(0x4691,'e*c8')]);A=[];for(let I=0x0;I=G['x']||L[iP(0x3cfb,'^d9X')]+L[iP(0x3cf7,'H4NX')]<=F['y']||L[iP(0x4146,'t#L0')]>=G['y']?L[iP(0x49f0,'hRv2')][iP(0x9fd,'xrMR')](iP(0x1fbf,'Nf2)')):(L[iP(0x290f,'(qw$')][iP(0x4264,'lnIj')](iP(0x36de,'eu*B')),A[iP(0x344d,'Nf2)')](L));}}j['marktd']=A,editor[iP(0x3ac6,'vGE1')]['getSelection']()[iP(0x1a30,'1Ue]')]();}},k[iF(0x526,'Jc8$')]['resetTable']=function(){var iQ=iF;let x=editor[iQ(0x11f9,'V8^Q')]['getSelection']();if(x[iQ(0x163,'v^si')]){editor['saveHistory']();let y=x[iQ(0x3d49,'oKRq')];for(;y&&iQ(0x2bc7,'c]Mq')!=y['tagName']&&'THEAD'!=y['tagName'];)y=y['parentElement'];if(y&&(iQ(0x299d,'lnIj')==y[iQ(0x23c6,'@HIr')]||'THEAD'==y[iQ(0x2698,'hK)l')])){let z=v(y),A=Math['floor'](y[iQ(0xdfb,'v^si')]/z);for(let C=0x0;C=y[iS(0x97d,'Jj0M')]+y['offsetWidth']&&(C[iS(0x4ceb,'*p#P')](B[iS(0x4af3,'Jj0M')][H]),G=!0x0);}G||D['push'](B),B=B[iS(0x2bd3,'kGY9')];}let E=null;for(let J=0x0;J0x0){let P,Q=j[iS(0xa58,'@HIr')][0x0],R=j[iS(0x3f83,'kGY9')][0x0]['parentElement'],S=0x0,T=0x0;for(let U=0x0;Uz['offsetTop']+z[iV(0x3376,'^d9X')]&&(E['children'][F][iV(0x14d8,'Nf2)')]+=0x1,B-=E[iV(0x264e,'c]Mq')][F][iV(0x4455,'!Yg[')]);if(E==z)break;}z[iV(0x3924,'*p#P')]?A[iV(0xe13,'esU(')](C,z['nextElementSibling']):A[iV(0x3bd9,'Nf2)')](C);for(let G=0x0;G0x1&&D[iZ(0x2813,'N!xX')]+D[iZ(0x1db,'*gI2')]>y[iZ(0x2e71,'(qw$')]&&(D[iZ(0x1aff,'Jc8$')]-=0x1);}}for(let E=0x0;E0x1){let G=y[iZ(0x2e4b,'[T9%')][iZ(0x1995,'XcPC')][E],H=editor[iZ(0x16b7,'*p#P')][iZ(0x4d94,'H4NX')]('td');H['rowSpan']=F['rowSpan']-0x1,H[iZ(0x3e9c,'zbs!')]=F['colSpan'],G[iZ(0xdff,'XcPC')][iZ(0x4961,'!%Yf')](H,G);}}z['removeChild'](y),editor['$'](iZ(0x4b65,'vGE1'))[iZ(0x40a4,'Nf2)')](iZ(0x273,'(qw$'));},k[iF(0x4548,'*p#P')][iF(0x146b,'zbs!')]=function(x){var j0=iF;let y=u();if(!y)return;editor[j0(0x3242,'zx#%')]();let z=y[j0(0x36e8,'VTTT')]['parentElement'];if(j0(0x32a,'XcPC')==x){if(null==y[j0(0x215b,'t#L0')])return void function(D){var j1=j0;let E=l(D['parentElement']);if(E){let F=editor[j1(0x1ade,'6YaW')]['createElement'](j1(0x1b14,'V8^Q'));F[j1(0x2641,'zbs!')][j1(0x9d0,'lnIj')]=j1(0x38c2,'sYdH'),E[j1(0x1861,'H4NX')](F);}for(let G=0x0;Gy['offsetLeft']&&A[j0(0x4107,'(qw$')](E[j0(0x179e,'eu*B')][F]);}}let B=q(y),C=l(z['parentElement']);if(C){let H=editor[j0(0xe2c,'@HIr')]['createElement'](j0(0x1ff1,'NrJk'));H[j0(0xaaa,'@HIr')][j0(0x29f9,'^Syn')]=j0(0xb46,'6YaW'),C['insertBefore'](H,C[j0(0x210a,'esU(')][B]);}for(let I=0x0;Iz&&A[j3(0x20d2,'N!xX')](A[j3(0x56c,'hK)l')][z]);let B=[];for(let C=0x0;Cx[j3(0x75b,'!%Yf')]&&B[j3(0x3761,'N!xX')](F);}}for(let G=0x0;G{var jb=a0f,h;void 0x0===(h=function(){let i=function(){var j5=a0f,j='';editor['$'](j5(0x35b1,'lmQg'))[j5(0x26cd,'zbs!')](function(k,l){var j6=j5;l['id']=new Date()['getTime']()+k,j+=''+l[j6(0x3289,'oKRq')]+j6(0x1706,'*ZKE');}),editor['$'](j5(0x7bc,'e*c8'))[j5(0x2e68,'*gI2')](j),editor['$'](j5(0x1e3,'1Ue]'))['show']();};return{'init':function(){var j7=a0f,j;j=[j7(0x394f,'oKRq'),j7(0x4e7,'pMQs'),j7(0x3a04,'Q@hO')]['join'](''),$(editor[j7(0x41fa,'lmQg')]['body'])['append'](j),editor['$'](j7(0x12c,'XcPC'))['on']('click','li',function(k){var j8=j7,l=$(k[j8(0x12b6,'v^si')])[j8(0x2492,'Q@hO')](j8(0x245c,'XCXV'));$(editor['document'][j8(0x321d,'(qw$')])[j8(0x138f,'esU(')](editor['$'](l)['offset']()[j8(0x4f4f,'NrJk')]-0xa);}),editor[j7(0xb66,'t#L0')][j7(0x47fd,'Jc8$')]&&(i(),editor['$'](j7(0x1eef,'N!xX'))['show'](),$('[cmd=tag]')[j7(0x4ae1,'zbs!')](j7(0x2e93,'H4NX')));},'toggle':function(j){var j9=a0f;editor[j9(0x4773,'H4NX')][j9(0x2543,'xbuu')]=!editor[j9(0x288b,'v^si')]['tag'],editor[j9(0x3c35,'Q@hO')][j9(0x27e7,'!Yg[')]?(i(),editor['$'](j9(0x4b19,'xrMR'))[j9(0xd62,'Ws11')](),$(j)['addClass'](j9(0x4043,'(qw$'))):(editor['$'](j9(0x2e72,'NrJk'))['hide'](),$(j)[j9(0x34ca,'sYdH')](j9(0x4ccf,'[T9%')));},'show':function(){var ja=a0f;editor['$'](ja(0x186c,'@HIr'))[ja(0xd62,'Ws11')](),i();},'rebulidTag':i};}[jb(0x24cd,'[T9%')](g,[]))||(f[jb(0x39f5,'hRv2')]=h);},0x1c10:(f,g)=>{var h;void 0x0===(h=function(){var jc=a0f;return{'fontHead':'title','defaultFont':jc(0xd85,'VTTT'),'normal':jc(0x8db,'v^si'),'lineHeight':'line\x20height','bold':jc(0x2d1a,'6YaW'),'italic':jc(0x1fc,'NrJk'),'underline':jc(0x3fca,'oKRq'),'strikeThrough':jc(0x2bb6,'e*c8'),'fontColor':jc(0x126a,'c]Mq'),'backColor':jc(0x4aeb,'^d9X'),'superscript':'superscript','subscript':jc(0x227d,'*ZKE'),'justifyFull':jc(0x1670,'sYdH'),'justifyLeft':jc(0xd7e,'#ZU^'),'justifyCenter':'Align\x20center','justifyRight':jc(0x3d07,'c]Mq'),'textIndent':jc(0x3d6,'MDwy'),'cancelTextIndent':jc(0x2455,'IvBK'),'indent':'Increase\x20indent','outdent':jc(0x162d,'esU('),'insertorderedlist':jc(0x1423,'Q@hO'),'insertunorderedlist':'list','removeFormat':jc(0x357b,'*ZKE'),'file':jc(0x917,'^d9X'),'template':'Template','recent':jc(0x3eb,'@HIr'),'open':jc(0x1a04,'*gI2'),'new':jc(0x4ca4,'t#L0'),'save':'Save\x20document','edit':'Edit','undo':'Undo','redo':'Redo','cut':jc(0x2cd8,'6YaW'),'copy':'Copy','paste':jc(0x2e33,'(qw$'),'pasteText':jc(0xbb8,'lnIj'),'selectAll':jc(0x1e83,'v^si'),'clearFont':jc(0x1e9,'lmQg'),'clearSize':jc(0x249,'eu*B'),'view':jc(0x397a,'UDz8'),'tag':jc(0x421e,'v^si'),'previewHtml':jc(0x44f9,'vGE1'),'previewPdf':jc(0x4039,'*gI2'),'importDCXml':jc(0xd66,'Jc8$'),'mobile':jc(0x2f67,'Ws11'),'fullscreen':jc(0x4ac8,'e*c8'),'insert':'Insert','insertParagraph':jc(0x47a5,'esU('),'insertPageNum':jc(0x147a,'$(mV'),'insertField':'Text','insertDateTime':jc(0xbde,'Nf2)'),'insertDropdownList':jc(0x25a7,'6YaW'),'insertDataList':'Dynamic\x20dropdownbox','insertCheckbox':'Checkbox','insertRadio':'Radio','insertImage':jc(0x6f4,'e*c8'),'insertBarcode':jc(0xcd4,'vGE1'),'insertQRcode':jc(0x4f42,'*gI2'),'insertSignate':jc(0x3a24,'6YaW'),'insertSymbol':jc(0x4cac,'!%Yf'),'insertLine':jc(0x3f9e,'XCXV'),'expression':jc(0x14f9,'!%Yf'),'insertMenstruation':jc(0x2a96,'hRv2'),'insertTooth':'Tooth\x20chart','table':jc(0x2b17,'^Syn'),'insertTable':'Insert\x20table','insertRowBefore':jc(0x9a2,'1Ue]'),'insertRowAfter':jc(0x3547,'zbs!'),'insertColBefore':'Insert\x20column\x20on\x20the\x20left','insertColAfter':jc(0x30e6,'N!xX'),'mergeCell':jc(0x2d24,'Ws11'),'cancelMergeCell':'Cancel\x20merged\x20cells','mergeRight':jc(0xcd8,'UDz8'),'mergeDown':jc(0x14c7,'IvBK'),'deleteTable':jc(0x1595,'Bbr@'),'deleteRow':jc(0x3baf,'^d9X'),'deleteCol':jc(0xbee,'(qw$'),'revision':jc(0x1966,'[T9%'),'remark':jc(0x381,'UDz8'),'history':'history','createRemark':jc(0x33d6,'5WLj'),'clearHistory':jc(0x2359,'oKRq'),'print':'Print','preview':jc(0x40d6,'[T9%'),'printStart':'Continue\x20printing\x20settings','directPrint':'Direct\x20printing','directPrintPdf':'Print\x20PDF\x20directly','import':jc(0x2b11,'hK)l'),'export':jc(0x419f,'Q@hO'),'importHtml':jc(0x1f0c,'oKRq'),'importJson':'Import\x20JSON','importXml':'Import\x20XML','exportHtml':'Export\x20HTML','exportPdf':'Export\x20PDF','exportJson':jc(0x2898,'MDwy'),'exportXml':jc(0x2e31,'IvBK'),'develop':'Tool','vitalSigns':jc(0x346e,'NrJk'),'javascript':jc(0x39d9,'[T9%'),'htmlSource':jc(0x27de,'XcPC'),'help':jc(0xd71,'Jj0M'),'about':jc(0x52c,'Jj0M'),'update':jc(0x1295,'Ws11'),'copyright':'Copyright,\x20any\x20infringement\x20will\x20be\x20prosecuted.\x20','version':jc(0xd0d,'H4NX'),'language':'Language','license':jc(0x1ae,'Q@hO'),'invalid':jc(0x4426,'*ZKE'),'notice':jc(0x2234,'vGE1'),'notice1':jc(0x45ab,'XcPC'),'notice2':jc(0x1809,'sYdH'),'term':'Term','property':jc(0x14c8,'Q@hO'),'structure':jc(0x2a31,'hK)l'),'dictionary':'Dictionary','fileName':jc(0x457a,'e*c8'),'charLength':jc(0x5f0,'eu*B'),'charLengthUnit':jc(0x19ca,'Bbr@'),'characterSet':'Character\x20set','author':jc(0x3751,'6YaW'),'lastModified':'Last\x20updated','updater':jc(0x42a4,'@HIr'),'total':jc(0x35e7,'H4NX'),'no':'No','pages':'\x20pages','header':jc(0x466e,'5WLj'),'footer':'footer','escToExit':jc(0x382a,'Q@hO'),'confirm':'OK','cancel':jc(0x3320,'oKRq'),'clear':jc(0x2f50,'1Ue]'),'pageSetting':jc(0x1c3a,'N!xX'),'orientation':jc(0x13a,'vGE1'),'pageSize':jc(0x2daf,'t#L0'),'portrait':'portrait','landscape':jc(0x1223,'eu*B'),'margin':jc(0x4ad0,'(qw$'),'top':'To','left':jc(0x6c6,'Nf2)'),'right':jc(0x3585,'Ws11'),'bottom':jc(0x1b69,'*gI2'),'headerHeight':'Header\x20height','footerHeight':jc(0x4a53,'lnIj'),'signature':'Signature'};}['apply'](g,[]))||(f['exports']=h);},0x1ee2:(f,g,h)=>{var je=a0f,j,k;j=[h(0x1c10),h(0x1109),h(0x2619),h(0x1c4),h(0x1214)],void 0x0===(k=function(l,m,o,p,q){return{'init':function(){var jd=a0f;switch(editor[jd(0xbfc,'VTTT')]['lang']){case'zh-cn':default:return m;case jd(0x2a91,'Ws11'):return l;case'zh-tw':return o;case jd(0x3bf4,'xbuu'):return p;case jd(0x132c,'eu*B'):return q;}}};}[je(0x21a1,'v^si')](g,j))||(f[je(0x4aa6,'^Syn')]=k);},0x1c4:(f,g)=>{var jg=a0f,h;void 0x0===(h=function(){var jf=a0f;return{'file':jf(0x15f7,'hK)l'),'start':jf(0x3701,'eu*B'),'insert':jf(0x5c4,'5WLj'),'expression':'བར་འཇུག','revision':jf(0x1135,'#ZU^'),'page':jf(0x2b90,'Nf2)'),'print':'པར་སྐྲུན།','export':jf(0x1586,'lnIj'),'help':jf(0x4b62,'c]Mq'),'template':jf(0x3df8,'sYdH'),'open':jf(0x2db4,'xrMR'),'new':jf(0x1b7f,'xrMR'),'newDoc':jf(0x4cc1,'Jc8$'),'save':'ཉར་ཚགས།','previewHtml':'སྔོན་ལྟ།HTML','previewPdf':jf(0x2903,'UDz8'),'importDCXml':jf(0x8f7,'#ZU^'),'undo':'ཕྱིར་འཐེན།','redo':jf(0xdbe,'!Yg['),'normal':jf(0x4aa2,'Bbr@'),'lineHeight':'ཡིག་ཕྲེང་།','bold':'སྦོམ་གཟུགས།','italic':jf(0x3f10,'Q@hO'),'underline':jf(0x688,'vGE1'),'fontColor':jf(0x45c7,'XCXV'),'backColor':'རྒྱབ་ལྗོངས།','superscript':'གོང་རྟགས།','subscript':'ཞབས་རྟགས།','justifyFull':jf(0x2930,'6YaW'),'justifyLeft':jf(0x321,'$(mV'),'justifyCenter':jf(0x18b7,'^Syn'),'justifyRight':jf(0x4d14,'#ZU^'),'textIndent':'ཐིག་ཕྲེང་དང་པོ།','cancelTextIndent':jf(0x3ed,'Nf2)'),'indent':jf(0x2558,'kGY9'),'outdent':jf(0x4051,'hK)l'),'insertorderedlist':jf(0x1a61,'zx#%'),'insertunorderedlist':'རེའུ་མིག','removeFormat':jf(0x1e99,'V8^Q'),'revision':jf(0x425d,'Q@hO'),'remark':jf(0x3d30,'VTTT'),'history':'ལོ་རྒྱུས།','clearHistory':'གཙང་བཤེར།ལོ་རྒྱུས།','tag':'ཡིག་ཆའི་མཚོན་རྟགས།','mobile':jf(0x30dd,'XCXV'),'pageSetting':jf(0x3404,'eu*B'),'inserttable':jf(0x1afe,'(qw$'),'insertPageNum':'ཤོག་གྲངས།','insertField':'ཡིག་རྐྱང་སྒྲོམ།','insertDateTime':jf(0x375c,'VTTT'),'insertDropdownList':jf(0x4f41,'(qw$'),'insertDataList':'གཞི་གྲངས་རེའུ་མིག','insertCheckbox':jf(0x2d0b,'XcPC'),'insertRadio':jf(0x4ab4,'6YaW'),'insertImage':'པར་རིས།','insertBarcode':jf(0x3ca2,'MDwy'),'insertQRcode':jf(0xd7d,'!%Yf'),'insertSignate':'མིང་འགོད།','insertMenstruation':'ཟླ་མཚན་ལོ་རྒྱུས།','insertTooth':jf(0x3efc,'$(mV'),'insertSymbol':jf(0x1158,'1Ue]'),'insertLine':jf(0x3863,'Nf2)'),'preview':jf(0x32b2,'esU('),'printStart':'མཐུད་རྒྱག་སྒྲིག་བཀོད།','print':jf(0x605,'kGY9'),'directPrint':jf(0x3e3d,'zx#%'),'directPrintPdf':jf(0x7b0,'MDwy'),'exportHtml':jf(0x3767,'eu*B'),'exportPdf':jf(0x4977,'kGY9'),'exportJson':jf(0x4ddb,'vGE1'),'exportXml':jf(0x1eb3,'[T9%'),'importJson':jf(0x401c,'c]Mq'),'help':jf(0x114c,'#ZU^'),'update':jf(0x4b56,'IvBK'),'property':jf(0x16eb,'zbs!'),'data':'གཞི་གྲངས།','dictionary':jf(0x4957,'MDwy'),'fileName':'ཡིག་ཆའི་མིང་།','charLength':jf(0x44e1,'*gI2'),'charLengthUnit':jf(0x1a37,'v^si'),'characterSet':jf(0x3d55,'MDwy'),'author':jf(0x2a29,'XcPC'),'lastModified':jf(0x37f4,'hK)l'),'updator':jf(0x2caa,'$(mV'),'total':jf(0x19a1,'VTTT'),'no':jf(0x1d4b,'sYdH'),'pages':'ཤོག་གྲངས།','header':jf(0x3dfd,'Nf2)'),'footer':jf(0x4c9f,'Nf2)'),'escToExit':jf(0x3f56,'t#L0')};}[jg(0xb6d,'@HIr')](g,[]))||(f[jg(0x1628,'t#L0')]=h);},0x1109:(f,g)=>{var ji=a0f,h;void 0x0===(h=function(){var jh=a0f;return{'fontHead':'标题','defaultFont':'宋体','normal':'正常','lineHeight':'行距','bold':'粗体','italic':'斜体','underline':jh(0x4bd3,'*ZKE'),'strikeThrough':jh(0x4891,'^d9X'),'fontColor':jh(0x268b,'[T9%'),'backColor':'背景色','superscript':'上标','subscript':'下标','justifyFull':jh(0x19e7,'esU('),'justifyLeft':'左对齐','justifyCenter':jh(0x196b,'oKRq'),'justifyRight':jh(0x4c69,'t#L0'),'textIndent':'首行缩进','cancelTextIndent':jh(0x355c,'NrJk'),'indent':jh(0x98a,'NrJk'),'outdent':jh(0x443a,'5WLj'),'insertorderedlist':'列表','insertunorderedlist':'列表','removeFormat':jh(0x3636,'XCXV'),'file':'文件','template':jh(0x25a,'e*c8'),'recent':'最近打开','open':jh(0x122c,'Nf2)'),'new':'新建文档','save':jh(0x326a,'xrMR'),'edit':'编辑','undo':'撤销','redo':'重做','cut':'剪切','copy':'复制','paste':'粘贴','pasteText':'粘贴文本','selectAll':'全选','clearFont':jh(0x3aec,'UDz8'),'clearSize':'清除字号','edited':'已编辑','view':'查看','tag':jh(0x444f,'@HIr'),'previewHtml':jh(0x1aa4,'sYdH'),'previewPdf':jh(0x159b,'eu*B'),'mobile':jh(0x8e8,'c]Mq'),'fullscreen':'全屏','insert':'插入','insertParagraph':jh(0x3ead,'*gI2'),'insertPageNum':'页码','insertField':jh(0x263b,'Ws11'),'insertDateTime':'日期','insertDropdownList':'静态下拉框','insertDataList':jh(0x178b,'esU('),'insertCheckbox':jh(0x3fa6,'!%Yf'),'insertRadio':jh(0x41fe,'kGY9'),'insertImage':'图片','insertBarcode':'条码','insertQRcode':jh(0x2507,'$(mV'),'insertSignate':'签名','insertSymbol':jh(0x8ff,'XCXV'),'insertLine':'横线','expression':'表达式','insertMenstruation':'月经史','insertTooth':'牙位图','insertFetalHeart':jh(0xac9,'Q@hO'),'table':'表格','insertTable':jh(0x3617,'NrJk'),'insertRowBefore':jh(0x470c,'Q@hO'),'insertRowAfter':jh(0x3c15,'H4NX'),'insertColBefore':jh(0x178c,'Nf2)'),'insertColAfter':'右侧插入列','mergeCell':'合并单元格','cancelMergeCell':'取消合并单元格','mergeRight':jh(0x2ae,'VTTT'),'mergeDown':jh(0x4125,'6YaW'),'deleteTable':jh(0x2f71,'zbs!'),'deleteRow':jh(0x6d2,'^Syn'),'deleteCol':jh(0x2021,'kGY9'),'revision':'审阅','remark':'批注','history':'历史','createRemark':'添加批注','clearHistory':jh(0x4613,'xrMR'),'print':'打印','preview':jh(0x46f7,'v^si'),'printStart':jh(0x7e2,'Ws11'),'directPrint':'直接打印','directPrintPdf':'直接打印PDF','import':'导入','export':'导出','importHtml':jh(0xb31,'NrJk'),'importJson':jh(0x1480,'#ZU^'),'importJsonWithCode':jh(0x327b,'lmQg'),'importXml':jh(0x4143,'6YaW'),'importWord':'导入Word','importDCXml':jh(0x4cb4,'c]Mq'),'exportHtml':jh(0xb3e,'*gI2'),'exportHtmlWithStyle':jh(0x1545,'*ZKE'),'exportPdf':'导出PDF','exportJson':jh(0x1abb,'Bbr@'),'exportJsonWithCode':jh(0x747,'MDwy'),'exportXml':jh(0x34c6,'Jj0M'),'develop':'开发工具','vitalSigns':jh(0x3208,'1Ue]'),'javascript':'文档脚本','htmlSource':jh(0x2bd5,'lmQg'),'help':'帮助','about':'关于','update':'更新','copyright':jh(0x47d5,'NrJk'),'version':'版本','language':'语言','license':'授权码','invalid':jh(0x232a,'c]Mq'),'notice':'注意','notice1':jh(0x3958,'Ws11'),'notice2':'使用,请勿使用于其他未授权机构。','term':'许可期限','property':'属性','structure':jh(0xfa8,'zbs!'),'dictionary':jh(0xe7,'vGE1'),'fileName':jh(0x34fa,'^d9X'),'charLength':jh(0x350a,'hRv2'),'charLengthUnit':'个字','characterSet':jh(0x491e,'lnIj'),'author':'作者','lastModified':jh(0x26db,'e*c8'),'updator':'更新者','total':'共','no':'第','pages':'页','header':'页眉','footer':'页脚','escToExit':jh(0x41fb,'oKRq'),'confirm':'确定','cancel':'取消','clear':'清除','pageSetting':'页面设置','orientation':jh(0xd8c,'hK)l'),'pageSize':jh(0x3e8a,'H4NX'),'portrait':'纵向','landscape':'横向','margin':'边距','top':'上','left':'左','right':'右','bottom':'下','headerHeight':jh(0x1b43,'$(mV'),'footerHeight':jh(0x2773,'V8^Q'),'signature':jh(0x2b5f,'@HIr')};}['apply'](g,[]))||(f[ji(0x37a3,'6YaW')]=h);},0x2619:(f,g)=>{var jk=a0f,h;void 0x0===(h=function(){var jj=a0f;return{'fontHead':'標題','defaultFont':'宋體','normal':'正常','lineHeight':'行距','bold':'粗體','italic':'斜體','underline':'底線','strikeThrough':'删除線','fontColor':jj(0x11ea,'UDz8'),'backColor':jj(0x29b7,'Jc8$'),'superscript':'上標','subscript':'下標','justifyFull':jj(0x3372,'(qw$'),'justifyLeft':'左對齊','justifyCenter':jj(0x2bb7,'Jc8$'),'justifyRight':jj(0x4bb6,'eu*B'),'textIndent':'首行縮進','cancelTextIndent':jj(0x4ca2,'5WLj'),'indent':jj(0x73b,'#ZU^'),'outdent':jj(0x45aa,'e*c8'),'insertorderedlist':'清單','insertunorderedlist':'清單','removeFormat':jj(0x2793,'v^si'),'file':'檔案','template':jj(0x3069,'N!xX'),'recent':jj(0x2317,'Nf2)'),'open':'打開檔案','new':jj(0x27df,'*ZKE'),'save':jj(0x2364,'[T9%'),'edit':'編輯','undo':'撤銷','redo':'重做','cut':'剪切','copy':'複製','paste':'粘貼','pasteText':jj(0x18b,'zbs!'),'selectAll':'全選','clearFont':jj(0x4a6,'t#L0'),'clearSize':jj(0x334d,'Bbr@'),'edited':jj(0x3982,'Bbr@'),'view':'查看','tag':jj(0x12a7,'NrJk'),'previewHtml':jj(0x1ab7,'xrMR'),'previewPdf':jj(0x4358,'^Syn'),'mobile':jj(0x27c7,'v^si'),'fullscreen':'全屏','insert':'插入','insertParagraph':jj(0x2bbd,'5WLj'),'insertPageNum':'頁碼','insertField':jj(0x4a99,'#ZU^'),'insertDateTime':'日期','insertDropdownList':'靜態下拉清單','insertDataList':jj(0x3f14,'(qw$'),'insertCheckbox':jj(0x1582,'^Syn'),'insertRadio':jj(0x3f63,'!%Yf'),'insertImage':'圖片','insertBarcode':'條碼','insertQRcode':jj(0x121,'6YaW'),'insertSignate':'簽名','insertSymbol':'特殊符號','insertLine':'橫線','expression':jj(0x4e93,'UDz8'),'insertMenstruation':'月經史','insertTooth':jj(0x28c8,'[T9%'),'insertFetalHeart':'胎心位置','table':'表格','insertTable':jj(0x206f,'Jj0M'),'insertRowBefore':jj(0x5ac,'zx#%'),'insertRowAfter':jj(0x3083,'hRv2'),'insertColBefore':'左側插入列','insertColAfter':'右側插入列','mergeCell':jj(0xa01,'Jc8$'),'cancelMergeCell':jj(0x2f99,'XcPC'),'mergeRight':jj(0x4707,'oKRq'),'mergeDown':jj(0x191f,'N!xX'),'deleteTable':'删除表格','deleteRow':jj(0x4016,'xrMR'),'deleteCol':jj(0x12b0,'1Ue]'),'revision':'審閱','remark':'批註','history':'歷史','createRemark':jj(0x4413,'XCXV'),'clearHistory':jj(0x208e,'pMQs'),'print':'列印','preview':jj(0x3c8d,'xrMR'),'printStart':jj(0x324a,'esU('),'directPrint':'直接列印','directPrintPdf':jj(0x3798,'(qw$'),'import':'導入','export':'匯出','importHtml':jj(0x2cdd,'H4NX'),'importJson':jj(0x2a6e,'lmQg'),'importJsonWithCode':'導入JSON(含編碼)','importXml':jj(0x215c,'Ws11'),'importWord':jj(0x39a0,'IvBK'),'importDCXml':jj(0x379,'$(mV'),'exportHtml':jj(0x2abf,'XCXV'),'exportPdf':jj(0x46a8,'(qw$'),'exportJson':'匯出JSON','exportJsonWithCode':jj(0x62b,'oKRq'),'exportXml':jj(0x46c9,'UDz8'),'develop':'開發工具','vitalSigns':'三測單','javascript':jj(0x1839,'vGE1'),'htmlSource':jj(0x35b2,'hRv2'),'help':'幫助','about':'關於','update':'更新','copyright':jj(0x3eb0,'Jj0M'),'version':'版本','language':'語言','license':jj(0x2cb7,'H4NX'),'invalid':jj(0x36f4,'t#L0'),'notice':'注意','notice1':'僅限於','notice2':'使用,請勿使用於其他未授權機构。','term':jj(0x40df,'Jj0M'),'property':'内容','structure':jj(0xcd2,'XCXV'),'dictionary':jj(0x40a5,'Jc8$'),'fileName':'檔名','charLength':jj(0x2c61,'sYdH'),'charLengthUnit':'個字','characterSet':jj(0x25a3,'Jc8$'),'author':'作者','lastModified':jj(0x2aae,'(qw$'),'updator':jj(0x129d,'Jj0M'),'total':'共','no':'第','pages':'頁','header':'頁眉','footer':'頁腳','escToExit':jj(0x3d20,'VTTT'),'confirm':'確定','cancel':'取消','clear':'清除','pageSetting':jj(0x7bb,'v^si'),'orientation':jj(0x2810,'XCXV'),'pageSize':jj(0x436e,'Ws11'),'portrait':'縱向','landscape':'橫向','margin':'邊距','top':'上','left':'左','right':'右','bottom':'下','headerHeight':jj(0x4552,'N!xX'),'footerHeight':'頁腳高度','signature':jj(0x4b39,'oKRq')};}[jk(0x38fd,'XcPC')](g,[]))||(f[jk(0x4aa6,'^Syn')]=h);},0x1214:(f,g)=>{var jm=a0f,h;void 0x0===(h=function(){var jl=a0f;return{'defaultFont':jl(0xcff,'1Ue]'),'normal':jl(0x4353,'^Syn'),'lineHeight':jl(0xcd6,'kGY9'),'bold':'\x20توم\x20جىسىم\x20','italic':jl(0x3a3,'vGE1'),'underline':jl(0xd52,'sYdH'),'strikeThrough':jl(0x3b20,'IvBK'),'fontColor':jl(0x1ed8,'xrMR'),'backColor':'\x20ئارقا\x20كۆرۈنۈش\x20رەڭگى','superscript':'\x20بەلگە\x20قويۇش','subscript':jl(0x178d,'XCXV'),'ustifyFull':jl(0x2cea,'oKRq'),'ustifyLeft':'\x20سول\x20تەڭلەش','ustifyCenter':jl(0x249d,'v^si'),'ustifyRight':jl(0x535,'kGY9'),'cancelTextIndent':jl(0x324,'VTTT'),'indent':jl(0x2730,'MDwy'),'outdent':'\x20ئىلگىرىلەش\x20مىقدارىنى\x20ئازايتىش','insertordedlist':jl(0x213,'Ws11'),'inserturderedlist':'\x20تىزىملىك','removeFormat':jl(0x14b7,'$(mV'),'file':jl(0x3c10,'zbs!'),'template':jl(0x1106,'Bbr@'),'recent':jl(0xd18,'IvBK'),'open':'\x20ئارخىپنى\x20ئېچىش','new':jl(0x1155,'zbs!'),'save':jl(0x19cb,'^Syn'),'edit':'\x20تەھرىرلەش','undo':jl(0x151e,'c]Mq'),'redo':jl(0xdd5,'lmQg'),'cut':jl(0x1bf8,'(qw$'),'copy':'\x20كۆپەيتىش\x20','paste':jl(0x3501,'Bbr@'),'pasteText':jl(0xc17,'N!xX'),'clearFont':jl(0x325a,'Bbr@'),'clearSie':'\x20خەت\x20نومۇرىنى\x20تازىلاش','edited':'\x20ئاللىقاچان\x20تەھرىرلەندى','view':jl(0x366a,'oKRq'),'tag':jl(0x1f0a,'zbs!'),'previewHtml':'HTMLنى\x20ئالدىن\x20كۆرۈش','previewPdf':jl(0x125,'$(mV'),'mobile':jl(0x3ab7,'xrMR'),'fullscreen':jl(0x2fa7,'v^si'),'insert':jl(0x4610,'zx#%'),'insertParagraph':jl(0x117,'N!xX'),'insertPageNum':'\x20بەت\x20نومۇرى','insertField':jl(0x18db,'@HIr'),'insertDateTime':jl(0x2e69,'[T9%'),'insertDropdownList':jl(0x3ed1,'*ZKE'),'insertDataList':jl(0x1f6d,'*p#P'),'insertCheckbox':jl(0x31c4,'$(mV'),'insertRadio':jl(0x1e23,'sYdH'),'insertImage':jl(0x1fcb,'IvBK'),'insertBarcode':jl(0x2058,'UDz8'),'insertQRcode':'ئىككى\x20ئۆلچەملىك\x20كود','insertSignate':jl(0x2223,'VTTT'),'insertSymbol':jl(0x571,'*ZKE'),'insertLine':jl(0x437,'6YaW'),'expression':jl(0x16e4,'hK)l'),'insertMenstruation':jl(0x48f6,'lmQg'),'insertTooth':jl(0x1b8a,'oKRq'),'insertFetalHeart':jl(0x44f1,'xrMR'),'insertTable':'\x20جەدۋەل\x20قىستۇرۇش','insertRowBefore':jl(0x3fdb,'Q@hO'),'insertRowAfter':'\x20ئاستى\x20تەرەپكە\x20قىستۇرۇپ\x20قۇرغا\x20قىستۇرۇڭ','insertColBefore':jl(0x2065,'$(mV'),'insertColAfter':jl(0x2a4d,'*ZKE'),'cancelMergeCell':jl(0x62e,'lmQg'),'mergeDown':jl(0x31e7,'Nf2)'),'deleteTable':'\x20جەدۋەلنى\x20يۇيىۋېتىش','deleteRow':jl(0x4f61,'NrJk'),'deleteCol':jl(0x4e7a,'1Ue]'),'revision':jl(0x237b,'^Syn'),'remark':jl(0x1482,'*p#P'),'history':jl(0x3650,'oKRq'),'createRemark':jl(0x25b6,'5WLj'),'clearHistory':jl(0xc33,'#ZU^'),'print':jl(0x1905,'1Ue]'),'preview':'\x20بېسىپ\x20چىقىرىشتىن\x20ئالدىن\x20مۆلچەرلەش','printStart':jl(0x48f0,'xrMR'),'directPrint':jl(0x1df3,'N!xX'),'directPrintPdf':'PDFنى\x20بىۋاستە\x20بېسىپ\x20چىقىرىش','import':jl(0x4ea7,'MDwy'),'export':jl(0x663,'#ZU^'),'importHtml':jl(0x4302,'c]Mq'),'importson':jl(0x3310,'#ZU^'),'importsonWithCode':jl(0x1c6,'lnIj'),'importXml':jl(0x3ae0,'Jj0M'),'importWord':jl(0x4af1,'^Syn'),'importDCXml':jl(0x45fa,'MDwy'),'exportHtml':'\x20HTML\x20نى\x20چىقىرىش','exportPdf':jl(0x166d,'*gI2'),'exportson':jl(0x20a,'hK)l'),'exportsonWithCode':jl(0x32e3,'N!xX'),'exportXml':jl(0x1fd6,'c]Mq'),'develop':'\x20ئېچىش\x20قورالى','vitalSigns':jl(0x2368,'pMQs'),'avascript':jl(0x4805,'v^si'),'htmlSource':'\x20ئارخىپ\x20مەنبەسى\x20كودى','help':jl(0x3e50,'MDwy'),'about':jl(0x4dbb,'Jc8$'),'update':jl(0x411b,'VTTT'),'copyright':'\x20نەشىر\x20ھوقۇقىغا\x20ئىگىدارلىق\x20قىلىش\x20ھوقۇققا\x20دەخلى-تەرۇز\x20قىلىشنى\x20چوقۇم\x20سۈرۈشتۈرۈش\x20كېرەك\x20','version':jl(0x4c3e,'@HIr'),'language':jl(0x37ef,'Nf2)'),'license':jl(0x2d6b,'Q@hO'),'invalid':'\x20ھوقۇق\x20بېرىش\x20نومۇرى\x20كۈچتىن\x20قالدى','notice':jl(0x2dd5,'vGE1'),'notice1':jl(0x77e,'VTTT'),'notice2':jl(0x2c14,'xbuu'),'property':jl(0x1acd,'v^si'),'structure':jl(0x46c8,'H4NX'),'dictionary':jl(0x2bf6,'(qw$'),'fileName':jl(0x24d,'hRv2'),'charLength':jl(0x3ac7,'NrJk'),'charLengthUnit':jl(0x138c,'IvBK'),'characterSet':'\x20ھەرپلەر\x20توپلىمى','author':'ئاپتور','lastModified':'ئەڭ\x20ئاخىرىدا\x20يېڭىلاش','updator':jl(0x1ac1,'lmQg'),'pages':jl(0x1053,'$(mV'),'header':jl(0x31ab,'IvBK'),'footer':jl(0x3ddc,'esU('),'escToEXit':jl(0xb65,'oKRq'),'confirm':jl(0x4e88,'Jj0M'),'cancel':'\x20ئەمەلدىن\x20قالدۇرۇش','clear':jl(0x1fdf,'pMQs'),'pageSetting':jl(0x4580,'sYdH'),'orientation':jl(0x3648,'^d9X'),'pageSie':jl(0x45ed,'#ZU^'),'portrait':jl(0x2919,'XCXV'),'landscape':jl(0x2f13,'XCXV'),'margin':jl(0x3bf1,'*p#P'),'top':jl(0x2c5e,'$(mV'),'left':jl(0x1838,'*gI2'),'bottom':jl(0x20d5,'V8^Q'),'headerHeight':'\x20بەت\x20قاش\x20ئېگىزلىكى','footerHeight':jl(0x26b,'XcPC'),'signature':jl(0x25d2,'(qw$'),'footerHeight':'بەت\x20پۇتىنىڭ\x20ئىگىزلىكى','signature':jl(0x1395,'!Yg[')};}[jm(0x259c,'Q@hO')](g,[]))||(f[jm(0xd7b,'*gI2')]=h);},0x9f9:(f,g,h)=>{var j,k;j=[h(0x1b6d)],void 0x0===(k=function(l){let m=!0x1,p=function(v){l['toDataURL'](v,function(w,x){var jn=a0f;$('#_mobile-url')[jn(0x27a9,'IvBK')]('src',x);});},q=function(){var jo=a0f;$(editor[jo(0x3251,'#ZU^')][jo(0x1d7b,'hK)l')])['on'](jo(0x14f8,'Q@hO'),jo(0x40f5,'hK)l'),function(){var jp=jo;u(),$(jp(0x2139,'^d9X'))['hide']();}),$(editor['mobileDoc'][jo(0x3875,'eu*B')])['on'](jo(0x4340,'t#L0'),'#btn-print',function(){u(),editor['execCommand']('print');}),$(editor[jo(0xbd1,'V8^Q')][jo(0x2134,'@HIr')])['on']('click',jo(0x4e40,'5WLj'),function(v){var jq=jo;new editor[(jq(0x422d,'zx#%'))][(jq(0x1cc6,'@HIr'))]['DtPicker']({'type':jq(0x2945,'6YaW')})[jq(0x4e3c,'IvBK')](function(w){var jr=jq;$(v[jr(0x13f3,'hRv2')])[jr(0x202e,'[T9%')](w['y'][jr(0x409d,'XcPC')]+'年'+w['m'][jr(0x5d5,'sYdH')]+'月'+w['d']['text']+'日');});}),$(editor['mobileDoc']['body'])['on'](jo(0x1d5b,'lnIj'),jo(0x3deb,'^Syn'),function(v){var js=jo;let w=v['target'][js(0x551,'t#L0')][js(0x41ac,'xbuu')];w&&(w=JSON['parse'](w));var x=new editor[(js(0x274c,'MDwy'))][(js(0x3bd5,'Bbr@'))][(js(0x2037,'(qw$'))]();x[js(0x2e3e,'#ZU^')](w),x[js(0x2546,'hRv2')](function(y){var jt=js;v['target'][jt(0x37d9,'MDwy')]=y[0x0][jt(0x2738,'$(mV')],v['target']['dataset']['value']=y[0x0]['value'];});});},s=function(v){var ju=a0f;let w=editor['$']('#'+v),x=[],y=!0x1;return w[ju(0x2b8b,'@HIr')]('field[name!=null],[tag]')[ju(0x21a9,'@HIr')]((z,A)=>{var jv=ju;if((0x0==z||A[jv(0x2f92,'1Ue]')](jv(0x4ac1,'XCXV')))&&(y&&x[jv(0x3ba1,'Q@hO')](''),x['push'](jv(0x4076,'5WLj')),A['getAttribute'](jv(0x4e9,'Jj0M'))&&x[jv(0x3c55,'zx#%')](jv(0x4657,'V8^Q')+A[jv(0x43ac,'xbuu')]+jv(0x2b52,'Q@hO')),x[jv(0x2d04,'kGY9')](jv(0x1d37,'N!xX')),x['push'](jv(0x33bd,'kGY9')),y=!0x0),A[jv(0x1e2,'(qw$')]&&jv(0x49ca,'Jc8$')==A[jv(0x170a,'MDwy')]){let B=A[jv(0xaf9,'^d9X')](jv(0xb1b,'NrJk')),C=A[jv(0x3ccf,'sYdH')](jv(0x33cb,'XcPC')),D='';'DropdownList'!=C&&jv(0x3136,'XCXV')!=C||(D=jv(0x15b1,'1Ue]'));let E=A[jv(0x1c99,'xrMR')]('format');'number'==E&&(C=E);let F=A[jv(0x125c,'H4NX')][jv(0xcf3,'VTTT')],G=A[jv(0x4382,'hK)l')],H='';A[jv(0xcda,'sYdH')][jv(0x1ae2,'*ZKE')](jv(0x3497,'zx#%'))||(H=A['innerText']);let I=[jv(0xcd5,'eu*B'),jv(0x25e2,'*p#P')+G+'',jv(0x301e,'esU(')+B+jv(0x4be3,'xbuu')+H+'\x22','\x20type=\x22text\x22','\x20field=\x22'+C+'\x22',D,jv(0xf68,'VTTT')==E?jv(0x489a,'@HIr'):'',F?jv(0x990,'pMQs')+F+'\x27':'','/>',jv(0x1bba,'*p#P')];x=x[jv(0xe1d,'e*c8')](I);}}),y&&x['push'](ju(0x1903,'eu*B')),x[ju(0xff,'NrJk')]('\x0a');},u=function(){var jw=a0f;$(editor[jw(0x4746,'*gI2')][jw(0x8d3,'MDwy')])[jw(0x1022,'^d9X')](jw(0x3dea,'c]Mq'))['each']((v,w)=>{var jx=jw;w[jx(0x1b8d,'c]Mq')](jx(0x23e6,'e*c8')),editor[jx(0x30ff,'hRv2')](w[jx(0x3c51,'esU(')],w['id']);});};return{'show':function(){var jy=a0f;$(jy(0x15d1,'kGY9'))['show'](),m||(function(){var jz=jy;m=!0x0,editor[jz(0x30f3,'1Ue]')]=document[jz(0x44dc,'5WLj')](jz(0x4184,'eu*B'))[jz(0x4d0a,'oKRq')]['document'],editor[jz(0x3704,'VTTT')]=document[jz(0x465e,'1Ue]')](jz(0x48ca,'[T9%'))[jz(0x28cb,'c]Mq')];let v=[jz(0x890,'^Syn'),jz(0x1752,'UDz8')+editor[jz(0x42f0,'lmQg')]['baseUrl']+jz(0x126b,'Ws11'),jz(0x1ad7,'*p#P')+editor[jz(0xbfc,'VTTT')][jz(0x1a85,'1Ue]')]+jz(0x4324,'Nf2)'),jz(0x46d7,'XcPC')+editor[jz(0x1ca7,'Nf2)')]['baseUrl']+jz(0x131f,'V8^Q'),'{var jH=a0f,j,k;j=[h(0xe3)],void 0x0===(k=function(l){var jC=a0f;function m(){}return m['render']=function(){var jB=a0f;let o=l['parse']();var p=jB(0xc0d,'N!xX')+editor[jB(0x24cc,'hRv2')]['about']+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[jB(0x1525,'NrJk')][jB(0x2d06,'XcPC')]+jB(0x3316,'N!xX')+editor[jB(0x2324,'t#L0')][jB(0x795,'Jc8$')]+jB(0x3640,'Jj0M')+editor[jB(0x49ab,'xrMR')][jB(0xa3d,'esU(')]+':\x20'+(editor[jB(0x4245,'xrMR')][jB(0x275f,'!%Yf')]?editor[jB(0x288b,'v^si')]['license'][jB(0x2d7f,'xbuu')]():editor['lang']['invalid'])+jB(0x358d,'V8^Q')+editor[jB(0x4e1d,'Jj0M')][jB(0x42dc,'t#L0')]+':'+editor['lang'][jB(0x45f9,'#ZU^')]+''+o[jB(0x1cd,'lmQg')]+jB(0xef0,'hK)l')+editor['lang'][jB(0x20bb,'UDz8')]+jB(0x136a,'oKRq')+editor[jB(0x39da,'#ZU^')][jB(0xdb6,'#ZU^')]+jB(0x470b,'#ZU^')+o[jB(0xd94,'^d9X')][jB(0x4f2,'VTTT')]()+jB(0x252e,'1Ue]')+editor['lang'][jB(0xe6,'lnIj')]+jB(0x4649,'!Yg[');$(jB(0x49fb,'zx#%'))[jB(0x3bd0,'zbs!')](p);},m[jC(0x120e,'IvBK')]=function(){var jD=jC;$(jD(0xe23,'e*c8'))['click'](function(o){var jE=jD;$(jE(0x2de2,'Q@hO'))[jE(0xd03,'c]Mq')]();}),$(jD(0x4342,'vGE1'))['click'](function(o){var jF=jD;$('.pop-mask')[jF(0x3506,'pMQs')]();});},m[jC(0x468,'UDz8')]=function(){var jG=jC;m[jG(0x1f1d,'$(mV')](),m['bindEvent']();},m;}['apply'](g,j))||(f[jH(0x230,'V8^Q')]=k);},0x11bb:(f,g)=>{var jO=a0f,h;void 0x0===(h=function(){var jI=a0f;function i(){}let j,k;return i[jI(0x1387,'6YaW')]=function(){var jJ=jI,l=[jJ(0x135b,'^d9X')];['rgb(255,\x20255,\x20255)',jJ(0x11bb,'6YaW'),jJ(0x4b51,'Jj0M'),jJ(0x28a1,'*gI2'),jJ(0x3fe8,'t#L0'),jJ(0x2ebe,'*p#P'),jJ(0x24fe,'^Syn'),jJ(0x1749,'VTTT'),jJ(0x4974,'v^si'),jJ(0x2822,'*p#P'),'rgb(242,\x20242,\x20242)',jJ(0x13a5,'6YaW'),jJ(0x270e,'[T9%'),jJ(0x4df2,'lmQg'),jJ(0xa09,'kGY9'),jJ(0xd36,'c]Mq'),jJ(0x24ea,'hK)l'),jJ(0x3d70,'kGY9'),'rgb(255,\x20249,\x20227)',jJ(0x3006,'xrMR'),jJ(0x22ba,'c]Mq'),jJ(0x122a,'c]Mq'),jJ(0x1de4,'$(mV'),jJ(0x2a67,'N!xX'),jJ(0x1da7,'XCXV'),'rgb(195,\x20234,\x20213)',jJ(0x28aa,'Jc8$'),jJ(0x1541,'zbs!'),jJ(0x3f53,'(qw$'),jJ(0x15c9,'t#L0'),jJ(0x131a,'5WLj'),jJ(0x2b7a,'MDwy'),'rgb(128,\x20139,\x20158)',jJ(0xf2b,'pMQs'),jJ(0x3f9c,'XcPC'),jJ(0x2cd2,'!Yg['),jJ(0x234f,'Bbr@'),jJ(0x429e,'t#L0'),jJ(0x5e2,'@HIr'),jJ(0x3333,'zbs!'),jJ(0x4eeb,'!%Yf'),jJ(0x33e6,'#ZU^'),'rgb(53,\x2059,\x2069)','rgb(20,\x2080,\x20184)','rgb(18,\x20116,\x20165)','rgb(39,\x20124,\x2079)','rgb(158,\x2030,\x2026)',jJ(0x3189,'MDwy'),jJ(0x4704,'Q@hO'),jJ(0x2ea5,'e*c8'),jJ(0x2faf,'Q@hO'),jJ(0x41d7,'hK)l'),jJ(0x32c9,'Ws11'),jJ(0x3aff,'kGY9'),jJ(0xdc0,'!%Yf'),jJ(0x3afb,'Nf2)'),jJ(0x30b,'^Syn'),jJ(0x1d29,'[T9%'),jJ(0x2ccc,'$(mV'),'rgb(59,\x2021,\x2081)','stand',jJ(0x35f0,'NrJk'),jJ(0x2515,'c]Mq'),'rgb(244,\x20194,\x2067)','rgb(254,\x20251,\x2084)',jJ(0x1ecc,'1Ue]'),jJ(0x48a0,'hK)l'),jJ(0x35cc,'xrMR'),jJ(0x32d6,'Ws11'),jJ(0x407,'XcPC'),'rgb(103,\x2053,\x20154)'][jJ(0x1f10,'#ZU^')](function(o){var jK=jJ;jK(0x3f0d,'lnIj')==o?l[jK(0x4f4e,'eu*B')](jK(0xea7,'1Ue]')):jK(0x4ae9,'NrJk')==o?l[jK(0x2740,'[T9%')](jK(0x16e,'lnIj')):l['push'](jK(0x4fc,'kGY9')+o+jK(0x4c7d,'t#L0'));});var m=[jJ(0x1286,'esU('),l[jJ(0x3ef7,'VTTT')](''),jJ(0x3d22,'XcPC')][jJ(0x1e39,'*ZKE')]('');$('#_layout')[jJ(0x4033,'xrMR')](m);},i[jI(0x4a00,'oKRq')]=function(){var jL=jI;$(jL(0x1d2b,'*ZKE'))[jL(0x28ba,'Nf2)')](jL(0x31e1,'lmQg'))['click'](function(l){var jM=jL;let m=l['currentTarget'][jM(0x30c2,'sYdH')]['background'];k?(j[jM(0x3a2d,'zx#%')][jM(0x556,'^d9X')]=m,j['dispatchEvent'](new Event(jM(0x2a21,'XCXV')))):($(j)['children'](jM(0x2163,'e*c8'))[jM(0x2978,'6YaW')](jM(0x2173,'*ZKE'),m),jM(0x46fc,'lnIj')==j['id']?editor['execCommand']('backColor',m,l[jM(0x1695,'kGY9')]):editor[jM(0x203f,'UDz8')](jM(0x40cc,'eu*B'),m,l['currentTarget'])),$(jM(0x3816,'Jc8$'))[jM(0x2fce,'XcPC')]();});},i[jI(0x9b9,'e*c8')]=function(l,m){var jN=jI;j=l,k=m;let o=l[jN(0x160d,'Bbr@')]();k?$(jN(0x3290,'Jj0M'))[jN(0x17c9,'Ws11')](jN(0x48c5,'1Ue]'),'')['css'](jN(0x2cc0,'*p#P'),'5px'):$(jN(0xb9a,'!%Yf'))['css'](jN(0x4dc8,'UDz8'),o['x']+'px')[jN(0x2ee3,'UDz8')]('right',''),$(jN(0x2064,'xrMR'))[jN(0x491b,'xbuu')](jN(0x40b0,'esU('),o['y']+0x19+'px'),$(jN(0x3177,'XCXV'))[jN(0x1bb0,'(qw$')]();},i;}[jO(0x24cd,'[T9%')](g,[]))||(f[jO(0x1ff4,'N!xX')]=h);},0x1c9b:(f,g)=>{var jY=a0f,h;void 0x0===(h=function(){var jR=a0f;function i(){var jP=a0f;this[jP(0x25d0,'Ws11')](),this[jP(0x26f7,'MDwy')]();}var j=null;return i['render']=function(k){var jQ=a0f,l='\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20胎心位置\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
    肚脐右上方
    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
    腹壁右方
    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
    ':'')+jV(0x637,'IvBK')+(l[0x5]?jV(0x2552,'^d9X'):'')+jV(0xe57,'*p#P')+(l[0x6]?'':'')+'\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20';j?(j['style'][jV(0x3815,'lmQg')]=jV(0x128b,'$(mV'),j[jV(0x4065,'^Syn')]('type',jV(0x44cc,'^d9X')),j['setAttribute'](jV(0x1432,'XcPC'),JSON[jV(0x288f,'Q@hO')](l)),j['innerHTML']=m):(m=jV(0xe5c,'6YaW')+JSON['stringify'](l)[jV(0x2deb,'Jc8$')]('\x22',jV(0x4c77,'MDwy'))+'\x22>'+m+jV(0x42e3,'$(mV'),editor['document'][jV(0x2137,'XcPC')](jV(0x493a,'xbuu'),!0x1,m)),$(jV(0xe56,'@HIr'))[jV(0x9fd,'xrMR')](),editor['document'][jV(0x13f9,'XcPC')](new CustomEvent('input'));});},i[jR(0x4608,'!Yg[')]=function(k){var jX=jR;if(k){j=k;let l=k[jX(0x434b,'eu*B')](jX(0x3855,'Nf2)'));i[jX(0x23d0,'5WLj')](JSON[jX(0x4763,'H4NX')](l));}else j=null,i[jX(0xeae,'*ZKE')]({});i[jX(0x2484,'!%Yf')]();},i;}['apply'](g,[]))||(f[jY(0x1343,'H4NX')]=h);},0x1fe4:(f,g)=>{var k5=a0f,h;void 0x0===(h=function(){var jZ=a0f;function i(){}return i[jZ(0x3793,'xrMR')]=function(){var k0=jZ,j='';['宋体','黑体','等线','-','楷体','仿宋',k0(0x1067,'NrJk'),'隶书',k0(0x1933,'zx#%'),'-',k0(0x4c50,'esU('),'华文琥珀',k0(0xbed,'esU('),k0(0x25cf,'XcPC'),k0(0x362d,'Bbr@'),'华文彩云',k0(0x4d87,'esU('),'-','Arial',k0(0x1a9,'Bbr@'),k0(0x378f,'*p#P')][k0(0x3ea4,'V8^Q')](function(l){var k1=k0;j+='-'==l?k1(0x31bd,'esU('):k1(0x3806,'Jj0M')+l+'
  • ';});var k=[k0(0x2cc,'Jj0M'),j,k0(0x450a,'@HIr')][k0(0x17e3,'(qw$')]('');$(k0(0x1863,'(qw$'))[k0(0x2d88,'IvBK')](k);},i[jZ(0x4ae6,'^d9X')]=function(){var k2=jZ;$(k2(0x4a34,'xrMR'))['on'](k2(0x3071,'zbs!'),'li',function(j){var k3=k2;$(k3(0x4a74,'!%Yf'))[k3(0x13ce,'[T9%')](),$(k3(0x159,'Bbr@'))[k3(0xf04,'xbuu')](k3(0x1d66,'XcPC'))['html'](j[k3(0x359,'$(mV')][k3(0x4f6a,'v^si')]),editor['document'][k3(0x2114,'(qw$')](k3(0x1f84,'[T9%'),!0x1,j[k3(0x44bb,'N!xX')]['textContent']);});},i['toggle']=function(j){var k4=jZ;let k=j['getBoundingClientRect']();$('#_font-family-setting')[k4(0x2c6,'oKRq')](k4(0x43f6,'Ws11'),k['x']+'px'),$('#_font-family-setting')['css'](k4(0x30c3,'zx#%'),k['y']+0x19+'px'),$(k4(0x103a,'Bbr@'))['toggle']();},i;}[k5(0x2565,'5WLj')](g,[]))||(f[k5(0x3a6d,'UDz8')]=h);},0x955:(f,g)=>{var kd=a0f,h;void 0x0===(h=function(){var k6=a0f;function i(){}return i[k6(0x3717,'Bbr@')]=function(){var k7=k6,j='';['正文','标题1',k7(0xdc,'NrJk'),'标题3',k7(0x4496,'Jc8$')][k7(0x266d,'^Syn')](function(l,m){var k8=k7;j+=0x0==m?k8(0x418,'Bbr@')+l+k8(0xe4d,'Q@hO'):k8(0x3a73,'Bbr@')+m+k8(0x3218,'Jj0M')+m+'>'+l+k8(0x23c8,'[T9%')+m+k8(0x10d4,'IvBK');});var k=[k7(0x8a0,'(qw$'),j,k7(0x29a4,'kGY9')]['join']('');$(k7(0x1000,'Nf2)'))[k7(0x367,'(qw$')](k),$('#_font-head-setting')['hide']();},i['bindEvent']=function(){var k9=k6;$(k9(0x43f7,'eu*B'))[k9(0x17ea,'XCXV')](function(j){var ka=k9;$(ka(0x3151,'NrJk'))['hide'](),$('#_fontHead')[ka(0xecc,'6YaW')](ka(0x22c1,'(qw$'))[ka(0xdf7,'eu*B')](j[ka(0x359,'$(mV')]['value']),editor['document'][ka(0x3a5f,'^d9X')](ka(0x23c4,'!Yg['),!0x1,j[ka(0xd49,'xbuu')]['firstChild'][ka(0x32e9,'t#L0')]);});},i[k6(0x1009,'!%Yf')]=function(j){var kc=k6,k=function(l){var kb=a0f;for(var m=0x0,o=0x0;kb(0x262d,'vGE1')!=l['id'];)o+=l[kb(0x2c1e,'xrMR')],m+=l[kb(0x4762,'hRv2')],l=l[kb(0x2a33,'!%Yf')];return{'x':m,'y':o+0x1c};}(j);$(kc(0x223a,'Q@hO'))[kc(0x3cc1,'!%Yf')](kc(0x906,'pMQs'),k['x']+'px'),$(kc(0x326b,'5WLj'))['css']('top',k['y']+'px'),$(kc(0x4eff,'6YaW'))[kc(0xa08,'V8^Q')]();},i;}[kd(0x2baa,'esU(')](g,[]))||(f[kd(0x28bb,'Q@hO')]=h);},0x1c25:(f,g)=>{var kk=a0f,h;void 0x0===(h=function(){var ke=a0f;function i(){}const j={'12px':'最小','13px':ke(0x3a15,'Jj0M'),'16px':'正常','18px':'较大','24px':'大字体','32px':'更大','48px':'最大'};return i['render']=function(){var kf=ke,k='';[{'size':0x1,'label':'最小'},{'size':0x2,'label':kf(0x2f2e,'[T9%')},{'size':0x3,'label':'正常'},{'size':0x4,'label':'较大'},{'size':0x5,'label':kf(0x27e,'V8^Q')},{'size':0x6,'label':'更大'},{'size':0x7,'label':'最大'}]['forEach'](function(m){var kg=kf;k+=kg(0xa17,'VTTT')+m[kg(0x4da0,'5WLj')]+'\x22>'+m[kg(0x4de3,'VTTT')]+'';});var l=[kf(0xf55,'vGE1'),k,kf(0x31bf,'V8^Q')][kf(0x1910,'esU(')]('');$(kf(0x4b7e,'!Yg['))[kf(0x19c7,'NrJk')](l);},i['bindEvent']=function(){var kh=ke;$(kh(0x3bed,'6YaW'))['on'](kh(0x45b7,'lmQg'),'li',function(k){var ki=kh;$('#_font-size-panel')[ki(0x24e8,'Ws11')](),$(ki(0x2ef3,'(qw$'))['children'](ki(0x3b8d,'^Syn'))[ki(0x27cf,'*ZKE')](k[ki(0x621,'zx#%')]['textContent']);let l=k[ki(0x3eb5,'e*c8')][ki(0x24d7,'vGE1')][ki(0x1bec,'zbs!')];editor['execCommand'](ki(0x2b21,'IvBK'),l);});},i[ke(0x4a3d,'oKRq')]=function(k){var kj=ke;let l=k[kj(0x1b6d,'IvBK')]();$(kj(0x48a3,'5WLj'))[kj(0x1640,'hK)l')](kj(0x1178,'MDwy'),l['x']+'px'),$(kj(0x4b6,'kGY9'))['css'](kj(0x932,'*p#P'),l['y']+0x19+'px'),$(kj(0x4dd,'(qw$'))[kj(0x1e6d,'c]Mq')]();},i[ke(0xa7d,'*ZKE')]=function(k){return j[k]||k;},i;}[kk(0x409,'V8^Q')](g,[]))||(f['exports']=h);},0x196b:(f,g)=>{var ku=a0f,h;void 0x0===(h=function(){var km=a0f;function i(){}const j=function(l){var kl=a0f;return $(kl(0x14e6,'lmQg'))[kl(0x4449,'^Syn')](l);};i[km(0x904,'Q@hO')]=function(l){var kn=km;i[kn(0x23bd,'zx#%')](),i[kn(0x23c2,'*p#P')]();},i[km(0x23bd,'zx#%')]=function(){var ko=km,l=[ko(0x284c,'lmQg'),ko(0x277b,'!Yg['),ko(0x4ad4,'zx#%'),ko(0x3d26,'*gI2'),ko(0x4b16,'v^si'),ko(0x45b9,'xbuu'),ko(0x1e26,'lmQg'),ko(0x4954,'Bbr@'),'',ko(0x7ee,'v^si'),'',ko(0x433d,'NrJk'),ko(0x151d,'IvBK'),ko(0x2f41,'VTTT'),ko(0xda6,'UDz8'),ko(0x387a,'pMQs')][ko(0xfa6,'Bbr@')]('');$(ko(0x25a2,'1Ue]'))[ko(0xe2b,'XCXV')](l);const {basicSetup:m,EditorView:p}=CM[ko(0x4b22,'Bbr@')],{htmlLanguage:q}=CM[ko(0x3093,'zx#%')];let s=p[ko(0x405c,'N!xX')];i[ko(0x1c41,'zbs!')]=new p({'lineWrapping':!0x0,'doc':editor['getHtml'](),'extensions':[m,q,s],'parent':document[ko(0xaf8,'xbuu')]('#_code')});},i[km(0x1852,'5WLj')]=function(){var kp=km;j('#_btn-close-panel')[kp(0x1db9,'xrMR')](function(l){var kq=kp;$(kq(0x43ed,'MDwy'))[kq(0x293e,'V8^Q')]();}),j(kp(0x4a5d,'Ws11'))[kp(0x1a3a,'Bbr@')](function(l){var kr=kp;$('.pop-mask')[kr(0x4bc,'MDwy')]();}),j(kp(0x2fed,'N!xX'))[kp(0x1db9,'xrMR')](function(l){var ks=kp;k()&&$('.pop-mask')[ks(0x2eae,'6YaW')]();});};let k=function(){var kt=km;let l=i[kt(0x17b3,'[T9%')][kt(0x24d2,'1Ue]')]['sliceDoc']();return editor['saveHistory'](),editor['loadHtml'](l),editor[kt(0x1ec3,'Jj0M')](),!0x0;};return i;}[ku(0x4ed3,'^Syn')](g,[]))||(f[ku(0x4c61,'zbs!')]=h);},0xda:(f,g)=>{var kI=a0f,h;void 0x0===(h=function(){var kw=a0f;const j=function(p){var kv=a0f;return $(kv(0x1332,'1Ue]'))[kv(0x19e4,'H4NX')](p);};window[kw(0x8f3,'XcPC')]&&fabric[kw(0x36f6,'[T9%')][kw(0x2bea,'XCXV')][kw(0x40ea,'IvBK')]({'borderColor':kw(0x36e2,'xrMR'),'cornerColor':kw(0x4e4f,'zx#%'),'cornerStrokeColor':kw(0x76a,'kGY9'),'borderOpacityWhenMoving':0x1,'borderScaleFactor':0x1,'cornerSize':0x8,'cornerStyle':kw(0x9d2,'^d9X'),'centeredScaling':!0x1,'centeredRotation':!0x0,'transparentCorners':!0x1,'rotatingPointOffset':0xa,'originX':kw(0x16f1,'*p#P'),'originY':kw(0x4d8d,'c]Mq'),'lockUniScaling':!0x1,'hasRotatingPoint':!0x0,'selectionDashArray':[0x5,0x5]});var k,l='',m=kw(0x400b,'c]Mq');function o(){var kx=kw;let p=[];return[kx(0x3efb,'(qw$'),'rgb(0,\x200,\x200)',kx(0x2e4a,'t#L0'),'rgb(41,\x20114,\x20244)',kx(0xd5b,'^d9X'),kx(0x38ca,'zbs!'),kx(0x214e,'xrMR'),kx(0x48bd,'V8^Q'),kx(0x4b88,'Nf2)'),'rgb(154,\x2056,\x20215)',kx(0xba1,'Nf2)')][kx(0x3986,'zbs!')](q=>{var ky=kx;p[ky(0x4f4e,'eu*B')](ky(0x325,'IvBK')+q+ky(0x4ecd,'V8^Q'));}),p[kx(0x129e,'H4NX')]('');}return fillColor=kw(0x20b0,'v^si'),{'show':function(p){!function(q){var kz=a0f;let s=[kz(0xa9,'!Yg['),kz(0x4719,'NrJk'),kz(0xbad,'IvBK'),kz(0x4c36,'hK)l'),'',kz(0x4080,'lnIj'),'',kz(0x1b8e,'e*c8'),kz(0x2600,'^d9X'),kz(0x2636,'lmQg'),'',kz(0x4002,'t#L0'),kz(0x12ad,'^Syn'),kz(0x425e,'sYdH'),'',kz(0x49fc,'xrMR'),kz(0x2a63,'vGE1'),kz(0x1c31,'V8^Q'),kz(0x4c17,'t#L0'),kz(0x4473,'VTTT'),kz(0x22dc,'XcPC'),kz(0x1f27,'!%Yf'),kz(0x16a1,'eu*B'),kz(0x30f6,'5WLj'),kz(0x1f27,'!%Yf'),kz(0x49dc,'t#L0'),kz(0x42e9,'oKRq'),'',kz(0x2c36,'lmQg')+o()+kz(0x3336,'6YaW'),kz(0x10ab,'zbs!'),kz(0x1ec2,'Q@hO'),kz(0x2bbc,'MDwy'),kz(0x1920,'XcPC'),kz(0x2872,'hRv2'),kz(0x2a4a,'sYdH'),kz(0x3d22,'XcPC')][kz(0xff3,'xrMR')]('');$(kz(0x916,'Nf2)'))[kz(0x3af1,'zx#%')](s);let u=q[kz(0x92e,'^d9X')]/q[kz(0x2b83,'t#L0')]*0x258;window[kz(0x30d6,'#ZU^')]&&((k=new fabric['Canvas'](kz(0x2ad0,'V8^Q'),{'isDrawingMode':!0x1,'width':u,'height':0x258,'stopContextMenu':!0x0}))[kz(0x2e79,'UDz8')]['color']=m,q[kz(0x36a0,'e*c8')]['mark']?k['loadFromJSON'](JSON[kz(0x2204,'t#L0')](q[kz(0x15ac,'#ZU^')][kz(0x3d46,'sYdH')]),k[kz(0x384c,'!Yg[')][kz(0x1afb,'Nf2)')](k),function(v,w){}):fabric['Image'][kz(0x4a0c,'*gI2')](q[kz(0x1668,'5WLj')],function(v){var kA=kz;k['setBackgroundImage'](v,k[kA(0x2ac9,'5WLj')][kA(0x37b4,'esU(')](k),{'originX':kA(0x3af4,'hK)l'),'originY':kA(0x3296,'Ws11'),'scaleX':u/v[kA(0x9d0,'lnIj')],'scaleY':0x258/v['height']});}));}(p),function(q){var kB=a0f;j('#_color')[kB(0xa3b,'NrJk')]()['first']()[kB(0x450f,'pMQs')](kB(0x3b21,'$(mV'),kB(0x28eb,'UDz8'))[kB(0x4cd5,'5WLj')](kB(0x1453,'[T9%'),kB(0x492c,'^Syn'))['css'](kB(0x1d84,'Q@hO'),kB(0x3302,'*gI2'))[kB(0x2c6,'oKRq')](kB(0xe20,'Nf2)'),m),j('#_color')['on'](kB(0x4340,'t#L0'),kB(0x37bf,'Nf2)'),function(s){var kC=kB;m=s['target'][kC(0x4db6,'vGE1')]['background'],j(kC(0x626,'N!xX'))['children']()[kC(0x19d5,'VTTT')]()[kC(0xaba,'NrJk')](kC(0x2680,'Ws11'),m),k[kC(0x33f0,'Q@hO')]['color']=m;}),k['on'](kB(0x46a7,'pMQs'),s=>{var kD=kB;0x1===s[kD(0x46af,'IvBK')]&&l&&(kD(0x15a,'^d9X')==l?(k[kD(0x3029,'[T9%')]=!0x0,k[kD(0x3633,'Bbr@')][kD(0x259e,'(qw$')]=m):kD(0x260c,'VTTT')==l?k['isDrawingMode']=!0x1:(k[kD(0x486f,'*gI2')]=!0x1,function(u,v){var kE=kD;if('Rect'==l){let w=new fabric[(kE(0x4cad,'!Yg['))]({'left':u,'top':v,'stroke':m,'strokeWidth':0x2,'fill':fillColor,'width':0x64,'height':0x64});k[kE(0x24fa,'H4NX')](w);}else{if('Text'==l){let x=new fabric[(kE(0x1d3b,'zbs!'))]('文字',{'fill':m,'left':u,'top':v});k[kE(0x3839,'sYdH')](x);}else{if('Circle'==l){let y=new fabric[(kE(0x4a8e,'pMQs'))]({'left':u,'top':v,'stroke':m,'strokeWidth':0x2,'fill':fillColor,'radius':0x32});k[kE(0x3cc,'5WLj')](y);}else{if(kE(0xfb5,'*gI2')==l){let z=new fabric[(kE(0x4deb,'Nf2)'))]([u,v,u+0xc8,v],{'stroke':m,'strokeWidth':0x2});k[kE(0x18a6,'XcPC')](z);}}}}}(s['pointer']['x'],s[kD(0x1163,'esU(')]['y']),l='',j('.active')['removeClass']('active')));}),j(kB(0x1bf0,'@HIr'))['click'](function(s){var kF=kB;j(kF(0x931,'^d9X'))[kF(0x4408,'XcPC')](kF(0x2cd9,'*p#P')),l=$(s[kF(0x406c,'*p#P')])[kF(0x2979,'VTTT')](kF(0x12f3,'VTTT')),$(s[kF(0x39f0,'pMQs')])[kF(0x1a38,'xrMR')](kF(0xc18,'t#L0'));}),j(kB(0x446b,'hRv2'))['click'](function(s){var kG=kB;$('.pop-mask')[kG(0x366b,'1Ue]')]();}),j(kB(0x443d,'$(mV'))['click'](function(s){var kH=kB;q['src']=k[kH(0x42b3,'e*c8')](),q[kH(0x49aa,'*gI2')][kH(0x10db,'Q@hO')]=JSON['stringify'](k['toJSON']()),$(kH(0x1ca,'1Ue]'))[kH(0x2dcc,'UDz8')](),editor[kH(0x4d08,'e*c8')]['dispatchEvent'](new CustomEvent(kH(0xf4a,'hK)l')));});}(p);}};}[kI(0x4ed3,'^Syn')](g,[]))||(f[kI(0x4c61,'zbs!')]=h);},0x21fd:(e,f)=>{var kR=a0f,g,h;g=[],h=function(){var kL=a0f;function i(){}const j=function(l){var kJ=a0f;return $(kJ(0x3517,'*p#P'))['find'](l);};i['show']=function(l){var kK=a0f;i[kK(0x1387,'6YaW')](),i[kK(0x2484,'!%Yf')]();},i[kL(0x12d1,'hRv2')]=function(){var kM=kL,l=[kM(0x432b,'v^si'),kM(0x29bc,'H4NX'),kM(0x2ea3,'!Yg['),kM(0x20fb,'pMQs'),kM(0x13cb,'@HIr'),kM(0x3dcc,'eu*B'),'',kM(0x485,'N!xX'),kM(0x2bdb,'Jj0M'),kM(0x9e5,'e*c8'),kM(0x3454,'c]Mq'),'','运行',kM(0x1f5d,'Ws11'),kM(0x3342,'$(mV'),'

    ',kM(0xb5c,'esU('),kM(0x4a45,'oKRq')][kM(0x297f,'t#L0')]('');$('body')['append'](l);const {basicSetup:m,EditorView:p}=CM[kM(0x19ab,'NrJk')],{javascript:q,javascriptLanguage:s,scopeCompletionSource:u}=CM['@codemirror/lang-javascript'];i[kM(0x133b,'*gI2')]=new p({'doc':editor['$']('script')[kM(0x4503,'esU(')](),'extensions':[m,q(),s[kM(0x1bd5,'xrMR')]['of']({'autocomplete':u(globalThis)})],'parent':document[kM(0x577,'Bbr@')](kM(0x43e0,'Q@hO'))});},i[kL(0xb63,'^Syn')]=function(){var kN=kL;j(kN(0x1b3b,'hRv2'))['click'](function(l){var kO=kN;$('.pop-mask')[kO(0xd03,'c]Mq')]();}),j('#_btn-cancel')[kN(0x3022,'[T9%')](function(l){$('.pop-mask')['remove']();}),j(kN(0x2fe9,'H4NX'))[kN(0x6d1,'hK)l')](function(l){k();}),j(kN(0x4f68,'oKRq'))[kN(0x43a0,'XcPC')](function(l){var kP=kN;k()&&$('.pop-mask')[kP(0x4676,'IvBK')]();});};let k=function(){var kQ=kL;j('#_error')[kQ(0xaa2,'Bbr@')](''),0x0==editor['$'](kQ(0x2876,'Bbr@'))[kQ(0x4682,'Nf2)')]&&editor['$'](kQ(0x4247,'@HIr'))[kQ(0x4c22,'e*c8')](kQ(0x33d9,'Q@hO'));let l=i[kQ(0x2559,'!Yg[')][kQ(0x3c68,'Nf2)')][kQ(0x47c6,'*gI2')]();try{eval(l),editor['$'](kQ(0x7dd,'$(mV'))[kQ(0xad9,'Q@hO')](l);}catch(m){return j(kQ(0x4a5,'VTTT'))[kQ(0x72e,'Jc8$')](m['message']),!0x1;}return editor['document'][kQ(0x5ed,'XCXV')](new CustomEvent(kQ(0x2cdb,'^d9X'))),!0x0;};return i;}['apply'](f,g),void 0x0===h||(e[kR(0x48f3,'MDwy')]=h);},0x1dd1:(f,g)=>{var l1=a0f,h;void 0x0===(h=function(){var kS=a0f;function i(){}return i[kS(0x1b27,'xbuu')]=function(){var kT=kS,j='';['1',kT(0x265,'Jj0M'),kT(0xe84,'#ZU^'),kT(0x837,'(qw$'),kT(0x37a5,'5WLj'),kT(0x2401,'6YaW'),'2',kT(0x30a,'H4NX')][kT(0x376d,'c]Mq')](function(l,m){var kU=kT;j+=kU(0x8ef,'Nf2)')+l+kU(0x4a29,'v^si');});var k=['',j,kT(0x450a,'@HIr')][kT(0x1e39,'*ZKE')]('');$(kT(0x3047,'MDwy'))[kT(0x4d43,'N!xX')](k),$(kT(0x33e0,'5WLj'))[kT(0x22ad,'hRv2')]();},i[kS(0x3149,'kGY9')]=function(){var kV=kS;$('#_line-height-container')['on'](kV(0x2a12,'Jc8$'),'li',function(j){var kW=kV;$(kW(0x4655,'!Yg['))['hide']();let k=j[kW(0x1a51,'VTTT')]['innerText'];$(kW(0x3107,'XcPC'))[kW(0x363d,'Ws11')]('span')[kW(0x918,'[T9%')](k),function(l){var kX=kW;let m=editor[kX(0x460a,'pMQs')][kX(0x2f3c,'VTTT')]();if(kX(0x5ef,'(qw$')!=editor[kX(0x386d,'eu*B')][kX(0x3853,'!Yg[')]||m[kX(0x381f,'N!xX')]<0x0)return;editor[kX(0x2547,'e*c8')]();let o=m[kX(0x3723,'Jj0M')](0x0);var p=!0x1;if(o[kX(0x3658,'#ZU^')])q(o[kX(0x31b7,'V8^Q')]);else{let s=o['commonAncestorContainer'];0x1!=s[kX(0x1f24,'NrJk')]&&(s=s['parentElement']),function u(v){var kY=kX;v==o[kY(0x29f6,'t#L0')]&&(p=!0x0);for(let w=0x0;w0x0&&q(w);}else v[kZ(0x44a0,'Jj0M')][kZ(0x20e2,'hRv2')][kZ(0x1ccf,'1Ue]')]=l;}}(k);});},i[kS(0x1d4e,'Bbr@')]=function(j){var l0=kS;let k=j[l0(0xa5,'^d9X')]();$(l0(0x5ff,'UDz8'))[l0(0x41ee,'V8^Q')](l0(0x4dc8,'UDz8'),k['x']+'px'),$(l0(0x3489,'^d9X'))[l0(0x2edf,'#ZU^')](l0(0x4bf8,'hRv2'),k['y']+0x19+'px'),$(l0(0xdbb,'xrMR'))[l0(0x15f4,'VTTT')]();},i;}[l1(0xfbf,'VTTT')](g,[]))||(f[l1(0x260a,'esU(')]=h);},0x267c:(f,g)=>{var lb=a0f,h;void 0x0===(h=function(){var l4=a0f;function i(){var l2=a0f;this['render'](),this[l2(0x694,'@HIr')]();}var j=null;return i['render']=function(k){var l3=a0f,l=l3(0xadc,'eu*B')+k['firstYear']+'\x22>岁\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

    月经持续\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20~\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20岁\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20取消\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20确定\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20';$('body')[l3(0x2e94,'esU(')](l);},i[l4(0x3149,'kGY9')]=function(){var l5=l4;$(l5(0x1b6e,'MDwy'))[l5(0x3cfc,'VTTT')](function(k){var l6=l5;$(l6(0x9bf,'esU('))[l6(0x3e4a,'5WLj')]();}),$(l5(0x191a,'*ZKE'))[l5(0x49eb,'eu*B')](function(k){var l7=l5;$(l7(0x132,'Jj0M'))[l7(0x2dcc,'UDz8')]();}),$(l5(0x314c,'zx#%'))[l5(0x3071,'zbs!')](function(k){var l8=l5;let l={};$(l8(0x4435,'$(mV'))[l8(0x3d5e,'kGY9')]('input')['each'](function(o,p){var l9=l8;let q=p['getAttribute'](l9(0x44b4,'1Ue]'));l[q]=p[l9(0x45fc,'$(mV')];});let m=[l8(0x25be,'(qw$')+l[l8(0x43ce,'$(mV')]+l8(0x268c,'*ZKE'),l8(0xe67,'^d9X')+l['durationDays']+'-'+l[l8(0xb03,'^Syn')]+l8(0x1e98,'XcPC'),l8(0x34b2,'Q@hO')+l[l8(0x1add,'lmQg')]+'-'+l[l8(0x13ae,'1Ue]')]+l8(0x15fc,'*gI2'),l8(0x3fbb,'V8^Q')+l[l8(0x3483,'oKRq')]+'',l8(0xb10,'!Yg[')][l8(0x14ef,'vGE1')]('');j?(j[l8(0x28b1,'MDwy')]['verticalAlign']=l8(0x2d7a,'hRv2'),j[l8(0x2865,'XcPC')](l8(0x28c9,'NrJk'),l8(0x4f43,'XcPC')),j[l8(0x360b,'oKRq')](l8(0x3070,'lmQg'),JSON[l8(0x49cf,'*ZKE')](l)),j[l8(0x43bf,'kGY9')]=m):(m=l8(0x1a64,'Q@hO')+JSON[l8(0x2b10,'N!xX')](l)[l8(0x204,'vGE1')]('\x22',l8(0xf36,'6YaW'))+'\x22>'+m+l8(0xdfc,'[T9%'),editor[l8(0x2098,'$(mV')][l8(0x203f,'UDz8')](l8(0x3f4d,'V8^Q'),!0x1,m)),$(l8(0x2107,'*gI2'))[l8(0xf9b,'xbuu')](),editor[l8(0x2a35,'*ZKE')][l8(0x29c2,'$(mV')](new CustomEvent(l8(0x4117,'(qw$')));});},i[l4(0x4932,'lmQg')]=function(k){var la=l4;if(k){j=k;let l=k['getAttribute'](la(0x1432,'XcPC'));i[la(0x15c1,'XCXV')](JSON[la(0x2532,'hRv2')](l));}else j=null,i[la(0x1f1d,'$(mV')]({});i[la(0x2b34,'*gI2')]();},i;}[lb(0x3852,'*ZKE')](g,[]))||(f[lb(0x37a3,'6YaW')]=h);},0x1a2b:(f,g,h)=>{var ll=a0f,j,k;j=[h(0x9b5),h(0x20d1),h(0x1587)],void 0x0===(k=function(){var ld=a0f;function l(){var lc=a0f;this[lc(0xdce,'^d9X')](),this[lc(0x694,'@HIr')]();}return l[ld(0x3a85,'lnIj')]=function(m){var le=ld,o='\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[le(0x8af,'MDwy')][le(0x441d,'(qw$')]+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

    \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[le(0x3c1d,'VTTT')][le(0x18ee,'c]Mq')]+le(0xf88,'lmQg')+editor[le(0x4b44,'hK)l')][le(0x478f,'Nf2)')]+le(0x4e64,'N!xX')+editor[le(0x127c,'sYdH')][le(0x2e27,'!Yg[')]+le(0x306e,'zx#%');$('body')[le(0x4499,'Jc8$')](o),$(le(0x27d2,'Jj0M'))[le(0x14f2,'XCXV')]({'UndoButton':!0x0,'width':0x258,'height':0x12c,'lineWidth':0x5}),m&&m[le(0x4976,'6YaW')]&&$('#_signature')[le(0x4a2a,'hRv2')](le(0x4266,'kGY9'),m[le(0x4c4f,'H4NX')]);},l[ld(0x4725,'pMQs')]=function(m){var lf=ld;$(lf(0x853,'*ZKE'))[lf(0x4c2b,'V8^Q')](function(o){var lg=lf;$('.pop-mask')[lg(0x240b,'e*c8')]();}),$('#_btn-cancel')['click'](function(o){var lh=lf;$(lh(0x9bf,'esU('))[lh(0x4ce0,'@HIr')]();}),$(lf(0x2964,'Nf2)'))[lf(0x27a4,'pMQs')](function(o){var li=lf;$(li(0x34ec,'Jc8$'))['jSignature'](li(0x3610,'lmQg'));}),$(lf(0x39d5,'^Syn'))[lf(0x4031,'!Yg[')](function(o){var lj=lf,p=$(lj(0x8a9,'c]Mq'))[lj(0x306b,'xrMR')](lj(0x31e9,'oKRq'));m?m[lj(0x2f5,'Q@hO')]=p:editor['document'][lj(0x1a77,'N!xX')](lj(0x26b9,'Ws11'),!0x1,'{var lx=a0f,h;void 0x0===(h=function(){var lo=a0f;function i(){var lm=a0f;this['render'](),this[lm(0x41a1,'xbuu')]();}function j(){var ln=a0f;let k=localStorage[ln(0x242c,'XCXV')](ln(0x3c99,'XcPC')),l=k?JSON[ln(0xf2f,'Ws11')](k):{},m=[];for(let o in l)m[ln(0x4ceb,'*p#P')](l[o]['text']);return m[ln(0x297f,'t#L0')](ln(0x2618,'Jc8$'));}return i[lo(0xe42,'eu*B')]=function(){var lp=lo,k=['','',lp(0x4ad4,'zx#%'),' ',lp(0x3c26,'zx#%'),'',lp(0x3d3d,'lmQg'),lp(0xa5a,'^Syn'),lp(0x121b,'hK)l'),'数学符号
    ',lp(0xd40,'1Ue]'),lp(0xbe1,'(qw$'),lp(0x2560,'H4NX'),''+[lp(0x1f5a,'Bbr@'),j(),''][lp(0x130,'zbs!')]('')+lp(0x3d22,'XcPC'),lp(0x182f,'MDwy')+[lp(0x4e8f,'Bbr@'),lp(0x2cc1,'@HIr'),lp(0x4e0a,'#ZU^'),lp(0x464b,'5WLj'),lp(0x4e59,'v^si'),lp(0x1dba,'hRv2'),lp(0x483c,'!%Yf'),'PR.',lp(0x845,'^Syn'),'I.V',lp(0x3fec,'esU('),'IH','IM',lp(0x1f0f,'zbs!'),lp(0xa6c,'lmQg'),'HS.','AM.',lp(0x2174,'xbuu'),lp(0x4277,'XCXV'),lp(0x129a,'MDwy'),lp(0x3b7e,'e*c8'),lp(0xf49,'H4NX'),'QD',lp(0xe0c,'zx#%'),'TID',lp(0x21f0,'Bbr@'),'QH',lp(0xbe5,'H4NX'),'Q3H',lp(0x2413,'NrJk'),'MG','G','ML',lp(0x3b8f,'eu*B'),'qd',lp(0x32b4,'^d9X'),lp(0x2fa4,'#ZU^'),'qid','qh',lp(0x1653,'eu*B'),lp(0xd05,'^d9X'),lp(0x43dd,'hRv2'),'qn','qod',lp(0x1741,'[T9%'),'hs','am','pm','St','DC',lp(0x14ea,'vGE1'),lp(0x1d10,'v^si'),'ac','pc',lp(0x9a8,'^d9X'),lp(0x390d,'xbuu'),lp(0x51d,'kGY9'),'ID','IH','IM','IV','aa','et',lp(0x3b7b,'xbuu'),lp(0xcbb,'*gI2'),lp(0x2c5f,'Nf2)'),lp(0x1c91,'VTTT'),lp(0xdc6,'[T9%'),lp(0x96b,'*gI2'),lp(0xe1a,'UDz8'),lp(0xe1b,'VTTT'),'a.m.','p.m.',lp(0x497c,'VTTT'),lp(0x4221,'!%Yf'),lp(0xe69,'5WLj'),'q.d.',lp(0x4343,'#ZU^'),lp(0x400d,'!Yg['),'Q.i.d.',lp(0x3900,'Jj0M'),lp(0xeb1,'pMQs'),lp(0x770,'pMQs'),lp(0x4d36,'1Ue]'),'H.',lp(0x1fee,'NrJk'),lp(0x1913,'^Syn'),lp(0x4924,'Ws11'),'Inhal.',lp(0x3c78,'(qw$'),lp(0x41d6,'!Yg['),'O.S.',lp(0x4d58,'^d9X'),'No./N.',lp(0xe68,'lnIj'),lp(0x40b6,'sYdH'),lp(0x103,'Q@hO'),'g.',lp(0x891,'hK)l'),lp(0x3168,'5WLj'),'L.',lp(0x47cc,'H4NX'),lp(0x14f3,'oKRq'),lp(0xaf,'c]Mq'),'Aq.dest.',lp(0xac1,'sYdH'),lp(0x2a13,'hK)l'),lp(0x1e7f,'hRv2'),'Co./Comp.','Mist',lp(0x1dd0,'5WLj'),'Amp.',lp(0x638,'^d9X'),lp(0x4a56,'[T9%'),lp(0x401,'Nf2)'),'Neb.','Garg.','rtt./gutt.',lp(0x4233,'MDwy'),lp(0x3253,'zbs!'),lp(0x3746,'lnIj'),'Sol.',lp(0x248f,'@HIr'),lp(0x4325,'^d9X'),lp(0x1d4a,'*ZKE'),lp(0x151a,'Bbr@'),'Past.',lp(0x48e4,'VTTT'),lp(0x465f,'xbuu'),lp(0x4b49,'Bbr@'),lp(0x1e60,'e*c8'),'Pil.',lp(0x4023,'Q@hO'),lp(0x2779,'UDz8'),'po','im','iv',lp(0x21b5,'@HIr'),'qd',lp(0x609,'UDz8'),lp(0x2fa4,'#ZU^'),lp(0x2ff7,'Q@hO'),lp(0x1fd,'MDwy'),'qn','Rp',lp(0x288a,'5WLj'),lp(0x13de,'lmQg')][lp(0x17e3,'(qw$')](lp(0xbb9,'hK)l'))+lp(0x25df,'1Ue]'),''+['⒈','⒉','⒊','⒋','⒌','⒍','⒎','⒏','⒐','⒑','⒒','⒓','⒔','⒕','⒖','⒗','⒘','⒙','⒚','⒛','⑴','⑵','⑶','⑷','⑸','⑹','⑺','⑻','⑼','⑽','⑾','⑿','⒀','⒁','⒂','⒃','⒄','⒅','⒆','⒇','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩','㈠','㈡','㈢','㈣','㈤','㈥','㈦','㈧','㈨','㈩','ⅰ','ⅱ','ⅲ','ⅳ','ⅴ','ⅵ','ⅶ','ⅷ','ⅸ','ⅹ','Ⅰ','Ⅱ','Ⅲ','Ⅳ','Ⅴ','Ⅵ','Ⅶ','Ⅷ','Ⅸ','Ⅹ','Ⅺ','Ⅻ','g/L',lp(0x3072,'xbuu'),lp(0x3555,'[T9%'),lp(0x2c74,'kGY9')][lp(0x4849,'xbuu')](lp(0x319f,'@HIr'))+lp(0x229f,'!Yg['),lp(0x2e83,'IvBK')+[lp(0x38bd,'5WLj'),'。','·','ˉ','ˇ','¨','〃','々','—','~','‖','…','‘','’','“','”','〔','〕','〈','〉','《','》','「','」','『','』','〖','〗','【','】','±','×','÷','∶','∧','∨','∑','∏','∪','∩','∈','∷','√','⊥','∥','∠','⌒','⊙','∫','∮','≡','≌','≈','∽','∝','≠','≮','≯','≤','≥','∞','∵','∴','♂','♀','°','′','″','℃','$','¤','¢','£','‰','§','№','☆','★','○','●','◎','◇','◆','□','■','△','▲','※','→','←','↑','↓','〓','〡','〢','〣','〤','〥','〦','〧','〨','〩','㊣','㎎','㎏','㎜','㎝','㎞','㎡','㏄','㏎','㏑','㏒','㏕',lp(0x3830,'kGY9')][lp(0x297f,'t#L0')]('')+lp(0x29a4,'kGY9'),'',lp(0x4a41,'Ws11'),lp(0x420e,'1Ue]'),lp(0x30d5,'oKRq'),lp(0x318,'*ZKE'),'','']['join']('');$(lp(0x497b,'!Yg['))[lp(0xee0,'c]Mq')](k);},i['bindEvent']=function(){var lq=lo;$(lq(0x8a7,'kGY9'))[lq(0x1db9,'xrMR')](function(k){var lr=lq;$(lr(0x43ed,'MDwy'))[lr(0x380d,'NrJk')]();}),$(lq(0x1d0d,'v^si'))[lq(0x14f8,'Q@hO')](function(k){var ls=lq;$(ls(0x10f8,'v^si'))[ls(0x9fd,'xrMR')]();}),$(lq(0x1859,'vGE1'))[lq(0x1bd,'N!xX')](lq(0x371c,'^Syn'))['on'](lq(0x44ab,'^d9X'),lq(0x1bfd,'!%Yf'),function(k){var lt=lq;$('#_symbol-panel')[lt(0x1734,'XcPC')]('.select')['removeClass'](lt(0x100b,'Q@hO')),k[lt(0x391b,'XcPC')]['classList'][lt(0xd1,'MDwy')](lt(0x2229,'lmQg')),$('#_symbol-panel')[lt(0x31be,'*gI2')]('#_tool-panel-body')[lt(0xe5b,'pMQs')](lt(0xb8,'c]Mq'))[lt(0x2eff,'zx#%')]();let l=k[lt(0x142e,'^d9X')][lt(0x4934,'pMQs')](lt(0x4457,'c]Mq'));$('#_symbol-panel')['find'](l)[lt(0x2c71,'!%Yf')]();}),$(lq(0x1f7c,'$(mV'))[lq(0x4cfd,'v^si')]('#_tool-panel-body')['on'](lq(0x43a0,'XcPC'),lq(0x3d86,'lmQg'),function(k){var lu=lq;let l=k[lu(0x2924,'H4NX')][lu(0x3c46,'*gI2')];editor[lu(0x4ba8,'IvBK')][lu(0x4131,'V8^Q')]('insertHTML',!0x1,l),function(m){var lv=lu;let o=localStorage[lv(0x4f1a,'zx#%')](lv(0x2c52,'kGY9')),p=o?JSON[lv(0x224,'^d9X')](o):{};p[m]={'text':m,'date':new Date()['getTime']()},localStorage[lv(0x141f,'XcPC')]('recent_symbol',JSON[lv(0x49cf,'*ZKE')](p));}(l),$(lu(0x116e,'N!xX'))[lu(0x17f5,'v^si')]();});},i[lo(0x4ea3,'zx#%')]=function(){var lw=lo;i[lw(0x478d,'VTTT')](),i[lw(0x1852,'5WLj')]();},i;}[lx(0x483e,'vGE1')](g,[]))||(f['exports']=h);},0x12f1:(f,g)=>{var lH=a0f,h;void 0x0===(h=function(){var ly=a0f;function i(){}return i[ly(0x3ae7,'Q@hO')]=function(){var lz=ly;for(var j=['',lz(0x25c5,'VTTT'),lz(0x13e1,'1Ue]')],k=0x0;k<0x14;k++){j['push'](lz(0x124f,'Jj0M'));for(var l=0x0;l<0x14;l++)j[lz(0x196d,'hK)l')]('');j['push'](lz(0x4913,'Jc8$'));}j[lz(0x3c80,'Jj0M')](lz(0xacb,'lmQg')),$(lz(0x34e0,'zx#%'))[lz(0x4f10,'Bbr@')](j[lz(0x3a83,'5WLj')](''));},i[ly(0xb63,'^Syn')]=function(){var lA=ly;const j=document['querySelector'](lA(0x23a5,'IvBK')),k=document[lA(0x4452,'zbs!')](lA(0x3883,'UDz8')),l=document[lA(0x3091,'$(mV')](lA(0x22a6,'IvBK'))['children'][0x0];var m=0x0,o=0x0;j['onmousemove']=function(p){var lC=lA,{offsetX:q,offsetY:u}=p;!(function(){var lB=a0f;for(var y=0x0;y{var h;void 0x0===(h=function(){var lV=a0f;function j(){var lI=a0f;this['render'](),this[lI(0x2484,'!%Yf')]();}function k(l,m){var lJ=a0f;let o=localStorage[lJ(0xf31,'t#L0')]('filehistory'),p=o?JSON['parse'](o):{};p[l]={'id':l,'name':m,'date':new Date()[lJ(0x43ae,'H4NX')]()},localStorage[lJ(0x1412,'6YaW')]('filehistory',JSON['stringify'](p));}return j['render']=function(){var lK=a0f,l=lK(0x1b5d,'$(mV')+editor[lK(0x1444,'Nf2)')]['template']+lK(0x119e,'*ZKE')+editor[lK(0x347c,'N!xX')][lK(0x1396,'pMQs')]+lK(0x38e9,'hK)l');$('body')[lK(0x360d,'hK)l')](l);},j['bindEvent']=function(){var lL=a0f;j['initTree'](),j['initHistroy'](),$(lL(0x1f1a,'*gI2'))[lL(0x4385,'Ws11')](function(l){var lM=lL;$(lM(0x434a,'sYdH'))[lM(0x503,'eu*B')]();}),$(lL(0xfe6,'esU('))[lL(0x16d9,'Q@hO')]('#_tool-panel-head')['on'](lL(0x125d,'kGY9'),lL(0x38f9,'Nf2)'),function(l){var lN=lL;let m=l[lN(0xf54,'zbs!')];$(lN(0x1b9f,'!%Yf'))[lN(0x4911,'*p#P')](lN(0x2c6e,'Jj0M'))[lN(0x10d0,'xrMR')](lN(0x4b7,'1Ue]'))[lN(0x26c0,'N!xX')](lN(0x42b4,'hK)l')),m['classList']['add'](lN(0x3b0d,'vGE1')),$(lN(0x344e,'(qw$'))['find'](lN(0x4e62,'xbuu'))[lN(0x1022,'^d9X')]('div')['hide']();let o=m[lN(0x188a,'H4NX')](lN(0x1ce7,'lmQg'));$('#_template-panel')[lN(0x3202,'Jc8$')]('#_tool-panel-body')['find'](o)['show']();});},j['initTree']=function(){var lO=a0f,l={'view':{'dblClickExpand':!0x1,'showLine':!0x1,'selectedMulti':!0x1,'showIcon':!0x0},'edit':{'enable':!0x0,'showRemoveBtn':!0x1},'data':{'simpleData':{'enable':!0x0,'idKey':'id','pIdKey':lO(0x1361,'Nf2)'),'rootPId':''}},'callback':{'onDblClick':function(o,p,q){var lP=lO;editor[lP(0x15f9,'c]Mq')][lP(0x2090,'XcPC')]=q[lP(0x56b,'c]Mq')],editor[lP(0x2157,'!%Yf')](lP(0x1389,'Q@hO')+q['id']+lP(0x3881,'hRv2'),q['id']),k(q['id'],q[lP(0xd88,'e*c8')]),$(lP(0x3bc9,'Bbr@'))['remove']();},'onRename':function(o,p,q){var lQ=lO;$[lQ(0x2e06,'zx#%')]('/doc/'+q['id']+lQ(0x1d02,'Q@hO'),{'title':q[lQ(0x4945,'zbs!')]},function(r,s){var lR=lQ;editor[lR(0x4b2b,'(qw$')][lR(0x4382,'hK)l')]=q[lR(0x328c,'Q@hO')];});}}};function m(o){var lS=lO;$[lS(0x275d,'xbuu')](lS(0x3de5,'6YaW')+o,function(p,q){var lT=lS;t=$['fn']['zTree'][lT(0x1974,'!%Yf')]($(lT(0x1d04,'pMQs')),l,p);});}$(lO(0x15b8,'5WLj'))[lO(0x16ca,'pMQs')](function(o){var lU=lO;0xd==o[lU(0x2fe7,'V8^Q')]&&m($(lU(0x29eb,'@HIr'))[lU(0x1cc7,'Bbr@')]()[lU(0xd91,'VTTT')]());}),m('');},j[lV(0x43a7,'^d9X')]=function(){var lW=lV;let l=localStorage[lW(0x39bf,'oKRq')]('filehistory'),m=l?JSON[lW(0x20ef,'(qw$')](l):{},p=[];for(var q in m)p[lW(0x2632,'XcPC')](m[q]);p[lW(0x1f67,'lmQg')]((u,v)=>v[lW(0x4b08,'$(mV')]-u[lW(0x2045,'v^si')]);let s=['
      '];p[lW(0x2096,'N!xX')](u=>{var lX=lW;s['push'](lX(0x16db,'NrJk')+u['id']+'\x22\x20name=\x22'+u[lX(0x45d1,'t#L0')]+'\x22>'+new Date(u[lX(0x3914,'Q@hO')])['toLocaleString']()+'  '+u[lX(0x3689,'XcPC')]+lX(0x26f3,'XcPC'));}),s[lW(0x2117,'@HIr')](lW(0x1c70,'hK)l')),$(lW(0x176e,'Bbr@'))[lW(0x3654,'xbuu')](lW(0x38fc,'V8^Q'))['html'](s[lW(0x4c02,'1Ue]')]('')),$(lW(0x3a13,'[T9%'))[lW(0x16d9,'Q@hO')](lW(0x38fc,'V8^Q'))['on'](lW(0x37ca,'(qw$'),'li',function(u){var lY=lW;let v=u[lY(0x1ce7,'lmQg')],w=v['getAttribute']('id'),x=v['getAttribute'](lY(0x29be,'xbuu'));editor[lY(0x41fa,'lmQg')][lY(0x226b,'Bbr@')]=x,editor[lY(0x4e75,'Ws11')](lY(0x1dd7,'*gI2')+w+'.html',w),k(w,x),$(lY(0x2842,'^d9X'))[lY(0xd03,'c]Mq')]();});},j[lV(0x4847,'Nf2)')]=function(){var lZ=lV;j[lZ(0x4d4f,'Nf2)')](),j[lZ(0xa80,'XcPC')]();},j;}['apply'](g,[]))||(f['exports']=h);},0x4ab:(f,g)=>{var mc=a0f,h;void 0x0===(h=function(){var m0=a0f;function i(){this['render'](),this['bindEvent']();}var j=null;return i[m0(0xdce,'^d9X')]=function(k){var m1=m0;$(m1(0xc35,'vGE1'))[m1(0x4033,'xrMR')]('\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20牙位图\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20全口\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20上半口\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20下半口\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20左上\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20左下\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20右上\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20右下\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20乳牙\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20清除\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20E\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20D\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20C\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20B\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20A\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20A\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20B\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20C\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20D\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20E\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20E\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20D\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20C\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20B\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20A\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20A\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20B\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20C\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20D\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20E\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20取消\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20确定\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

      \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'),k&&$(m1(0x41e8,'c]Mq'))[m1(0x31d0,'NrJk')](m1(0x162,'V8^Q'))[m1(0x303a,'Nf2)')](function(l,m){var m2=m1;let o=m[m2(0x4a11,'v^si')][m2(0x1c99,'xrMR')]('code')+m[m2(0x2add,'hK)l')];k[m2(0x381b,'xrMR')](o)>=0x0&&(m['style'][m2(0x2802,'H4NX')]=m2(0x26ed,'vGE1'));});},i['bindEvent']=function(){var m3=m0;$('#_btn-close-panel')[m3(0x2cbc,'MDwy')](function(k){var m4=m3;$(m4(0x1a08,'XcPC'))[m4(0x3579,'zbs!')]();}),$(m3(0x25c8,'e*c8'))['click'](function(k){var m5=m3;$('.pop-mask')[m5(0x293e,'V8^Q')]();}),$('#_toothSelecter')['on'](m3(0x2735,'!%Yf'),m3(0x3811,'Q@hO'),function(k){var m6=m3;''==k['target'][m6(0x2baf,'V8^Q')][m6(0x48f7,'UDz8')]?k['target'][m6(0x42ed,'xrMR')]['background']=m6(0xf0c,'zx#%'):k[m6(0x1cf8,'IvBK')]['style'][m6(0x1dec,'IvBK')]='';}),$(m3(0x4e6d,'5WLj'))['on']('click',m3(0x3807,'lmQg'),function(k){var m7=m3;$(m7(0x2369,'oKRq'))[m7(0x10e7,'lmQg')]('button')['css']('background','');let l=k[m7(0x4457,'c]Mq')]['getAttribute'](m7(0x3079,'zx#%'));m7(0x1bf9,'Bbr@')==l?$(m7(0x1989,'Jc8$'))['find'](m7(0xed5,'!%Yf'))[m7(0x1774,'*gI2')](m7(0x1ff7,'5WLj'),m7(0x48e5,'kGY9')):m7(0x200c,'V8^Q')==l?$(m7(0x19fb,'6YaW'))[m7(0x1a87,'Jj0M')]('button.milk')[m7(0x2c6,'oKRq')](m7(0x3f6,'N!xX'),m7(0x1f3d,'NrJk')):m7(0x3bdf,'NrJk')==l?$(m7(0x10d,'[T9%'))['find']('[code=1]\x20button:not(.milk)')[m7(0x10b4,'IvBK')]('background','pink'):m7(0x1546,'sYdH')==l?$('#_toothSelecter')[m7(0xbc6,'eu*B')](m7(0xdf,'xrMR'))[m7(0x2268,'VTTT')](m7(0x48f7,'UDz8'),m7(0x47f5,'[T9%')):m7(0x255e,'xrMR')==l?$(m7(0x4148,'H4NX'))[m7(0x2c75,'XCXV')]('[code=3]\x20button:not(.milk)')[m7(0xa23,'1Ue]')](m7(0x4036,'zx#%'),'pink'):'right-bottom'==l?$('#_toothSelecter')[m7(0x2b8b,'@HIr')]('[code=4]\x20button:not(.milk)')[m7(0x31b8,'eu*B')](m7(0x4036,'zx#%'),m7(0x3cef,'lnIj')):m7(0x3ed6,'c]Mq')==l?($(m7(0x3c3e,'Nf2)'))[m7(0x2b8b,'@HIr')](m7(0x118d,'N!xX'))[m7(0x2268,'VTTT')]('background',m7(0x1f3d,'NrJk')),$(m7(0x4a51,'sYdH'))['find'](m7(0x308c,'esU('))[m7(0x2cb6,'^d9X')](m7(0x1ff7,'5WLj'),'pink')):'bottom'==l&&($(m7(0x33f4,'V8^Q'))[m7(0x1734,'XcPC')](m7(0x2f95,'UDz8'))[m7(0x100f,'!Yg[')](m7(0x10de,'NrJk'),m7(0x14c0,'e*c8')),$('#_toothSelecter')[m7(0x10d0,'xrMR')](m7(0x127d,'hRv2'))[m7(0x4759,'(qw$')]('background',m7(0x4c2c,'hK)l')));}),$(m3(0x2590,'eu*B'))['click'](function(k){var m8=m3;let l=[];$('#_toothSelecter')['find'](m8(0x2d2f,'N!xX'))[m8(0x2460,'^d9X')](function(p,q){var m9=m8;''!=q[m9(0x4afb,'*gI2')][m9(0x1dec,'IvBK')]&&l[m9(0x344d,'Nf2)')](q[m9(0x4a11,'v^si')][m9(0x2f00,'*ZKE')](m9(0x52d,'Ws11'))+q[m9(0x615,'hRv2')]);}),console['log'](l);let m={0x1:[],0x2:[],0x3:[],0x4:[]};l[m8(0x3ea4,'V8^Q')](p=>{var ma=m8;m[p[ma(0x1d9d,'Bbr@')](0x0)][ma(0x4258,'*ZKE')](p[ma(0xac4,'hK)l')](0x1));});let o=[m8(0x27fc,'*ZKE')+m[0x1]+m8(0xe0f,'pMQs'),m8(0x33cc,'t#L0')+m[0x2]+m8(0xc07,'vGE1'),m8(0xb2d,'IvBK')+m[0x3]+m8(0xcad,'H4NX'),''+m[0x4]+m8(0x37e7,'v^si'),m8(0x4ff,'lnIj'),m8(0x24ee,'Bbr@')]['join']('');j?(j['style'][m8(0x3259,'N!xX')]=m8(0x4d39,'XcPC'),j['setAttribute']('type',m8(0x1198,'VTTT')),j[m8(0x4889,'!Yg[')](m8(0x2ad3,'V8^Q'),JSON[m8(0x104a,'VTTT')](l)),j['innerHTML']=o):(o=m8(0x27ac,'zbs!')+JSON['stringify'](l)[m8(0x3ac1,'V8^Q')]('\x22',m8(0x2fdb,'Jc8$'))+'\x22>'+o+m8(0x206c,'NrJk'),editor[m8(0x1028,'#ZU^')][m8(0x1fbb,'kGY9')](m8(0x1204,'hK)l'),!0x1,o)),$(m8(0x43ed,'MDwy'))[m8(0x3579,'zbs!')]();});},i[m0(0x90a,'#ZU^')]=function(k){var mb=m0;if(k){j=k;let l=k[mb(0x4934,'pMQs')]('data');i['render'](JSON['parse'](l));}else j=null,i[mb(0xe42,'eu*B')]();i[mb(0x42cf,'Q@hO')]();},i;}[mc(0x4984,'lnIj')](g,[]))||(f[mc(0x26d3,'v^si')]=h);},0x1ba8:(f,g)=>{var mG=a0f,h;void 0x0===(h=function(){var md=a0f;function j(){}j[md(0x2546,'hRv2')]=function(Q){var me=md;if(Q){currentTarget=Q;let R=Q[me(0x4ae7,'*gI2')](me(0x15bf,'!Yg['));j[me(0x2bf1,'H4NX')](JSON['parse'](R));}else currentTarget=null,j['render']();j['bindEvent']();},j[md(0x3407,'oKRq')]=function(){var mf=md,Q=['\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20三测单\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20脉搏数据:\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20口温腋温肛温耳温\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20确定\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20取消\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

      \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'][mf(0x17e3,'(qw$')]('');$(mf(0x3757,'*ZKE'))[mf(0x4033,'xrMR')](Q),q();},j[md(0x42cf,'Q@hO')]=function(){var mg=md;k(mg(0x4270,'e*c8'))[mg(0x21b8,'1Ue]')](function(Q){q(Q);}),$(mg(0x853,'*ZKE'))[mg(0x13e0,'IvBK')](function(Q){var mh=mg;$(mh(0x4d9c,'lmQg'))[mh(0x1f91,'N!xX')]();}),$(mg(0x191a,'*ZKE'))[mg(0x27a4,'pMQs')](function(Q){var mi=mg;$(mi(0x2842,'^d9X'))[mi(0x3dd8,'Jc8$')]();}),$(mg(0x3eca,'esU('))[mg(0x29f0,'vGE1')](function(Q){var mj=mg;v(q()),$('.pop-mask')[mj(0x7b9,'lmQg')]();});};const k=function(Q){var mk=md;return $('#_vitalSigns')[mk(0x4911,'*p#P')](Q);};let q=function(Q){var ml=md;let R={};return k(ml(0x2bcf,'XCXV'))[ml(0x21bd,'e*c8')]((S,T)=>{var mm=ml;R[T['id']]=$(T)[mm(0x2427,'Jc8$')]();}),k('#data')[ml(0x4434,'!%Yf')](JSON[ml(0x2ed5,'*p#P')](R,!0x1,0x4)),R;},v=function(Q){var mn=md;let R=[mn(0x489,'Ws11'),'']['join']('');editor['$'](mn(0x6cf,'MDwy'))[mn(0x4c22,'e*c8')](R),editor['$'](mn(0x339d,'$(mV'))['on'](mn(0x165b,'*ZKE'),S=>{var mo=mn;S[mo(0xa97,'(qw$')][mo(0x27b7,'pMQs')][mo(0x108,'*ZKE')]?editor['$'](mo(0x277a,'6YaW'))['show']()[mo(0x1ce2,'t#L0')](mo(0x405,'!Yg['),S[mo(0x4ab2,'NrJk')])['css'](mo(0x2626,'VTTT'),S['offsetY']-0x19)['html'](mo(0x4c10,'zx#%')+S[mo(0x4250,'5WLj')][mo(0x2a7e,'oKRq')][mo(0x12b7,'lmQg')]+mo(0x454,'*ZKE')):S['target'][mo(0x49aa,'*gI2')][mo(0x37b3,'zbs!')]?editor['$'](mo(0x1164,'!Yg['))[mo(0x1316,'kGY9')]()['css'](mo(0xeef,'^Syn'),S['offsetX'])[mo(0xaba,'NrJk')](mo(0x447e,'sYdH'),S[mo(0x3f82,'[T9%')]-0x19)['html'](mo(0x1590,'XcPC')+S[mo(0x219e,'^Syn')][mo(0x36a0,'e*c8')][mo(0x4e81,'VTTT')]+'℃'):editor['$'](mo(0xd53,'zbs!'))[mo(0x1d6f,'*p#P')]();});},w=function(Q){var mp=md;return[mp(0xd2c,'*ZKE'),Q,mp(0x494e,'(qw$')][mp(0x3628,'c]Mq')]('');},x=function(){var mq=md;return[''][mq(0x4d0e,'oKRq')]('');},z=function(){var mr=md;let Q=[],R=0x0,S=0x0,T='';for(let U=0x0;U<0x36;U++)R+=0x14,U>0x3&&U<0x2b?(S=0x78,T=(U-0x3)%0x5==0x0?'\x20style=\x22stroke:\x20blue;\x22\x20':''):(S=0x0,T=''),0x2c==U&&(R+=0x14),Q[mr(0x4f7,'!%Yf')]('');return Q['join']('');},A=function(){var ms=md;let Q=[],R=0x78,S=0x3c,T=0x370,U='';Q[ms(0x3c80,'Jj0M')](ms(0x1ee5,'t#L0')+(S+0x14)+'\x22\x20x2=\x2260\x22\x20y2=\x22'+T+ms(0x1ba2,'Bbr@'));for(let V=0x0;V<0x2a;V++)V%0x6==0x0?(S=0x0,T=0x460,V>0x0&&(U=ms(0x112,'MDwy'))):(S=0x3c,T=0x398,U=''),Q[ms(0x17ac,'e*c8')](ms(0x2c85,'c]Mq')+R+ms(0x4cff,'*ZKE')+S+'\x22\x20x2=\x22'+R+ms(0x3ab3,'*p#P')+T+ms(0x48f,'v^si')+U+ms(0xd64,'MDwy')),R+=0x14;return Q[ms(0x3ef7,'VTTT')]('');},B=function(Q){var mt=md;return[mt(0x2b8f,'#ZU^'),mt(0x36c2,'@HIr')+Q[mt(0xd88,'e*c8')]+'',mt(0x1717,'zx#%')+Q[mt(0x4b53,'N!xX')]+mt(0x546,'zx#%'),mt(0x88a,'Q@hO')+Q[mt(0x2c94,'XcPC')]+mt(0x461,'Nf2)'),mt(0x3b37,'Q@hO')+Q[mt(0x2307,'5WLj')]+mt(0x3856,'!Yg['),mt(0x2ff9,'^Syn')+Q[mt(0x3403,'esU(')]+mt(0xf51,'zbs!'),mt(0x3131,'lnIj')+Q[mt(0x4d7e,'esU(')]+mt(0x2cba,'xrMR'),mt(0x137,'*gI2')][mt(0x1e39,'*ZKE')]('');},C=function(Q){var mu=md;let R=0x14,S=0xf,T=new Date(Q[mu(0x18ae,'1Ue]')]),U='',V=(new Date(Q[mu(0x6f7,'sYdH')])-new Date(Q[mu(0x4e30,'esU(')]))/0x5265c00+0x1,W=[mu(0x1aa3,'lmQg')+R+mu(0x11ad,'v^si')+S+mu(0xc0a,'H4NX')];for(let X=0x0;X<0x7;X++)R+=0x78,W[mu(0x4f7,'!%Yf')](mu(0x32fc,'Q@hO')+R+mu(0x40d4,'hK)l')+S+'\x22>'+T[mu(0x47cb,'^d9X')]()+''),T[mu(0xf67,'VTTT')](T[mu(0x4d70,'*p#P')]()+0x1);R=0x14,S+=0x14,W['push'](''+V+mu(0x2280,'xrMR')),V+=0x1;R=0x14,S+=0x14,W[mu(0x3ba1,'Q@hO')](''+(V>0x0?V:'')+mu(0xc07,'vGE1')),V+=0x1;R=0x14,S+=0x14,W['push'](mu(0x3408,'6YaW')+R+mu(0x4e68,'Jc8$')+S+'\x22>时间'),R+=0x64;for(let a0=0x0;a0<0x2a;a0++){let a1=a0%0x6*0x4+0x2;U=a1>0x6&&a1<0x12?'':mu(0x377d,'(qw$'),W['push'](mu(0x1862,'IvBK')+U+mu(0x344a,'Ws11')+(a1<0xa?R+0x8:R+0x2)+mu(0x8e3,'t#L0')+S+'\x22>'+a1+mu(0xc07,'vGE1')),R+=0x14;}return W[mu(0x34e5,'*gI2')]('');},E=function(Q){var mv=md;let R=[];R[mv(0x423d,'hRv2')]('脉搏(次/分)');let S=0x14,T=0xb4,U=0xa0;for(let W=0x0;W<0x7;W++)R[mv(0x2d0f,'^Syn')](''+U+mv(0xc1c,'(qw$')),T+=0x64,U-=0x14;R[mv(0x2117,'@HIr')](mv(0x207e,'hK)l')),S=0x50,T=0xb4,temp=0x29;for(let X=0x0;X<0x7;X++)R['push'](''+temp+mv(0x4c87,'sYdH')),T+=0x64,temp-=0x1;S=0x4,T=0x370,R[mv(0x423d,'hRv2')](mv(0x3408,'6YaW')+S+mv(0x4885,'(qw$')+(T+0x1a)+'\x22>呼吸(次/分)'),T=0x384;let V=[mv(0x17da,'v^si'),mv(0x14cf,'Q@hO'),'出水量(ml)',mv(0x322b,'H4NX'),mv(0x79e,'pMQs'),mv(0x41da,'sYdH'),mv(0x3c95,'MDwy'),mv(0x43de,'c]Mq')];Q['labels']&&(V=Q[mv(0x2fd6,'^d9X')][mv(0x2da8,'5WLj')]('|'));for(let Y=0x0;Y'+V[Y]+mv(0x1ce9,'[T9%'));return R[mv(0x2689,'^d9X')]('');},F=function(){var mw=md;return[mw(0x23bf,'vGE1'),mw(0xe71,'6YaW'),mw(0x1433,'MDwy'),mw(0x1168,'hK)l'),mw(0xf62,'!%Yf'),'','',mw(0x1e05,'Bbr@'),mw(0x2a5b,'5WLj'),'',mw(0x2281,'XCXV'),'',mw(0x359c,'pMQs'),'',mw(0xcf4,'#ZU^'),mw(0x3f20,'sYdH'),mw(0x2a46,'^Syn'),mw(0x2ba9,'6YaW'),mw(0x6b1,'vGE1'),mw(0x3eef,'Jj0M'),'脉搏','',mw(0x5d4,'Ws11'),mw(0x3aca,'N!xX'),mw(0x3eaf,'Q@hO')][mw(0x14ef,'vGE1')]('');},G=function(Q){var mx=md;let R=[];if(R['push'](mx(0x3d38,'Q@hO')),Q['notes']){let S=Q[mx(0x4865,'!%Yf')][mx(0x6b8,'vGE1')](',');for(let T=0x0;T');}return R['push'](mx(0x48d,'zx#%')),R['join']('');},H=function(Q){var my=md;let R=[];if(R[my(0x32a7,'VTTT')](my(0x3106,'eu*B')),Q[my(0x22ff,'^Syn')]){let S=Q[my(0x2e8c,'oKRq')][my(0x2ec3,'N!xX')](','),T=0x0,U=0x0;for(let V=0x0;V=0x28){let W=0x82+0x14*V,X=0x50+0x5*(0xb4-S[V]);R['push'](my(0x4601,'XCXV')+W+my(0x1539,'Q@hO')+X+my(0x4448,'1Ue]')+S[V]+'\x22>'),V>0x0&&R[my(0x444e,'Jc8$')]('=0x28){let W=0x82+0x14*V,X=0x50+0x5*(0xb4-T[V]);R[mz(0xfe8,'NrJk')]('=0x0;Y--){let Z=0x82+0x14*Y,a0=0x50+0x5*(0xb4-U[Y]);S[mz(0x1a5e,'t#L0')](Z+','+a0);}R[mz(0x4b4d,'xbuu')](''),Q[mA(0xc3e,'eu*B')]){let S=Q[mA(0x7b8,'^d9X')][mA(0x3f13,'^Syn')](','),T=0x0,U=0x0;for(let V=0x0;V=0x23){let W=0x82+0x14*V,X=0x50+0x78*(0x2a-S[V]);V>0x0&&T&&U&&R[mA(0x449,'sYdH')](mA(0x2cd7,'eu*B')+T+mA(0x4710,'^d9X')+U+mA(0x3c1a,'Q@hO')+W+mA(0x220b,'*gI2')+X+mA(0x2960,'v^si')),R[mA(0x4b4d,'xbuu')]('不升➔'));}return R['push'](mA(0x2756,'zbs!')),R[mA(0xdfa,'hRv2')]('');},K=function(Q,R){var mB=md;let S=mB(0x1987,'zx#%')+R+'\x22\x20';return 0x0==Q?mB(0x3111,'@HIr')+S+mB(0x475d,'*p#P'):0x1==Q?mB(0x2573,'Jc8$')+S+'>':0x2==Q?mB(0xda2,'hK)l')+S+'\x20>':0x3==Q?mB(0x206d,'Nf2)')+S+mB(0x4341,'xrMR'):0x4==Q?mB(0x2856,'kGY9')+S+'\x20>':void 0x0;},L=function(Q){var mC=md;let R=[];if(R[mC(0x4476,'$(mV')](''),Q[mC(0x2f0e,'*ZKE')]){let S=Q[mC(0x37b1,'!%Yf')]['split'](','),T=Q['temperature'][mC(0x3f4b,'zx#%')](',');for(let U=0x0;U'),cx0=V,cy0=W;}}return R[mC(0x10cc,'lnIj')](''),R[mC(0x4706,'v^si')]('');},N=function(Q){var mD=md;let R=[];if(R['push'](mD(0x3b3f,'!Yg[')),Q[mD(0x11e,'Bbr@')]){let S=Q[mD(0x402e,'xbuu')][mD(0x776,'XcPC')]('R','Ⓡ')[mD(0x4c26,'kGY9')](','),T=0x0,U=0x0;for(let V=0x0;V'+S[V]+mD(0x37e7,'v^si')),T+=0x14;}return R[mD(0x344d,'Nf2)')](mD(0x1f57,'[T9%')),R['join']('');},O=function(Q){var mE=md;let R=[];R[mE(0x2a93,'xrMR')](mE(0x1f38,'hK)l'));for(let S=0x1;S<=0x8;S++)if(Q[mE(0xd4d,'MDwy')+S]){let T=Q[mE(0x1793,'*ZKE')+S][mE(0x2ceb,'zbs!')](','),U=0x0,V=0x14*S;for(let W=0x0;W'+T[W]+mE(0x2a84,'^Syn')),U+=0x78;}return R['push'](mE(0x338e,'esU(')),R['join']('');},P=function(){var mF=md;return[mF(0xb4,'N!xX'),mF(0x4532,'*p#P'),mF(0xffd,'UDz8'),mF(0x3e48,'t#L0')][mF(0x1d1c,'^Syn')]('');};return j[md(0x9db,'sYdH')]=v,j;}[mG(0x33fe,'XCXV')](g,[]))||(f[mG(0x26d3,'v^si')]=h);},0x8d1:(f,g,h)=>{var n8=a0f,j,k;j=[h(0x1dfa),h(0x1f64),h(0x22af),h(0xe3)],void 0x0===(k=function(l,m,p,q){'use strict';let s=function(){var mH=a0f;if($(mH(0x34c3,'c]Mq'))['is'](mH(0xf4b,'eu*B')))return;let v=mH(0x5a4,'Jc8$')+l[mH(0x4744,'eu*B')]()+'\x20'+l[mH(0x101d,'!Yg[')]()+';}';editor[mH(0x36aa,'6YaW')][mH(0x387b,'H4NX')][mH(0x1ff0,'oKRq')]('printSzie')[mH(0xaed,'H4NX')]=v;let w=u();mH(0x2b6d,'vGE1')!=editor[mH(0x23f8,'hRv2')][mH(0x26f5,'e*c8')]&&(w+=mH(0x102f,'XcPC')+editor['lang'][mH(0x4cd6,'Jc8$')]+mH(0x2436,'t#L0')),editor[mH(0xf08,'hRv2')]['document'][mH(0x5c2,'*ZKE')]=editor['document'][mH(0x3c4b,'NrJk')],editor[mH(0x3891,'eu*B')][mH(0x47a2,'Q@hO')][mH(0x2134,'@HIr')]['innerHTML']=w,$(editor['printWindow'][mH(0x1527,'sYdH')]['body'])['find'](mH(0x46dd,'1Ue]'))[mH(0x2a05,'hRv2')](function(y,z){var mI=mH;$(z)[mI(0xcb3,'*p#P')](mI(0x4436,'Jj0M'),!0x1);});let x=$(editor[mH(0x4159,'kGY9')]['document'][mH(0x47e5,'oKRq')])['find'](mH(0x3543,'Bbr@'))[mH(0x3698,'!Yg[')];$(editor[mH(0xf5,'*p#P')][mH(0xe2c,'@HIr')][mH(0x2134,'@HIr')])[mH(0x35b0,'V8^Q')](mH(0x1f8a,'[T9%'))[mH(0x33d5,'hK)l')](function(y,z){var mJ=mH;$(z)[mJ(0x4449,'^Syn')]('field[page=\x22pageNum\x22]')[mJ(0x33d5,'hK)l')](function(A,B){var mK=mJ;$(B)[mK(0x4c32,'xbuu')](y+0x1);}),$(z)['find'](mJ(0x19a7,'pMQs'))[mJ(0x2309,'H4NX')](function(A,B){var mL=mJ;$(B)[mL(0x113b,'c]Mq')](x);});}),$(mH(0xf71,'#ZU^'))[mH(0x2696,'^Syn')]('共'+x+'页');},u=function(){var mM=a0f;let z='',B=0x0,C=0x0;const E=editor[mM(0x454f,'!%Yf')][mM(0x34ef,'Q@hO')](mM(0x2e38,'IvBK')),F=editor[mM(0x718,'MDwy')][mM(0x138b,'lnIj')](mM(0x2d0e,'!%Yf')),G=editor[mM(0x54d,'V8^Q')][mM(0x3f87,'zx#%')]('_footer'),H=editor[mM(0x44c2,'oKRq')][mM(0x2d0d,'pMQs')](mM(0xb33,'(qw$'))[mM(0x1df2,'MDwy')]();B=l['getPageHeightPx']()-E['offsetHeight']-G[mM(0x1db,'*gI2')],C=E[mM(0x282c,'eu*B')]+B;let J=H['y']+C*editor[mM(0x4245,'xrMR')][mM(0x2814,'zbs!')],K=l[mM(0x16b2,'V8^Q')](),O=mM(0x1c7,'e*c8')+K[mM(0x2f0b,'*gI2')]+mM(0x4950,'#ZU^')+K['height']+mM(0x22c0,'XCXV')+K[mM(0x4365,'*ZKE')]+mM(0x3d2,'$(mV'),P=editor['$'](mM(0x4c47,'VTTT'))[0x0][mM(0x1c79,'eu*B')]+'\x0a',Q=''][mO(0xee6,'N!xX')]('');}());return z+=O+P+U+Q,function a7(a8){var mP=mM;for(let a9 of a8['childNodes'])if(0x3!=a9[mP(0x1a41,'hK)l')]){if(0x1==a9['nodeType']&&mP(0xa12,'xbuu')!=a9['id']){if(mP(0x1cde,'oKRq')!=a9[mP(0x1082,'$(mV')]){if(mP(0x1648,'Nf2)')==a9[mP(0x3b2c,'^d9X')]&&(a9['style'][mP(0x3384,'*p#P')]=a9['offsetWidth']+'px'),a9[mP(0x284,'eu*B')]()[mP(0x2970,'Jc8$')]>J){if(a9[mP(0x210a,'esU(')]['length']>0x0){if(mP(0x3bb5,'#ZU^')==a9[mP(0x3752,'hRv2')]){z+='';let aa=a6(a9);if(aa&&(z+=aa[mP(0x210d,'!%Yf')]),a9[mP(0x36fc,'NrJk')]&&(z+=a9[mP(0x4c72,'Jj0M')][mP(0x22be,'^d9X')],B-=a9['tHead'][mP(0x35d8,'oKRq')]),a9[mP(0x729,'hRv2')][mP(0x3bce,'eu*B')]>0x0){for(let ab of a9[mP(0x4438,'$(mV')])a7(ab);}a9['tHead']&&(B+=a9[mP(0x3c5a,'6YaW')][mP(0x35d8,'oKRq')]),z+=mP(0x46bd,'1Ue]');}else{if('TR'==a9[mP(0x13a1,'*ZKE')])z+=W(a9,J),z+=a4(a9),z+=Y(a9,J),C+=B,J=H['y']+C*editor['option']['scale'];else{let ac=a9[mP(0x3752,'hRv2')][mP(0x183d,'MDwy')]();z+='<'+ac+a5(a9)+'>',a7(a9),z+='';}}}else{if(a9['previousSibling']&&0x3==a9[mP(0x9f3,'xrMR')]['nodeType']){let ad=a0(a9[mP(0x2e51,'kGY9')]);ad[mP(0x2ad6,'kGY9')]()['bottom']>J&&(a9=ad);}else['P',mP(0x1d7d,'VTTT')][mP(0x1eda,'*gI2')](a9['tagName'])>=0x0&&a9[mP(0x835,'[T9%')][mP(0x284a,'XcPC')]>0x0&&(a9=a0(a9[mP(0x5e4,'hK)l')]));if(a9['getClientRects']()['length']>0x1){let ae=a9[mP(0x1b71,'xbuu')];a9=a1(a9,J),z+=a9[mP(0x32ea,'N!xX')],a9=a3(a9),z+=a4(a9),mP(0x3f6f,'NrJk')==ae&&(z+=a9[mP(0x2e4b,'[T9%')][mP(0xd23,'lmQg')]);}else a9=a3(a9),z+=a4(a9),z+=a9[mP(0x2f0f,'[T9%')];C+=B,J=H['y']+C*editor[mP(0x170b,'zx#%')][mP(0x2e24,'t#L0')];}}else'TR'==a9['tagName']?z+=W(a9,J):z+=V(a9);}else{let af=mP(0x2d29,'XcPC')+a5(a9)+'\x20src=\x22'+a9[mP(0x3365,'Nf2)')]()+'\x22/>';z+=af;}}}else z+=a9[mP(0x3f5d,'kGY9')];}(F),z+=mM(0x4080,'lnIj')+R+mM(0x684,'vGE1')+S,z=mM(0x36cc,'*gI2')+z+mM(0x9aa,'#ZU^'),z;function V(a8){var mQ=mM;if(0x0==a8[mQ(0x15a7,'sYdH')])return a8['outerHTML'];{let a9=[],aa=a8[mQ(0x28f7,'(qw$')][mQ(0x1636,'5WLj')]();a9['push']('<'+aa+'\x20'+a5(a8)+'>');for(let ab of a8[mQ(0x338,'xbuu')])0x3==ab['nodeType']?a9[mQ(0x706,'V8^Q')](ab[mQ(0xa84,'!%Yf')]):'CANVAS'==ab[mQ(0x32a8,'vGE1')]?a9['push'](mQ(0xa8f,'c]Mq')+a5(ab)+'\x20src=\x22'+ab[mQ(0xd6a,'#ZU^')]()+mQ(0x1647,'hK)l')):ab[mQ(0x21af,'*gI2')]>0x0?a9[mQ(0x4ea,'oKRq')](V(ab)):a9['push'](ab[mQ(0x4ef5,'vGE1')]);return a9[mQ(0x32a7,'VTTT')](''),a9['join']('');}}function W(a8,a9){var mR=mM;let aa=[],ab=a8[mR(0x1b6d,'IvBK')]();if(Math[mR(0x4d22,'Jc8$')](ab[mR(0x38ba,'xbuu')]-a9)/editor[mR(0x2ef8,'Jj0M')][mR(0x4379,'^d9X')]<0x14)return'';for(let ac of a8['children'])ac[mR(0x34ee,'e*c8')]()[mR(0x4087,'N!xX')]>a9?aa[mR(0x2d04,'kGY9')](X(ac,a9)):aa[mR(0x3f34,'#ZU^')](ac[mR(0x7d6,'zx#%')]);return mR(0x2e14,'MDwy')+a5(a8)+'>'+aa[mR(0x4706,'v^si')]('')+'';}function X(a8,a9){var mS=mM;let aa=[];for(let ac of a8[mS(0x4e7f,'Q@hO')]){0x3==ac[mS(0x48e,'Q@hO')]&&(ac=a0(ac));let ad=ac[mS(0x2126,'NrJk')]();if(ad[mS(0x2406,'c]Mq')]<=a9)aa[mS(0x4ea,'oKRq')](ac[mS(0x3f46,'lnIj')]);else{if(ad[mS(0x167c,'(qw$')]a9){if(!(ac[mS(0xf32,'Q@hO')]>0x0)){if(ac[mS(0x3d91,'hK)l')]()['length']>0x1){let ae=ac[mS(0x18fe,'Jj0M')],af=a1(ac,a9);mS(0x3c0,'lmQg')==ae?(aa[mS(0x2d04,'kGY9')](mS(0x154a,'@HIr')+a5(ac)+'>'),aa[mS(0x2a38,'lmQg')](af[mS(0x22be,'^d9X')]),aa[mS(0x444e,'Jc8$')](mS(0x1f55,'Jj0M'))):aa[mS(0x4b4d,'xbuu')](af[mS(0x3bc6,'@HIr')]);}break;}aa[mS(0x35ec,'UDz8')](X(ac,a9));}else{if(ad[mS(0x10a1,'6YaW')]>a9)break;}}}let ab=a8['tagName'];if('TD'==ab){let ag='';return a8[mS(0x4eda,'eu*B')]&&a8[mS(0x435c,'sYdH')]['getBoundingClientRect']()['y']'+aa['join']('')+mS(0x3e8c,'t#L0');}return'<'+ab+'\x20'+a5(a8)+'>'+aa['join']('')+'';}function Y(a8,a9){var mT=mM;let aa=[];for(let ae of a8[mT(0x39ed,'@HIr')])aa[mT(0x4107,'(qw$')](ae);let ab=0x0,ac=a8[mT(0x564,'IvBK')];for(;ac;){ab+=0x1;for(let af of ac[mT(0x1995,'XcPC')])af[mT(0x141d,'XcPC')]>0x1&&af[mT(0x154c,'!%Yf')]+af[mT(0x427c,'Ws11')]>a8[mT(0x331b,'oKRq')]&&(af[mT(0x49f9,'XcPC')]=af[mT(0x4f4b,'zx#%')]-ab,aa[mT(0x2d1f,'pMQs')](af));ac=ac[mT(0x3460,'NrJk')];}for(let ag=0x0;agaa[ah+0x1][mT(0x1e94,'XCXV')]){let ai=aa[ah];aa[ah]=aa[ah+0x1],aa[ah+0x1]=ai;}let ad=[];return aa[mT(0x3533,'xbuu')](aj=>{var mU=mT;ad[mU(0x196d,'hK)l')](Z(aj,a9));}),mT(0x59c,'NrJk')+function(aj){var mV=mT;let ak='';for(let al=0x0;al=0x0&&(an=an['replace'](mV(0x1fea,'eu*B'),'no-height')),ak+='\x20'+am+'=\x22'+an+'\x22';}return ak;}(a8)+'>'+ad[mT(0x489e,'IvBK')]('')+mT(0x3239,'zx#%');}function Z(a8,a9){var mW=mM;let aa=[],ab='';for(let ad of a8['childNodes']){0x3==ad['nodeType']&&(ad=a0(ad));let ae=ad[mW(0x418f,'!%Yf')]();ae[mW(0x476d,'t#L0')]<=a9||(ae[mW(0x4f4f,'NrJk')]a9?(ab=mW(0x1254,'Nf2)'),ad[mW(0x21c2,'Bbr@')]>0x0?aa['push'](Z(ad,a9)):aa[mW(0x196d,'hK)l')](ad[mW(0x2f0f,'[T9%')])):aa[mW(0x2117,'@HIr')](ad['outerHTML']));}let ac=a8[mW(0x2fb8,'VTTT')];return'TD'==ac?mW(0xa8a,'oKRq')+function(af){var mX=mW;let ag='';for(let ah=0x0;ah'+aa[mW(0x4624,'Q@hO')]('')+mW(0x3675,'lnIj'):'<'+ac+'\x20'+a5(a8)+'>'+aa['join']('')+'';}function a0(a8){var mY=mM;let a9=editor['document']['createElement'](mY(0x1bfd,'!%Yf'));return a9[mY(0x315a,'*gI2')]=a8[mY(0x3a89,'zx#%')],a8[mY(0x4dcb,'esU(')][mY(0x345d,'5WLj')](a9,a8),a8[mY(0xeb,'!Yg[')][mY(0x4a3e,'@HIr')](a8),a9;}function a1(a8,a9){var mZ=mM;let aa=a8[mZ(0x31e0,'e*c8')](),ab=0x0,ac=0x0,ad=0x0,ae=0x0,af=a8['textContent'];for(let ah of aa)ab+=ah['width'];for(let ai of aa){if(ai[mZ(0x3659,'!Yg[')]>a9){ae=Math['ceil'](af['length']*ac/ab);break;}ad+=0x1,ac+=ai[mZ(0x3b21,'$(mV')];}let ag=a2(a8,af,ae);for(;ag[mZ(0x240d,'5WLj')]()[mZ(0x3698,'!Yg[')]>ad&&ae>0x0;)$(ag['nextElementSibling'])['remove'](),ae--,ag=a2(a8,af,ae);return ag;}function a2(a8,a9,aa){var n0=mM;let ab=a9[n0(0x4f00,'!%Yf')](0x0,aa),ac=a9[n0(0x20ec,'(qw$')](aa);return n0(0x4892,'vGE1')==a8[n0(0x3b2c,'^d9X')]?(a8[n0(0xbfb,'IvBK')]='',a8['insertAdjacentHTML'](n0(0x6f9,'sYdH'),n0(0x9bd,'$(mV')+ab+n0(0x2811,'XCXV')),a8[n0(0xddb,'zx#%')](n0(0x45e9,'hRv2'),''+ac+n0(0x4b00,'IvBK')),a8['firstElementChild']):(a8[n0(0xa84,'!%Yf')]=ab,a8[n0(0xb0a,'5WLj')](n0(0x3c1b,'oKRq'),''+ac+''),a8);}function a3(a8){var n1=mM;if('TD'==a8[n1(0x133a,'XcPC')])return a8[n1(0x4122,'hRv2')];if(a8[n1(0xc66,'sYdH')])return a8;let a9=a8[n1(0x2e6c,'esU(')];for(;a9;){if('TD'==a9[n1(0x8fb,'[T9%')])return a3(a9);a9=a9[n1(0xf53,'lnIj')];}return a8;}function a4(a8){var n2=mM;let a9='',aa=[],ab=n2(0x2eb1,'Nf2)'),ac='',ad=a8[n2(0x316a,'eu*B')];for(;n2(0x14e,'XCXV')!=ad['id'];)aa[n2(0x2b2a,'XCXV')](ad),ab+='\x0a',ad=ad[n2(0x4162,'t#L0')];ab+='\x0a'+R+n2(0x1116,'UDz8');let ae=!0x0;return aa[n2(0x2215,'Ws11')](af=>{var n3=n2;let ag=a5(af);if('TABLE'==af[n3(0x2d1d,'6YaW')]){let ah='<'+af['tagName']+ag+'>',ai=a6(af);ai&&(ah+=ai[n3(0x22a3,'1Ue]')]),af[n3(0x2c7e,'*p#P')]&&(ah+=af[n3(0x2b64,'MDwy')]['outerHTML']),ac=ah+ac;}else{if(ae&&a8['previousElementSibling']&&(n3(0x2d2d,'sYdH')==af[n3(0x3485,'*p#P')]||'P'==af['tagName'])){let aj=af[n3(0x4e52,'VTTT')]();aj[n3(0x42ed,'xrMR')][n3(0x1478,'vGE1')]=n3(0x3954,'MDwy'),ag=a5(aj),ae=!0x1;}ac='<'+af[n3(0x3752,'hRv2')]+ag+'>'+ac;}}),ac=O+P+U+Q+ac,a9=ab+S+ac,a9;}function a5(a8){var n4=mM;let a9='';for(let aa=0x0;aa{var nn=a0f,j,k;j=[h(0x1dfa)],void 0x0===(k=function(l){var ne=a0f;function m(){}const p=new ResizeObserver(w=>{var n9=a0f;w[n9(0x1a93,'V8^Q')]>0x0&&w[0x0][n9(0x3e2d,'Q@hO')]&&w[0x0][n9(0x1511,'zbs!')][n9(0x44ba,'5WLj')]>0x0&&(m[n9(0x2ad4,'XCXV')](),m['show']());});function q(w){var na=a0f;let x=editor[na(0x1ade,'6YaW')][na(0x3b1f,'xrMR')](na(0xc1,'6YaW'));return x['textContent']=w[na(0x1a48,'oKRq')],w[na(0x4c93,'H4NX')][na(0x264,'NrJk')](x,w),w['parentNode'][na(0xd4b,'hK)l')](w),x;}function s(w,x){var nb=a0f,y=editor[nb(0xc8f,'Ws11')][nb(0x1669,'sYdH')](nb(0x32dc,'H4NX')),z=editor[nb(0x1755,'t#L0')][nb(0x1878,'XCXV')](nb(0x10f1,'!%Yf'));z[nb(0x1ea8,'oKRq')]=nb(0x4616,'sYdH')+editor[nb(0x453c,'^Syn')]['no']+x+editor['lang'][nb(0x1f68,'UDz8')]+nb(0x23ea,'eu*B'),z['classList'][nb(0x606,'VTTT')](nb(0x68d,'Jj0M')),z['style'][nb(0x30c3,'zx#%')]=w+'px',y[nb(0x182,'^Syn')](z);}function u(w,x){var nc=a0f;return(x['y']-w['y'])/editor['option'][nc(0x3b45,'zx#%')];}function v(w,x,y){var nd=a0f;let z=x[nd(0x34ee,'e*c8')]();return y?(z['bottom']-w['y'])/editor[nd(0x4041,'[T9%')]['scale']:(z['y']-w['y'])/editor[nd(0x3a95,'$(mV')][nd(0x37df,'*ZKE')];}return m[ne(0x2c62,'zbs!')]=function(){var nf=ne,w=editor['document'][nf(0x11a2,'VTTT')](nf(0x4638,'*gI2')),x=editor[nf(0x1527,'sYdH')]['getElementById'](nf(0x28ad,'#ZU^')),y=editor[nf(0xe2c,'@HIr')][nf(0x3464,'zbs!')](nf(0x1e63,'!%Yf'));w&&p[nf(0x43d9,'MDwy')](w,{'box':nf(0x2457,'MDwy')}),x&&p[nf(0x81f,'vGE1')](x,{'box':nf(0x4567,'VTTT')}),y&&p[nf(0x20b3,'t#L0')](y,{'box':nf(0x25d3,'H4NX')});},m['unbindResize']=function(){var ng=ne,w=editor[ng(0x1565,'^d9X')]['getElementById'](ng(0x1217,'NrJk')),x=editor[ng(0x908,'Jc8$')][ng(0x1ff0,'oKRq')](ng(0xe4a,'^d9X')),y=editor[ng(0xafc,'zx#%')][ng(0x4b3,'#ZU^')](ng(0x670,'sYdH'));w&&p[ng(0x3edb,'lmQg')](w),x&&p[ng(0x1bcb,'XCXV')](x),y&&p['unobserve'](y),m[ng(0xb00,'xrMR')]();},m[ne(0x109,'!%Yf')]=function(w){var nh=ne;editor['option'][nh(0x25f5,'oKRq')]=!editor[nh(0x1ca7,'Nf2)')]['showPageLine'],editor[nh(0x4773,'H4NX')]['showPageLine']?(m['show'](),m['bindResize'](),w[nh(0x1f80,'oKRq')][nh(0x6cc,'XCXV')](nh(0x4ccf,'[T9%')),editor[nh(0x39dc,'c]Mq')](nh(0x1ca3,'V8^Q'))):(m[nh(0x387c,'lnIj')](),m[nh(0x189d,'kGY9')](),w[nh(0x2191,'xrMR')]['remove'](nh(0x296,'#ZU^')),editor[nh(0x7c5,'sYdH')](nh(0x2477,'XcPC')));},m[ne(0x3ad7,'!%Yf')]=function(){var ni=ne;editor[ni(0x31e5,'[T9%')]['querySelectorAll'](ni(0x25f7,'esU('))['forEach'](w=>{var nj=ni;w[nj(0xf53,'lnIj')][nj(0x300a,'!Yg[')](w);});},m[ne(0x2336,'VTTT')]=function(){var nk=ne;if(0x0==editor['$'](nk(0x2eeb,'N!xX'))['length'])return;let w=0x0,x=0x1,y=0x0;const z=editor[nk(0x89d,'xrMR')][nk(0x70f,'Bbr@')](nk(0x195f,'vGE1')),A=editor[nk(0x4b2b,'(qw$')]['getElementById'](nk(0x2a59,'MDwy')),B=editor[nk(0x291c,'v^si')]['getElementById'](nk(0x18b9,'Nf2)')),C=editor[nk(0xafc,'zx#%')][nk(0x4b3,'#ZU^')]('_page')['getBoundingClientRect']();var D,E,F;w=l['getPageHeightPx']()-z[nk(0x38bc,'!Yg[')]-B[nk(0x188e,'xbuu')],D=v(C,z,!0x0),E=editor['document']['querySelector'](nk(0xddd,'XcPC')),(F=editor['document'][nk(0x13b5,'*gI2')](nk(0xd04,'^Syn')))[nk(0x3e78,'(qw$')]=nk(0x17c8,'hK)l')+editor[nk(0x181f,'$(mV')][nk(0x1836,'kGY9')]+nk(0x3f7e,'@HIr'),F['classList'][nk(0x4264,'lnIj')](nk(0x19a8,'Nf2)')),F[nk(0xbd6,'NrJk')]['top']=D+'px',E[nk(0xb6f,'lnIj')](F),y=z[nk(0x2803,'#ZU^')]+w;let G=C['y']+y*editor[nk(0x3346,'vGE1')]['scale'];!function H(I){var nl=nk;for(let J of I[nl(0x19f2,'XCXV')]){if(0x3==J[nl(0x458a,'!%Yf')]){if('TD'==I['tagName'])J=q(J);else{if(!(J[nl(0x501,'v^si')]>0x1f4))continue;J=q(J);}}else{if(0x1!=J[nl(0x156d,'[T9%')]||nl(0x1930,'Bbr@')==J['id'])continue;}if(J[nl(0x3cf8,'UDz8')]()[nl(0x1a01,'Ws11')]>G){if(J[nl(0x3286,'pMQs')]>0x0){if('TABLE'==J[nl(0x1b71,'xbuu')]){if(J['tHead']&&(w-=J[nl(0x2cac,'5WLj')][nl(0x188e,'xbuu')]),J[nl(0xe9,'pMQs')][nl(0x25c6,'lnIj')]>0x0){for(let K of J[nl(0xdab,'eu*B')])H(K);}J[nl(0x2ee7,'e*c8')]&&(w+=J[nl(0x2e0,'eu*B')][nl(0xffe,'NrJk')]);}else H(J);}else{if(J[nl(0x2911,'*p#P')]&&0x3==J[nl(0x3b33,'zbs!')][nl(0x48e,'Q@hO')]){let L=q(J[nl(0x3605,'oKRq')]);L[nl(0x2e87,'lnIj')]()[nl(0x126d,'Bbr@')]>G&&(J=L);}else['P','DIV']['indexOf'](J[nl(0x3eac,'IvBK')])>=0x0&&J['childNodes'][nl(0x14b8,'kGY9')]>0x0&&(J=q(J[nl(0x241c,'Bbr@')]));if('TD'==J[nl(0x4d0c,'NrJk')][nl(0xf5d,'esU(')]&&J['offsetTop']<=0x14)y=v(C,J[nl(0xdb2,'Q@hO')]),s(y,x),x++,y+=w,G=C['y']+y*editor[nl(0x2fa0,'esU(')]['scale'];else{for(let M of J['getClientRects']())M['bottom']>G&&(y=u(C,M),s(y,x),x++,y+=w,G=C['y']+y*editor['option'][nl(0x1055,'Q@hO')]);}}}}}(A),function(I,J){var nm=nk,K=editor[nm(0x4ba8,'IvBK')][nm(0x783,'kGY9')](nm(0x299f,'eu*B')),L=editor['document'][nm(0x4d7c,'Jj0M')](nm(0x168c,'^d9X'));L['innerHTML']=nm(0x3002,'5WLj')+editor[nm(0x2640,'H4NX')]['no']+J+editor[nm(0x35d,'5WLj')]['pages']+nm(0x1dae,'Q@hO')+editor[nm(0x1315,'Q@hO')][nm(0x4764,'vGE1')]+nm(0x173a,'Bbr@'),L[nm(0x30e9,'v^si')][nm(0x24fa,'H4NX')](nm(0x32c8,'[T9%')),L[nm(0x3ab8,'eu*B')][nm(0x1f4d,'!Yg[')]=I+'px',K[nm(0xca9,'xrMR')](L);}(v(C,B),x),$(nk(0x3c59,'xrMR'))[nk(0x4779,'@HIr')](editor[nk(0x139e,'c]Mq')]['total']+x+editor[nk(0xa76,'Jc8$')][nk(0x41b4,'!%Yf')]);},m[ne(0x30f0,'NrJk')]=v,m;}[nn(0xfbf,'VTTT')](g,j))||(f['exports']=k);},0xa24:(f,g)=>{var nv=a0f,h;void 0x0===(h=function(){var np=a0f,j=null,k=null,l=null;function m(){}function o(p){var no=a0f;p['target']==l?(l[no(0x1b16,'lmQg')][no(0x336f,'UDz8')]='none',j['style']['display']='block',k[no(0x1d60,'c]Mq')][no(0x2147,'!Yg[')]=no(0x437d,'5WLj'),editor['$'](no(0x4a33,'#ZU^'))[no(0x6d0,'Bbr@')](no(0x1085,'kGY9'))[no(0x943,'V8^Q')](no(0x4a28,'t#L0'),!0x0),editor['$'](no(0x479b,'sYdH'))[no(0x220c,'*gI2')](no(0x3aa3,'^Syn'))[no(0x3d6f,'XcPC')]('contenteditable',!0x1),editor['$'](no(0x180d,'zbs!'))[no(0x35fd,'pMQs')](no(0x29f,'lnIj'))[no(0x1eae,'MDwy')](no(0xb7,'5WLj'),!0x1)):p[no(0x3d95,'zx#%')]==j?(l['style'][no(0x2147,'!Yg[')]=no(0xe9d,'esU('),j[no(0xe33,'*p#P')][no(0x484e,'N!xX')]=no(0x4df1,'!Yg['),k[no(0x53b,'oKRq')][no(0x3c32,'*gI2')]=no(0xbaa,'e*c8'),editor['$'](no(0x164a,'!%Yf'))['addClass']('mask')[no(0x2a2f,'xbuu')]('contenteditable',!0x1),editor['$'](no(0xcbf,'Ws11'))['removeClass'](no(0x269e,'pMQs'))[no(0x294a,'Nf2)')](no(0x143e,'hRv2'),!0x0),editor['$'](no(0x3057,'lmQg'))[no(0x578,'Nf2)')](no(0x357f,'XCXV'))['attr']('contenteditable',!0x1)):p[no(0x4457,'c]Mq')]==k&&(l[no(0x33d4,'lnIj')][no(0x3bb7,'sYdH')]='block',j[no(0x32c3,'Jc8$')][no(0x4070,'t#L0')]='block',k['style'][no(0x336f,'UDz8')]=no(0x38ce,'Jj0M'),editor['$']('#_body')[no(0x1767,'sYdH')]('mask')[no(0x4284,'*ZKE')](no(0xd00,'@HIr'),!0x1),editor['$'](no(0x547,'V8^Q'))[no(0x1a38,'xrMR')](no(0xdde,'Ws11'))[no(0x2a4c,'hRv2')](no(0x2ec,'XCXV'),!0x1),editor['$'](no(0x6d4,'H4NX'))[no(0x3ee4,'lnIj')]('mask')[no(0x4433,'XCXV')]('contenteditable',!0x0));}return m[np(0x2716,'lmQg')]=function(){var nq=np;editor[nq(0x48c,'Nf2)')][nq(0xfd2,'v^si')]('#_mask')[nq(0x3986,'zbs!')](p=>{var nr=nq;p['parentElement'][nr(0x1b15,'[T9%')](p);}),editor['$']('#_header')[nq(0x3fd2,'Jj0M')](nq(0x4e9d,'c]Mq'))['attr']('contenteditable',!0x1)[nq(0x100f,'!Yg[')](nq(0x4cd3,'c]Mq'),nq(0x11d4,'1Ue]')),editor['$'](nq(0x36a3,'VTTT'))[nq(0x194b,'xrMR')]('mask')[nq(0x549,'c]Mq')]('contenteditable',!0x1)[nq(0xa2b,'Jj0M')](nq(0x4a26,'^d9X'),nq(0x2c70,'zbs!'));},m[np(0x2362,'Bbr@')]=function(){var ns=np;j=m['addPageMask'](ns(0x294d,'pMQs')),k=m[ns(0x30a2,'vGE1')]('footer'),l=m[ns(0x19f9,'esU(')]();},m[np(0x4b1d,'*ZKE')]=function(p){var nt=np,q=editor[nt(0x4ba8,'IvBK')]['querySelector']('#_header');nt(0x2f6c,'Nf2)')==p&&(q=editor['document'][nt(0x261c,'NrJk')](nt(0x1322,'pMQs'))),q[nt(0x1556,'6YaW')][nt(0x31d7,'NrJk')](nt(0x106a,'IvBK')),q[nt(0x9d8,'esU(')](nt(0x14ca,'*gI2'),!0x1),q[nt(0x20e2,'hRv2')]['position']='relative';var s=editor[nt(0x19d4,'N!xX')][nt(0x360a,'!Yg[')](nt(0x356e,'IvBK'));s['id']=nt(0x499b,'sYdH'),s[nt(0x18b1,'Nf2)')][nt(0x4488,'oKRq')](nt(0x4674,'xrMR')),s[nt(0x320f,'t#L0')][nt(0x4598,'v^si')]=nt(0x2b4,'XcPC'),s[nt(0x3bd7,'IvBK')][nt(0x15d7,'*p#P')]=nt(0x27f4,'lnIj'),s[nt(0x3225,'$(mV')][nt(0x3ed6,'c]Mq')]=0x0,s[nt(0x4444,'esU(')][nt(0xeef,'^Syn')]=0x0,s[nt(0x320f,'t#L0')][nt(0x2007,'#ZU^')]=q[nt(0x3014,'5WLj')]+'px',s['style'][nt(0x207c,'V8^Q')]=q[nt(0x3d16,'@HIr')]+'px';var u=editor[nt(0x908,'Jc8$')][nt(0x20a5,'XcPC')](nt(0x46e1,'$(mV'));u[nt(0x2641,'zbs!')][nt(0x15d7,'*p#P')]=nt(0x3130,'v^si'),'footer'==p?u[nt(0x2b51,'kGY9')][nt(0x41af,'vGE1')]=0x0:u[nt(0x1d60,'c]Mq')][nt(0xbdc,'*gI2')]=0x0,u[nt(0xb7a,'6YaW')]['left']='calc('+q[nt(0x2baf,'V8^Q')][nt(0xf6d,'Nf2)')]+nt(0x44b5,'6YaW'),u[nt(0x436d,'UDz8')]['width']='20px',u[nt(0x71e,'^Syn')][nt(0x280c,'^Syn')]=nt(0x2e9b,'XCXV'),u[nt(0x3a2d,'zx#%')][nt(0x7da,'5WLj')]=nt(0x4577,'^d9X'),u[nt(0x3220,'Bbr@')][nt(0x3dcf,'V8^Q')]=nt(0x18c7,'Jj0M'),u[nt(0x1b16,'lmQg')][nt(0x2913,'zbs!')]=nt(0x2071,'Q@hO'),nt(0x14ba,'e*c8')==p?(u['style']['borderTopWidth']='1px',u[nt(0x26a0,'*ZKE')][nt(0x4ccc,'6YaW')]='solid',u[nt(0x6fe,'Nf2)')][nt(0x4716,'e*c8')]=nt(0x47c4,'!Yg[')):(u[nt(0x3ab8,'eu*B')][nt(0x2fd8,'Nf2)')]=nt(0x4f01,'vGE1'),u[nt(0x3225,'$(mV')][nt(0x26e8,'zx#%')]=nt(0x3c83,'^d9X'),u['style'][nt(0xbfd,'@HIr')]=nt(0x596,'*ZKE'));var v=editor[nt(0x4d08,'e*c8')][nt(0xc69,'t#L0')](nt(0x2dba,'*gI2'));return v['style'][nt(0x3567,'hK)l')]=nt(0x451e,'Jc8$'),nt(0x30ae,'MDwy')==p?v['style'][nt(0x4f4f,'NrJk')]=0x0:v[nt(0x3ab8,'eu*B')][nt(0x19ce,'!%Yf')]=0x0,v[nt(0x2641,'zbs!')][nt(0x590,'6YaW')]=nt(0x1bb4,'t#L0')+q['style'][nt(0x2c2f,'6YaW')]+'\x20-\x2022px)',v['style'][nt(0x2fb7,'6YaW')]=nt(0x4559,'t#L0'),v[nt(0x1e2a,'5WLj')][nt(0x1fea,'eu*B')]=nt(0x386b,'hRv2'),v[nt(0x6cd,'!Yg[')][nt(0x29af,'N!xX')]=nt(0x3609,'xbuu'),v[nt(0x20e2,'hRv2')][nt(0xc9e,'$(mV')]='solid',v[nt(0x320f,'t#L0')][nt(0x3c08,'UDz8')]=nt(0x135e,'esU('),nt(0x1f77,'H4NX')==p?(v[nt(0x3dcd,'^d9X')]['borderTopWidth']=nt(0x2e3b,'eu*B'),v[nt(0x4afb,'*gI2')][nt(0x1006,'zx#%')]=nt(0x30f1,'NrJk'),v['style'][nt(0x2331,'xbuu')]=nt(0x1edb,'MDwy')):(v[nt(0x2907,'Q@hO')][nt(0x1c0c,'Jj0M')]='1px',v['style'][nt(0x4e38,'lnIj')]=nt(0x3519,'eu*B'),v['style'][nt(0x3319,'oKRq')]=nt(0x3551,'V8^Q')),s[nt(0xb6f,'lnIj')](u),s[nt(0x1f05,'NrJk')](v),q['appendChild'](s),s[nt(0x2357,'H4NX')](nt(0x3f7d,'oKRq'),o),s;},m[np(0x3c21,'!%Yf')]=function(){var nu=np,p=editor[nu(0x387b,'H4NX')]['querySelector']('#_body');p[nu(0x48de,'N!xX')][nu(0x47f3,'lnIj')](nu(0x182b,'@HIr')),p[nu(0x19e,'c]Mq')](nu(0xb7,'5WLj'),!0x0);var q=editor[nu(0xb50,'pMQs')][nu(0xc1b,'$(mV')](nu(0x1f6e,'MDwy'));return q['id']='_mask',q[nu(0x3056,'lnIj')]['add']('mask-div'),q[nu(0xe33,'*p#P')][nu(0x34a5,'6YaW')]='absolute',q[nu(0x364f,'XcPC')][nu(0x4692,'V8^Q')]=nu(0x5be,'V8^Q'),q[nu(0x3dcd,'^d9X')]['top']=0x0,q[nu(0x33d4,'lnIj')][nu(0x1cc,'*p#P')]=0x0,q['style'][nu(0x3360,'xrMR')]=p[nu(0x28d3,'e*c8')]+'px',q['style']['height']=p[nu(0x3376,'^d9X')]+'px',p['appendChild'](q),q[nu(0x32ac,'1Ue]')](nu(0x2279,'*ZKE'),o),q;},m;}[nv(0xa96,'e*c8')](g,[]))||(f[nv(0x4b4e,'Nf2)')]=h);},0x24c2:(f,g,h)=>{var nF=a0f,j,k;j=[h(0x1dfa)],void 0x0===(k=function(l){var nx=a0f;function m(){var nw=a0f;this['render'](),this[nw(0xa80,'XcPC')]();}return m[nx(0x3ae7,'Q@hO')]=function(){var ny=nx,o=ny(0x17b8,'UDz8')+editor['lang']['pageSetting']+ny(0xe70,'*p#P')+editor[ny(0x127c,'sYdH')][ny(0x3f9d,'kGY9')]+ny(0x289f,'sYdH')+editor[ny(0x2640,'H4NX')][ny(0x411d,'1Ue]')]+ny(0x9eb,'H4NX')+editor[ny(0x4d57,'XcPC')][ny(0x490,'IvBK')]+ny(0x118c,'^d9X')+editor[ny(0xa76,'Jc8$')][ny(0xa31,'#ZU^')]+ny(0x337f,'lnIj')+l[ny(0x3e4c,'c]Mq')]()+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

      \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

      \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[ny(0x1957,'^d9X')][ny(0x3f85,'oKRq')]+ny(0x211d,'N!xX')+editor['lang'][ny(0xa10,'5WLj')]+ny(0x25e5,'lmQg')+editor[ny(0x150,'pMQs')]['left']+ny(0xb9d,'v^si')+editor[ny(0x453c,'^Syn')][ny(0x2151,'Nf2)')]+':cm\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

      \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+editor[ny(0x127c,'sYdH')]['bottom']+ny(0x383f,'1Ue]')+editor['lang'][ny(0x3789,'#ZU^')]+ny(0x4d98,'pMQs')+editor[ny(0x388d,'6YaW')][ny(0x4a89,'*p#P')]+ny(0x1bf,'kGY9')+editor[ny(0x181f,'$(mV')]['cancel']+ny(0x2d94,'#ZU^')+editor[ny(0x224d,'*gI2')]['confirm']+ny(0x3e96,'xbuu');$(ny(0x8d3,'MDwy'))[ny(0x23aa,'V8^Q')](o),ny(0x17a4,'lnIj')==editor['$'](ny(0x16e3,'V8^Q'))['attr'](ny(0x37fb,'*ZKE'))?$(ny(0x3a77,'hRv2'))[ny(0x2b71,'hRv2')](ny(0x286e,'c]Mq'),'checked'):$(ny(0x2352,'N!xX'))[ny(0x210f,'vGE1')](ny(0x3fa,'MDwy'),'checked');var p=editor['$'](ny(0xc60,'sYdH'))[ny(0x42fb,'e*c8')](ny(0x380f,'Ws11'));$(ny(0x3d98,'*ZKE')+p+'\x22]')[ny(0x1a0c,'Nf2)')](ny(0xccb,'N!xX'),!0x0),$(ny(0x13c5,'1Ue]'))[ny(0x423b,'IvBK')](editor['$'](ny(0x2d77,'(qw$'))[0x0]['style']['paddingTop'][ny(0x307e,'esU(')]('cm','')),$(ny(0x43a2,'vGE1'))[ny(0xde2,'c]Mq')](editor['$']('#_body')[0x0][ny(0x1ed1,'e*c8')][ny(0x10a9,'esU(')]['replace']('cm','')),$(ny(0x346c,'kGY9'))['val'](editor['$'](ny(0x85a,'@HIr'))[0x0]['style'][ny(0x2987,'*ZKE')][ny(0x4d50,'hRv2')]('cm','')),$(ny(0x3d11,'xrMR'))['val'](editor['$'](ny(0x25ed,'*p#P'))[0x0][ny(0x1f39,'Jj0M')]['paddingBottom'][ny(0x2bf4,'^d9X')]('cm','')),$(ny(0x806,'pMQs'))[ny(0xd50,'v^si')](editor['$'](ny(0x2a81,'eu*B'))[0x0][ny(0x28b1,'MDwy')][ny(0x3aa9,'Ws11')][ny(0x16f5,'N!xX')]('cm','')),$(ny(0x4c0e,'VTTT'))[ny(0x1cc7,'Bbr@')](editor['$'](ny(0x3c24,'!%Yf'))[0x0]['style'][ny(0x419,'5WLj')][ny(0x2f56,'IvBK')]('cm',''));},m[nx(0x4409,'NrJk')]=function(){var nA=nx;$('#_btn-close-panel')['click'](function(o){var nz=a0f;$(nz(0x7b2,'*p#P'))['remove']();}),$(nA(0x4a5d,'Ws11'))[nA(0x3cfc,'VTTT')](function(o){var nB=nA;$(nB(0x1a08,'XcPC'))['remove']();}),$('#_page-margin')[nA(0x3f75,'XcPC')](function(){var nC=nA,o=$(nC(0x1f90,'UDz8'))[nC(0x2d53,'UDz8')]();$(nC(0x1ec0,'MDwy'))[nC(0x2527,'*gI2')](o/0x2),$(nC(0x2b18,'IvBK'))[nC(0x1731,'XCXV')](o/0x2),$('#margin-left')[nC(0x116b,'V8^Q')](o),$(nC(0x1e6c,'sYdH'))[nC(0x333f,'Q@hO')](o),$(nC(0x354a,'xrMR'))[nC(0x4256,'t#L0')](o),$('#footer-distance')[nC(0x10ae,'xrMR')](o);}),$(nA(0x314c,'zx#%'))[nA(0x1a3a,'Bbr@')](function(p){var nD=nA;let q=$(nD(0xdb5,'Jj0M'))[nD(0x4ce,'zx#%')](),s=$(nD(0x6ec,'IvBK'))[nD(0x3cae,'XcPC')]();editor['$'](nD(0x3004,'oKRq'))[nD(0x26e3,'H4NX')](nD(0x788,'H4NX'),q),editor['$'](nD(0x39c6,'Q@hO'))['attr'](nD(0x4a5c,'VTTT'),s);let u=l[nD(0x323f,'zbs!')]();editor['$'](nD(0x22f,'UDz8'))[0x0][nD(0x364f,'XcPC')][nD(0x47df,'Ws11')]=u[nD(0x2a54,'V8^Q')]+'mm',editor['$'](nD(0x1ad3,'v^si'))[0x0]['style'][nD(0x36d4,'sYdH')]=u[nD(0xc2d,'NrJk')]+'mm',editor['$']('#_header')[0x0][nD(0x1e2a,'5WLj')][nD(0x35a1,'v^si')]=$('#margin-top')[nD(0x4256,'t#L0')]()+'cm',editor['$']('#_header')[0x0][nD(0x4afb,'*gI2')][nD(0x212b,'6YaW')]=$(nD(0x4390,'XCXV'))[nD(0x4bbf,'^Syn')]()+'cm',editor['$'](nD(0x4820,'hK)l'))[0x0]['style'][nD(0x1d58,'Bbr@')]=$(nD(0x1363,'!%Yf'))['val']()+'cm',editor['$'](nD(0x19cd,'UDz8'))[0x0]['style'][nD(0x142a,'!%Yf')]=$('#header-distance')[nD(0x3acd,'eu*B')]()+'cm',editor['$'](nD(0x4a1,'[T9%'))[0x0][nD(0x2baf,'V8^Q')][nD(0x3221,'N!xX')]=$(nD(0xf69,'XcPC'))[nD(0x2855,'#ZU^')]()+'cm',editor['$'](nD(0x38a0,'*ZKE'))[0x0]['style'][nD(0x107d,'xbuu')]=$('#margin-right')[nD(0x4acc,'!Yg[')]()+'cm',editor['$'](nD(0x30cc,'1Ue]'))[0x0][nD(0xefc,'H4NX')]['paddingLeft']=$(nD(0x4390,'XCXV'))[nD(0x2f59,'pMQs')]()+'cm',editor['$'](nD(0x545,'esU('))[0x0]['style'][nD(0x1f94,'XcPC')]=$(nD(0x3631,'Q@hO'))[nD(0x1ff,'oKRq')]()+'cm',editor['$'](nD(0x310d,'Ws11'))[0x0][nD(0x53b,'oKRq')]['paddingBottom']=$(nD(0x1509,'hK)l'))[nD(0x2d53,'UDz8')]()+'cm',editor['$'](nD(0x11fe,'6YaW'))[0x0][nD(0x3225,'$(mV')]['minHeight']=$('#footer-distance')[nD(0x4acc,'!Yg[')]()+'cm';let v=nD(0x1483,'Q@hO')+u['height']+nD(0x494d,'xbuu')+editor['$'](nD(0x1b38,'1Ue]'))[0x0][nD(0x4afb,'*gI2')]['minHeight']+nD(0x84e,'c]Mq')+editor['$'](nD(0x3553,'hRv2'))[0x0][nD(0x20e2,'hRv2')][nD(0x2e8f,'XCXV')]+nD(0xc84,'lmQg')+editor['$'](nD(0x1af2,'lnIj'))[0x0]['style']['paddingTop']+nD(0x264a,'1Ue]')+editor['$'](nD(0x19dd,'^d9X'))[0x0]['style'][nD(0x9f7,'MDwy')]+')';editor['$']('#_body')[0x0]['style'][nD(0x3a90,'oKRq')]=v,editor[nD(0x1ef3,'*p#P')](editor[nD(0x2bc6,'MDwy')]['mode']),$(nD(0x8a5,'[T9%'))[nD(0x34ff,'Ws11')]();});},m[nx(0xf9c,'xbuu')]=function(){var nE=nx;m[nE(0x1b27,'xbuu')](),m[nE(0x3013,'$(mV')]();},m;}[nF(0xa96,'e*c8')](g,j))||(f[nF(0x260a,'esU(')]=k);},0x2f2:(f,g)=>{var nK=a0f,h;void 0x0===(h=function(){const i=function(k){var nG=a0f;return $(nG(0x4376,'^d9X'))[nG(0x1734,'XcPC')](k);};var j=null;return{'showProp':function(k){var nH=a0f,l;l=[nH(0x4955,'e*c8'),'','',nH(0x3042,'XcPC'),nH(0x4c83,'c]Mq'),nH(0xb51,'e*c8'),'',nH(0x1551,'*p#P'),nH(0x2dc6,'!%Yf'),nH(0xa4c,'XcPC'),nH(0x7c0,'1Ue]'),nH(0x6ff,'6YaW'),'',nH(0x3d5,'NrJk')][nH(0x4421,'XcPC')](''),$(nH(0x33ae,'xbuu'))[nH(0xaa2,'Bbr@')](l),i('input,select')[nH(0x3991,'hK)l')](function(m){var nI=nH;$(j)['attr']('id',i(nI(0x1bf7,'hRv2'))[nI(0x2d53,'UDz8')]()),$(j)[nI(0xbe,'Ws11')]('format',i('#code-type')[nI(0x240,'(qw$')]()),$(j)['attr']('code',i('#code-value')['val']()),editor[nI(0x376b,'xrMR')]['rendbarcode']();}),function(m){var nJ=nH;j=m,i(nJ(0x1e62,'eu*B'))[nJ(0x240,'(qw$')](m[nJ(0x3db9,'VTTT')]('id')),i(nJ(0x360,'Nf2)'))['val'](m[nJ(0x2351,'#ZU^')](nJ(0x278,'zx#%'))),i(nJ(0x5d8,'pMQs'))[nJ(0x1ff,'oKRq')](m[nJ(0x4ae7,'*gI2')](nJ(0x2be0,'V8^Q')));}(k);}};}[nK(0x472d,'zbs!')](g,[]))||(f[nK(0x486e,'e*c8')]=h);},0x1f4:(f,g)=>{var nR=a0f,h;void 0x0===(h=function(){const i=function(k){var nL=a0f;return $(nL(0x2be3,'Bbr@'))[nL(0x41d2,'pMQs')](k);};var j;return{'showProp':function(k){var nM=a0f,l;l=[nM(0xae4,'sYdH'),nM(0x1b80,'6YaW'),'',nM(0x2f1c,'e*c8'),nM(0xfae,'MDwy'),'',nM(0x3b25,'v^si'),nM(0x4e0b,'xrMR'),'',nM(0x44bd,'[T9%'),nM(0x1347,'!%Yf'),nM(0x1e8c,'NrJk'),nM(0x3684,'lmQg'),nM(0x3a04,'Q@hO')][nM(0x485c,'*p#P')](''),$(nM(0x1a92,'$(mV'))[nM(0x24d5,'sYdH')](l),i(nM(0x1aaf,'c]Mq'))[nM(0x3f75,'XcPC')](function(m){!function(p){var nN=a0f;let q=i(nN(0x145e,'Jj0M'))['val']();q?(j['id']=i(nN(0x260,'sYdH'))[nN(0x13fd,'xbuu')](),j[nN(0x276f,'Bbr@')](nN(0x711,'XCXV'),i(nN(0x145e,'Jj0M'))['val']())):(j[nN(0x439b,'esU(')]('id'),j[nN(0x2283,'vGE1')](nN(0x27ae,'hK)l')));let s=i(nN(0x17a3,'Bbr@'))[nN(0x2527,'*gI2')](),u=[],v=i(nN(0x49d2,'#ZU^'))[nN(0x44d1,'vGE1')]();s[nN(0x27af,'V8^Q')]('\x0a')[nN(0x1f10,'#ZU^')]((w,x)=>{var nO=nN;let y=q+x;u[nO(0x4258,'*ZKE')](nO(0x1891,'hRv2')+y+nO(0x323c,'hRv2')+y+nO(0x2d9b,'^d9X')+w+'\x22>'),u[nO(0xb5d,'zbs!')](nO(0xdac,'sYdH')+y+'\x22>'+w+''),'1'==v&&u[nO(0x449,'sYdH')]('
      ');}),$(j)[nN(0x317d,'VTTT')](u['join'](''));}();}),function(m){var nP=nM;j=m,i('#_id')['val'](m[nP(0x515,'UDz8')]('id'));let o=[],p=!0x1;$(m)['children']()[nP(0x3375,'eu*B')](function(q,s){var nQ=nP;'BR'==s[nQ(0x3752,'hRv2')]&&(p=!0x0),nQ(0x45c6,'XcPC')==s[nQ(0x3fc6,'zx#%')]&&o[nQ(0x3c55,'zx#%')](s[nQ(0x448f,'^d9X')]);}),p?i(nP(0xc13,'oKRq'))[nP(0x17b6,'^d9X')](0x1):i(nP(0x364e,'v^si'))[nP(0xe40,'6YaW')](0x0),i(nP(0x2f1b,'^Syn'))[nP(0x449a,'*ZKE')](o[nP(0x3a83,'5WLj')]('\x0a'));}(k);}};}['apply'](g,[]))||(f[nR(0x484a,'xbuu')]=h);},0x1873:(f,g)=>{var nW=a0f,h;void 0x0===(h=function(){const i=function(k){var nS=a0f;return $(nS(0x3691,'XcPC'))['find'](k);};var j=null;return{'showProp':function(k){var nT=a0f;$(nT(0x49a8,'5WLj'))[nT(0x9b2,'5WLj')]('\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20文档标签\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20可编辑\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20'),i(nT(0x1d19,'sYdH'))[nT(0x4a55,'zx#%')](function(l){var nU=nT;i('#_id')[nU(0x2d53,'UDz8')]()?(j['id']=i(nU(0x23b5,'^d9X'))[nU(0x26ef,'@HIr')](),j['setAttribute'](nU(0x1a8f,'N!xX'),i(nU(0x237e,'!%Yf'))['val']())):(j[nU(0x38c5,'Ws11')]('id'),j['removeAttribute'](nU(0x248a,'*ZKE'))),j[nU(0x463,'UDz8')]=i(nU(0x4801,'vGE1'))[nU(0x116b,'V8^Q')](),j[nU(0x4065,'^Syn')](nU(0x437e,'$(mV'),i(nU(0x1bb3,'Jc8$'))[nU(0x3adb,'Jj0M')](nU(0x44da,'esU('))),i('#_tag')[nU(0x119b,'xrMR')](nU(0x4319,'sYdH'))?j[nU(0x3f2c,'$(mV')](nU(0xe50,'H4NX'),nU(0xb05,'xrMR')):j['removeAttribute'](nU(0x149b,'@HIr')),editor[nU(0x2cc8,'[T9%')][nU(0x34fd,'N!xX')]();}),function(l){var nV=nT;j=l,i(nV(0x2e23,'^Syn'))[nV(0x4198,'lmQg')](j[nV(0x1ad5,'lmQg')]),i(nV(0x247c,'1Ue]'))[nV(0x4ad3,'N!xX')](j['id']),i(nV(0x3b23,'*p#P'))['val'](j[nV(0x56a,'^d9X')]),i(nV(0x3156,'V8^Q'))['prop'](nV(0x13fc,'IvBK'),nV(0xda5,'hRv2')==$(j)[nV(0x26e3,'H4NX')](nV(0x1128,'eu*B'))),i(nV(0x246b,'c]Mq'))[nV(0x153,'(qw$')](nV(0x1f01,'e*c8'),nV(0xb3d,'[T9%')==$(j)[nV(0xb45,'5WLj')](nV(0xb7,'5WLj')));}(k);}};}[nW(0x1b7e,'^d9X')](g,[]))||(f[nW(0x299c,'(qw$')]=h);},0xf65:(f,g)=>{var o3=a0f,h;void 0x0===(h=function(){const i=function(k){var nX=a0f;return $(nX(0x4e06,'!%Yf'))[nX(0x331c,'IvBK')](k);};var j=null;return{'showProp':function(k){var nY=a0f,l;l=['',nY(0x4224,'sYdH'),'',nY(0x3912,'pMQs'),nY(0x1909,'XcPC'),nY(0x185,'e*c8'),nY(0x2d1,'H4NX'),nY(0x1a8d,'6YaW'),nY(0x2e49,'H4NX'),nY(0xbf7,'IvBK'),nY(0x1a8e,'(qw$'),nY(0x387a,'pMQs'),nY(0x2f68,'$(mV'),nY(0x4e3,'Nf2)'),nY(0x2dc9,'#ZU^'),'必须输入',nY(0x39e8,'N!xX'),nY(0x2915,'UDz8'),'',nY(0x4b4c,'e*c8'),nY(0x2023,'IvBK'),nY(0x1a7,'sYdH'),nY(0x460b,'MDwy'),nY(0x1ee6,'v^si'),nY(0x2dfd,'VTTT'),nY(0x26d,'*ZKE'),nY(0x1748,'e*c8'),nY(0xb14,'hRv2'),'',nY(0x25df,'1Ue]')][nY(0x107e,'Jj0M')](''),$(nY(0x2b86,'lmQg'))[nY(0x4b79,'xrMR')](l),i(nY(0x2fe3,'*gI2'))[nY(0xb16,'^Syn')](function(m){var nZ=nY;j&&(j['id']=i('#_name')['val'](),j[nZ(0x30ec,'xrMR')](nZ(0x44b4,'1Ue]'),j['id']),j['setAttribute'](nZ(0x3886,'hRv2'),i(nZ(0x1633,'5WLj'))['val']()),j[nZ(0x475a,'6YaW')]('validate',i('#_input_validate')[nZ(0x3e82,'5WLj')](nZ(0x5e3,'xbuu'))),j['setAttribute'](nZ(0x2d2b,'H4NX'),i(nZ(0x4e44,'vGE1'))[nZ(0x4460,'UDz8')](nZ(0x924,'N!xX'))),i(nZ(0x1d12,'hRv2'))[nZ(0x449a,'*ZKE')]()?(j[nZ(0x453a,'$(mV')]=i('#_content')['val'](),$(j)[nZ(0x6d0,'Bbr@')](nZ(0x1990,'Ws11'))):(j[nZ(0x4b84,'Nf2)')]=i(nZ(0x248e,'esU('))[nZ(0xc34,'hRv2')](),$(j)[nZ(0x421b,'N!xX')](nZ(0x2c8d,'!%Yf'))),j[nZ(0xc51,'(qw$')](nZ(0x331d,'v^si'),i(nZ(0x21e5,'MDwy'))[nZ(0x39d8,'lnIj')]()),j['dataset'][nZ(0x223b,'^d9X')]=i(nZ(0x3e12,'XCXV'))[nZ(0x2427,'Jc8$')](),i(nZ(0x2fb,'VTTT'))['val']()>0x0?(j[nZ(0xbd6,'NrJk')][nZ(0x3983,'esU(')]=nZ(0x224b,'XcPC'),j[nZ(0x15ca,'#ZU^')][nZ(0x1d6b,'hK)l')]=i('#_min-width')[nZ(0x333f,'Q@hO')]()+'px'):(j[nZ(0x1a56,'v^si')][nZ(0x2201,'#ZU^')]='',j['style'][nZ(0x327d,'hRv2')]=''),j[nZ(0x6fe,'Nf2)')][nZ(0x923,'pMQs')]=$('input[name=\x27_text-align\x27]:checked')[nZ(0xeeb,'Ws11')](),$(j)[nZ(0xcc0,'#ZU^')](nZ(0x319c,'Ws11')),setTimeout(function(){var o0=nZ;$(j)[o0(0x21de,'H4NX')](o0(0x3fba,'esU('));},0x1f4));}),function(m){var o1=nY;j=m,i(o1(0x6a7,'!%Yf'))[o1(0xe40,'6YaW')](j['id']),i(o1(0x2858,'lmQg'))[o1(0x4434,'!%Yf')](j[o1(0x45a7,'e*c8')]('title')),$(j)['hasClass'](o1(0x11e0,'MDwy'))?i(o1(0x4083,'^d9X'))[o1(0x3acd,'eu*B')](''):i('#_content')[o1(0x13fd,'xbuu')](j['textContent']),i(o1(0x2c13,'N!xX'))[o1(0x1ea1,'zx#%')](o1(0x3ff3,'zx#%'),o1(0x4b9d,'^d9X')==$(j)[o1(0x2a2f,'xbuu')](o1(0x4c6e,'zbs!'))),i(o1(0x1c6c,'zbs!'))['prop'](o1(0x45b,'vGE1'),'true'==$(j)[o1(0x3980,'NrJk')](o1(0x14b4,'XcPC'))),i('#_data-url')[o1(0x4434,'!%Yf')](j[o1(0x3762,'hRv2')](o1(0xc67,'XCXV'))),i(o1(0x3e12,'XCXV'))[o1(0x3acd,'eu*B')](j[o1(0x2a07,'c]Mq')][o1(0x39ef,'*ZKE')]?j['dataset'][o1(0x17e6,'eu*B')]:0x0),i('#_min-width')[o1(0xde2,'c]Mq')](j[o1(0x15ca,'#ZU^')][o1(0x327d,'hRv2')]?j[o1(0x2b51,'kGY9')]['minWidth'][o1(0x1965,'xbuu')]('px',''):''),$(o1(0x3aeb,'[T9%'))[o1(0x372,'zx#%')]((o,p)=>{var o2=o1;p[o2(0x20fc,'Bbr@')]==j[o2(0x30c2,'sYdH')][o2(0x1358,'#ZU^')]?p[o2(0x43b4,'@HIr')]=!0x0:p[o2(0x435,'(qw$')]=!0x1;});}(k);}};}[o3(0x24cd,'[T9%')](g,[]))||(f[o3(0x27f8,'5WLj')]=h);},0xef3:(f,g)=>{var oa=a0f,h;void 0x0===(h=function(){const i=function(k){var o4=a0f;return $(o4(0x2b58,'NrJk'))[o4(0x30f,'MDwy')](k);};var j=null;return{'showProp':function(k){var o5=a0f,l;l=[o5(0x13fa,'xrMR'),o5(0x137e,'t#L0'),o5(0xde6,'eu*B'),'','',o5(0x2097,'lnIj'),o5(0x27d9,'IvBK'),o5(0x322f,'kGY9'),'',o5(0x2f9d,'MDwy'),o5(0x40a2,'esU('),'yyyy年MM月',o5(0x48f8,'!%Yf'),o5(0x1e9e,'zx#%'),o5(0x4d13,'Jj0M'),o5(0x37e2,'xbuu'),o5(0x3a04,'Q@hO'),'',o5(0x2598,'^Syn'),o5(0x1efc,'MDwy'),'必须输入',o5(0x2e01,'Jc8$'),'',o5(0x254a,'UDz8'),'宽度',o5(0x41f5,'oKRq'),'','',o5(0x1ee6,'v^si'),o5(0x308a,'6YaW'),'居右',o5(0x3ff5,'*p#P'),o5(0x3249,'$(mV'),'',o5(0x2d1,'H4NX')]['join'](''),$(o5(0x4ded,'MDwy'))[o5(0x3402,'zbs!')](l),i(o5(0x440f,'pMQs'))[o5(0x2a06,'*ZKE')](function(m){var o6=o5;j&&(j['id']=i(o6(0x3588,'t#L0'))[o6(0xc34,'hRv2')](),j[o6(0x360b,'oKRq')](o6(0x32aa,'kGY9'),i(o6(0x2a1c,'zx#%'))[o6(0x4f19,'kGY9')]()),j[o6(0x483,'#ZU^')](o6(0x14cb,'[T9%'),i(o6(0x2d66,'lnIj'))[o6(0x4bbf,'^Syn')]()),$(j)[o6(0x4bf0,'e*c8')](o6(0x46a4,'sYdH'))?i(o6(0x1f23,'1Ue]'))[o6(0x1c82,'e*c8')](''):i(o6(0x543,'6YaW'))[o6(0x2855,'#ZU^')](j[o6(0x448f,'^d9X')]),j[o6(0x434d,'^Syn')][o6(0x4585,'6YaW')]=i(o6(0xd87,'!%Yf'))[o6(0x41eb,'H4NX')](),j[o6(0x1318,'sYdH')](o6(0x40fa,'*gI2'),i(o6(0xbce,'pMQs'))[o6(0x560,'MDwy')](o6(0x3e1e,'XCXV'))),j[o6(0x1344,'N!xX')]('format',i(o6(0x61c,'^Syn'))[o6(0x3acd,'eu*B')]()),i(o6(0x3dd6,'^Syn'))['prop']('checked')?j['removeAttribute']('readonly'):j[o6(0x360b,'oKRq')]('readonly',!0x0),i(o6(0x3181,'zx#%'))[o6(0x13fd,'xbuu')]()>0x0?(j[o6(0x321b,'1Ue]')][o6(0x4070,'t#L0')]=o6(0x4019,'xrMR'),j['style'][o6(0x3513,'^Syn')]=i(o6(0xba3,'V8^Q'))[o6(0x116b,'V8^Q')]()+'px'):(j['style'][o6(0x2f1e,'xrMR')]='',j[o6(0xe33,'*p#P')][o6(0x2bc3,'*ZKE')]=''),j[o6(0x20e2,'hRv2')][o6(0x1cd5,'sYdH')]=$(o6(0x2b37,'*gI2'))[o6(0x1ff,'oKRq')](),$(j)[o6(0x230a,'xbuu')](o6(0x3606,'hRv2')),setTimeout(function(){var o7=o6;$(j)[o7(0x2c3f,'[T9%')](o7(0xa5f,'Nf2)'));},0x1f4));}),function(m){var o8=o5;j=m,i(o8(0x1369,'eu*B'))['val'](j[o8(0x4106,'*p#P')](o8(0x1272,'Nf2)'))),i(o8(0x1f1e,'@HIr'))[o8(0x423b,'IvBK')](j[o8(0x45a4,'$(mV')]('title')),$(j)[o8(0x2b9,'xbuu')](o8(0x24c7,'H4NX'))?i('#_content')[o8(0x2f59,'pMQs')](''):i(o8(0x1726,'#ZU^'))[o8(0x4434,'!%Yf')](j['textContent']),i(o8(0x120a,'Nf2)'))[o8(0x2855,'#ZU^')](j[o8(0x4e82,'1Ue]')][o8(0x366f,'*gI2')]),i(o8(0x1f7d,'Jj0M'))[o8(0x1b0d,'#ZU^')](o8(0x4c8d,'*ZKE'),o8(0x355e,'5WLj')==$(j)[o8(0x4a0e,'6YaW')]('validate')),i('#_editable')['prop'](o8(0x36dd,'Jc8$'),!$(j)[o8(0x3604,'zbs!')]('readonly')),i(o8(0x4cab,'Jc8$'))[o8(0xbe7,'*p#P')](j['getAttribute'](o8(0x3475,'*p#P'))),i(o8(0x2fb,'VTTT'))[o8(0x1355,'$(mV')](j[o8(0xefc,'H4NX')]['minWidth']?j[o8(0x6fe,'Nf2)')][o8(0x489b,'!%Yf')][o8(0x2f56,'IvBK')]('px',''):''),$('input[name=\x27_text-align\x27]')['each']((o,p)=>{var o9=o8;p[o9(0x4beb,'zx#%')]==j['style'][o9(0x944,'vGE1')]?p[o9(0x4da1,'!%Yf')]=!0x0:p[o9(0x43b4,'@HIr')]=!0x1;});}(k);}};}[oa(0x2c33,'hRv2')](g,[]))||(f[oa(0x484a,'xbuu')]=h);},0x208f:(f,g)=>{var oq=a0f,h;void 0x0===(h=function(){var on=a0f,j=null;function k(){}const l=function(w){var ob=a0f;return $(ob(0x1113,'eu*B'))[ob(0x10e7,'lmQg')](w);};function m(w,x,y,z,A){var oc=a0f;let B=editor[oc(0x291c,'v^si')][oc(0x3e8b,'H4NX')](w[oc(0x1578,'V8^Q')][oc(0xe7d,'Q@hO')],w[oc(0xac8,'^d9X')]['clientY']-0x37);var C=editor['contentWindow'][oc(0x47fb,'c]Mq')]();C[oc(0x27d8,'N!xX')]();var D=editor[oc(0x48d3,'!Yg[')][oc(0x3f41,'Nf2)')]();D['selectNodeContents'](B),D['collapse'](),C[oc(0x46c1,'Ws11')](D),u(0x0,0x0,y[0x0]);}function p(w){var od=a0f;let x=w;for(;x[od(0x24dd,'N!xX')]();)x=x[od(0x30a5,'Bbr@')]();return x;}function q(w,x,y){var oe=a0f;if(y[oe(0x41cb,'@HIr')])l(oe(0x46e4,'^d9X'))[oe(0x47ff,'pMQs')]();else{if('list'==y['type']){let z=[oe(0x53c,'esU('),oe(0x27dc,'Jj0M')+(y['name']?y[oe(0xd88,'e*c8')]:'无')+oe(0x9d1,'Nf2)'),oe(0x16d6,'IvBK')+y[oe(0x1142,'6YaW')]+'',oe(0x539,'*ZKE')+(y[oe(0x124e,'@HIr')]?y['code']:'无')+'',oe(0x4392,'eu*B')][oe(0xee6,'N!xX')]('');l('#items')[oe(0x317d,'VTTT')](z)[oe(0x1316,'kGY9')]();let A=p(y),B=A['itemUrl']+oe(0x45e6,'xbuu')+A[oe(0x272e,'5WLj')]+oe(0x1bc5,'!Yg[')+y[oe(0x2df8,'1Ue]')];$[oe(0x4e39,'*ZKE')](B,function(C){var og=oe;let D=[];C&&C['forEach'](E=>{var of=a0f;D[of(0x179f,'c]Mq')](E[of(0x3c51,'esU(')]+':'+E[of(0x49ae,'zx#%')]);}),l(og(0x3931,'Bbr@'))['val'](D[og(0xff3,'xrMR')]('\x0a'));});}else{let C='文本';'DT'==y['type']?C='时间':'DA'==y[oe(0x59d,'*ZKE')]?C='日期':'D'==y[oe(0x18f3,'pMQs')]&&(C='数值');let D=[''+C+oe(0x430d,'^Syn'),''+(y[oe(0x44b4,'1Ue]')]?y[oe(0x1272,'Nf2)')]:'无')+oe(0x2e75,'5WLj'),oe(0x20a6,'Jj0M')+y[oe(0x2986,'e*c8')]+'',oe(0x4483,'6YaW')+(y[oe(0x3446,'eu*B')]?y[oe(0x1201,'Q@hO')]:'无')+'']['join']('');l(oe(0x181,'Jj0M'))[oe(0xdf7,'eu*B')](D)[oe(0x4da2,'1Ue]')]();}}}function u(w,x,y){var oh=a0f;if(oh(0xdb9,'#ZU^')==y[oh(0x3eb7,'^d9X')]){let z=p(y),A=z['itemUrl']+oh(0x398d,'kGY9')+z[oh(0x3729,'N!xX')]+oh(0x1b1a,'UDz8')+y[oh(0x3c81,'esU(')];$[oh(0x2615,'zx#%')](A,function(B,C){var oi=oh;editor[oi(0x15dc,'N!xX')][oi(0x429a,'1Ue]')](y,B,',');});}else editor[oh(0x3305,'H4NX')][oh(0x3114,'esU(')](y);}function v(w){var oj=a0f;if(0xd==w['keyCode']){let y=l(oj(0x2b04,'*gI2'))[oj(0xd50,'v^si')]();if(''==y[oj(0x15e5,'N!xX')]())j[oj(0x3b3e,'*ZKE')](j['getNodesByFilter'](function(z){return!0x0;})),j[oj(0x16ef,'Jc8$')](!0x1),j[oj(0xbef,'sYdH')](oj(0x10a6,'lnIj'),0x0)[oj(0x4f5b,'$(mV')](z=>{var ok=oj;j[ok(0x71f,'XCXV')](z,!0x0,!0x1,!0x1);});else{var x=[];j['hideNodes'](j[oj(0x425a,'Q@hO')](function(z){return!0x0;})),j[oj(0x425a,'Q@hO')](function(z){var ol=oj;return!(!z[ol(0x47d0,'pMQs')]||-0x1==z['title'][ol(0x1750,'oKRq')](y)||(x=x['concat'](z['getPath']()),0x0));}),j[oj(0x40f,'6YaW')](x),j[oj(0x43c6,'NrJk')](!0x0);}}}return k['render']=function(){var om=a0f,w=[om(0x5e7,'Jj0M'),'
    ',om(0x19b1,'hK)l')][om(0x209e,'V8^Q')]('');$(om(0x4881,'6YaW'))['html'](w);},k[on(0x2ead,'Nf2)')]=function(){var oo=on,w={'async':{'enable':!0x0,'url':(y,z)=>z[oo(0x125e,'hK)l')],'type':oo(0x1abd,'$(mV'),'autoParam':[oo(0x25f1,'IvBK')]},'data':{'key':{'name':'title'}},'edit':{'enable':!0x0,'drag':{'isCopy':!0x0,'isMove':!0x1,'prev':!0x1,'next':!0x1,'inner':!0x1},'showRemoveBtn':!0x1,'showRenameBtn':!0x1},'view':{'dblClickExpand':!0x1,'showLine':!0x0,'selectedMulti':!0x1,'showIcon':!0x0},'callback':{'beforeDrag':function(y,z){var op=oo;return!z[0x0][op(0xb27,'oKRq')];},'onDrop':m,'onClick':q,'onDblClick':u}};let x=[{'type':'symptoms','title':'体征','isParent':!0x0,'treeUrl':'/dict','itemUrl':oo(0x1b1d,'eu*B')},{'type':'signs','title':'症状','isParent':!0x0,'treeUrl':oo(0x4df,'xrMR'),'itemUrl':oo(0x2cd0,'hRv2')},{'type':oo(0x4afd,'^d9X'),'title':'卫生信息数据元','isParent':!0x0,'treeUrl':oo(0x4968,'#ZU^'),'itemUrl':'/dictitem'},{'type':oo(0x150c,'hRv2'),'title':oo(0x3714,'sYdH'),'isParent':!0x0,'treeUrl':oo(0x256d,'V8^Q'),'itemUrl':'/dictitem'},{'type':oo(0x3b32,'c]Mq'),'title':oo(0x3878,'H4NX'),'isParent':!0x0,'treeUrl':'/insutance','itemUrl':oo(0x3bc1,'^Syn')},{'type':oo(0x3f8b,'@HIr'),'title':oo(0x3db6,'Jc8$'),'isParent':!0x0,'treeUrl':oo(0x4947,'[T9%'),'itemUrl':oo(0x19bf,'oKRq')}];editor[oo(0x4773,'H4NX')][oo(0x4e48,'!Yg[')]&&(x=editor[oo(0x2fa0,'esU(')][oo(0x3970,'*ZKE')]),j=$['fn'][oo(0x1504,'lmQg')][oo(0x18b2,'Ws11')](l('#dictTree'),w,x),l('#keyword')['keydown'](v);},k;}[oq(0x2176,'!%Yf')](g,[]))||(f[oq(0x41d9,'*p#P')]=h);},0x151d:(f,g)=>{var oz=a0f,h;void 0x0===(h=function(){const i=function(k){var or=a0f;return $(or(0x1c9e,'sYdH'))[or(0xbc0,'hK)l')](k);};var j=null;return{'showProp':function(k){var os=a0f;$('#_proertyContain')[os(0x2e68,'*gI2')]('\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20必须输入\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20可编辑\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20当选择项为时,
    显示标识为的元素\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20宽度\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 像素\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20居左\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20居中\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20居右\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'),i(os(0x2343,'xrMR'))[os(0x13ea,'[T9%')](function(l){!function(m){var ot=a0f;if(j){j['id']=i('#_name')['val'](),j[ot(0x28e6,'*gI2')](ot(0x2791,'@HIr'),i(ot(0x2807,'^Syn'))['val']()),j['setAttribute'](ot(0x47d0,'pMQs'),i(ot(0x3c3d,'Nf2)'))[ot(0x41eb,'H4NX')]()),i(ot(0x1b36,'5WLj'))[ot(0x1355,'$(mV')]()?(j[ot(0x13ab,'XCXV')]=i('#_content')[ot(0x2427,'Jc8$')](),$(j)[ot(0x2c3f,'[T9%')]('blank')):(j[ot(0x11e4,'c]Mq')]=i(ot(0x156c,'(qw$'))[ot(0x1731,'XCXV')](),$(j)[ot(0x421b,'N!xX')]('blank')),j[ot(0x3ef2,'esU(')][ot(0x987,'Nf2)')]=i(ot(0x37bd,'5WLj'))[ot(0x39d8,'lnIj')](),j[ot(0x28b,'@HIr')]['expression']=i(ot(0x11a1,'*ZKE'))[ot(0x32fe,'Nf2)')](),j[ot(0x2027,'Jc8$')]('event',i('#_input_event')[ot(0x465c,'zbs!')]()),i(ot(0x1559,'hK)l'))[ot(0x1c4b,'lmQg')]('checked')?j[ot(0x47f4,'!%Yf')](ot(0x976,'*gI2')):j[ot(0x4889,'!Yg[')]('readonly',!0x0),j['setAttribute'](ot(0x217a,'$(mV'),i('#_multi')['prop']('checked')),j[ot(0x63f,'lnIj')](ot(0xb40,'Nf2)'),i(ot(0x3664,'$(mV'))[ot(0x3260,'kGY9')](ot(0x286e,'c]Mq')));let o=i('#_list')[ot(0x454e,'hK)l')]();if(o){let p=[];o[ot(0x3f13,'^Syn')]('\x0a')[ot(0x4807,'*gI2')](function(q){var ou=ot;if(q&&q[ou(0x1c60,'*p#P')]>0x0){var r=q[ou(0x2eec,'pMQs')](':');0x1==r[ou(0x2b23,'NrJk')]?p[p[ou(0xfdb,'(qw$')]]={'text':r[0x0]}:0x2==r[ou(0x1724,'#ZU^')]&&(p[p[ou(0xbf6,'zx#%')]]={'value':r[0x0],'text':r[0x1]});}}),j[ot(0x24d7,'vGE1')][ot(0x333e,'1Ue]')]=JSON[ot(0x5fe,'oKRq')](p);}j[ot(0x15eb,'$(mV')][ot(0x12ff,'kGY9')]=i(ot(0x2708,'[T9%'))[ot(0x44d1,'vGE1')](),j[ot(0x1bf2,'Nf2)')][ot(0x1c6e,'esU(')]=i(ot(0x4620,'pMQs'))['val'](),i(ot(0x33c1,'Jc8$'))[ot(0xd50,'v^si')]()>0x0?(j[ot(0xbd6,'NrJk')]['display']=ot(0x4019,'xrMR'),j[ot(0x4c92,'[T9%')][ot(0x1d4,'lmQg')]=i(ot(0x1846,'^Syn'))['val']()+'px'):(j['style'][ot(0x798,'v^si')]='',j[ot(0x2e40,'N!xX')][ot(0x312a,'*p#P')]=''),j[ot(0x15fb,'!%Yf')][ot(0x2890,'xrMR')]=$(ot(0x3805,'t#L0'))['val'](),$(j)['addClass'](ot(0x3fba,'esU(')),setTimeout(function(){var ov=ot;$(j)[ov(0x82f,'UDz8')](ov(0x48b9,'5WLj'));},0x1f4);}}();}),function(l){var ow=os;j=l,i(ow(0x2999,'vGE1'))[ow(0x2855,'#ZU^')](j[ow(0xc10,'v^si')](ow(0x27ae,'hK)l'))),i(ow(0x4814,'Q@hO'))[ow(0xeeb,'Ws11')](j[ow(0x4ec4,'!%Yf')](ow(0x2a0f,'*gI2'))),$(j)[ow(0x575,'sYdH')]('blank')?i('#_content')[ow(0x41eb,'H4NX')](''):i('#_content')[ow(0x44d1,'vGE1')](j[ow(0x49df,'Jj0M')]),i(ow(0x1129,'6YaW'))[ow(0x4ce,'zx#%')](j[ow(0x441e,'xrMR')][ow(0x238,'*ZKE')]),i(ow(0x8bd,'kGY9'))[ow(0x4198,'lmQg')](j[ow(0x2154,'[T9%')][ow(0x2611,'hK)l')]),i(ow(0x311d,'xrMR'))[ow(0x4f0,'e*c8')](ow(0x22c4,'Bbr@'),!$(j)[ow(0x2a2f,'xbuu')](ow(0x1c1e,'(qw$'))),i(ow(0x2f22,'xrMR'))['prop']('checked','true'==$(j)[ow(0xcb3,'*p#P')]('multi')),i(ow(0x342e,'pMQs'))[ow(0x29b,'^Syn')]('checked',ow(0x3c6a,'lnIj')==$(j)['attr'](ow(0x1c13,'XcPC')));let m='';j[ow(0x441e,'xrMR')]['list']&&JSON[ow(0x4715,'lmQg')](j[ow(0x15eb,'$(mV')][ow(0x44e9,'e*c8')])[ow(0x4e1c,'hK)l')](function(o){var ox=ow;o[ox(0x8a4,'Jc8$')]?m+=o['value']+':'+o['text']+'\x0a':m+=o['text']+'\x0a';}),i(ow(0x4e54,'oKRq'))[ow(0x454e,'hK)l')](m),i(ow(0xf3f,'XcPC'))[ow(0x2427,'Jc8$')](j[ow(0x551,'t#L0')][ow(0x27e6,'c]Mq')]?j['dataset'][ow(0x270a,'XCXV')]:''),i('#_showId')[ow(0xd50,'v^si')](j[ow(0x19dc,'N!xX')]['showId']?j[ow(0x27b1,'(qw$')]['showId']:''),i('#_min-width')[ow(0x423b,'IvBK')](j[ow(0x2b09,'Ws11')][ow(0x32ba,'zx#%')]?j[ow(0x3225,'$(mV')][ow(0x1d4,'lmQg')][ow(0x1ee2,'NrJk')]('px',''):''),$(ow(0x3abd,'NrJk'))[ow(0x142d,'Ws11')]((o,p)=>{var oy=ow;p[oy(0x2133,'*ZKE')]==j['style'][oy(0x18d9,'H4NX')]?p[oy(0x924,'N!xX')]=!0x0:p['checked']=!0x1;});}(k);}};}[oz(0x36b7,'*gI2')](g,[]))||(f[oz(0x4c61,'zbs!')]=h);},0xeb0:(f,g)=>{var oE=a0f,h;void 0x0===(h=function(){const i=function(j){var oA=a0f;return $(oA(0x4a18,'XCXV'))['find'](j);};return{'showProp':function(j){var oB=a0f,k;k=[oB(0x4921,'eu*B'),oB(0x17e8,'1Ue]')+editor[oB(0x4d57,'XcPC')]['fileName']+':',oB(0x5b7,'t#L0')+editor['lang']['author']+':
    ',oB(0x647,'xrMR')+editor[oB(0x1957,'^d9X')]['lastModified']+':
    '+editor['document'][oB(0x46de,'xrMR')]+'
    ','
    ');};return{'render':function(){var oK=a0f;let P=[oK(0x1692,'6YaW'),'',oK(0x383d,'Jc8$'),oK(0x4a42,'IvBK'),oK(0x2be8,'Q@hO'),oK(0x14eb,'H4NX'),oK(0x387a,'pMQs'),'',oK(0x3ce1,'xrMR')][oK(0x1910,'esU(')]('\x0a');$(oK(0xcc2,'Ws11'))[oK(0x4c32,'xbuu')](P);},'bindEvent':function(){var oL=a0f;$(editor[oL(0x4a9a,'hK)l')][oL(0x4188,'#ZU^')])['on']('click',K),J(oL(0xdf8,'kGY9'))['click'](function(P){var oM=oL;I['style'][oM(0x13f4,'eu*B')]='',J(oM(0x32cd,'$(mV'))[oM(0x829,'!%Yf')](''),J('#_style-contain')['hide']();}),$(oL(0x658,'*ZKE'))['on'](oL(0x29f0,'vGE1'),oL(0x1ed0,'sYdH'),function(P){var oN=oL;if(P['target']['input']){let Q=editor[oN(0x162e,'$(mV')][oN(0x301b,'vGE1')]();Q[oN(0x4d2e,'^Syn')]();let R=editor[oN(0x89d,'xrMR')][oN(0x3cee,'vGE1')]();R[oN(0x3838,'1Ue]')](P[oN(0x2924,'H4NX')][oN(0x3a96,'*p#P')]),Q[oN(0x9d5,'[T9%')](R),editor['$'](oN(0x4a33,'#ZU^'))[oN(0x2f3b,'N!xX')](),L(P['target'][oN(0x2f3a,'[T9%')],!0x0);}}),$('#_path')['on'](oL(0x2735,'!%Yf'),oL(0x1922,'6YaW'),function(P){var oO=oL;let Q=P[oO(0x1384,'*p#P')][oO(0x6fb,'pMQs')][oO(0x2988,'$(mV')];$(Q)[oO(0x28ba,'Nf2)')]()[oO(0x5bd,'!%Yf')]>0x0?($(Q)[oO(0x4af3,'Jj0M')]()[oO(0x6b6,'eu*B')](),$(P[oO(0x312b,'oKRq')]['target'])[oO(0x4ce0,'@HIr')](),$(P['target'])[oO(0x62d,'*ZKE')]()):($(Q)[oO(0x34ff,'Ws11')](),$(P[oO(0x1e8e,'$(mV')][oO(0x3363,'!%Yf')])['remove'](),$(P[oO(0x880,'hK)l')])[oO(0x35c9,'t#L0')]());}),$('#_path')['on'](oL(0x1336,'eu*B'),oL(0x427b,'V8^Q'),function(P){var oP=oL;let Q=$(oP(0x4b81,'v^si'))['find'](oP(0xe49,'zx#%'));P['target'][oP(0x4f57,'H4NX')]&&(Q[oP(0x1ce2,'t#L0')](oP(0x1ddb,'V8^Q'),P[oP(0x3443,'*ZKE')][oP(0x386,'IvBK')]+P['target'][oP(0x151b,'!%Yf')]-0xf),$(P[oP(0x1cf8,'IvBK')][oP(0x2382,'Jc8$')])[oP(0x4875,'c]Mq')](oP(0x1f0,'#ZU^'),oP(0x14fe,'c]Mq')),['P','DIV','SPAN','LABEL',oP(0x3f6f,'NrJk'),'IMG','GROUP',oP(0x205c,'hK)l')]['indexOf'](P[oP(0x3d95,'zx#%')]['input'][oP(0x1ad5,'lmQg')])>-0x1&&[oP(0x346b,'xrMR'),oP(0x1434,'V8^Q'),oP(0x4ba9,'Q@hO'),oP(0x4823,'oKRq')][oP(0x414a,'#ZU^')](P[oP(0x2ea2,'Jj0M')][oP(0xd26,'!Yg[')]['id'])<0x0?(Q[oP(0x222a,'XCXV')](),Q[0x0][oP(0x3b08,'v^si')]=P[oP(0x44be,'6YaW')]):Q[oP(0x27fb,'IvBK')]());}),$(oL(0x31a6,'lnIj'))['on']('mouseout',oL(0x330e,'5WLj'),function(P){var oQ=oL;P[oQ(0xa97,'(qw$')][oQ(0x2cdb,'^d9X')]&&$(P[oQ(0x3fea,'Q@hO')][oQ(0x1da0,'XCXV')])[oQ(0x2c6,'oKRq')](oQ(0x4a26,'^d9X'),'');});},'showProp':L};}[oR(0x3852,'*ZKE')](g,j))||(f[oR(0x2bd7,'zx#%')]=k);},0x44e:(f,g)=>{var oX=a0f,h;void 0x0===(h=function(){const i=function(k){return $('#_radioGroup-property')['find'](k);};var j=null;return{'showProp':function(k){var oS=a0f,l;l=['','',oS(0x3128,'XcPC'),oS(0xdf9,'sYdH'),oS(0xa68,'hK)l'),oS(0x2720,'t#L0'),oS(0x1cb9,'sYdH'),oS(0x2901,'zx#%'),oS(0x2453,'pMQs'),'左右排列','上下排列','',oS(0x471d,'[T9%'),'']['join'](''),$(oS(0x31ff,'xrMR'))[oS(0x3769,'v^si')](l),i(oS(0x2703,'e*c8'))[oS(0x2142,'MDwy')](function(m){!function(p){var oT=a0f;let q=i(oT(0x1764,'H4NX'))['val']();$(j)[oT(0x188d,'kGY9')]('id',q);let s=i(oT(0x3d6e,'V8^Q'))[oT(0x1355,'$(mV')](),u=[],v=i(oT(0xbdf,'V8^Q'))[oT(0x10ae,'xrMR')]();s[oT(0x37bb,'^d9X')]('\x0a')[oT(0x4807,'*gI2')]((w,x)=>{var oU=oT;let y=q+x;u[oU(0x3ba1,'Q@hO')](oU(0x280f,'oKRq')+y+'\x22\x20name=\x22'+q+oU(0x3e66,'t#L0')+w+'\x22>'),u['push'](oU(0x8fd,'^d9X')+y+'\x22>'+w+oU(0xfd5,'e*c8')),'1'==v&&u[oU(0x32a7,'VTTT')](oU(0x4e8c,'[T9%'));}),$(j)[oT(0x113b,'c]Mq')](u[oT(0x4d0e,'oKRq')](''));}();}),function(m){var oV=oS;j=m,i('#radio-id')[oV(0x3212,'sYdH')](m[oV(0x2269,'!Yg[')]('id'));let o=[];$(m)[oV(0x19e2,'^d9X')]()[oV(0x3a6c,'N!xX')](function(p,q){var oW=oV;oW(0x40bc,'xbuu')==q[oW(0x45c9,'Bbr@')]&&o[oW(0x2117,'@HIr')](q[oW(0x3f5d,'kGY9')]);}),i(oV(0x23ae,'eu*B'))[oV(0x13fd,'xbuu')](o[oV(0x3a83,'5WLj')]('\x0a'));}(k);}};}[oX(0x472d,'zbs!')](g,[]))||(f[oX(0x3a6d,'UDz8')]=h);},0x1b03:(f,g)=>{var p8=a0f,h;void 0x0===(h=function(){var p4=a0f,j=null;function k(){}const l=function(s){var oY=a0f;return $(oY(0x267a,'Jj0M'))['find'](s);};function m(u,v,w,x,y){var oZ=a0f;let z=editor[oZ(0x908,'Jc8$')][oZ(0x48d1,'esU(')](u['originalEvent'][oZ(0x872,'#ZU^')],u[oZ(0x42c6,'UDz8')][oZ(0x26a4,'@HIr')]-0x37);var A=editor[oZ(0x4b1b,'Jj0M')][oZ(0xd7f,'hRv2')]();A[oZ(0x1b87,'zbs!')]();var B=editor[oZ(0x1c02,'vGE1')][oZ(0x3cee,'vGE1')]();B[oZ(0x301a,'kGY9')](z),B[oZ(0x2b26,'H4NX')](),A[oZ(0x3d66,'MDwy')](B),p(0x0,0x0,w[0x0]);}function p(s,u,v){var p0=a0f;let w=editor['option'][p0(0x1f5f,'#ZU^')][p0(0x2cdf,'c]Mq')]+p0(0x33a7,'*ZKE')+v[p0(0x1ec7,'hRv2')];$[p0(0x2e08,'c]Mq')](w,function(x,y){var p1=p0;editor[p1(0x44c4,'MDwy')]['insertFieldByDic'](x[0x0]);});}function q(s){var p2=a0f;if(0xd==s[p2(0xc3c,'H4NX')]){let v=l(p2(0x29fe,'XcPC'))[p2(0x116b,'V8^Q')]();if(''==v['trim']())j[p2(0xf6c,'!%Yf')](j['getNodesByFilter'](function(w){return!0x0;})),j[p2(0x17e5,'N!xX')](!0x1);else{var u=[];j['hideNodes'](j[p2(0x44e7,'!%Yf')](function(w){return!0x0;})),j[p2(0x1a98,'*ZKE')](function(w){var p3=p2;return!(!w[p3(0x454b,'!%Yf')]||-0x1==w['name'][p3(0x4d7,'1Ue]')](v)||(u=u['concat'](w[p3(0x31c3,'xbuu')]()),0x0));}),j[p2(0x2997,'XcPC')](u),j['expandAll'](!0x0);}}}return k[p4(0x1487,'esU(')]=function(){var p5=p4,s=[p5(0x4d71,'^d9X'),p5(0x4eac,'lmQg')][p5(0x129e,'H4NX')]('');$(p5(0x3548,'[T9%'))[p5(0x49ec,'UDz8')](s);},k['bindEvent']=function(){var p6=p4,s={'edit':{'enable':!0x0,'drag':{'isCopy':!0x0,'isMove':!0x1,'prev':!0x1,'next':!0x1,'inner':!0x1},'showRemoveBtn':!0x1,'showRenameBtn':!0x1},'view':{'dblClickExpand':!0x1,'showLine':!0x0,'selectedMulti':!0x1,'showIcon':!0x0},'callback':{'onDrop':m,'onDblClick':p}};$[p6(0x1b2d,'Q@hO')](editor['option'][p6(0x3da4,'6YaW')]['treeUrl'],function(u,v){var p7=p6;j=$['fn'][p7(0x37de,'XCXV')][p7(0x3da0,'kGY9')](l('#referTree'),s,u);}),l('#keyword')[p6(0x47a9,'*p#P')](q);},k;}[p8(0x33c5,'xrMR')](g,[]))||(f[p8(0x4817,'xrMR')]=h);},0xdb5:(f,g)=>{var pd=a0f,h;void 0x0===(h=function(){const i=function(k){var p9=a0f;return $(p9(0x4a57,'^d9X'))[p9(0x4f63,'!%Yf')](k);};var j=null;return{'showProp':function(k){var pa=a0f,l;l=[pa(0x1372,'esU('),'','',pa(0x26fe,'V8^Q'),pa(0x33e,'eu*B'),pa(0x3a07,'^Syn'),pa(0x4080,'lnIj'),pa(0xb14,'hRv2')][pa(0x4421,'XcPC')](''),$(pa(0x874,'lnIj'))[pa(0xaa2,'Bbr@')](l),i(pa(0x1d19,'sYdH'))['change'](function(m){!function(o){var pb=a0f;let p=i(':radio[name=\x22_align\x22]:checked')['val']();'center'==p?j[pb(0x71e,'^Syn')][pb(0x3082,'XcPC')]='auto':'right'==p?(j[pb(0x28b1,'MDwy')]['margin']='',j[pb(0x2b51,'kGY9')]['marginLeft']=pb(0x32a9,'VTTT')):'left'==p&&(j[pb(0x1ed1,'e*c8')][pb(0x455,'H4NX')]=''),j[pb(0x2482,'xbuu')]['borderWidth']=i(pb(0x4236,'MDwy'))[pb(0x10ae,'xrMR')]()+'px',j['id']=i('#_id')[pb(0x4679,'VTTT')](),j['name']=i(pb(0x32d2,'XCXV'))[pb(0x13fd,'xbuu')](),j[pb(0x4761,'lmQg')]=i(pb(0xb48,'c]Mq'))[pb(0x449a,'*ZKE')](),i(pb(0x3ee1,'V8^Q'))[pb(0x406f,'XCXV')](pb(0x4160,'kGY9'))?j['removeAttribute'](pb(0x16ae,'H4NX')):j['setAttribute']('readonly',!0x0);}();}),function(m){var pc=pa;''==(j=m)[pc(0x4db6,'vGE1')][pc(0x4ad0,'(qw$')]?i(pc(0x1c86,'[T9%'))['prop'](pc(0x49b7,'^Syn'),!0x0):pc(0x4f0b,'lnIj')==j[pc(0x1e2a,'5WLj')][pc(0x1515,'kGY9')]?i('#radio2')['prop'](pc(0x1f01,'e*c8'),!0x0):pc(0x4822,'V8^Q')==j['style'][pc(0x14a5,'lnIj')]&&i(pc(0x208,'e*c8'))[pc(0x27a1,'NrJk')](pc(0x1a33,'VTTT'),!0x0),i('#_id')['val'](j['id']),i(pc(0x63a,'xrMR'))['val'](j[pc(0x226b,'Bbr@')]),i('#_editable')[pc(0x29b,'^Syn')](pc(0x481d,'^d9X'),!$(j)[pc(0x2979,'VTTT')](pc(0x3e65,'1Ue]')));}(k);}};}[pd(0x1b7e,'^d9X')](g,[]))||(f[pd(0xd7b,'*gI2')]=h);},0x173d:(f,g)=>{var h;void 0x0===(h=function(){var pg=a0f;function i(){}const j=function(k){var pe=a0f;return $('#_structurePanel')[pe(0xbc6,'eu*B')](k);};return i['render']=function(){var pf=a0f,k=[pf(0x221d,'xrMR'),pf(0xb3b,'NrJk')][pf(0x4819,'XCXV')]('');$(pf(0x494f,'t#L0'))[pf(0x4b78,'MDwy')](k);},i[pg(0x1976,'Jc8$')]=function(){var ph=pg;let k={'view':{'dblClickExpand':!0x1,'showLine':!0x0,'selectedMulti':!0x1,'showIcon':!0x1},'data':{'key':{'title':ph(0x45c,'5WLj')}},'callback':{'onClick':function(o,p,q){var pi=ph;let r=editor['$']('[name='+q[pi(0x4e1f,'@HIr')]+']');r[pi(0x4927,'zbs!')](),r[pi(0x11ef,'lnIj')](pi(0x2b85,'V8^Q')),setTimeout(function(){var pj=pi;editor['$']('.shink')[pj(0x540,'eu*B')](pj(0x3a66,'!%Yf'));},0x1f4);}}},l=editor[ph(0x523,'sYdH')](),m=$['fn'][ph(0x3b04,'!%Yf')][ph(0x3988,'c]Mq')](j(ph(0xb9b,'UDz8')),k,l);m['getNodesByParam'](ph(0x986,'Bbr@'),0x0)[ph(0x710,'vGE1')](o=>{m['expandNode'](o,!0x0,!0x1,!0x1);}),j(ph(0x1a9f,'kGY9'))[ph(0x45e1,'kGY9')](function(o){var pk=ph;if(0xd==o[pk(0x1477,'$(mV')]){let q=j('#keyword')['val']();if(''==q[pk(0x3c1e,'hRv2')]())m[pk(0x40f,'6YaW')](m['getNodesByFilter'](function(r){return!0x0;})),m[pk(0x4e4c,'^d9X')](!0x0);else{var p=[];m['hideNodes'](m[pk(0x1228,'^d9X')](function(r){return!0x0;})),m[pk(0x3a3f,'1Ue]')](function(r){var pl=pk;return!(!r[pl(0x2843,'zx#%')]||-0x1==r[pl(0x3766,'V8^Q')][pl(0x3ba3,'lmQg')](q)||(p=p[pl(0x32b,'!Yg[')](r[pl(0x4c2a,'Nf2)')]()),0x0));}),m[pk(0x3f9b,'oKRq')](p),m[pk(0x33bc,'1Ue]')](!0x0);}}});},i;}['apply'](g,[]))||(f['exports']=h);},0x17fb:(f,g,h)=>{var ps=a0f,j,k;j=[h(0x11bb)],void 0x0===(k=function(l){const m=function(p){var pm=a0f;return $(pm(0x3616,'V8^Q'))['find'](p);};var o=null;return{'showProp':function(p){var pn=a0f;$('#_proertyContain')[pn(0xdf7,'eu*B')](pn(0x1d35,'^d9X')),m(pn(0x34d4,'Nf2)'))[pn(0x2605,'c]Mq')](function(q){!function(s){var po=a0f;let u=m(po(0x3d32,'*gI2'))['val']();'center'==u?o['style'][po(0x3daf,'lnIj')]='auto':po(0x1729,'lmQg')==u?(o[po(0x2b51,'kGY9')][po(0x1da6,'VTTT')]='',o[po(0x1b16,'lmQg')][po(0x14a5,'lnIj')]=po(0x15cb,'pMQs')):po(0x1820,'zx#%')==u&&(o[po(0x3ab8,'eu*B')][po(0x3daf,'lnIj')]=''),m(po(0x2d12,'Ws11'))['prop'](po(0x43b4,'@HIr'))?($(o)[po(0x4014,'^d9X')](po(0x4255,'$(mV')),m(po(0x262a,'hRv2'))[po(0x408b,'Jj0M')](0x0)):$(o)[po(0x21de,'H4NX')]('noborder'),o[po(0x1f39,'Jj0M')][po(0x1e71,'esU(')]=m(po(0x400e,'zx#%'))[po(0x1355,'$(mV')]()+'px';let v=m(po(0x154d,'^d9X'))[0x0][po(0x1e2a,'5WLj')][po(0x44ea,'XcPC')];if(po(0x1cf3,'kGY9')!=v){o[po(0x4afb,'*gI2')][po(0x4732,'oKRq')]=v;for(let w of o['rows'])for(let x of w[po(0x264e,'c]Mq')])x[po(0xef9,'pMQs')]['borderColor']=o[po(0x4e58,'hK)l')]['borderColor'];}o['id']=m(po(0x2766,'VTTT'))['val'](),o[po(0x29d0,'^Syn')]=m(po(0x463d,'V8^Q'))[po(0xa32,'esU(')](),o[po(0xe1c,'xbuu')][po(0x202f,'$(mV')]=m(po(0x51e,'Bbr@'))[po(0x4ad3,'N!xX')]()[po(0x2287,'#ZU^')]('\x0a',',');}();}),m(pn(0x4a6c,'xrMR'))['on'](pn(0x452e,'*gI2'),function(q){var pp=pn;l['toggle'](q[pp(0x3716,'Q@hO')],pp(0x3a4,'v^si'));}),function(q){var pq=pn;o=q,m()[pq(0x1995,'XcPC')](pq(0x3ad9,'c]Mq'))[pq(0x2f2c,'zbs!')](),m()[pq(0x181e,'1Ue]')]('#_table-property')[pq(0x266f,'^d9X')](),''==o[pq(0x436d,'UDz8')][pq(0x2bfe,'!%Yf')]?m('#radio1')[pq(0x2b71,'hRv2')](pq(0x4423,'hK)l'),!0x0):pq(0x3acc,'Bbr@')==o[pq(0x42ed,'xrMR')][pq(0x2ca2,'*p#P')]?m(pq(0x2a0b,'[T9%'))[pq(0x210f,'vGE1')]('checked',!0x0):pq(0x1db8,'$(mV')==o[pq(0x321b,'1Ue]')]['marginLeft']&&m(pq(0x4e22,'(qw$'))[pq(0x2b71,'hRv2')](pq(0x5e3,'xbuu'),!0x0);let s=getComputedStyle(o),u=o['style'][pq(0x34f2,'!%Yf')][pq(0xf18,'c]Mq')]('px','');u||(u=0x1),m(pq(0x400e,'zx#%'))[pq(0x4acc,'!Yg[')](u),m('#border-color')[pq(0x31b8,'eu*B')](pq(0x14d3,'xbuu'),function(v){var pr=pq;if(/^(rgb|RGB)/['test'](v)){for(var w='#',x=v[pr(0x3c5f,'$(mV')](/(?:\(|\)|rgb|RGB)*/g,'')[pr(0x443f,'xrMR')](','),y=0x0;y{var pA=a0f,j,k;j=[h(0x15b5),h(0x11bb)],void 0x0===(k=function(l,m){var pu=a0f;const o=function(q){var pt=a0f;return $(pt(0x4077,'*ZKE'))[pt(0x3654,'xbuu')](q);};l[pu(0x16e0,'Jj0M')]([pu(0xba5,'esU('),'',pu(0x4a72,'c]Mq')]['join']('')),l[pu(0x1702,'v^si')]([pu(0x1c23,'!%Yf'),'','\x20'][pu(0x49be,'!Yg[')](''));let p=null;return{'showProp':function(q){var pv=pu,s;s=['',pv(0x22fa,'N!xX'),pv(0x3b66,'$(mV'),pv(0x4cb6,'sYdH'),pv(0x3bf8,'e*c8'),pv(0x4102,'MDwy'),pv(0x3ce1,'xrMR'),pv(0xea3,'#ZU^'),pv(0x1b80,'6YaW'),pv(0x3fbd,'IvBK'),'',pv(0x3471,'kGY9'),pv(0x55d,'eu*B'),pv(0x26cf,'XCXV'),pv(0x45c8,'e*c8'),'',pv(0x4b9a,'esU('),pv(0x18e2,'NrJk'),pv(0x44a2,'$(mV'),pv(0x34ea,'zx#%'),pv(0x4d97,'xbuu'),'',pv(0x3541,'kGY9'),'',pv(0x2e43,'$(mV'),pv(0x2041,'NrJk'),pv(0xee5,'kGY9'),pv(0x16df,'vGE1'),pv(0x1bba,'*p#P'),pv(0x394a,'MDwy')][pv(0x430a,'sYdH')](''),$(pv(0x1b28,'IvBK'))['html'](s),o(pv(0x3f69,'Q@hO'))[pv(0x1536,'Jc8$')](function(u){!function(v){var pw=a0f;p['id']=o(pw(0x2e9d,'Jc8$'))[pw(0x7cb,'1Ue]')](),p[pw(0x4668,'hRv2')]=o(pw(0x37c,'c]Mq'))[pw(0x333f,'Q@hO')](),p[pw(0x48c3,'V8^Q')]=o(pw(0x2d3c,'hK)l'))[pw(0x2855,'#ZU^')](),p[pw(0x4444,'esU(')][pw(0x24fb,'hRv2')]=o(pw(0x47a,'Jc8$'))[0x0][pw(0x15fb,'!%Yf')][pw(0x1dec,'IvBK')],p[pw(0x4444,'esU(')][pw(0x4771,'kGY9')]=o('#_valign')[pw(0x44d1,'vGE1')](),p[pw(0x321b,'1Ue]')][pw(0x2897,'IvBK')]=o(pw(0x2602,'lmQg'))[pw(0x2d53,'UDz8')]();let w=o('#_line')[pw(0x2427,'Jc8$')]();0x0==w?(p[pw(0x1fa,'hK)l')][pw(0x4bc,'MDwy')](pw(0x3001,'sYdH')),p[pw(0x48de,'N!xX')][pw(0x2eae,'6YaW')]('td-line-style2')):0x1==w?(p['classList'][pw(0x2abd,'t#L0')](pw(0x4c6a,'(qw$')),p[pw(0x4636,'xbuu')][pw(0x9ca,'V8^Q')]('td-line-style1')):0x2==w&&(p[pw(0x2956,'XcPC')]['remove']('td-line-style1'),p[pw(0x5f9,'lmQg')][pw(0x334b,'6YaW')](pw(0x1f9d,'^Syn'))),o(pw(0x1ffc,'Bbr@'))[pw(0x28e4,'*gI2')](pw(0x4da1,'!%Yf'))?(p[pw(0x1d94,'MDwy')](pw(0xc2,'^Syn'),'true'),editor['tag'][pw(0xf9c,'xbuu')]()):p[pw(0xb0c,'H4NX')]('tag');}();}),o(pv(0x17c4,'!%Yf'))['on'](pv(0x2f8e,'*ZKE'),function(u){var px=pv;m[px(0x123f,'#ZU^')](u[px(0x621,'zx#%')],px(0x458d,'@HIr'));}),function(u){var py=pv;p=u,o(py(0x1654,'t#L0'))[py(0x465c,'zbs!')](py(0x115a,'xbuu')),o('#_id')['val'](p['id']),o(py(0x2444,'1Ue]'))[py(0x17b6,'^d9X')](p['title']);let v=getComputedStyle(p);o(py(0x4384,'hRv2'))[py(0x1cb2,'xrMR')](py(0x3f6,'N!xX'),function(y){var pz=py;if(pz(0x1c0d,'zx#%')==y)return'#ffffff';if(/^(rgb|RGB)/[pz(0x1929,'*gI2')](y)){for(var z='#',A=y['replace'](/(?:\(|\)|rgb|RGB)*/g,'')[pz(0x1337,'Jc8$')](','),B=0x0;B{var pH=a0f,h;void 0x0===(h=function(){const i=function(k){var pB=a0f;return $(pB(0x21a3,'@HIr'))['find'](k);};var j=null;return{'showProp':function(k){var pC=a0f,l;l=['',pC(0xaea,'zbs!'),pC(0x440,'6YaW'),pC(0x3088,'VTTT'),'',pC(0x4728,'VTTT'),pC(0x20c3,'*ZKE'),pC(0x2af1,'^d9X'),'',pC(0x3a1f,'N!xX'),pC(0x2b3d,'!%Yf'),pC(0x4948,'!Yg['),pC(0x197,'Jj0M'),'电子邮箱',pC(0x1e8c,'NrJk'),'',pC(0x121e,'!Yg['),pC(0x4224,'sYdH'),pC(0x4e7c,'!%Yf'),pC(0xc14,'lmQg'),pC(0x1927,'Nf2)'),pC(0x3d5,'NrJk'),'',pC(0xf1d,'^d9X'),'宽度',pC(0xf5c,'N!xX'),pC(0x2cf0,'Jc8$'),pC(0x3c40,'vGE1'),pC(0x4268,'pMQs'),pC(0x1e6f,'xrMR'),'居右',pC(0x1f34,'lmQg'),'','',pC(0x10b0,'c]Mq'),pC(0x3427,'@HIr'),pC(0x25df,'1Ue]'),''][pC(0x3a83,'5WLj')](''),$('#_proertyContain')['html'](l),i('input,select')['change'](function(m){var pD=pC;j&&(j['id']=i('#_name')[pD(0x3bcf,'NrJk')](),j[pD(0x2fc5,'*p#P')]('name',i(pD(0x2999,'vGE1'))[pD(0x1731,'XCXV')]()),j[pD(0xbf1,'XcPC')][pD(0x4abd,'hK)l')]=i('#_code')[pD(0x3acd,'eu*B')](),j['dataset'][pD(0x3414,'xbuu')]=i('#_expression')[pD(0xe40,'6YaW')](),j[pD(0x360b,'oKRq')](pD(0x250a,'Q@hO'),i('#_title')['val']()),i(pD(0x3e27,'XcPC'))[pD(0x4256,'t#L0')]()?(j[pD(0x453a,'$(mV')]=i(pD(0x1a8a,'c]Mq'))[pD(0xde2,'c]Mq')](),$(j)[pD(0x3ae4,'VTTT')](pD(0xf01,'*ZKE'))):(j[pD(0x3c46,'*gI2')]=i(pD(0x350d,'MDwy'))[pD(0x4ce,'zx#%')](),$(j)[pD(0x2203,'XCXV')]('blank')),j[pD(0x360b,'oKRq')](pD(0x4cd4,'xrMR'),i(pD(0x231a,'(qw$'))[pD(0x3b40,'*p#P')](pD(0x191b,'v^si'))),j[pD(0x3f2c,'$(mV')](pD(0xbc9,'*gI2'),i(pD(0x2bff,'zbs!'))[pD(0x1ea1,'zx#%')](pD(0x140e,'XcPC'))),j['setAttribute'](pD(0x3479,'Jc8$'),i('#_validate')[pD(0x18cb,'6YaW')]('checked')),j['setAttribute'](pD(0x35ce,'5WLj'),i(pD(0x2dd1,'e*c8'))[pD(0x4679,'VTTT')]()),i('#_min-width')[pD(0x4198,'lmQg')]()>0x0?(j[pD(0x320f,'t#L0')][pD(0x484e,'N!xX')]=pD(0x1de1,'MDwy'),j[pD(0x28b1,'MDwy')][pD(0x350f,'NrJk')]=i(pD(0xb13,'sYdH'))['val']()+'px'):(j[pD(0xaaa,'@HIr')][pD(0x49e3,'Bbr@')]='',j['style'][pD(0x1e8d,'Jc8$')]=''),j['style'][pD(0x3d2c,'XcPC')]=$(pD(0xb23,'zbs!'))[pD(0x240,'(qw$')](),$(j)[pD(0x12b1,'!Yg[')](pD(0x3f86,'*p#P')),setTimeout(function(){var pE=pD;$(j)[pE(0x3f00,'^d9X')](pE(0x41e7,'^d9X'));},0x1f4));}),function(m){var pF=pC;j=m,i(pF(0x44a,'v^si'))[pF(0x1c82,'e*c8')](j[pF(0x4106,'*p#P')](pF(0x426,'Jc8$'))),i(pF(0x2d3c,'hK)l'))[pF(0xe40,'6YaW')](j[pF(0x1c99,'xrMR')](pF(0x3d0d,'Jc8$'))),$(j)[pF(0x226e,'lmQg')](pF(0x2070,'pMQs'))?i('#_content')[pF(0x116b,'V8^Q')](''):i('#_content')[pF(0x1731,'XCXV')](j['textContent']),i(pF(0xef6,'[T9%'))['val'](j[pF(0x364a,'Jc8$')][pF(0x2eca,'^d9X')]),i(pF(0x2b1e,'[T9%'))['val'](j[pF(0x712,'zbs!')]['expression']),i(pF(0x3fff,'!%Yf'))[pF(0x1c4b,'lmQg')](pF(0x8ae,'$(mV'),pF(0x2693,'MDwy')==$(j)[pF(0x4433,'XCXV')](pF(0x14ca,'*gI2'))),i(pF(0x3c44,'*gI2'))[pF(0x15a1,'pMQs')]('checked','true'==$(j)['attr'](pF(0x3dcb,'V8^Q'))),i(pF(0x3f21,'(qw$'))[pF(0x3963,'N!xX')]('checked','true'==$(j)[pF(0xb4f,'zx#%')](pF(0x3f4e,'!%Yf'))),i(pF(0x2fdd,'*p#P'))[pF(0x1cc7,'Bbr@')](j[pF(0x3119,'5WLj')](pF(0x2d2c,'hRv2'))),i(pF(0x30ab,'lnIj'))[pF(0xeeb,'Ws11')](j[pF(0xe33,'*p#P')]['minWidth']?j[pF(0x71e,'^Syn')]['minWidth'][pF(0x4c0,'kGY9')]('px',''):''),$('input[name=\x27_text-align\x27]')['each']((o,p)=>{var pG=pF;p[pG(0x2133,'*ZKE')]==j[pG(0x321b,'1Ue]')][pG(0x2897,'IvBK')]?p['checked']=!0x0:p[pG(0x481d,'^d9X')]=!0x1;});}(k);}};}[pH(0x132f,'N!xX')](g,[]))||(f[pH(0x1ff4,'N!xX')]=h);},0xe21:(f,g,h)=>{var pQ=a0f,j,k;j=[h(0x11bb)],void 0x0===(k=function(l){const m=function(q){var pI=a0f;return $('#_tr-property')[pI(0x30e1,'#ZU^')](q);};var o=null;let p=function(q){var pJ=a0f;for(;q&&pJ(0x2fe2,'Bbr@')!=q[pJ(0x2b50,'1Ue]')];)q=q['parentElement'];return q;};return{'showProp':function(q){var pK=a0f,s;s=[pK(0x12a6,'lmQg'),pK(0x26c8,'pMQs'),pK(0x36eb,'[T9%'),pK(0x39ba,'Ws11'),'',pK(0x1c01,'sYdH'),pK(0x454a,'*gI2'),pK(0x3680,'zx#%'),'',pK(0x464,'e*c8'),pK(0x2980,'!%Yf'),pK(0x4e3a,'c]Mq'),pK(0x34fc,'Nf2)'),pK(0x79b,'(qw$'),pK(0x3423,'MDwy'),pK(0x23ac,'UDz8'),pK(0x45b9,'xbuu'),pK(0xe6f,'Q@hO'),pK(0x153c,'V8^Q'),pK(0x2bec,'esU('),pK(0x6a4,'#ZU^'),pK(0xc43,'XcPC'),pK(0x3ad5,'IvBK'),pK(0x1dd3,'(qw$'),pK(0x26e1,'@HIr'),pK(0x2e01,'Jc8$'),pK(0x25df,'1Ue]')][pK(0xdfa,'hRv2')](''),$(pK(0x1d6a,'@HIr'))['html'](s),m('input,\x20select')['change'](function(u){var pL=pK;o['id']=m(pL(0x26fc,'5WLj'))['val'](),o['name']=m(pL(0x332e,'t#L0'))['val'](),o[pL(0x43ac,'xbuu')]=m(pL(0x2d7,'e*c8'))[pL(0x1355,'$(mV')](),o[pL(0x3a2d,'zx#%')][pL(0x4922,'*p#P')]=m(pL(0x349d,'*gI2'))[0x0]['style'][pL(0x4a31,'!Yg[')],m(pL(0x2e17,'XcPC'))[pL(0x2e35,'lnIj')](pL(0x1f01,'e*c8'))?(o[pL(0x3447,'pMQs')](pL(0xfd8,'VTTT'),pL(0x2693,'MDwy')),editor[pL(0x486b,'IvBK')][pL(0x4a4f,'(qw$')]()):o[pL(0x2519,'zx#%')](pL(0x27e7,'!Yg[')),o[pL(0x2e26,'(qw$')][pL(0x379d,'Q@hO')]=m(pL(0x6b2,'zx#%'))[pL(0x1c82,'e*c8')]();}),m('#_pageSetting')[pK(0x4b46,'!Yg[')](function(u){!(function(){var pM=a0f;let v=m('#_pageSetting')[pM(0xa32,'esU(')](),w=p(o);if(editor[pM(0x4215,'v^si')](),0x0==v){if(0x0==w[pM(0x3aac,'hK)l')][pM(0x2169,'c]Mq')]){let x=w[pM(0x32f6,'^Syn')]();x[pM(0x4961,'!%Yf')](o,x[pM(0x3930,'*gI2')]);}else{let y=w['tBodies'][0x0];y[pM(0x3c84,'lmQg')](o,y[pM(0x3e28,'IvBK')]);}}else 0x1==v?(w[pM(0x1a36,'sYdH')]?w['tHead']:w[pM(0x4d55,'hK)l')]())[pM(0x2778,'N!xX')](o):0x2==v&&(w[pM(0x1cf7,'v^si')]?w['tFoot']:w[pM(0x636,'@HIr')]())[pM(0x2669,'VTTT')](o);w[pM(0x2e0,'eu*B')]&&0x0==w['tHead'][pM(0x206,'(qw$')][pM(0x3fe1,'N!xX')]&&w[pM(0x2767,'kGY9')](),w['tFoot']&&0x0==w[pM(0x47be,'xrMR')]['children'][pM(0x1f04,'*ZKE')]&&w['deleteTFoot']();}());}),m(pK(0x18d1,'Ws11'))['on'](pK(0x49eb,'eu*B'),function(u){var pN=pK;l['toggle'](u[pN(0x39f0,'pMQs')],pN(0x5d2,'NrJk'));}),function(u){var pO=pK;pO(0x3143,'^d9X')==(o='THEAD'==u[pO(0x3226,'H4NX')]?u['firstElementChild']:u)[pO(0x819,'zx#%')]['tagName']?m(pO(0x39f6,'oKRq'))[pO(0x17b6,'^d9X')](0x1):pO(0x9ee,'1Ue]')==o[pO(0x44a0,'Jj0M')][pO(0x1b4b,'pMQs')]&&m(pO(0x2068,'UDz8'))[pO(0x408b,'Jj0M')](0x2),m(pO(0x148a,'MDwy'))['val'](pO(0x4a90,'kGY9')),m(pO(0x678,'#ZU^'))[pO(0xde2,'c]Mq')](o['id']),m(pO(0x1633,'5WLj'))['val'](o[pO(0x2a0f,'*gI2')]);let v=getComputedStyle(o);m('#_background')[pO(0x241d,'vGE1')](pO(0x49d5,'hK)l'),function(x){var pP=pO;if('rgba(0,\x200,\x200,\x200)'==x)return pP(0x7db,'Ws11');if(/^(rgb|RGB)/[pP(0x3340,'*ZKE')](x)){for(var y='#',z=x[pP(0x4c0,'kGY9')](/(?:\(|\)|rgb|RGB)*/g,'')[pP(0x302f,'hK)l')](','),A=0x0;A{var pS=a0f,h;void 0x0===(h=function(){var pR=a0f;return'\x0a\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'); + fabric.util.setStyle(this.lowerCanvasEl, 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 + * @param {CanvasRenderingContext2D} ctx + */ + _setBrushStyles: function (ctx) { + 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, + + /** + * Draws a straight line between last recorded point to current pointer + * Used for `shift` functionality + * + * @type boolean + * @default false + */ + drawStraightLine: false, + + /** + * The event modifier key that makes the brush draw a straight line. + * If `null` or 'none' or any other string that is not a modifier key the feature is disabled. + * @type {'altKey' | 'shiftKey' | 'ctrlKey' | 'none' | undefined | null} + */ + straightLineKey: 'shiftKey', + + /** + * Constructor + * @param {fabric.Canvas} canvas + * @return {fabric.PencilBrush} Instance of a pencil brush + */ + initialize: function(canvas) { + this.canvas = canvas; + this._points = []; + }, + + needsFullRender: function () { + return this.callSuper('needsFullRender') || this._hasStraightLine; + }, + + /** + * 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.drawStraightLine = options.e[this.straightLineKey]; + 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; + } + this.drawStraightLine = options.e[this.straightLineKey]; + 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.drawStraightLine = false; + 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; + } + if (this.drawStraightLine && this._points.length > 1) { + this._hasStraightLine = true; + this._points.pop(); + } + this._points.push(point); + return true; + }, + + /** + * Clear points array and set contextTop canvas style. + * @private + */ + _reset: function() { + this._points = []; + this._setBrushStyles(this.canvas.contextTop); + this._setShadow(); + this._hasStraightLine = false; + }, + + /** + * @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 + * @param {CanvasRenderingContext2D} [ctx] + */ + _render: function(ctx) { + var i, len, + p1 = this._points[0], + p2 = this._points[1]; + ctx = ctx || this.canvas.contextTop; + 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 + * @param {CanvasRenderingContext2D} ctx + */ + getPattern: function(ctx) { + return ctx.createPattern(this.source || this.getPatternSrc(), 'repeat'); + }, + + /** + * Sets brush styles + * @param {CanvasRenderingContext2D} ctx + */ + _setBrushStyles: function(ctx) { + this.callSuper('_setBrushStyles', ctx); + ctx.strokeStyle = this.getPattern(ctx); + }, + + /** + * 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:before before drop event. same native event. This is added to handle edge cases + * @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 + * + */ + 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 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: [], + + /** + * When the option is enabled, PointerEvent is used instead of MouseEvent. + * @type Boolean + * @default + */ + enablePointerEvents: false, + + /** + * 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); + this.hasLostContext = false; + } + 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 (Array.isArray(this.selectionKey)) { + 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'); + }, + + /** + * Returns context of top canvas where interactions are drawn + * @returns {CanvasRenderingContext2D} + */ + getTopContext: function () { + return this.contextTop; + }, + + /** + * @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, + }); + } + else if (objects.length > 0) { + this.fire('selection:created', { + e: e, + selected: added, + }); + } + 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._onDrop.bind(this); + 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 = []; + }, + + /** + * @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); + }, + + /** + * `drop:before` is a an event that allow you to schedule logic + * before the `drop` event. Prefer `drop` event always, but if you need + * to run some drop-disabling logic on an event, since there is no way + * to handle event handlers ordering, use `drop:before` + * @param {Event} e + */ + _onDrop: function (e) { + this._simpleEventHandler('drop:before', e); + return this._simpleEventHandler('drop', 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) + ); + } + } + var corner, pointer; + if (target) { + corner = target._findTargetCorner( + this.getPointer(e, true), + fabric.util.isTouchEvent(e) + ); + if (target.selectable && target !== this._activeObject && target.activeOn === 'up') { + this.setActiveObject(target, e); + shouldRender = true; + } + else { + var control = target.controls[corner], + mouseUpHandler = control && control.getMouseUpHandler(e, target, control); + if (mouseUpHandler) { + pointer = this.getPointer(e); + mouseUpHandler(e, transform, pointer.x, pointer.y); + } + } + target.isMoving = false; + } + // if we are ending up a transform on a different control or a new object + // fire the original mouse up from the corner that started the transform + if (transform && (transform.target !== target || transform.corner !== corner)) { + var originalControl = transform.target && transform.target.controls[transform.corner], + originalMouseUpHandler = originalControl && originalControl.getMouseUpHandler(e, target, control); + pointer = pointer || this.getPointer(e); + originalMouseUpHandler && originalMouseUpHandler(e, transform, pointer.x, pointer.y); + } + 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, + 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())) { + this._fire('modified', options); + } + }, + + /** + * @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. + * this Property does not work on Text classes or drawing call that uses strokeText,fillText methods + * 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]; + } + // basically a check for [] === [] + if (Array.isArray(object[prop]) && Array.isArray(prototype[prop]) + && 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._cacheContext) { + 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._cacheContext = 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 + * @param {fabric.Object} clipPath + */ + drawClipPathOnCache: function(ctx, clipPath) { + ctx.save(); + // DEBUG: uncomment this line, comment the following + // ctx.globalAlpha = 0.4 + if (clipPath.inverted) { + ctx.globalCompositeOperation = 'destination-out'; + } + else { + ctx.globalCompositeOperation = 'destination-in'; + } + //ctx.scale(1 / 2, 1 / 2); + if (clipPath.absolutePositioned) { + var m = fabric.util.invertTransform(this.calcTransformMatrix()); + ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + clipPath.transform(ctx); + ctx.scale(1 / clipPath.zoomX, 1 / clipPath.zoomY); + ctx.drawImage(clipPath._cacheCanvas, -clipPath.cacheTranslationX, -clipPath.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.clipPath); + this.fill = originalFill; + this.stroke = originalStroke; + }, + + /** + * Prepare clipPath state and cache and draw it on instance's cache + * @param {CanvasRenderingContext2D} ctx + * @param {fabric.Object} clipPath + */ + _drawClipPath: function (ctx, clipPath) { + if (!clipPath) { return; } + // needed to setup a couple of variables + // path canvas gets overridden with this one. + // TODO find a better solution? + clipPath.canvas = this.canvas; + clipPath.shouldCache(); + clipPath._transformDone = true; + clipPath.renderCache({ forClipping: true }); + this.drawClipPathOnCache(ctx, clipPath); + }, + + /** + * 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 && this._cacheContext && !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 && this._cacheContext && !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 + * the context here is not transformed + * @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; + } + if (this.flipX) { + options.angle -= 180; + } + ctx.rotate(degreesToRadians(this.group ? options.angle : this.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 arguments.length > 1 ? Array.from(arguments).includes(this.type) : 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; + } + }, + + /** + * cancel instance's running animations + * override if necessary to dispose artifacts such as `clipPath` + */ + dispose: function () { + if (fabric.runningAnimations) { + fabric.runningAnimations.cancelByTarget(this); + } + } + }); + + 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; + + /** + * Defines which properties should be enlivened from the object passed to {@link fabric.Object._fromObject} + * @static + * @memberOf fabric.Object + * @constant + * @type string[] + */ + fabric.Object.ENLIVEN_PROPS = ['clipPath']; + + 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.enlivenObjectEnlivables(object, object, function () { + 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. + * @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); + }, + + 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/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas} + * + * @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 + * @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.AnimationContext} context + */ + fxCenterObjectH: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + return fabric.util.animate({ + target: this, + startValue: object.left, + endValue: this.getCenterPoint().x, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('left', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + }, + + /** + * 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.AnimationContext} context + */ + fxCenterObjectV: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + return fabric.util.animate({ + target: this, + startValue: object.top, + endValue: this.getCenterPoint().y, + duration: this.FX_DURATION, + onChange: function(value) { + object.set('top', value); + _this.requestRenderAll(); + onChange(); + }, + onComplete: function() { + object.setCoords(); + onComplete(); + } + }); + }, + + /** + * 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.AnimationContext} context + */ + fxRemove: function (object, callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + return fabric.util.animate({ + target: this, + 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(); + } + }); + } +}); + +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} + * @return {fabric.AnimationContext | fabric.AnimationContext[]} animation context (or an array if passed multiple properties) + * + * 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, out = []; + 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; + out.push(this._animate(prop, arguments[0][prop], arguments[1], skipCallbacks)); + } + return out; + } + else { + return this._animate.apply(this, arguments); + } + }, + + /** + * @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 = { + target: this, + 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 = { }), + degreesToRadians = fabric.util.degreesToRadians; + + 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, + + /** + * degrees of start of the circle. + * probably will change to degrees in next major version + * @type Number 0 - 359 + * @default 0 + */ + startAngle: 0, + + /** + * End angle of the circle + * probably will change to degrees in next major version + * @type Number 1 - 360 + * @default 360 + */ + endAngle: 360, + + 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) % 360; + + if (angle === 0) { + svgString = [ + '\n' + ]; + } + else { + var start = degreesToRadians(this.startAngle), + end = degreesToRadians(this.endAngle), + radius = this.radius, + startX = fabric.util.cos(start) * radius, + startY = fabric.util.sin(start) * radius, + endX = fabric.util.cos(end) * radius, + endY = fabric.util.sin(end) * radius, + largeFlag = angle > 180 ? '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, + degreesToRadians(this.startAngle), + degreesToRadians(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, + projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; + + 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, + + /** + * WARNING: Feature in progress + * Calculate the exact bounding box taking in account strokeWidth on acute angles + * this will be turned to true by default on fabric 6.0 + * maybe will be left in as an optimization since calculations may be slow + * @deprecated + * @type Boolean + * @default false + */ + exactBoundingBox: false, + + 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); + }, + + /** + * @private + */ + _projectStrokeOnPoints: function () { + return projectStrokeOnPoints(this.points, this, true); + }, + + _setPositionDimensions: function(options) { + var calcDim = this._calcDimensions(options), correctLeftTop, + correctSize = this.exactBoundingBox ? this.strokeWidth : 0; + this.width = calcDim.width - correctSize; + this.height = calcDim.height - correctSize; + if (!options.fromSVG) { + correctLeftTop = this.translateToGivenOrigin( + { + // this looks bad, but is one way to keep it optional for now. + x: calcDim.left - this.strokeWidth / 2 + correctSize / 2, + y: calcDim.top - this.strokeWidth / 2 + correctSize / 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 + correctSize / 2, + y: calcDim.top + this.height / 2 + correctSize / 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.exactBoundingBox ? this._projectStrokeOnPoints() : 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 = {}), + projectStrokeOnPoints = fabric.util.projectStrokeOnPoints; + + 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 + */ + _projectStrokeOnPoints: function () { + return projectStrokeOnPoints(this.points, this); + }, + + /** + * @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, + clone = fabric.util.object.clone, + 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 = clone(options || {}); + delete options.path; + this.callSuper('initialize', options); + this._setPath(path || [], options); + }, + + /** + * @private + * @param {Array|String} path Path data (sequence of coordinates and corresponding "command" tokens) + * @param {Object} [options] Options object + */ + _setPath: function (path, options) { + this.path = fabric.util.makePathSimpler( + Array.isArray(path) ? path : fabric.util.parsePath(path) + ); + + 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); + if (object.clipPath) { + fabric.util.enlivenObjects([object.clipPath], function(elivenedObjects) { + path.clipPath = elivenedObjects[0]; + callback && callback(path); + }); + } + else { + 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, this.clipPath); + }, + + /** + * 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; + }, + + /** + * 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(); + }, + + dispose: function () { + this.callSuper('dispose'); + this.forEachObject(function (object) { + object.dispose && object.dispose(); + }); + this._objects = []; + }, + + /** + * 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); + var clipPath = options.clipPath; + delete options.clipPath; + group.set(options); + if (clipPath) { + fabric.util.enlivenObjects([clipPath], function(elivenedObjects) { + group.clipPath = elivenedObjects[0]; + callback && callback(group); + }); + } + else { + callback && callback(group); + } + }); + return; + } + fabric.util.enlivenObjects(objects, function (enlivenedObjects) { + fabric.util.enlivenObjectEnlivables(object, options, function () { + 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.callSuper('dispose'); + 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.enlivenObjectEnlivables(object, object, function () { + 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() { + return this.rotate(this._getAngleValueForStraighten()); + }, + + /** + * 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 + */ + fxStraighten: function(callbacks) { + callbacks = callbacks || { }; + + var empty = function() { }, + onComplete = callbacks.onComplete || empty, + onChange = callbacks.onChange || empty, + _this = this; + + return fabric.util.animate({ + target: this, + startValue: this.get('angle'), + endValue: this._getAngleValueForStraighten(), + duration: this.FX_DURATION, + onChange: function(value) { + _this.rotate(value); + onChange(); + }, + onComplete: function() { + _this.setCoords(); + onComplete(); + }, + }); + } +}); + +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 + */ + fxStraightenObject: function (object) { + return object.fxStraighten({ + onChange: this.requestRenderAllBound + }); + } +}); + + +(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. + * @param {Number} filterType gl.NEAREST or gl.LINEAR usually, webgl numeri constants + * @returns {WebGLTexture} + */ + createTexture: function(gl, width, height, textureImageSource, filterType) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filterType || gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filterType || 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, mainly for non webgl scenario + * to save some calculation + * @type Boolean + * @default true + */ + 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. + * @type String + * @default + **/ + color: '#F95C63', + + /** + * Blend mode for the filter: one of multiply, add, diff, screen, subtract, + * darken, lighten, overlay, exclusion, tint. + * @type String + * @default + **/ + mode: 'multiply', + + /** + * alpha value. represent the strength of the blend color operation. + * @type Number + * @default + **/ + 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", "mask") + * @type String + * @default + **/ + 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. + * @type Number + * @default + */ + 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 pathAlign').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', + 'pathAlign' + ], + + /** + * @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', + + /** + * How text is aligned to the path. This property determines + * the perpendicular position of each character relative to the path. + * (one of "baseline", "center", "ascender", "descender") + * This feature is in BETA, and its behavior may change + * @type String + * @default + */ + pathAlign: 'baseline', + + /** + * @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 + * this is for internal use, please do not use it + * @private + * @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 = 'alphabetical'; + if (this.path) { + switch (this.pathAlign) { + case 'center': + ctx.textBaseline = 'middle'; + break; + case 'ascender': + ctx.textBaseline = 'top'; + break; + case 'descender': + ctx.textBaseline = 'bottom'; + break; + } + } + 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, currentDirection = ctx.canvas.getAttribute('dir'); + ctx.save(); + if (currentDirection !== this.direction) { + ctx.canvas.setAttribute('dir', isLtr ? 'ltr' : 'rtl'); + ctx.direction = isLtr ? 'ltr' : 'rtl'; + ctx.textAlign = isLtr ? 'left' : 'right'; + } + top -= lineHeight * this._fontSizeFraction / this.lineHeight; + if (shortCut) { + // render all the line in one pass without checking + // drawingLeft = isLtr ? left : left - this.getLineWidth(lineIndex); + 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 = fabric.util.hasStyleChanged(actualStyle, nextStyle, false); + } + 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; + 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 {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] !== undefined) { + return this.__lineWidths[lineIndex]; + } + + var lineInfo = this.measureLine(lineIndex); + var 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); + obj.styles = fabric.util.stylesToArray(this.styles, this.text); + 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) { + textInstance.styles = fabric.util.stylesFromArray(object.styles, object.text); + 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) { + var styles = fabric.util.stylesFromArray(object.styles, object.text); + //copy object to prevent mutation + var objCopy = Object.assign({}, object, { styles: styles }); + parseDecoration(objCopy); + if (objCopy.styles) { + for (var i in objCopy.styles) { + for (var j in objCopy.styles[i]) { + parseDecoration(objCopy.styles[i][j]); + } + } + } + fabric.Object._fromObject('IText', objCopy, 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; + } + + // regain focus + document.activeElement !== this.hiddenTextarea && this.hiddenTextarea.focus(); + + 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 test is required in order to close #6841 + // when a pasted buffer begins with a newline then + // this.styles[cursorLoc.lineIndex + i] and copiedStyle[0] + // may be undefined for some reason + if (this.styles[cursorLoc.lineIndex + i] && copiedStyle[0]) { + 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 = fabric.util.hasStyleChanged(actualStyle, nextStyle, true); + } + 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) { + var styles = fabric.util.stylesFromArray(object.styles, object.text); + //copy object to prevent mutation + var objCopy = Object.assign({}, object, { styles: styles }); + return fabric.Object._fromObject('Textbox', objCopy, 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/vender/fonts/mui.ttf b/public/vender/fonts/mui.ttf new file mode 100644 index 0000000000000000000000000000000000000000..45d3b04a371f3351b6dea69de647aa6b394c282b GIT binary patch literal 29884 zcmdUYd3;>edGyng1C%5n z2oT2xLP;PAB*Dp-l#u3Y#*j3WP$E*&CMj*xFH4iAi$j2>ZPKK_A1Q5$#@}=9j4T;Q znza1Wv1abM_nv)um*;)YF`0K=2l@QrQV-rlwA?N{7N2o2+Y zX8+OI6E|kgdw_2l_=AO8z}{eM?aAZ_(33K!@r zL_$6vV$b8@dn;G^&m>1RJSDQg<60kzCSLqZ&X3NKMRLn1-(Td!Ucp~m@`T_ev*9*c6xfkYsDEu(@!y{NtDdC~V` z_ltcm4!=16;-MF>dr5t%3ZbN0dygr_kJgQSD3A=}74 zdgFM>H#u!?W|TN}C(-ALpi^DgN2sdOJ#MP0Hd))}p$=J;9s7t~v>$d+mBtD5Fzugi|kqtQB5 z=d4z*s^hPhVX;^=8@F#?ySA${6xzOT`@Sot*KS+8ZG53nw^YoURJ6z~WAI!npv7Me*Yh>=OQv2`?*l>Nb6S5J33BrCcQ zr)%Y|o=h9fq%$<%J;2t|u3#;tk!a0!Z$r#0+(&KUO#IIBX7+I!s7vX3oo=+2H)U&+ z{z$~|sW&>jUWe1`y<3$fyU0YB-Y`5hY1W5=nxsjRT>gaQstG(@*Tm|mBa*4v(&Ubb zP9y4_J=Gmrv!)@WQM#}|eNjVybkI}d!QVS-g1&m)?Nq}x&XmXJr#HT65BnUo=^ygd zJ9A+{ILj6>i+7QY=IG{4!+pIynRF}?3OYn#tBX;QnM4$*AYv4VM43QXk>F04Ac8=r z2o>n4rfwPUXjOM*ZXrYJyYp3~ip(l-Q zN}=CqD5&_A0IqQaL%~2tz%Tn<)SuF+f-x8n*=Ne1dgT>5{YrUe;@8ZmaRvKkk zXpA`}pQ@--w28W}aU!*)(XS&+@lVoKQCImwY&sDuMmslb-_I6bsbuw+{qy%uoIaAM zqp~gtGX7*)kc8S~Fq`r-8tC(_smV!Fu4av|FMzUazM6=kCE{*F6(nA`qtS8XO!xln z8#<$$j0j}$!g;}DGYD@di?nCFWXIBoz3=`z!8hz5edvKZZk7mr?9uCo%t4P$wGqL@ zigf&{I0}#RtFtH;E9K+`O6|l>$vl{p%}#7{AOd!qGN-9bk?o2+htalEW-mIZEK8F_ zlI#*E3A_UBs}n1BqRiPeRrnRj%>Q0-ScvhRJIY}w&uBCoC;19(aCa5(_5uy*!A@_(7x1sw$hna|V!X!6f7l8&erryHTI`!nl;sDuh(Q761900%_WW41qm$_1T?Q<*qaFW z)e%K?+eK>?pmfczcd43!dx5Enrgep5$$NIO(bfY0%@lp$;|8Y`cPd_ z(SiX{bt{U>1W^+ma+j{@=p7NAWOpM5_`P^>P1%WqTuGj%*Hu^5BOm>3hp333{+*BA zcb6n9pMT=k%eIWy235DbvZ{Vr94C)s#(vprp2hO2+MrQIlrgF1ZB%xPvU`rWoub=0 z=hB(eAvzs%L?s@odWv9K=zMVcU4#-*q2ewtbvW#k#E#GMI$OUwJEtf+=iDw`_>D3$ z->NEME!NM@eBi-rPaHdX^^wDeW}csYes^(nWbK+7kPGDU zIq84V>J-ebSQq}vJO9_L(tqLg?Hc`GxmH)p5+kI`Y+oO(fe0f|lQTyM$3+o*n6e36 z7?&Fumm4zm>WDVtb4x2y15KtgRhrM~b0$4V^ITLIU?Ab3^_)6GX2XROd@h-RBooYFG>bWf3?vBbBA1D zxZ{aHYswFr-5FFOHFf$$i9o2x1NN}rt$S2mrge#ybqO?;UZh#uNWtu0KRDoqn#m$2@hm{^;R;cjc+0UKsP}eS(caV zWFJ{HZ4=|0V*$x7Q?o+j+lWeuy2m1!MA-)dY!_ww+(kk=y-a8?A+fDUr5^gV6Pdh} zYWymh|CObHYWj=QBg1R@x;mPh;9~|i3UA5qEz_^VZ{;Y`3W+J-@Rp7yBWEux z3R~C$*+lLpd(7fhR91fLMz32WGBdII6(%V2L{^{;lX(oiA~U6kNuW`p6)}#gMAagZ zL{XZ+f)J+&1ccE$-?ebdf&H^HTPKFh)@*ZQebVOUOM?r5ikwQO+iB&mP2j>D*S8?z z@kgm7-dBV-KZLdAzg$-(2BbSA=Tcys%*DixLRY>g*A=Xz7Hx?`xvD54brjFIepkS2 zF8V24bU4@`J8WWyO|;orLlERllw~KxG@Gh2SyP}Qbh6rXtRA9~9&(AIsQTThjk)Za zp8RZmFqksDJuPx=ecBc<4|VsoL>e0sDIdf=Ex~B5&k?Y*wp74i)Mj_kPykXRgs)(R z#XQcsI@#uGX-e6mvUpo`Eo7xS#m4Hw0)zUZIDJlPw?S=F?Yfp7Oso~^Ly*pbVTUuA ztk2b&v-!4;OuCubYJ%$v^5GVdZcewiM%ugbgSm8Ak3r3W_R0D-$FgC~r|UElvO|ic zVkpVNF2Cpr*Vv*#57%`Th#@R610k9CNWa7c}#jXrZZJik3 zv~g^7bY!5vsnO&0B+_oDzlC(gDKuO@o06$aDxFCxn0L?^pt*EcxL3Zrr=DvpmhOVAshGu;(mfSWGuc{S&7jy_eHz;UY$$5f2HI zkAB`yWr-7M8#Ko?eDMOE!Quuyu@{}oalu^7SAu9r=tte8Dr*H`Om?Q&n})Xo5tC)3DjhaC(R|6@{t7GiW3s%zc>yF`oG=mNSR zLkGn0qi+EG(s6{yR;(*Zh6w>^=^zdP(u_w6DH;B+(BPp7DlC+LvAn3uzhFOIO3)Jq zeeqA0SM;BhV|LEcqpj4Q`x)VmD=FT_ERw3g2|0v(0nZEwIzTVG&?(uV<_&;YK z`QQiMfBzltTDbL{@3`*0*S~lDVDDJp7$iKmTW^7Q0b}j?FZ6H4*3e~PN+KggllbvQLEV`Fa~}P zrr`)QL?XG2;SPnwWc%QnL}u64Ay>WW{^y3#uK3Z3t=A0Yr)wMCNUuBFx79e9H#gw& z`*c;;dp1fNxBC6r(Uv-05WSh9 z=KbBy^2;oc=}WA?QWCeU-&|<{fCiUtvB*%RsQfMnO!%nyz8tF}pkND|9j0tvFpack|{# zTb&<7g_svUAuO_oiB2Mryx58v#*=U>uglPB2erw zrEn%F&bgTjCPXJJIhgTI8GjHn385LS9hVWV)M6s6qg!Wh%+x_MWbwUzWPL zCc^C*7IsNX2A&EPn^aR_l3B5F8|n&K@-;FFh7^GCap16+aUE+J&vXD|i8DNdgHK`0RtRn4IwYozdmaNj$4^^izy-kLR4g~Tv(-GFDZ(TG%DT>GbJe;w+%yJD zH^y;Rc$U2Y3gaezNHeu0P1-^2=FR;bhEHH(4}@@5sC9tI(bgCfNH$Xw0vHL=v8uj9 zBm&o&gTVpw1?CY@Y(bI)P;5bvrU;^r*2Mz>kEgjYo~}y=!hx{I@9}#B8l(cgfk`TLo5SgiNl7X{8g6TXwRUoBGF(%L9enDpt#|#y>66c%Y?(ZC`^e6R zj}M)AX6w3UeqP!=Lc`69O4Id3O51;00%AHVIZ*T444_J?QJ94ihtteL%Y)9gb#=%blBfVB9l%h&NV@EH3``djql>+hjgk-NxAaviw*F>;h#L#`%A$YG3e0&BxZ zypZ?Zkl|oV!c4%>LI%XZkSLxIx`B?-QR|tV&d|Md550o!rbRkUHY8l^QfLc=sj1JqA_)Jr|oZLwk}b&#Ks=gFUvuaU2kKOuiio+E!mzD%Aai{wk> z8S;nZY0`rFYt%-5ME-$%(`pHqF-$r_-i>i|pgHQm4PG&z6{tHG zTzG}&2z*SaHpkT&qDgbmo%rfe=2faF2MMuDWSp;?tz3$chS$gGF1mvb(;>PZgfvTA zXcKKDOXL^i-^r`w$K;>MKaqbV-zR@fzDK@GeusRLd=Tz0eIQ6W(OZ1&EI%ma2bKI_ zaxJav3@8DYP)qAP0hhCdwL4NSN1ZbeQfet)xKc{0-3cW$ML`N~vQ<<~6m2$MxdvV>cssybLIDA? zJAmyfHdsF0s_IZx)s_hcp'$VQccNb$N^wd0*YQ7Wn|p&cj@k42jdt1@yIVHM^L zx578t=;qA1V$loFxeDDe{&?723QL(FZ92PbRS z+NMGwMg!2rHmFmG0r(5Wp%*Lp@u5H}Srm{St?kRhg{OYJTX%7;9Ljuz~wCNPD{hc}6F z{)Wy$#jK&^V~R|W7uUde)6pT^g3hN9?+|2(a!_?#kshybBXFZ2oRmZWRtPxM4-rq@Ns5E!5pXGeQmwxziVHb#z0RakcsxDQwo;PLt}Ksv6aJPMeJYkw9BzNWrED3Z?&_$op zefyqEE-Wy^6Ze)+#afzc=}mUO+aZ@XxPs_FG+LxLIYG~yhky5Ky3k*Kb@+~9v`2=% zO@L{cqzxye1aYOubd*?xus48#_6$+X#jtM3ZNTIOg z7eU^1w_PuCa_;nciXg=f*$G^W12z*7L#1s5eK;01xARQxE_fQfb-5PJEF}l#W@q;9 zxnlRO%eQaadfE8qv5_^s@BwK}r)ol8SU{Z)RU&n?&X2wahSml$nFK_uScXS1+LfV5 z`?!FcNTx9o+`Go|2mp2KSW5xV#)Uetav*Z`+${%QIWm`UAqW?8fs|-HNhgKjw+nJd zz5ARy)uxKkk!ZBBsfI1oq*8*St4j;cpsYESQ<~@!(oV-YzUo01Tw$+GJB=6Rf4FyU z?%ug;PfkspoXRJY7f3@-LqiWeFDUs$UAHP3Mt!7wHrm)2r870D@_w7k7Ie}XxL}}{ zePXi%(h~$lPt0$J>ZmegHh1s;LuOMa&nFM_yc(eNk_$6nx92fv>AUhYvN>O$(l*)#I~mAwQ3iPyL|_9TBftbi zVYIEaJJ;IR*4NyWZb%pvcn88-9gD(8WRP-sP6@~^;6N4kBi=xNx;ifuv>D0_rV1DG zj{n_9d{p@P&oR_g*2FFlhwpqB&K9=Q&z=6lk@4{(;|rNWB-@pZM6&Gs!^d)6g1q!9 z(?C-McOvOI{#DEovD`=hRG`7O(Cn#Gvu$5Ms_`T5D`X-(6_SGHtc+;`N3(1^1GrBC zWdUKrYqS}KyMuRWK)RAM*Z-P z^40n4+#<)>UN;cZuj=mdE5EvP|7cYoN>(3sVD5mtkpQ%}Jh{@`!`W~w>W4ba642WW zjD~0er$FyA-n&pIK>M*!fUH6Mc$0-9iTUna7v~l&&Gk^Q!yz|($RwK`6sbU!hOPqF zhzp)>n3jOV(4ImbYeWh4C{Wvg{{;LuXCcLg;4gZgY43zSyrzQ_2q5#^C$ob?p_H530M|+4K9C)Pj^el) z79sBr31o zSm`C?TR{I|QR*T$zp;v?PVfv+a9WUUAA54Lw|BCas;~Vllt_dGH-2BTy8tMqxvU`x zP7N{^y~gSH>q|_xvFByjXy^>m^iEPW5nj43oJfS(z2QXpZ4Oy7EP({A5a~&mtNa&C zvpGSCmtMuhRmtTS)(Kx=Un5PV2Uer~(1I4A2YiG)ZhrPU7>g!ARZjr*>@65R(4Z}& zBN0q6AP+<~ckZ}M5FG=sXljZK(~+bzos(upbVdL}Q=M>Kfdb-z9mD}_Q-eaQ*)Ru* z;KUU4QYUe%PIu8m6{5mdt4LhB?wTMjmp(;wmp*#?ZM!c2;DhhG|MaQbe*5;{zV((H zZ#a6*fw{eVF5kav|J3BBjpjgCM=Dtx3qj(5Q>qI<4@V|J4dMa_UAdrP1cYMPy zFhh`eXk47|!Pd#KM_@o)>&1`&lu*ETQy3eM6&o2ym7%MMK=5?sKCzsKuA}KfRV!Y{ zmEV*stX7RVSXn`%ph63eeZl_yuPMqmf%!o?m4!e1I+q0F?Zc8^n#*1$z<{8XU+dYE z8E#y6`QFEJvedbwYs=N?;MXh>@~LOP&Hxhr&BvEMt=jD_yQDr31QOz^U3gAXZQQ0L zxjOWS*WMV@BR;#YPOkC!oC@?3_DD0G+aU}K=e~JW{lGCVL{C{| zqxx&#-ue0F%t+?){^rj=~ZhgzuggrrIv zxDVewiruc<54mOp6H!=bSVJP}2^ z8ll1FFcbrB9;1uK2Yqx`sQkh8^iar*K=xO|q2>r^p#$a1f;CXS&!-N5N!K&>Tn7U_{&kJU2xjx2-HdY9*`ga0CG_Q6TwIc z@P*w)R2a%92qgM#L{vqPqv1*(4q&El$V9NKSYQ8jE=4nP*~FId&6_rE7#kfK9$McW z%-7^Rft=eN;+}2Y7CMTV4F@-$CGig^d0(!f;=3J(ww(ez%B6q!6|{-4x+WAj@cU9i zPDuH3f^jw(Ib#pHwX@CbsW7dP>`+kP@z2_t%I}m^yL3hZg%JLj>K;e=WoSZ{az{Jp z6F-W@?yU{R|GYL3XX_dxNzEN}G}bPCF_L7^YoV^%WHve~+cBj;Wu(TXN2t@o^(L-+ zLV+4F%W?Wb<%^-gELp_Zn3l}w2ZGV>_gJE}B7jWsuA=kU6o5s*H^{(A5l}O?*NKyu zXW|GUJ}-n#JC`s#st-Qze6Iqxq$WHKnF6STC*@)0d03Aw5VlYr3O(>Z=$OGL&3R@R zOJ~_kH-B`rTti>JSeLWN(oY7=waazEj5blee5hI#NI$<(RV26wWR(B1D&MoJLSHV| z@HKbz!ac(K*did6TG0B8S>M!{NjKD|k_n?O?w}AhLDRV`!_`4TM|bp~L^U^sxn4doAl=qk5-zu%wqC!_wL zTlTg=B62c(5Sdh=zm7Bfv3$_rmOw4c$lw6Xl%yFWV$ zn@PBL>$?VSOgUT*JVl8gQxDC3Ql>MvJ^SU`_uQyN#>~rlGpI7geYwtcq?xQS3k?p` zx!!QXvq|3MkQxwRX)EK7Ph~#T&=-@jqz>zX}jpi-?9jJC4>ntV}Xr%!o z6ciQ&cQ%zjv#-2B7YbXNTQ;ZC0b}&DcbC6Q8>v`KnQ~3@)Kn^85I=T-Tp(we&*#6a zcYp&iiYlwV8~5D)2FJgaV+pcM?70fNSk6ZYL(cCX#PX3hS!i;G7deDGTj?zp%S;$UeWTNoa~b_iUk zvHO-a`)}C4=J@f2X+(SAYI?T1(P8PY=1T|pu8C@FYn)89cM)qs!|ZA^TihVT13o7( zA^=MORIs%L-Z6}f<3iY0kQ>5=y)|&?LiH1NnvjGBNeE?>LMBlNB@|(?{Mg~c<;Q6F zC;-~Pv9@o2d*^qlyloqmze{hY1LZG2SN`%>zCs6n^uZ7Q{DD_r#TLJnK37N$Ns|pG zh)~6XpixPM@FmIIeMDiR0{0OMvcY8;v(eOxOSm=`b?a)4R^xNqSG$o`y0|MYLOWvc z0;&2i=Toa(MbU}qoysl_^v}A)Pk);D+0RzFg3g!zyfkm^9O>qL$lm!g9);KR;+#=n zM~ZPDD9mZ#RTAb1m&50lo5RgSz?*PkbQ?EvjA?2tS`+cO)vy-!xs^*R&HdOagHIEI z#HDpz8SwHG@R1;wRCM(q(|eNEI4{p@UdNSvYH#X_yIh@1TyZUVad|GTY{I!@?)}nA zV2uo1*e84$_BIbN_+~SMMO5(x1{HV{2+jc)Bj%|>aO`!s4+9gfLU7O)gn)3x(&j)q z=I(D{zu(f|?}F{cw$v|U3PVDS2iS9}+kssSFSujrz|v;Ku;RV;H#XQqAiQZdyUd;o z#3h@7}pd3*ZD0xVYPz@_J72`&G&h`u_&n ze|va^*uQR^Vr1AfbudOZP)}yU!GR7EAfdo^E$D}v*sv@tD%AET8w;p`($l@X>%&kw{(s*$o%R4|qrWcMM2i+;&ddu!3s3V2W zxlA*{W&m=;fJG}L!f6Hi#?9QTXv6C%!X;Dy*FdU4>3)N~(BHEB z))5MLbi1^4eEB5`Gsz5Fq#SdVpMX;vG=o)g9{bcPQI4K}>{At4E%X?d)%XH1fY;sx zX%}M-6pvApqf&#|0X6{Phnqt<%*U-d9HS?em7DPJxntApCue3pIXiJ?e>jr)>O1jj z-NAd=+4*ya4tr0X@*duF$4u|cC%^p3nSo;)l#}-#Ja_1_dk=z&vQ>1%#mC&HU>d*( zzztK7tuB>35FLVtH-M?uz(tM*E_^gV;s|Bvv?XgCrQxdVvB;&3o!@?@B56FwrH*g? z+XFxU056kVFhDpBj8BWjsX01m0X7&AagJ};f@Zdp59AbPA8aQ>!2l(Img@p7!4{Wp zQ{heQSJ6|zI+OvH>%ypUYRbI^x(0-TE~JvcoH-tgu4Clj7vHl@5KcUE{4=iroqErY z&un}69ar`+Hgf!f+pCWRW9waiFn{CMPL_Z1RAX;%z{zv4fdE|lHP5@87Y#nwTw#DJD^?A68 zJV==)nZyQ|#Kt|Gu{)8?+4-b^N?7DV7KQap;_?8WAGoYckSD+J!4ItM?e1)8Oc{Wg zf*!X`BQEN4SkncXF*h9m0qXJbJj-bxa}E@Hm@6OLRmSIXgQU7&#XttUJmz=}Nu z zY)9evWD}N-$g=&YG~M*b56=2h-Rnl{ukCzc$JN{JnR@)SCq{lqUFo@=YtKG%ZNuog z?v!s8-8Mk;8_Xo%=PvTi3Y_6ChnQ!x15i|r+5iqE}ELeczUO3Md z*$lKq`3Zuj#5#dZ^S8zY-8l8&;SwU0mV4_gv(-BIgyr6wU(u_P~#)j%+bEL4B%$ zr0WB_SE&<}&?K;M3`V?6=ExB&iwf7nKp^R*`#I1{0vV13EmvbNq%nW|nbI@401HN$<$~ZNsQ9i%8VA2J;uyocmnR$VfXJFtV^ei-be!ceP zm+A3`b}#0dXs-NcO$)iZ(9#vXD?$>aful=F$XE@4vY5&132_UUDNIFxG-2$3*f@cX z<9v^DzE_{Bi$`m~`64uet#+J5VS|vLyYX^H2ddLOAXMeOjFj*&lAGOC-?L^-Pp~uJ zx3;c&cl6AER5kJQWj?Hr)O6Js&OCIc5U7v(w93uh*Q`lYm2m$eAMcg^_mVjI9hjDm zjhE7ZOJ^Z4)=Ek(OjA-_l-0;m4X4g=8Y6P7m;k{RxZH0`GSQ$BuNWzYsTba*%nQ>3 z_&W|PCE-<>uRv9Yay`;9FV-GDpq<9~=n+x*nJyfdDsL z1oYA3`YjlkIt&D%Jv0XZH3>o-qi#6BLiokNE|J&Bf0CcV|LGsdi{yLcui%vRmvG?v zDx9{y1b3~c$Y;o7K9VH@Sn{O5RC+i`)nowS#0g7Uvd#&tqf+%&-s6 ziCv@>ti29CR0({cz*#PAd-2IYWv`q&51<0`ZZuoh3Tb3y> zA&5ep#c`0Bp8&v_JXlVe!|Spi42RoYEs+3mIY$guSRS5MBXA>(WpAmzg!P1b>LMv# zSmjjsVh>IxXv1zVP?#h(u5lRxaga$WX_aS{j-yDd>SRy>&P1(uyuQ34)pwl9!qn5v ztI1#GI=Dv!^0K5kUc6OBR0j-EtW>YE^DThsQ|%f=2{7hwNE*&wXT;n4$hCdF&O7%u zZ{D@J<+E{L)aBRpRvjbm6YWmfR~z&92WlnNp{trl@$na}y6$s10**Skhp86q&Eoz# zJOC5I&azf#(C_le9-A*#>tE-ON8#7u)0=rRzbopC`=gE^bH$C20T^1?5-bD0 zaH6gqhX{!MiaivrbI0vo{be&$UA&fYv`Ntc(W_`RUNpJ9y5AAxpeNc4cP0t_f_nu(gtwB^r>IC7s0Hlr zi@JGNAeGPQ_r+rVy0{*4PR89~#p}1*0$#=Ea9C|?O~ko#olQlLgq#kaB(+Mq*Law@3*Yb?6;5gk;g?#6D8~t!{pE=aM9E$0!OvcXpO3- zk~PiIW-uhL>QOo4_2OUqp!1jEfUvK^9+R$EZTYs=Ty=kZTBw4@@KIY) zyJs#z=f+z4jWnCB?+SQB5qQH;r%_Wg*6H&3V}219yNjCn>v3XMjP^F|ZA}FmJb`pm zEvg%CtLs`@KI4nQ{4UwCy##HCq_YTI$YhI4Q*a(gp!S-Lc4a(ziL}ObCli4sKmKR2fqvjlbbLq(04kX6OJy^Mh1*04{ zOL3{39}& zB!f|%7fd>XIqm`{&scj1M$I9(7r~#MW$6 zBZQHRkqHOgaZj8hcRG+CzHx&i0hOWc#vw|U1rjI_-!@cznigLdjs7e49K-{ImIGP& zS)1Dya4CHEjth;*r_=O1tKQQQL=D6{E!Q;-aa{pfgFj&)W<6<$t$K~-^5qK}K_J(5 zz$B2Uc>KrPLrfFK2bc{WVv^cP6{)qE0k$`zTz;$`EwmB~n*E!^GFKwJX*SCzv9$Be z;_$u3RzDCaLBoM7IOj;Epf4(Ri>VSe>H;QJu=x+ZzvA+VEA5D-F^#}CqnZw6$fmMi zn#}x73&O?&*XE5wW_`9h6>RGCa^Ea~1eP!f9&8!oxi}D^IqvAf#ZAtO1@5}&$-@?E zxx!d(Mb`Te=gI|ko`L*1FoZR6E$UTbiCnPR1m7{7xkg`eUfquXS_yC5A`w=N(hj4I2d3H0SzUcyKhaSrPx#*e% zEB}$=6*3j5&4L2hHWc_3x3 zmLnl5atLi%jO057`AKq6HC73deCHO2O{}+Ehy>;P6%r($M->s0Zy85E+^GktAVv11 zv26=tY$u*zt_f2Y#kFQQfma1CXOII08W&40N^9JgJL<)D5gkGec95({XXw= zBmsv^2;erd3h#7Tf(i=_ah$94fc8Z?$|Vaw%q4CO9+q1hEmWi zFm>b=Vc@XK=c~0C{ea=5X+;luyn(LzS(c`E<;H3*wxfKLpO2kvQx)MKj6^e}>8RV2 z4y0Q5zGH1yydCXY;dyRYH{cN}!b-y-(+Mj8n4bGs!GS_kg8}Z=q@vmt^X4)yTjpLy zHNc`(epO*oj99mHrox6+{c;S{yzmX!tWQ`xHw0CWbKN{yONO!MW;?k8qgzUVCR_!Q z!LUysZl8h;`;fsSAf?$gS+iS1pz$G4B;{~8#QfHNq5$`;0tur<7jdE+ zU|s*(HGRF@UzN)c4fT*5tdBc_sr)**fW7Yi_3uNlG52&Q{UJpa>WFjo;QF1A zebxI6)I$D*Jz)t;Yr50v?)1c}aJeEyRbJGw#M0l>Mf$Oiv4M|$jM6XBrN3th`XwHI zhVq+9OODDRlXUme%d2i^aMg{?P~C3TDF4iWiU*lW6%Ca&S~s8{{1$@12<$FTvp)sm zk%4?Zf*p!`;m3Ikxtn?`yX^OcaLk=x!wIQQhD|s7Fnb_`g05o=88{f!5-mVw%no3W zV24ftAf!D=Z8+TkE}0SPmRxQa>-<#2F1#Y~=(=Zucs%-47%p8pwx-Wm1=t=+Cz#S2+?G)fw>556nI-*3dUUBXMoFl_^6S$mmcmc)`;dL*qeHeHy3J)#)tq9y0 zB!aU)u0Dux;{99Msmc6-;^5b%zty2zuoQlmgqD6!G#!X`L}9_V`&^pl0v*&ME&Z|f zLPr$OZ7#?t8jitrV`~|F?N;6(eatW5p~i-w;mdGz)csam-TzY=+lzHS_KD;7r~X<$ z{^&#f&4F~(6KjtJ8e;BKm7?4}SNZ<_W<0b?#8aD9ZB;$+6|-!@oEu@^Ek&XO}>iseDc0*4J)Z{?h&q zdPT?5_aK`>*A87cCHy0M5}H>M(AOYYXRfKzgw`ya+UnwdoUUV8(?Vb@CNJ^LOn~k2 zP%C%m;YQ9vrehs;9zzOcO2`r%ph?N_Z_jta1+HSvfunn^5XX#wam6~RNBHLUoq;2V zr|9~lAJ|qnyk{aB>nc6}nFA2F3^s}2|3%PX?D$hhk1h5r#SRCzZx2!fKSvISDckBK{u1IB_);tnRDk8wM1MmRP`S8xww#=-++huCb|)yzPqi_A74q@YGj7vcFa> z`#Pr0hBIF|b7p_jSmX39)8WQmYJ>-d2Xl2TBa;*T6O$v&CpL@+Uq}z_?mTesyU^$;J$Wh0RSR-uSsZ{90s1NJo z^;!Cpi}gW0SE{pW%xbxZLC}lJpf;$qb$-)fL?9MX;IoyIv6w%V0x4|3xzyP5iN&7a z2g&gT4#@|!fWzlK^c^rDhP!V};VTD?mZ*e|eAz!sF1YWQgs{uz4 zN=9pAsg!_2B5>kml@sG!;sL&igCk+qxngoE&F(-wRt<=NodXz=H&z4|eUXpl#j2na z;ujuef6Zac%e+%n_G=g!#AbTqD@yl~s{gLPmB*`D(&r2)-f8y|EX`qLl)vtT6W;Yvljv z9J2|ZlTORJ{Bh-!x}bhlyFvSz?PlBO?6BrL{@wXE`bpOw&)2880RriTXr;Vo&0kWUPL^{#5-d4R@tU`liecncr`Gu1RmYx#_9q=d$kX zK=$d@jjeyz`fqJ|Te@wa?Nr-0+Jo(T+HbN(WG}thGL(UYz@)YVCqY0+(H=y*_{TvW ztd1K$>uq@q@gHsA4xNOLfPTb|Co3%CX|5WUklWg7n8&^iF_AHzxXF8}VTSxaR1FI- z>3y;q77^#C)vyE;;Du^fhGXQJ!~2gPn?HW+kb|;y4pI7HOBH= z1UqxBot>>+9i92*hnF9??)t;Wj~Shay4E;(a(?!0CyyUGeC(vrctb~9Uw4zydX3R* zv|eZAJB-#1Mpp+eHyNGXM(d46azoN+oi|#qU(OBQ_xs6l9G@|7{hq`=f&ssV`694AOENXBPZx12PF@y|E&>N6jn`JF+!hI`-*&w}E=sNu7t$MZMa3fyJ zj_Tb_xa-8S;qh<8ZxZj5*4@1I%jhoOX zIp&b{9`mqzU4EXc+*i|eRnv7Xr^{8}_f*sORMYoV)AuaD@3!*ERm;g$%gJ?A;=u@9 zd0)*pSIsw9%{N!gC)ZQSr@NX@cQv2xYChf7e7ZZ9#nBjuC%ASyPCdQ|L$tPz#>#BuiBpO<@WSc z>)BJSXHT`9LN)(FHUB~-eZJcMe6{`gYWwrm_UEhZ&v#Vv%~#u>@2IpR-%-gwUme$c zwcqpA_5xe9>Y1;$J6~-_zFPl$wf^~Pz4JZQe5>`&_g3ni@2%83-&?JBZ>3-Iy_J5= z_g2g4t+YGeTg|7p7weDW{~>a{VR?3uE+Gu<6?}X7S|Zt!F*Dy}vr%%r! z%H;G6o)}gn9_rzto|)(j{?C?ZiL?}psS=r-K2R*SmRQz6K4QZxsvr$dPM4(AP)SY= zMH7i)iO#f^glr1+G3L%mR}C5b&Eq!g*G(WeY@9YuBfE1QQp4$~>6yvs?6%@`s)%>y z_G!F`^4eF6YAuP`l8U?)d5@9!@;JI}cxI>+JP<>Tq%7X?O0m)XB|3W5 z%t&iVUVcVeQs+dmVa6Ceotov{X0?(;c~6v#D9T$dup~63W=D`#$)1y>(Gs0SSk0nM z2D)f?Gw%TeQbWa(o!@Q4ogH_rB`s?_DUz%B&wi9ratzNHr)P|kBQ=z2E!nc;JEqSm zbfj2cavn(C)LOD<$ET*pw^trU6L{{ko;$MVh;w-7^f{+<7{w2jw3Z@oHftEFw)mV4 z;d40ec&J2if-$O$Rv?DhFmxJyi?Y?`L<(t_!x%j)J@T;L715FnsQ-qU(!y0MJ$4Qo z4^ty0I$Rdu~H{OZY`6ZqAeJty+34;{;|{_HuKUjx~53cm)k=Tv?TWzT8+8qSuK zmS3~%2+9s4O%2NC*C@*7*BHv?*IJa#uW^*kuXQM!Uk#MauL+dRuSt~6uc<6rs?Qk1 z=*1b{b@-c_P8t2UO_jU{Vtk&rmg=*mR7)w<+*)eL8pa09kmbpjn(a*)r*}@j{vpP> zwUoZtr8HP-XfDw}ht-3b)h)jCRb$r3TkEPROG-jzhGVHLm&b!7_&JMyj0~iD&o$Bj zYSo-I`cZ@BSTmRgv%RgQmTX(7zqOQoleZZB{fNGbIhsSQ{> z(^sJlC5i=|rO;MUN*h*L5R(soR+N%F+H&A@Tgou{Pb07PmDfgFC6AKGsl{lEQJUea z$()*gf*FDleFB?1Yl=fdn9~}_1j<8+sj-=oIE;ykwM1Gfw2BSS%%w`g@a!CBJ{z8m zB0N*XLO`@x6bzb}8k_BnrjXAVA3kl^$_rUkvO)=<3AjO@w3H+)3yG&iI#5Co3Z6TD zK>?$wL~$iYj9v$CkAX6zbhSOHel)YwdR)@5NDN~vg??+nBxo&lSZ?Ac-=} zRw$3qK5N$CaSv{;%$7PY#-8CfR_s|T#A9D&g?QBc*;3cVsB`?rih95b@u>4wh(|q` zE#)poJ;ZOUsE4f(k9x!k@u*j4TT1GIl2AW+6DQvQC&!;8U)?s1BdYE#{(%bhdcOf8 zIRfUKI#JR`4Fm7>sqvD$efM;!J6dWi&Oq+k3F=i{=2w+iYUBx=q9L|CVYBmJ-G$%c z<6@&Wx(#ofve4)z!pONazk|%dZy{aa^CZ1+XGy&0oCKJJkpB;f C=Z*>h literal 0 HcmV?d00001 diff --git a/public/vender/jquery/jquery.base64.js b/public/vender/jquery/jquery.base64.js new file mode 100644 index 0000000..8cda79d --- /dev/null +++ b/public/vender/jquery/jquery.base64.js @@ -0,0 +1,190 @@ +/*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false, + debug: false, devel: true, eqeqeq: true, es5: false, evil: false, + forin: false, fragment: false, immed: true, laxbreak: false, newcap: true, + nomen: false, on: false, onevar: true, passfail: false, plusplus: true, + regexp: false, rhino: true, safe: false, strict: false, sub: false, + undef: true, white: false, widget: false, windows: false */ +/*global jQuery: false, window: false */ +"use strict"; + +/* + * Original code (c) 2010 Nick Galbreath + * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript + * + * jQuery port (c) 2010 Carlo Zottmann + * http://github.com/carlo/jquery-base64 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* base64 encode/decode compatible with window.btoa/atob + * + * window.atob/btoa is a Firefox extension to convert binary data (the "b") + * to base64 (ascii, the "a"). + * + * It is also found in Safari and Chrome. It is not available in IE. + * + * if (!window.btoa) window.btoa = $.base64.encode + * if (!window.atob) window.atob = $.base64.decode + * + * The original spec's for atob/btoa are a bit lacking + * https://developer.mozilla.org/en/DOM/window.atob + * https://developer.mozilla.org/en/DOM/window.btoa + * + * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255] + * If any character is not [0,255], then an exception is thrown. + * + * window.atob and $.base64.decode take a base64-encoded string + * If the input length is not a multiple of 4, or contains invalid characters + * then an exception is thrown. + */ + +jQuery.base64 = ( function( $ ) { + + var _PADCHAR = "=", + _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + _VERSION = "1.0"; + + + function _getbyte64( s, i ) { + // This is oddly fast, except on Chrome/V8. + // Minimal or no improvement in performance by using a + // object with properties mapping chars to value (eg. 'A': 0) + + var idx = _ALPHA.indexOf( s.charAt( i ) ); + + if ( idx === -1 ) { + throw "Cannot decode base64"; + } + + return idx; + } + + + function _decode( s ) { + var pads = 0, + i, + b10, + imax = s.length, + x = []; + + s = String( s ); + + if ( imax === 0 ) { + return s; + } + + if ( imax % 4 !== 0 ) { + throw "Cannot decode base64"; + } + + if ( s.charAt( imax - 1 ) === _PADCHAR ) { + pads = 1; + + if ( s.charAt( imax - 2 ) === _PADCHAR ) { + pads = 2; + } + + // either way, we want to ignore this last block + imax -= 4; + } + + for ( i = 0; i < imax; i += 4 ) { + b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 ); + x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) ); + } + + switch ( pads ) { + case 1: + b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ); + x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) ); + break; + + case 2: + b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 ); + x.push( String.fromCharCode( b10 >> 16 ) ); + break; + } + + return x.join( "" ); + } + + + function _getbyte( s, i ) { + var x = s.charCodeAt( i ); + + if ( x > 255 ) { + throw "INVALID_CHARACTER_ERR: DOM Exception 5"; + } + + return x; + } + + + function _encode( s ) { + if ( arguments.length !== 1 ) { + throw "SyntaxError: exactly one argument required"; + } + + s = String( s ); + + var i, + b10, + x = [], + imax = s.length - s.length % 3; + + if ( s.length === 0 ) { + return s; + } + + for ( i = 0; i < imax; i += 3 ) { + b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 ); + x.push( _ALPHA.charAt( b10 >> 18 ) ); + x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) ); + x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) ); + x.push( _ALPHA.charAt( b10 & 0x3f ) ); + } + + switch ( s.length - imax ) { + case 1: + b10 = _getbyte( s, i ) << 16; + x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR ); + break; + + case 2: + b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ); + x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR ); + break; + } + + return x.join( "" ); + } + + + return { + decode: _decode, + encode: _encode, + VERSION: _VERSION + }; + +}( jQuery ) ); + diff --git a/public/vender/jquery/jquery.js b/public/vender/jquery/jquery.js new file mode 100644 index 0000000..e0966d1 --- /dev/null +++ b/public/vender/jquery/jquery.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. + "use strict"; + + var arr = []; + + var getProto = Object.getPrototypeOf; + + var slice = arr.slice; + + var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); + } : function( array ) { + return arr.concat.apply( [], array ); + }; + + + var push = arr.push; + + var indexOf = arr.indexOf; + + var class2type = {}; + + var toString = class2type.toString; + + var hasOwn = class2type.hasOwnProperty; + + var fnToString = hasOwn.toString; + + var ObjectFunctionString = fnToString.call( Object ); + + var support = {}; + + var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + + var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + + var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + + function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + } + /* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + + var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + + jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice + }; + + jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support + } ); + + if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; + } + +// Populate the class2type map + jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + + function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; + } + var Sizzle = + /*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ + ( function( window ) { + var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) + try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; + } catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; + } + + function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); + } + + /** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ + function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; + } + + /** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ + function markFunction( fn ) { + fn[ expando ] = true; + return fn; + } + + /** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ + function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } + } + + /** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ + function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } + } + + /** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ + function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; + } + + /** + * Returns a function to use in pseudos for input types + * @param {String} type + */ + function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; + } + + /** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ + function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; + } + + /** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ + function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; + } + + /** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ + function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); + } + + /** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ + function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; + } + +// Expose support vars for convenience + support = Sizzle.support = {}; + + /** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ + isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); + }; + + /** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ + setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; + }; + + Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); + }; + + Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; + }; + + Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); + }; + + Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + }; + + Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); + }; + + Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); + }; + + /** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ + Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; + }; + + /** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ + getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; + }; + + Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } + }; + + Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos + for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); + } + for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); + } + +// Easy API for creating new setFilters + function setFilters() {} + setFilters.prototype = Expr.filters = Expr.pseudos; + Expr.setFilters = new setFilters(); + + tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); + }; + + function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; + } + + function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; + } + + function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; + } + + function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; + } + + function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; + } + + function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); + } + + function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); + } + + function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; + } + + compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; + }; + + /** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ + select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; + }; + +// One-time assignments + +// Sort stability + support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function + support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document + setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* + support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; + } ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx + if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; + } ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); + } + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") + if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; + } ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); + } + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies + if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; + } ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); + } + + return Sizzle; + + } )( window ); + + + + jQuery.find = Sizzle; + jQuery.expr = Sizzle.selectors; + +// Deprecated + jQuery.expr[ ":" ] = jQuery.expr.pseudos; + jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; + jQuery.text = Sizzle.getText; + jQuery.isXMLDoc = Sizzle.isXML; + jQuery.contains = Sizzle.contains; + jQuery.escapeSelector = Sizzle.escape; + + + + + var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }; + + + var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + }; + + + var rneedsContext = jQuery.expr.match.needsContext; + + + + function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + + }; + var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not + function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); + } + + jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); + }; + + jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } + } ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) + var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation + init.prototype = jQuery.fn; + +// Initialize central reference + rootjQuery = jQuery( document ); + + + var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + + jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } + } ); + + function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; + } + + jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } + }, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; + } ); + var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones + function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; + } + + /* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ + jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; + }; + + + function Identity( v ) { + return v; + } + function Thrower( ex ) { + throw ex; + } + + function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } + } + + jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } + } ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. + var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + + jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } + }; + + + + + jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); + }; + + + + +// The deferred used on DOM ready + var readyList = jQuery.Deferred(); + + jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; + }; + + jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } + } ); + + jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method + function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); + } + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon + if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); + } + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function + var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; + }; + + +// Matches dashed string for camelizing + var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() + function fcamelCase( _all, letter ) { + return letter.toUpperCase(); + } + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) + function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + } + var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); + }; + + + + + function Data() { + this.expando = jQuery.expando + Data.uid++; + } + + Data.uid = 1; + + Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } + }; + var dataPriv = new Data(); + + var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + + var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + + function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; + } + + function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; + } + + jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } + } ); + + jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } + } ); + + + jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } + } ); + + jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } + } ); + var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + + var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + + var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + + var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } + var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + + function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; + } + + + var defaultDisplayMap = {}; + + function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; + } + + function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; + } + + jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } + } ); + var rcheckableType = ( /^(?:checkbox|radio)$/i ); + + var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + + var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + + ( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; + } )(); + + +// We have to close these tags to support XHTML (#13200) + var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] + }; + + wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; + wrapMap.th = wrapMap.td; + +// Support: IE <=9 only + if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; + } + + + function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; + } + + +// Mark scripts as having already been evaluated + function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } + } + + + var rhtml = /<|&#?\w+;/; + + function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + } + + + var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). + function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); + } + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 + function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } + } + + function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); + } + + /* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ + jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } + }; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. + function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); + } + + jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } + }; + + jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; + }; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } + }; + +// Includes all common event props including KeyEvent and MouseEvent specific props + jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } + }, jQuery.event.addProp ); + + jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; + } ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). + jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" + }, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; + } ); + + jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } + } ); + + + var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows + function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; + } + +// Replace/restore the type attribute of script elements for safe DOM manipulation + function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; + } + function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; + } + + function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } + } + +// Fix IE bugs, see support tests + function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } + } + + function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; + } + + function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; + } + + jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } + } ); + + jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } + } ); + + jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; + } ); + var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + + var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + }; + + + var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + + ( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); + } )(); + + + function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; + } + + + function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; + } + + + var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined + function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } + } + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property + function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; + } + + + var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + + function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; + } + + function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; + } + + function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; + } + + jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } + } ); + + jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; + } ); + + jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } + ); + +// These hooks are used by animate to expand properties + jQuery.each( { + margin: "", + padding: "", + border: "Width" + }, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } + } ); + + jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } + } ); + + + function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); + } + jQuery.Tween = Tween; + + Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } + }; + + Tween.prototype.init.prototype = Tween.prototype; + + Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } + }; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes + Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } + }; + + jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" + }; + + jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point + jQuery.fx.step = {}; + + + + + var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + + function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } + } + +// Animations created synchronously will run synchronously + function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); + } + +// Generate parameters to create a standard animation + function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; + } + + function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } + } + + function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } + } + + function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } + } + + function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; + } + + jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } + } ); + + jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; + }; + + jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } + } ); + + jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; + } ); + +// Generate shortcuts for custom animations + jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } + }, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; + } ); + + jQuery.timers = []; + jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; + }; + + jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); + }; + + jQuery.fx.interval = 13; + jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); + }; + + jQuery.fx.stop = function() { + inProgress = null; + }; + + jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 + }; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ + jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); + }; + + + ( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; + } )(); + + + var boolHook, + attrHandle = jQuery.expr.attrHandle; + + jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } + } ); + + jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } + } ); + +// Hooks for boolean attributes + boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } + }; + + jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; + } ); + + + + + var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + + jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } + } ); + + jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } + } ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop + if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; + } + + jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" + ], function() { + jQuery.propFix[ this.toLowerCase() ] = this; + } ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + + function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; + } + + function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; + } + + jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } + } ); + + + + + var rreturn = /\r/g; + + jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } + } ); + + jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } + } ); + +// Radios and checkboxes getter/setter + jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } + } ); + + + + +// Return jQuery for attributes-only inclusion + + + support.focusin = "onfocusin" in window; + + + var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + + jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + + } ); + + jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } + } ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); + } + var location = window.location; + + var nonce = { guid: Date.now() }; + + var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing + jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }; + + + var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + + function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } + } + +// Serialize an array of form elements or a set of +// key/values into a query string + jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); + }; + + jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } + } ); + + + var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport + function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; + } + +// Base inspection function for prefilters and transports + function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); + } + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 + function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; + } + + /* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ + function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } + } + + /* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ + function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; + } + + jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } + } ); + + jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; + } ); + + jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } + } ); + + + jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); + }; + + + jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } + } ); + + + jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); + }; + jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); + }; + + + + + jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} + }; + + var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + + support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); + support.ajax = xhrSupported = !!xhrSupported; + + jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } + } ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) + jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } + } ); + +// Install script dataType + jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } + } ); + +// Handle cache's special case and crossDomain + jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } + } ); + +// Bind script tag hack transport + jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " diff --git a/src/page-subspecialty/views/modules/template/formManage/index.vue b/src/page-subspecialty/views/modules/template/formManage/index.vue new file mode 100644 index 0000000..bb5c447 --- /dev/null +++ b/src/page-subspecialty/views/modules/template/formManage/index.vue @@ -0,0 +1,53 @@ + + +