From 58e75d03c2f83cdbf28b1f61d28535ab7985b52a Mon Sep 17 00:00:00 2001 From: ValkaTR Date: Sat, 12 Nov 2011 22:47:16 +0200 Subject: [PATCH] utf8 support and truetype font --- data/font.ttf | Bin 0 -> 369888 bytes src/CMakeLists.txt | 19 + src/cguittfont/CGUITTFont.cpp | 1141 +++++++++++ src/cguittfont/CGUITTFont.h | 377 ++++ src/cguittfont/CMakeLists.txt | 22 + src/cguittfont/irrUString.h | 3399 +++++++++++++++++++++++++++++++++ src/cguittfont/xCGUITTFont.h | 6 + src/gettext.h | 18 +- src/guiTextInputMenu.cpp | 8 +- src/intlGUIEditBox.cpp | 1481 ++++++++++++++ src/intlGUIEditBox.h | 160 ++ src/main.cpp | 25 +- 12 files changed, 6642 insertions(+), 14 deletions(-) create mode 100644 data/font.ttf create mode 100644 src/cguittfont/CGUITTFont.cpp create mode 100644 src/cguittfont/CGUITTFont.h create mode 100644 src/cguittfont/CMakeLists.txt create mode 100644 src/cguittfont/irrUString.h create mode 100644 src/cguittfont/xCGUITTFont.h create mode 100644 src/intlGUIEditBox.cpp create mode 100644 src/intlGUIEditBox.h diff --git a/data/font.ttf b/data/font.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7513c86a6931eaf02d766dc5b03b1ff43bb77bb GIT binary patch literal 369888 zcmeFadwf*Y+4%dcz4y%IGD&7~hukKaTuDemE+l~f;U45-xTpaUf*_)zqGCk_rHX<| zEmf?j)S^YD6?GtwlwPN)at8z7h&8T2NF_$ei!mGeNZN+jBm@^T+w)V88oW zd+l|7*6mqq?@2|8NFof{YVTCxZS%q)r*ioa$JoC+y?P9$?Q$!6K+j8dA z(mk2?i1qFgkqw@)Q_mVPWqiRPv9_-hiP?MR)QYOn1MWVHu$zc~_OuIHeZSmVa5w2~ z6zShM_tH6wj{dIummsa!9iSZv&SsF zeD0itZSOB2XfpSUE}gSvQPdwz(pIYo;Ja+jr59Xr&C`#Fb)8SdabVHqS1ta@Z{n^N zYk8E&_{oc|ykODusdK6*Uq0a<60IxgsJ`}zBUj8$X#7%g%mdozmDx`m?3LgCtmi?; ztJY)OM^OwrhQ69BLQdJ{Gt{5}AkpY-2BRZ;Z%Wk)={*Y!bimlpQzT3`jg_XbfX6WzUs)l%*b-!u>6jVH)>y z&}W(9kjZK*aUJ4E=BWX8Ju)_u? z-AAwGxvoF`Me9hH@xMy?GiB8J{=TG-+vTef54!E9es3Y)MZSpq7`a`K0p0h=bA;ie z{EP?PCm9dX_~+?yq2}`Z3Yn-zk%t~1y3S!a-1sb#2W@2Q?FZ7_E*a=7+h80i`f@}z z>M^DJ^?#I%H@naGO5M&5nP|7SLncLJ%MYbJHW?r2!icm*c#JYm?6G>9WGr4lnnHWj zP;xu+M78#q&Q~eE3=x?}NbYHal!$GN~>Pgg4Nup7XXc7nKp8vFWIl+14#G+uS04^DE|_67>9yOt#kJ_h;~sMx_7HR-e*V zJA3o)tuyIm*<*@+eX8x5%j_~o`t7N5(59#7c`AIQJ$uv9V~hIbFkd{N_A(ZG`|w$z zyVcfEsJAbw)SmY#(`~)|$k-ctfq3pyWxO?XhdMj-QKY@|jHg16q3e|1vY%@A-uCV7 ztBth#-)V#1e0%#-Z+kY8Jm>1{D3(O%mN|Bl+NvxHV zjrj1qt>>fON7_x@=XkN!@KHo z{3nJ!VH||#|3*o-`ww-s%NSk<;?}7=AF1uwgekJM)>Uf;_ zeSGLcbujb~wOHzP{d>ckgVEWm==MtFjg;R5PrzH$Bbqu7q16bfREL}TOD5rFL92YoGrcAq zl{a9jeLpeuKJGd2d(zRigO1mm&TP`rgwv#qJYPs3`GCIqiB1D9L9Yjqv+Z*Il=61d zhqR4UM&Hos|Bzhr$kp~u&!zAhTnw{GV>DxC8gA^`(Amgq02{SuM8u6f+4CX%6#9S; zcOlfmU%0o^qr8{FO8l+^?600*MOcfx9zMYTO6bjl^g}>~h-{M=B@WW%a=pID zbh~|1nFAi6ooQp^MCh1#3GUw^t04#l+((7p)8iZ3Y`beX^RBiBurF9wX4!KP>ymAk zF?LSbWe*UJ`9O!EU(RODs?&Cv4sW}$$s+DD@=vne*V5mgB%VRPijp+itAsUHkNXnl zpbX}LeEr;>4|EtkK2qTm+FP%wrS>zipL>WimcFdl$gNV0Em@*o3w>>;HP%i~KQEzP zxzur|?N1zAYCoO!QCkk6Z1#B5_9%XJ+7_lx+0>&S<*Bswflkb7#vp4j&qmmDrnXsA zLnn0^tT^g;l4mCfqixt{aBI6XEW`SZ=$N(4wkNRhO3-ta9Y%-K@%}_Q+V<7=TEALq z{pvi?uTImp-F4c3vg6FM2FomK0_B^B{;#DS!*+I+<>mRU=uz8XdJWR{*jna^4E(e$ z^9g-rt7IB|m`8xMYBKXn66>y>YbWbC_8hL=+E#mwc~{$3J-F%RMt(RR_mk1#WMdL@ z{Nsex`RH`tCmqwyBW&LtWL>@$+vQox!rUs@+^k!Fw{4s+$oC{^I0&Q5q06K5LYix3 z1iBwV9#NDj5xZg*x@a*c&?h!XymcRS*Y^SOF!y^b+QAx1-g&l7o@n&Ab-UX-3fl;i&1~BEQsO;BSae`6r>)OMpSq3FGxxKMmDnV@ zO|eaMo$U7MwO{qxP5aRH+SW2ollK0^Tq6$SSKp=ATW(vjwxYL0@=5NMjJsK<%BuUN zuG4PXC%BLxr{B^JzP*-P}V)+^_lxb{Y;0I z-4ctew4bp)^Gw?~9(A5A`{*$ApE6@U`5$HM>bVRXioSR&=ryHCbDHkk>^9SLfo{8A z+1p?A{zltq|4sgI-=pV9_RXXh-rw}vivK|}Kkj3U|Cu(lZQ*~Bv;g}R8}XEK`cm1NEeeHGz2@)+}vXlz?Y+dDsiwQe+E2M{mJac#S4yXk+! ze3*C-6YqWEg*lEq#+o4-Y_1Y)p%d^@gjbL+L%U7bwohUY@l}N5$Yb<1vC%fyzrnnf zd~f~Ne3_?gd+R?*PFa&IU+611#u}jeeD41S>+@(xG7nI{FvpR{EY3VNz3tGe&t6?o zx6ib_*<1GB@|?=+-?icCe*dK%d-M5E^1rpqsdD^>`v2dpcW?c_CmH*spJBYi`p;g# z#Li~lHX6oi$r>Z9BX$3_>?eq0?;FIS`=6=Dd1~k@IBv&F4c&?RPW1a;gm6Df4ZVv0 ztLk^0-?86_#5rci`C*^X_PZwHuk9L}AbILN%6Y7x>|->Xi&BSw?IY%ygw=bR^N{Rm z%*&)q?|-OY66uOQ%|9inuRbRyE!&TDMYcgEoc52h#@G=5RNSNT6yd+yFKhQi@|egv z-b?S^nSF#gg6EvUT6apF(_p?1?<+4eR^i5Nz_(#Ix}Gfupim=fQm)?KP$;sp9^y?KwNzmuFtcl zl6M(yVXe?-Esgp)gtgV25hG+q%c5)N3Vv+D_GTOQP`{X)J~^+N>8{Cw=C{*_yWR zcpha=K`%N#A7_;w>;rw)b`*O#JU7^SBfaps?b$NTZX?}JHmqq8-4bT1!d~aRuP=KJ zZLj+|SMwVdXKmV6KPqp-&0yEV)-!JID>+O3Cw;`!&mt6a&gV2rPjX(?b1y8l&iVF5 z_?bOk^!e3M`l>$n)%JAne0#cN-t0X?Kxc>1WwX5}|KYol@4pAxLw~Qa&j9p(q!(?o zX!O|9_DSSyxmQw#@Hwe&15K)2$g_1;w2X!%YdGs|nB&M}7H@oQusJMj4hwrD!g1s= z>tUX2Ch&X$&sXp~%yHx~>v^65`RMjp0c&BVhOwi^n0~kC)zTV?Tfc|Xdq>SN(0bDP z(R(yqN9<*Lf5x89-s|?BU+D7+;*f5(u9tnzaM~Gzalp25r_g^m2crG=%UOig{l`W& z(>~2|j{1=N=E>QN+pFkfsrL9SBkX>j?Y6@*7lz}Y%XrR^^cc^UcS5?)WN=WIVYIis8#rV|2?XH|Dp3u55rk@hK4yI zhOqjKO3!op3|8y;4$yh&{B=3>IGjQsIY9rMrq7B_hi5J96DRfrdoJ#o-7~6ZX3u*) zOM33@nborZdhagA#Z!Ahba18Gtf@4$h;t_|uleWMhOc&&A!BTR%ejf3@F<=xd6XwN zs9aT``m0(sNDWn^RI@ruU7)T|%hj!Fm0F|jQun9_)Pw3F^@!S}9#>DOfO=N_R_#I&NebIYzEgWK^J?=t z^9J*FbG7-9`KI}n`GNVd`K5J@b*uHJb=(R$k{ns_!?GmHl@*&6m(?dLGi!X-#H^`V zty$-0&B~gSwIu8Atb4QmnDuIQQg%jmQFj0A@!4~;ugShI`+@A^IVm|=IiqtX=A4r= zJ?Gq<^KzEubmqLA^G?oRbN-ogG^fWG=S%jb`tp2*z7pR6-(cSmUz2Z)Z;Ef4Z@KRw z-$vh~zF+$```UBO+_c>6TwiWpZc%P|?%lcT@|<~zd1-ll^Iq}i`TOPj3ygxv1+N$E zFYI5~RCsaWlZEX?S!Gw0r&nfdyyVg0kB)ovoX0XBD|)Q|W0jB9{`1Qt2}iPz_>asy za`lnrM{YT?{>Vc|HXeEU^GTmS^!dimfBpI9&!0NF;OLcKdcMs0>ea7ye6{PV55La; zy5Z}GzqNYiheEm~#U~q7zA93cszD7=!_`=IhH6y{)MC4x?zG$KKH6ym?ev)X4ej(4 z?ev^_fp+>IwV!tSR2@?%jBMJez$iDWj9S{M#dyUyZVK&`WM-N*W}P{PcA9CnnTzdq zT0uMAY5t0K+HHPBJMp=OwaVIIop2;N`bOF*hIUG)okr45XJ(z9H6!c1tTx(d4ej)D z*0yZPPS4KH?w4JeJuQ1d_EOquLyqL6=lF7(b6O(pG&^T`&NDf$JB`FTHRrzcLeQ{RmjKic$Y z^P^`!mT_7;$r107+#`iYW*@oc$W2F99(nM{uZ}$W`Gn7B(oT=kPEQ_fJ9_b#@?|3J z^yXLZ*zHtFJ4v{mLZ5_Q40VNG2>mYf+t8NKGoj8 z*L3%Ef75-e`>XDw-G{mlbnol_r2C`p_qyNh{!8~e-EVd8=>Bu}o8518zux^?_p9B1 z>VBpBvwj~0G(5vI}R-OgQ(InU1iWcCNM-9ldv5VGd1@2ha4(8Z2+xc5 zi2DC8|6kUCTNf@G>(u`DPDJ(!AC4P9uX!6-&vKFHBlDpE_dXX94ZA<$)iB*I{%-XZJtyb*o_j|25l zPef=!J_P~L^?NqLNaSBIr&@pW z;20c-OW_0-RR;2MWH#jB=KZLFjx_3LNZQKK?SwuI^kry0p%0@9v~KQ2q6^;o*}5@C z;|8r8qXk~V{Y&I4K-r8WAb;Mzh*1~r9%ce=;+veVX^e45!m*4wLwc%EtjYpEMMVcmk+1df<%S4CP2?*gPO-CYV9le1>PmV0yq#r*lj7ge! z*_ia3ELRxwFUS~(CHyXATo_&6taRvu|9i;HFy>#8BY|?6dywc>V|F9YgsHgqBhLoP zYyJZ{1J1?$84}%R&BA>ciT<j(Uh|RDfH7e;Ar}Dpw_1=(;Xd4y zG5diq)(yxFKv}FCMREjEaHF3b^36fP)<2OxpwC;BKc^YckApJhkZ#U7xEV`1)5ADQ zkmrVRP{theqj8iXXTvhw>=AO7Lnm&^o%0N!3y1c51!!BxPm#1=&R=o=0{H<@76<(? z2mR)tSBH-G6`&`_b0QQ|d~vv6M0y|@_YP!g7zcgBN4;D;FpT44B<0pPm~VWQ zFaY-_$OagUoBrS%62@@=Nk8;8;eQZ03dZ23Z}_H!anLt>w1vh&xqax@NBta!kq?D& zP!He6Fpe*fkA`tjH{Y+rIMBK8ao9{;>gsC`<2WXgYld+gM^eY!G{VrAa_J`;2Yo5m z7shcCNxd}=^p#89bLmTt5VAasQz6#?eU+0)$)&I6uEXsV$&28O!krk#8I2@Qjgv8# z$2iqEW09|fak7f~$y4K`ZT#riPrq=+^T{@T`F`9UkplA5I7z!;au_FLpy2f|PTI0y ze;6lyw2;29aZ>L>+Ck%_9EBH$anjC(Pr#G-_d%jJjWa`}C@YLJlWpl0VVwAt6G!7j zca_wmlCaJkk&UFe@e`A?WC@c>3eXN zA}O!NN&kP0KBjSMzuGWP+TowHx5il^azr2jH{p(WfpR&?{|MzcqV+ZjSqL+6qq8Hk z;cDEgkk`O++`mNL1h?S+HF71a$Bk}|JO~frehK+2*oga2$VcI6+^>p!J^{$b`5uyd zKG)&*APK8+(T1NB{&UiHQKrx7ry3V^{v6$Z{uJ(Fk)v&}05@%a6n!4O5_h$2_Q;pC zzpD{9ZT}_uc1;xdio7+hbCGYt4&3Db)jO~YH+B8$!!WM7m`wR$T=S6V>ubiX>jLD% zVO*Enrj&eZ;lGHpjrn0*SBt{_P}<&d-9)#KLFuCG|7q8$e~K^a!~c5asYgF>5Sf1_ z%b!D>;^Hg1Xih$2c{35mDRY8CxCB*~H@sglOmSbd}~?^`li%(9t;eVm=)986~4- z4DWB6WgK77jF$;AQ6|Zmoa{`NDKb^2$ysu?oFlC=U1rF+GE>guP3vrFlR1?CR#_>x z$sKaHtdj@jSMsoIMs{e8RE3#0Q8V}1R`K|nzFE=l9 z#wz*EIKFmtJNCP-a9!#6 zNUnA4cI;seUm>gIX8DQSE8QE>-0NCD$arIV2H{#yW_&t5!=D>)l(q)O z{AjxUVES~@bCku+oY6nvEDPqTRpjnlHFIW0KuE?_7RQ=+EJg8n>#(@cKdEh_)cdS6E2-6!;?!Uv1Puu z?drJ8d=&v#>C9H2Z>)dJoQr*}zIo?|lhEPfbk1b$yWKbT_Azt(xBG7Q+u8X8GNm=p zL`dqVvkf#|pl^s1Z|5{*S9)%4hHuyHsDVi1DBD?)vQa*QC6xJnyCRwUeXZlCX5DZiYmf!K*8v9f?1v00s4r@z1%q?ev=Hs7Ym1 z;`WtVZD-8$-|hFgegi#ta z$NY%2;(eokEAAUZa>^%x0P*#`x6>(OXh>+5i29xJs1d~S9*S2o^THU$`*@haJWUKz?WK6vze6P zJmSxS*`r{lNE`XI5$F73k+~LZ0qQ)Da$G>T3rPC{(wLtPJe$9p>3xmJMciLh38a1T zP9}NEvCt=S8Ge@&{&M^lk>?c};$69k#eqB*6L&H0#a+U}A#%-9SSxa^TjaV{SOlwp zc-K>h>q+Z+(z^bT$WqF;G+$)dYT(&2o-O0qGM?SQvl}vDhsg2?Fb_z3IcYE73@?lP zcm=G7PS`GT(+QEAlSNjL?=4$d9tnRd;cq4Ut%SdI4;&R)8GvoDSL8Mi_=JTA(82AM z&`;lip8uzyV>g0^;6D+&hW;Qwz3;tXT*vU_EpKWn8mQ z!fJl6N%~KFM4q8+&t?Mt&*A@D;{5iI$n%>;wr&ymJz-v0AktNjDM7eD>=1cz zgUCyi^`*rkFBATc6Zo|!_phvn!yDZ8bo+ZG_uKUfb5fW_TI)z)_J` z-H;EBFbx)pyhc4jy-(3#3COG(#IKfip2qw!jWJAo6E{bf|=8XoDrN z1~x&L$XkSe8y&oj4&L4j=-_R1@HRSl$1SpxxPK`F;_TWg@-EMIlh=FPzuzMASHk?2 zy!I>>`CzZehs5982!#8Xygs1}pWweQz!9rYFnLbfg6zmh_WI1&%73Ffn4nDM7!!d3;5H5x| zv8|%qm7?N!9=`xii1HM}R#9H|DBkU&5?6~#nkOom@F|c=*wn=w0rNbaeA1gm^&w85 zL!$a_1mb4aLyM?v!sbi>;^$C~oVBo7ly4!d5|z6^RNf*uBnrDt7zN8=Eo_1VqACerNm(n&V*v37l)=lQs)%2m50s&rylVD|sy!rXAmQtLqUzgV zFPz}Wns^PAsi6fHz((L%!%>dmqaYJ1p$Vn|VOX=(pfy0)L0e!u5N8l^1`}s6aR%4J z1ZacBunN{g0A7aOK$#i^lA#zHfpRp?gC(#UHi*I|P(#w844Qy)451uDD8~@WF@$mq z*#g^PFHnx5lw&AihZ28iCv1Z~a9GqZ(i=v4!$@x!VTO_3Fwz@Fdc#O>81b7{zyXfs zTVXBi5H%thNN+?y)W~LN1M(fovyp^l=%`VoJE{_RK8m=b7Qhnd5;b}=Yz4|WnzD>p z1?ylFuQXbK^u{g%o{!~uGwC!p!67&yYTRC4cWi^*qQ>)l0`;Cqyh$4%09~*H2si1l zs21uqxlPoR8d1|UM|o9(|Jj5)hqAOL18Gesof(wz+ ziGMA5T(?)$^)HKBN?Dd|gicX6)W9gvZl2wk4#iLpYeX$yCF&*t(z|In92Rx+JUA+9 z#U|+D1GF|#xBB3asFfQaz-uJRbQ{laJ0R-z^+5i2>=3njo2Z{m0MfWK8J3IsX&J1A zjc|-tPEkNP)+~S}ymr|F#Jj5=c8j{Z1@?(r%d@p>c>R(NU7~)ziq|irU>YnIwXPKw z@fyYhl<&SuXo40|_mlViJ9u?P*&gVG?LeCAXNvm8LSENU#$RRvVK$KWLz_iCjQrIW zUS|>aQPROyQJW~kuZj0~ji}ALMLoF_xPR(|utNjV4idMWbUO&!IZxEn+&_c=maU?m z&*Wpa4WfRZEb4_CAl@H%_9EqdiSoX*QPj(X`Qs*0+m?%ZwM*1%#Cv^>sO_Ufy+NJd z+#`y0RsEUge?BZ~N2{o}i1XGCQSTIs+PO&7U&wdYLQ(JXY&U-I@%(+t_g4Yp@7c<0 zw56gx^uT&iA7#QCQF{-G`j|XF$rrVcxZT9*-YjZA_xo1^VgJ@F>VOaSiTe8}QUBN= z>QEzW6ZL6}sL!H+I{lM!9L9Y(UDOf6e@arfZvIV38yk@K}pVyIPovDOJM!#*)g3z8up4vS&&*DkF`_bIyBN`=6VnFEfv_dQB0hY8rUvIziFHc z2rL()jC9I&iqXGVjPf#GWqRO*7y~Hh0AzKm7&W9Z(BjqPOfl-YukRA0fv^qaGiVcV zKWH}`7K3%p80>?3Xn_SlK7-dm0JgziI3`9Td9l73jZM%Bi(nOOfGw~C4hTO)hIFWe zW@v*Yum(0k7wm?^Vz9m$Lw!&WEwBKV!#W7SHrNZt#2Ds*VrYU^SOlwJ18jjEa6pVE zfpn;ZW@v*Yum(0k7wm?^VhoQ0AJjt&EP&;(4g#Y)V|z;ajz0oVq6;g}esJx~ly&NYl zumcW=F-9OADxn$LU=k3~0$2{~#Fz&c>=t7_ z&o4Y6#sbo~i07=S#>Ira_?Q?A$@|hJVq8YQzI=@si!_^{3y8~_YFyFDYjMhX74fgy zAjaZWF|MwJ17iG$a7#$vcDaMVIeR;YVKNc}=BFxRCz2c}C zw-$@B(kI5MWOy0&h;iEqG499|V>RimJ}kyhc8GE3Rx#EP=4Xq6JM^=gP7#MnZa ze!CpjK>)VFUN|Pk^By4V^G(ny##Yk!U5yyOZxrJN{JJKH@dwKDB4J-5>`R1w3ICS} z`_dv<1sh-se;}p{c8l=};a(x!pW0wKtb+h-gS~J}jBSK@mAb!5-CrY(*D2#0mKbj~ z@tMIPG2SBHTf}>dcyAH!E#kd{j&_p%F2e4{|9#@J78)OH7vm%RKEnMGy5H-De4q|{ zr@=y40qda?2)}n9oDk#VWGI7CFcTKTYS;){VJ93C<9{qM_ED$(ggHQY|IV{d7l?6~ zdK||8Fy%hn3X6c}hd00$*Z~K`;9Z|_BwdWpDfiK7Vtjd6jIW}^`1+_A$J~$)jli>G z3tWmG;Dne)GVmR)F$!kFVpt6uVJqx}Lt?V# zn$})1orlGAMS&0Mp&8m>5o{7ODj$d&y$%Avx4zNDi_H|%y$Ut}{%-u;yMgrLqJZ?` z)1eZY#Y|`w)3ZQKZ;P0Toj{or_W@;2as&A!HHw*B0~27Lm?@;4x<|~kqhj_UOkcue zbOHHfkWVK0WRgcFd1P(_{IimwOw4T3;yVd5hw!|l`q#il*eYf|?!qWo0K_Xw7PI)6n5ENTHS7?x{E(QH zgsJ4d^01f#&_@;Fs)%1rJ~gCYL)~hT1Ie>~iJ0th&BlFV4guCpll71fzQr7Y|HzqQ zjyfUc82rYPUNip9JNev*c;k+Wc?S86$8Y=wF(<4Sb7C7T0`i);UCc=U17)&m-;g_KG>XUd%aV zVxI2-$}qPE@SjJX^YNR%M$8K>;C=z=Uqtwem%~~h&5JwXWib~n1oFF-^e@GI=_WBR zUjT>TgqVwPFT#DrC?FjBG4rY};Mw9bXcY5m^1FJQm_OqFnpQEF5dPZLVqRAaguOl> z=F){?F0){}m^V4i+K}ezj*?mTrC#!7UZpMVy-0JRXgF3n70w{woE94 zCTIb|+_pu`+mW|#2g-8?@$cy3b1gRzel_7&lg{cT&=5(kg#S6=@8$VAkC^un=Kf09Bjy8yTi*m*#r(x; zF&`xF2g&F*m~#>&1MM^jO2pz(z5H49Qsf>u}rt6&3cfgNHVZiX%~*+-g3HjDZBJTbp;!)`H; zQl_H^_91%-sfi#fobI z;>Yb4D?UoBge78mNW-&UEN_iiiR8on(@G+(q{Xle2%Ef9tW?rTYZNP;c3MSfYhvw4>NvRFCIFjFiadHD{Bl{ZZ+KhN{sKsx#O=MyHM zF!_sM1*`+&=aY8+PB;K3#3~@4f_$il3D5?MVHKD?6=*f(go zS6Tt9+!1hh1zn!wRv@7(=!{zF2t<`z0cTe*-gDd$i0KMAJsl2bR8lDqqe!g7VY!n^ z9ojqkl(&l;SAzDA@pQz-#1lFu-VPlb4vlx*DQ`l@>Cp15<#0yF#>FR;NB*g29j>V8 zm{@mQuTP+3WWL9|NBjW$i?jWPC}Y*ooUhx|ri&!;_~sw99Sj@ z!x!b?xswO6yU;>XwVC4Q;;+47Qq+)NPKq94yxnu3QVV+4DLdc#P@Wk@zO(qtVSkhM zB%N}!m^S{#uc)w~y3Su; z@JQ}9fAKrx4W*pUSeM(Ytma>wp^V-;vw2l!bX>tBd9TKMEy5|KOp~$f&>_dJ|NedR zZCO!n6}>)h{m-*ktE?PV?r^wbTn?{0I=<(l-n&G%JErF&XG}u&>bO{EPS4w><%n{) zEHlv^6|ZuCc<+oZUrm>1G?#_;sppN)_#&{MRPt3`doCk8XfYd>R<&p2ZjaN$z*8M8 z!Y_qBKA?i39Q4N>4ER0mF+L9yKwKfczca@kb?pf$rFwwmc-npCp3-0j0jmff!^l?+ z!HjZmVlXMOp+SO0mN&5}T2xF@a%x6lWqumn#v4dvb{M9zRhrjdZq(KctYfA~O>-3% zrDU5a$xc^_zqXJFX|?`%l{8e<6c&}a%STqLArt#fpD?&(lzZgp@=F4Z@_d0gSxMe#|AQ3FQQI8Vk77& zcqX)*k%(>_QWx51Mx&b;zFaEhYse9Nlh~fjx}hh6Kvq>?SVgeHbx;LHRs__pU@SF_ z^|Y&WW*~o@Bs1rsp>!`waPr9MY4`TWJrMUmPkaAH+yiirvL_6G9utPYJdoEF?CU)q z$mlXU`ex+$bx&38xFS6^=x0Q=M<<&N4GqD@fw(Jbe7Gf8F$@JqHw2Qs?aCRCSLJ}j znt^m@l;+PXO3vZOueE3^UZo_})T!_jmr9N|>B{*v1OHvXy0Zt?Pnq0M7c(d(qp&Eq zufIH6b-3J7vF@I8T!zzW(KAP$ecAVdsCR#J=@nBZU-A4UUS*uoP&YxDLr0!`!$>rC zx?`iFlTHp%(Wc9A&-pjO=#M3#zZ<_Z4$vDLWTaXx?Zx!LfWJCej>@yE0>dkUaa!f0 z^l}p{Wo|EJg^oexgW{w=Dj!6r9Exf^sMh0YADYIa^zft6wx$gWO&jF_t1DQacsww$ zD`+*Y407_%^6UsS;yW-ZIMBuNozz7@CTYXcQ=z?hH!?vwEq!&_*C>6ZIXkKbCgE9a zUu*1Zt-e};dQV4P{Xiy2lKF0eY{!oYu-#!be&@-`7;rTNq8i#sK~Imh4T0*0Kov8j zCn~9`k+&5Nr>nZAc3@q-?ii>4Q~y1Pp7DZ%7>B{)@`i@MP_Le)J&ElFg_U%dK$^EB zrEj*MVJN}!VmebnLtwbK6Wyly@WhI!8S1EGri^qdS8Ce8x-=&uwYsVyktsQ&ToKgN^HNrO%bJfM7zgKkfJ+o~^sDR7vHrNW< zUg$7Y#6Gc&y^3DbA}V4nFxM2#o#Zrz@W)JgLh}`WE?rtePBoD^!Ob_;DiCKU=b{(J z>o|3ux@xD(nc~S&{=$|QhSv9)^;6%_7rs&B9&haT;NhNk9?5%xH0nsBhBWkd+A5G( z5peDbCXhywP9troRa;YT7FDNuJ+8XGzJ}b4?A&2#eTQ^)eUn*uUfcTV=RG`YPT$PU zJEg~ zR%Ko-EGlP4pfz*UsaAFIBs^!hm}*ng^nypDR;chaQdrxlh8lI$k%n$cZ^}wc@ip{G zOmKV7k4kgLrbS&6WmyR+-#v(rn;Vttj!!X^X)JVFj)WA|;YrL*h*7cL>_m?{EoSuf z@yT9qqP23osZ!!kf0i0O=6a^;`FHU;F zie+)isR;Dhh56Z;7&g{psscW3cV^I^`CxLuz?z||I@eoMU6ta^tx8Gu=X#U%QeIf= z&81Ft@gA>|>>OqFEAIIw+wCz_Pmg6KeU_eJDb>G!Pbeq5r>B2Hl$FSGuFKLEx>-79 znz&dW0?rC;6USJX!%=!kU;_~}XxOk--027}3emfQ4s8d!bX#acI6?XA+NPeoF@yrNWPo3&07~;R!@9ZMUYQlI{DFG6tf+_Gosto=BC7`m_H029M(@8 zLYK+pd;afP>Ze)lIw3uSVy%ZQcAbzeOdAF{^{S_jK5u-=Ua6iw#jYs4?J>;o8XL;jXDJQPT zX2CV|+^=3U9_4om)qFMFo~|vCe5~te3x~bX*4SmvMy1)z`Xzdr$6dQCSb}XCOpj(E zqh8XsG5^ZkTEnU za|rg7gL=dxw3%cw11rH1OOH*et*Wc`R;T3FR(qW@mW)kW*7MA=!A8G9BTn8kc(9>= zl%)G%370>RpI^2~dxFl-9ec3d(3xeq4tDqxj3|0#enp^fIIp;>K!Lp+x}#PG+~t9& zuAnFBcp#-K;AS`KX2;qQqpz%?9bQigw~6*O$-XA*Ydij@EnciwNthoE?ZnfIS2W93 zbd1Lv6`h!roN{WdQcW>2?tk$Oo8!SOKNbzY$F$9tr0`4>Z!i@VQ2)B2E_*eJHxg9I zfS}{rh{oVV%+6UPWj9_?1d~iPo8e-j=O(mgYLx8Rss^jw{vXM!?I|Qd$v+ z+7&EJI2cIw?eYe5X|OV;7Po^Uiz?JhDCVo5g*34}BQw{o0*0(nU8NVnQdK+DsJ2Zo ze;ySo3Rlh*nP5v|edkoA)r}fHY+SlO#;hDt-@mom8IwPH>4b|eA3f&UbFZH~q_ir! zW$2)(Lk&|!PparwJw7_Q?$Q~<77k0FRX_P!#!m));9cXV{0=BZmhqV?HDc-rL{|sn zse_9;q*eqHb_Lv3K^JQSb7Pv_1>zG|2I4ukNW>IMJ|1v%1>!v&N%4s^da}N@b3Lu0 zlNzMcawI1v*=?$xwjA-jH>Y-0m9=b+bhq_muNPH&bt7iPem-;V+?MD6;Ot*KD)sS7 zcaplNXZn!GA0MJ_Dwsd^>O$R*3u%)-P=^eeC+&T7n`m1*x+0i_@I;Ki%!+_}SI|ox zyqb*%!#d2&TLUvG`&&s z<5_Jt)#|ycU!*T(@~el-r9DlTJcr(75BDe>2_`_=6g($-}`nmqa9~9CQ`&Y(w0x}Fbx~G5u?^NbbAflQDZA7E}Am+@}a}(rY&hb=i13*8tR(I)(@Cu zjviP)dURd=sLuMCeG?`&jJfFI(G_Q=dCwi$a_y3-O%o@K7&d9_H?>W}Ye3KC-}NOQ zHpQjkaU4uX104RcWkds6g>DNCFtMw^KwATPHp&eziGhr&U^JSq+ZD`Z5ede#*$$L= zJKR>^EW6t2dXU6(4C4(XGz13}(yyW=BRu8S*z2Yd-ZR!^yO^=+7z;(Z;&xx6CsmiT zWyA#R*F5-4|M20(cVE;zBu6DU;-ftYNim~lM2(p{yma6cce1apzF8TDyHBOBW=M=v zbzC;CF>CbC=HLHn`9Kw8MOjYMP|GiwI%L8{<%6@z&u#KXXV(qMA5)T!8u+U~ZHP|w zyTn2)W%0Q5mA^8JzqYw-|Ag=S>5o%GuNY7BR~B;lMWG7hRRsL@^k!Ns1E#&-^J22Z zbp>-cip#N&;=FNDK|J+VuOr?Y$Ap()Up@9U(aR359rtOIUxIzG#S`|}(iCf22_A1^ zQg%*U{HYZVQ|LcE=sk2otC1aeyqTclR9dzbR=FxyQ;paZBR<|4=e5jO)9LE*s}EdG zoEEl{ug8%R`;9v;R>iQTa>O`YhGiZ(!h+{=#yH%G9wo7{x)0UMHO5!m;i`c?!ow81wJLpZfMYzPU0PeoyCrJYDA&R)u9-64?>42pi?7(^|paD7W+EG{2az$q~m9cuHz!7F%Ub!cS`y-7Nph z!&64F4K^s88#&?WN;Tw0%%fwb~KpRQm+>hVBqSBEl$Fc0;qvBa zv&Zn-dn;|PWixfeTP|mo@`tVP+8S2YB=!|`6VI)mT;z>%C%V1H%#kViIfYj8RV~Bk zPct5= @-OeyHo$DLqIzp2v6I-klMTCW;^Ub8{Rypg%?=8-VV0A zOMyNI8E7AW)V;Ezd|Y|1H(Fgbe!`^-Cl*u|oHO?q_g+?ik+Y`t)|Ia)Yh+o;sF7v; zMyiW{u`8!+?BFTozROmunmgx?jQN3;KYqGk?z<08JiqP!^XF(?VCNaj_*;c8b{CI? zcX-$tE?!2o8~Ux5s_u01ZBJaOHvOD2dfQ=dGve*((Ei{WYtPNgEAa$fvBw#4EZfI} zF-(*UT75R=37TogIs6L7q#f^Y#Bcx>;JuU6#mlRXD1Fths-m^O*%ZT1QS6hcsOay8 z8~uf|7d8U+CZ<|dD}Pb0E2-Kja%L#UbY;z)*z@6pImdqFj5D4&dB*nb%2HzshS1ig zp7&{M{yzxV;TQ8eZF>(JOo}wXv{Npu}!sZ9e5x1(IlGj{Jgg z-0~w|uRT!uasJGKS|CPuT=av&B7uKcUNuLjjQd$ z(U%O)I(tgX;u~ANc^mJXzhJ?*=ziCzC9^KQ@S-b~@l4*pL77!&r^L-3Ic`C;Q9o@! z-6WSPm^*1e*?2#7Q!;|{%?ZYK&f6QqBVQY~?G9Zp`&`p+SH{EoosZ>|b0O+I!LypBk7l`Gq@qpuBh)-hKxQH^MrdD+0C{>}Xo`i~trZ`Ez{lk(ED z;xk;?rgPw=p+9|2ojc{unYCtuXKF^@_8XT6O*6*wSV_hhU)mnszh-l}AN{-e`vLV4 z?Z@?MKUMEp)9g~lQOY>G%e6l>o~fvOWab@P;|vVz#W(&2HKC_jl%dqsccJenq zwT9TK@goMi^s%&tQKesp2rp#y0hsO}apA^6W!`Ep+b!>`8EV>$89kegXL{aNYtVt=#<~@V_VQlJe&=Lw)k%yKM}J;PT%vbKZq=xBat02{ZA~8UiSJX}Wb`@N zT|JHu4s4z1adL+}PS#3$w9ZSSn1k)XtgLF@Sckid&e5#mct?LYfHi7ij`BXC%L>LO zV|zNff-%u61JUIirv;BKY)AH0>TXY_jk z-uy?V--s!r^}@WHqIgM}GW%?G$$33TRo_`(+=8}$qOR)sO!b_nE?K#+w|&(Y)WzAW zZ~JAp9&f#e=33wO$+%9px-MnbjF*<6^gGlDJ$~jfal440Dk~!WBQ~-cMWcuCe342w zPNhe zVE66^e)0A@OoHD$_v|e`+3v}D;T)eB&J zm41PzQX^W;-W5#MLsl>1ncDIFRfZ|;|6=Y<;N-ZfbMd-Wz3b+fjYj)6k}TP>yn(R|Sa`PqJ2D1?9k3rHV0Z~@Lf-4@8QWp85o9GH&jANHdBlVS z%zN+)OF~HE#A^M&b8q)dk0je<`~9CmHQm)UJyqwPd+xdCeCIpeOywb7)rY`PjbxrF$8Y=SQNYQqbSq7pJi%nY~l~G-T$)4)SwS z-A*%_R%a{SEH~BM-@wKa_L=SwwkzwpOXeWg~^QU%bGL(G{Jh>PVhLJ7J(Er1@>Oj67Xs+8Yp8R zlP5zj@^SeJW&>Y=9jy9=yYKz{``>o`zQOMMe*54%PPhuE4ll85-g$yuc@I1M(Lek1 zU;WDICvH=}`JuP}midwEkN(;N|5L8D#_)PRhGFTg&A5bQg~j8`au@??S8cQqa8Cw_ z8HmYAVk+01kd(JMkWoH8?6xyCd$DFM!iCT*5SG=2kTs}!YZjyuS|BFwMuw3eS{rTJ zVHk`6USvXubm5WsSpUt|8^@xD_Wr^07oNEP`or$R&Y?5%Q`xcc*yz>4#^>3l#_zH_ zc9#8JonhFj9;^{R!#L-qPfPV2jj{sYLZ!7hK+&s0i9+#V%;d1E?x64@?YuhqZChcS zaL?wmTFm)!Epc`w9!p>#aa*UrQxApA>_f9D@>M%r2fm3t)@4-A^*p;GC zMb?16%80n07+?tj1O+<@z_6OZJprpi_9>Hxl{IGM6YstC@M-y`>1lTU5&34ymtFxo zME%4P$miE1Bx`HaldO4RKtaL-C84DS*e%HeQPU!kI;VLMt<|iEN>P4`)?@@PDV9C~ zObC*7%|*H>!-GI(BpgK!jP2}QIMCg5V0-E3?8}WS4FSXWp>DUgXa9&|Q`l-_Z0fp+ zRC3}5{)`Sh<9l#l7VtyWEx0f2mH1=UB_kGb+!|t-Ha_GM=Cs7OMc6K}wQZ4RFrKLy zp$bK8jWm<>WnImT0HpP0aO4;eYmqV`p}nc`rS;n9&nr4@=6D3);?`_O{Pu=Q7$ z8b3LT_2~5PrShL=@8kXq?o;{ixX%cS3;ESmcbIm1H0M@vuTED-0Cx+gF(&Rd(tfu= zzKM$jUvAzl$0quHSdPN9-kzzsi->&zy7DqDw+`o-nzL9_fm?HFE;pV?bs>I;t4-ML znJBDap`#8HV`oA>-LRi3Y+q^ z1ZH*cpc*h<8!r*-KOOmUs4>KE>pGiQ@z=!ZNysEszThM6pky2e-&)k``rfkVbj+iZ zE4Oa_zs|w!R(so z8*KCqb$p``Jl2yseusYG7uE-(j=w}}&67;lo6OBf8Jlvf@8(-Z_#(MuyUYfaN`wJV z$ud^vV7A8fU;g5WzhjZ>U;J}+BX(4ce`Wf{Z?nexS%2d>E<+hId>ndgCI67-TXV8C zg$GB=;whSrl&2EFk8uLeDLFaNT2##m?&<3 z^zfsXj&+<_zhBCos+2yJp454=~oD+G;W#te*alo=@I#%P<1wngl% zAAb&X_`no`$gc68KMi~|dWBY)t@|an_Sgangc+=SxgPsR|=~#2SfDIu)tV+i^K5CPXaioq9p|B8> z^t2I?~zUAdle)1XRP2;bit{we?o?$P_r?KyPGxQ5NLPYWmuqOm0v2vmF{g+AD zcH&Bag6T!*&VYoy9)HpEvZmhrE8C)4N`la5;ObIjfuUY6+)Wfs<%_;Ngjf z$8WlcJ@dKGJ@_E*Y2O8t{N#l`Aibj!fGn%BMGEP$J6D_H~hru*%FmTm*~Zpp#YENgx^Q-f7k_8S`4g6XAB1(92{r zS%~~V^k4!|f@?hf8fI+f%B2U!<@?@&DG49$=jh$>Cy1xyrTc)&B#Xk+#gugAxmF6R zWb`4Y9cWC8G(vwJjvc&>C`Kp>ZzBzFL+>q@dFHhf=P6regF9VIt96SrL~#_4o77E9 zc(G{v;HjyagfN_DLDHY%=1#)UQMM4#IN+%e0v3sVfL8}8h|tUzjxzvGdWMf2?2&_^ z+>Qmq_C0gQbc;G{L`Fsy1`FMDiK*GHTe8)NJv|kTS7v&SY;HW4(Yb@WdmjjKQQsi2GtKOu2qo>_zHo5s(VG_MCxKixDyja# z<#2b8KR*kg+!sb4I(F)pMv4!Z%qC<>3i}S1<~!74RLrLgp5dbyw;;to{dDsxZeF4L z<^i@4A0pg0fGU!v`f+pKk(w3pPoswiwnY5^^0T--f(5UI-GN1fz%#d9U79G``opop z?84H-(oX%(eMj&9t7DJ4!_x_)>Du{iCx&J!@50T<65UIk%QGPz7JFn4n$X<1C9o1> zF4BD=`P_0;38hC{88fMl52{1*gtuy@eX@ewyre}aM$u2oqDn?&C`Cyf8K3^P|eD@WH_l$(Ut_&p8&=2 zJB-T#Un-6*m5^gqWO>uK?~V^uJCw#BDF?0eK}_Z8BSH9S48@V@rfhUCYDL?M42io8ScW!;Eo^j} z*&sWFC0K1cnQjVRPU7w)$|ewC+w59R3UuM5t8SiyDui%0WRyy{HC+<# zrd9g=t?m_r+0R42HdS*3NIrnrK{|kPifko7MU;jz=9?22dnW%FLsE_k7=-fC2xHTX zTn6CtJVV99IEKhnD3&@tRVm5a_FCgHWve-Vbhf8t4JNl|`WN~w`ga^X_Vz79(}~ID z$0lR3bf&*EU8R5Xn{yq7-aP9boRs<_|jP3hI%j8`39S{eywo!#j< zrWDxTzLEYB{DYqCf}X5m{sqAtEMm$w>}%HS7!QU+K@ee6Ak;va_K3_58QZvd(CpYF z0svF9sRTCDykyDRtQYD9peex8a=qELebc_&P{gS$F{>vZ9qfmx`XBSbR3#H^e20Zp zZ`Q9aPyLi|2issBJ}!R>xPu>ae8KG!Lk|Y|!tY$DNS~KKh4_$1nv?1(;25hes}w*D zl$mtNQ4%3=csmql#yvx=5Z2(KBJzN+!>8TPFg#R*p3OW=NdXNb!mp@Y!=9DmlYkSz6Eo8h0ZDAx$aNsF7wu_<8`1P4XtAhP z5R%CoeS5clZTZvB?)mU8NH_fT&o+MYojvb`G#NOdCF#Ss=eBr026`=?Z$~^Ix4IV3 z-@P66U`3%L@*iOicM+-zwhxHUIG&X+bj*zMs~jvz7?uR^Jgn_q&i z#vo}GJtOK8`4b|OD^fyYRV0`b9*)3G?Xv)=COpSR{ii#>NdIxG*}y#qcS^gAGq<*@{xmtLZ*A!ePNt^ z2eKib=ZM5Zv3z-^g+yoplK((Cvf5=l9v^P3>GAm1JsvmikvyKDiim5C37bLN7|5*U z@u=&j(P3~h_&dku4&StW+xSRf`0(N;t57qRA+*JL4`xn zbDEd#Ug7?lN_USspCDXO9qB2ATMp31VxxOOt7Ya*9~@uYIkRv6s-<1SowI|*k;5;Z zdSGVe{OO;MKX_vPnPqNC;=i{7;vY!J=_fx&;>(gYBRPz zlmz%309)_gVoUGt3lEHB*jJW4nF5<(YId>mzo6q0=+t%4sbT3lsa~ad3_Nr_jK3j3 zkt1Xu`QiyYq>nO0W&E*GoI+MpQy7*(oBE_#4X2A$T0hHfEsC`fu+A#v0gGBd%+wbv z(`R8fstkEX&mhtiD0JYp4wN%T*whTsAetzGrsSqaY_i`P@@Q3cXXY3yjrp9vjF+2a;yYRPx znL{PpyugPGw-M+NaRW9i89_eCRKH8XSSzo3<|-M&T&J}b(ISl*@WMrU2eZ$;39)=} z^(!j7mRO>1U{c4H7H8iO@A6sM9c-qdW#ZKii-!dpGks&8V0Vb$6XI&@S=^HuT?2Uj zl-~*>UKCD4l-Vjay@z&_E;69qC71=!c})d*`5?hQ^?Gqx(iI|%;mIM%H< z-M{U#huAa!a7V1d4mDpskkY0)cOn1Ed&0}_A9N*wet%ZMSsJ>LkDM*M)|s(?3u>b4og`1=m*^SR9jn$5vuie%Wpi^F4zn07891jL#l1QU-bIxGmC0J&p9 zOc7ZWU>lK}_$V;(l`rmnh&{0PxBqbOnR|m|XriZ}sQ~w7P(%lLSTMpY@o%$Jjeq*8uh_{BgDST1 z88%6OdjRpjXA$2T0&joaL19hUq*|_|W$=T_>S?*JmTG-`AC!b`@RV!AG$k^y%L!Bt zl2AfO)YKdZDIkpn%PwVd+eS^W$ zXuuH4l(K4|ubLU!73`hwbNHQBy$+h>+V`!A;ej97g88U#+!FA(p3jb^0w%_ssp;Mq zlF4zO!R2ta%iRU_f!6-d}`XwV`SgkmwsJ~h-!{X7)0aSu{)29oCzf?XO zi@mZh2K8!_%P7euL~;p3Afr6)S1XmM9F^6^`2Nsa)&?M#Lbrfxo z7vOT)9q5mR3LZyLNXFpWTN%AVkCFErn;3jXHu_w8|NrWpPC3_Q!=TX+j|bvW*>hQE zt`o8hJXdh$a(K?Yark4hE+6v0^bC8_5|AmXeM-!yivqql=2EQQ9-A?PB@__H-+Fm z0py1Q3RcZqO*1C8Z{P389Szrjd@px1xu5n??5Q$9zvQJWMW(G5uV{V@dPli#z@M)v z6%DcfS{4Ekfy5T*J_c_^=|Ba1l#7H%hLA1>7>>#gLx{5wd@o4hD}#XV!Dpkoc8=dO z=T%-ui$a+qBbdDk!M)|mvC??ODhG64gWKYE`mDQl8g}m+JAUiZwoNlL-#+k={m?o!o_Dct*m#u7%?IcN+a1Df={a5&2b1Ws*hiHdUu4rR; z7!>(ONVx$GS)Eme5G<|rRv;+OM#Li#$}!_T?6GawQ{jGa8~QY;+;KP%eq!_*-mUO^ZZz#V{O2-QW+>? zrNKvSD@`7b-AEmu@I@ty`t?F*g_~;1u!{HP)(LK`HRA~2x{7&dnqn(KSg93fvb&%B z!d{hMM7Hg0F=*b zjDsm+P^fqlWr4Y^OftCL)onwrjBe}Jn>P>bN@ZdO6B|tDCa%8Ps^pTrJ?_$yd~~v7 zX7}J?ro7!@bXsjjMTx4B(Okc=!~4+ayKlX9;doymeR%8QvGyO?1p#j+gVav+$S z$fp21H|iDHNaI?(@D2G7{5VcW zXR_Gd((hnf{>{s*_n=cT}Wb@s< zgQJ_|SiCYYKGD>ui+-@Fz(tp{*Sucn-?mw(l7UK5wH@<#85!cXzgcrl!8%|#26I4( zqfU#W3xUc6qm3u30#QwmpMYP2g9MvpUQnN>wZNE0Y`Z;PS9Io5fo%~%!-mtb1dQCbIXkfuQA9bgHa#A2i|zM1GOrX*MIN> zH@@NV$GB~|@Ddd9Ddb1Xs8xDFs`o+%IYma@69`fot0eb&VN^iifC65uk*jV!ozPPF zkZFsT&=gPrI7^gE~9*jl0zQkwLHa|4=4qf<_*BH^}){#ysV{zXKHCA zjZpcJc4>ZCmViqO;)e%`a}UzND{eaC!e5IuF5Ojz3IUa6OPWL$5$v69*#yDk3}Q$EPDf9JvOeLUslxRiwc6;&6u{6wQ@XheE=aXm|jty zhI|O*PXI6q@b=hpv95ux4x1WM0}QqOiG_uI^E+n^hFm@z@64K9F2CI)KJZ0C`xdT@ zy2CM#;!ZbzJTTko^3UbH8NbhLMc<6lT>fx%e#i86quE%e-Ml5ICj4%P$s|5nuKH4& zQq$M57eiekk8z@aA8sR+FTj_1gFMC_lt19gOSelevG<^I?RF^*E)%M4s5XA8iz77T zL_~6u_;$uA7BUEbKL&BH>V)|&(T0XZZ!fKSn4US_>U?fZG(+rKW%*osc2|we8%|scPi@Y{oyF>K@ z3SuKz%|H%8**+=2%S(+#BM+Ovw?p-sC^V?P>-!H}_4I>#9(w74`(L{1-f!G~*Pq?J zc=x9dKil}bFMsI+bK4z?$K*z=EMR}b!|%R1I~+(Fx<{*zeC2wU{<{Zvz3X>xeDv?n zJjmYhOMi9uz2AP|)RXsYYkdE?H#`yCD^qzDiUaT1xpb%9=9vq)K0>k!F_rzg+`!(w zOB$ePV31rFq&pEo(`5X}bH5xlKbn^|frBNK188u}U{s*7L-5FWMT#_{6;bY^WwP+d z%Df6PNIDB@DpZA#HhM7QIXeVmXefpg`CTgIfhFdZ+f?5qJ|a;KO#%cHDzK5xeB(L9 zf1GZ;XZYZ7pd%Qo1iXe#bECT#rxy}k_H8T|Nc8L3p;6YCNW{^KACNme1mD7M~#%$vSD@%-}>}A6*eeW6-SSoJ1nwk36@_bn0NjW99MQZH~ zY`746B^v*Um?KSo3zq?QOoEr+J}-)}KwpyT-83_jU5NI#O^x*#vD(wuO;xZxa8^^B z_{$WH`o!htNfGm;gdE?-sWQpen4a!4wVq!A`S+XL8c_kf-jwp7-iYVqNaPtG-hq&%sPJl| z*O*lQ@|_#D96UDr%yRA5?>yc!gsrhH87gdhfW5|qYD}>2Cp*VWbIuq4?9Z56^%l%# zpT~rXKm5m}Ir#?mTk>yUcKI*n84=kS4-iIk>^k{|sVUOOD*JQh<2bGWI{$G*4ARYn z4?&Uss?#EM63~K25?5_PBW&0$phT#pAP6R+1hADQcol?sdu?@fbFnl<{|bdftg{f0 zb;9M$Hn{GjT!=g!d_Vg5-A=2FiiG zG$9@msP4&-B8d$(sw%-_VkC%H3iMCJ5hScpXJ*IHp6k?XYD5hleJpfsFW0RTC&+-h#~F3D?T(JI%*nEmeIy)ZeM^u!r3yPbxn%mD+J|@~GZu zGdqn=)-iEv&jEJFc*GfWnWApT1e0Y0>L-Hk=)iDwyvOQuxh!_QnOS=~N{d`So&_%H z(H1TV#2CtJ;*wA&Njzb^NK(Q62VrNXl2t(1xgD1Ti}~yWOOLU}JKh0#?n1tY;_N=k zX%i32C2@8hzW@;j(mg)N5cw3}#%MdzXjUXWHb&bCK*E=rwrG3a*B))Z!Qw_TYGbHf z{yfc#>V*k;4!PG5%~(CT8LF5E$P6RfU`Mzc@lxM8B26L_?sl!hKQZ!93*OXyPR!os zWJcMzrqW_bY)Vvx$hR@z&VY}JY&#u`ay347!|V@&1|>RDm@CY`{W$wxZr4J|8Dy_$ zu^33aSCcXJAzlAXVxBCZ)9uaDhCr${^883nb-VD~B#ue7SZAb$c(p$HUZ$A4O z5v!lWJU)#%V#6M%357>U1U`qL^x@Ij*QW3pYEk$M8s^?Q>$#@!;*C}L-cgjheQa#( z=ONtrx^ELkjq`C-IFr2q5UJ(c1WQ!!%;4jI!bOWfb5PLiOu+{vEh^v?VUVmQOt_S) z1&Qh54JQy0sm~1z7aLs~{f&Ef-}IIpOK;veS?SucrMqXY z<-{=K?z`^Vz3-M=j&0sCyKTP3V}Y75)Zqx<#Us@XG%^yDjr%PWqOKy?51U5EtKn*V z?|aw7?fC9@*IW*Y{!#vv%RYc^vR%@LV5MkZLU~iB1Z2C;)UpVFbe#o8>gAf_tX#J{y0Gpu zWI(d?LhSBiRH6}qR4rM&$d70tO221lG7Q1z3HTAbOQmxFM-wg~QC@^E^`@b8BAEdbhe%&?s^hx8)C)H8QB=}OwBqm}D>2<}+@RNu-MYhn(rj%Ak zNB9S$kkk+)1;=UZoHhiJfSz)!3z`+hYpk8ZO^RU09Vrr(Z35O1al8oFXd^@kn{cfp z6It40^JAJHGyK>=$2vZ33(BMbu*BA5q}DM4W=n_$FNN>ztx4eF(m0K1n64YhJVMrPB+BNLclFj-J`V%|V~FlePr z0s>`J+NriK8l+@)aggn~*3;23;JS%98lS$!*lAB%{YINI-&^YQSX-ml8n{#W)Zzpv zr~Gm2j?H6x^&zv*;&pgD*6v(3Etl5@^CBJJA}vZ^km`Lj`>`ur*YWv_b$qTw$V5)92}hka4^h;+rB( zM-;2J0P5B`{0w5)knfp>=(F@Od|#~$(bopu;HrF$S-_D_qFQSZk z;xJ!5HPjR6h}cW|ki+jbrBeC1iPX}8>BC05L$w(U*Ia#ISLm|Shkbb>)Ef>YZTfLp z)*B6hv^Uj1Jg~Xsi1@Ir*XcTDD!UtxHzyXw%q!sNaABvDL9VNwr2K>6=s<-cSs(7Z zVcUas5$x70qvjSvhhXJkM3l#PS*sVtHST7ef`aMd1-TXovna5stWS$N&aJHgRlJ50$Z;YLgUMy~ z$O|(|;5)S_dbWIMPhk1lBRlTi*X`>Fge+d=z}vgyAG==fGrI6sY*&s>RZqyBlqmtG z5;pyAWe@Ud9^jQe(ac?6%s%1e>=V9q_6Y!(!d&}W>=QxZSP}aKXq$+A!oLB+F3PO@ z#6IDtI38!8pg5k_NG1mbl|DM(#y-);F-HoFb&uQ>A}(ELp8#Yylg=0spi?elqBwN5 zwfoHr@FA_IO8#|L3T01VhE;;+zaq#f+MnMBAMe$k{}T2M_zp-!R0XH0D zPWfUMj%yz?=*?F>;dfFYeT$Cc_YTPNP76a!ON{&duAK4oqJ%762Q5Eq`fxk+Ap@?C z|GQBiXr->1DjHE8$h4&(W|9uB8`+j_0HDlM&O|K@CJ$sOz@1EdtVk)DfP-4~3-aEs z4eC=^SD%7VpWJ2CCr9d&TT`C`)ThIUV=aet1f6eFpSG2U)JOADe%l(NMMds_B4MeI z1BIhGfd6k(DG@m1+Zvt?qKFyDB`;R5DK?Vx=U{Z~E~7lbM)Ag<;cuer$|k?Cdv_w2 za3&IgJ-g^dG9GC3Hfd4T#$XTPiZ=iQ`fPI)S~__f{QEd~Ai*eveR?-ai9?7I(N;WA z(NLTVUN8!fA=AfdT~yoHBbT9m2X8wAZzfEA+JM&yN}ek6oY-3&S>ALgg` zEXpWW0#TIy0sXuZ3{grbOh-B&q4R`IfDOY^j+Vka+#Zeylh#~jv;>inA)(VjVK{Jt zhmo(dxno4o1}S^`@$7&LlB&H%s*lX$aA~i}DNdHhB3(R^a^LHRNcc zhdDqi3zc7(Xl1D>q7a*J7-l==&A4e^BT3f&dFbEDRAAc!^H=V%C_(M^e3`0V_9?Z3Hs% zKYPMyD-5NOwYb0gaepC-@W1B$c}jJEfF>&6gRkFWgwH~cm(pK4Of}3x4`v~}?_dD4 zFi^zC!-Thbj$-niRe)mi5nlj%s+rho&a9^SXOxJ7QX2QlQNAc ziuJiWWCCs*)5gkF$~);)Y4(1CcG$V)~25@-Dd`c0H6 z5*>rYoTvk;MFqGtjujU5_8@>oy^V?|3YUpa&PfCtL>P%WCOnG8e1kxdt#3(7@d9{j7~2fd<2wE_uu(mwz9&iV{EoX?8K@UK<#w* zXS4rjjBF9{=A2Y+M}#KJ)OA)Bit=kVp!*5)4w#v68T1uma!gfd;nG~l6*HjIs3xSv zx&o~5YF4-mS*&D~2@4CC0qhOI7AH%d&!z4=JRVi{Q`Hhyodj<~V+|i2v8P zS_Bg^F+yIBv;}0yo;LS=l_*?PJ_Xs7SrFEE;_JC)%u1#O5kk{ygQSquBhIbxdj^<&(= zmZ4I}Q4)6y<{WXyBnU6exs4!yp;-VywdNvenh1u8@&RV!vC5ak(?R|)U85y-Us z(MyPAGJNWO6ake>7-illX7c*I-p}tfCVlWS_lg)1!VEEPVns3B#dn?Hx)DHpBLG`V zHZ?{w8*WZCf|(sDYO$yZZ}nOjGdn~xI~a=cnGFB|I?=?|Z7qNPUzj}y={CZ-Uh?dl z@0uVReCd;K7lybsMl^@~VAgnl+jIAj(+!u83SoJ{GJ)3mMX(@`*)z&l!j=rcmA5(~ zW)DzZfjr(8#M^?ao+oBE8dt=HMglMm*!|~$!gu;2N)P3a3+`Gcti{?WYyDs;)?y_d z?QgnmNRxtk2Offm_S1MH)eMJ0H!w)u5|CgepIPViT_T1Zx?->=7tDCfc9TJ7C(}_+N8ZQF zVCg3Iu5K#BUNNklHqz-86F-{iNLBA1cN*8a)BIYGxE7~lYo~khrWanKp01A5>6HXO zCiyW%M>14BYkw5K(L=uhW9419O902i*{(-=;bs$vs*dk!`!sZ~5b=)OgfHD&k zf^$LCd?6MQjm5irdV%5w84*S1Kok2a|FM7juaH5+sz{mkNfc`!zXhj1!JsRC(6KpD zs{uC?x>90J&aU|ZgWkrz_>9g1>RYSH;yruT<2CCM&v@n;#e|@S%z$F@e)$ir2ED-s zT2@^B3|(zB=nPix*)MxgzGL85n-#sH(vI34F z=2H~&$&Hd7Y@*=q^RmzYk`p9e z1yBhLA2g(9WVpIlR65dmuLtK>Li`w}BVCi=*L2dcjtkm`ErgO6KCbuz8a51LCk>ti zn3KU|R=uHcqH}$y2>o=K(|k$FVV0x%07(K&>nPlRu(cI)T28vbwB(< z=f31}->)ox_<6iIJoKU6@7>S-vEw%z*E%e=Pd@x098FrIIDb%cje3x;QONSbhY;uY zV14RDZQ&)%LbYs3%aPxYL4s3FHVplj)(D2WK`?|mmKAFxSs**+S8F*uFx(M0W&qR> zUfHWL4#B-_ZVoO-J@kpS4Z>@a5XoCwTZY!N+>d9;5zENMEJ<+M>Y0ulU%-(4xy<7f zxV=iI&eW1vz*1PKaT1L6#P?wJ{cPjVpLuLyWPEfqtC(AwKCNm$JqH z`=+^Rj1gRGlE*Y-Z2y(yF{p>b66Xa}82e+=7aR1YRSiURGD=H7+IVF;*R*0I!wmbI zmKKmspTNNZf8K%k!}ewrhtoXSQ9Xlr12|Y$m5`v?B&5q)&EPu6gNJ;1g!-e{;f9b9 zgcaZ(rBA@S;J*6=n=P@X8BgrHW9&#dc+2(oU9)?Snp+&$4GPu7k20Pv4qYINY<1j+5%D!xzm9kWHQ-{Jeg?ZVnOOG(8Xz zZiplf+AVUgF)OIygqLA)=fcr~+rs35zAYKo_QT~Z38!AR4Tgh2 z##*;jC%5;`>hJ|92<-~@tIVb5w+_u2z`&z3SzMlo%jwPlCgIPiuJg}9-#K@Qe4i^P z%?wj4b(TcD;r!`8T04*RNL>FA;!|nPPTR(Ig6ijWwv#TwcGA^kJ1KEn(QMSmo8_#r zouHZr;M!;m4ooKsC}<=AYLqyQlE+S@{9AMG>wSD%WYqiCMAFOzH7#>I39UTwiNdC!w;W4*|>F$GzEec z^81*4FXUGQ66fpfY%%3_`3(vA4RQI^dP*Exm@TiQ6Fo?!bz<3%!hc5550tzFwJo`7 zMQC!f>;s6R)uM2nOVIv7sQa3{vF_*S%GN%$w3-uFZfc{Q(FOr8dkWG`q&umORr64a zndoQAP4Nyvyzs664dSKnG`Zf@RyB_C7ZEiZ%}4h8JU%v#SK)(RaIwF(*#EeHQ(x5A zH__MExUx<5YdZY_==u#PfNknJa-Kq`X-x9y_B0{=Ui+xLI)@K7GOJayG>-hhrqXIR zf5p&;k%pwj>r=oUW9%504eqc2+_ceD86x5V{8^B@?%==|y5T04(15!XM2TI2|#^S^vCqfZ*Z zji_Ms;jA8Xtw z7O4WoyC&~K5W{_Wx*&EZnd%>M(jdrE$bj34 z@2$xOG0?w)=<1MVUYfaZ57<}?(vKO*K8f=`wSFFRZ}`Hu>UHvq zGUZw?!G_UECpC(0F@zHi4CV-8L}kw?;!WpNUNyA-emEu{Gy|6rJ^#_4vFK)>Rkk@d zyKRxpE|+X`Ogo+5FX+rh@88Zu9j<HUkUJIuysw0Uz4R{9T&8zE|vjA4iR|57mxy@ zqeZqYK;c5ISZp2m#mlo0pt$%nSMW5a13`adNefd)G^^CXw*=fo@$iJeF;a8_Dje{T z{?d}dV-tycju!ehv%%S&_Z}$}XNKl>ZO{E$#y@=UP&6{SKg(V`@rduO@!NM^f8s&U zZSgzj-ZVAB687bPvL4v8O~lMOkA~3GGU?r+rk<{!KmCnM&vQLBUwWSFS2Bkj@4IEJpLk**OK_h2!zbkHFu%wDj?VHu5_Df&Pk0kAva8QE z_o7Yw5w1fF4*YIwU=tq)oA_H`6P{!R!~~^M1TirKV&Z_e=oD{7&b5Wm39Um+LIPqE zxD3Q3KoFC_8pH%mmR5X59Es!IPv_epCO-dq5Dv<$If$-=I8|hVdj}!?b)-@pWrj_s z{ozEOe!Gqo6}&yKBi70N(r@k|V2N+PhCw}~@Q6S@MHR(tvpH-wlODX)y{@4HL#5ne zdruw8q-Z-<*Z^zk(}ns$s?Xkw(PiLQFKN*l;LYS#$gJ<)Wtsg-3dtFEY) z9!_->A?&f{qaBDRPDU|Ki#-swJ!(CYM131EC831AHr&2-goB_G-e$vwrB&J%P}8V1 z5ulEy?5b^Krqn?f2&c8(N@g+|6nv&$i@`b?im4S>^}yhMA~zi#1-Yp^oegZDH}$w& zg5cDsmmLtKgI59T@T$9*Q4$f@o}(lCdor+?=%IUrWG6~(RKg(MOUG6 ziOlOs?&ITEUO9fp*4LEV%1t-@+PVzLhx1-&ml@#(enq(*7{uE%bUsf@Bi01SSRR*O z!P-dPd%cMgq4u|zV{#Rq`(>G2P2lrHxS*CSYgtS>lsJpx07TYx2(2SRrgdR2l{>qV z@5;f;!ExQokyewbE|A*Lf2^MBqF3Byd3ADFq610uS%@W)w5H7MDHeBl+S#!i_+Jzf3Q=`~vVT{E#SEs8zCDM5s)j zWZgmz250`mc4*aP%vi7%I7EQJhP6OYp-_ja0BjfKQ<_bxgzH9)sqmDX-jzyg*Qk>>5aoX9ON*yYU7jrW@v)n49y|XR z7xU%fc?D)k_!tv>jsQo7^R(yunD8-vM*7s+r;F=J&+gnt=LvPzy8a2QrFo<%zt6u1 zzp?iH2XXyvm$_cd&Ffp&BbI&z)?Vsu)*+n~oeZglWdp_#^(zr97ba^5#r-+BQ0)4f z05&SAgrcs7#Bz~4r-jn6`$>p5lEe#y%Yo95p?e_(%P&C@!X?HYuL%pM#j$!Z3asF@ z`~0~|px23Yaeq4&Oz&{^1v|OT-b!g2=uaB!An_4ru@a*1G+_%tLWXQ9t$rxaMP!s3 ztE}eC8KNc5Q3Oc@r-|YYP8jv-T}2%xxf6Zp(@TC4qDWqK)uu!x66Y-D@ryC`0zXecIAPBZ0y7nXHUjMtA{i5`I!7*V5-#s$ zTgi#mh%Kx~6jF`}DaTy(D1kd@-li!g+5}yrQ(_wB1W#F9iZA4)?lf8e9>ssC)10T$ z=FDEc?c9gjUE0g_pp%~_-*(~fGR-$3BU&G_YZ9XKLZ)Jkyi}%Q9xk_jqd8{RU<`n1 zzmCSHo75TSd5l7NA76uCVb}vBQi`wzRjRY^%1^^*MtxoaBY=6!f!zoGC+%G04UbuEoVG*xmKc=(idv^-;nIt^h9eOMSa^0;PH*G=2s z`(BV46X&cTa~&uzu?u5&-#vEkd^kCn6okgU4!?S6`lpQSy-YkUtgL(xm{h-XN?@Bg zPYdS>7Mx#BgAHj81@qMeyUyE#wJ_l*0u{M)xC-dt<^k(RYY&BnVIHD9r~cWZ8knfN z3EKj`NG$6 z&vE&QjrR;m{~PZaHs(Kg&!tQ6d3L+pUES2Y=kwnL(<5jl5r4#-Ws}MTB8<0O_+{w> z&>dhkasDef--Wd%FOOfio9m#6SN=854|1R8&PPcH3E4^4e+}mo&9&y{_3LRZlYUz{ z32tg!$(cob@)63<;{1nEcihIW3`wmO&tA%}%Xid+P&g!HL>&v;VDWW*Ne8yxLT)g$* z@V4S!8f0%VR=t&4D$eZ1wqA!AM}>V;{uJ)T2%gf2bSpZN;8xIB4`UwUCB3K%1_2EZ zcpytb{j51`9mV{*bDGiqgOy<&g}dlZjjpkQ&7$|2B6Gl<)+o2NAiWB^RSI-(IrE*~?Gpu=FKp~Tq+ZtmU7d$-RxJ94ptjx8>h#*Qw_ONS4iuahvr z)Sd9^d|>K!?Ga4fWJ{iGx5@)5RoqsQlFZ)$|B7+N6!u5oAcEZvN zP#9jkaoo;=7czfHI6J0mH8~Ta+w*XVE0%Iqz?=UGsl}u12H{Y(cR0%|aMBhh* z?skk3&u17-@V1SJBbd-3Q-!~6Z#HMIJK9^ZODjgz?hxL#Nu?L$Z9^K60ZBn=dCkN68$zordPnMV#lH>Zo<>LgWcPQL!jXz2c2lm<^Ah_9e*0N4U$Z~5gpRIn_cy2 z@=*+H#bJ|8Ko!!lMZ#=nsG}-t)K3-cz|e9#-4~~cRj`M!N@tq;FKT7JVg(&C?w^h=N_tE4{vlLx{}#G`PGrOH z-zx4Od6U<KIuLxQ4Pmqa`rRr`Y56rG7V%@UMYLwnoE0)4EU{a98oD(cW=Ctse;nB4E!~2|h||?EqMTu|4&nqw~?*Pp>!< z<6T?l(deTRoAT(L{_D2xze9d1a_HmlX?!CwJecU(uJ^?zU0p0W(ox;@*7*i;+U&yG zx(Y7)EP1;044MrUR>Eljp?wM-uB${`t~`>B;2{Nj@B}Cy;Z2fgHOiw&65uy0F1^nU zgWl>fLao>^B1FWFhHWX4Gr`nc^V7-EK`s@Z0|-Rdn~$LR0&hOT*p`t^qmfi5T4K#U zBs3Jk`(-JTWj4Y3~n#c`NEMMEv3g?hBUrK1JkAd5(miZRJ44^*nxYn&vsK zZo6K@FMhW68{ppJ^&We9EkOc&=uI|us<>IJAYee=P@J{3muyuAltsnIlT`7MERm!P zYOR-soajhivvqK*@}n|gDvz;%Q@#}cFFcKa=3v^Zti~?EmR~BoUGn-12r9_DM)w-04`7$=s<@v z#L>?;*IkHz*HLU%wsoHy4{DjL*>kZtP(Ddo7nM#U-HQ`ZoTya*D9?i&ig+X7HE|peLZ70F`XcxbJ^ltd z8a;>p#P2-xhMCB6IW{n~0rJzaFm0G!x_L|Nih=$2EbJH`+Osq|wv(OuzV|?3{;yBG z=VVXyQiRW`($Ir9?7Dh+dFSEfG*CTUHnbjI51+sZ9BxLs=XE}SV=nQY%8*$e_Tx7$+f^C%<1$^9%iM+dPwcAvq=K%> zg1XdRex9j>|?(HE@;uLrs8RL2?W*y}i}<s|?<7!HDBpy>WdSI*(g7n)wlhIiZ@>7br*jQCui(tFcIEZ!vC9(UazF7wr0PdG_f zwKQ6=xC6W}%uAVB-hxGxg%tG}gqbnZdG{5c7kF(B+0LY?byUO_iqCWm6wIm`aC+IV zetcn@RdE{t7O}ZqUhAH+aoLxuequjp*3z?1m!}Z1`4_%=Y`Bgp6yF~Ej)k^NROwQy}`S|N>=Z}SX{0} zIBfrAl)3l6MwvN|a$T9bgtY(tO~NT**WPte2-H`0-YO!h|Eq;;&+_J5=!?qT|<9ew&qIDrps9Z)P1Dy9$knAhPq@onHhrh#H8-&kso7&^5hxM?p71-B#KAo%6XKK}AZThS>jLqK2S#4zW zj5a)CLKg&mZ4_+zIJ@a=t?F8tsZPV{&eE}tq%Ms=$6F8QIgO{lQLz);KYlr_Ro^SuiCZF4*9 zhE1VfSzfSAp%#jF5q;H`Szwn!=n$+1&6ZghOX^9&`#ijE>#W?M3M{jc_s<&aD!JyD zN@*uCl<+26uk1z>tu2$xbpF}TdKETaNvn~O!6Dd~9q{2+d9JF9D&*3%24+bBAqap+oD!8+&07JILY2hm zf{V~HNLcA{DP@JUcr9J#RJ$fh(l%j>XkO_i_3ji2w^haF3}yKUdqX(Q$tW*=WP_1% z8gU{k7b%TqzqTEhrnW{eY2ntE>T2wKm3D5J8=@vx#3``D#_Rw<0Oyw=o2LZMVguGy zT+ibaJU7DO1h`&cX+@mk75+VhvQ?Vje;f3x8@r4L+bU&?h&5AzRy~gw^=>+}l&+5} zT`PRiDg*|1{%WrfbT87eCA@7`WaV7-5@lye%@Euufdvt2)@kMP?WEdI1Ll?12X{dO zi#}~(UrY7cNDzj+<$=s)%Vi&)>FJxC?e3o#e$&wF=-k1+NxFFY_|;c)*|ubrlk-oJ z#7R;lbCT^bGR{9P@>(EbG60(={;t6J3cvch{Q9G9*SB+*(Qklf5r;nP8$+x$2!ie z+f0hvsl0~bNu3u4(Zj7LnhELjxO(kt!7pHW}sfe~3&}TYR2! zD36N0_*Y4$$C@$~GNPRCcr)kwf_wyHAg%|l0r8vD?4#m0a9&)$zIVrO@i7qBb6nlu ztbIS?rKj+Bsb9BB8Alp{x{l@(*o|B1I;!FI03v9nIIfYTp^{<;cu#I>rRh^o9p9Si z>B)ACfI~5}_gZw~?&_aAn9()v;o~ep@BRwd2|o~e$P4=Dukbu`i>5?iC{Rxf3`PDT z3JO*?lo1r4?m<)LPSdx5ifGkV-#eP*=8 zE;P=74MVXhxxKv2eJKoi4q)z0Ag-9A&SjiCCxF5sTfmI9%?{Ed^o!;JMb768awE7a zWS24!a2)J8bwe1TG7gX9B-`ASgZ#pnL6uH9j>c0IUgiW$z;O)>?V39C%(Hi9cI|bt zpfefCd2l-?f9bu;fx)S&{8%#Z7a#iY*A*qP*J(lkl|6aa{htlNi@|*oZgN=uBy!R~ z{ZSntFXuKR!mr_m1$+y^a)$i{Wq&##X@nmb+~ZcsYw4V(M}%)hN#*&rKMK$pTHS%i zj|u_15FuC5p)=NrLD2%pxg!@sfGsKj9O4SZX+8qRe9;8Sqwk%u)$9e2$cq3ox1X%$ zw+#&C1L$yO&>4*ui`^JYDa_U%jumDXmL`^V>UZut{`N0EV&6Ttt2m)UDYxFJNB5BK zXz;q}gwb^E{I(NAGssfXGk4LAX^RUbJo!#Q-% z*4!8zFBSPfe**ZQni@#-`8jAUlZ| zv;1cHYi@(`De?D#2l!{`#+Y=y=)^{y1nVmOeejB`<~oM}5T~ZrtEOgg>n83Ubwl{w zu38Fifatqi;%>n>bf!22!B)We0f;vOGFlWA)tVQ8B8oi|!yT$p!M9r}qM^$KXx9Qp zG*03yl~gznz-3wx_%*w_6qU|+W;ozh*!Yfk@62fS#1RMcTO)yF&YS2jM=im@+@a~u zZaX}%D^oO#9aS=eY-W(VB`Mcy?_C+0fVaO~H(qgdhIvB?dH z0Ggq?0mucrZpT^lLtQbOP(7z#vCxkdI~{c^4*tf;k1l>x>8MAKkCj|k2QKWSV;#TS zwnV^32C@+ODimK&nN8-7E{79KgIFoZ*PgOt*4sOBofmyTSC9j~-VDL2VjHI=iRF^^ zXB<>Q6gQqs6?u>9go5Fq;u;3bJz&%dfSF{3z@Z)J@K~9AVw-K7V%8b3V1T5?6zQE? zLZ%@V@ADe;sW;o+bjW5i$2MmtfIe{rf@n{4cFfh~MmsyB)nrgRdZRGJk+9A7_*moQ zsNchTW5NfJe;2m6OmTm1?>S_X(mmugi9}hXB6?hc4heIgAQ1g!LP0Ph1}BOM>|DA4 z_vJm-2he$)rni1XQ?d1^JA+^W;H}k(D!quXHYmZoJdUNOdBd**D;*y%bd@m;CE=zFdPsny$zosj?c(h zE##$gTQDm$$x*2kSJ0u>L6*9%X40xR5Nz$D60PDb4Um$#Z8->GL`TmJNy_IzZQ?B9EJz^hKUJ%RB~i>L9OuYZ;` ze)^p~03FdB1RjYUfG$`OyQ^DhRi>qBl}Q#_gN+qx01F?CgEuVgdu&eP2&@#wkz=;i z-kY}HbklaWbBy&io*!#y4DKSk@P97+1N#Sje$g2@j z=QJzeA%cQ78i$xcPXXrH03zGIvm73hOcA6dl_Yq`dT}k`5IQ`Z>!#*G|07%v)R56& z#|$^o5$t{|=9C3ATPcePTtF*kI;*GpBU=(Vdthk!X&ar^0zMO#x)oms%5Zg}l*@SJQOEbJYXT@h;jn;q%V1wW z2}l}ff3VMk4RWFo$?oy_Q4fV-&vdL=BJl$4m0$Y;VUv=y|BqnuDF_S26|idsj2=$H ziH?Ls#70PP4^>X`e(CvKD4!e%2NlE8ZK=0?F6^BQXEw(@k2pPQ!11XN&dsYv=eX>a zKbWxD!UpT(6O$8-kM#R1ca3)ZQy}QH`7yNiOtd0FG#~^EgOq#-F#tP^45j2F$Uf8lmcUkCazv?Ku|40p7t%x*bVJLxRVBs zeVzwyBJc!{tOp(e^}(fGmN-NV~G<}#(_q}(5>nfmoEbErxsM37~cp+ zx^A^gO%IQ9piOEm3cOJNN0h6z@#R%NfA8+?iO{~uw}0W4&oAF~)$#l8Ke9{--g0!` zzW&=*Km7djm~j8{xu+j^=da;eDBt}8cQK#pAmCJ9Bi_O{z^mfo)2zR23PXf5JzU*57Q71}pD z{m#S3?pfM@^ZxzAC>Fo^;Zl6xzJbN#SKt5O4TqMG9Nb6z^0G9DeipwAyPSkQdK(okWhCNl%5Qp)4Gv|UopAjK^g>d?S^9(_AUv%&IU8rz4XH-2!FB3tD||Im?0caJ|m`#bXUZg+o=w-8HA zc-?+$sFHq{*hl<5Td>&l&bCbipDQEKO9`^*l>S2E^@Lg)zN&|oC_6Qe65M79JS-@@ zB|_y=&_Wnb9U8{#iZvrREMvr3DZ0O-=lcqrXkp62QL0vr&=GyyFY51(4=Fu}<@;%! zF%;Ds)``2ZRlev9k2xkVB{dh&JvEEEV)vx;ByvAhWJ+3^5KSogwqrZs@^{cD9->y^ zz9|Er8vc*&$F!#L_IkA2flEX+Dv&ha2;@dQQx(r?d#0D|*)+VZ%W?S1BljO0PtSXb zh3(tkc&+p1x1GB4^U3KQK2y}-7)=b%BvX6)Z`r@~q|4$my0<2~5ANMN_ja;DLCld; zz&@0uKNcNJ$=|N~NU`8@t~h&rCIW~t_?OjrGOo&In@=D6ePcjBuS`11owDnFxi-IQe=@wDM+%2NJYppSWSB0 z;AkZ zXMbf1tE5v^Rf{j^a68_D^Ph=FE7ghWbY~!^ zbLnKG^;U~FpcuQ|1tm>-;{N}5d-M24ukwEMJ@2c1-}l9mY{|B4$(C%(n{1D_vBw_S zGoBgGK9kuJvXKNrLPAJF5+<1tC<#k}u(m&z5{hM;1V{o?T1X0kLMaoVB+&Nvqb%)( zLMaT*Nca1k^UC%l(0f1kk4qbkbu7O+>vNvv`+OgaXD4D1&C=~4%p*xeSRjUdvYFsP zmE4df$3Qp;1Aa>k0_t&!(wb1PlZJ~FEh7+dRX@sruf&{dW_O6jwBm5B!aomb3O)n! z>7pq?8gdn!BCZuH-3}XW3l9cOED{(jE@%RoQH>A0LpW&P^jqKBm(4k5yF+P*+3(54 zoz~9G9^tS5ujRzBVgAsh+wB}FMg6%!%k9jCCF5fx?7><1Dwb4f8*GfxEoh^?1u6jt zL^2~HW|7O{$9lN{`GfTtBx#DunFuO`2dY+L#4}Tn6?&`adk=Oc8J%gav8wg6)#am( zyPQ*<0|kGoyCtnKF^KJz-RO90PqL|_ZKS;rGCDIu7{gHdZe~HB$+x+V`#McE>&Vl- z5oJJ$U?}la(VQ2`!rjmSnv+YHsy$&e*m-WcxapQa-7Gf@m#P_YwJOyj!U6!wO>SNv zFZA$-oV#9*>()C3QA$p}%%|HL+;cJ>y=k^%-(3Db*_Ucp=={3!{;bQ>v3CHD8MdBC z?AbG0dvNzaX})O$X@R5@{YLiV&W@2!z(+cGkOrlr&>_IPxF1%1`Ougk&hCjI<0yd z1*O6J!PrO}iR*YPxkRE0qK3!l=;y5XnaleYP_VDre32wc z2Qw!e`cTRcU&EBxB$&}jH7>1ULq^Ktf9sC|=>{u~w~W{Z^fW|}FH3Jfgus_gQSkCyO(RH2S^N`a^L zg)mz_n9d=X&aN0tXUBPjxJ zXQw-2b{LG>SYMNEc|SYdq_JxavV7&P${T`)=;uAmthIs#%%C^v+Ly;hn`%Oio~)O_ z@~E)eL`{cp}M@5}x^MJkt$cy;VJgAy2vi^S`j>Au7-> zbCED+Q1uzrfCRcs*5M*{LX|dHuykJf4wPA>v}|GGdxe=_dg6&ED){e-%H3nT$JnXb zqxh%x6q}@Zkdd<`Nq?)3XIxl#RLC&^pEvnd)FDwC4GA$V|7Q*I0j_Zr5Jn{}8eLUG z*9gxdgH5CqmDJ5J4~!Fg_Q+3M`W=sD0H3Ip5r@wptM2bPFBsSWAcxSdIVnGm_UvWt z@O3NilK-B!g9{8iV%z!WahE6`q0dX5a*Vg<)%K$NUEaR)vUb}4pP=U;Yv&jI^Kf&} z_tt-=nNgqfH@v-DZ7(XwucOb)TibzAm%k`e>}Dr&O{hOWVv@fYlX!=~7x1`RGfgYn zRlY>z4^FZF&Esm5)L#F0EO@*4{AKxr|A2Ns&mWv(Tk{7uvEi@(BiQgMwt)@D_in~$ zU;js9v{P&|Mk`BE=+^InZcSWvh<@;z6sVTAY9z6$fm_=v*^TgUqjnCj!3*D_1zQ@W zcdHUCW3s}Kho?iBKm?jf2{H>dtOD*9OG9O_)ynHO`b6ciDkewNSS{d{gYC!&Z?zn0=+A6MsG0d8B#6RCI)~H?prkvu_Q|x)(zJs?DX7)j`7Pr%LkPye)$=CQi#a2;;)z0&YEBFQ3 z>S^{J%vBT?9%5pg_ZjgPVXGq&hT?ysO0Y2WVmL4{hyki_0S5ws1k9*#(F_4g1%$zA z-3-0uamDENh7x3b!GTCqia1!~F{f>Bwd68!`dyTV;Y+cd9#dzl?Y~R&wjQgNsleWZ!$) zx}W5+_OmjCIzBJX{vy_W9r4_ZJRtY*_Kh=+eJGy)LEgS~AM)qRPz=RR#JD&gJ&(=b zFYlDP(f(dOpMnSON4!5@k&dJN^L&2Z$7KjT|6zvMFWMnRFmIP|Hvfe8XJ)Gm*^oII z{Ufj7;Ch75^X%5o*B`<2MO~(c8F1lvek*@|9nb%O`utb&W6(KzL>}4vyvq4V=V*2- zpGNKbSpUyre$x7f-#|}qBZ;@$r>m*r5;Zk2)cFG5%S z7@z-#*%kcr1^T>XLI1y_K7XqWY{*RN|J7SQFY<3h|F?X;{vhV(RqFHMU&jbAOX$xJ z(a!_w^S{E|fu0xp7M~aM1AKDh^E^-YW_A9ze7^oM^naK7{8|2af$i|3|6;$*Y|3Oj zzahh^|GT$*zJVQ~`QMP?_nhW*Qt8xM-$HGo9gj6s37lju!&hEBQjzfO}`8 z0A+nuI{}eu6^8mO5AmSX9n@4_111hCv*>22yR`rnwp$DG*ARc@rS4!>uEnBpXs&qO z)Ld8b5#dN9(_W~tV@8zHi1X4hswPVzMH4l0_4e`-dB}pLL^Q?Q#7zQN*6_qkoDB0H1Mek9vZ8OmZsvLKrkPjgPI6!JtCTpPtpC(6! zm08XROG*nvLO7CAr*~E+=b8d-qaz(NohG}(WY;-0KBeML`(v)DD=XyNY##*Kxn42q z%tou#Y3Mu5#+@B@PsnY4@z{g?EOY5!tOMChVblzU^^h^4@6e_vQ@VSu%x`aT7Cjo* zUo)e|q%~@F8hPo51&7t{g9JcMaebOS1G_9NU8mlTM)*IuF)8DAgj3tz_*}ee6a~U)Wi-y6I9Z_A(ya z_rh;MWjrf}N>LH=H`Pqo*@#?uz&$rO5)@$wu32PaG?Cjd+IL5l=+i z0YaZ!CT^fVpw9_Tk}LU{y2Yei|3yo%ULHwtHZXuswta0Al59w*93 z;{!IX@}-@Z2Xi+3?X*iZ`cz*@Q1~Mh+`_#U+{sB%o=hKDP?`eS;cab`S*t&6Zju)c z&+*jC))5xaCtcWwF1H%}NrdjPl{0N!1NKDTwM}_)>@sgqYXh1TltlX-4I1QzDS(LHG~`Byb)O*%*g-Q(ATu zv)fe=F*jn6S}}d|^Z&j6;Q6kfa!~~0?Rq}H1yyB{A4IAn(S?HTW5+&S)g!C2Q$fPt(l8>91F?#Dw zT{CxZ``pZd#WS}p?I>4v9Gbhbvi#bNT|1H4c8D!QYcVKIUE7ZyDYs3$c;f6JYp-1x zJa_oan+I}3(+?b5TF!Ya_G+Z_z+(oZQ88;WM;7)j=fm(Or0QiQg7X@Lx3fgt3WS7j zOjAzeX~N|JvPMt6Vr6l!tTkr*~67nx9^@GsmvXmKUrBC z?U)-bl#jAK0NN>u;^^rkBW+VJoW5^nW{C624*%!HZ4VxuymBz{U}EPLj~cC7t0sGL zcKa2DFzyHk4(waOonex0Rizocg(#KNpn&BS94{~3XoUMoa#T&ei@>l{neQSMa3mL= zoI{kXssyO`UsW5ph-*WO>w-NY>NgrWyM%guz}Z8RjAJ2Lk)@P0&Y$97&cn=k@W8!tT)rP{Xte}rpoFDgHEUHZf_}tt;z9l3@NRX*+VO`*WD6LrHEmR^9{09 z>}eY^^p{n(5YE;J2msgLR&**m1BhlJRxjgf1o#skDxRRfg;+6#LtT|1HeW_6xJjwV z6&zK)fl{+DF;|(`J$sVtFKy$OX)iAxJ69^z26{#xxPmm4{>XD%^b_=-YT^6{{f7-s z-fez|n#p5Npn=963Ngl3K>mgP!~I#6ZCE_q1ROFz{Ri_`0ASCm8P|YHkO1FD)JaT4 zDi^bgZBXORy)TbD@0y!EIC1n9VP^KF-oblM5<2zGuLvs3(ka9mbcoRfunU3ugmp^frA*2N!RrLYoo| zNhhiyLiqA^(xEsj!8JJSlLv#gUh>{v3iJwqyY8zZjV>R4-OZG$nA z=C2ugUB$1qSReQeSsxi(cZlRmEQ~);IZ7eONU~kt!rkbd2>q|=9nlo6YLl~S=jup4 zi}`9AsQM73UDcR_By98|%;$%&;Ltq)CIplh8NM5{m_a0;G?U&Q2$bN_mfY>f3L~8s zvq^UATqd{OYo6MX89Gqde)U+oLm9WfHX1ltY%V_h+1QDJ<8O5b$Mt$L|P5h#ATNwg`nw7 z!iBb>GDuYmA~A#m^%ID^nxWXVgs{`)5(c`$mS{AO#tO~?3csk=pwJGO9E)}P{LSUU z{vFFZ&s3HNQv*JkO=Qc5UOf3`s05c1$r#0>7kAw-aqj5Sa!cHxwN%5o1FFoFufn-) z1)DE�jt|5>_ZxVfcpWLD8ku(Zd`fCKcE`sqr<|JY~D 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 z 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

E9D49k8woCk z6T%BxdonPO0AyC>Or}!8f@7DSn^Y|1JLc4_wgx)%Ta% z&&201X#eW_%k5u%f4Tju?=QE%NB#cu+rRq$Qv0=0ss*F_^7-vQ@BVW8cZknl(Eio; zm)pPk{&M?Q-(PP3J?i(L-~QG2(SEGyyd-JAAO~vga}BUBK=D--psRGC4#iSosy9j%Q_)09DkWP| zu=z^}TNBRZOv&PXol3y#0Ct&|B??+w9P?%i!IbfK*?27D9`W-om}G4gu?_Tg^)+S! zY6I1WMK%NV7r9PAadEW`ATHLOM%Rq90zQfs=oawR~H`9gf&h*<~ zeKnHt1}BpIC#F>$FRG^<)od0y?;;J&o?8eRgB0inrVD`Y>g>vRB5AEuT{eK%sK*?{ ze&NurIG}rs0`(qr6ks-6$UahFJ)`4kcnK`lrb|b+=-CXrX?k|ebxwty z`QvXKRL&gg4TzRwgu(gk=`F|pr3`-sn3>;YiYx%r_82MWfIvdFa6YNCi61if2bD zRzP9!1*2JGL_IzJd^?^mIMGO(TUtmMrlp01`*FdfK!UAgBuWK84vXrN;?*trdICOD zuke+gWn0FX+1Hj#w>jl;2*8HIL|;n{s#AUNg9gQIGMc;=(+3}Lcp{dd^2KKjVONy5 z6*Z$j$;WBsdV)nw>xlxg=dUNV&DHgUx96=V+*w*rl-5Z90}!KY%lI?+U*#|3@Vei| zZsPw&9(e@1nZUAbMSZ&4xb7uzA2W`Oa+4;5Qp$P>IDdSOpKG$XU*Pxuo!=KeV*2|J z@b@SHAiZ1tTr2KX6;P>I>!slsE+$Y_Fc5od@jlIzO$NBI1nv~w@NNcWp9o&LQQV`D(J`egF>XG4l zgw4Z0_XGMI^x^8?OS{D1_t1SzAiqz10uPG&r|EuNy$`!q`l7ghl|LXEvqTFM2};F%ld&V{i&Y z+^Ur%lGiD7C(Mz#nmA5#9p3sVk==?HAS8;D?4}Gylu=oP4>l3PExZ@=PcuN&OF@4#C|4%T}%Ze8EBxiPaYm1~xq(`}Yus@7uK z)Y`SDr_mQm3>Ru5bzLC~7M%nwSR?hIKQbyM4ynE2wFgb44wFh$MX%Caa0)`>840q8 zlp=^w{1on78b@~yIbXnFDh>ZU^Usytto?q)h!&iCZJh1TwO&c*0_9@9PqJYxRQYFl zS?OwtI-H)ABgzv8)avBUX;vHvJS^GPuu2Z=iGi_8!@h{`up@Ioproy^YpdV0pqT6= zTzS@sATuZLBTf_b_~7U*f;xK%I<}q7j0EOhdu@O4qx6W66rNzCcm^h$y@YXbYSyWv zV#VR5RTE}ht#c@^j=_;R7!QeQP-`fa(n=bmjD#{aFKY!lS6ySUA6QaJfcJ-6Jl=h9nm zzH@w><~)lQ-^kZ#Sd7Jz`vOT=WY*NICYR*LSYUu zbkrSWN?5D&D6$K+$SyQdb|ESO2|pI)87j!GLtP0RA9T3rO;i`iNLS61bgEDuEd9rx z6i1AB#}!n*g`IF#yOXlw-8s%|`+o3?t!Iu(&dTqY_4}2tGTV_y)%ErjwAG|JED(4I zM^jso+Hc`4R0Ql3VHX*^_&EK54%aEro`DgkxC^e!sxzhK&r51$9V$+=oS|8574~Wp z#m)3`_|aXDZtX%nG2JaRaQ|@QeU0lXa2+wQasa-HHYHh5U4JgLNR-V(L1wC78HN9c zfIJj?6Mo|bhh5{sUF$89#BuHXYUNiOzuM^v9;^8P;G+Ps z@@t4+!~7cI*BZJav2$fHE?xtNJc#z*GCoDD{h;{*n@b3&4tRKbHATB4N>$=)c7Fg4 z;7E;_r==?j;=I}KY2->$5^Frc`QyMC!)Mn;wHgh1>@>s_6(vcE+>Rr=Z77I?>Qhdm z&luFceL`n4x{Q7$z%~pe^_cx$%t1PwnaM#?kcF&<^d(4{xk+ z;mI`DU?M(`Ik0N(7yENggE>IqQk<0FFrYc0(f(iTy?1zA#rZFOW=>OYmetFu7u&LS z_o%t6>~Sy11=|?gvb3^f%aTxWqZ#7>0YV6&1qdxb2mxZ*h7dx45Fo}O1PB2_2t9-l zLJJ|LSo(ct=H1oW&XfDS_xb&C|G3gKJMYXnXWr>=nVEM4Cmb@a5tVc(TE*fR6;6j> znu--y$R`OZYS^HrJW}|I#dUtM;_jK_g)hWO;R}5yMv+W0r71Afhr$AvDDvRILchIa zaG}rU$$!1CnZ17OqxVOeRXN0svl{yX7cq%56p6>_BjqV>978-5AEybk`j~&DkfCO4 zoSiKY8+B*t?a8hI2kB-0>mU4QN_Y zP(rC8NP|{u#Dr2d%v<0H5U#a{VqMy^n8fT;4{X}7Y29Xhk!cq+RL@$tn(^e$>9abX zVbLtfS5`4QpXRHdBR>hk4w1;sNE`?T5F=BDXu)xN1WYX=3;rQCPKy388~|P}x(ciL zm{1X~rZn7N2QE#{rOLQ5&Lz{}M30jUMok$y0gh{><3#<$?CY;*H=mOE!nHfRA280J zZfs(|ev-Aw`k#pU`W^aPv{pn?q%ah@UzbOx#0}6mwJ2A&Vx@qORAD0r{}b7R)fX(e zKy)fLTFZ_g8R7-Xut#g1vY9UAEEuT*ZImZJ)V?xlm`Z0z`I9qB zQj&E8OxMKnoT|BeiBal{HkQmT)7cMaHcw2eil4~!w4BKqf$0I>T@m~zD?AbqbRLCJ zyo|7v(oXsgX$;QtkEEd##lAy{6BESQGMsY}_Ls!TEVW%rnDB?CV4=BFayIzZ5mT5_ z!JLyII9eg!mO?*~CKdvtu}L$mFp)f~5F@J?mTuE)unam&hl#`IBF`w846*=Fq3d(b{awkReP6&<712Iw|=A3`&74!8}G(Jnlx452MvX#Knm?Ok48nIPtYG zK&6N=rqBcUMtDdqfjt-|C<0->pQf;eXn_2|S2IK7ha z#821mW4l)0^}^a)uVlHO9Xj+G8-3@^H&x%|m8hqw+CuF>k(f1bqj82hKnUhjn8SuO zJJEz##;Gg<4;kkhDMT-MgHdsbDOfg@>H;5hZFz=Jx6Q(3dBPmr zL>T*l-as_BQRWDJM)s`432C@~QID%Ao>x1?h{bi2GgC8TaOYR_(#7*8H+oWVSxa65 z`~EB_7ADS})rj@)b4T~2Y}mLVIeHeKQ0%EqoYNN9+8Q;tfD}BlTDjD5+dFiUVBT|t zPBIH*F?4az@z(=s2X*iZq5;@Jt}95g1F2A+@?2X z%wDu;wr|(jwJ5Vb_B?)H;b+J_A{iPq#j&D2aR58rFOIjvD&_tZG$~B>gARyYA!1h) zmK5XyctX;BR%}9&P|XS3Y2r!_xdA5*8I@Pxr3sUSr4!JgDKg6I zpUhtU=U{XUTjRQ-dq8jUQTe3NCq9=V__<<%oxEhjZ4r%)2Kvdr5nJ(vWZ*S-bGo@u-MUm3AMnFroE3WA+NXd zqijUSxozkWHSJ3o*NC{qGEQ6yq~conQQTN6-a9uOSFfAwAw7^}TS1%)IE;9%(R*m~ z0?=u}*yl+LK4}=t$r#MwEs7+EQsa4S^NR9IpRgROZj5ba>kGBGo&a^n2Nw5Ss! z^j26IFruOolQQC1%+kmJ@WWOIEQkMqp9}n0m&83fme?489)@K*rq=VBnncF$)ESAz zEEn3iCk_Z=9bX<0TsD(umE^@I#AjKXAc=x6SG+kkMMzb`Oja)!6%Qs13LO4B$p{#4NmRN5jLp`1>akRMY>!72TH_`xH?x{0X0o%QGD|BJKNtV zxy?#+)=C)2n1nO#dQ36SxF4#OReT_>unM95wMhpi)!|`6K0>}j>FQxp?sV}Gr{n8p zVPeceDornrREpINn6u8Bg)x~rK}>Jcs*+PMdhJx0$#?3e&jAe*wvs1cf}hQBI8D+b zv5uwIvS6xInuL^ke!C208@3NpQgM)e0)&COHD%g~*R8o_qEGKM3!%Q0!R6-cVn+!f1`-nztDa*-Ie9}TdjpO%C zOjTwt#U({p7~ocKXBIADX7q&YN!dmItOQsFVhNcwCE1g+CPbT0 z$9;A5lQi$TRcR^JS*58-S;fARRFh|AWY&AK6DO2pREgz8%vB+~R#*v_`X-3ASVWXt z3~{|qlsJhZHJwm&$G|iUa-0B9$YxmPmkUG6Qd$jm0>Y-OP~TZ=&umUTd-VRZYuU=_ zQ%293KAlxU(Jt^ocguc>I?BUrqO2K14=OJDfV~sB79^I~fdk8!V*M~)45MUW>-C^! zP84Qe!N@7*Btge=4`#g@7@#qvcFwM7+OvM=#3|*?6B5qCncA)2G7@H%OrF>}b3+m9 z+_rXpc12!lZnQnuZCt_aoYegC%&9e}QNCc8QP_2z46Bm+ajZ$mNij-RO_?ezx3T>& z)PW1p%CSb6By{28{*@|ZF$P=RAbrcb@?w+sm%wOy%7OF@P<7-rUEG~Q%5UTo#`s}l z3i&Pr_bE3HpcG0=URV*kc&M&fm~&0@R5eU4Uyz~4m*l0-T)L<`Bd0XMnzd;A>ASYi z+_YqR!znQ8nmDJ`S5|0b)K;WST|LV)ttdZApKyBpl3k)aLa$K9Ucy>d3^W((iQ9zW z6pLaR%E+2jSU^t~?He&LK5Nv{+bD*;e|QSGJkRc%l>M8d;7AVJreEC5{&W8KmKthTU#Ya3fkX?eEkyn@@TME z9xoQvaD9Lb5T7=cNf{ExaYBw=>M_-|DgYT5-lNS^>vt^@S zz${ei#N5PX+xD)Rar(}>wF_4+S+ay}X?)T!qhf7q@0tzg)XO=#5@Y=-eh1d61T!S; z0{hW_eu1$M*&C#BA=U^n-ivGSr$AKN~+ao6ZG zHy&c;T};qzF`p$5V5N0~mD z*Ok{6=79{3On{LE91}hWD@=wI0i?;52~y5aZ5Ww~vz}Nci4l98g&th=)I1$a00}S^ z1nW*>J0tk)vN|CVsP0{+&NbOqW#h~hIsS&qY=3l?Z$V{XPJWGd-l@@Z+hV$>k*$d)#J>HHqU-~Zgb+4xo4lU>$Kz|zh_Eq$(~^A^aJ^o(^50m&PIgH{gYVndi9h{+Z=TqnxkB>9^q ze#J|<;-!NH;@3P_D7*uRiV&0w8era8s3OHST^!KEQrS=K=$OQ$tlWZ-Xbh$m7rv;t zh&#@utUQuJSA3Mfe0e-$9;0*{_fVCV>97#(lSll@?6NxS5J;a; zD5jXAx}n9z88xhCO0++=A^n~Tk9FF5>-0_2oIvf|YhIhab-unScZHtb zlw*%Rky6|2&p11G@7ygdRVkW zK&%Mn`=ve&mu$-ZpHk|VR*yzf%W)$R=2US-H?01oVOS^o_NU?Ck=QjNI1C&`1{KVN zakYcd39-rsf1F_0#HCxBItRNy z%YOj2VbCekiB4qFTJ=hGoy6Z_Ju5Y#j=GcRQtx$ zV_Q-xe5yppR;)O7o;=7W=L?}jdje$DZ-)vPLDN$wsE&9P+_zPl;5ZoLF!+UT~q4h-^%; z2}(8P+8OyJ#RW-u`LZ@v`0{f-*`i^eRL$CU`X+SLn8_vKn#m5H9uuI3=I2JXv+T3{ zPqMy5ZGG#c+Cr6x+QK-MO`eY$k#!UrsY&Ib8mjznH8dHOQHGH!YG|^kAz3{q)y-to zO>SX@FiipVw;a8pYDkEz8yKQcl@sbJVslLPg5zsO+@6(=dy`eiIU>5vf2f<9rMQW1 zyeo0hjX*zRw3RHCt%e+xq+JO)Dhax9L95sEm)KSMKG;FUo+SK2No@K3;x6(yA*#a? zBP2;(-8m1Fu4+F4t}#?*ZLA4j2y?S+!WX)RINbbbTA0)YPlV#Cqa*9jTGTglJ@DLf zk1)lEy!1fczDPd>`huM|eF-hX&Nd};|CW!kGW}H8RNJNP<4a}!)**l2({~|fum&&s zyU1ERgll0lS>?_Mt<})IQz_b`mVmdTg4oH3BQhY>pLYc z*s69A3mcls%$Y65le&zS_Q{2$W(X;A?YS!2lbf41babF-sQYCo+ifUYJS0*{Hn2Yfe3D!ojFjGm zG##acd0{nF5Chl=7e!QOUDTnYWi$1r@=3E`^eNcf64ZB9dHs{;tz8Q|jgX-i^EI&4 zSSoC5hORATFxD6ad{|{f8>1GZP_617R=IppSXSED5mUNzNo;T`Uo+A5XCSTHk=6*( zN&-~~DH`I{r?{;bd72gFo6gb``Rx-cvoaGC#2#wBCGWUQ{64W|2V-^xzl-0;pU2pQ&QHfs0)J44pJ?nkRd)5T zzAa>CT>BdfDsw1KS+R}>?STUMkVO~NOVyOz>C?W`wxdWFa;nEN!{YOSpebIH7;Y)vFUa1rDL^ z$GOv}(7G~=4kfFt9vBxBl2qTA5ZO1>LRU79=w(;!#}Pg3ZNersSVoV>6c#6bm5sPe zR39S8YYOJBAb4SlYS8YF$;2oL+Zbdggym!=Z@$EGOKP&TB`1~&4n-%YWvFYv!XpVg zMtHig!HR)}y+?Ad4AtB~Re6PB)wc{RX8NA_3#z3_F*UfZx#Wk{?(g3~NBuN++32ZX z{5){%ljG=LgV5pbx76;qaT*A_x#B=BZ4(boYX8l0wxoTGC?S6d^lzS^gK`cVUCfV; zt&uwYb)#`&sKbu(2;zPr<4z05ZQ(DC-J&Axlorv2Vj$6-}UVFP{yqrGspJJ_bx>ocKPAk4WoCD-95Gh z?_I2EU;RPGwT#t|9Uc>9y9DnEqN2g}&*$g`>TeQk&?s2!4Y-wH4!-XZf_1Hu&5$fLXP+lcrM#0F((75H5bc=9&H_Thbx)`@3*kitF4PY|^wVnkX)VvkvK zU+?zdz)-NmvuT&7vA1I&xXZJu%CoGkw{N?rbD(dVXGL(er?anjNW6^mEGzN-a+&&| zghYP2k)qbTc%^4~TkoJ}d2pZ`;l(nO-B5UJLm=4EJq+I^%)%dl_A-bxe8_P%{AqCi z4TE~a3!V2AtTIo*ch&fBK%&T<+c&T|=tcZkcOw+jJFZrOK_gDmXCM-Ph}>uBrppP|BgISuQ^fRg9_?(J^x>j+j2 z_6-lT2RlVARP_dj@XiXTH@lIPCtT^G;rfu^khTL2C_0RX-sq9&gFrBdSkbjamPBR- z01qK0@$PbMJ{0>N92DwDxED~UIX#FM1qh`u8P86vJTD3+TuemHj>j9yji`uzq^EK+ zgplYcDi6 z_C!v!6%lkTqcC-xyJ&5S(ZV&bjVl)o-b_54JLtR17^7)HAOZ$Vp>g7f1 zg~^^!_w!bHt72qg^sPvyy|2F;i465^4kC#G&yMb)E+mF0VkCR|+uFCbVYqbndX~D2 zs)lwBO^#{n?GRtK^$hlT+P1fK_q1*5391jsNZQ%!#eRZ+TSqXf3H2OJ*WKy-S1=UCVo+SLHh%m>+jk>v~OY;XEz4R zHgwTm>^|w!&OpZ%bKWo-b32CG`xr30L0Qh!&eqP-9?))qWr%aMJ=%HN2ik|)gUnh{WYV9rUZRW+z6Mp8f8dl5dSUqcC z)7W%216<$;o5{pwm2+4lYr^K>7VRtTYwaEFFD$@Z?5~=~=CcKCAzQ>2vn6aPTgFad zt!z14!B(n~c>)7dRJ=?%GvNr7wwu!Z~4i;pcY%}X(-E0fn z%6ix~*30@>KRbgBut7G&hS_$ugY9Iy*luFS-)5Jy@31S_ciENfDt0xy21h}@$F5`7vm4m=*^TTbNbt9?TiFlT zZR~b-2m2wr6E`T`#eU50X7{jr*-zMhHUjP0LEIa5AG@FZls&+H#vWupXAj{HsfXDk z>`|-~KE{5<9%oPB{Nk_KVXUwnVZUKdv){63*zef0>^b&4`#pPs{Q>t9y~JK-e`K$) zKe1P#V|k6e&fZ{evbWgV>>c(O_AdJ?dyoB%z0dy6K4AY~AL8zskJ%^eQ}$2nCixfp zoc)^}WnZu_*;lyP>=-mYV=&l+wJMG+=G??BKsJwpVTTwVi#>et*t4C;lXx;u;S+c& zpUBgAI?v#lJd0=Z9Gu|DEm#m+~@R&MUAVV-latr|>F1l~;2w_i;aW zcnz=RbvV=1z^C!)d32tLw zru|8KRr|B{hW1D8b$$wO<;(dBzLKxPspr*v4PVPoViMR6(9^{>T zGw zhxjk}!~7BcDE}pY3_H#q=TGn_`LFq5{uDog8_1vLzva*H-|=VpbNqS!d;S9d1Amdf z#9!ur8UGjmoc|m9p}yc>@~`;U{1_kQxNt^ixH(^#nap zPtue16nz3tR!-E@^mIK#&(yQ@Y&}QM)${ax-J=)ig?f=*te5DedYN9XSLl`cBz>|z zMX%DQ>eaee_vwD!(QEWty-u&!8}w=VbbW?CQ=g^J*5~MrdXwI)x99=g)#vK-^!fS% zeWAWcU#u_Dm+H&(Q}kASxxPYQsjt#c)mQ6l^tJkF`a1n|eZ9Ux->A3goAh?QLl5em z`ewaL@7A~ITlF4&o8GJU>HYc{`hY&D59!1Dc72DwQ{ScU*3Z<>($Ch<(a+WQ=;!Hs z_4D<8`Zx3o^b7Ti^l$1H>zC-4>fh2Y)4#1>u75|rLjSIQrGAxuwSJ9$t^PgzI{kY6 z2L1c`jrvXc&H63+t@;o2+w|M@JMu)H->d&b->;A82lRvbA^kr6 ze*LHV1NzVO2lb!p59zKcfFee_H>o{*3-R z{aO7v{dxWO`V0CW^cVG)^p|mF^%eb3`m6e%_1E;*^*8i4^|$o5^>_5Y=7_D~y%KD&tgRwXw!nYn*1R zGfp?w8yk#`Mw_w8Xg4~HpwVe;HoA;%V~erX=rOh#y+)tWZ=7Ka7=y-;F>Gu%b{IR2 zUB+(XOyex$Y~vi`Tw{-Mp0U?B-`Hn-!??h>(74F>rg5=xiE*j%E#ord+s5U_cZ@5H z?;2McR~c6u*BI9t-!rZ=t~YKlzHi)U++^Hr++y5n{J^-)xZSwJ_@Qy9@gw6d#)xsiIA|O)?lbN;erh~m{LFaJ__^_r@eAW&;}INm`=#-i@hjtT;|b$Q z_B9~l2IJ~TcuJ~lovJ~jSnd}jR1_}uunan$(2 z_|o{w_}VyTj2dI6W-^oGGB3k4P0O^+C^OoOF=Nd*Gu})v6LB?SG8Wb+n5pJOGtEpl zGt5jg%gi=&%v>|i%r`w|fmvu4nZ;&_S!$M<@+ugbGLb>d6s#$d5(FmxyL-u+-sh1?lZq(USM8mUSxjL zyx6?Nywv=bd71fb^K$b$<`w35%`45T%&X07%xlf>nb(=un>U!>H*Yj=GH*6-F>f`0 zVBTilZr)-3(7e<9k$IQ-WAkqF9`j!FCpf`4VjeILnupB$%=^usnh%&iGaod6Za!rG z!hG0##C+8JrTLioEAw&l3G+$w*XCjKDf5W=8}n)Nx8^hE@62b-=gjBL-gmR{ImI*`MUXr`KI}n`L_9v`4{tD^RMQ6=HJZs&A*!;nEx<8G(R#w zHa{^xHUDXTX8z0k-26B6oCnRL+Wp#3wa3jbaOdHD+7srN+FtE3?LzIx=2zO2=GU;5 z{fK$Y95u%*O?${<7PoZEuuNPwYg}e4mX&SgXdhX*+U-`Jm2Y{h0;|v}vWl$|tJEs9%B>2k(wbyVwx(EB)>Nz7@>)L2 zZ#hQ>U2CX4$*xGLGuy$Iztlid`)>+os);ZR>)*kCTYp->_wa@y7b%AxE zb&>T=>tgE?>r(4m)@9bWt;?rd9J)}O7{tk)Ecujo7vpfZNoOP)z7x0>}Wd% zccaDG@pgipXeZgpc8Wd0PPHf6X?D7uVQ1P|cD9{k=h}I8zU{FK>_WT9F1AbTQoGD9 zw=3*Qdy+lbo?=(oQ|)ToYx``!?btPTEet!=+YRtgdG>sJfxXaPWG}Xt*h}qY_9=F&z1&`5ue4X$r`oISHTGKjG<%(Wy1m}sU~jbB z>`ivN-C+mqPJ6T6Wp~?K?5%c>z0K~m`|N)E412&Hw1@0rd%L~E-f8c$ciU&$XW3`l z=h)}kd+hVW%jr2%kA&jSJ>aRue7hS zuePtTueHBtUuR!$-(Y{=zR|wPzS+LTzSaJLeVcu|eTV%+`%e2u_FXnqM%C4g)#SG* zzp2J<+}74U(AR6XDc@?`G!Wb#wA!R+H}-Ar>kV$T+ms*M+}=IVKD@27C%7}VeH@Ny z?&uq8YljwND5gCOS}pBuNZRU9UJLNF4cP%g7$k%Ng)k_+m_YdJm|z&R1C))R@~wbM zF(|#*x#Jm$-8>G*%nc_HvpEdL&KrLtwrd=g72@^PS@Vawdpd$vxAg4!l-q8~?R=Hn zZt3y)3wZaI*ahRM#BLdftwn9^!$U!o_mzHL`C*0Z;6WRm7X5x-K+1&p1E?MyLYoWEPmrxM&>tu zI5HG_>Uch4cSOKzBH&%)u(ei|Xt(rY)`pupW_K7gd-{4e52`k?h+6&_!;!sDw}czo3gkFQ$c z@l~r@_EoD|_EjsqzG_u2U$v@bU$rWquUgfzuUeJUSFOtFt5)Uoc?rLl@Oufrm+*TD zznAcP3BR{4Zh6FTjT?*zs1kW8Ctk{lmoR$?vyU+PC}kg|?4y$T=zSl(@28ykDQABA z&R-ja@z)a^9NeN1`>9I&R3#3*SVKjvA;dM5U=1Z$Lq)9#y-yjcp$yefx;2z;4HdA4 z3Rpu`T|-q}Lj|az0@M=zTEbsT_-hG&E#a>v{I!I?mhjgS{#wFcOZaOEe=Xs!CH%F7 zzn1XV5&k;DUq|?H-d9jnuOs|*gujmP(}d`& zC;atLgu>TE_=#Zn zh+z1dnxfk}ySr=Z8hn(qCdxq*<$y?vk4TD-NQ#e0im#b+KqSLQB*RA}!$%~;M<4KTZ68n)v-* zRW`p@^#;FJmB~-jz2B>Pq2H_8$WPP0pQe4kPt~cPrhmWB7l-M4Q?RFR$9R!_RAizJ zIM1RAMYO^1BjnWK{5~qIPYp%Ck22w-Ob{*c6D{%kDHDFxuzsQ~em~(Q+T!<9VTq*p ziKO`bgwId-{DjX>_#DcwL*F};UxzBoq4$Vn_=#lriA4B`MEHqB_-ShP)70#zsn=f@ zFo*hj`v&7Xx`PA3!R|pBifQcW?`o4^G`3m{1$%O~Iiyb8Z`WdDT#uO9`K`7LRn`Ggov0 zG6X_%VOxKH8w8han>yO~;$gmIn6K`JAR=?Xmv!r{U47>A?#y z!z~-^R%jX<;zW9(93|3=p>RxFxFX_$5haR3MpX5P@<=FkSUnGYDkDw#y|D?eo0~=9 z%#L8sP@7F@8N2a77Z0%xj-rXg%&oF$J+f#jlTFRMcbM<&w)<51^ntEEYfu!+;j?z@EMh|5mMt&?oOJM7V3&E)RkMP zE4NTrCbrs7bD^K+LO;!geqwx`>S{Urd#kH`{t#5-4MV;#H2%~H$JKZSDaJt@bYTj^a-f(KZaB9AAj(wq2s(s;n`oih?!uj-tQ}%^Z z_JvdShg0^4Q}%~b_J`}jAI9MiWoj!YS8Wj)pLfhA@tXFph>Wj)pJ} z;uxKpaB6kqsfBQ1#8A)mR)?N?LHz|Ug_!CPE^l=Rm$y2E%Ud17<*g2t$6FmLkC%i& zrzSKuyxuS_;&vS(HBN09ceqb@i4%56B5_C}aY!P8#bY_MIV6!dB#}5IkvJrgI3$rc zB#}5IkvJrgI3$rc;o9+rd!sj8uikLK^b$vo^R5a%NhS`FLx&_0ha?e)BoT)s5r-rZ zha?e)BoT)s5r-rZha?e)BoT)s5r-rYha?b(BoK!r5QiiXha?b(BoL?G4b4&GJ%XeO zholfE)GndACn>}Ul{wU>!r!U++aW2$At}TmDa0Wu#33ofAt}TmDa0Wu#33ofAt}Tm zDa0Wu#33ofAt}TmDa0Wu#33ofAt}TmDa0Wu#33ofAt}TmDa0Wu#33ofAt}TmDa0Wu z#33ofAt}TmDa0Wu#33ofX;SpdX;SpdAsNIW8N?wO#332PAsNIW8N?wO#A#M6pwp~) zdWY0n4ym;ql2IIzQ5=#{9FkESl2IIzQ5=#{9Fk2Ol1&_vO&pR<9Fk2Ol1&_vO&pR< zoR-k&t|#WwA3lL(&Y+A*)eF(u_mW zj6>3l6HuIkL#i|Q4yn`}QmHwlQgcY9=8#Iw32_#kgujz!8&a`3 zBth_9XbSC&8EHfluiwp#Wj}-XS~=;ZZ^Yo>RV*{>s~; z^jGku{0^V+)lm9MUxj$d>A|OTm7WS=%D>W2d0P}EfG;^jZ;R6Ncw3ad1U|*9nF?XT zr)DH?iyATTshnzrAgsz2p!y6{t8@ZN#pw+w6(@XEPXQ(0BCN_0p!yFe6({0D>8bJr zs67Hovc+>%u7Hwk5mxmUP%2JuKuNgpRlNmNzd%^kQ-Inrp!x~oRXYTf1dA}GrzBW! zKuNIhDLtR6mw*yp5wGeapoCY1Rl5h2u!=CHuY^^EDgR11^#+u13ZK$ficf^8zLb!P zFx8h5RuQK1C}9<0sxKw1BCPspKnbe|%W(sr%I8So*c))Dy&S?%{W?JXIzat8K>a#E z{W?JXIzat8K>a#E{W_rZtlogqv%;tRQ@;&RzYQooE1pySss9F)(2RJhFY3nurDyd9 zs6Pj&KL?Z$jd;J9&)`%2D6R!zN?&m;2vhwit_5MLAH}tJ1Bz>bPxYhps0dT~N?1pj z+Fx-i2vhqj?gU}Vzv501cCAyTTFBa=yi=uW$l4*j=u<;7VDydIB0mY%~{0G8Re#L(vOyyVnhc}@35BOAm#eX17Sg?sG3Vu%_XYl5><1F zs<}kfT%u~O(%*VrrN4!*TF)ga=Mt53iORXey|{`S^14LjT%vL=Q8|~WoJ&;BB`W6< zm2-*8xk^Qgbf~_F>bXSqT%vj|Q9W0wXz{*kEtjaEOH|M$D(DgwbcqVOLS_3sGv(!&?PG95*2ib3c5rEU7~s}Q9YNao=a5E zb=2r^iK}yon{$bqbBUXCiJNnYO1eZPU80gMQAwAmqf1oLC2Hsr6?BOTxTIBsH96&(j_YC5|wm`O1eZPU80gMQAwAmq)SxN zB`WC>m2`{bE>TUFsHRI)(WQ5_NTny1GPNU81foQCF9!t4q|?CF<%Db#;ll zxgp18b&0yVL|t8?t}ankm#C^sRMjP_>Jn9TiK@CpRb8U0 zE>TsNsH#g;)g`Lx5><7Hs=7o~U81HgQB#+wsY}$Om#C*p)YB!Z=@Qj+iE6q;HC>{bE>TUFsHRI)()8Jyr49I!D}XP@Equupz?b;oOS%kSj$8PWF2k4e6nsf%;7h#l z)v9!@w-z&<(5d=E7!>97ReQZLo!y&<2Z9~go+Fq5MAgQhF^mN>u{Uu5CnYIX4l!)a zAry)vVT+C|8hlwK`1GD49oV`ucJu^0hinNA_s2+opnG%Ikb2nBw?l3?^Lf>51z*)V zwj;&04Whof2e%?IRf|3~)O=Xl#q#6k9xOk04&ewWAepAjx}*3BpQHE*Uk$Z;O^w$Q zd+vIIF?~3fx@owlCpaX(sP(m2o!HIU6O7$9JlNgd)+3fS)gv`2`D)dq!+k?ZR_r{8C1z0t%YU>#iF#2A#fYmmGs-Z!z(GzUzjFLMG zu+kpgwX44?D3XbmdnUyj(Q>z(fRZA&Ns5R#yeL^^nNojnurCUSfOm;}tL+OSJ|?(L zWTmQo*FcO)UgY3}QsUu>r9@Q1;Lt$d*6^r{YF^XY5?HjNF$&MriYsE4ESk5_MnrE< z*H#ylg}<2mR^j7$Psd=p@VAja06!M*&BgP1&8^Et>dPAe#NwsRc-YlGFeor# ztLng@B~qNXP6(W96Oz z1xIZgCSY)Qlk6r@0F=_Atz&yzZ##D4#ta09uupghpw%Ai>2K??#io^^zWyk&yk6Da zk7f1V&BBkC%hfUl3*o|#7u#xkFemq5Jv*2rLY-|rJxEK0y(dQaPK@xM7~v#}G%#z0 zk~kskJ0a{pA?zd!Vk0dEE@dfM1|#!yQjG7U82?EzPLfO~B3~y)_)d)QpBUl958?!V zaD(WRP|Y3}syQxHe_W^`N;Vz#;)v~(vh`%OcZ;4Onkq)^X^<_{v#W1&mly=r(C|QS zdoWsVHV}`nApv_9ss`IdAgaH+KUjrvlCY_5AhbynU!gN>@9*mosYYWnMBi4?8nNgT zAvlI$e408CY#)-uV{jOgp|TE;Dl#RhR77OFj1aU+;Eo;cmtg@Xs;}C*$s9@irnXJH zD6w@UNI3U2pE!b=`2o^&Gkb=rYy(3cNUE892WfG`1%3Zz4X}ql5P${f_ zSzj?j;atUs9u0}?Mi1@i6U`Ish#l&}pjB{c=$*|yeM8tU)GuC52nFbUK~B8QUXdAz z-`nhy8S5E{LGcHAMZh}_`o=;3IOxQQWPA;>Oy0MTooe_FG9C|$7ovX3S?YK}NGUW9}hJ43|e8)X&JTBA}8S+OArfO^y zJMP(W-}#UKt~v5eCo-Q{caC_5)#u|vwa0}b25&XiqoV{*D7r{oNH;8+Xgnb3yE+(C zwY@jGLmrV4P@^L20+K^s#z`&#P(tJQ=?D2h5fHo_2!E#bx{H?s$G<#3{L1`r%5C8| z9QnYR6_IFL7+c%%38pz+P*b;p={H8C-x!g8V?_Fm5$QLE)5rJ=2)Q*%;G z{Yf!_6Jvb-lVX}qifK71=HztT6W{TlltceXIrN{DL;p!BJ16DPIVp$ENjY>*PA4LV zQIZM9#BDbTo^P-{MpUXF6k@PFYOdH|DdK43sCZ569ffZNq!%T3kcxP*=@WjG+(!x^ zT5Tl-WG)^Szf0ur>TbZ;zACqsis)q`!oucKcu{hHsR(0>DZChB!$k}>nhFnlO=W0M zrX%x;&O6wiDDa0T+QIgeFbA(Vdb}b^CS`&U2$3KJLLvx(O#uR-L=dqgPC+~*(GAe? z;us=gh<^tt#9ydj{X%|`v$K$V#Ieb!kjOSc(*84s+hB(cDh(;4C3N)l^x(`SM5Q4} zaZ(Er3dDk=4#VIeLU6oHClre7gh{aRK==0XZw9+}h91NZf?D^6LqVZY2!-N$yT=j5 zi6fn%C%m&$@9gY`>ygC^)Yc|M%&RE%I9)>^Ub3qq0U=eB6bmRy8~~J2ifoA}H6IVR zN2#e7fSS68+Y^tId57C=`SoynNLCtdw}!DR&vBGjqr6(>)g^~{?rLnL7f97qR9zhb zN5*4{3curzfSm}qHUh4Tfa@dR#t6760&b3g-ElZFZ@vh;z6iX&n(_C2m@Py3^kEha z!TyMH`DM9;4J`&IZctPfwKyzX>98QhU_T1huW?fU3BXU{pTnlq-vGale-2Age+T>l z{yA($eF*p?Sd!wfL?x_oeu{st{S#J{7_2Crs&QCS>IA%*Z3f)M>v64N1D~(yu(`7c z@M758(P3rh6u_-~72s1jY&gQsP8Z;A-UE0W-vM|h-vxL#-wpUo{Y#C(ipVid*GH`? z+|)YNnyTqmwOyexob0aDxH#Pnc#1s*aFy)?jPu=qYwQ}pwRSDwIvbX)aZY=^rsI@$ zAK-p_2=T-AFyQU>nTS8jz5wur_I1*>n6RWAs=FBNadw?%w782_XjvG?Jz6n@?XB7> zD1v%nLCX*?!s;=uGt>Cug)JVfczJ6Rer2qUc#brM-D7dvn|M=vA!6df-`c2C4pYA{ zS1xUs2zb@}6&_(x87WwJ+klPdShTE4lfxbwY*gZzu!9+b z4A@3XZiB){>uUE6wrjoZZG$kY*VDbZP1`Mh_sHLi7dEwD@=tQpLPJ%j(LHG>7PY_J@53}&40KL;s<{+p0?wca)-zfm?BBeh z6~W5QhuS3AwE0@|!g5WV)&To7Sz4o6V3uohVSC2WmcqhJt9B}E#ca~H!6M9nHVm6D z=V@odw#(((Uf6KCQM(ZKTJF&E0UE$zCs9Cx7A%%;iJQL|MxTz6>w&F9G1?RCL4;vC z)II~@MHu%k|kQ4D-?j5+lz`YCiS2*$hKjHob_Zb}QBU}H5`vMMgn*~~KfreY} z!~FveG~D_K?qfL6a0|56`U>tC+}CiUaATl>m}9NC;68wR74FY)M-i`~)a9~I?1hEO zeXw77G3-`eijsaC^X*kA?KRrmB4rFqfYr$~SendZg{%~oB&%RKvKH1NXTdt8 z3rmnoVEu6wTMH|Xn_$;*3v4+Kun>?&VmDnU`N44!^ObiJ^?!pE*>rc zE)gyXE*UNbZUP*}tc{yD>@>J^xD2>VxGcDA^!yam`!_)IE=DUFvK?)-8xf z1-n?gVM`VFpK7JjPSBgM6f|xnNLU2=7i!rQL5!hIKls|^J0n}b4 z<}x2jHVr+v2{X|GSb%LkVO9X2irSt8HyI9drwu;Vo(hMtZey(5J~%&|11EV__;qmL zp>Y{EvoZhJVzx?w{l)iSbMX^cSR4&oRE&ZZ#bnq_%+o!3fnJFB&?}H*W;MZQ<_q|> zFgt}|yA>_LU?De0a#9**{Ee8aBKg}=$=}vW{?E0mY$EM3?l8hf;GTwi2JSVu*Af2){5Rph1^;dMpCbLSv3D4Q z)8S0Gg0Xv8;n-nT1ix6TXC?4gBYp$iMuazOeQYb>9>8b7KNs#iIOK_4gLkh*{PkL& zVU4|GWWZ%=^~T1r?-*@k_ZXYtx5Mv%3yvK&I^l1I-vz%L{ucOK;rGDb27dtlAih5f z@Y(R6L-={E-h37LJ_X~v@Bh^xYUKZ<24B|tWG!M$z;YgFCMXW(T(%Ztu>kVP0`M!V z(C0S60<*Bezgyb_9^q1qx@*B7+=jVzKX`+Oz!@9{U+@CBf;Yeue1INx6toRgVDuRw zA&(jI*W#dAV;0|vvGWCA0X+No)uEWN^Z9orya!*6hglEd%4y3bz-p89-Vi*Po2DzpY1GX7F zSTt<%&e!6E_De~Dw%nE{9X|Q^&f#1+Tr8Lkd zO}i8BUbsVW55heP_axlYaL>cN4EGw`J8!ezmE;7Z{z zPeH=c8161*v*23b7Qih-DW74#MTrghYmNWHzBS@u`&m3y@#l}jqoV>I9g#67eS30h zDuvNc+5Je(WZ+cP@93ZS(}1VSU+aJRJGx=~O)YU8_V4z-teO5t(H;$HGf*#DT2tCQ z;R9NdhMPYmv^H%cL7izmX#+BDXBuQF3GGW0ErY+b%hN>L24>h(?lC-Xtf9a{|XeSAI z($P{9nw+k5Tv~d4`mA)c72aErzDz#U(pROgBWP247eT%0Lj>(k-$T#^>6fNok$!FZ zjp?_g--Q(Rr{ACcQ2Jwl4yQkp{zCdIfZj-dH~oY3Pt%X4k7gJdF&RmkmVx_kG72-w z0aax<8PhW60CF=HWwd6j&RCz(k+CJCKVv)M&dS)Eago54aaqQd8P{dp4Cs!GyE6`C zJOJq7jK?#MWIPM##f(=o-pY6{VCJKlPiCU- zGoR0VIrFtl)OhCmnIC63-D$T-Z5Pw;|EQ|*U&B~Is zjJO3^RL@zfvZ$UByNTZG%IYO(C~G%Cd$KMd=+dk!2)Z`wMuKk3LfwnMto>Q{6ZB9P zT0#6}9nN}&pck^x9^x_URdv#SVlvZoO= zC)*`xQFbdqtFzY=)RDb~p#E&Ms`$%3D|@ek0LvDJe_8gGDlYrF?3)R?Bl~WG4rHUv z#b5Tr*^d)+Bpa4QV7b($s?#Zr;?!R z9P}6Qmop=$iJ*BoO9)z#vzDNZIh_RcB<);vok)KCUaegI1)%mpq z&B$*eXkPvjf>z|OC1_)QCqX^=0|f2NKZl@w`4^2An01pjRf81xr?Cvp8E-U$nzLM zhds{_^n&LVg5L1FOV9_NPYF8e8C6h$Q4m8=QbC%6G_4?q;tC6>{}ogfQ2#5KRxpR2 zx&_q#3R(-O{}rq+p#E2|rGWZh!S;f)=!?AtG$$5ZRzP!N!F2^U)6+W&Xih9RP(X8H z!NUcQ)6*jbG$$6kSnw*vy;blYK_3=;M$ngq3eXCz!Z-yMrW9rnlvh|xP-S5?LA8Z5 z2x=;vN6?bO6$Gs<+(=MoVGltAg*yp4r*I!Z7Z+Ym(A9-E5Oizdodn%mc!;0}3m+xu z$-<`zdcN>wf?g|phoJWhKPKq&!mkxn#EYUdttg==wJ58|1E{oUa*?m79?-0!mZAkk z%K)t^T358Gs0&bU(NNLuqCJ2vD7qBC=uJh}7Ts8MTM_zD(f*?QiykUM&nY@w^i0tU zMd&X@Zxp>-^g$7NNzu`w(PE<*eWN(3IITFR7?oOFUR+h|6i)**r`RoCRNM+^b@BS* zj^ZtV`ir+0pH;jU&_%_U6<=9=9iW?w?iXSe1y!Z&9XNzAfezo{5K<^cQ zSo~S>mm03&DX~i8N>Tu2l;o8ZmsA3(E~zb9c5TM6O50^eu`U0R=O5Z4bxAX%*pOzji9W667tt_T2sVuE5 z2T);Id0ADN187>=oHDm;5unzx)n)6;Isk1c>o410b{3$$WfzrQR(2(z>&k8}yQA!G zKnKbmD0{f{UQ-mAzN?VcBPZzAV?ut@1ccD^DrUD9yz(XGD*&x6-&o#R-UDc$d}sMN<@*3#Tz+}^)#Wz;y0!ey@_Wk<0eZ0f(efwD zp9b`N`OD?6mA?b%{qm2?KQI4U(<*pHR7FBXDxjm zu@2Ctimr;@iXlL|EA~`eP;n`sD=Mz7xUu3kKzCK_ueiVBAwZ8+9Ikk#;srpjRJ>8~ zZp8^6ha88jLUJ3 zTZME(MwAeeRHjTd8ZsD_kP*>kA~h=MGNy|>jcFt!N{7lcsF7q0U7zW4M)jMT0QOmg;3GEOq#6HPBK+Ej8RymsskjmKtlRt1LCeQqwJUqow9p zYM!MQSZblA7FlYErIuN0rKJijwa!u-Ew$NF+bs2lrQWgBPD_1ksZT9cY^c_Or4F=I z4NKLrR0B&jwN&$vYMs}*eMEI?eUiP}!&1F1)z4CcEj7$i7ll;o%UX|)sPU~Q+N)D7 zb-kr-w$xlp-D#0b&I8Lx77TQ%DX@Bp@@1kZ)rrW$Xgvz&*iNTDaXm%WT~yu)z|Ve z5ml7;fA;DpmipXKZ9Gd=u~c}x2hFj_qOa0VRV?(OVRc)q3)butt+N*OcHP2EDEVVGC+AQJ=@cL`Btj$V$wa`-Q zEVa>6n=NJAbelIU&$j6{J1y^HOMPmoVngKzA(ek%evOE#li$EzZEC6J(bc^C_7T-7 z|D=fOk>5L_`sELfsA2gRMbu^aqa$j3{=|rynty#rIZpn~mYN$~y)*ybhT1uaSD1`>I_Q_u+)%{D!8B^YIy}C3&z;16D(!hTfsETv+b?m7R!s; zTfzK-`$JyALj{jU)Y5_#5w*JDIi}L3o?M>B=~3pF-P4smU1^D(=lx1c-duNt(%qDf zj>?fzyKYCNXDB^G>ETKb7n-#P_%TQ2Y|*QO)#o^+$0;p(h;n!3@^^`9);JO2pmO7BmPZE4Qk~ zR#o{!m3}`w3dgOj@zmC<<;8#>zrOhQ>#P5p)c;KtX^DKtO&6sNRoH79j* zV{?jxAJ&CCQ2h+l9ERmuFaCM1iywEg@}JfCpH=>)%D+_k(z;w{maI&2#Xgm;*+_;O zYJ-N_prOS2y3RYAt4D;-It7u{K;iS9Q%Su7|E2j7)_Gt3|6b$&y~e*q8VJE0X7}mgecLrR5o} z@iT=u#ZJ5KT?r{TxQAudm}$nwd9tM)T$YoQgo%BM~v z31p=#uHjR^mQPtMpQqK9FE+>cm#5W{os{S1tY=4(?g(Er+3064>u0AY`851VF}{Q~ z{Glr&_8HES zYSpFa?&C{+MK)I3uW3^nr`o6;s!LJtQQLmh>yY;7*3y=>TYbJMxw0)qQa7=wZbz|+ z#zy^2y_=XE3g6^Pw8G?ywqR=?zLiXzFX_f4W~~i((#V48{lfBdER3}V62Fl!+GzO_ zzu`-{DrmEopIalQg_gu$dCN65C~`3Jk=9}Ok_W>VdbsM+aIKw)ueD<|>KyWKsQ$=YBKd##*tz5q8jVr&E#KRZNv8ARZ zDjn}{KdE_pO4E(=bmXKPz9`h^Kv2zE(~S zoi)mTT>amt61Y$KYOkE(}*R^u71<+@k+ z@$#~+QhH@tu2SXwm-^RnkQ*BxMUtw_HPLzC|xJQbqFbR?&3h+)c<_t&sTmdb!WWBIbQf)b)miL>gPxed!*16jg?CC)xVCR{5+xkJdNjO z<=>nuF(=epwe#^BbYqfqwfEwFCh=grgBu(Fy0tX_+cjMs>AOYhzc|K^`FEnef%>nAAJ_Bn zKDATx6XQn~DVfWU`pmn_@WXVqO?hewJ)J2s_de}$#Vz*7THcQ}pRsgxRjljmQvN?x zKL6CT!kMM^N>|i=Pgnn^iy!AkrDL-TFg&?Ue4Tw6588{;r&GEpKn-clFOA z{&4O>XZ*t0oEwxM&g_MAiQ#N#INx}urr1r6UJIq$>2Zi2!}-biN{4fjAwQgf4C!zN z@+$SyUytEz;XO)s*JJ2Yk6uIJgh%&5y}Ck=VcvYbdbo0aD93O#P0wFyEtE;v3Yx0~ z^q%gHR{A-WLp43t(qo<;)f&6BNMZ;jrnFmEIXdg;{7va_erb}X^|po$W2>t4q}Z{k z(gXCU_ho0L($~q+`9D3r=FNrPt+ZM^XF|x)(lyXgLpWm)mZ%~+lwO#VaE3}}VBAhB zpO-aVt&ym7@mYvye%q(dY5IHqJm^pd!roe(zDnk!oN%UMglg;v32XY08dhh;84bz( zZeM8CE;Fm3c@Ar>qsHISA4>e%-*Bt>HxZ{9r|6hnT8gv78v@_-tEDWaUoBq`5H+C?~{YerJiw>Kjx8Lfw!uXDMjXl*sjUrAaO(aW@4%XB;y&GG8Y zfZ|RNXR(F%t*-iCA2o-J-wcIMRfRVqPLEDqx)l-Hf3foq<#l#y_~JH5PRc`{uuwaKpX!eL&KL@i|{E)cpne* z(?{Ej$&dIkeByKrU*a_5JCVN`--&-SLn5;)TU2gabk;?mwm9KjcbpENTtpJ~c#qcR zP)5ooW})M)EN&%AwNRB(H~((@hvT4~-YjUnH=+45zZADN@!ByZl99DZS4PT)Pgu*p zlQ(I)JI$OENUxi79`Dr2Tej_;>v;$Lc;54z=bXxWp7-!Jy1b#>hj(ax=k((Zn&rHy z^dxV;UC7&SFY#{kX5Mxi&s)lWBYY8WB~9mD;v*K<^7cZjl88)-MPuF=^o-N zaBI`f?{yDz8#wp5NAL#CgS>~E=REEf@FvYN_jtFHv)nz|J=Iy^4#1kMaxdmBxb^N& z+|ka9?$4NweZ~EydyDgddn<3hedON7`)~hp?{WX(eB$nLi`^f3w|ci?T^D!@md&+yt?e7(Og|v&!-e&h~?^SP` zJCL_2|LzX)KJcr$Kk^Uqo4Ql|W_~kwo_~zr+`YqZ;kR(_@;mq)-TD4$eouFSKgqw= zz1P3Pztdgl|Jq;ZKHxv-Kjc2-KjANT7x~3OOLuXQ7o6s93wi}p++D%1f_Yxc;I3f4 z*E)DOSnRb4)&y(4cJh9#*CBW{*y&@yPd%2eN{gR*k?s{`S9z4MxYsJ0rEjd&;^{r9y2|`8DIby0xkd}z{qdv zZLi{u*~fURE0Ck}QuZ^BMRLsc<(+Id+eGQ`*i`AJ@?LKC7rd*@yJoz@=@vhh-2=Tq zIZd`nF--j&5qcB3A5=uvzov!BQ4RZ7b-k=;UUS7mR89`4jqei$}9ZWWH) z!8oqqcuMvIjzJdtnjM^FpMm1{c}LtW-l6@A#^ST=TWryM^O4$Tc~Otr!wvcSa{PMV zgF_ncNQt83o6yy=ZzxCTWcGSJKJB=}hRM6fUguy+a%1X}16CZc^}ylDjR&r+x+Qf< z>XK?Bs})s$=-_0H<~2Ij7+Pa^%}WkW9x~>TrH5=ebZIc9GNN9`CZn5dsn@aT z^-bS8a$4hl0!g9SZdPeGJ!+bu$%rN+jz0RBmB+lr>f(=E)M~i3#U|q7DF2X>Bb=Qr zlP#;YtlzSI%V8}iw!FRNy)75DT*7fh%XKX`;cf??wA|jxY1zJ2vQ@QK9a}AKRopsg zUB7kXR@GV;n`y?yoe z&D-Cse#9SWzqb9x_L~cOwJ9p-mEXJIB7+VCJ4_H-_!3`-=^f^FSkPf@hiwIO1ad9U z>DVf7bKAUjPRB_Ca@t(t6rQ-|T; zwe>o7F6t6=nQm~x=;*(o>yi_|iMOBBDh{c?CL>Pjb;DULYH&>zh4jw*u;^680Cz@2kvq7H@p4+niprXMG z1}_=>!r-lg-x<I|wgwC>PGLz@kqHFWiPo%oMEZ_as3z?}0w9#&&m{b9|A<>9s;)@fL`VZAu( z3)&CsTce|MYngZTk7dV#rs-?8tyjTx6m+rSl6g4O|DN14`{&5AFelU@dr#(l!A{f};Rh=AH*efuDhK;8yUf zY{oAp)eFFIa1j^*E&-Q;k>ICbG(hs?j{P~vS-Om_cjHPAa2gQZ?gjdSvp_$9EFI+N z3rn0+=}#i8rP_4_j)i3+=~CXxVHh(^LZfBacVQ|QwG)Zz3F&8{Ivre zz_9>r3(&OS1aKla8Jr5b1L`&SAvhiM0cQg0HlWM_^%tBEE(AXU7X#6I(RtB#(RH*g z7zch1t^`KkHzD(s(~6yM-Uoko8ZeG;;9d=`aT)|o;Wf3o)*ip2OUPPvVkz$9U>SG< zEC-?srtZa_i7k_Qj_O$J)fCV_DKz zmUOhAP}(^cdMF@Al}b-aZmhxbN7geHdMa8^9~z zRqz+^I(Qotft^6)<^T^=2lYV%&=iE)k#+|`v)2<=GL-KHxa@<3Wh{~PSZI;=crXE6 z1tx+?U<#NDrUC8_Si>~dFsz%oxVM8?9bpC2+~w2U<61F!KL6Q;BtWe z1XlpcgVjr8_0m|qG<&t3jeZOPL&4MF8S1wwXa=SMvGv&WGIqBjyBfEpJ=ycDfpZ$@ z3HpHnz@&(^)>I#cw+K7}7K1cc3LXc`z!P9OSOKCOI)8-z6W9P=1h0Ts!C$~%!47OT zn@;cqqJbNLBrv5q2YL*c0ImX4z*JyLdpk5GW=5-ly9>LY0tbMq;2>}?s0r91EvOB; zBc1pisXWQh*FpZ_ohL3K9}6zlSa$15Glvr}BU=qL1t&S(y{b<4#1`->*arRz-(@bg zfpfBi)cwo4A;%uTSm(+t@Wc4qS1Y^*zhZsG^8OM032Xo_f>*%aEV1luMqTS&O?n~H z+Yk#p7K{fIz*WFlUM!Z}>9og?LROL0HPX?2%tjz7*}qKoE~C9Vva8;=r(wC871t`G zP2ZFz)u^x)OY1F9C#4q@*TpIlgS3)zedE(PR(k>8MP2(E^vr2%`#aL2_N6s_nU=IK zE$7RY#`IH8`&L^;Wp?+wU~^9`(_cCkOL81_9`8+ymSfee9t?mU1f;)j?w6JIv+ly* zd~i2d0PZ1;@~rP;Tw4kr2g^Ww>|pM8(q|J3EaL~UzJCTUgQyQI4Om;sQN^sVJCD5JJ`J!yYi?urM1 zs^B1SFsKR4ow6=8qmXZ8uIOsglDo&B(d!zF0lxVB&|1PgM@f&ub+tDW_H0}ofqQlw z-p<~2OndA_h<;!okaiRU^QETL`_hYe7>O(bkAS_+ zjC{pDIeYVepGND^2%6Cl<7J(R61nb+Ouvjgb1g%a^!6Wjo90>1>a!L8snz&$=7 zR{8nQ0Kr19yV?U;(%n+z%Fl2LNO1;8CyyJO&tJ z2aK-+#?}F2>tH2#3akc&U^6Li0b5Ch@igBEXpsC6d<1rb|A5cI9;ZRd$!1b6IT`r1 z^c$onf?O(`!}@RZtzIBKxp#aQ`Vh^y&*b*gQswp2%$x>0t;c4qS&cyqU+Esxgd7~H zxzOh|%zS>A#@Xh+Y>5F_{#(CdGTQnGJK$|LeOcV8cbUYcvEs;Dw zV}u#AP3DOj&#(SVn#pq#eWv^^YpCfXHzhY>&&M#EY-T@`rT*5^%@)FYB+YiQo$|-=+I*T!*J2_36Oqa!=$TPl@+G2dv;Zpws5N zGK#Tt9$(<8jFWU9W#q%y!F>jI6WHnWPmW8@_q-FWz*hrMpRFNm0%TE4c4)CIOWaZ>K)*2 zupm3mV|LZMFFV~^m>s7nTn#0UIVX#H?o@(wLu+F57Y-IfUclB=mAa#y}_BFFE|VI1J{5_ zU^18jt|gbXyay@q67W=ZjrUx3O`-`n5*!7N2FHNrfG5(4Q^0A^J;9C8H-VeMFTpKf zadr*mUgaFa*9V!+@aTUdiz4c;Na|0oo#3>kq!~(@(K;@~eHJi>M~O3(I75julsH3) zGn6<(i8GWqLy0q#IFo1%@<0Kgf0t+n==&py43fwoi42m+Bz_2bfzv^6z+4;>$smyo z63HZf0!D&SfG3oRE5I0V4VVNNvm>btlFB3)LnIhOB+$#mAHWLAQ+_Fr@K~YXF>d!> z!)3g#GX(#@-39&y?-SgAgHOS3&iAmIgEI1NecEq`Oa65xVKnFD$-fG)@_<=|w9YE9 z>cF1?Sb5;j1mr7)Oj5|iImB-dhp~#^31E|b)~oov!5N@0V3ydwExXQVoq|u>@$Uuq zf&0M&fY#@)(AFVO)Lm~I#{+$2?4JsdrO&*kk1T!U=rg|Zk%zyQ7IF^hAE71RjzxHz z(!GoOAuy$<)IRrnKSdfW zs}wb#>Ihgr;T&E@_9o@H+$)lKdGfAI)^)wGoN3CLmU2?IbX2xJvP9iaXwPg=`UU8QNtAkquAuE9ZNb@kW|J$ z0MrHamHi)sUx0;Ln^;@*Q9ZiaDt4u+=1DZDCJ^m0nj_jG`HAPHW>kW8k z?@C@pejAM$VrNAo&IF*|i$ywr0BDQK^B^s++zZOnvD?XUIb}vmhZ5pE zO6=;fam=b4yO3+u_ucZ9SoRVN7+Utw3bxkvTiOy<(b^Ts8~qdMi{$46B#y>QokUXR z>@?0qi~r0uPix@?r~|YHucUNFe@(5uga(>gOQ`>n8p}|Z8G1|mV(*QeH`XzKsja+?(wa!2PpsUwjn-_oJ^or*Ru`+zwfonL}l~5@!5zej7uw7!|5FUP0ZFHX<0L# zajww0klc2_{p|mnC*e)eGtuDz=pLG8SHzqP&3fhU!&@o)xEeQE-t4x#FXA+|mXQ1V zf9si)t}PmaRHuNcfEKT7h3b-Kr|;YIsxP}n*s1zO-iF!#W(!s-d^77%w52wha{T{- zmEYS+{qN}eJE;oq!`!u<>-OJEuxj7)drA0hcG?;<5S=j*I}~s|N$_ zodKh<;C!@V`hKn0uNAVUG_DoFg=of%{hF~~Gs@Epc^mqF{0*M#_v`+C-LFXZgNxDs znfo3CwTkyeD&!i^a2+90F#+f;$1vc!LFiXVn2uAcKYA0q_V|4AS6H@LRA1{0=+@eh-#{#{o~q zgD1dpz!UO-C*;ACfE9+pD!_BofTv*r&-w#aPy~fw4R{v3Ol@w)MWcf)xM+3wZWK@1 zKL8(sj{wivlK%ueYfJtMd;%t1|I+rQ-AkL7_AYIG8IU$E?OWQmv}<_ zpCR`da$mar=CseftTryaURc&bm%ldnU|A9D{Q;04R@!d%ursW--OOs+&8)WF%xc@s zthU{pI0iHar-7beF>7=Kjn}NPz6@&-rerv>0-3pCU3D!hm1WKI>}W0X9q`XZ`Xy_b zCAN|k+a|ViSP5;`aGzsiD_On0jn&&FXR?<2%rax7a0iXF(}nbUu@a>ZzRrOj!kVz5 zoX;fZ)T?tFpl+RC0qnbTC%6ZHPvhqqu#WQ=zrUDND3-T$tcbDUjA!MwS90p~w^&VPbC68Cc4F}Ob?{6y$$a9K$g zY;ulEY$x9yJ3EbHS0|*(ZUUp&9UxpGe-`_6kJ>L;B=uz^yA>&_bkXK;t$)RzfP zMk>B;%%|TfYxr0Rme%!qhnUszwAHV;GCtLi^`36(NY)3``zF>YI|qH4HNZ}z*jm4D zbOrZUShJj}Y_+kIs%)jPQ{$_y4t7$hQ?u(*tW`~&0qFUs`r@+AK6SR(Uaov3T*ps! z$<9u7h2JAPA5G|jWppK+(&7_Nst@)}uFclt2O26Jg_3txIy=pH!{P_5J;~#}qP9*u zX7-O~y+>!K3#%Ktv)|qivA<`q=U#tj5Z`|og7uy1{KA>W^W^C~Bbvj`xwkpDJHO&9 zA9wPNkNcfpI}4rP@STqb*?aGC_T77eJ@;1dosX5yD(5Ne@-t2$UwvH5cW0h=*74ns zP3*b%D*NrdjvaoVy;A?_{LA?_-}=bP2WDK)_1(Zd)UCzWKI*vj-3ENgt+Ct0ZR#HB zHgk`1+wipyzVyKtKF)Rrx`W+w+;iOv+zZ{|?nw6vcZ~ZpcdR?k9q&$XuX3+;uW={4 zQ`~FasqXdeEWZ0O*S*cX-Tjq2&%M+Ajr)N6TX&^f=)T}?a$j~|cZ=MQ+)unJ-hp0K zZ;Cg|yTQB3yV;xL&Gl~ce&yZa-Q~^q?&kX-_jvbt3%v)thxk6oW8Uw*$Gv5I7i6Wk z%3I?->%HP_@&4j%^R|0Cyw|-q`5MSO-uvDM-aov5djIl1@&4_7>h1BeKA*hseV^}0 z_y_QnkedFXel5SYU&lYpuj@DTkMJA&P5h(zT1YFuwco}s@Z0(A<*On7@qQ=2v){$< z>YwDF>UZ|f^p#2@Kj z?*G&u<&XBS@W=Y&{PF(J{R#e+{zU&8zCtqDpW2aXZv&fd;EL-`~3Um zdnEqD{v!Vof3g3#zf8VN;{U;4;XmoG^jG;$`A_?+{b&3_|5^Vz|BwC){)_&f{Y`$c zpAFo=4-!F@;J~0-P(7#-92(RP>I8=c^@65Bv*4)U=-`;3dGLdvMbL_Goa6^51t$lm z1g8f5g8spP;B2-v9~2A@&I!&9&I`^DE(nGP7X=pwBZ40XmjssvmjyowMh2G$KMh6) zV}hRrR|gY=Yl6vq2jSXaYVeC-T5w%3J(v+(AIuD91vdmY1~&ya2fqw%31$a#f?I>R z!EM3q!6U)qfPQZ9+h9rXyWp|l_rcQO@nBi-M6f*gL$D%vGFTa`3Z4p{4ps-x1cmaQ zmEh%IbMQ*AMZUHYycxU|yd7kMzXk6E?*{J$?*~P}--DgO2f>GlL?W3uAaP)#YNA@A zcA`$AUZQ^D@I-?|!$hOR5sAi$A0%2NS|(a0PE4GXI5p8N(LK>4aeAV6qEF(C#F>e{ ziL(;@68#ec5@#m{CI%%2C(cO>NnDedl$e~jHZd(RBQY~ED{*_`SBZIvyAtygcPADk zew|pD_)TI-;<3czi6;}Q6VD{JBwkHyO>9g2HL*QeCD|}pknEG>-L&NS$xD+zNp4TR zmV7<=M)J+%Tgms5?iATrRF_oO)QPE6Q{7WNQaw|>QoU1sQfH*jO!ZCmOP!q>Enf-& z&U2?;e3ZK9FTN&p(|rFzD95{G>_y_Q?xkZ#{>X6}@hzp)blz_IM6X5%zTV|Tc+D%~ zjU9pE74gma5RP{m_N%`$(ftDf`x8d^vvKzU5(Z548ar+1=S>#Zrm>7Tz8WU(Q}~jy z$d?!8_it~K=1a>jc@%!kEz~^z2L5lHkdCfLbWV=s@Bwn&1YhJT^aoL#E2F##Pp*l~PG*%l*A-Hhm732&T=^|B*CapCg%+gi z_j4S@1Y84nkt`RLxt`-+{AsuxzVI|(MNWQsacKOT@I!t}LCO@>2l-u8-Zje;zvaat zamqE3OJn33Pa}+9>QQ7{=y%88CA>=``4xZ5!n%|^%egi8hvc=6mibCt$!p|K$|CWbb0ZUb9?1fKi==MMbvdX16P_ar zoB!N&j0{Y^C^NDZ8H>9?;PWhY!r_2^eBw4YLs488?VWG&UIwIjXb{S{mxlSJ-=x3E%H%04_Y@259v*@( zwmIHs#dq85#=eUDu~&xAJ06AEe78|{3eS^(mC3`|F|HtrPhF9Z#6z1GmtUctLu;8O zZpowM{Y1$x?n{7norA+n`redcPcf_5_a^W8$h&0ER&UXsN#5k*>fVEU9`okzdDh!n z>~h{r{Y~EUy0>A^|0yJ__*=$x;9s=oa#N1%^KJ)$fApT^*;n0e+2@_&5WHQn!5PQe z_rD0TE1kQuPdRsGSM!YRC1mlwv)ajbwh;C(z6Nnj_FlIQ*A`@-W=3r_&*rAM2W6-5 z3~mCmZTWoJ_rz=)BQx&VyEQEdH{0!=z00lQ-CNwzTUcBx`=r}5dxhIOyNa2%ci9{D zB)_*^HTDT$v+-XoHps$xV66~eX>|OY73;lMqkIID>`lL0x#IIt!pBOlPij&i3a1w1az|AiVdznQh^9%60|4 zx!zyyhnlCTPbPG9Ztur>UtH$OH{Rc%ujcrR)yB<5pB7Q38Mq6O$!hwTt1G&WzlZVn zFz&m!FVGKvm-xipj{8UyPWrks#@J}i@vrIlXOZq5TIbs}{4!@deaA=C9$u&XQu(pr zx;u0I%S-ZU($_kiLY!eARO)E1`#fWjoOAdy;4hGHxGzNE)pd8zSp`m0F)O3FGpEHC^; zwj5#K-4QwQRo&9%5`R&-a;|TP#!zAY!}Mkl?*gq;`DU@C#n^&6cf3cPnT+3NnsHk2 zAnDM*D&xw1>Kq-<&BUEq=DG=&zeERoiLr~T;gGTHnU88x#7)u1@YK& zmRt|V*RzOwMmh24_{$B?xKi^K)!Bw9e}$1h>r&TJAKxU@=8|Sb?l+m2lC;A9r1U9E zV{fuCeNf|mQU4~Kam$7`aw^GdSwC{^`MuzEu2%+=PDO5IVdYxo>8~g4%CLs^f1=v@ z=eZ|@afN-}=Y2*5I?q(9+{LcmJA^0x!fUo40k2R|Dvb4K{5xJrx*;!$tNb*h@VQ}( zF6V|VQ>Tnq!~B#fV@&VNn1=j>{&jTpWe%TbPjrA=kgXtIe8TBb8w}skAh{CBKQx_i)R%p3a!uu@Z zg?(hh$JKfGP0nk^^<zSHC!bv~#=albx;;aK5^^G+RDWCLF{}A<&!}Cy{GS46o9r&g8XTpBrm3w|*WhKuk zq#V-!m#{H7GM13%3^AUSmE-}wgi}Zyx$#K8qCT?bAv~9H3~-N)coJU6HZebh)9WJd z-@y;tx3p1_t4Rl)mb%pXlsq0?k{_E-^ql+n8dEQ3&P30{xfa{cle}t-)SoN;hbjQt z$vqcOxxAcn?}_5NA(#(&0&rcR@(5{3*UHaaBj!S$Zd;i08aEb3ZK1|jA`>l3#4DtK zW$lcFGjh}Gox{99zbr8E=eW7{R^y1pWAiWZgEPc6WffP;qjlsSgO5D7Mz%R$keLrH z=gf4vDe>doR@~Zqi#a3UH+A#;fy}{3xqc2F1djshSo#S20-gV-@3l8~Z*f%;zL0C7 z?#&=?%bdHu64n#GGKhZ5SXx|zsNPjhAuV=5+kWKUC(i?H8TvVOEPamBdd_%-bp_yB z;tt&nN?)tIkT3Dqh{2qTZ4tgXkNc^?HN!VGYC zgtqeqVxvUo#QujmXmqlqE%023YhnXK*E}mUa*SNDb^f$HKk&~Yy+M0CA3Nz!*fT<0 z4_#7-J_;?agfsb$T%HY2A>ZG|?;z(OlC4`|(%toX*~=12eqnfX7V^xo;#iH3FXr~z zi}$^y*JLl@r4;3tWG?|@mrI9l7P}HB&y7y1#+fnN#PUURl(2D>Ckw1#^z#gnvvRomw%EAv=hzpt$S!`;SiwSEcl9b87J~2wyPu-l6SIOOHpCbE?da781n7OIs*XsWP$cWpZ literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index afe8a58..f69ed54 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -53,6 +53,12 @@ if(WIN32) CACHE FILEPATH "Path to zlibwapi.dll (for installation)") set(IRRLICHT_SOURCE_DIR "${PROJECT_SOURCE_DIR}/../../irrlicht-1.7.2" CACHE PATH "irrlicht dir") + set(FREETYPE_INCLUDE_DIR_ft2build "${PROJECT_SOURCE_DIR}/../../freetype2/include/" + CACHE PATH "freetype include dir") + set(FREETYPE_INCLUDE_DIR_freetype2 "${PROJECT_SOURCE_DIR}/../../freetype2/include/freetype" + CACHE PATH "freetype include dir") + set(FREETYPE_LIBRARY "${PROJECT_SOURCE_DIR}/../../freetype2/objs/win32/vc2005/freetype247.lib" + CACHE FILEPATH "Path to freetype247.lib") else() # Unix probably if(BUILD_CLIENT) @@ -83,6 +89,10 @@ endif() find_package(Jthread REQUIRED) find_package(Sqlite3 REQUIRED) +find_package(Freetype REQUIRED) + +set(CGUITTFONT_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cguittfont") +set(CGUITTFONT_LIBRARY cguittfont) configure_file( "${PROJECT_SOURCE_DIR}/cmake_config.h.in" @@ -154,6 +164,7 @@ set(minetest_SRCS camera.cpp clouds.cpp clientobject.cpp + intlGUIEditBox.cpp guiMainMenu.cpp guiKeyChangeMenu.cpp guiMessageMenu.cpp @@ -180,9 +191,11 @@ include_directories( ${ZLIB_INCLUDE_DIR} ${CMAKE_BUILD_TYPE} ${PNG_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} ${GETTEXT_INCLUDE_DIR} ${JTHREAD_INCLUDE_DIR} ${SQLITE3_INCLUDE_DIR} + ${CGUITTFONT_INCLUDE_DIR} ) set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/bin") @@ -198,11 +211,13 @@ if(BUILD_CLIENT) ${BZIP2_LIBRARIES} ${PNG_LIBRARIES} ${X11_LIBRARIES} + ${FREETYPE_LIBRARIES} ${GETTEXT_LIBRARY} ${JTHREAD_LIBRARY} ${SQLITE3_LIBRARY} ${PLATFORM_LIBS} ${CLIENT_PLATFORM_LIBS} + ${CGUITTFONT_LIBRARY} ) endif(BUILD_CLIENT) @@ -364,4 +379,8 @@ else (SQLITE3_FOUND) add_subdirectory(sqlite) endif (SQLITE3_FOUND) +if (BUILD_CLIENT) + add_subdirectory(cguittfont) +endif (BUILD_CLIENT) + #end diff --git a/src/cguittfont/CGUITTFont.cpp b/src/cguittfont/CGUITTFont.cpp new file mode 100644 index 0000000..c883d4d --- /dev/null +++ b/src/cguittfont/CGUITTFont.cpp @@ -0,0 +1,1141 @@ +/* + CGUITTFont FreeType class for Irrlicht + Copyright (c) 2009-2010 John Norman + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#include +#include "irrUString.h" + +#include +#include "CGUITTFont.h" + +namespace irr +{ +namespace gui +{ + +// Manages the FT_Face cache. +struct SGUITTFace : public virtual irr::IReferenceCounted +{ + SGUITTFace() : face_buffer(0), face_buffer_size(0) + { + memset((void*)&face, 0, sizeof(FT_Face)); + } + + ~SGUITTFace() + { + FT_Done_Face(face); + delete[] face_buffer; + } + + FT_Face face; + FT_Byte* face_buffer; + FT_Long face_buffer_size; +}; + +// Static variables. +FT_Library CGUITTFont::c_library; +core::map CGUITTFont::c_faces; +bool CGUITTFont::c_libraryLoaded = false; +scene::IMesh* CGUITTFont::shared_plane_ptr_ = 0; +scene::SMesh CGUITTFont::shared_plane_; + +// + +video::IImage* SGUITTGlyph::createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const +{ + // Determine what our texture size should be. + // Add 1 because textures are inclusive-exclusive. + core::dimension2du d(bits.width + 1, bits.rows + 1); + core::dimension2du texture_size; + //core::dimension2du texture_size(bits.width + 1, bits.rows + 1); + + // Create and load our image now. + video::IImage* image = 0; + switch (bits.pixel_mode) + { + case FT_PIXEL_MODE_MONO: + { + // Create a blank image and fill it with transparent pixels. + texture_size = d.getOptimalSize(true, true); + image = driver->createImage(video::ECF_A1R5G5B5, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the monochrome data in. + const u32 image_pitch = image->getPitch() / sizeof(u16); + u16* image_data = (u16*)image->lock(); + u8* glyph_data = bits.buffer; + for (s32 y = 0; y < bits.rows; ++y) + { + u16* row = image_data; + for (s32 x = 0; x < bits.width; ++x) + { + // Monochrome bitmaps store 8 pixels per byte. The left-most pixel is the bit 0x80. + // So, we go through the data each bit at a time. + if ((glyph_data[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8))) != 0) + *row = 0xFFFF; + ++row; + } + image_data += image_pitch; + } + image->unlock(); + break; + } + + case FT_PIXEL_MODE_GRAY: + { + // Create our blank image. + texture_size = d.getOptimalSize(!driver->queryFeature(video::EVDF_TEXTURE_NPOT), !driver->queryFeature(video::EVDF_TEXTURE_NSQUARE), true, 0); + image = driver->createImage(video::ECF_A8R8G8B8, texture_size); + image->fill(video::SColor(0, 255, 255, 255)); + + // Load the grayscale data in. + const float gray_count = static_cast(bits.num_grays); + const u32 image_pitch = image->getPitch() / sizeof(u32); + u32* image_data = (u32*)image->lock(); + u8* glyph_data = bits.buffer; + for (s32 y = 0; y < bits.rows; ++y) + { + u8* row = glyph_data; + for (s32 x = 0; x < bits.width; ++x) + { + image_data[y * image_pitch + x] |= static_cast(255.0f * (static_cast(*row++) / gray_count)) << 24; + //data[y * image_pitch + x] |= ((u32)(*bitsdata++) << 24); + } + glyph_data += bits.pitch; + } + image->unlock(); + break; + } + default: + // TODO: error message? + return 0; + } + return image; +} + +void SGUITTGlyph::preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags) +{ + if (isLoaded) return; + + // Set the size of the glyph. + FT_Set_Pixel_Sizes(face, 0, font_size); + + // Attempt to load the glyph. + if (FT_Load_Glyph(face, char_index, loadFlags) != FT_Err_Ok) + // TODO: error message? + return; + + FT_GlyphSlot glyph = face->glyph; + FT_Bitmap bits = glyph->bitmap; + + // Setup the glyph information here: + advance = glyph->advance; + offset = core::vector2di(glyph->bitmap_left, glyph->bitmap_top); + + // Try to get the last page with available slots. + CGUITTGlyphPage* page = parent->getLastGlyphPage(); + + // If we need to make a new page, do that now. + if (!page) + { + page = parent->createGlyphPage(bits.pixel_mode); + if (!page) + // TODO: add error message? + return; + } + + glyph_page = parent->getLastGlyphPageIndex(); + u32 texture_side_length = page->texture->getOriginalSize().Width; + core::vector2di page_position( + (page->used_slots % (texture_side_length / font_size)) * font_size, + (page->used_slots / (texture_side_length / font_size)) * font_size + ); + source_rect.UpperLeftCorner = page_position; + source_rect.LowerRightCorner = core::vector2di(page_position.X + bits.width, page_position.Y + bits.rows); + + page->dirty = true; + ++page->used_slots; + --page->available_slots; + + // We grab the glyph bitmap here so the data won't be removed when the next glyph is loaded. + surface = createGlyphImage(bits, driver); + + // Set our glyph as loaded. + isLoaded = true; +} + +void SGUITTGlyph::unload() +{ + if (surface) + { + surface->drop(); + surface = 0; + } + isLoaded = false; +} + +////////////////////// + +CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(env); + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + return font; +} + +CGUITTFont* CGUITTFont::createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + if (!c_libraryLoaded) + { + if (FT_Init_FreeType(&c_library)) + return 0; + c_libraryLoaded = true; + } + + CGUITTFont* font = new CGUITTFont(device->getGUIEnvironment()); + font->Device = device; + bool ret = font->load(filename, size, antialias, transparency); + if (!ret) + { + font->drop(); + return 0; + } + + return font; +} + +CGUITTFont* CGUITTFont::create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(env, filename, size, antialias, transparency); +} + +CGUITTFont* CGUITTFont::create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + return CGUITTFont::createTTFont(device, filename, size, antialias, transparency); +} + +////////////////////// + +//! Constructor. +CGUITTFont::CGUITTFont(IGUIEnvironment *env) +: use_monochrome(false), use_transparency(true), use_hinting(true), use_auto_hinting(true), +batch_load_size(1), Device(0), Environment(env), Driver(0), GlobalKerningWidth(0), GlobalKerningHeight(0) +{ + #ifdef _DEBUG + setDebugName("CGUITTFont"); + #endif + + if (Environment) + { + // don't grab environment, to avoid circular references + Driver = Environment->getVideoDriver(); + } + + if (Driver) + Driver->grab(); + + setInvisibleCharacters(L" "); + + // Glyphs aren't reference counted, so don't try to delete them when we free the array. + Glyphs.set_free_when_destroyed(false); +} + +bool CGUITTFont::load(const io::path& filename, const u32 size, const bool antialias, const bool transparency) +{ + // Some sanity checks. + if (Environment == 0 || Driver == 0) return false; + if (size == 0) return false; + if (filename.size() == 0) return false; + + io::IFileSystem* filesystem = Environment->getFileSystem(); + irr::ILogger* logger = (Device != 0 ? Device->getLogger() : 0); + this->size = size; + this->filename = filename; + + // Update the font loading flags when the font is first loaded. + this->use_monochrome = !antialias; + this->use_transparency = transparency; + update_load_flags(); + + // Log. + if (logger) + logger->log(L"CGUITTFont", core::stringw(core::stringw(L"Creating new font: ") + core::ustring(filename).toWCHAR_s() + L" " + core::stringc(size) + L"pt " + (antialias ? L"+antialias " : L"-antialias ") + (transparency ? L"+transparency" : L"-transparency")).c_str(), irr::ELL_INFORMATION); + + // Grab the face. + SGUITTFace* face = 0; + core::map::Node* node = c_faces.find(filename); + if (node == 0) + { + face = new SGUITTFace(); + c_faces.set(filename, face); + + if (filesystem) + { + // Read in the file data. + io::IReadFile* file = filesystem->createAndOpenFile(filename); + if (file == 0) + { + if (logger) logger->log(L"CGUITTFont", L"Failed to open the file.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + face->face_buffer = new FT_Byte[file->getSize()]; + file->read(face->face_buffer, file->getSize()); + face->face_buffer_size = file->getSize(); + file->drop(); + + // Create the face. + if (FT_New_Memory_Face(c_library, face->face_buffer, face->face_buffer_size, 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Memory_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + else + { + core::ustring converter(filename); + if (FT_New_Face(c_library, reinterpret_cast(converter.toUTF8_s().c_str()), 0, &face->face)) + { + if (logger) logger->log(L"CGUITTFont", L"FT_New_Face failed.", irr::ELL_INFORMATION); + + c_faces.remove(filename); + delete face; + face = 0; + return false; + } + } + } + else + { + // Using another instance of this face. + face = node->getValue(); + face->grab(); + } + + // Store our face. + tt_face = face->face; + + // Store font metrics. + FT_Set_Pixel_Sizes(tt_face, size, 0); + font_metrics = tt_face->size->metrics; + + // Allocate our glyphs. + Glyphs.clear(); + Glyphs.reallocate(tt_face->num_glyphs); + Glyphs.set_used(tt_face->num_glyphs); + for (FT_Long i = 0; i < tt_face->num_glyphs; ++i) + { + Glyphs[i].isLoaded = false; + Glyphs[i].glyph_page = 0; + Glyphs[i].source_rect = core::recti(); + Glyphs[i].offset = core::vector2di(); + Glyphs[i].advance = FT_Vector(); + Glyphs[i].surface = 0; + Glyphs[i].parent = this; + } + + // Cache the first 127 ascii characters. + u32 old_size = batch_load_size; + batch_load_size = 127; + getGlyphIndexByChar((uchar32_t)0); + batch_load_size = old_size; + + return true; +} + +CGUITTFont::~CGUITTFont() +{ + // Delete the glyphs and glyph pages. + reset_images(); + CGUITTAssistDelete::Delete(Glyphs); + //Glyphs.clear(); + + // We aren't using this face anymore. + core::map::Node* n = c_faces.find(filename); + if (n) + { + SGUITTFace* f = n->getValue(); + + // Drop our face. If this was the last face, the destructor will clean up. + if (f->drop()) + c_faces.remove(filename); + + // If there are no more faces referenced by FreeType, clean up. + if (c_faces.size() == 0) + { + FT_Done_FreeType(c_library); + c_libraryLoaded = false; + } + } + + // Drop our driver now. + if (Driver) + Driver->drop(); +} + +void CGUITTFont::reset_images() +{ + // Delete the glyphs. + for (u32 i = 0; i != Glyphs.size(); ++i) + Glyphs[i].unload(); + + // Unload the glyph pages from video memory. + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + delete Glyph_Pages[i]; + Glyph_Pages.clear(); + + // Always update the internal FreeType loading flags after resetting. + update_load_flags(); +} + +void CGUITTFont::update_glyph_pages() const +{ + for (u32 i = 0; i != Glyph_Pages.size(); ++i) + { + if (Glyph_Pages[i]->dirty) + Glyph_Pages[i]->updateTexture(); + } +} + +CGUITTGlyphPage* CGUITTFont::getLastGlyphPage() const +{ + CGUITTGlyphPage* page = 0; + if (Glyph_Pages.empty()) + return 0; + else + { + page = Glyph_Pages[getLastGlyphPageIndex()]; + if (page->available_slots == 0) + page = 0; + } + return page; +} + +CGUITTGlyphPage* CGUITTFont::createGlyphPage(const u8& pixel_mode) +{ + CGUITTGlyphPage* page = 0; + + // Name of our page. + io::path name("TTFontGlyphPage_"); + name += tt_face->family_name; + name += "."; + name += tt_face->style_name; + name += "."; + name += size; + name += "_"; + name += Glyph_Pages.size(); // The newly created page will be at the end of the collection. + + // Create the new page. + page = new CGUITTGlyphPage(Driver, name); + + // Determine our maximum texture size. + // If we keep getting 0, set it to 1024x1024, as that number is pretty safe. + core::dimension2du max_texture_size = max_page_texture_size; + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = Driver->getMaxTextureSize(); + if (max_texture_size.Width == 0 || max_texture_size.Height == 0) + max_texture_size = core::dimension2du(1024, 1024); + + // We want to try to put at least 144 glyphs on a single texture. + core::dimension2du page_texture_size; + if (size <= 21) page_texture_size = core::dimension2du(256, 256); + else if (size <= 42) page_texture_size = core::dimension2du(512, 512); + else if (size <= 84) page_texture_size = core::dimension2du(1024, 1024); + else if (size <= 168) page_texture_size = core::dimension2du(2048, 2048); + else page_texture_size = core::dimension2du(4096, 4096); + + if (page_texture_size.Width > max_texture_size.Width || page_texture_size.Height > max_texture_size.Height) + page_texture_size = max_texture_size; + + if (!page->createPageTexture(pixel_mode, page_texture_size)) + // TODO: add error message? + return 0; + + if (page) + { + // Determine the number of glyph slots on the page and add it to the list of pages. + page->available_slots = (page_texture_size.Width / size) * (page_texture_size.Height / size); + Glyph_Pages.push_back(page); + } + return page; +} + +void CGUITTFont::setTransparency(const bool flag) +{ + use_transparency = flag; + reset_images(); +} + +void CGUITTFont::setMonochrome(const bool flag) +{ + use_monochrome = flag; + reset_images(); +} + +void CGUITTFont::setFontHinting(const bool enable, const bool enable_auto_hinting) +{ + use_hinting = enable; + use_auto_hinting = enable_auto_hinting; + reset_images(); +} + +void CGUITTFont::draw(const core::stringw& text, const core::rect& position, video::SColor color, bool hcenter, bool vcenter, const core::rect* clip) +{ + if (!Driver) + return; + + // Clear the glyph pages of their render information. + for (u32 i = 0; i < Glyph_Pages.size(); ++i) + { + Glyph_Pages[i]->render_positions.clear(); + Glyph_Pages[i]->render_source_rects.clear(); + } + + // Set up some variables. + core::dimension2d textDimension; + core::position2d offset = position.UpperLeftCorner; + + // Determine offset positions. + if (hcenter || vcenter) + { + textDimension = getDimension(text.c_str()); + + if (hcenter) + offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X; + + if (vcenter) + offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y; + } + + // Convert to a unicode string. + core::ustring utext(text); + + // Set up our render map. + core::map Render_Map; + + // Start parsing characters. + u32 n; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter(utext); + while (!iter.atEnd()) + { + uchar32_t currentChar = *iter; + n = getGlyphIndexByChar(currentChar); + bool visible = (Invisible.findFirst(currentChar) == -1); + if (n > 0 && visible) + { + bool lineBreak=false; + if (currentChar == L'\r') // Mac or Windows breaks + { + lineBreak = true; + if (*(iter + 1) == (uchar32_t)'\n') // Windows line breaks. + currentChar = *(++iter); + } + else if (currentChar == (uchar32_t)'\n') // Unix breaks + { + lineBreak = true; + } + + if (lineBreak) + { + previousChar = 0; + offset.Y += font_metrics.ascender / 64; + offset.X = position.UpperLeftCorner.X; + + if (hcenter) + offset.X += (position.getWidth() - textDimension.Width) >> 1; + ++iter; + continue; + } + + // Calculate the glyph offset. + s32 offx = Glyphs[n-1].offset.X; + s32 offy = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y; + + // Apply kerning. + core::vector2di k = getKerning(currentChar, previousChar); + offset.X += k.X; + offset.Y += k.Y; + + // Determine rendering information. + SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* const page = Glyph_Pages[glyph.glyph_page]; + page->render_positions.push_back(core::position2di(offset.X + offx, offset.Y + offy)); + page->render_source_rects.push_back(glyph.source_rect); + Render_Map.set(glyph.glyph_page, page); + } + offset.X += getWidthFromCharacter(currentChar); + + previousChar = currentChar; + ++iter; + } + + // Draw now. + update_glyph_pages(); + core::map::Iterator j = Render_Map.getIterator(); + while (!j.atEnd()) + { + core::map::Node* n = j.getNode(); + j++; + if (n == 0) continue; + + CGUITTGlyphPage* page = n->getValue(); + + if (!use_transparency) color.color |= 0xff000000; + Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true); + } +} + +core::dimension2d CGUITTFont::getCharDimension(const wchar_t ch) const +{ + return core::dimension2d(getWidthFromCharacter(ch), getHeightFromCharacter(ch)); +} + +core::dimension2d CGUITTFont::getDimension(const wchar_t* text) const +{ + return getDimension(core::ustring(text)); +} + +core::dimension2d CGUITTFont::getDimension(const core::ustring& text) const +{ + // Get the maximum font height. Unfortunately, we have to do this hack as + // Irrlicht will draw things wrong. In FreeType, the font size is the + // maximum size for a single glyph, but that glyph may hang "under" the + // draw line, increasing the total font height to beyond the set size. + // Irrlicht does not understand this concept when drawing fonts. Also, I + // add +1 to give it a 1 pixel blank border. This makes things like + // tooltips look nicer. + s32 test1 = getHeightFromCharacter((uchar32_t)'g') + 1; + s32 test2 = getHeightFromCharacter((uchar32_t)'j') + 1; + s32 test3 = getHeightFromCharacter((uchar32_t)'_') + 1; + s32 max_font_height = core::max_(test1, core::max_(test2, test3)); + + core::dimension2d text_dimension(0, max_font_height); + core::dimension2d line(0, max_font_height); + + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + for (; !iter.atEnd(); ++iter) + { + uchar32_t p = *iter; + bool lineBreak = false; + if (p == '\r') // Mac or Windows line breaks. + { + lineBreak = true; + if (*(iter + 1) == '\n') + { + ++iter; + p = *iter; + } + } + else if (p == '\n') // Unix line breaks. + { + lineBreak = true; + } + + // Kerning. + core::vector2di k = getKerning(p, previousChar); + line.Width += k.X; + previousChar = p; + + // Check for linebreak. + if (lineBreak) + { + previousChar = 0; + text_dimension.Height += line.Height; + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + line.Width = 0; + line.Height = max_font_height; + continue; + } + line.Width += getWidthFromCharacter(p); + } + if (text_dimension.Width < line.Width) + text_dimension.Width = line.Width; + + return text_dimension; +} + +inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const +{ + return getWidthFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getWidthFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + int w = Glyphs[n-1].advance.x / 64; + return w; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const +{ + return getHeightFromCharacter((uchar32_t)c); +} + +inline u32 CGUITTFont::getHeightFromCharacter(uchar32_t c) const +{ + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + //FT_Set_Pixel_Sizes(tt_face, 0, size); + + u32 n = getGlyphIndexByChar(c); + if (n > 0) + { + // Grab the true height of the character, taking into account underhanging glyphs. + s32 height = (font_metrics.ascender / 64) - Glyphs[n-1].offset.Y + Glyphs[n-1].source_rect.getHeight(); + return height; + } + if (c >= 0x2000) + return (font_metrics.ascender / 64); + else return (font_metrics.ascender / 64) / 2; +} + +u32 CGUITTFont::getGlyphIndexByChar(wchar_t c) const +{ + return getGlyphIndexByChar((uchar32_t)c); +} + +u32 CGUITTFont::getGlyphIndexByChar(uchar32_t c) const +{ + // Get the glyph. + u32 glyph = FT_Get_Char_Index(tt_face, c); + + // Check for a valid glyph. If it is invalid, attempt to use the replacement character. + if (glyph == 0) + glyph = FT_Get_Char_Index(tt_face, core::unicode::UTF_REPLACEMENT_CHARACTER); + + // If our glyph is already loaded, don't bother doing any batch loading code. + if (glyph != 0 && Glyphs[glyph - 1].isLoaded) + return glyph; + + // Determine our batch loading positions. + u32 half_size = (batch_load_size / 2); + u32 start_pos = 0; + if (c > half_size) start_pos = c - half_size; + u32 end_pos = start_pos + batch_load_size; + + // Load all our characters. + do + { + // Get the character we are going to load. + u32 char_index = FT_Get_Char_Index(tt_face, start_pos); + + // If the glyph hasn't been loaded yet, do it now. + if (char_index) + { + SGUITTGlyph& glyph = Glyphs[char_index - 1]; + if (!glyph.isLoaded) + { + glyph.preload(char_index, tt_face, Driver, size, load_flags); + Glyph_Pages[glyph.glyph_page]->pushGlyphToBePaged(&glyph); + } + } + } + while (++start_pos < end_pos); + + // Return our original character. + return glyph; +} + +s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const +{ + return getCharacterFromPos(core::ustring(text), pixel_x); +} + +s32 CGUITTFont::getCharacterFromPos(const core::ustring& text, s32 pixel_x) const +{ + s32 x = 0; + //s32 idx = 0; + + u32 character = 0; + uchar32_t previousChar = 0; + core::ustring::const_iterator iter = text.begin(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + x += getWidthFromCharacter(c); + + // Kerning. + core::vector2di k = getKerning(c, previousChar); + x += k.X; + + if (x >= pixel_x) + return character; + + previousChar = c; + ++iter; + ++character; + } + + return -1; +} + +void CGUITTFont::setKerningWidth(s32 kerning) +{ + GlobalKerningWidth = kerning; +} + +void CGUITTFont::setKerningHeight(s32 kerning) +{ + GlobalKerningHeight = kerning; +} + +s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const +{ + if (tt_face == 0) + return GlobalKerningWidth; + if (thisLetter == 0 || previousLetter == 0) + return 0; + + return getKerningWidth((uchar32_t)*thisLetter, (uchar32_t)*previousLetter); +} + +s32 CGUITTFont::getKerningWidth(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + // Return only the kerning width. + return getKerning(thisLetter, previousLetter).X; +} + +s32 CGUITTFont::getKerningHeight() const +{ + // FreeType 2 currently doesn't return any height kerning information. + return GlobalKerningHeight; +} + +core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const +{ + return getKerning((uchar32_t)thisLetter, (uchar32_t)previousLetter); +} + +core::vector2di CGUITTFont::getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const +{ + if (tt_face == 0 || thisLetter == 0 || previousLetter == 0) + return core::vector2di(); + + // Set the size of the face. + // This is because we cache faces and the face may have been set to a different size. + FT_Set_Pixel_Sizes(tt_face, 0, size); + + core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight); + + // If we don't have kerning, no point in continuing. + if (!FT_HAS_KERNING(tt_face)) + return ret; + + // Get the kerning information. + FT_Vector v; + FT_Get_Kerning(tt_face, getGlyphIndexByChar(previousLetter), getGlyphIndexByChar(thisLetter), FT_KERNING_DEFAULT, &v); + + // If we have a scalable font, the return value will be in font points. + if (FT_IS_SCALABLE(tt_face)) + { + // Font points, so divide by 64. + ret.X += (v.x / 64); + ret.Y += (v.y / 64); + } + else + { + // Pixel units. + ret.X += v.x; + ret.Y += v.y; + } + return ret; +} + +void CGUITTFont::setInvisibleCharacters(const wchar_t *s) +{ + core::ustring us(s); + Invisible = us; +} + +void CGUITTFont::setInvisibleCharacters(const core::ustring& s) +{ + Invisible = s; +} + +video::IImage* CGUITTFont::createTextureFromChar(const uchar32_t& ch) +{ + u32 n = getGlyphIndexByChar(ch); + const SGUITTGlyph& glyph = Glyphs[n-1]; + CGUITTGlyphPage* page = Glyph_Pages[glyph.glyph_page]; + + if (page->dirty) + page->updateTexture(); + + video::ITexture* tex = page->texture; + + // Acquire a read-only lock of the corresponding page texture. + void* ptr = tex->lock(true); + + video::ECOLOR_FORMAT format = tex->getColorFormat(); + core::dimension2du tex_size = tex->getOriginalSize(); + video::IImage* pageholder = Driver->createImageFromData(format, tex_size, ptr, true, false); + + // Copy the image data out of the page texture. + core::dimension2du glyph_size(glyph.source_rect.getSize()); + video::IImage* image = Driver->createImage(format, glyph_size); + pageholder->copyTo(image, core::position2di(0, 0), glyph.source_rect); + + tex->unlock(); + return image; +} + +video::ITexture* CGUITTFont::getPageTextureByIndex(const u32& page_index) const +{ + if (page_index < Glyph_Pages.size()) + return Glyph_Pages[page_index]->texture; + else + return 0; +} + +void CGUITTFont::createSharedPlane() +{ + /* + 2___3 + | /| + | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + 0---1 + */ + + using namespace core; + using namespace video; + using namespace scene; + S3DVertex vertices[4]; + u16 indices[6] = {0,2,3,3,1,0}; + vertices[0] = S3DVertex(vector3df(0,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,1)); + vertices[1] = S3DVertex(vector3df(1,-1,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,1)); + vertices[2] = S3DVertex(vector3df(0, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(0,0)); + vertices[3] = S3DVertex(vector3df(1, 0,0), vector3df(0,0,-1), SColor(255,255,255,255), vector2df(1,0)); + + SMeshBuffer* buf = new SMeshBuffer(); + buf->append(vertices, 4, indices, 6); + + shared_plane_.addMeshBuffer( buf ); + + shared_plane_ptr_ = &shared_plane_; + buf->drop(); //the addMeshBuffer method will grab it, so we can drop this ptr. +} + +core::dimension2d CGUITTFont::getDimensionUntilEndOfLine(const wchar_t* p) const +{ + core::stringw s; + for (const wchar_t* temp = p; temp && *temp != '\0' && *temp != L'\r' && *temp != L'\n'; ++temp ) + s.append(*temp); + + return getDimension(s.c_str()); +} + +core::array CGUITTFont::addTextSceneNode(const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent, const video::SColor& color, bool center) +{ + using namespace core; + using namespace video; + using namespace scene; + + array container; + + if (!Driver || !smgr) return container; + if (!parent) + parent = smgr->addEmptySceneNode(smgr->getRootSceneNode(), -1); + // if you don't specify parent, then we add a empty node attached to the root node + // this is generally undesirable. + + if (!shared_plane_ptr_) //this points to a static mesh that contains the plane + createSharedPlane(); //if it's not initialized, we create one. + + dimension2d text_size(getDimension(text)); //convert from unsigned to signed. + vector3df start_point(0, 0, 0), offset; + + /** NOTICE: + Because we are considering adding texts into 3D world, all Y axis vectors are inverted. + **/ + + // There's currently no "vertical center" concept when you apply text scene node to the 3D world. + if (center) + { + offset.X = start_point.X = -text_size.Width / 2.f; + offset.Y = start_point.Y = +text_size.Height/ 2.f; + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text).Width) >> 1; + } + + // the default font material + SMaterial mat; + mat.setFlag(video::EMF_LIGHTING, true); + mat.setFlag(video::EMF_ZWRITE_ENABLE, false); + mat.setFlag(video::EMF_NORMALIZE_NORMALS, true); + mat.ColorMaterial = video::ECM_NONE; + mat.MaterialType = use_transparency ? video::EMT_TRANSPARENT_ALPHA_CHANNEL : video::EMT_SOLID; + mat.MaterialTypeParam = 0.01f; + mat.DiffuseColor = color; + + wchar_t current_char = 0, previous_char = 0; + u32 n = 0; + + array glyph_indices; + + while (*text) + { + current_char = *text; + bool line_break=false; + if (current_char == L'\r') // Mac or Windows breaks + { + line_break = true; + if (*(text + 1) == L'\n') // Windows line breaks. + current_char = *(++text); + } + else if (current_char == L'\n') // Unix breaks + { + line_break = true; + } + + if (line_break) + { + previous_char = 0; + offset.Y -= tt_face->size->metrics.ascender / 64; + offset.X = start_point.X; + if (center) + offset.X += (text_size.Width - getDimensionUntilEndOfLine(text+1).Width) >> 1; + ++text; + } + else + { + n = getGlyphIndexByChar(current_char); + if (n > 0) + { + glyph_indices.push_back( n ); + + // Store glyph size and offset informations. + SGUITTGlyph const& glyph = Glyphs[n-1]; + u32 texw = glyph.source_rect.getWidth(); + u32 texh = glyph.source_rect.getHeight(); + s32 offx = glyph.offset.X; + s32 offy = (font_metrics.ascender / 64) - glyph.offset.Y; + + // Apply kerning. + vector2di k = getKerning(current_char, previous_char); + offset.X += k.X; + offset.Y += k.Y; + + vector3df current_pos(offset.X + offx, offset.Y - offy, 0); + dimension2d letter_size = dimension2d(texw, texh); + + // Now we copy planes corresponding to the letter size. + IMeshManipulator* mani = smgr->getMeshManipulator(); + IMesh* meshcopy = mani->createMeshCopy(shared_plane_ptr_); + mani->scaleMesh(meshcopy, vector3df((f32)letter_size.Width, (f32)letter_size.Height, 1)); + + ISceneNode* current_node = smgr->addMeshSceneNode(meshcopy, parent, -1, current_pos); + meshcopy->drop(); + + current_node->getMaterial(0) = mat; + current_node->setAutomaticCulling(EAC_OFF); + current_node->setIsDebugObject(true); //so the picking won't have any effect on individual letter + //current_node->setDebugDataVisible(EDS_BBOX); //de-comment this when debugging + + container.push_back(current_node); + } + offset.X += getWidthFromCharacter(current_char); + previous_char = current_char; + ++text; + } + } + + update_glyph_pages(); + //only after we update the textures can we use the glyph page textures. + + for (u32 i = 0; i < glyph_indices.size(); ++i) + { + u32 n = glyph_indices[i]; + SGUITTGlyph const& glyph = Glyphs[n-1]; + ITexture* current_tex = Glyph_Pages[glyph.glyph_page]->texture; + f32 page_texture_size = (f32)current_tex->getSize().Width; + //Now we calculate the UV position according to the texture size and the source rect. + // + // 2___3 + // | /| + // | / | <-- plane mesh is like this, point 2 is (0,0), point 0 is (0, -1) + // |/ | <-- the texture coords of point 2 is (0,0, point 0 is (0, 1) + // 0---1 + // + f32 u1 = glyph.source_rect.UpperLeftCorner.X / page_texture_size; + f32 u2 = u1 + (glyph.source_rect.getWidth() / page_texture_size); + f32 v1 = glyph.source_rect.UpperLeftCorner.Y / page_texture_size; + f32 v2 = v1 + (glyph.source_rect.getHeight() / page_texture_size); + + //we can be quite sure that this is IMeshSceneNode, because we just added them in the above loop. + IMeshSceneNode* node = static_cast(container[i]); + + S3DVertex* pv = static_cast(node->getMesh()->getMeshBuffer(0)->getVertices()); + //pv[0].TCoords.Y = pv[1].TCoords.Y = (letter_size.Height - 1) / static_cast(letter_size.Height); + //pv[1].TCoords.X = pv[3].TCoords.X = (letter_size.Width - 1) / static_cast(letter_size.Width); + pv[0].TCoords = vector2df(u1, v2); + pv[1].TCoords = vector2df(u2, v2); + pv[2].TCoords = vector2df(u1, v1); + pv[3].TCoords = vector2df(u2, v1); + + container[i]->getMaterial(0).setTexture(0, current_tex); + } + + return container; +} + +} // end namespace gui +} // end namespace irr diff --git a/src/cguittfont/CGUITTFont.h b/src/cguittfont/CGUITTFont.h new file mode 100644 index 0000000..42ef1c4 --- /dev/null +++ b/src/cguittfont/CGUITTFont.h @@ -0,0 +1,377 @@ +/* + CGUITTFont FreeType class for Irrlicht + Copyright (c) 2009-2010 John Norman + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __C_GUI_TTFONT_H_INCLUDED__ +#define __C_GUI_TTFONT_H_INCLUDED__ + +#include +#include +#include FT_FREETYPE_H + +namespace irr +{ +namespace gui +{ + struct SGUITTFace; + class CGUITTFont; + + //! Class to assist in deleting glyphs. + class CGUITTAssistDelete + { + public: + template + static void Delete(core::array& a) + { + TAlloc allocator; + allocator.deallocate(a.pointer()); + } + }; + + //! Structure representing a single TrueType glyph. + struct SGUITTGlyph + { + //! Constructor. + SGUITTGlyph() : isLoaded(false), glyph_page(0), surface(0), parent(0) {} + + //! Destructor. + ~SGUITTGlyph() { unload(); } + + //! Preload the glyph. + //! The preload process occurs when the program tries to cache the glyph from FT_Library. + //! However, it simply defines the SGUITTGlyph's properties and will only create the page + //! textures if necessary. The actual creation of the textures should only occur right + //! before the batch draw call. + void preload(u32 char_index, FT_Face face, video::IVideoDriver* driver, u32 font_size, const FT_Int32 loadFlags); + + //! Unloads the glyph. + void unload(); + + //! Creates the IImage object from the FT_Bitmap. + video::IImage* createGlyphImage(const FT_Bitmap& bits, video::IVideoDriver* driver) const; + + //! If true, the glyph has been loaded. + bool isLoaded; + + //! The page the glyph is on. + u32 glyph_page; + + //! The source rectangle for the glyph. + core::recti source_rect; + + //! The offset of glyph when drawn. + core::vector2di offset; + + //! Glyph advance information. + FT_Vector advance; + + //! This is just the temporary image holder. After this glyph is paged, + //! it will be dropped. + mutable video::IImage* surface; + + //! The pointer pointing to the parent (CGUITTFont) + CGUITTFont* parent; + }; + + //! Holds a sheet of glyphs. + class CGUITTGlyphPage + { + public: + CGUITTGlyphPage(video::IVideoDriver* Driver, const io::path& texture_name) :texture(0), available_slots(0), used_slots(0), dirty(false), driver(Driver), name(texture_name) {} + ~CGUITTGlyphPage() + { + if (texture) + { + if (driver) + driver->removeTexture(texture); + else texture->drop(); + } + } + + //! Create the actual page texture, + bool createPageTexture(const u8& pixel_mode, const core::dimension2du& texture_size) + { + if( texture ) + return false; + + bool flgmip = driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS); + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); + + // Set the texture color format. + switch (pixel_mode) + { + case FT_PIXEL_MODE_MONO: + texture = driver->addTexture(texture_size, name, video::ECF_A1R5G5B5); + break; + case FT_PIXEL_MODE_GRAY: + default: + texture = driver->addTexture(texture_size, name, video::ECF_A8R8G8B8); + break; + } + + // Restore our texture creation flags. + driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip); + return texture ? true : false; + } + + //! Add the glyph to a list of glyphs to be paged. + //! This collection will be cleared after updateTexture is called. + void pushGlyphToBePaged(const SGUITTGlyph* glyph) + { + glyph_to_be_paged.push_back(glyph); + } + + //! Updates the texture atlas with new glyphs. + void updateTexture() + { + if (!dirty) return; + + void* ptr = texture->lock(); + video::ECOLOR_FORMAT format = texture->getColorFormat(); + core::dimension2du size = texture->getOriginalSize(); + video::IImage* pageholder = driver->createImageFromData(format, size, ptr, true, false); + + for (u32 i = 0; i < glyph_to_be_paged.size(); ++i) + { + const SGUITTGlyph* glyph = glyph_to_be_paged[i]; + if (glyph && glyph->isLoaded) + { + if (glyph->surface) + { + glyph->surface->copyTo(pageholder, glyph->source_rect.UpperLeftCorner); + glyph->surface->drop(); + glyph->surface = 0; + } + else + { + ; // TODO: add error message? + //currently, if we failed to create the image, just ignore this operation. + } + } + } + + pageholder->drop(); + texture->unlock(); + glyph_to_be_paged.clear(); + dirty = false; + } + + video::ITexture* texture; + u32 available_slots; + u32 used_slots; + bool dirty; + + core::array render_positions; + core::array render_source_rects; + + private: + core::array glyph_to_be_paged; + video::IVideoDriver* driver; + io::path name; + }; + + //! Class representing a TrueType font. + class CGUITTFont : public IGUIFont + { + public: + //! Creates a new TrueType font and returns a pointer to it. The pointer must be drop()'ed when finished. + //! \param env The IGUIEnvironment the font loads out of. + //! \param filename The filename of the font. + //! \param size The size of the font glyphs in pixels. Since this is the size of the individual glyphs, the true height of the font may change depending on the characters used. + //! \param antialias set the use_monochrome (opposite to antialias) flag + //! \param transparency set the use_transparency flag + //! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load. + static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true); + + //! Destructor + virtual ~CGUITTFont(); + + //! Sets the amount of glyphs to batch load. + virtual void setBatchLoadSize(u32 batch_size) { batch_load_size = batch_size; } + + //! Sets the maximum texture size for a page of glyphs. + virtual void setMaxPageTextureSize(const core::dimension2du& texture_size) { max_page_texture_size = texture_size; } + + //! Get the font size. + virtual u32 getFontSize() const { return size; } + + //! Check the font's transparency. + virtual bool isTransparent() const { return use_transparency; } + + //! Check if the font auto-hinting is enabled. + //! Auto-hinting is FreeType's built-in font hinting engine. + virtual bool useAutoHinting() const { return use_auto_hinting; } + + //! Check if the font hinting is enabled. + virtual bool useHinting() const { return use_hinting; } + + //! Check if the font is being loaded as a monochrome font. + //! The font can either be a 256 color grayscale font, or a 2 color monochrome font. + virtual bool useMonochrome() const { return use_monochrome; } + + //! Tells the font to allow transparency when rendering. + //! Default: true. + //! \param flag If true, the font draws using transparency. + virtual void setTransparency(const bool flag); + + //! Tells the font to use monochrome rendering. + //! Default: false. + //! \param flag If true, the font draws using a monochrome image. If false, the font uses a grayscale image. + virtual void setMonochrome(const bool flag); + + //! Enables or disables font hinting. + //! Default: Hinting and auto-hinting true. + //! \param enable If false, font hinting is turned off. If true, font hinting is turned on. + //! \param enable_auto_hinting If true, FreeType uses its own auto-hinting algorithm. If false, it tries to use the algorithm specified by the font. + virtual void setFontHinting(const bool enable, const bool enable_auto_hinting = true); + + //! Draws some text and clips it to the specified rectangle if wanted. + virtual void draw(const core::stringw& text, const core::rect& position, + video::SColor color, bool hcenter=false, bool vcenter=false, + const core::rect* clip=0); + + //! Returns the dimension of a character produced by this font. + virtual core::dimension2d getCharDimension(const wchar_t ch) const; + + //! Returns the dimension of a text string. + virtual core::dimension2d getDimension(const wchar_t* text) const; + virtual core::dimension2d getDimension(const core::ustring& text) const; + + //! Calculates the index of the character in the text which is on a specific position. + virtual s32 getCharacterFromPos(const wchar_t* text, s32 pixel_x) const; + virtual s32 getCharacterFromPos(const core::ustring& text, s32 pixel_x) const; + + //! Sets global kerning width for the font. + virtual void setKerningWidth(s32 kerning); + + //! Sets global kerning height for the font. + virtual void setKerningHeight(s32 kerning); + + //! Gets kerning values (distance between letters) for the font. If no parameters are provided, + virtual s32 getKerningWidth(const wchar_t* thisLetter=0, const wchar_t* previousLetter=0) const; + virtual s32 getKerningWidth(const uchar32_t thisLetter=0, const uchar32_t previousLetter=0) const; + + //! Returns the distance between letters + virtual s32 getKerningHeight() const; + + //! Define which characters should not be drawn by the font. + virtual void setInvisibleCharacters(const wchar_t *s); + virtual void setInvisibleCharacters(const core::ustring& s); + + //! Get the last glyph page if there's still available slots. + //! If not, it will return zero. + CGUITTGlyphPage* getLastGlyphPage() const; + + //! Create a new glyph page texture. + //! \param pixel_mode the pixel mode defined by FT_Pixel_Mode + //should be better typed. fix later. + CGUITTGlyphPage* createGlyphPage(const u8& pixel_mode); + + //! Get the last glyph page's index. + u32 getLastGlyphPageIndex() const { return Glyph_Pages.size() - 1; } + + //! Create corresponding character's software image copy from the font, + //! so you can use this data just like any ordinary video::IImage. + //! \param ch The character you need + virtual video::IImage* createTextureFromChar(const uchar32_t& ch); + + //! This function is for debugging mostly. If the page doesn't exist it returns zero. + //! \param page_index Simply return the texture handle of a given page index. + virtual video::ITexture* getPageTextureByIndex(const u32& page_index) const; + + //! Add a list of scene nodes generated by putting font textures on the 3D planes. + virtual core::array addTextSceneNode + (const wchar_t* text, scene::ISceneManager* smgr, scene::ISceneNode* parent = 0, + const video::SColor& color = video::SColor(255, 0, 0, 0), bool center = false ); + + protected: + bool use_monochrome; + bool use_transparency; + bool use_hinting; + bool use_auto_hinting; + u32 size; + u32 batch_load_size; + core::dimension2du max_page_texture_size; + + private: + // Manages the FreeType library. + static FT_Library c_library; + static core::map c_faces; + static bool c_libraryLoaded; + static scene::IMesh* shared_plane_ptr_; + static scene::SMesh shared_plane_; + + CGUITTFont(IGUIEnvironment *env); + bool load(const io::path& filename, const u32 size, const bool antialias, const bool transparency); + void reset_images(); + void update_glyph_pages() const; + void update_load_flags() + { + // Set up our loading flags. + load_flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + if (!useHinting()) load_flags |= FT_LOAD_NO_HINTING; + if (!useAutoHinting()) load_flags |= FT_LOAD_NO_AUTOHINT; + if (useMonochrome()) load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO | FT_RENDER_MODE_MONO; + else load_flags |= FT_LOAD_TARGET_NORMAL; + } + u32 getWidthFromCharacter(wchar_t c) const; + u32 getWidthFromCharacter(uchar32_t c) const; + u32 getHeightFromCharacter(wchar_t c) const; + u32 getHeightFromCharacter(uchar32_t c) const; + u32 getGlyphIndexByChar(wchar_t c) const; + u32 getGlyphIndexByChar(uchar32_t c) const; + core::vector2di getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const; + core::vector2di getKerning(const uchar32_t thisLetter, const uchar32_t previousLetter) const; + core::dimension2d getDimensionUntilEndOfLine(const wchar_t* p) const; + + void createSharedPlane(); + + irr::IrrlichtDevice* Device; + gui::IGUIEnvironment* Environment; + video::IVideoDriver* Driver; + io::path filename; + FT_Face tt_face; + FT_Size_Metrics font_metrics; + FT_Int32 load_flags; + + mutable core::array Glyph_Pages; + mutable core::array Glyphs; + + s32 GlobalKerningWidth; + s32 GlobalKerningHeight; + core::ustring Invisible; + }; + +} // end namespace gui +} // end namespace irr + +#endif // __C_GUI_TTFONT_H_INCLUDED__ diff --git a/src/cguittfont/CMakeLists.txt b/src/cguittfont/CMakeLists.txt new file mode 100644 index 0000000..1c92231 --- /dev/null +++ b/src/cguittfont/CMakeLists.txt @@ -0,0 +1,22 @@ +# Alas, CGUITTFont.{cpp,h} don't include irrUString.h even though they use +# core::ustring. +# We don't want to (and shouldn't have to) modify our copies of +# CGUITTFont.{cpp,h} because that would complicate updating them later. + +#if(CMAKE_COMPILER_IS_GNUCXX) +# add_definitions(-include "${CMAKE_CURRENT_SOURCE_DIR}/irrUString.h") +# add_definitions(-include "stddef.h") +#endif(CMAKE_COMPILER_IS_GNUCXX) + +include_directories( + ${IRRLICHT_INCLUDE_DIR} + ${FREETYPE_INCLUDE_DIRS} +) + +add_library(cguittfont CGUITTFont.cpp) + +target_link_libraries( + cguittfont + ${IRRLICHT_LIBRARY} + ${FREETYPE_LIBRARIES} +) diff --git a/src/cguittfont/irrUString.h b/src/cguittfont/irrUString.h new file mode 100644 index 0000000..518a8c5 --- /dev/null +++ b/src/cguittfont/irrUString.h @@ -0,0 +1,3399 @@ +/* + Basic Unicode string class for Irrlicht. + Copyright (c) 2009-2011 John Norman + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + + The original version of this class can be located at: + http://irrlicht.suckerfreegames.com/ + + John Norman + john@suckerfreegames.com +*/ + +#ifndef __IRR_USTRING_H_INCLUDED__ +#define __IRR_USTRING_H_INCLUDED__ + +#if (__cplusplus > 199711L) || (_MSC_VER >= 1600) || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define USTRING_CPP0X +# if defined(__GXX_EXPERIMENTAL_CXX0X__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))) +# define USTRING_CPP0X_NEWLITERALS +# endif +#endif + + +#include +#include +#include +#include + +#ifdef USTRING_CPP0X +# include +#endif + +#ifndef USTRING_NO_STL +# include +# include +# include +#endif + +#include "irrTypes.h" +#include "irrAllocator.h" +#include "irrArray.h" +#include "irrMath.h" +#include "irrString.h" +#include "path.h" + +//! UTF-16 surrogate start values. +static const irr::u16 UTF16_HI_SURROGATE = 0xD800; +static const irr::u16 UTF16_LO_SURROGATE = 0xDC00; + +//! Is a UTF-16 code point a surrogate? +#define UTF16_IS_SURROGATE(c) (((c) & 0xF800) == 0xD800) +#define UTF16_IS_SURROGATE_HI(c) (((c) & 0xFC00) == 0xD800) +#define UTF16_IS_SURROGATE_LO(c) (((c) & 0xFC00) == 0xDC00) + + +namespace irr +{ + + // Define our character types. +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + typedef char32_t uchar32_t; + typedef char16_t uchar16_t; + typedef char uchar8_t; +#else + typedef u32 uchar32_t; + typedef u16 uchar16_t; + typedef u8 uchar8_t; +#endif + +namespace core +{ + +namespace unicode +{ + +//! The unicode replacement character. Used to replace invalid characters. +const irr::u16 UTF_REPLACEMENT_CHARACTER = 0xFFFD; + +//! Convert a UTF-16 surrogate pair into a UTF-32 character. +//! \param high The high value of the pair. +//! \param low The low value of the pair. +//! \return The UTF-32 character expressed by the surrogate pair. +inline uchar32_t toUTF32(uchar16_t high, uchar16_t low) +{ + // Convert the surrogate pair into a single UTF-32 character. + uchar32_t x = ((high & ((1 << 6) -1)) << 10) | (low & ((1 << 10) -1)); + uchar32_t wu = ((high >> 6) & ((1 << 5) - 1)) + 1; + return (wu << 16) | x; +} + +//! Swaps the endianness of a 16-bit value. +//! \return The new value. +inline uchar16_t swapEndian16(const uchar16_t& c) +{ + return ((c >> 8) & 0x00FF) | ((c << 8) & 0xFF00); +} + +//! Swaps the endianness of a 32-bit value. +//! \return The new value. +inline uchar32_t swapEndian32(const uchar32_t& c) +{ + return ((c >> 24) & 0x000000FF) | + ((c >> 8) & 0x0000FF00) | + ((c << 8) & 0x00FF0000) | + ((c << 24) & 0xFF000000); +} + +//! The Unicode byte order mark. +const u16 BOM = 0xFEFF; + +//! The size of the Unicode byte order mark in terms of the Unicode character size. +const u8 BOM_UTF8_LEN = 3; +const u8 BOM_UTF16_LEN = 1; +const u8 BOM_UTF32_LEN = 1; + +//! Unicode byte order marks for file operations. +const u8 BOM_ENCODE_UTF8[3] = { 0xEF, 0xBB, 0xBF }; +const u8 BOM_ENCODE_UTF16_BE[2] = { 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF16_LE[2] = { 0xFF, 0xFE }; +const u8 BOM_ENCODE_UTF32_BE[4] = { 0x00, 0x00, 0xFE, 0xFF }; +const u8 BOM_ENCODE_UTF32_LE[4] = { 0xFF, 0xFE, 0x00, 0x00 }; + +//! The size in bytes of the Unicode byte marks for file operations. +const u8 BOM_ENCODE_UTF8_LEN = 3; +const u8 BOM_ENCODE_UTF16_LEN = 2; +const u8 BOM_ENCODE_UTF32_LEN = 4; + +//! Unicode encoding type. +enum EUTF_ENCODE +{ + EUTFE_NONE = 0, + EUTFE_UTF8, + EUTFE_UTF16, + EUTFE_UTF16_LE, + EUTFE_UTF16_BE, + EUTFE_UTF32, + EUTFE_UTF32_LE, + EUTFE_UTF32_BE +}; + +//! Unicode endianness. +enum EUTF_ENDIAN +{ + EUTFEE_NATIVE = 0, + EUTFEE_LITTLE, + EUTFEE_BIG +}; + +//! Returns the specified unicode byte order mark in a byte array. +//! The byte order mark is the first few bytes in a text file that signifies its encoding. +/** \param mode The Unicode encoding method that we want to get the byte order mark for. + If EUTFE_UTF16 or EUTFE_UTF32 is passed, it uses the native system endianness. **/ +//! \return An array that contains a byte order mark. +inline core::array getUnicodeBOM(EUTF_ENCODE mode) +{ +#define COPY_ARRAY(source, size) \ + memcpy(ret.pointer(), source, size); \ + ret.set_used(size) + + core::array ret(4); + switch (mode) + { + case EUTFE_UTF8: + COPY_ARRAY(BOM_ENCODE_UTF8, BOM_ENCODE_UTF8_LEN); + break; + case EUTFE_UTF16: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + #endif + break; + case EUTFE_UTF16_BE: + COPY_ARRAY(BOM_ENCODE_UTF16_BE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF16_LE: + COPY_ARRAY(BOM_ENCODE_UTF16_LE, BOM_ENCODE_UTF16_LEN); + break; + case EUTFE_UTF32: + #ifdef __BIG_ENDIAN__ + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + #else + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + #endif + break; + case EUTFE_UTF32_BE: + COPY_ARRAY(BOM_ENCODE_UTF32_BE, BOM_ENCODE_UTF32_LEN); + break; + case EUTFE_UTF32_LE: + COPY_ARRAY(BOM_ENCODE_UTF32_LE, BOM_ENCODE_UTF32_LEN); + break; + } + return ret; + +#undef COPY_ARRAY +} + +//! Detects if the given data stream starts with a unicode BOM. +//! \param data The data stream to check. +//! \return The unicode BOM associated with the data stream, or EUTFE_NONE if none was found. +inline EUTF_ENCODE determineUnicodeBOM(const char* data) +{ + if (memcmp(data, BOM_ENCODE_UTF8, 3) == 0) return EUTFE_UTF8; + if (memcmp(data, BOM_ENCODE_UTF16_BE, 2) == 0) return EUTFE_UTF16_BE; + if (memcmp(data, BOM_ENCODE_UTF16_LE, 2) == 0) return EUTFE_UTF16_LE; + if (memcmp(data, BOM_ENCODE_UTF32_BE, 4) == 0) return EUTFE_UTF32_BE; + if (memcmp(data, BOM_ENCODE_UTF32_LE, 4) == 0) return EUTFE_UTF32_LE; + return EUTFE_NONE; +} + +} // end namespace unicode + + +//! UTF-16 string class. +template > +class ustring16 +{ +public: + + ///------------------/// + /// iterator classes /// + ///------------------/// + + //! Access an element in a unicode string, allowing one to change it. + class _ustring16_iterator_access + { + public: + _ustring16_iterator_access(const ustring16* s, u32 p) : ref(s), pos(p) {} + + //! Allow the class to be interpreted as a single UTF-32 character. + operator uchar32_t() const + { + return _get(); + } + + //! Allow one to change the character in the unicode string. + //! \param c The new character to use. + //! \return Myself. + _ustring16_iterator_access& operator=(const uchar32_t c) + { + _set(c); + return *this; + } + + //! Increments the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator++() + { + _set(_get() + 1); + return *this; + } + + //! Increments the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator++(int) + { + uchar32_t old = _get(); + _set(old + 1); + return old; + } + + //! Decrements the value by 1. + //! \return Myself. + _ustring16_iterator_access& operator--() + { + _set(_get() - 1); + return *this; + } + + //! Decrements the value by 1, returning the old value. + //! \return A unicode character. + uchar32_t operator--(int) + { + uchar32_t old = _get(); + _set(old - 1); + return old; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return Myself. + _ustring16_iterator_access& operator+=(int val) + { + _set(_get() + val); + return *this; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return Myself. + _ustring16_iterator_access& operator-=(int val) + { + _set(_get() - val); + return *this; + } + + //! Multiples the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return Myself. + _ustring16_iterator_access& operator*=(int val) + { + _set(_get() * val); + return *this; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return Myself. + _ustring16_iterator_access& operator/=(int val) + { + _set(_get() / val); + return *this; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return Myself. + _ustring16_iterator_access& operator%=(int val) + { + _set(_get() % val); + return *this; + } + + //! Adds to the value by a specified amount. + //! \param val The amount to add to this character. + //! \return A unicode character. + uchar32_t operator+(int val) const + { + return _get() + val; + } + + //! Subtracts from the value by a specified amount. + //! \param val The amount to subtract from this character. + //! \return A unicode character. + uchar32_t operator-(int val) const + { + return _get() - val; + } + + //! Multiplies the value by a specified amount. + //! \param val The amount to multiply this character by. + //! \return A unicode character. + uchar32_t operator*(int val) const + { + return _get() * val; + } + + //! Divides the value by a specified amount. + //! \param val The amount to divide this character by. + //! \return A unicode character. + uchar32_t operator/(int val) const + { + return _get() / val; + } + + //! Modulos the value by a specified amount. + //! \param val The amount to modulo this character by. + //! \return A unicode character. + uchar32_t operator%(int val) const + { + return _get() % val; + } + + private: + //! Gets a uchar32_t from our current position. + uchar32_t _get() const + { + const uchar16_t* a = ref->c_str(); + if (!UTF16_IS_SURROGATE(a[pos])) + return static_cast(a[pos]); + else + { + if (pos + 1 >= ref->size_raw()) + return 0; + + return unicode::toUTF32(a[pos], a[pos + 1]); + } + } + + //! Sets a uchar32_t at our current position. + void _set(uchar32_t c) + { + ustring16* ref2 = const_cast*>(ref); + const uchar16_t* a = ref2->c_str(); + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into the high and low surrogate pairs. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + // If the previous position was a surrogate pair, just replace them. Else, insert the low pair. + if (UTF16_IS_SURROGATE_HI(a[pos]) && pos + 1 != ref2->size_raw()) + ref2->replace_raw(vl, static_cast(pos) + 1); + else ref2->insert_raw(vl, static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + else + { + // c will be a single byte. + uchar16_t vh = static_cast(c); + + // If the previous position was a surrogate pair, remove the extra byte. + if (UTF16_IS_SURROGATE_HI(a[pos])) + ref2->erase_raw(static_cast(pos) + 1); + + ref2->replace_raw(vh, static_cast(pos)); + } + } + + const ustring16* ref; + u32 pos; + }; + typedef typename ustring16::_ustring16_iterator_access access; + + + //! Iterator to iterate through a UTF-16 string. +#ifndef USTRING_NO_STL + class _ustring16_const_iterator : public std::iterator< + std::bidirectional_iterator_tag, // iterator_category + access, // value_type + ptrdiff_t, // difference_type + const access, // pointer + const access // reference + > +#else + class _ustring16_const_iterator +#endif + { + public: + typedef _ustring16_const_iterator _Iter; + typedef std::iterator _Base; + typedef const access const_pointer; + typedef const access const_reference; + +#ifndef USTRING_NO_STL + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::difference_type distance_type; + typedef typename _Base::pointer pointer; + typedef const_reference reference; +#else + typedef access value_type; + typedef u32 difference_type; + typedef u32 distance_type; + typedef const_pointer pointer; + typedef const_reference reference; +#endif + + //! Constructors. + _ustring16_const_iterator(const _Iter& i) : ref(i.ref), pos(i.pos) {} + _ustring16_const_iterator(const ustring16& s) : ref(&s), pos(0) {} + _ustring16_const_iterator(const ustring16& s, const u32 p) : ref(&s), pos(0) + { + if (ref->size_raw() == 0 || p == 0) + return; + + // Go to the appropriate position. + u32 i = p; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + } + + //! Test for equalness. + bool operator==(const _Iter& iter) const + { + if (ref == iter.ref && pos == iter.pos) + return true; + return false; + } + + //! Test for unequalness. + bool operator!=(const _Iter& iter) const + { + if (ref != iter.ref || pos != iter.pos) + return true; + return false; + } + + //! Switch to the next full character in the string. + _Iter& operator++() + { // ++iterator + if (pos == ref->size_raw()) return *this; + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; // TODO: check for valid low surrogate? + else ++pos; + if (pos > ref->size_raw()) pos = ref->size_raw(); + return *this; + } + + //! Switch to the next full character in the string, returning the previous position. + _Iter operator++(int) + { // iterator++ + _Iter _tmp(*this); + ++*this; + return _tmp; + } + + //! Switch to the previous full character in the string. + _Iter& operator--() + { // --iterator + if (pos == 0) return *this; + const uchar16_t* a = ref->c_str(); + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) && pos != 0) // low surrogate, go back one more. + --pos; + return *this; + } + + //! Switch to the previous full character in the string, returning the previous position. + _Iter operator--(int) + { // iterator-- + _Iter _tmp(*this); + --*this; + return _tmp; + } + + //! Advance a specified number of full characters in the string. + //! \return Myself. + _Iter& operator+=(const difference_type v) + { + if (v == 0) return *this; + if (v < 0) return operator-=(v * -1); + + if (pos >= ref->size_raw()) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + u32 sr = ref->size_raw(); + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos < sr) + { + if (UTF16_IS_SURROGATE_HI(a[pos])) + pos += 2; + else ++pos; + --i; + } + if (pos > sr) + pos = sr; + + return *this; + } + + //! Go back a specified number of full characters in the string. + //! \return Myself. + _Iter& operator-=(const difference_type v) + { + if (v == 0) return *this; + if (v > 0) return operator+=(v * -1); + + if (pos == 0) + return *this; + + // Go to the appropriate position. + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + u32 i = (u32)v; + const uchar16_t* a = ref->c_str(); + while (i != 0 && pos != 0) + { + --pos; + if (UTF16_IS_SURROGATE_LO(a[pos]) != 0 && pos != 0) + --pos; + --i; + } + + return *this; + } + + //! Return a new iterator that is a variable number of full characters forward from the current position. + _Iter operator+(const difference_type v) const + { + _Iter ret(*this); + ret += v; + return ret; + } + + //! Return a new iterator that is a variable number of full characters backward from the current position. + _Iter operator-(const difference_type v) const + { + _Iter ret(*this); + ret -= v; + return ret; + } + + //! Returns the distance between two iterators. + difference_type operator-(const _Iter& iter) const + { + // Make sure we reference the same object! + if (ref != iter.ref) + return difference_type(); + + _Iter i = iter; + difference_type ret; + + // Walk up. + if (pos > i.pos) + { + while (pos > i.pos) + { + ++i; + ++ret; + } + return ret; + } + + // Walk down. + while (pos < i.pos) + { + --i; + --ret; + } + return ret; + } + + //! Accesses the full character at the iterator's position. + const_reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + const_reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + const_pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + + //! Is the iterator at the start of the string? + bool atStart() const + { + return pos == 0; + } + + //! Is the iterator at the end of the string? + bool atEnd() const + { + const uchar16_t* a = ref->c_str(); + if (UTF16_IS_SURROGATE(a[pos])) + return (pos + 1) >= ref->size_raw(); + else return pos >= ref->size_raw(); + } + + //! Moves the iterator to the start of the string. + void toStart() + { + pos = 0; + } + + //! Moves the iterator to the end of the string. + void toEnd() + { + const uchar16_t* a = ref->c_str(); + pos = ref->size_raw(); + } + + //! Returns the iterator's position. + //! \return The iterator's position. + u32 getPos() const + { + return pos; + } + + protected: + const ustring16* ref; + u32 pos; + }; + + //! Iterator to iterate through a UTF-16 string. + class _ustring16_iterator : public _ustring16_const_iterator + { + public: + typedef _ustring16_iterator _Iter; + typedef _ustring16_const_iterator _Base; + typedef typename _Base::const_pointer const_pointer; + typedef typename _Base::const_reference const_reference; + + typedef typename _Base::value_type value_type; + typedef typename _Base::difference_type difference_type; + typedef typename _Base::distance_type distance_type; + typedef access pointer; + typedef access reference; + + using _Base::pos; + using _Base::ref; + + //! Constructors. + _ustring16_iterator(const _Iter& i) : _ustring16_const_iterator(i) {} + _ustring16_iterator(const ustring16& s) : _ustring16_const_iterator(s) {} + _ustring16_iterator(const ustring16& s, const u32 p) : _ustring16_const_iterator(s, p) {} + + //! Accesses the full character at the iterator's position. + reference operator*() const + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + reference operator*() + { + if (pos >= ref->size_raw()) + { + const uchar16_t* a = ref->c_str(); + u32 p = ref->size_raw(); + if (UTF16_IS_SURROGATE_LO(a[p])) + --p; + reference ret(ref, p); + return ret; + } + reference ret(ref, pos); + return ret; + } + + //! Accesses the full character at the iterator's position. + pointer operator->() const + { + return operator*(); + } + + //! Accesses the full character at the iterator's position. + pointer operator->() + { + return operator*(); + } + }; + + typedef typename ustring16::_ustring16_iterator iterator; + typedef typename ustring16::_ustring16_const_iterator const_iterator; + + ///----------------------/// + /// end iterator classes /// + ///----------------------/// + + //! Default constructor + ustring16() + : array(0), allocated(1), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + array = allocator.allocate(1); // new u16[1]; + array[0] = 0x0; + } + + + //! Constructor + ustring16(const ustring16& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + + //! Constructor from other string types + template + ustring16(const string& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other; + } + + +#ifndef USTRING_NO_STL + //! Constructor from std::string + template + ustring16(const std::basic_string& other) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + *this = other.c_str(); + } + + + //! Constructor from iterator. + template + ustring16(Itr first, Itr last) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + reserve(std::distance(first, last)); + array[used] = 0; + + for (; first != last; ++first) + append((uchar32_t)*first); + } +#endif + + +#ifndef USTRING_CPP0X_NEWLITERALS + //! Constructor for copying a character string from a pointer. + ustring16(const char* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, strlen(c)); + //append((uchar8_t*)c); + } + + + //! Constructor for copying a character string from a pointer with a given length. + ustring16(const char* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + loadDataStream(c, length); + } +#endif + + + //! Constructor for copying a UTF-8 string from a pointer. + ustring16(const uchar8_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-8 string from a pointer with a given length. + ustring16(const uchar8_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-16 string from a pointer. + ustring16(const uchar16_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-16 string from a pointer with a given length + ustring16(const uchar16_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a UTF-32 string from a pointer. + ustring16(const uchar32_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c); + } + + + //! Constructor for copying a UTF-32 from a pointer with a given length. + ustring16(const uchar32_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + append(c, length); + } + + + //! Constructor for copying a wchar_t string from a pointer. + ustring16(const wchar_t* const c) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c)); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c)); + } + + + //! Constructor for copying a wchar_t string from a pointer with a given length. + ustring16(const wchar_t* const c, u32 length) + : array(0), allocated(0), used(0) + { +#if __BIG_ENDIAN__ + encoding = unicode::EUTFE_UTF16_BE; +#else + encoding = unicode::EUTFE_UTF16_LE; +#endif + + if (sizeof(wchar_t) == 4) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 2) + append(reinterpret_cast(c), length); + else if (sizeof(wchar_t) == 1) + append(reinterpret_cast(c), length); + } + + +#ifdef USTRING_CPP0X + //! Constructor for moving a ustring16 + ustring16(ustring16&& other) + : array(other.array), encoding(other.encoding), allocated(other.allocated), used(other.used) + { + //std::cout << "MOVE constructor" << std::endl; + other.array = 0; + other.allocated = 0; + other.used = 0; + } +#endif + + + //! Destructor + ~ustring16() + { + allocator.deallocate(array); // delete [] array; + } + + + //! Assignment operator + ustring16& operator=(const ustring16& other) + { + if (this == &other) + return *this; + + used = other.size_raw(); + if (used >= allocated) + { + allocator.deallocate(array); // delete [] array; + allocated = used + 1; + array = allocator.allocate(used + 1); //new u16[used]; + } + + const uchar16_t* p = other.c_str(); + for (u32 i=0; i<=used; ++i, ++p) + array[i] = *p; + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + +#ifdef USTRING_CPP0X + //! Move assignment operator + ustring16& operator=(ustring16&& other) + { + if (this != &other) + { + //std::cout << "MOVE operator=" << std::endl; + allocator.deallocate(array); + + array = other.array; + allocated = other.allocated; + encoding = other.encoding; + used = other.used; + other.array = 0; + other.used = 0; + } + return *this; + } +#endif + + + //! Assignment operator for other string types + template + ustring16& operator=(const string& other) + { + *this = other.c_str(); + return *this; + } + + + //! Assignment operator for UTF-8 strings + ustring16& operator=(const uchar8_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-16 strings + ustring16& operator=(const uchar16_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for UTF-32 strings + ustring16& operator=(const uchar32_t* const c) + { + if (!array) + { + array = allocator.allocate(1); //new u16[1]; + allocated = 1; + } + used = 0; + array[used] = 0x0; + if (!c) return *this; + + //! Append our string now. + append(c); + return *this; + } + + + //! Assignment operator for wchar_t strings. + /** Note that this assumes that a correct unicode string is stored in the wchar_t string. + Since wchar_t changes depending on its platform, it could either be a UTF-8, -16, or -32 string. + This function assumes you are storing the correct unicode encoding inside the wchar_t string. **/ + ustring16& operator=(const wchar_t* const c) + { + if (sizeof(wchar_t) == 4) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 2) + *this = reinterpret_cast(c); + else if (sizeof(wchar_t) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Assignment operator for other strings. + /** Note that this assumes that a correct unicode string is stored in the string. **/ + template + ustring16& operator=(const B* const c) + { + if (sizeof(B) == 4) + *this = reinterpret_cast(c); + else if (sizeof(B) == 2) + *this = reinterpret_cast(c); + else if (sizeof(B) == 1) + *this = reinterpret_cast(c); + + return *this; + } + + + //! Direct access operator + access operator [](const u32 index) + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + iterator iter(*this, index); + return iter.operator*(); + } + + + //! Direct access operator + const access operator [](const u32 index) const + { + _IRR_DEBUG_BREAK_IF(index>=size()) // bad index + const_iterator iter(*this, index); + return iter.operator*(); + } + + + //! Equality operator + bool operator ==(const uchar16_t* const str) const + { + if (!str) + return false; + + u32 i; + for(i=0; array[i] && str[i]; ++i) + if (array[i] != str[i]) + return false; + + return !array[i] && !str[i]; + } + + + //! Equality operator + bool operator ==(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + if (array[i] != other.array[i]) + return false; + + return used == other.used; + } + + + //! Is smaller comparator + bool operator <(const ustring16& other) const + { + for(u32 i=0; array[i] && other.array[i]; ++i) + { + s32 diff = array[i] - other.array[i]; + if ( diff ) + return diff < 0; + } + + return used < other.used; + } + + + //! Inequality operator + bool operator !=(const uchar16_t* const str) const + { + return !(*this == str); + } + + + //! Inequality operator + bool operator !=(const ustring16& other) const + { + return !(*this == other); + } + + + //! Returns the length of a ustring16 in full characters. + //! \return Length of a ustring16 in full characters. + u32 size() const + { + const_iterator i(*this, 0); + u32 pos = 0; + while (!i.atEnd()) + { + ++i; + ++pos; + } + return pos; + } + + + //! Informs if the ustring is empty or not. + //! \return True if the ustring is empty, false if not. + bool empty() const + { + return (size_raw() == 0); + } + + + //! Returns a pointer to the raw UTF-16 string data. + //! \return pointer to C-style NUL terminated array of UTF-16 code points. + const uchar16_t* c_str() const + { + return array; + } + + + //! Compares the first n characters of this string with another. + //! \param other Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const ustring16& other, u32 n) const + { + u32 i; + const uchar16_t* oa = other.c_str(); + for(i=0; array[i] && oa[i] && i < n; ++i) + if (array[i] != oa[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (used == other.used); + } + + + //! Compares the first n characters of this string with another. + //! \param str Other string to compare to. + //! \param n Number of characters to compare. + //! \return True if the n first characters of both strings are equal. + bool equalsn(const uchar16_t* const str, u32 n) const + { + if (!str) + return false; + u32 i; + for(i=0; array[i] && str[i] && i < n; ++i) + if (array[i] != str[i]) + return false; + + // if one (or both) of the strings was smaller then they + // are only equal if they have the same length + return (i == n) || (array[i] == 0 && str[i] == 0); + } + + + //! Appends a character to this ustring16 + //! \param character The character to append. + //! \return A reference to our current string. + ustring16& append(uchar32_t character) + { + if (used + 2 >= allocated) + reallocate(used + 2); + + if (character > 0xFFFF) + { + used += 2; + + // character will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(character); + uchar16_t vh = UTF16_HI_SURROGATE | ((((character >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[used-2] = vh; + array[used-1] = vl; + } + else + { + ++used; + array[used-1] = character; + } + array[used] = 0; + + return *this; + } + + + //! Appends a UTF-8 string to this ustring16 + //! \param other The UTF-8 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar8_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar8_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF8_LEN); + + // Check for BOM. + unicode::EUTF_ENCODE c_bom = unicode::EUTFE_NONE; + if (len == unicode::BOM_ENCODE_UTF8_LEN) + { + if (memcmp(other, unicode::BOM_ENCODE_UTF8, unicode::BOM_ENCODE_UTF8_LEN) == 0) + c_bom = unicode::EUTFE_UTF8; + } + + // If a BOM was found, don't include it in the string. + const uchar8_t* c2 = other; + if (c_bom != unicode::EUTFE_NONE) + { + c2 = other + unicode::BOM_UTF8_LEN; + length -= unicode::BOM_UTF8_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + + // Convert UTF-8 to UTF-16. + u32 pos = start; + for (u32 l = 0; l> 6) & 0x03) == 0x02) + { // Invalid continuation byte. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if (c2[l] == 0xC0 || c2[l] == 0xC1) + { // Invalid byte - overlong encoding. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + } + else if ((c2[l] & 0xF8) == 0xF0) + { // 4 bytes UTF-8, 2 bytes UTF-16. + // Check for a full string. + if ((l + 3) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 3; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+3] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0x7) << 2) | ((c2[l+1] >> 4) & 0x3); + uchar8_t b2 = ((c2[l+1] & 0xF) << 4) | ((c2[l+2] >> 2) & 0xF); + uchar8_t b3 = ((c2[l+2] & 0x3) << 6) | (c2[l+3] & 0x3F); + uchar32_t v = b3 | ((uchar32_t)b2 << 8) | ((uchar32_t)b1 << 16); + + // Split v up into a surrogate pair. + uchar16_t x = static_cast(v); + uchar16_t vh = UTF16_HI_SURROGATE | ((((v >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + + array[pos++] = vh; + array[pos++] = vl; + l += 4; + ++used; // Using two shorts this time, so increase used by 1. + } + else if ((c2[l] & 0xF0) == 0xE0) + { // 3 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 2) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 2; + break; + } + + // Validate. + bool valid = true; + u8 l2 = 0; + if (valid && (((c2[l+1] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (valid && (((c2[l+2] >> 6) & 0x03) == 0x02)) ++l2; else valid = false; + if (!valid) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += l2; + continue; + } + + // Decode. + uchar8_t b1 = ((c2[l] & 0xF) << 4) | ((c2[l+1] >> 2) & 0xF); + uchar8_t b2 = ((c2[l+1] & 0x3) << 6) | (c2[l+2] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 3; + } + else if ((c2[l] & 0xE0) == 0xC0) + { // 2 bytes UTF-8, 1 byte UTF-16. + // Check for a full string. + if ((l + 1) >= len) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + l += 1; + break; + } + + // Validate. + if (((c2[l+1] >> 6) & 0x03) != 0x02) + { + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + ++l; + continue; + } + + // Decode. + uchar8_t b1 = (c2[l] >> 2) & 0x7; + uchar8_t b2 = ((c2[l] & 0x3) << 6) | (c2[l+1] & 0x3F); + uchar16_t ch = b2 | ((uchar16_t)b1 << 8); + array[pos++] = ch; + l += 2; + } + else + { // 1 byte UTF-8, 1 byte UTF-16. + // Validate. + if (c2[l] > 0x7F) + { // Values above 0xF4 are restricted and aren't used. By now, anything above 0x7F is invalid. + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(c2[l]); + ++l; + } + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a UTF-16 string to this ustring16 + //! \param other The UTF-16 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar16_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Determine if the string is long enough for a BOM. + u32 len = 0; + const uchar16_t* p = other; + do + { + ++len; + } while (*p++ && len < unicode::BOM_ENCODE_UTF16_LEN); + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF16_LE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF16_BE, unicode::BOM_ENCODE_UTF16_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar16_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF16_LEN; + length -= unicode::BOM_UTF16_LEN; + } + + // Calculate the size of the string to read in. + len = 0; + p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + if (used + len >= allocated) + reallocate(used + (len * 2)); + u32 start = used; + used += len; + + // Copy the string now. + unicode::EUTF_ENDIAN m_end = getEndianness(); + for (u32 l = start; l < start + len; ++l) + { + array[l] = (uchar16_t)c2[l]; + if (c_end != unicode::EUTFEE_NATIVE && c_end != m_end) + array[l] = unicode::swapEndian16(array[l]); + } + + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + return *this; + } + + + //! Appends a UTF-32 string to this ustring16 + //! \param other The UTF-32 string to append. + //! \param length The length of the string to append. + //! \return A reference to our current string. + ustring16& append(const uchar32_t* const other, u32 length=0xffffffff) + { + if (!other) + return *this; + + // Check for the BOM to determine the string's endianness. + unicode::EUTF_ENDIAN c_end = unicode::EUTFEE_NATIVE; + if (memcmp(other, unicode::BOM_ENCODE_UTF32_LE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_LITTLE; + else if (memcmp(other, unicode::BOM_ENCODE_UTF32_BE, unicode::BOM_ENCODE_UTF32_LEN) == 0) + c_end = unicode::EUTFEE_BIG; + + // If a BOM was found, don't include it in the string. + const uchar32_t* c2 = other; + if (c_end != unicode::EUTFEE_NATIVE) + { + c2 = other + unicode::BOM_UTF32_LEN; + length -= unicode::BOM_UTF32_LEN; + } + + // Calculate the size of the string to read in. + u32 len = 0; + const uchar32_t* p = c2; + do + { + ++len; + } while(*p++ && len < length); + if (len > length) + len = length; + + // If we need to grow the size of the array, do it now. + // In case all of the UTF-32 string is split into surrogate pairs, do len * 2. + if (used + (len * 2) >= allocated) + reallocate(used + ((len * 2) * 2)); + u32 start = used; + + // Convert UTF-32 to UTF-16. + unicode::EUTF_ENDIAN m_end = getEndianness(); + u32 pos = start; + for (u32 l = 0; l 0xFFFF) + { + // Split ch up into a surrogate pair as it is over 16 bits long. + uchar16_t x = static_cast(ch); + uchar16_t vh = UTF16_HI_SURROGATE | ((((ch >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[pos++] = vh; + array[pos++] = vl; + ++used; // Using two shorts, so increased used again. + } + else if (ch >= 0xD800 && ch <= 0xDFFF) + { + // Between possible UTF-16 surrogates (invalid!) + array[pos++] = unicode::UTF_REPLACEMENT_CHARACTER; + } + else array[pos++] = static_cast(ch); + } + array[used] = 0; + + // Validate our new UTF-16 string. + validate(); + + return *this; + } + + + //! Appends a ustring16 to this ustring16 + //! \param other The string to append to this one. + //! \return A reference to our current string. + ustring16& append(const ustring16& other) + { + const uchar16_t* oa = other.c_str(); + + u32 len = other.size_raw(); + + if (used + len >= allocated) + reallocate(used + len); + + for (u32 l=0; l& append(const ustring16& other, u32 length) + { + if (other.size() == 0) + return *this; + + if (other.size() < length) + { + append(other); + return *this; + } + + if (used + length * 2 >= allocated) + reallocate(used + length * 2); + + const_iterator iter(other, 0); + u32 l = length; + while (!iter.atEnd() && l) + { + uchar32_t c = *iter; + append(c); + ++iter; + --l; + } + + return *this; + } + + + //! Reserves some memory. + //! \param count The amount of characters to reserve. + void reserve(u32 count) + { + if (count < allocated) + return; + + reallocate(count); + } + + + //! Finds first occurrence of character. + //! \param c The character to search for. + //! \return Position where the character has been found, or -1 if not found. + s32 findFirst(uchar32_t c) const + { + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + if (c == t) + return pos; + ++pos; + ++i; + } + + return -1; + } + + //! Finds first occurrence of a character of a list. + //! \param c A list of characters to find. For example if the method should find the first occurrence of 'a' or 'b', this parameter should be "ab". + //! \param count The amount of characters in the list. Usually, this should be strlen(c). + //! \return Position where one of the characters has been found, or -1 if not found. + s32 findFirstChar(const uchar32_t* const c, u32 count=1) const + { + if (!c || !count) + return -1; + + const_iterator i(*this, 0); + + s32 pos = 0; + while (!i.atEnd()) + { + uchar32_t t = *i; + for (u32 j=0; j& str, const u32 start = 0) const + { + u32 my_size = size(); + u32 their_size = str.size(); + + if (their_size == 0 || my_size - start < their_size) + return -1; + + const_iterator i(*this, start); + + s32 pos = start; + while (!i.atEnd()) + { + const_iterator i2(i); + const_iterator j(str, 0); + uchar32_t t1 = (uchar32_t)*i2; + uchar32_t t2 = (uchar32_t)*j; + while (t1 == t2) + { + ++i2; + ++j; + if (j.atEnd()) + return pos; + t1 = (uchar32_t)*i2; + t2 = (uchar32_t)*j; + } + ++i; + ++pos; + } + + return -1; + } + + + //! Finds another ustring16 in this ustring16. + //! \param str The string to find. + //! \param start The start position of the search. + //! \return Positions where the string has been found, or -1 if not found. + s32 find_raw(const ustring16& str, const u32 start = 0) const + { + const uchar16_t* data = str.c_str(); + if (data && *data) + { + u32 len = 0; + + while (data[len]) + ++len; + + if (len > used) + return -1; + + for (u32 i=start; i<=used-len; ++i) + { + u32 j=0; + + while(data[j] && array[i+j] == data[j]) + ++j; + + if (!data[j]) + return i; + } + } + + return -1; + } + + + //! Returns a substring. + //! \param begin: Start of substring. + //! \param length: Length of substring. + //! \return A reference to our current string. + ustring16 subString(u32 begin, s32 length) const + { + u32 len = size(); + // if start after ustring16 + // or no proper substring length + if ((length <= 0) || (begin>=len)) + return ustring16(""); + // clamp length to maximal value + if ((length+begin) > len) + length = len-begin; + + ustring16 o; + o.reserve((length+1) * 2); + + const_iterator i(*this, begin); + while (!i.atEnd() && length) + { + o.append(*i); + ++i; + --length; + } + + return o; + } + + + //! Appends a character to this ustring16. + //! \param c Character to append. + //! \return A reference to our current string. + ustring16& operator += (uchar32_t c) + { + append(c); + return *this; + } + + + //! Appends a char ustring16 to this ustring16. + //! \param c Char ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const uchar16_t* const c) + { + append(c); + return *this; + } + + + //! Appends a ustring16 to this ustring16. + //! \param other ustring16 to append. + //! \return A reference to our current string. + ustring16& operator += (const ustring16& other) + { + append(other); + return *this; + } + + + //! Replaces all characters of a given type with another one. + //! \param toReplace Character to replace. + //! \param replaceWith Character replacing the old one. + //! \return A reference to our current string. + ustring16& replace(uchar32_t toReplace, uchar32_t replaceWith) + { + iterator i(*this, 0); + while (!i.atEnd()) + { + typename ustring16::iterator::access a = *i; + if ((uchar32_t)a == toReplace) + a = replaceWith; + ++i; + } + return *this; + } + + + //! Replaces all instances of a string with another one. + //! \param toReplace The string to replace. + //! \param replaceWith The string replacing the old one. + //! \return A reference to our current string. + ustring16& replace(const ustring16& toReplace, const ustring16& replaceWith) + { + if (toReplace.size() == 0) + return *this; + + const uchar16_t* other = toReplace.c_str(); + const uchar16_t* replace = replaceWith.c_str(); + const u32 other_size = toReplace.size_raw(); + const u32 replace_size = replaceWith.size_raw(); + + // Determine the delta. The algorithm will change depending on the delta. + s32 delta = replace_size - other_size; + + // A character for character replace. The string will not shrink or grow. + if (delta == 0) + { + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + ++pos; + } + return *this; + } + + // We are going to be removing some characters. The string will shrink. + if (delta < 0) + { + u32 i = 0; + for (u32 pos = 0; pos <= used; ++i, ++pos) + { + // Is this potentially a match? + if (array[pos] == *other) + { + // Check to see if we have a match. + u32 j; + for (j = 0; j < other_size; ++j) + { + if (array[pos + j] != other[j]) + break; + } + + // If we have a match, replace characters. + if (j == other_size) + { + for (j = 0; j < replace_size; ++j) + array[i + j] = replace[j]; + i += replace_size - 1; + pos += other_size - 1; + continue; + } + } + + // No match found, just copy characters. + array[i - 1] = array[pos]; + } + array[i] = 0; + used = i; + + return *this; + } + + // We are going to be adding characters, so the string size will increase. + // Count the number of times toReplace exists in the string so we can allocate the new size. + u32 find_count = 0; + s32 pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + ++find_count; + ++pos; + } + + // Re-allocate the string now, if needed. + u32 len = delta * find_count; + if (used + len >= allocated) + reallocate(used + len); + + // Start replacing. + pos = 0; + while ((pos = find_raw(other, pos)) != -1) + { + uchar16_t* start = array + pos + other_size - 1; + uchar16_t* ptr = array + used; + uchar16_t* end = array + used + delta; + + // Shift characters to make room for the string. + while (ptr != start) + { + *end = *ptr; + --ptr; + --end; + } + + // Add the new string now. + for (u32 i = 0; i < replace_size; ++i) + array[pos + i] = replace[i]; + + pos += replace_size; + used += delta; + } + + // Terminate the string and return ourself. + array[used] = 0; + return *this; + } + + + //! Removes characters from a ustring16.. + //! \param c The character to remove. + //! \return A reference to our current string. + ustring16& remove(uchar32_t c) + { + u32 pos = 0; + u32 found = 0; + u32 len = (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i + 1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + if (uc32 == c) + { + found += len; + continue; + } + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes a ustring16 from the ustring16. + //! \param toRemove The string to remove. + //! \return A reference to our current string. + ustring16& remove(const ustring16& toRemove) + { + u32 size = toRemove.size_raw(); + if (size == 0) return *this; + + const uchar16_t* tra = toRemove.c_str(); + u32 pos = 0; + u32 found = 0; + for (u32 i=0; i<=used; ++i) + { + u32 j = 0; + while (j < size) + { + if (array[i + j] != tra[j]) + break; + ++j; + } + if (j == size) + { + found += size; + i += size - 1; + continue; + } + + array[pos++] = array[i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Removes characters from the ustring16. + //! \param characters The characters to remove. + //! \return A reference to our current string. + ustring16& removeChars(const ustring16& characters) + { + if (characters.size_raw() == 0) + return *this; + + u32 pos = 0; + u32 found = 0; + const_iterator iter(characters); + for (u32 i=0; i<=used; ++i) + { + uchar32_t uc32 = 0; + if (!UTF16_IS_SURROGATE_HI(array[i])) + uc32 |= array[i]; + else if (i + 1 <= used) + { + // Convert the surrogate pair into a single UTF-32 character. + uc32 = unicode::toUTF32(array[i], array[i+1]); + } + u32 len2 = (uc32 > 0xFFFF ? 2 : 1); + + bool cont = false; + iter.toStart(); + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (uc32 == c) + { + found += (c > 0xFFFF ? 2 : 1); // Remove characters equal to the size of c as a UTF-16 character. + ++i; + cont = true; + break; + } + ++iter; + } + if (cont) continue; + + array[pos++] = array[i]; + if (len2 == 2) + array[pos++] = array[++i]; + } + used -= found; + array[used] = 0; + return *this; + } + + + //! Trims the ustring16. + //! Removes the specified characters (by default, Latin-1 whitespace) from the begining and the end of the ustring16. + //! \param whitespace The characters that are to be considered as whitespace. + //! \return A reference to our current string. + ustring16& trim(const ustring16& whitespace = " \t\n\r") + { + core::array utf32white = whitespace.toUTF32(); + + // find start and end of the substring without the specified characters + const s32 begin = findFirstCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + if (begin == -1) + return (*this=""); + + const s32 end = findLastCharNotInList(utf32white.const_pointer(), whitespace.used + 1); + + return (*this = subString(begin, (end +1) - begin)); + } + + + //! Erases a character from the ustring16. + //! May be slow, because all elements following after the erased element have to be copied. + //! \param index Index of element to be erased. + //! \return A reference to our current string. + ustring16& erase(u32 index) + { + _IRR_DEBUG_BREAK_IF(index>used) // access violation + + iterator i(*this, index); + + uchar32_t t = *i; + u32 len = (t > 0xFFFF ? 2 : 1); + + for (u32 j = static_cast(i.getPos()) + len; j <= used; ++j) + array[j - len] = array[j]; + + used -= len; + array[used] = 0; + + return *this; + } + + + //! Validate the existing ustring16, checking for valid surrogate pairs and checking for proper termination. + //! \return A reference to our current string. + ustring16& validate() + { + // Validate all unicode characters. + for (u32 i=0; i= allocated) || UTF16_IS_SURROGATE_LO(array[i])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + else if (UTF16_IS_SURROGATE_HI(array[i]) && !UTF16_IS_SURROGATE_LO(array[i+1])) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + ++i; + } + if (array[i] >= 0xFDD0 && array[i] <= 0xFDEF) + array[i] = unicode::UTF_REPLACEMENT_CHARACTER; + } + + // terminate + used = 0; + if (allocated > 0) + { + used = allocated - 1; + array[used] = 0; + } + return *this; + } + + + //! Gets the last char of the ustring16, or 0. + //! \return The last char of the ustring16, or 0. + uchar32_t lastChar() const + { + if (used < 1) + return 0; + + if (UTF16_IS_SURROGATE_LO(array[used-1])) + { + // Make sure we have a paired surrogate. + if (used < 2) + return 0; + + // Check for an invalid surrogate. + if (!UTF16_IS_SURROGATE_HI(array[used-2])) + return 0; + + // Convert the surrogate pair into a single UTF-32 character. + return unicode::toUTF32(array[used-2], array[used-1]); + } + else + { + return array[used-1]; + } + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c C-style ustring16 of delimiter characters + \param count Number of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const uchar32_t* const c, u32 count=1, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + if (!c) + return 0; + + const_iterator i(*this); + const u32 oldSize=ret.size(); + u32 pos = 0; + u32 lastpos = 0; + u32 lastpospos = 0; + bool lastWasSeparator = false; + while (!i.atEnd()) + { + uchar32_t ch = *i; + bool foundSeparator = false; + for (u32 j=0; j(&array[lastpospos], pos - lastpos)); + foundSeparator = true; + lastpos = (keepSeparators ? pos : pos + 1); + lastpospos = (keepSeparators ? i.getPos() : i.getPos() + 1); + break; + } + } + lastWasSeparator = foundSeparator; + ++pos; + ++i; + } + u32 s = size() + 1; + if (s > lastpos) + ret.push_back(ustring16(&array[lastpospos], s - lastpos)); + return ret.size()-oldSize; + } + + + //! Split the ustring16 into parts. + /** This method will split a ustring16 at certain delimiter characters + into the container passed in as reference. The type of the container + has to be given as template parameter. It must provide a push_back and + a size method. + \param ret The result container + \param c A unicode string of delimiter characters + \param ignoreEmptyTokens Flag to avoid empty substrings in the result + container. If two delimiters occur without a character in between, an + empty substring would be placed in the result. If this flag is set, + only non-empty strings are stored. + \param keepSeparators Flag which allows to add the separator to the + result ustring16. If this flag is true, the concatenation of the + substrings results in the original ustring16. Otherwise, only the + characters between the delimiters are returned. + \return The number of resulting substrings + */ + template + u32 split(container& ret, const ustring16& c, bool ignoreEmptyTokens=true, bool keepSeparators=false) const + { + core::array v = c.toUTF32(); + return split(ret, v.pointer(), v.size(), ignoreEmptyTokens, keepSeparators); + } + + + //! Gets the size of the allocated memory buffer for the string. + //! \return The size of the allocated memory buffer. + u32 capacity() const + { + return allocated; + } + + + //! Returns the raw number of UTF-16 code points in the string which includes the individual surrogates. + //! \return The raw number of UTF-16 code points, excluding the trialing NUL. + u32 size_raw() const + { + return used; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert(uchar32_t c, u32 pos) + { + u8 len = (c > 0xFFFF ? 2 : 1); + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos(); --i) + array[i] = array[i - len]; + + if (c > 0xFFFF) + { + // c will be multibyte, so split it up into a surrogate pair. + uchar16_t x = static_cast(c); + uchar16_t vh = UTF16_HI_SURROGATE | ((((c >> 16) & ((1 << 5) - 1)) - 1) << 6) | (x >> 10); + uchar16_t vl = UTF16_LO_SURROGATE | (x & ((1 << 10) - 1)); + array[iter.getPos()] = vh; + array[iter.getPos()+1] = vl; + } + else + { + array[iter.getPos()] = static_cast(c); + } + array[used] = 0; + return *this; + } + + + //! Inserts a string into the string. + //! \param c The string to insert. + //! \param pos The position to insert the string. + //! \return A reference to our current string. + ustring16& insert(const ustring16& c, u32 pos) + { + u32 len = c.size_raw(); + if (len == 0) return *this; + + if (used + len >= allocated) + reallocate(used + len); + + used += len; + + iterator iter(*this, pos); + for (u32 i = used - 2; i > iter.getPos() + len; --i) + array[i] = array[i - len]; + + const uchar16_t* s = c.c_str(); + for (u32 i = 0; i < len; ++i) + { + array[pos++] = *s; + ++s; + } + + array[used] = 0; + return *this; + } + + + //! Inserts a character into the string. + //! \param c The character to insert. + //! \param pos The position to insert the character. + //! \return A reference to our current string. + ustring16& insert_raw(uchar16_t c, u32 pos) + { + if (used + 1 >= allocated) + reallocate(used + 1); + + ++used; + + for (u32 i = used - 1; i > pos; --i) + array[i] = array[i - 1]; + + array[pos] = c; + array[used] = 0; + return *this; + } + + + //! Removes a character from string. + //! \param pos Position of the character to remove. + //! \return A reference to our current string. + ustring16& erase_raw(u32 pos) + { + for (u32 i=pos; i<=used; ++i) + { + array[i] = array[i + 1]; + } + --used; + array[used] = 0; + return *this; + } + + + //! Replaces a character in the string. + //! \param c The new character. + //! \param pos The position of the character to replace. + //! \return A reference to our current string. + ustring16& replace_raw(uchar16_t c, u32 pos) + { + array[pos] = c; + return *this; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + iterator begin() + { + iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator begin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the beginning of the string. + //! \return An iterator to the beginning of the string. + const_iterator cbegin() const + { + const_iterator i(*this, 0); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + iterator end() + { + iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator end() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Returns an iterator to the end of the string. + //! \return An iterator to the end of the string. + const_iterator cend() const + { + const_iterator i(*this, 0); + i.toEnd(); + return i; + } + + + //! Converts the string to a UTF-8 encoded string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-8 encoded string. + core::string toUTF8_s(const bool addBOM = false) const + { + core::string ret; + ret.reserve(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.append(unicode::BOM_ENCODE_UTF8[0]); + ret.append(unicode::BOM_ENCODE_UTF8[1]); + ret.append(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + ret.append(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + ret.append(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.append(b1); + ret.append(b2); + } + else + { // 1 byte + ret.append(static_cast(c)); + } + ++iter; + } + return ret; + } + + + //! Converts the string to a UTF-8 encoded string array. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-8 encoded string. + core::array toUTF8(const bool addBOM = false) const + { + core::array ret(used * 4 + (addBOM ? unicode::BOM_UTF8_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the byte order mark if the user wants it. + if (addBOM) + { + ret.push_back(unicode::BOM_ENCODE_UTF8[0]); + ret.push_back(unicode::BOM_ENCODE_UTF8[1]); + ret.push_back(unicode::BOM_ENCODE_UTF8[2]); + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (c > 0xFFFF) + { // 4 bytes + uchar8_t b1 = (0x1E << 3) | ((c >> 18) & 0x7); + uchar8_t b2 = (0x2 << 6) | ((c >> 12) & 0x3F); + uchar8_t b3 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b4 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + ret.push_back(b4); + } + else if (c > 0x7FF) + { // 3 bytes + uchar8_t b1 = (0xE << 4) | ((c >> 12) & 0xF); + uchar8_t b2 = (0x2 << 6) | ((c >> 6) & 0x3F); + uchar8_t b3 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + ret.push_back(b3); + } + else if (c > 0x7F) + { // 2 bytes + uchar8_t b1 = (0x6 << 5) | ((c >> 6) & 0x1F); + uchar8_t b2 = (0x2 << 6) | (c & 0x3F); + ret.push_back(b1); + ret.push_back(b2); + } + else + { // 1 byte + ret.push_back(static_cast(c)); + } + ++iter; + } + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-16 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-16 encoded string. + core::string toUTF16_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret[0] = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ret.c_str()); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + } + + ret.append(array); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + char16_t* ptr = ret.c_str(); + for (u32 i = 0; i < ret.size(); ++i) + *ptr++ = unicode::swapEndian16(*ptr); + } + return ret; + } +#endif + + + //! Converts the string to a UTF-16 encoded string array. + //! Unfortunately, no toUTF16_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-16 encoded string. + core::array toUTF16(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(used + (addBOM ? unicode::BOM_UTF16_LEN : 0) + 1); + uchar16_t* ptr = ret.pointer(); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + *ptr = unicode::BOM; + else if (endian == unicode::EUTFEE_LITTLE) + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_LE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_LE[1]; + } + else + { + uchar8_t* ptr8 = reinterpret_cast(ptr); + *ptr8++ = unicode::BOM_ENCODE_UTF16_BE[0]; + *ptr8 = unicode::BOM_ENCODE_UTF16_BE[1]; + } + ++ptr; + } + + memcpy((void*)ptr, (void*)array, used * sizeof(uchar16_t)); + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + { + for (u32 i = 0; i <= used; ++i) + *ptr++ = unicode::swapEndian16(*ptr); + } + ret.set_used(used + (addBOM ? unicode::BOM_UTF16_LEN : 0)); + ret.push_back(0); + return ret; + } + + +#ifdef USTRING_CPP0X_NEWLITERALS // C++0x + //! Converts the string to a UTF-32 encoded string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the UTF-32 encoded string. + core::string toUTF32_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::string ret; + ret.reserve(size() + 1 + (addBOM ? unicode::BOM_UTF32_LEN : 0)); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.append(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.append(t.full); + } + } + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.append(c); + ++iter; + } + return ret; + } +#endif + + + //! Converts the string to a UTF-32 encoded string array. + //! Unfortunately, no toUTF32_s() version exists due to limitations with Irrlicht's string class. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the UTF-32 encoded string. + core::array toUTF32(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + core::array ret(size() + (addBOM ? unicode::BOM_UTF32_LEN : 0) + 1); + const_iterator iter(*this, 0); + + // Add the BOM if specified. + if (addBOM) + { + if (endian == unicode::EUTFEE_NATIVE) + ret.push_back(unicode::BOM); + else + { + union + { + uchar32_t full; + u8 chunk[4]; + } t; + + if (endian == unicode::EUTFEE_LITTLE) + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_LE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_LE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_LE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_LE[3]; + } + else + { + t.chunk[0] = unicode::BOM_ENCODE_UTF32_BE[0]; + t.chunk[1] = unicode::BOM_ENCODE_UTF32_BE[1]; + t.chunk[2] = unicode::BOM_ENCODE_UTF32_BE[2]; + t.chunk[3] = unicode::BOM_ENCODE_UTF32_BE[3]; + } + ret.push_back(t.full); + } + } + ret.push_back(0); + + while (!iter.atEnd()) + { + uchar32_t c = *iter; + if (endian != unicode::EUTFEE_NATIVE && getEndianness() != endian) + c = unicode::swapEndian32(c); + ret.push_back(c); + ++iter; + } + return ret; + } + + + //! Converts the string to a wchar_t encoded string. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return A string containing the wchar_t encoded string. + core::string toWCHAR_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + else if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::stringw ret(array); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + } + else if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::stringw ret(a.pointer()); + return ret; + } + + // Shouldn't happen. + return core::stringw(); + } + + + //! Converts the string to a wchar_t encoded string array. + /** The size of a wchar_t changes depending on the platform. This function will store a + correct UTF-8, -16, or -32 encoded string depending on the size of a wchar_t. **/ + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An array containing the wchar_t encoded string. + core::array toWCHAR(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { + if (sizeof(wchar_t) == 4) + { + core::array a(toUTF32(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar32_t)); + return ret; + } + if (sizeof(wchar_t) == 2) + { + if (endian == unicode::EUTFEE_NATIVE && addBOM == false) + { + core::array ret(used); + ret.set_used(used); + memcpy((void*)ret.pointer(), (void*)array, used * sizeof(uchar16_t)); + return ret; + } + else + { + core::array a(toUTF16(endian, addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar16_t)); + return ret; + } + } + if (sizeof(wchar_t) == 1) + { + core::array a(toUTF8(addBOM)); + core::array ret(a.size()); + ret.set_used(a.size()); + memcpy((void*)ret.pointer(), (void*)a.pointer(), a.size() * sizeof(uchar8_t)); + return ret; + } + + // Shouldn't happen. + return core::array(); + } + + //! Converts the string to a properly encoded io::path string. + //! \param endian The desired endianness of the string. + //! \param addBOM If true, the proper unicode byte-order mark will be prefixed to the string. + //! \return An io::path string containing the properly encoded string. + io::path toPATH_s(const unicode::EUTF_ENDIAN endian = unicode::EUTFEE_NATIVE, const bool addBOM = false) const + { +#if defined(_IRR_WCHAR_FILESYSTEM) + return toWCHAR_s(endian, addBOM); +#else + return toUTF8_s(addBOM); +#endif + } + + //! Loads an unknown stream of data. + //! Will attempt to determine if the stream is unicode data. Useful for loading from files. + //! \param data The data stream to load from. + //! \param data_size The length of the data string. + //! \return A reference to our current string. + ustring16& loadDataStream(const char* data, size_t data_size) + { + // Clear our string. + *this = ""; + if (!data) + return *this; + + unicode::EUTF_ENCODE e = unicode::determineUnicodeBOM(data); + switch (e) + { + default: + case unicode::EUTFE_UTF8: + append((uchar8_t*)data, data_size); + break; + + case unicode::EUTFE_UTF16: + case unicode::EUTFE_UTF16_BE: + case unicode::EUTFE_UTF16_LE: + append((uchar16_t*)data, data_size / 2); + break; + + case unicode::EUTFE_UTF32: + case unicode::EUTFE_UTF32_BE: + case unicode::EUTFE_UTF32_LE: + append((uchar32_t*)data, data_size / 4); + break; + } + + return *this; + } + + //! Gets the encoding of the Unicode string this class contains. + //! \return An enum describing the current encoding of this string. + const unicode::EUTF_ENCODE getEncoding() const + { + return encoding; + } + + //! Gets the endianness of the Unicode string this class contains. + //! \return An enum describing the endianness of this string. + const unicode::EUTF_ENDIAN getEndianness() const + { + if (encoding == unicode::EUTFE_UTF16_LE || + encoding == unicode::EUTFE_UTF32_LE) + return unicode::EUTFEE_LITTLE; + else return unicode::EUTFEE_BIG; + } + +private: + + //! Reallocate the string, making it bigger or smaller. + //! \param new_size The new size of the string. + void reallocate(u32 new_size) + { + uchar16_t* old_array = array; + + array = allocator.allocate(new_size + 1); //new u16[new_size]; + allocated = new_size + 1; + if (old_array == 0) return; + + u32 amount = used < new_size ? used : new_size; + for (u32 i=0; i<=amount; ++i) + array[i] = old_array[i]; + + if (allocated <= used) + used = allocated - 1; + + array[used] = 0; + + allocator.deallocate(old_array); // delete [] old_array; + } + + //--- member variables + + uchar16_t* array; + unicode::EUTF_ENCODE encoding; + u32 allocated; + u32 used; + TAlloc allocator; + //irrAllocator allocator; +}; + +typedef ustring16 > ustring; + + +//! Appends two ustring16s. +template +inline ustring16 operator+(const ustring16& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const ustring16& left, const B* const right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16 operator+(const B* const left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const ustring16& left, const string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16 operator+(const string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const ustring16& left, const std::basic_string& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16 operator+(const std::basic_string& left, const ustring16& right) +{ + ustring16 ret(left); + ret += right; + return ret; +} + + +#ifdef USTRING_CPP0X +//! Appends two ustring16s. +template +inline ustring16&& operator+(const ustring16& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, const ustring16& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends two ustring16s. +template +inline ustring16&& operator+(ustring16&& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&&, &&)" << std::endl; + if ((right.size_raw() <= left.capacity() - left.size_raw()) || + (right.capacity() - right.size_raw() < left.size_raw())) + { + left.append(right); + return std::move(left); + } + else + { + right.insert(left, 0); + return std::move(right); + } +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(ustring16&& left, const B* const right) +{ + //std::cout << "MOVE operator+(&&, B*)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a null-terminated unicode string. +template +inline ustring16&& operator+(const B* const left, ustring16&& right) +{ + //std::cout << "MOVE operator+(B*, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(const string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(left, 0); + return std::move(right); +} + + +//! Appends a ustring16 and an Irrlicht string. +template +inline ustring16&& operator+(ustring16&& left, const string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(const std::basic_string& left, ustring16&& right) +{ + //std::cout << "MOVE operator+(&, &&)" << std::endl; + right.insert(core::ustring16(left), 0); + return std::move(right); +} + + +//! Appends a ustring16 and a std::basic_string. +template +inline ustring16&& operator+(ustring16&& left, const std::basic_string& right) +{ + //std::cout << "MOVE operator+(&&, &)" << std::endl; + left.append(right); + return std::move(left); +} +#endif + + +#ifndef USTRING_NO_STL +//! Writes a ustring16 to an ostream. +template +inline std::ostream& operator<<(std::ostream& out, const ustring16& in) +{ + out << in.toUTF8_s().c_str(); + return out; +} + +//! Writes a ustring16 to a wostream. +template +inline std::wostream& operator<<(std::wostream& out, const ustring16& in) +{ + out << in.toWCHAR_s().c_str(); + return out; +} +#endif + + +#ifndef USTRING_NO_STL + +namespace unicode +{ + +//! Hashing algorithm for hashing a ustring. Used for things like unordered_maps. +//! Algorithm taken from std::hash. +class hash : public std::unary_function +{ + public: + size_t operator()(const core::ustring& s) const + { + size_t ret = 2166136261U; + size_t index = 0; + size_t stride = 1 + s.size_raw() / 10; + + core::ustring::const_iterator i = s.begin(); + while (i != s.end()) + { + // TODO: Don't force u32 on an x64 OS. Make it agnostic. + ret = 16777619U * ret ^ (size_t)s[(u32)index]; + index += stride; + i += stride; + } + return (ret); + } +}; + +} // end namespace unicode + +#endif + +} // end namespace core +} // end namespace irr + +#endif diff --git a/src/cguittfont/xCGUITTFont.h b/src/cguittfont/xCGUITTFont.h new file mode 100644 index 0000000..9b7c953 --- /dev/null +++ b/src/cguittfont/xCGUITTFont.h @@ -0,0 +1,6 @@ +// A wrapper header to avoid hack with gcc and modifying +// the CGUITTFont files. + +#define USTRING_NO_STL +#include "irrUString.h" // must be included before CGUITTFont.h +#include "CGUITTFont.h" diff --git a/src/gettext.h b/src/gettext.h index 0e6ee0f..b058098 100644 --- a/src/gettext.h +++ b/src/gettext.h @@ -38,12 +38,18 @@ inline wchar_t* wgettext(const char *str) inline void changeCtype(const char *l) { - char *ret = NULL; - ret = setlocale(LC_CTYPE, l); - if(ret == NULL) - std::cout<<"locale could not be set"< rect(0, 0, 300, 30); rect = rect + v2s32(size.X/2-300/2, size.Y/2-30/2-25); - gui::IGUIElement *e = - Environment->addEditBox(text.c_str(), rect, true, this, 256); + //gui::IGUIElement *e = Environment->addEditBox(text.c_str(), rect, true, this, 256); + gui::IGUIElement *e = (gui::IGUIElement *) new gui::intlGUIEditBox(text.c_str(), true, Environment, this, 256, rect); + e->drop(); Environment->setFocus(e); irr::SEvent evt; diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp new file mode 100644 index 0000000..7ee2014 --- /dev/null +++ b/src/intlGUIEditBox.cpp @@ -0,0 +1,1481 @@ +// 11.11.2011 11:11 ValkaTR +// +// This is a copy of CGUIEditBox from the irrlicht, but with a +// fix in the OnEvent function, which doesn't allowed input of +// other keyboard layouts than latin-1 +// +// Characters like: ä ö ü õ ы й ю я ъ № € ° ... +// +// This fix is only needed for linux, because of a bug +// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht +// +// Also locale in the programm should not be changed to +// a "C", "POSIX" or whatever, it should be set to "", +// or XLookupString will return nothing for the international +// characters. +// +// From the "man setlocale": +// +// On startup of the main program, the portable "C" locale +// is selected as default. A program may be made +// portable to all locales by calling: +// +// setlocale(LC_ALL, ""); +// +// after program initialization.... +// + +// Copyright (C) 2002-2010 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "intlGUIEditBox.h" + +#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUISkin.h" +#include "IGUIEnvironment.h" +#include "IGUIFont.h" +#include "IVideoDriver.h" +//#include "rect.h" +//#include "irrlicht/os.cpp" +#include "porting.h" +//#include "Keycodes.h" + +/* + todo: + optional scrollbars + ctrl+left/right to select word + double click/ctrl click: word select + drag to select whole words, triple click to select line + optional? dragging selected text + numerical +*/ + +namespace irr +{ +namespace gui +{ + +//! constructor +intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border, + IGUIEnvironment* environment, IGUIElement* parent, s32 id, + const core::rect& rectangle) + : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), + Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), + OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), + Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), + WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), + PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), + CurrentTextRect(0,0,1,1), FrameRect(rectangle) +{ + #ifdef _DEBUG + setDebugName("intlCGUIEditBox"); + #endif + + Text = text; + + if (Environment) + Operator = Environment->getOSOperator(); + + if (Operator) + Operator->grab(); + + // this element can be tabbed to + setTabStop(true); + setTabOrder(-1); + + IGUISkin *skin = 0; + if (Environment) + skin = Environment->getSkin(); + if (Border && skin) + { + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + + breakText(); + + calculateScrollPos(); +} + + +//! destructor +intlGUIEditBox::~intlGUIEditBox() +{ + if (OverrideFont) + OverrideFont->drop(); + + if (Operator) + Operator->drop(); +} + + +//! Sets another skin independent font. +void intlGUIEditBox::setOverrideFont(IGUIFont* font) +{ + if (OverrideFont == font) + return; + + if (OverrideFont) + OverrideFont->drop(); + + OverrideFont = font; + + if (OverrideFont) + OverrideFont->grab(); + + breakText(); +} + + +//! Sets another color for the text. +void intlGUIEditBox::setOverrideColor(video::SColor color) +{ + OverrideColor = color; + OverrideColorEnabled = true; +} + + +//! Turns the border on or off +void intlGUIEditBox::setDrawBorder(bool border) +{ + Border = border; +} + + +//! Sets if the text should use the overide color or the color in the gui skin. +void intlGUIEditBox::enableOverrideColor(bool enable) +{ + OverrideColorEnabled = enable; +} + + +//! Enables or disables word wrap +void intlGUIEditBox::setWordWrap(bool enable) +{ + WordWrap = enable; + breakText(); +} + + +void intlGUIEditBox::updateAbsolutePosition() +{ + core::rect oldAbsoluteRect(AbsoluteRect); + IGUIElement::updateAbsolutePosition(); + if ( oldAbsoluteRect != AbsoluteRect ) + { + breakText(); + } +} + + +//! Checks if word wrap is enabled +bool intlGUIEditBox::isWordWrapEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return WordWrap; +} + + +//! Enables or disables newlines. +void intlGUIEditBox::setMultiLine(bool enable) +{ + MultiLine = enable; +} + + +//! Checks if multi line editing is enabled +bool intlGUIEditBox::isMultiLineEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return MultiLine; +} + + +void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) +{ + PasswordBox = passwordBox; + if (PasswordBox) + { + PasswordChar = passwordChar; + setMultiLine(false); + setWordWrap(false); + BrokenText.clear(); + } +} + + +bool intlGUIEditBox::isPasswordBox() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return PasswordBox; +} + + +//! Sets text justification +void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical) +{ + HAlign = horizontal; + VAlign = vertical; +} + + +//! called if an event happened. +bool intlGUIEditBox::OnEvent(const SEvent& event) +{ + if (IsEnabled) + { + + switch(event.EventType) + { + case EET_GUI_EVENT: + if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) + { + if (event.GUIEvent.Caller == this) + { + MouseMarking = false; + setTextMarkers(0,0); + } + } + break; + case EET_KEY_INPUT_EVENT: + { +#if defined(linux) + // ################################################################ + // ValkaTR: + // This part is the difference from the original CGUIEditBox + // It converts UTF-8 character into a UCS-2 (wchar_t) + wchar_t wc = L'_'; + mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) ); + + //printf( "char: %lc (%u) \r\n", wc, wc ); + + SEvent irrevent(event); + irrevent.KeyInput.Char = wc; + // ################################################################ + + if (processKey(irrevent)) + return true; +#else + if (processKey(event)) + return true; +#endif // defined(linux) + + break; + } + case EET_MOUSE_INPUT_EVENT: + if (processMouse(event)) + return true; + break; + default: + break; + } + } + + return IGUIElement::OnEvent(event); +} + + +bool intlGUIEditBox::processKey(const SEvent& event) +{ + if (!event.KeyInput.PressedDown) + return false; + + bool textChanged = false; + s32 newMarkBegin = MarkBegin; + s32 newMarkEnd = MarkEnd; + + // control shortcut handling + + if (event.KeyInput.Control) + { + // german backlash '\' entered with control + '?' + if ( event.KeyInput.Char == '\\' ) + { + inputChar(event.KeyInput.Char); + return true; + } + + switch(event.KeyInput.Key) + { + case KEY_KEY_A: + // select all + newMarkBegin = 0; + newMarkEnd = Text.size(); + break; + case KEY_KEY_C: + // copy to clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + core::stringc s; + s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(s.c_str()); + } + break; + case KEY_KEY_X: + // cut to the clipboard + if (!PasswordBox && Operator && MarkBegin != MarkEnd) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // copy + core::stringc sc; + sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + Operator->copyToClipboard(sc.c_str()); + + if (IsEnabled) + { + // delete + core::stringw s; + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + } + break; + case KEY_KEY_V: + if ( !IsEnabled ) + break; + + // paste from the clipboard + if (Operator) + { + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + // add new character + const c8* p = Operator->getTextFromClipboard(); + if (p) + { + if (MarkBegin == MarkEnd) + { + // insert text + core::stringw s = Text.subString(0, CursorPos); + s.append(p); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos += s.size(); + } + } + else + { + // replace text + + core::stringw s = Text.subString(0, realmbgn); + s.append(p); + s.append( Text.subString(realmend, Text.size()-realmend) ); + + if (!Max || s.size()<=Max) // thx to Fish FH for fix + { + Text = s; + s = p; + CursorPos = realmbgn + s.size(); + } + } + } + + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_HOME: + // move/highlight to start of text + if (event.KeyInput.Shift) + { + newMarkEnd = CursorPos; + newMarkBegin = 0; + CursorPos = 0; + } + else + { + CursorPos = 0; + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + case KEY_END: + // move/highlight to end of text + if (event.KeyInput.Shift) + { + newMarkBegin = CursorPos; + newMarkEnd = Text.size(); + CursorPos = 0; + } + else + { + CursorPos = Text.size(); + newMarkBegin = 0; + newMarkEnd = 0; + } + break; + default: + return false; + } + } + // default keyboard handling + else + switch(event.KeyInput.Key) + { + case KEY_END: + { + s32 p = Text.size(); + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); + if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) + p-=1; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_HOME: + { + + s32 p = 0; + if (WordWrap || MultiLine) + { + p = getLineFromPos(CursorPos); + p = BrokenTextPositions[p]; + } + + if (event.KeyInput.Shift) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + newMarkEnd = p; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + CursorPos = p; + BlinkStartTime = porting::getTimeMs(); + } + break; + case KEY_RETURN: + if (MultiLine) + { + inputChar(L'\n'); + return true; + } + else + { + sendGuiEvent( EGET_EDITBOX_ENTER ); + } + break; + case KEY_LEFT: + + if (event.KeyInput.Shift) + { + if (CursorPos > 0) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos-1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (CursorPos > 0) CursorPos--; + BlinkStartTime = porting::getTimeMs(); + break; + + case KEY_RIGHT: + if (event.KeyInput.Shift) + { + if (Text.size() > (u32)CursorPos) + { + if (MarkBegin == MarkEnd) + newMarkBegin = CursorPos; + + newMarkEnd = CursorPos+1; + } + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + if (Text.size() > (u32)CursorPos) CursorPos++; + BlinkStartTime = porting::getTimeMs(); + break; + case KEY_UP: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); + if (lineNo > 0) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo-1].size() < cp) + CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo-1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + case KEY_DOWN: + if (MultiLine || (WordWrap && BrokenText.size() > 1) ) + { + s32 lineNo = getLineFromPos(CursorPos); + s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); + if (lineNo < (s32)BrokenText.size()-1) + { + s32 cp = CursorPos - BrokenTextPositions[lineNo]; + if ((s32)BrokenText[lineNo+1].size() < cp) + CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; + else + CursorPos = BrokenTextPositions[lineNo+1] + cp; + } + + if (event.KeyInput.Shift) + { + newMarkBegin = mb; + newMarkEnd = CursorPos; + } + else + { + newMarkBegin = 0; + newMarkEnd = 0; + } + + } + else + { + return false; + } + break; + + case KEY_BACK: + if ( !this->IsEnabled ) + break; + + if (Text.size()) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text behind cursor + if (CursorPos>0) + s = Text.subString(0, CursorPos-1); + else + s = L""; + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + --CursorPos; + } + + if (CursorPos < 0) + CursorPos = 0; + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + case KEY_DELETE: + if ( !this->IsEnabled ) + break; + + if (Text.size() != 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // delete marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + + CursorPos = realmbgn; + } + else + { + // delete text before cursor + s = Text.subString(0, CursorPos); + s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); + Text = s; + } + + if (CursorPos > (s32)Text.size()) + CursorPos = (s32)Text.size(); + + BlinkStartTime = porting::getTimeMs(); + newMarkBegin = 0; + newMarkEnd = 0; + textChanged = true; + } + break; + + case KEY_ESCAPE: + case KEY_TAB: + case KEY_SHIFT: + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + case KEY_F13: + case KEY_F14: + case KEY_F15: + case KEY_F16: + case KEY_F17: + case KEY_F18: + case KEY_F19: + case KEY_F20: + case KEY_F21: + case KEY_F22: + case KEY_F23: + case KEY_F24: + // ignore these keys + return false; + + default: + inputChar(event.KeyInput.Char); + return true; + } + + // Set new text markers + setTextMarkers( newMarkBegin, newMarkEnd ); + + // break the text if it has changed + if (textChanged) + { + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + } + + calculateScrollPos(); + + return true; +} + + +//! draws the element and its children +void intlGUIEditBox::draw() +{ + if (!IsVisible) + return; + + const bool focus = Environment->hasFocus(this); + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + FrameRect = AbsoluteRect; + + // draw the border + + if (Border) + { + skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW), + false, true, FrameRect, &AbsoluteClippingRect); + + FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1; + FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; + } + core::rect localClipRect = FrameRect; + localClipRect.clipAgainst(AbsoluteClippingRect); + + // draw the text + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + s32 cursorLine = 0; + s32 charcursorpos = 0; + + if (font) + { + if (LastBreakFont != font) + { + breakText(); + } + + // calculate cursor pos + + core::stringw *txtLine = &Text; + s32 startPos = 0; + + core::stringw s, s2; + + // get mark position + const bool ml = (!PasswordBox && (WordWrap || MultiLine)); + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; + const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; + const s32 lineCount = ml ? BrokenText.size() : 1; + + // Save the override color information. + // Then, alter it if the edit box is disabled. + const bool prevOver = OverrideColorEnabled; + const video::SColor prevColor = OverrideColor; + + if (Text.size()) + { + if (!IsEnabled && !OverrideColorEnabled) + { + OverrideColorEnabled = true; + OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + } + + for (s32 i=0; i < lineCount; ++i) + { + setTextRect(i); + + // clipping test - don't draw anything outside the visible area + core::rect c = localClipRect; + c.clipAgainst(CurrentTextRect); + if (!c.isValid()) + continue; + + // get current line + if (PasswordBox) + { + if (BrokenText.size() != 1) + { + BrokenText.clear(); + BrokenText.push_back(core::stringw()); + } + if (BrokenText[0].size() != Text.size()) + { + BrokenText[0] = Text; + for (u32 q = 0; q < Text.size(); ++q) + { + BrokenText[0] [q] = PasswordChar; + } + } + txtLine = &BrokenText[0]; + startPos = 0; + } + else + { + txtLine = ml ? &BrokenText[i] : &Text; + startPos = ml ? BrokenTextPositions[i] : 0; + } + + + // draw normal text + font->draw(txtLine->c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + + // draw mark and marked text + if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) + { + + s32 mbegin = 0, mend = 0; + s32 lineStartPos = 0, lineEndPos = txtLine->size(); + + if (i == hlineStart) + { + // highlight start is on this line + s = txtLine->subString(0, realmbgn - startPos); + mbegin = font->getDimension(s.c_str()).Width; + + // deal with kerning + mbegin += font->getKerningWidth( + &((*txtLine)[realmbgn - startPos]), + realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); + + lineStartPos = realmbgn - startPos; + } + if (i == hlineStart + hlineCount - 1) + { + // highlight end is on this line + s2 = txtLine->subString(0, realmend - startPos); + mend = font->getDimension(s2.c_str()).Width; + lineEndPos = (s32)s2.size(); + } + else + mend = font->getDimension(txtLine->c_str()).Width; + + CurrentTextRect.UpperLeftCorner.X += mbegin; + CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; + + // draw mark + skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); + + // draw marked text + s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); + + if (s.size()) + font->draw(s.c_str(), CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), + false, true, &localClipRect); + + } + } + + // Return the override color information to its previous settings. + OverrideColorEnabled = prevOver; + OverrideColor = prevColor; + } + + // draw cursor + + if (WordWrap || MultiLine) + { + cursorLine = getLineFromPos(CursorPos); + txtLine = &BrokenText[cursorLine]; + startPos = BrokenTextPositions[cursorLine]; + } + s = txtLine->subString(0,CursorPos-startPos); + charcursorpos = font->getDimension(s.c_str()).Width + + font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); + + if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) + { + setTextRect(cursorLine); + CurrentTextRect.UpperLeftCorner.X += charcursorpos; + + font->draw(L"_", CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + } + } + + // draw children + IGUIElement::draw(); +} + + +//! Sets the new caption of this element. +void intlGUIEditBox::setText(const wchar_t* text) +{ + Text = text; + if (u32(CursorPos) > Text.size()) + CursorPos = Text.size(); + HScrollPos = 0; + breakText(); +} + + +//! Enables or disables automatic scrolling with cursor position +//! \param enable: If set to true, the text will move around with the cursor position +void intlGUIEditBox::setAutoScroll(bool enable) +{ + AutoScroll = enable; +} + + +//! Checks to see if automatic scrolling is enabled +//! \return true if automatic scrolling is enabled, false if not +bool intlGUIEditBox::isAutoScrollEnabled() const +{ + _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; + return AutoScroll; +} + + +//! Gets the area of the text in the edit box +//! \return Returns the size in pixels of the text +core::dimension2du intlGUIEditBox::getTextDimension() +{ + core::rect ret; + + setTextRect(0); + ret = CurrentTextRect; + + for (u32 i=1; i < BrokenText.size(); ++i) + { + setTextRect(i); + ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); + ret.addInternalPoint(CurrentTextRect.LowerRightCorner); + } + + return core::dimension2du(ret.getSize()); +} + + +//! Sets the maximum amount of characters which may be entered in the box. +//! \param max: Maximum amount of characters. If 0, the character amount is +//! infinity. +void intlGUIEditBox::setMax(u32 max) +{ + Max = max; + + if (Text.size() > Max && Max != 0) + Text = Text.subString(0, Max); +} + + +//! Returns maximum amount of characters, previously set by setMax(); +u32 intlGUIEditBox::getMax() const +{ + return Max; +} + + +bool intlGUIEditBox::processMouse(const SEvent& event) +{ + switch(event.MouseInput.Event) + { + case irr::EMIE_LMOUSE_LEFT_UP: + if (Environment->hasFocus(this)) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + if (MouseMarking) + { + setTextMarkers( MarkBegin, CursorPos ); + } + MouseMarking = false; + calculateScrollPos(); + return true; + } + break; + case irr::EMIE_MOUSE_MOVED: + { + if (MouseMarking) + { + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers( MarkBegin, CursorPos ); + calculateScrollPos(); + return true; + } + } + break; + case EMIE_LMOUSE_PRESSED_DOWN: + if (!Environment->hasFocus(this)) + { + BlinkStartTime = porting::getTimeMs(); + MouseMarking = true; + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + setTextMarkers(CursorPos, CursorPos ); + calculateScrollPos(); + return true; + } + else + { + if (!AbsoluteClippingRect.isPointInside( + core::position2d(event.MouseInput.X, event.MouseInput.Y))) + { + return false; + } + else + { + // move cursor + CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + + s32 newMarkBegin = MarkBegin; + if (!MouseMarking) + newMarkBegin = CursorPos; + + MouseMarking = true; + setTextMarkers( newMarkBegin, CursorPos); + calculateScrollPos(); + return true; + } + } + default: + break; + } + + return false; +} + + +s32 intlGUIEditBox::getCursorPos(s32 x, s32 y) +{ + IGUIFont* font = OverrideFont; + IGUISkin* skin = Environment->getSkin(); + if (!OverrideFont) + font = skin->getFont(); + + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + + core::stringw *txtLine=0; + s32 startPos=0; + x+=3; + + for (u32 i=0; i < lineCount; ++i) + { + setTextRect(i); + if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) + y = CurrentTextRect.UpperLeftCorner.Y; + if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y ) + y = CurrentTextRect.LowerRightCorner.Y; + + // is it inside this region? + if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) + { + // we've found the clicked line + txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text; + startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; + break; + } + } + + if (x < CurrentTextRect.UpperLeftCorner.X) + x = CurrentTextRect.UpperLeftCorner.X; + + s32 idx = font->getCharacterFromPos(Text.c_str(), x - CurrentTextRect.UpperLeftCorner.X); + + // click was on or left of the line + if (idx != -1) + return idx + startPos; + + // click was off the right edge of the line, go to end. + return txtLine->size() + startPos; +} + + +//! Breaks the single text line. +void intlGUIEditBox::breakText() +{ + IGUISkin* skin = Environment->getSkin(); + + if ((!WordWrap && !MultiLine) || !skin) + return; + + BrokenText.clear(); // need to reallocate :/ + BrokenTextPositions.set_used(0); + + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); + + if (!font) + return; + + LastBreakFont = font; + + core::stringw line; + core::stringw word; + core::stringw whitespace; + s32 lastLineStart = 0; + s32 size = Text.size(); + s32 length = 0; + s32 elWidth = RelativeRect.getWidth() - 6; + wchar_t c; + + for (s32 i=0; igetDimension(whitespace.c_str()).Width; + s32 worldlgth = font->getDimension(word.c_str()).Width; + + if (WordWrap && length + worldlgth + whitelgth > elWidth) + { + // break to next line + length = worldlgth; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i - (s32)word.size(); + line = word; + } + else + { + // add word to line + line += whitespace; + line += word; + length += whitelgth + worldlgth; + } + + word = L""; + whitespace = L""; + } + + whitespace += c; + + // compute line break + if (lineBreak) + { + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); + lastLineStart = i+1; + line = L""; + word = L""; + whitespace = L""; + length = 0; + } + } + else + { + // yippee this is a word.. + word += c; + } + } + + line += whitespace; + line += word; + BrokenText.push_back(line); + BrokenTextPositions.push_back(lastLineStart); +} + + +void intlGUIEditBox::setTextRect(s32 line) +{ + core::dimension2du d; + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + + if (!font) + return; + + // get text dimension + const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; + if (WordWrap || MultiLine) + { + d = font->getDimension(BrokenText[line].c_str()); + } + else + { + d = font->getDimension(Text.c_str()); + d.Height = AbsoluteRect.getHeight(); + } + d.Height += font->getKerningHeight(); + + // justification + switch (HAlign) + { + case EGUIA_CENTER: + // align to h centre + CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2); + CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2); + break; + case EGUIA_LOWERRIGHT: + // align to right edge + CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width; + CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth(); + break; + default: + // align to left edge + CurrentTextRect.UpperLeftCorner.X = 0; + CurrentTextRect.LowerRightCorner.X = d.Width; + + } + + switch (VAlign) + { + case EGUIA_CENTER: + // align to v centre + CurrentTextRect.UpperLeftCorner.Y = + (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + break; + case EGUIA_LOWERRIGHT: + // align to bottom edge + CurrentTextRect.UpperLeftCorner.Y = + FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + break; + default: + // align to top edge + CurrentTextRect.UpperLeftCorner.Y = d.Height*line; + break; + } + + CurrentTextRect.UpperLeftCorner.X -= HScrollPos; + CurrentTextRect.LowerRightCorner.X -= HScrollPos; + CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; + CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; + + CurrentTextRect += FrameRect.UpperLeftCorner; + +} + + +s32 intlGUIEditBox::getLineFromPos(s32 pos) +{ + if (!WordWrap && !MultiLine) + return 0; + + s32 i=0; + while (i < (s32)BrokenTextPositions.size()) + { + if (BrokenTextPositions[i] > pos) + return i-1; + ++i; + } + return (s32)BrokenTextPositions.size() - 1; +} + + +void intlGUIEditBox::inputChar(wchar_t c) +{ + if (!IsEnabled) + return; + + if (c != 0) + { + if (Text.size() < Max || Max == 0) + { + core::stringw s; + + if (MarkBegin != MarkEnd) + { + // replace marked text + const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; + const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + + s = Text.subString(0, realmbgn); + s.append(c); + s.append( Text.subString(realmend, Text.size()-realmend) ); + Text = s; + CursorPos = realmbgn+1; + } + else + { + // add new character + s = Text.subString(0, CursorPos); + s.append(c); + s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + Text = s; + ++CursorPos; + } + + BlinkStartTime = porting::getTimeMs(); + setTextMarkers(0, 0); + } + } + breakText(); + sendGuiEvent(EGET_EDITBOX_CHANGED); + calculateScrollPos(); +} + + +void intlGUIEditBox::calculateScrollPos() +{ + if (!AutoScroll) + return; + + // calculate horizontal scroll position + s32 cursLine = getLineFromPos(CursorPos); + setTextRect(cursLine); + + // don't do horizontal scrolling when wordwrap is enabled. + if (!WordWrap) + { + // get cursor position + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + if (!font) + return; + + core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; + s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; + + s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + + font->getDimension(txtLine->subString(0, cPos).c_str()).Width; + + s32 cEnd = cStart + font->getDimension(L"_ ").Width; + + if (FrameRect.LowerRightCorner.X < cEnd) + HScrollPos = cEnd - FrameRect.LowerRightCorner.X; + else if (FrameRect.UpperLeftCorner.X > cStart) + HScrollPos = cStart - FrameRect.UpperLeftCorner.X; + else + HScrollPos = 0; + + // todo: adjust scrollbar + } + + // vertical scroll position + if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos; + + else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) + VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; + else + VScrollPos = 0; + + // todo: adjust scrollbar +} + +//! set text markers +void intlGUIEditBox::setTextMarkers(s32 begin, s32 end) +{ + if ( begin != MarkBegin || end != MarkEnd ) + { + MarkBegin = begin; + MarkEnd = end; + sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); + } +} + +//! send some gui event to parent +void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type) +{ + if ( Parent ) + { + SEvent e; + e.EventType = EET_GUI_EVENT; + e.GUIEvent.Caller = this; + e.GUIEvent.Element = 0; + e.GUIEvent.EventType = type; + + Parent->OnEvent(e); + } +} + +//! Writes attributes of the element. +void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const +{ + // IGUIEditBox::serializeAttributes(out,options); + + out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); + out->addColor ("OverrideColor", OverrideColor); + // out->addFont("OverrideFont",OverrideFont); + out->addInt ("MaxChars", Max); + out->addBool ("WordWrap", WordWrap); + out->addBool ("MultiLine", MultiLine); + out->addBool ("AutoScroll", AutoScroll); + out->addBool ("PasswordBox", PasswordBox); + core::stringw ch = L" "; + ch[0] = PasswordChar; + out->addString("PasswordChar", ch.c_str()); + out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames); + out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames); + + IGUIEditBox::serializeAttributes(out,options); +} + + +//! Reads attributes of the element +void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0) +{ + IGUIEditBox::deserializeAttributes(in,options); + + setOverrideColor(in->getAttributeAsColor("OverrideColor")); + enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); + setMax(in->getAttributeAsInt("MaxChars")); + setWordWrap(in->getAttributeAsBool("WordWrap")); + setMultiLine(in->getAttributeAsBool("MultiLine")); + setAutoScroll(in->getAttributeAsBool("AutoScroll")); + core::stringw ch = in->getAttributeAsStringW("PasswordChar"); + + if (!ch.size()) + setPasswordBox(in->getAttributeAsBool("PasswordBox")); + else + setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]); + + setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames), + (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames)); + + // setOverrideFont(in->getAttributeAsFont("OverrideFont")); +} + + +} // end namespace gui +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_GUI_ + diff --git a/src/intlGUIEditBox.h b/src/intlGUIEditBox.h new file mode 100644 index 0000000..26b1819 --- /dev/null +++ b/src/intlGUIEditBox.h @@ -0,0 +1,160 @@ +// Copyright (C) 2002-2010 Nikolaus Gebhardt +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ +#define __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ + +#include "IrrCompileConfig.h" +//#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIEditBox.h" +#include "irrArray.h" +#include "IOSOperator.h" + +namespace irr +{ +namespace gui +{ + class intlGUIEditBox : public IGUIEditBox + { + public: + + //! constructor + intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, + IGUIElement* parent, s32 id, const core::rect& rectangle); + + //! destructor + virtual ~intlGUIEditBox(); + + //! Sets another skin independent font. + virtual void setOverrideFont(IGUIFont* font=0); + + //! Sets another color for the text. + virtual void setOverrideColor(video::SColor color); + + //! Sets if the text should use the overide color or the + //! color in the gui skin. + virtual void enableOverrideColor(bool enable); + + //! Turns the border on or off + virtual void setDrawBorder(bool border); + + //! Enables or disables word wrap for using the edit box as multiline text editor. + virtual void setWordWrap(bool enable); + + //! Checks if word wrap is enabled + //! \return true if word wrap is enabled, false otherwise + virtual bool isWordWrapEnabled() const; + + //! Enables or disables newlines. + /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, + instead a newline character will be inserted. */ + virtual void setMultiLine(bool enable); + + //! Checks if multi line editing is enabled + //! \return true if mult-line is enabled, false otherwise + virtual bool isMultiLineEnabled() const; + + //! Enables or disables automatic scrolling with cursor position + //! \param enable: If set to true, the text will move around with the cursor position + virtual void setAutoScroll(bool enable); + + //! Checks to see if automatic scrolling is enabled + //! \return true if automatic scrolling is enabled, false if not + virtual bool isAutoScrollEnabled() const; + + //! Gets the size area of the text in the edit box + //! \return Returns the size in pixels of the text + virtual core::dimension2du getTextDimension(); + + //! Sets text justification + virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical); + + //! called if an event happened. + virtual bool OnEvent(const SEvent& event); + + //! draws the element and its children + virtual void draw(); + + //! Sets the new caption of this element. + virtual void setText(const wchar_t* text); + + //! Sets the maximum amount of characters which may be entered in the box. + //! \param max: Maximum amount of characters. If 0, the character amount is + //! infinity. + virtual void setMax(u32 max); + + //! Returns maximum amount of characters, previously set by setMax(); + virtual u32 getMax() const; + + //! Sets whether the edit box is a password box. Setting this to true will + /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x + \param passwordBox: true to enable password, false to disable + \param passwordChar: the character that is displayed instead of letters */ + virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*'); + + //! Returns true if the edit box is currently a password box. + virtual bool isPasswordBox() const; + + //! Updates the absolute position, splits text if required + virtual void updateAbsolutePosition(); + + //! Writes attributes of the element. + virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const; + + //! Reads attributes of the element + virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options); + + protected: + //! Breaks the single text line. + void breakText(); + //! sets the area of the given line + void setTextRect(s32 line); + //! returns the line number that the cursor is on + s32 getLineFromPos(s32 pos); + //! adds a letter to the edit box + void inputChar(wchar_t c); + //! calculates the current scroll position + void calculateScrollPos(); + //! send some gui event to parent + void sendGuiEvent(EGUI_EVENT_TYPE type); + //! set text markers + void setTextMarkers(s32 begin, s32 end); + + bool processKey(const SEvent& event); + bool processMouse(const SEvent& event); + s32 getCursorPos(s32 x, s32 y); + + bool MouseMarking; + bool Border; + bool OverrideColorEnabled; + s32 MarkBegin; + s32 MarkEnd; + + video::SColor OverrideColor; + gui::IGUIFont *OverrideFont, *LastBreakFont; + IOSOperator* Operator; + + u32 BlinkStartTime; + s32 CursorPos; + s32 HScrollPos, VScrollPos; // scroll position in characters + u32 Max; + + bool WordWrap, MultiLine, AutoScroll, PasswordBox; + wchar_t PasswordChar; + EGUI_ALIGNMENT HAlign, VAlign; + + core::array< core::stringw > BrokenText; + core::array< s32 > BrokenTextPositions; + + core::rect CurrentTextRect, FrameRect; // temporary values + }; + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ +#endif // __C_GUI_EDIT_BOX_H_INCLUDED__ + diff --git a/src/main.cpp b/src/main.cpp index b959d8c..68590be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -440,6 +440,8 @@ Doing currently: #include "profiler.h" #include "log.h" +#include "xCGUITTFont.h" + // This makes textures ITextureSource *g_texturesource = NULL; @@ -1107,10 +1109,15 @@ int main(int argc, char *argv[]) log_register_thread("main"); + // This enables internatonal characters input + if( setlocale(LC_ALL, "") == NULL ) + { + fprintf( stderr, "%s: warning: could not set default locale\n", argv[0] ); + } + // Set locale. This is for forcing '.' as the decimal point. - std::locale::global(std::locale("C")); - // This enables printing all characters in bitmap font - setlocale(LC_CTYPE, "en_US"); + std::locale::global(std::locale(std::locale(""), "C", std::locale::numeric)); + setlocale(LC_NUMERIC, "C"); /* Parse command line @@ -1458,12 +1465,18 @@ int main(int argc, char *argv[]) guienv = device->getGUIEnvironment(); gui::IGUISkin* skin = guienv->getSkin(); - gui::IGUIFont* font = guienv->getFont(getTexturePath("fontlucida.png").c_str()); + std::string font_path = porting::getDataPath("font.ttf"); + gui::IGUIFont *font = gui::CGUITTFont::createTTFont(guienv, font_path.c_str(), 13); if(font) + { skin->setFont(font); + } else - errorstream<<"WARNING: Font file was not found." - " Using default font."<getFont(getTexturePath("fontlucida.png").c_str()); + } + // If font was not found, this will get us one font = skin->getFont(); assert(font);