From f54ca2f81ac8e1662aad6a6cde2ea85384380460 Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Sun, 13 Sep 2026 11:57:38 -0400 Subject: [PATCH] IME: map commits against the whole buffer; drop the renderer-side model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The renderer kept a mirror of the pushed IME snippet (the 'IME model') to translate commit positions, but it transiently desynced from the buffer on fling/tap sequences (observed as a few-byte mapping drift on both the x86_64 emulator and the ARM phone), corrupting text. The model string also sat on the main goroutine next to the JNI render path, where the app observed states that were impossible for Go memory (string contents changing between reads microseconds apart), pointing at corruption in the native bridge layer. Restructure along the lines of the Android InputConnection contract and Gio's own reference editor (widget/editor.go): - Commits carry absolute file runes (the pushed snippet's coordinate space) straight to the logic goroutine, which maps them to bytes against the WHOLE buffer (runeToByteWhole, an 8 KiB-step scan). Scrolling moves the window, not the buffer, so the mapping is exact mid-fling by construction — no mirror to desync. - Drift guard in HandleIMECommit: a small commit (range <= 2 runes) is always anchored at the caret the IME was last told about; if the IME reports it ending elsewhere, its snippet text is stale (a dropped restartInput, as Gboard does during flings) and its position is in the stale text's coordinates — snap the commit to the cursor, the only position it cannot drift from. - FlushIME simplifies to: push the snippet when the frame's (context+window) text differs from the last push (gioui dedupes against its own cache), force the selection re-push in the same frame. After a commit the frame text equals what the IME already holds locally, so the restart is naturally suppressed; a fling re-anchors the IME once per text change. - Remove the renderer model (adoptFrame/ModelTranslate/ ApplyIMEEdit/ApplyIMEKey/IMECaret), the IME freeze/settle machinery (IMEFrozen, markIMEScrollActive, imeSettleChan), and the window-relative imeRuneToByte. Also fixed along the way (both found while chasing the corruption): - real.ReadFileAt: loop over short reads. A single ReadAt on Android FUSE can return a short read, silently truncating a chunk and shifting every byte offset after it. - logic: a late lazy-chunk result no longer clobbers a buffer that SetContent has already fully loaded. - e2e: large-file IME test (1.6 MB file, fling + commit). - app icon (scripts/make_icon.py + cmd/pad/appicon.png) so gogio builds the mipmap/adaptive icon set. Verified: go vet + staticcheck, go test -race (all packages), and the emulator scenario loop (open moby excerpt, fling to mid-file, tap, type 'a', byte-compare the saved file) 75/75 clean. --- cmd/pad/appicon.png | Bin 0 -> 126159 bytes cmd/pad/main.go | 35 +- go.mod | 2 +- internal/editor/frame.go | 1 + internal/editor/ime_range_test.go | 117 +++++- internal/editor/logic.go | 26 +- internal/editor/state.go | 344 ++++++++++++++---- internal/io/pool/real/filesystem.go | 21 +- internal/test/e2e/real_file_fuzz_test.go | 44 ++- internal/test/e2e/real_file_large_ime_test.go | 219 +++++++++++ .../e2e/real_file_scroll_selection_test.go | 16 +- internal/test/e2e/restore_test.go | 16 +- internal/ui/element.go | 107 ++---- internal/ui/render.go | 141 ++++++- scripts/make_icon.py | 195 ++++++++++ 15 files changed, 1096 insertions(+), 188 deletions(-) create mode 100644 cmd/pad/appicon.png create mode 100644 internal/test/e2e/real_file_large_ime_test.go create mode 100644 scripts/make_icon.py diff --git a/cmd/pad/appicon.png b/cmd/pad/appicon.png new file mode 100644 index 0000000000000000000000000000000000000000..e96d91bafc65a02a3c0415bfa5b78c4d79256daf GIT binary patch literal 126159 zcmeFZ^$=viWwgAZM=<-}9;%+?VV4IT<36j`+0k6`7Fe-N^n_+ZPejT}%bll9N&Nb zu95s4(f;pC^V9eLx7qy+3H|@JmkIyZ{3wh6$>jf8#QzN9zk>LmLHy4k0N{VK;eWH? z|Nq#qXwLzv!G-b${1hku-AD?46XAXjzMr-OH26^J@?2iMCjcOh-wn2{%K_H-;$B*k zy*0!D(9HLEoC~iJbhEbzc=uDkxfO2oLmWKpCn770qz1%^$NFBbYq*u`-tDp1GUFHZ9O2LN8v#S)=dBu05B1+ z8o!Y_L!#^1=|rpPrs>`WCoIl;@TQdBpdGGeric7N>1GzO#XDt~(7pmVkpOSo$6mQ{ znTX;#?vd6P%6# zP`cfIUR?p)$WzF`X$%rLh8%L~J0w6a2oHLNMwm_p1nprw_G3hsbWZ$1j6w=n#urL| zT9Qc?L0br~@{e5}R=uz%CS$@{$K^pJT_fOU;W9qe`c9w36}&v05vfM(PJ z-CZC4b+rFpoaFK1P0fY}h|w7-G%x@Cxu4Gne>`CMrIMQ}ZJVJ3-M5dD7wYLagB#A= z>0BZ=4QL-XVKH)I37yZiaTA0|->j z%|J>AlNLNs3DQ5#3*p6V`Kj=6#4n6&_$ck+!|G<-0}L1jPFx8Hofj9=~!hOq}Hf6 zT1{m^?n0y#Fb`L2+FaGF-Q0&g#P^KWvyI__Z5>XeN7>N-c}z*FD^Y-Ds+{MSYhf3N zLaFlslwg|J%;a1yo0<-ce<{(50zim$1}>1cKZ`@82&9KxqNe zeexOWQw_yQi;aGOc@m>`jlR#-_(TX};oHc7#jDCbOd*mG9lXWjigW&$I37w*KxKm_ zBOPQFhbY%T2&N!@L;juc7D!yo4Jf3bOaFi?;NA=w-D}E)VPJ5%(x-;Cr7kDDbNIxW zRnohe4<5m`lz5Z7^fq!=Hxns~0_dBXSP+LZpARSrNnfPR(dTfaV_Ww3Jb2}>%HxI0 zH+=wNUQrNsXdp(rZk&tCW;g9rk3RCRGj$jNHAJADbc@p-bXDxq-W^K`Jt$gO4j-+3 zK$n(1L%I5Ox6JnKCc|E8XYMv8SdB;b>lhHviFGCZE$^de3lbfBJCja?#DE3Yn||mF z@V@L_A=bVidJuC=Hhu&SCYp~k>}JX}^YhC(bm@2*Tw%qTdmDHmB1Avt@VEV=d4R4Y zNNhmq8(ivU1v0%2-!edvcE9}+jw{H*R>Q*3Qt_$B5}J~r*D04H%nL-hp&a4?w(rd7 z$y@X{H4uRjW}{En^GPiI5@#cT;Iz)G$AbwDP4e|#1aU7`OpN} zzfMPcbbP{i6hOB`2xW1(n<=`s2YlTNHnB0+4c&3c#zqM;?p3s`FZlYNiz8Lv9I=mN z4@|$=a(E9U@1jTfU>|<&GuP-pz!mBjNV^@ZuM{YXj~@0}qP(Be6yIs_2l?6wiP z%1B8PFQ@&|e9Qkn7Kw+PG6}NOW#o)&bT{#0PM2>VX`KdiE6u9Qom*XMd-xCg5MAs) zxj#PhyYmZ3mc+L_<7A5fJYTUYH1-WDx&>K^lZ%K;f zsUO?f!H)Exmp-D&+3ENsYtlIk$e&N6BV#Rn<-$VU#0}!LSpC{(mDHRU7&2jG%_)#b zP7QpOxwu3OBX}#bdt(3NEskj(_3#sU0MSLZXs6swR;}<=+HG=hPp~j)gfgh#YMOk;TQgg5-0g1s@@&Ol)_i5C?sQ4TAhC(Fi(xcDQGCaUf zofqxI-*-rD&5QTZ6HZ@=+Qxl5aX$ul0N8u3&3tzhs~)i0_lx)fB+3k<81* z1;%Bm;(Obu3%6RNnvV$DRiMNIQTn?M$wg!P3u}isN}l3>hGGxID1f#Bjj=)xQpl+M zfKnj@^ym6lJTcW-!D01S*-J}$fi`AGyQ4IV+D0Z?o}nJNxILY|R;7=e=I4^8n%9F1 zn23PR;yNhH<3VW}fnOZxvI?jLAY^8fVPk|3xOkNh0MFsMs_f_1oc93hc9tL@doT`7 zeeHUH7eveeVcx%X1V&lxc2dnhvEu?s=Q_re&J-wGS1egmZWHijxIr)K2+1x>@8Mg$UA%JIsH0z;k$GgC=2DQQNJqqB30!~_- ze!Q3P4Bt+~5~7B;M>?tP`VEWk5HP3%;1eQhk$4i0p@Xv!Abmn+4Z$LTIh>mD-6i98 zVR0~b^VW?LI3S0ZRv_`@tiQ`%0S2|oV$n2kgKkIbeo1d351UA<>(2u3wag7|uP)w> zJ?O*If0M_=t0xNuB&1yRQ=BhB(k!uzHf{U0K!*u}Pj;tYXXDf70c-GZu9*~n5P09P%oI5QTD6cQfj6;ICi*KCY@^gHXfxaO+UU~SzoksKbSQ=U z$%MMS4J@|(6YNl=$%f*O8VbyT0!l@nk;|e7k@Oy;_cPN%3F%4wFehUKGhFE`uV`-NFZg1vIB3 z4EH)Ay|wd+NOY9Q(Re`)`>)uwgn@q?x-(-QCFyqSe4I;nZp0|-S1oVe2Fy_iENz9drut}16tr}YeQP(5AQ zbKWZac>)}mz&Ii3Q_?;yxjwi}uetmTWH@V?g1pN+C9wx;dKd+ccTD*J*{fV;c690E z`!7+P0)R-ri6ao%5X)g4(cQCyto|qxoq0CoNJF&T;AG zpCt@+D^Ol9KhEi%&wVVN?_F0o1K)<>U5VHzNgEB!b1us zC!lNL284&yjynA8#0YMLR=}ZkiVE%HT{a*zr!g)On znnMQB)6bESA}cj%sk41gF0-8Ay^#i%rbh8`ob|+tCO+!xi+_WZ`z_FnEsv{66iYw; zaL~7SUA*q5@gPiS@^98Hzo3;JFGxPo5{0omin6%tJ<8~wTkY0#&^6+y>*~bP#ta4M zMhDGF+uA!2JBQ`ET!7+mP0WI;vM^{sgjuI%^$tou|p+}m(znjxST zCV2?`bJic4LqsQEaOyPimUCspr=;%t_B|4jTokU>EI#4YECLi`GdkNon@^uw5#yby zIx+WbL-h*xD?(O5h!Z+NZCZmQTgRnqlX%5O4KR0T2oVh_bNM%Yq$O9LL_xBhv4#ex z+mHMQ;<^xlG8`ut{sH$naE{@1!|hb|VftswFbl2mxeC$8LYosBFtr0%?Hz4EcuHUz zWi7)P`9>~ho<_m5Z)v=I0IGcPc4IyS+jl;nDMd=a_D<8^y3gD0-|9HYO)My31jc_|!*GIPW$TmB4uqI>d@9Z|}wal6q-3wL>TDxwPdEMrsMmgn$`+!id)D}ZTAVuq?ylUHm+tpd-2eVd8Acqi4|HQ?XgSVs- zvtt!tL1aF$d+r&q@vgeMEe+(Xl)*3+MN7;;-was&1mca`^I{Tz#zZdG-}+1O;kgOv zWdtHde`j-zCgisYuQb%OxO6g_jQ;7@+~P{7SbDJ2T`<_(&c{>Gzcy1+`1 zLRs%4^mf96_GP_OsIXqV)jOP(B3R&SBY^s!TE#xb2d@8Uqjd%Ha~~ZgFFq$syg73%wPMW-rX<< z#mfvQX=ybf%}Zw%m`GYP7yU*63JQ&O#~V%Je>< z_N9$KA;}2b{3XyG_BcD98)d`B>!i*q*9t1@td=*X*1&iHdep!Mj#{S8x`Qx@Or$(c zgvww`p4{eEwGbc@-dS@TB#e{({GEDHiIb+wE>autI)NZ%vcA&%$y^khL6^;k$m}^P zz?L$5vzW*dB@fycvq4X)jlD#2d!)-Oi20hJGZ%5=c;lNwBl=71ZjtCK$dELI*m3q(O3emIpwY& zk7|V8aKU&Z;=VD&$TY7^iLq!t2+zJjEqv?gDkk^6t|=svnvFF>lH$P=Swa76st6`` z<(C*ru|2?slAz&JSk`lp%R#dSf~doF;}nMsXW@~~Fj#+jy7sn(#+wU>T(an_f+l>@ zV6`Bxl$9?wlg;9N`*h^+5fgsK$J=(_Qum}LtV1uGI$p|V<{f+8GU|~(({IQIZR6MK z1R1}N)^oonEa6^S6#W$X()HEH>skgbk2HR?KI7MJgb&c*^>MPF!)M%*<4-@|ZAXo| zVecoSeM{L|6tc^Gh)QqyEK=4bf=@u2;e|;m=|W%({ir@FF0GL!-2V^NNv%q`;q3RL54n+A}-F4aU(u~B!Z(q|YNmg==% zSOH_ed-lm|*q*A}i2t_`f0%tTCq{>5>1)w22e&#tyJu|BMQRVBAOZ(XTOgFOgHp;- z%>d8n=2b8OPO?($ub`1dU|$qO4=<4ErMrJ^MjfrG23uX_GTN(|Gm_OnDv@KiJ}waF zJ5QEEirfPiTJULm8?Hbz7 zc3RB?dfDz2d*sko$;o{e*DA+~gCYS@2NFpEGjr~#!5h9r@rU|(mT_laJ?#xe-3H}F zLv%w5^1DVfLaX*j$p`FKCt}Zi>Q_l6OR0bh3P>oqWUeUfQxi^1Kgqh9%FwP90$VVK4I~vRji9%(1bxt2KQ8!+QR6>qKqh*M=6! zEk>KVGp>Up8)p6J%(Hq+xda{wG7j&B06L!G?zfBEQ_uU>p>P{tg?hP)usd97HH8YT zf`^kE;Z9zz8rTSf;`BWp(990Tfn_?U5SaaO-^(lRV3vr?!)W$@gKuu?^DSznNS57v z6wTfh^kbDZdSq8v!53qG7YDZtAu&^X>6jxFUGp&32DOH}6)+Qj8nRja_SkmgZfTS; zsl>gmDzq!{$%iWMm`80B#;dpZrvL3sA4`6*h%lSG;*h|Nt%6dLL$SNX0hiuKQEI-2 zY8NU~>q>kfBb0asQ5oC+z`46TO6Z6+AmY@X@BKaZ!*tQQ6lZM(ClPe42_r$L9ldm4 zCu^BGr-hjlF`N})Wf7xzv|fF?_n8t{`8Mu?o6Q)dj;AC-pQJOc90_ZY&~tYGDhtDv z{yLkkQhG){s_QJsLnH~_Gd>FPNmU;_0Pp_GkI&H(um|0L z-k%4UuBwC9c-3?&2H_ce0$wDUx%E*n@`m=S!E|n1x_pgLclMCW*W~|lBA7g!G$-Ed za8?O+*)J!4QL?B-9-TE&+%1^PuTW!EIS$=~W@?P+BBQdgUFTiIXKTdUKQ%^D-V=Qs z$ir@k!isoMDj~pyH3||d!5noEidkyWyu}!!5)ZDqG-;Lhb@)lKW-`hmR zU>tSlrVKp^J&ZaSOWyc)q;J|7NI!g|Y8Ljem_9m6nP5GmfspSVX824TJ?|8-`a*p% z%4V$O>5#1v*Q#(esX~`u-&~m^h8FAdG)`sYZJDb{he!<}&zXU{6xG^uz&b~q6+$t; z{kQksTbH|mq$d?k(aE_7YS)zi`qS5Lo&6bE>(34~ee1`yxdF~c7i%ajzQn;3TDgGW z{I^95lmAQZB5-92NKwc8{79!F7kkpeU_~!#92bKT2IHQMwnCIKzq#Tb7V+?Zv8JE@ zbEmlh{n!+SXL>}wMx|By8XD4du#=)I2>-D_{gvUe!9PA@mI;ZvUEj?uEVbnW7P)pA zXU{e_8GV&hg~=A9Kbz4kOZ-}!pPYkuQ^e*ZaXJQ2g$qhk7lZncZD+o7G6#l_(Q1*N zC~FpEv%LNAo2DMpgH!d}N$?KBMO2dA_eM>NXE2=^4`Y?$wdB9Hw|%{q*f@!i)Uq#> zhFnbre|2^=vn84zrONiOIcc+MejR*PJkCQ}Eb=c&(@N(9ausH-I0VlQDU{hDuE0|T z|8}`R&Nv2@mT9~#xR*1(Sjqt}Afa1JFMfw9&Hdj>5G(#A28m8#1!_DwyU3KTR0l@j z*mG4L@7~dP*+r_{flYF2Am7gaAw0mS*(XSi_^KD}EMU;upnX!OKJiREXkj^(tWUf1 z4sfvs)99*b`#QuBz82waQXQnXZg1U&M16!SbO%#_IP+Dh0I`iq^x%=yTxf*IPLdLn zMU3#6-x~g`!xYbOXc3Um=*i=~ea3D6Z|l?bm>5f#KPzw720facURLV*#rR34*O-sZ zou?X^u$w_qqn^F(Y-pW$RJsY?$)r{CWnS~u8`f!)>(;v*e^on@Aupp@#_N1v8J9#? z9`(owPJP399)a)8?I*PFPM)7Q(dh9r?P(L}*U4JGb{ZxfzdW(OUKQJl6?VE1xHzO` ztwRYL5KQ|^6uB{>$FO-gsyc)offrQZ&cHwA@NU8}X!JC*oU0!>BMTEF7!NSQ00Se( z=D#DUj}NL{Bk0oo@Yr3+>QmTTbm@0Hni^bh5ExSRoUex&p_mSb-b8k)AP zTAOYI895UDI+dlSw(!y`U#Nj!NkpRxzfdq4=783ORO~>n=G?PgKl&?rEc++!e;mIS z;pIgAh?6FixdJcK`utqpMSJ&-kZ_^McF}mMX$nJ{{X5FPPB-JZXZXk77pp#7_j`kj zMF?_T!I=;J*X@w5e=MTe80$@|<1raC97xQz(R)aYDDTC0%0b?05wJFtdqNE~>o?5l z0L_NWsP}Vo1$A$u>FN;Tys8{Ch7r6v*$e&C4o>{ucpcX(XQvl8S2q{i#sfAFJ8yQze0YLMW)|vk9W>cTEoAChf4%jg_T!lZroY2ZeqeZ=_skl`LN1?@d`; zxXgMQtY+m@oLu4G>pE9B4)Rs^>v8;=a2A+!-fP?1*kADrFCMt9JbDpjZR3~}c)CQe zK%wg(jRimoiTxX1G;3QCE#DI z-V=2lck>Y{q`b#Gr+nS+2(h*HFSdPL{4fw@4HBqWygG#?su+q#yQ$HFH}`pZKH)xg%q z>G9t!zg$Tg{OC1eKF7A_uiIdUvp&xDb4GEE_giP)-q&1J0X~`+vG4okK4h`U7Rupm z`OiIjashPI9tS=Q#aNs6+wi~U02d`fsAPH0q_dMn-MS3Gtd5_873Fzz1XY${(J z^0Z~}d4m@5=WUE|*SQzOaugM0;%>pT%!r2E8u4ih zVc4l&5Wc{D)sRjDvxM4dw(`Eb=l)cu5s4et@y_pFoz}i5Kt*#lG{!Zc!FA4KVENW~ zb}n7iv*ceCNhc^ylSn_nTc}C=L!{*25H2tFr09pac_~`hxNfrHGc)YVHoF)@`*`87yVn*KjxjH8;M!u{6#S@Y z_KN+dC}=}$aT?mIPkWh?^$ipXOgcq)H!;5CODQCIpHSVZYbBpqcwfExA*hy1yjGvm zIAE77e5!z5C=|p?y^OOpgYmLTE5ekL^XRkPjpSt5d18I1i`#BC+62~j4Tf5_AL z?X!&cc+$>94SzdVq<*95FI_mDFR(cervH}5c@gFM8gR}hQMaFs(*yY_M-406QKlJc z<*&AcOMvxj4X#Dv8l)f&q%|afE3&)}5tLp11Xg{^#W?~`;d>Ag^7#}5BhGMNu%s>7 zY6X?nyK!>?{)4Zp)0?aPl_g34E)JjUk-09s{E>3}Ac+1S;r9jvpU#PR1ztftnT&() z>`4yl9Z$278p$N>wN0c#yJ8US_`4jf7E;bG;*!tsRR^{-rQYtLWU`+q#CLYdm?j%M zV;(VSM^%K6X?Uc?F7ww1hRge3Pz631N)n~5eKgv%vI=_v7y)4QjVtW12JrQfcANaZG75>k67jI!r@^hc6gW6jGuDu%Lsf!gRn z#B2ib_d7%E93bmPob0<+423F5x-8k8Q|w57_Wt_myX4DK%%>iW%q{_HXH}15HjhDM zcimrq!7_Zg>!S~1BQ9_aMYk15IRC&1RcKu&GMc zT#ow{Oh)FI&YYNXctvfRsJDq`m{e3rB)d#*5BwpKyfwK9d{AICO_-I5{UTRMW0tPu z`YNGRVwrGI8E@k4-*j<5Ld&oYwXC8@YMI@&dc5_%qdPO-XBH&!gqt4Llbx&RJd))J zznsF7Xc4K-F^<4<2}UI(Fsh+}Scoggz80}247VK2#ms;yWgQNFML2liV6lGgY8PJh z>Rdx^H*J{@hc#zhd#zroPrROQr)M~cII2m299ldZQ6yN%c`op4lO_ar!E@D{v7-o^ zslPC)Qr*Ibr6sYXXl0@3ua}rvIVXk}p17^ea2cyciLF;J3t{k2v>nj-H97hTrp`Jo zc6uHwVcJwn=!U?MgXG7pJ%z--u&jc<72)zk74413fE3<0EreCeMgoBp9o>wIWftf? zHLi^Rq!$nWP3TBDC(1K=CMSnr)~idcctB>ABe>lV`}xiB=oWo@r@sj|O-#P{pLviw z%9Jd=cCJHbaLgGfjJ0bq$h9z9OOp{1SkGwY0!h`q{>A`=11S}~f|<3BIwAQ1xFur2nPnhN255n*K)WY3Z4M+lSDTnGZ`V;2!4(r&ub>EbHq7gU0bc&fz8f$;nB_ zJ(vq%2ul#ZFj(Oqcy6Bs>pd*8jsE0nT`0kM?sD#b~h5*}c2cLKCyo8s+$ z3<;v9Wn5Y%T9WX#Fqn(I>hyBDcp@9%ye8X;c(0!e;{DyqPem;;Ek1gZ&lE3LeNM#q zm1PJB6Yy8%_$=k<6U_LHVrwc4crcNvrK336|3Fo#SZ8DDlg3aeKxZ6{)QO4@TXXPtzHwc}F0?0M_z=&5*IweDQ*o&#>#*7@ zFbd#Tn|7R_;8(BC7cor$@{mOOHEPO%ki)o*R1P-%LAz&gT&H1U>cI1q!lPT7&`#4j zkwAwOT0GJ=$0|n`!UmS3?)zq{G+ee z+YQGMv4wE4pDck`-r9eWnBj{JV&4oI^j}Ec5uLhoV|S=9H||A))3|9x_}4VmD265F zSJ+tdfQ6U;LEP3qU(YXj&C^iv^jXI)!o3^2QQEE)F6O*DV=oc+Uc-IAur-gB)19L~ zZAjn9^A3#=Gdw5v-}6`g(wcNRBMwxSju-Y^3^)mz3=!ogWUX60_f?1@U?DI|{WYwi zpVdW&xs7XosXMIW+m@;DUzOx>_frWowb-r60Dq59!TmShG~Et^BOI*)pv3jT^1cYI zQv~dA2~P4Fp?M8)&~lkT)K+9xO1;kLDcLuoScV)ebh%Zn4Rm#B}yfjAFjM(%WjD-Iq$`)Mv139>M3Zh^YxYC-SIvCkn(6&cVI(%S&Xk zUznZ!eIJnRwn{j4usLI6`$>J;o@lUIwmQs9A{q<&V}{-e#$uLMhoGrCoL@})+LfX3 zoX5$ymgwZ=UiKLmT`Jq!ixQrSqQ6_GuLY5d(p^sqqB_~Eqd2EW6rMht2spwkY`ni= zDfjOJKAZwE%ij^Ne%$yVNnug^K>LER_wvMe&BRi9>iDYK+p)KRKwwCP?fnVf7h3ZA-hZJNlWr(&maK07{GQw#ER~Us>V0V0PwbPpuv~-ySIlmT&*53uSmE@=lR`B5Cx5vP=u9QbD|5 z3*apcZKH6+=Hu{&)TUZ-p6|+TWoYgw^1YS$=;E>Oo4AAGKN`=joAHXZVDImRmIUn{9}iu*tLiBZi`#FYg)+Tpm2kO%Mvq8KI^MtRQt-sVQf(I}F0k^ zulmK=xOw~ih0Mi@`c%U&r@7-RmA3CHubWJnluIS;aOzF&_L0%TVP$=Qy2{siwds~0 zpTI&&8aOB38yZbqj}r`k{3EqjO*{Tki~9^#{D!G%q;5wK`5MX0IR+HsfY+CkRq$HG zLb(~<2!fy`0mrGPfOz~2__<`r>$r+Sx`RwFBh7!bQBPtYNdZ1ves(6*$P}<3j~>LX zy!Y#fQA5XAg-X+3a~K|j7o=|F-p~!a*GTI_J`>0!9CMi{Xy$?<3=?T}D zwtDp{)B@tyKlhq6owW8&wSE8MEXCoeVu6?fFMhnV(z)>0VM@dmtM6$5Z@6APCN6+#lmNqspZmt#RVJg4q3mC0Ak#i%@(M#VP!#=N z{4=sCE7vWQdS}nJ6}TDV;8*YAb%!skGhFy&smpf_J_82aEZCv&efS(0XF*=W`DIl? z-&xeFR@F1Jhw}OKm-ng$@e5%!(tC}qbyE$Yu;TnfK2wVio#OXhI)yXBW|2-L5@U;2 zq^r)W(FE_jT|4C9d?`igM7O~VGJ?r0Gt#C@APH4vn$4}M^kj|A{V{93INm?|JJojm zBia=$vR^6E0v9*Ok9XO+m2?R0Bt8>YvZc1mq~|JGFkHgTCdEsYHl|bawd&2^ ztw<@vQGh)tBs3K95T1kn99)X~o|t653)r(8tz2t3$jHlTrxGighTPX>CyZ`oA36;w@#j~CGI#J@AH0Iwb=7@wdo}(sS=cbhgm8tn6db^{V zB{?z2mKUZ<1sJ_>H>=APk9obou&-q=2srYm+;%>GZ1HKz{Hl4E@74q84a4!Kd|x`{ z-i7NqpqJd1I|35`CUMjx$rtZf%I$zygs%GQts;V%Bii7U=n_ODw9HCRMIr19z+(>GlfF)Gj*fo_gytAxtjY%zo1^^v z?C%@3QJ;TCI7sFgw4r1PGAHBkF9E7E&-S&y6~iZh8rRd!8~M@zLjPUT&9aF;4_UaDvd$NPWR@~tNL9S}B_W>? z=yP&^Hcp`$;2sER%7jt#a~7Vj;ti#8E+m|(;+hCsGxW*78@`6y#Gt;bllF1DqQx>F z`-q)74+)f-meHsayQok!zc3nn{V)3JPHEmnJJh=QvO5N-rnLgIjvi+^SrOk5XxxN7$8skEEr<_`jsW(XlG}V z?N?7adPu$^-mnvW@(g+O4i#NGB%Xy=mN5C?8>{XjSCO4%;Tb#c6H|17)$LYv?(IYx z6iEesrf4_Gqh9Er{7&xt?LYA!wWoHD+V>dvFH$l?qBA@11)bhD`FVFhE<3@*$rJIr zL5#|ac8uxT9Xd)ER57#2`iG#w$>Iah^ES_2huM~*iO7*oWS~=eGG@N15xB+zjnfW{ zk-6?YR7|vRHKGS%oAK}SB&bU75aPKYTff)Zdv^LC{$={af6nKhg-??lJmu;00QsxN z;@hMxqkV72=u%~YZt9I`e^w(UPRZ&wY|mw^S&m>koT6oUaC4@O8F{j(3R<;KcVSe{ z+unO~hGtXY<(of0FQy1q^M2s>%kn8hdoC)K>`q*Cu1B5M-zEKxNm^y9;l2lVzRT_! zzis7p+mi6#s@g3sv`=tZ%Pf$AhYA1K%~4zs5Un;E?)(1z_tLe^3tjG9i8~s_Tr45* zQ4=tq&df1v>QKTsTg+fCktu6wHReQW zMu@^Y_^^U1D`^cn=iC}~8l_mgJ^XQR;pobOl(<}yFoj`z;C1ero{8pXF76Ml?~SHf z6sms9PxQPn{%hwizwu5MrKVbyL*u!|v6gR#t;gPfo`7`}moY-Hvf$WOuOill z=qn%29VFYTO}^J%k>ac4_RSoa5}>LK~M>d8}YLiGdmM!*XV1OU`qPyfrk zClr7FyThj%Lm6N)(<4>tAMpCCS`=>);*Ztbv-|Yi;(_ZYs?J}+S@v2tLef{jRL96m zfUB5}1Sjbo-(B=xvv`I=E!%zWQj6VFa}|Nh=A%-rN&y9YIAP>NgdLNC%K3A>X>+;H zljFD}RaL(~{CH-TSSZ%agX6ha?SwPlWyJimCty_r%n-pm zacj!LD_zR#M5T-F=1<()z1DeQ$~T=lTIq*U#jIJMm{|^feBN=`+E;#QKl;Z#cxP>h zmyN~es}=ThSAmS{b3Usp({OSHFkm$6{X!?0Z+wr%Nu*hoOZYP_p^;N*iV2HYvo7Ij z$DnIMHzC~u0jCAm0-!krkKO?pqQ#ZK>r>zFkeOrJMHmQH{jMPwcuCCzChZjOB~$0` z9MB_dIGh4VxX6Ts>P`DH`jt?Tk?g@JuNR7giU8}A0A>DZmeJv|9dx<0f#!jq`C_{Z z%(w=6r@+e1O9b%>T!O1*dHp+w}jT%wo*QKA1(}5Q9Qrbt7ryFQ09+OzR=(Oz}v0x zooUJ?b^N@5@}RT}e`COrx-bSuLrmL20HHKIGHQ zoU|(xzd=M@e=(R7UMYU952h~+e6v@shJ0gW;G`+I^)YaNz9(PYv_8`R~q)!WDGjU8dz2kPd{dK z)EE}$`oJkYi%^?<^>vf~0n>!3RCjNbNamNqN-(RrXEe=6qcQaPcakpn-9mZNcw-<` zBrg+lPn3;(*m-T+BNEa7q7!=!a=F?v<}t8!2G%oLj{gKaJtubbo`g`_bS+85thLp` z1_DF{Q;;I1Nb4Y#%B}rXtz`X$zgC~Q#_Om*4eB#&LfTTnBb;zABWjZWS{}B%jQWGD zWIm1Ge@xezU2#h?<=an_<(FnfbF53N{A-q)I>jIBE%=z{hxiv#>L5;25~>-C_GQ#V z->(gv@)=)yFAQ53_Bwst*g1W{I)d}0!);7#tAoBSXYidrlx_9;?^wZ9{U;sE^tT+o zmca}~E`90@_jS2ubMKC@u{n{zR! zLJqa@k2KB!@eh3OmgNkl&i$WJY^wE`cJ138V$Nj60%G6C+7u%$+-(a-R2#WmL&D2Q zM4#V?u?7l<{A<5D3LI9>V8_6iyH$UvKZ$A!+-f{Noo*QuQ%z9#VPr7W^^Hx3_hUETbvu! z<&ts3fAD}E;>D;v<;|JwGMUqivc zr?`c32tLU!4w>fA_Sq7i@<21_UKt1`v}Uf3*kDo zbgs|$B0-{Epi5u?s|qj2p54)!9Wy9UETQUZ`6Q4j5vImquilP5yZPyJf@v8`Z3=TG?^^{9cM?^;s^>y|x}09M8P}I07b(AD!1Y@|Ta5|I!I2~}s3wvRJyJG$HzV$GXX}iK@g=Cat}jQtW*2A(nW>`My;fVmKKu`i zRmObc?ye1&|1QG7a96e>b>xY!vUHVY%-D8x79(T*WRz9RqBiGzuSNnpUu35C(-+{MoICl4Hh=#GDt_ovikj6`*( zgzMY#0T!x-ikAa#gERhNq9U^gz3C1L9pQcu?8lUF5hAh6Nc>bLF!kbMdZByv?VXn~ z!fH(x5m-#S)Bv+_%3P^(^lDnrw|9%;vZJw2*cH;8qz=+7ysLbozW&m&zu*+d#iV71Htd}P(c*PWW;qL#Z-@#`wUT@ z$Pf$Lvz8SnA5EOWnl-R5wH>_5t;F*9tNU>5c_02rYk^4nKD=mU$tS6;v?<|deJ1~t zvI$=TamV)O9Jzhgm8b_8mAAC&F7nJT1qc7As_efq{DryWHXjV*c99s+$N8#OVRzU+ z(W*wN2wyRXQISCYH5FaP)!h7UBaH3VLe<5w@RITyUj-duAvsH;R9E7JUuMVP1de3x zU#lUqy9-upQE!`{N9XBvwJQ}^bzi#DrSmqbh5LQxJOT)IdsL3r(gW?fFL4^~6cz*B zg!U$#*pIE1sOh&`u``S3giO6xYe2BEj0sLd&M~Sx(>{1_$WyiijwwDxxSU5J%$q&= z&QUC@$&vOhD6sAux@_v8kYoK)BYw5#xsKEZh7hxpex@D||y3qkGK$G2h#EnQgV z8_Oy$O9Q*i|Bs||46m!})^ME0wr$(CjmB)$q_Ne;c6PL5J53tfw$Zq;jeYj}oqy~9 zy4IRwJh*4e*2TtFm7NA#+Luyx;_ zd&#AvrmfaPghXI9s~z-nzt7*i5FUu&*o!qW_^?#D^C0D_LUYcJI0ubQkU@ZSI-!)- z`dp$H$y({oMcLMs)55(2g;q#=g8cUoWpqEChApa@RdlB~hJ%t#jOjbNT6_ zmm^Ht=MVTV-j&2%n5S<`wXnN^2zjTlx zX0@%|`VO1joKGf-kIloDrnB>WirC}u1es`+Q>vg?_+IvGJXAHrZ)exu(XL0-0C8Oo z#yX0~?7>oVDUHK$!Jt~GU)t*@%lC#8f-|$@6OOpOP+Wd!f7WaTCeA4|1S``ug z4gL`AqU3|h?0q~<^=bhGMw%xCS+x-hf{3WBAwSGm|Mu%BD9dO`yQ&x`dFYB5H&`K8 z&lmCv%6#`h{H&SZH4qPkDxRnd&{cUb)?dtI(u30Wp^fG6B{xzq@)&UWFjis6;-=nq zj4snJ{atPv?N|O1i2Da)p_z(Mw3gGo&B?(DWd2ZI{Ingn<0v31fl<1+fCpuFAX8ENSl^A8j)Wbl8jR`d*%&!p8L7^YaCiA)O_87atIuKA((Hean_;bO5UKGMQe1{0}F zBj%FALM5Hh@G_|R`t}?=E@c&5%>j|Bsd#1lAB8&D)<$E59GIqmaQKget? zHy-e)WnIrI`i$5DeRkZkJ9WRyq`$Y%|LUDax1nE{Z$jxcM0T~V*074D*w}#Rh=>5o zTVSVR>r-dpa(HTu(g}-*Tpta+D>s#Ci~$#g;{<0WfY(`A)A0EIjgS`t0{DU?3sgn`J_}NXp9y?1SQPNm`h>rETfy>yzqJ=;Inc+e@o@B5=B zTid|Fwd}ek%{gp>CtT)CeKTM|^aL4CUSO%c^c~vEo8$sp?R59qczaUAaBD*C+1FOp zgmBg#(4)n|C0HLTaC%AyMxh)*qN-hbpRb_1HKRr2cwo^&!VINU8!?z;{-q{I|3VKw zG|=}c`^1pP%-SbH5a6Sj*^Y6C6xZGgsEAf69jFLhD`gEt6Q)0Hd5TV{Pkg_n6iad! z)*(BnGG+xeRClt!Ofiznyi${nwURcorz9*?CMHH3t^^-jz1v#f>G9$$H-DXSVqAl$ z=!NnZ$oNK1ztmuZ?q-*>^7`x8sgm+k@SgG{gp*1x38&Bgt2HzX-P={uIe5zO}cHuv9 z7j6WaK~hfu;t$t~6x$Cgw}xb#UX%d9pIA1>yJCnFX>LoZv~QZCikvKyF8DH4MhGGT zD37~{i2@$7er|9b&Am*|`x)Yz&c>UntRktcD71Ia*aVKh)S!kVr>c*VDb3+HR2k98 zvOtv~;)6~Iia*Y=^n9mtO>o^L7H7h}fjP8+ma`JDTw!;UufLWat)r^#Jh4)4hBK#$ zyZGijOQfw>c;aY;Jn9vj%5DVam!dVlH6rf^vsw0^#jzRu8*V{e0UT1G|Jx2BfDk7N zP$i-S%!8XYa#p-4&w7aS4!Zk?Dahv0Pu))_Dwu9677vuF%h8qwK&YfQCor%c9!1uFtA zBQEtNhmGSSL3%O6YhQ|P$9k|}Y7I}jexo#%Q+O&ClzCAfS&+O(t+ox9Us>8m_aC-? zkABwE@z4FKC%VY6lw>D(NjtAS;Er*UTPS{~ZZmf#sxa9Yclz)Zy7{7>X@GStjp8}n zvg6OibTWyaQ%OdSGFnEi9FL_sA;i!zofPnSIpNF9h zznVznd&Jf!#k;28HS7~bE+J7SrDIJ_-bo4Efbx$l{Tg!N*TFx5()5j_lLLtBX!w@s zQ0`CHV>=f#P3Yx8hoh{lFKKX{lhoJKLMv_ib3V3XubwN&z~~$mi}0U>0ljgc^YvsLv!=6j8y4py64l%jD+D~ugQbA)gGyqH(ArLEB=(JBmYVY zdMFFz)YWS1lGl3W(8A5F*kAB`w4)}(=#Drjn{5>%h96KzluEA7*=bX z@Wxvg`2_z}x_FEFh}f4{)wjbVS#j3)(sad;^c+g;p`R*?n;Dd|m z^4Z}gI4y43(KZ+O2n`Y=MT-Di2-oiZzQUwnXGChnqYI_pTh^;w*LKWbQ=fJsd8#bp zT&!MK`Bl#vw4lcNTvq>>fm6Oj(S*smyjFIE3Ji#Bwk6ZD(-E(AyGas?+25&Gn;#zm zac%~jwYPR=XAvg-Ox@N&NGchz1CPC)MWdB|Pm+^|lrX%hV# zl;4G$i|#Fu?xNQd{p|)Rf}N98B|lh-Y5h2(i1Ul%-2|VMW|-22fipZiW!e9O`snL8 zJ>CcsOAEU0k4-KBMYfd9G?I*V8i25;6Gu(EFPc6-KPZ3u9GTjszxW$5=U5)QIm{OX zJkrvUmqPn*hzy*x8>@23xuXaNbME8#Ea;Ial-89og4~>UrQ!{L-_I;J4^_9MvE#YY zo29q8Rg{(quTTKyOKccB&k8q2SX|A8#FgC5+)2B@v6F{u+BT0d%etLvH^>n2r!SKb zn{>Y+9sQm_;4jz$^zJ%?Y&R>`!yWrzlHDuC<&RM+L>fOLwL;S6ww(Zs5)dXqyK4V* zyAk#3nrW^lRLv+dQfj_<*#}B;%QEyS2bSo*lJA4a8%9;OVGH~q#^y4}*F3~osEuVE`e>vJtu5Jc< zNDfX)o|~^JEUeSe*B?Fn1IlweF@n)cvP%JbmKi{p-q_wPNG^z{xVt8h|EnIH8hW(p zi}m0j{D+H36KFuoOBgvDgk1JIGlZ2y<1l7;w&9K{em-x&WJut(Ptn9_HFT`tSR=4h zPAH1>L(9>Ea3f`lc1siEaHcHT0EsQWI(5y#oVU(RjfGWm&Y9^6^XNa}7tl(u_7|vT0z~pF9SH6%YwD7lci4rQO>9`4+&fkMlu9{5?yc~XS5}ejz}WkytMSIcN3%f z@t>PLRyO0UIh+|-lKnCTjI)49Trp=~w4GX^=85>+S(IR>4emN6f;gYJjDX}lehbaT^n9`M&xV?%U5Q)Vmr_+$9zHV1F; zc432%6t>VKSNAtfF44$MICNUd=j}Wv{fE3#D#^f*5E#nnGB+)nJ-7oK)Vt_=jXIYM zgQvA9dRQn(FBp7?Cph5ymOe8uT~Ih0y{A62Dab+%_B?3y{@BIa_q_2!v~1mRp{2tZ zY=louC!@ea1%`(_edRnC6BWYzB|y(xDbGgS|FmDWoT=k3w;G&~#+#7ZR*?1?4>$Ac zLusAyT+D|Q4zXX#r)B>_c82U#%;Y0WF^}y^t^QO}KFE+)-1n*CT3^97`CsQ8oLQTV zHdYyyEj0Gd&F>m)8v&%cz}rJdZgp={KsMc=D0?>l6f6k z6g};S7kekgcc>Hy$!px}M(|dNU*9Hk9hDG3{m@%HjUOT;`xm~+eXMTP1NFUU=P|+( z6h5lI@%uvbHTK<65?dB+O9F`Yo8m2@R?HR@m`;i+_>$G1&!CF-X>QQ!UM9JAJ~Qhm zAEtLEwU)v~?i^9TFrQ6q&jYW?re}jte&BXE?ov05)VqFGIFxXuSv(=Lx*pCz7tY-T zY3IoPwOMkkgBNq3o_g8t-&E-uX)nJ&16|Q(tmYRZw_}uPG@(Oup@_-;K|=qnC2t3*onhQJyANM?rZTaI zCSgCd8PUvf5J>Hp0>M!?{<>F{a7Rjq2((j13^lzZcZgWphkl=V1VRb(9@J|}Vh7OvzRb;uJKD*BKhe6|?EJ;r5Hh-Kw{Wg9t zPoD$5F3>x^C6Q6xD*Q7qpI{Ys1-;b|y6CL^hSiI~l+czbi1xwEXhHiVV0{%ev~DP$ zpQXqg)UGcPm|@vim8_vbY7TtYmEJ<=6NAk{QMb)YbH*B|myf&Jom2&+szv{TkLK+R zpR?2D19k^1fXXB|@$q5j9nY!7bGDA{xO2&i2*gsxOsZ+F_8eEE< z~d>R-DxwNPbQ z#Fo`H2_%rQI53QE5}@3ix2>aaij;C|fIj`n$WnO)Px zbb?Sb<;lCVvON7oyaQn|TYGn9fm5W5Z(E4ZAM?|j-}}mO|CJ5#=|;xfG+D9W0H(^x zkR#~De>(sB<&x0~kbQ{v6g9*a5;l@ zzkx%4UhdOASNlm6mY|P*Q_VHdfrUj|+URG*B~ZVGupwycbd=#mp-)U>In;}#2i%}+ z*!lfc^e}3wd6ltToS{4mP2l#0dY)UTKwAgzm91RUKF-6H;e{`(o2~WXtO~_m@zY6v z#ZE=fV}(8-F?>bw(UJaP$qr{(#E-_xl6Q8o-e*Q#IM9XQ{UAlZJo>XA!?2g7A81(N@#n$)pe@4H*L4Pqebz)9@+V(Q`5(bAX zwjXw)yru!W(3~UYXb4+D75d2akfLMxcT)zC5Eb7*Lv_%ditM)zlH zQ|CV58rnO4G-Zo zfc_d8UnlInCB!J(maUpDODiZwo|`V!gnu|3TI!&zh__8bjUADV&HlTcjLNTcP`@}l ze_@2{3stPM?~?DB8WdwtJ8HD)ZyJXokuEfI2_+QC>WT4MuMAXXt{nzC01S38VWuwE*Mz#QK zqWB_?0$lr*7VJ!z)~{gqwxyT&UCKv{&%9(`Y>FBT&zl z%c7btML0#yax}Slpxxxk$Qcda8QH)AoPpT7$lm(Ce$N$yg!ZXUpG_SW{R5Pg_mckb z(+pF@_--hSr6_hyxM!sd^%s$}-LsoC-uyDc?|k8={!g9f167yV@Ks64#mfczUunf^ zES`+UJ(OE391NbEEEF8X4mH=_6(zl&&^HSXd&#RndNZ|_sSiUKyC zZ8tNIdw-AFB$bcnY0kmy&F+O@umzh4-VSDUhjSQL(L$);i42 zOymS&(@YfTaU_Ip^r ze(B4l-~Or2!mGu-_?<7EE4kAX(;KoExmPLdc{uNyk&UU6qv9M{fez+&O^Bi+T(UC5@aOx0^fO16?cN#^*N9psh9xCrq3&CPJTM&Sl__lbZ% zqJy5i;O<0Av&sg%>um;ByW0_S>USOPGeYB-vB!7742PHbri(xR1*V3iG4WdltHiP! z^LhuSN+UfW0}BUDJAO^f?G9hT(nDb)brWP$O^$%C?$~&*z3X(n7lOJ(*|u2j1E^&6 zmQb{w&?z?qZH~=TLxizG4XJUNibq>b>_|L1@nYk~d^W;$6i>2`iED`^G7TB|&Dqnm zF;`J0Wh_oFE5dd;2%`1sNMm(HmHGKBF(;K!j04C1c0JV!J(3}MpT^&e(3~AZeq%*H zUJRe;v=m;ThJbGUH03sf=#)2F_%K~v(x*T_8I?Z?mXLxacmr0ZcUWYp_kAi6vaVi< zX$2|Xjh$4sx5lP9g4cDzw$}7mA`U%yGZ0TZET47z=Qe7E83@wM*8C$e@{}P9-Q?dnTws9nh07#%%=@UT>Od?GKDk({v1O9RS29$Fq8q8Hj~x z2hHJ*YNsCf?y2IK$o9dNpUQae{~E>QS>|1CvC1C&{!ax0%mMk2FLOQpqJHIauHe;> zSP@v5%IT;(L>sQZmc-H!A7YD!55Y#5;Q?~%@V^Zpqsgt(cJR#F==Fv zex-I=cN0xck@1dbM%rhRJK;<#mWx))JNRPV=v4{@=5b6gv6!w@dN3dxux7>tuzq^N zYY?uRt>g){c^^pW_V%!M2r#$6>p683HBak zs=*kKLxwCNA<6 z2gTLYGw7a>+hh>QG0?FuUbpf-Mu7W`JxL|-}p;;a{bJr~*hr$CQ@yO}{Arh^G1vYRI= z97X{0yj#~Ak8~;Y@dnAniLkLz<*3I3#oO_Kv%Wb^wM)J%1!OFs&robQlLM@P0Y zP>&Z@z*;ZpB1%zVTWdbwI5HZKvFzE5^j4(=BJfqM{=(@)=|mB)~Mtxw>b z@{a=;QFMiy`-Q9rWtk+QR;c|ZCZz(!tgYR4>KRya6;@&+K1AIsO9HSBgk*zYhcLmP z#`vfkV?4jDsK2h3NY% zft_!nfyX3BVZoFCd`x=V#OycVb!#z95f7tosP@4rrLUf^+P&N1TuD1K6~stF+Ril5 zdUw{Upi@vEZ%XhTpZm3AA(Iwh`Ctu85CNZAR*w_7`445UFul+#k!n@6dDS4!x=BWu zqFq&)>;QWj`k&^&LLe)S?vRm0J-PQZt0S_Y&6ihggb!NRB9z(nVH;A!|4an1Zm!lX z_fJwOT1-OeEB{Ta>&JrdAdo)F_3Fs1nm zMuk$^gi=_Ta7rxffe^UU#>849kdn{et_^FU+e=XqP>58}C^D8Q^BSqEo>(1QjT}{B z^sKa9{6wP->=(P)uf$|V%!52)K(n#m%K|iG7cgpC?-Ats)a)K>KRKspGV!pDGOj|L zbL~idSsVX;{mzdIb#iNB<4=%AAOD#{JcCP}k`v~`Xy>mm^5ywoTwGzU#enBDrknf; zlq|=BlF7yk3@3C1?8=0KQ96k$HBm6!apZ0tO--#mGxv}%rv&Az19zfeP5yMomtRu|eQP-L~O5?1P^vOs<&jS>|N z%~qK{kDln|drG|zBI6gHx!x%;e5pa-mT_oyzRr_w5G1tJ-*V0GexpkgR(i8z>o< zz_@?%UhfmA$=uv-s>>vxFbTescz|PSUMoci4H$WyQwiRjs0VT{EHpRsxtaVY|0S71*ebVnjBTtTw z@#Vky(RyhbiN6}#2%QllB2Ges$Hd`SebV0Nzb^o-Tr1L`R2T=Y>lJ^-DhJH%8Llx=%ltWN{iHOmqjGyspm9>kpKl{`U0Y&I@9CJ63ioM3YpmL}O4-Cq-Lr*xtp@(MuoHme%6F zsnAjD!u>L+%T4E|KlVA|74<#mOz)>^6MQ8&!`qJi-lUIGK*@QMs|W-D{@Sc?5)CXE z!4gdXyEf;kGDzxk0!76lFf00(YGsjO5KjWPxo+Vp_>NcfqMq11nyBaZ)?tXq7o#G< zHO39@nr2=yl}?UfzpJ>X&JzML?$boNgtT{gLTYK~hCXVDdWZ|~O;IiVhg%>j&A@Vx z?@Hw$?{0h~bt%T*`Wr^OE|K}{Xb1YEnU8e;>tZ9!74RTdONnzYpySKOomds19!KZ8Z+@F zQFx~4GK1impSaJqL2bsA;0htN(5#z`?8 zuB(5A>6>|XL&RdpgsYzHz@V{FW(v0a=)zA2Ry$rjG7;0eJ~K$}i`o-ZFx?7ZzN1E1 z4IP~UuUsMrsWCy9@Z&Lm7$G%hqSi1FUm!AfmvhpB7xRk|P9~%`Shao=D-tgml}VmC zz$(`S^<#Z2edHW8l#W2oHt?kUL5V0W_M>hnxe;0nZBk(?hcfA^7L7kMS+lt{OG;5} zg0)3Z4(Ksc?a5T4)2eJ8T0u)?0aJ+l@QWGfV_f?CR_jp{-IyV4B#d|VyOEtNt2d<8 zO|t({05+#$u0mekywRn?QYB|ae=G`tA55X_1@p@v!x5weP|y3B?W4N>?jxGB_;hUkxO zbpAb<+DkqNm)4fbV}-<*)uK4;zDd@6J%E6ALQThQNPIH~7x$fK$b;8l^mw%!`9gk~CHZRT-=RK@P5$<)hw`YZiWL zV2-RtP62X>W#zyrvuWh7E{{Zg21)nJ^&om44cakWG)iJTTP90J>$M=^nrV4W`^mxZ zu*}_1uZ5)RjGFw`3pE_F$BKMAqe&#KETu)2i9cT)V>tVjGR6s+#UZjjza?=i^Un^7 zQ?*$CR;?nfj*ZosIOm+kkp5XtA=gyB2KP4&!Molpd9vpS`G=(p@gSBf z9Xj{bbfjSzWF>6is38R48O2RMbxi%7NP@9;*MMttj3Gw-PXQzByZVSvY6)jxUO*}R z^M2PI7y}0;D-Ne{d^j?%g;$ewcM8?A(?s`g*!zR9lYWmCS%%_3=0Nr~1ci@gn-%`F z28D)8Xk9PiY=Wxwe`ue~vN^rBST9>!qc9hfLX3I4q~ z88+&Apbznh+ap>^Vo?ZX*iNgVDePR=eQulxl=>Mg>@yQCh1h~)^qu66yH}NixQnKf zY>6IMU0z!aAebUIV`qp&Fe8&<<33${fqR%&*9t@_FzpZWF^K;-PFxks&Um1a>)$iI zVPUN$VwE%l39QXDu~pTLeooE8>|ULe;kf7Crg;UiFy+|kdxeQ|UT7lliGScp(^!|g zY69Vg8}p`Jh=1_&z1SfUYUce+&_@>qaY zPP@DMcU8^}U?&c@Bc|~Yw4)t^ib6Yr#O4~IzJtg?O<}Z(rL%B?sBQBPB3c83&~D9J zTcq76?HwIF7rl291Q%>!4#V2!Qdk(ca=2c##S?pT(oIviLddHBckdheSXkgV_Qoo*`ofXxe0laV3QTr7`l4C_v{(xo#KuV` zt6vyL8r=k8yv>vfP{{y<{27(Rz6)L3D*Zyli@ zK!ZPe)BYDM3& zPw_y}7N$_yLnCKU+IPLhh_XoVrEXI8Hqev)rr!X<0qngm**I*4_$m~2s}LFnTq0Cf z6&%YdW&PV6M?29H6W8zLRW_7XuCQF>VY4wD0y@FhJ=$h?`NychcM-mBkkIF>zc_3Q zFt2P-uTt&=rsQp=jw}IO#sn9Nx8}fYGq6JCF;Xk!K2jt^ntSFiL0+UAb|vO{UVaYb z$Y`C`G6Ut@+Ql-I@ea&&{o7)4KRT=p#CW4Ssj>r*e=l>=?AA)G97k(k6aNai2me<& z<=zL?t!k?E#l;u(F=*@*v4%!W?A@qvok^#_3tH(>?b=kw{}aM?8*BGvY@6^0VG}t+ zZKsRLc%nP4!p=ZCqF80g;vo+Lv7hxI3NNg6MXI%0ln^-Dhkn$kdpfn45FZ5v20I-C zE`Agj(h_9Npv=5O<&l8rHD$-6&x~HQM%Uvs)TK!u9hNI2FkJP&9kl37teaCHX7rJ$ zX|1$*KODIP;^C7Hmc;8e$?S)jjh56SnY`5*-P#iyGC=2CY?8#OZ|3u_oY?p@rz;}2 z1&P3c>@39)n#viLF@Xo-%+)-&!UY+1mjNatfA2YLWq%XXDzTsVQZv4VwoA*Vp%uY8Mn8!yQ-zJZrI?zPv-hO^tYl|1)}9p<%SMR>z&wsjgl#KU9OeyPWFDhI;f1*3 z)=;J@1=@Bu(BP8Yp7919<}NuxXmLZ2BhXZv!{@*JcN;?<&-B%20!%CxTap!=< zgxk?|%UIa{#r&r3EWTFA_qeEHUi3ByY}kT@@+2q6SLzG@{$25T6qdUpJ4Y3 zf?@2gern;!Y!B=HRk9d9-eh5FCqt5a?` zPYPd7lHX5rm85p5xOPVWdjsNNs7f=^|C~~1M*Q6M2lWq2_)MzJE)HTxYDS7=+t`%S zb|Ogq8aUF&Z=4`z&qOhCmPJ-L~u=We}0MkN(eeDG#-jD99dd3TBHYcai}96Wze%`7~+9! z6262x9lsCPjm0_}LUa};^mgy*SS4nZdbqyx=rZ5pEw@Qt)}56_=O9ji#x>h#zp*2J zfvb_^0Q{v^U)#D$pgiqG-w zTv&C9Ii&d28_On0Zyu! zGj6GbMOLH+BGJc2%$IwKTU7gvD@L4;>UxMzA#nWjSPi znksolFlJW!*i2(#e-ug=kxR*!uFZAxS4KGD|48~P3lBr{v~x3~Z`d~ZdKl65O%xV@ zJG&sG!8&2b1`9I5Up6%i2%+(!3qaRNna5T=5|*df1%y{P^CulQO(5jRsxNCLq%2QgD$=0RHyHeen7TFx5pcQmufqlr-9_@lc`izRj$_G{hedOTLcUK z9f5DN$YW^n0I>~go;rKug%5wek$rpubI**NAqFv{%Iyd@T0B?Zc4Vn!gtKIbKEdGv zhtyGNGuuex4vU{w+#(MjO;nU_#-jT-ga>zPt70>Gaycegq}b_nh{=X`9ADegUsBn= zapvI;P}=ibxZ@3zTNADpr~6%2iMpxfCPV zbuq;N9>3Xu@OKNq!8MHBT0gP@z18EdvrRRc-z&<6QTmH&Bh!d72{6sm+f||>?;e#k zovdFMQHIfQboBHEaLjHe=ISiN2wWu0F#ifb!}Z4Wuj?fOg)-C&Kkd?!Ev0Nux0!Cl ztV(-9;mjCo?L1cIdP1sLT+E@rbmf~~g^kTYVK1-ASHNHX=MVyXR+uWMM<)7C@K-@SiyZE(e5jN;v5_1*F1&Ty&C%?NSKcWeec8I z6Xmr}t=3=6%Jkau=675U7Nt<$VICcY5zOfrWpNen3tCtc`jE5l8Q_STI1YGF0J&sI zpLdg0a25Tn8TB2zuEz#Rwb;F0fTA4fX^11RAAu$szKk>MK2Zk~k+g(Rtap7{5Q)RL8@F!YiJ~vLy1!$k7UYLe-&drE>qIpDWve?cSNWFW0bv z5|=&@B6B&aW4s=IG(7qDtMEdO;pK%}L*HtWD7|a$>DOviSau7gB0AF*v;%NS$R!x^ znz+H~eTMT11X22PL5M8{l{A&ws_A9m5doNFYc)2p3iA7|=!V_z4HT7(B( zQ`w~2$Lo8CXy8fxy77{b^uDV}5Bt!R=0gP1s|{yPDjGz{|C$NJx0GGU{hV~sd@aJ# z9d{j;E0z>R#Uhwq(Vws~V7fTG{Um;kP1aR!l@UD?u`g7PHJFRQ1a!ur3!kjr+;bVD zVZm+1EiE!=2~IqP#D;1Nnif0G4WajrWK3AALJJ@X9E>wym3}Mtz__XKCQs?epZBKY z;pM~0H?q<>5L=kyTK$G8o*r@%Vhg^lnGHU}`-QRdGyEi8vyluq0q1NP%dwp)`WnOz zLHQ!`;q15&e^< z&GoH%D3M^jCtrXny!s6ONAzp4qrXsMxXhQDt_(nu#jliB#f(eFXr>)V2f%AZRgb(D!-_0~ zM*Y+O+d11pOC3tI&jg>Uc-3K(_MkUhU5-9tNA^*hjlX+{fI?)tc#2os>tO1+ngH`q zsrEZn5z8sXkz&(GA-gK`!Y67ERt%>yBd5YIp^^;WhEg3>F&g^@D@D&KxOj1~-kOMf znfjdbrEQ;F?%icHAbYl%H4{FQadPv~KqgRpC8)U@^u6oOLQi@wY-~Cv{?TGCth#XmYc`y(r0J zyo@QU@aCm2vAk-&xYzm;7PgmITmlrNZ+CrL%hPC!ir%ne$%z z=ZuM`$w&bw&V!=v0A*N7IUsH|DAHTJg4Cpcz3YZ2+`6P8GtDCIm(C&xQNL_Lh5wG5 zC|_%>)t!2XLM6~|P$KseuTNJiFNHa=o%Uh#lF3vkX-|8;Uo@X?u`uON3F zL2Is?==k+%6z)qz$UWe0xT9}v$LBbFXWHjDmZQaPeG59NWO+VxqZd`=M1-e;ptln( z|0Ebp=bk|V+dy&L0uT$aF4N$*pKT68`cFGbFpxhnJdfT+YL_BZ;++BG3YG z!%ob@z}AS^&3tGS*HNRduugysokluGOJb^+Ra!<-SWCOBW4*0AV%U|ybob*hrs9e+ z77s(4;5QtN;ndfCIB+hOx$QuSH(0XuST?WRt9W>}DBjjWz^H370LM`8_UXyk+Y!!N zHWnN-|8H?T8EgBV+4wl^rxyN-!sxhQxP#7<{1jMC?m)JCgPfZ4JCf#P26F-ByE(%o zr9kr_&_Qm=uChuC#lkARm4qDfG4d@RR2I2&fM2z;sFnyRf!fUwUFguPbh^iNi^ z0*PWK8o_{4==LPXe)CF*U5Yp7aRQ<)11~ywM5>I4?*}Kl*+?c*tZMqoaR6*g#TJ2*`zI3P|0e~N%XhoI;Jzt(9ZhZ@*dLHRwr6H8euH|!GGTKDK$HWW@nPYs6rzLcgUo@ z1>IjD=a0vA@|?~*1Q1M-mTJ8{1G&NB7(4K70&&f}uTbJSHQyWbs`Xra7SX~sb!4U) zl0FvBlg4oh+?7@S3{MsSo||LE;omH0}>;4LXA2gi2KU zdra&NU1~NL`6(;f;I1y+iT{rO^VOKi z_o?S31Flt6y!>vO#xlyEZ%p1s+&9PbV2z9NsX&?W`CSAGu9vI7CAaP#EGBqbvjS*t5W*tWKa1*aZ;92-sy* zc^+|xj5RL&hY!oUWE|Owkgrh3tm`P)^bDA>M7IChiJyd{G|6DqN57W&p>=&_$e(~} zg$+25@{y#Ss$PY_F=WSKK%*RSC^NOI8%>`uFv+d75GTvdOE&Emj1s|hf{+cNp~9V+ zlOYOr%lZfJ5Ds_v<^X1$;H25yZg+e~wNgRwovTzH!3%$+Qz$mxdD#V+_OgJd!KRTH zlji!WK(%s$NT^k?`(jC&>-Q7q?cXuc;8n%6V zzkl#-cb#*cD=5q+ae&JdyTObNPzEzt=riNry1mBKOgLc&PBo%1>|X5!HiEq288O{{ z-qsJbYnxx^|Aqa9g`Zii9O8-x)L+>a$f1D}*?;KuOg!Xz-l7FJyCB1hjZdE|DLt_j zhG}nDv8qz!t2lB+vWrKfH^V~_T`6TgRH#@8j@elH%ePRUw~oKK+1sI9d4^uS%9^U` z(k*G}3hB7;9(JS(-zPp+_!krL3kv6sMbAn0A2$v20Eql4-nnaLsCWIjQ|P|l=}NfB zEkdu4TyUmLGQ50L$Xr_EVdR@M_u9N-By)t+IA?1Zkh+2MGsa%wk>|K7)CGZn3&dN0 z=hH_DM;9~p{IK5@3!oF#pUqea4y2dke#aX%;ORS;6v>4qK|3-Io`081j~)lOBMX_M z++U5cUedxQGpXWrDtGqTUCA5P)ix6)atQLPGkKzGXtoKGX)omq@SX-gUH#_bxqcSK z7@477cx@7irn5{FJ`_n35qZhLblmou@OqT_jjt71!a{PHJYOyq&opDiSX{YY$y0-S z&S?6Dt@YcP!j&4L3kB-;%HosDY>Td@4HSld&P+{6Uff|etI*-v6;o~*Ub4H*KJ>KHttN2bE4Ue=Z$!o@{fyCC z>1FpJ%Kh+uRTM{d0rTJxi<`TalI2TonuRo=-r!E4i@kV^LOL9s4W*3IJm{h)!O7-T zuXOA$-y519oqWPk?)B~=Er%3c>+AX1*$*Pj}Ky$XoAuere zq&H5I$reoriP2H^fNiHj*{^&`8_gr@Kc+YJY=Mv0X}M;>2dT+e6({Pw*eh!#+o@~r z<1HRNtq5IiLFBYqRJ5FZ2PpX_m)fC=tx`lDa+@FOh~Z57chM+9q3B9B(HR-%E1HJG#F2^06HE}%twMh8EPwyVm`7RMdO*_(oYkrH zSK!$z$;M(Uaigs~LI|n-p&?fAuQ5-}9+1eib8Ll|Q7=)*VZ(!0E8g*EU{l@k@O2iE z+U~`nyqX5R@nQSzKi-z<5Aym*T4m;lcJv*el4Mh&egkrQS>Z2kW1te$u%w6LwFcy+ zSde`eq^xRAG{($_@PDvt0v) z+&=l0%zSg*aOovgQ6#a+!F~F9jgK$Ef)YM>MuLKGMw{h-JRTl_g}@kX3h-SzvT}4+ zkuabFQ%#6DC0hP!+ibqs+EAud+QiOJM}vu;!IAKe%E7y$b|S&z7lyv^2>H9|B&o4I)>DN!@k{|wYQk!UMrF1_42@Y8h@JRQ+76! z{|eQN?p}2xDfnmNL@g%$$ooMsGTBbtigbjy+LE(GvB)NF7IiE2)^yGuL(1 zbmVSKzHBMZr45G$q^a*PVn^yl%fle_%VjnUb&=QKWSv$k`0r;9cNeFV8pX@QxYak8 z#k=?jY8(2sv8cXN;%PzNNZtpTe_49lfoD;=v)$~BA&@MLtLo}6;m=&S%mPvh(JiI? zaJY;8Iit$oB5A4nhot@IiGTOpn`07W#(_k0e*ANr=v_oNR_{%llYn{*4X?WvozX7p zdocq*g23}G3M==h85TPfbq396M=`9vR-y=?;Fm-zvjL&TjR;$ ze<^3<(;%j*|?ds;D14hgER!YAhs2UTgqs$~! zD|+avATO=}yTzK?G8Gq=VejTFcN%YZvoZvh(A22HY4mPy(rnm>8VkGeb~mSXHH*ll-#_>SL9AyO*5v zv#eJnxduxu@*+-I!$XMBx@qNs=}1kggIJW4T%!Rdi##D6oSSMMeVkD=6>&af-6WPP zj|R<8uHvBk>?5XBxQ~IcO?Np{KZ2?4VWOgfKL0o=Pr6KFp%fuR%bTM}F}$ce-mD#@ zwDd!9E=suWmud^bcl)OSc${R-@c#8$q%A%Dp4nZ$9#3ZcX)9vG&nx~qIDF`q~{xi;7_D9v8jc1U1=a)S7fK)clZb$NyF`^xA=acaHGp zKAS*xDN9Nsn4R_EPcoGeUB(qzR@KUr*22Ct>O>!{?XK(IgTWP^$ zRh2UhhAevhPD;ZUi4n%fhc0L2DdZN?5)QBW0?=;*gwSIuUZF)MZ*;!oWb+19E(B>{ zOG;j*M1>MC)giDf&8%1xleb@~$ID8|ImfO)Ea4Th8Zi&Tp5&=+R&cd_{E&XGzwEP} z@%r+Kpcc&xX+Z%wKA+74Kg`UD#qr%0Bz*xb@Z&B8iA|c(5L+wnd|6q@hvK8wmU04 zxMb2#pRKL@mzbku`>}?Jsrv`U`6)#&qp4nkesx0&yLE|%yQK6gHb?5X5m`!)9_6iW ze=H)HF3W}O;B^zgDKGJf%tpq;N^8vG6f5*KnblBL-*53VQEq-4^1$nLIARzM;(7^{ z-FWA%o3X*NN6vY+gAj7(da==+sitS~b?NChB-1-Z?}X`)&!xa1hai$bE5zCl4m55E zE6P#DjtzJ(tOCbPo2$vDXHR8wlY^r3I*SiRrJ9p#w4$0SmWu|-7r~Rb+~sO6Sd9g8 zJY)|Qw;T627!BrAnXUNjG3E~UAcf)^^6>T%z&#ebYdOvX=OAblsdins~+0Tk%U5E3RRX3(1~r1XlH zNvlC;-CQM2Ol(?mXTa%*Nv-lvfq+?O227l3r$iD?Sch-U6gi~Zcn+-A=8$&DjbBQW z>q*QoY5iO@g@9{=de527l4h%(dXEVEnWtjyZe^!dW5lu>tH>#BqCVs)XSRyOTL9DG zR0#(rKXuNn)eeTd!UqFgArh=7W<^%d6cS^3hqO0PAQ4<~v_OUP-s1k#4$2eL8O43m z8k-J#M1yF1$nE!{zF8K*VN~uA-LNtfp}j8z%o^0O3C*gJPuxK`_1r2iIXQ)?%sZ%({AoHgT8;R7-dR$n z`v>mx?N`}Ndu0diX<2#3Jy@K8ukV#Dk@%LsA$aT`1MpU*I0IW{HCb@r=0Nc4m_+2s z4||nmz~bu&W;Z?Zx8_|a`uLG-_4YA+rJ|Wnm1(2dcXrXD9~~W*`pyxW$?R;RI#%=ER#F!4`$PpiV&VPbgu5%56KDfT{|1s{yY z3-3y1a4HQ;4zER%{KR8gvXe3p7|d2S{z3B(@X2A%&J4OJ*=PPs==Zs!xQ7H4i1G_V zXBx`Ng=NLcIV+^x?=oCGzwpT_tR|nI7&1GSlvge5S`QFusfvpbxozt0K==u5HBthe zQZ(oI*liTJH(aPpKZ*X<8Gc%?*62L4ZK+4_U;693nmS6GSZ1=MnZhGhl>SkfK_3|; zq@e)s{8}8+8S+H%7_@cCHIf-Lxsck5{4GdDzd6@1Q2u$fLL~+O!n>RfUValSY{&3R zXZlT`z_Kkl@|DJz?2;yZX;a>!MtVnLsX2GkPyUG|Q+%6JWd*aqXQqhN_-%`cwTc4?owi~A`3E&d)j~|h8|5Q})P40|>ceW>U>e`~ z)_R2CTaC2RyFHiE>PTbY2z8hUOUIk`c^--N8`6hJxT&I^EosAGYtRcQ**(<;lqJ$E zHyL;<;;eN3^MS0-BhW$2iSU@K6r&+J;e*CPIq{WQ)IrZop7|~cRwi_UJB{x7uwK&D zw0v=qY6bK?F(tJBE&K>xNsPrX)1EgpT2LDmK~*)P6PM9CGqdHWePwy^+7uOa>vWYt zXeMugJ$L4xViG3bmy0lKxQP#;qv*b&A5j-o)=c_~mC0q1Txyt-=lWIJHeR{@7KN@l zeM6ARVf6GT`%=7;`pla33icSBYCrPEYFt~e3a6x<%Yv+(&f|e7>of9h0G7BobXV2u zKTE2V(qkb*8nBN~4}I5AFFsDlqoC9a3o5Ha333Yjl$*xez` z*qv3KHF@hcvV61fj4fYw;4WGP7{=>cAWdQ_)oH3yGpuu`EJ-wl2iT4k`5rVo^Yl^2 zv-gyas>~4Rpw`p5aN}CUy;1w4h}N(8hHfKA_19;8jivjbImxgw9)-?zGP5qXfGucI zGu$|_%0YpxNuky}?}W!v?6Kx}#+aPKXmV{HF%Qy;1>b5&RDXKojlIuuEiH5+9)SAf z1R6>s#B!3?CFr3M&WY2Dg3yd4dyT~lBZW^-`u>Z*ezy!f^ky$;!qs-E?$4#qP`ju} zM&L8bG2(r1Q}HeHnqsp0(E$mC!2lCDFOym`13~V69`ILH@D-{ICumZ~a@?VJ4W%sj zWFR(n)JSekb}Z}ZoY@|L9!Fih$86kq=o`Ylv8#@P;v1yRSdK+_@Ov+n!L)F zTqe)CoKtKx(p+q6u>lXebrBAndYFiAT`j)lILEF~96B!gv#xtJr8-Vz=fNTjn2ikm zp}K1bma0HUQ}sWr<6jfZim5*j%~ph*+vzx>gmmQa4YGl=n8b&Ut2r^=r!o@iSr?x0 zh)r6!h3TFa6WU>aw^Ci4i+(55N*yIiFn`0RSB%9bLIde6&tNZa$&hc?Q7zbX^Eb2a z#ZW#^I*y&>E0UQ%Km9$d;&Y$KoO>jmxuMc$0qaabRgODgaSgNZv@pNFGx=-hChW>WMG;N zo*t=bX6rF0W?5Y{PFU*_sHPmmR{><9wj7$f91EhOHvJN|I;kDD7xm?_Q z<*!^H8>C{xpt9Oy)}WhtRhqcdXC%@H*;W5sIu$pX<6WkNQP=yNr&3rc%(5)fIP`sN zv~q`7I#DYMoE$)J-v_mGQ*W1H7Ve=9-WwaZq%>R{6EvC78V1#zm8{bFrME^*>U7B90?U}HG6|< z`j&p9XfIp++Fy|Uor1ZloqIl@oo5Q=LX>>3Nm1Q{BBN0{iB!cxGafPjgT^71PDdmD z%PMzbk1e0F@8sJ|J3LfLNHy*fD!uHGY}6WYi^ll|B$^(XE&Bh-6k{ruFm6MTTEEIO z=5#t#E^>c#uC#H4c)k|vou68BYx!eU+$U?KdnNhWEW3j6WsK>h1?(thi(;vNH)~&X z;T{DSAVJ5D+gQ$tG1`^>c8PLbg=woF`ZKS?oiml(@>Firj<{u_>Clzm4{yx$i>mXFoXa z6X*%8LoZ*eaE2iTN`ZCGLP=%-jVCv6X>@4yJB2DbYYz7g0rxa1+=I|VO@Kt2HZJb{qvi%hR}w} zY(`yr8V~+8jtVC86iu&hDhj@>wHg^wOm|~83dj%h6SzqZsHR^u?CS)gTsUyACs}t0 z;W~-c9oJgyuX148)FBUdJAno$+3ASj;+wHb&G#kky-I>cC2rFbvdv3tWNXgF{C7T) zXMsBGY^5F5#<2Pw~qsI5mh=;_GQRd-s{aOP<2@ilbS6g(L_URHTE{pd((IdY!l zS49h$0)2s3xbnt^dKu57G9EMmk61%>hR50P?h@h!SYi9rLb?7KY@ek@N8;ENhCX{EQ>+@bW=8&!#)7XqxAxmw0eQ8yEmdPmD`?S-kyAHXCzl@H@(!VtIef@} zEV%GsHT$Vfr!@Xy`@~|m{A2ClO{pS~(QXsVRrn@oI}IOMD#+XRTPE{ z?UcbXHZo-s6ik2=YD^HqRY5}cf!x;fRC(_`E@0Wi0B-ux=sl5V-H)1dJ*$rFz@kzP zW8issV-ZlZ0AuxJqd!#ODKq#1sXermYrjChuZ{I2>Sx^Geh<#>hT`$`lre8xEp<=e zaqQ-mq%JxX-=h3>01UGk;bRo3liE?vI!;;TdS2Ovi`j&BO#0QM@7>mfj74Lz;pqm-2XrZfDu1MNJ` z;O~=QjXUog)y^el8>2#ZwXAi3Y5s(NWxVQ(?B}I5Kuq2h(Y}3RRO2A7mXRk^!m=;4 zOqeAi>kVe0-e7e%!*L-LNr+^>v=|_>36H1c5nSgQ;@b%qikS$8Mh^hh?{lLFFl;r3 zRzIDBacPU+SJKG0GTRhSo>s2HR96bdX$?3JWoSe0i?U^etE%elW4pZ1C<>-g{;aw| z&_WTh*OHTWhu%F9mVp844yjA0YBLwh3ncfOvh+0BQ8g*Owgpw{sp8nZjJIiY{LGX! z!JQwZSggDl&EB~6*vC59p=_Wy{@mSwd$b7QWW$Qb?f5wFaE5RaL>VEMCJu00=qvoo zN#g7U+?<%`Gg88cGRoAMiIlDMWVwspt%q8N7yO?;FjinTU8(R{#fM`g-b~eN{tD6` zmr;<1vQG2nyXVy7?G}vY(h0j}{pOoHO;vlqlT>*eTEZ~`4-e1MUB+EaRwBEM$;ZX4 zq=#tz+`8LG0n0ugeq?KCBLF_Z6W=6zO6xg}1J}eTbVKh3CEE}w3FAjiEp>{LhR3~{ z=W)WLR4|XH<_46drZAG}Q$Oc0Eou!}tiXI$x+aK!{J?M?`n#2u7I9`L3VKn8m*&dm z-O7uAka9)E|(qiL;5MWqil$-5Gx~h)=LEh{Ch5 zsUOt4J@ezv|e_9!I6 zgy>NV(g~D83fgTkm)+gRH&?wsQg&ZQb+|qlmQla9=Q+*Lr8WHPwnkyAC_8$mAsj+K zgTmJKXBiN<4j*}DA;Qq=&izr{BrT>%BkjsS)P<86i|kT&5LVKUP4&v)ljY-so6^un zOnS=3oC3ELLPbzdEXCETI}+OqnFmoW$9{E{Kd9;O6yC9ED9%d!|I%c#)%Q#UOIgAL7YexUo2<;_*ZX* zJpt;SQQ8RME}nECD!a~@->NXFSlDE!8u8BJs+&lpsD_+BLCI(I{M+e>LgRoFkU|TW z72}Q7Y`mJKm}^4R0^eZ6s3ZA{7VW?_>&`-XV6}Z<<^?y8Sov)O_3(&_JwPF;!!URV z7giQ(sFQCcg2|OlYIT{tU@E^ zs2dLlJo&Y>RUlL~#5N;aJeEtUlhxxhVQZMKrO2us-h2G4CQ$1hX^c9W-(q4#N0`Ls zK^J%{qkJ*A(I^gdfz*y z^l2&`bwRbfeTi}UQ2rZ}V(cCXJ_aLsfX#cNxRTjnElFs$bTj%xW}!|>3W0t4hTSMp zvYytA8}5Ct4}q5RhbSc*NsK$p-_zBTSr&RuNa|83iD zguhaTi_%EeIfG5P7J2bqIOb8@{XdJGA{EI^pWko2)ChW_F2livwsg4c??3%SK@^!< zi?k%cIYJ947yjjbE5_c!GjF?v?|7p%KG*wYh&Ga*Y$#g6OvRAcdqV|oQNB9_$ZCBxF!Zq-;3Zo6b z)NuM&fUY@%bT$5-EnA@3E~ggmF&Cq}Dr_*z5&S;GHjH1U+j$WszA{S6x>~j#ODrjD z3tDrS!N}Vk*nVK2$#AvXFgMbTl@l~Mvd#KS^U5Gyt@sfFCvm8rFCR`9CIX=d#vRg z0(VGiZUoM;A;!{qeT5eJr;wWL5eBxqra@+81C^pqlM+OESC1{?|3Y9!OG{g8&W{i! zmv~0fOE#Z8t-2`uEJrYnS;jw-AtV3f4o=)=g`0Q@OrKa)_8~&4hjP;9t)+M&mx~vmGRgbhdMUoq8l1N= z&Zv>{2hX`yoOH4j#QaI!i|eBMz9QR7n2%#@#0#ZuQ_n^0zez@y$Vgg{342m*JFdc| zsMCfJHN4wxqS#f+Y`@S?D_uvIXa&%b1y+f`c+j}|J8PRt9Ea4mmz#M4*)4-ezFUr0 z02CqG1eb6_CiuLeMo>B+mK{$0>FlAOdN%T4607;Om?8hHZ9vS%>IiRj9Z{|PRJ71~ z-reyIKV8Li5i-}B!V0=sEY^8Bk$RCDc`Y}-ZxluFd4VI)fXmB=Bd6C#`raqe zR}5z6F1&~sTD#9Wa&THO>*0BK>KyU{-#$K&E9gV_b2d)b z(*{n@(+;X3=pX8~(1lk?ulKqV=9_=eex^J&9^1WFOILlh%bl!Wu9%_GY`F6`&(bcg z5vly9lL#PI_b|)lj{5H2gr;p=T1xx;qzb?{I&IyjC3`MdS*=p zF}<+5c;-OpEf__{g#EcmtI^cb*wONy{z^x-Lj(cO_ z5lJbc<{H4&^?Jj!%5?g@V&Nj^v+U}130DP{GVLb;e2n*p8O7r_jX)*iHA?N-xC-W^3vYEavuZonx7ksnuCNW16^3_X-+2O8qQP4ad`5qP z#iTP7)3|<$vqh3%M*i|C(yP>PXPZ?8^M6dZBz6vF4rbLJUzVo)js@Tb=my)v*wpE% zBGDwyq{ehz3~N@`#Cwr^8< zK3pnaDuX0`OR7O(y$N0>FC^ehKY+TtMlg`@1&fuZH6Y0)e5|G6!_L3q4+xDn_8U99 zGp86G-`#8!0pu^X1GBEx02z93L2>GTw9Sw51z!+e`Sz;govfW-aVl3{17ETb%7*sm zcSnJHNuu}oxfeLSw{pNur>9I-$Y&#Pu@P8<0=RN%>H5_zM)@Q8CvC)IQeN72tMebW z8J5+2v>a0A;T)0G3qNa{<)1j8Q{7B9#k)z+1-I#a)V4!-OYFZcwpx1FYqpt*`-*_$ z(~b~S=N#Yv{XLbkIid66?mU(hPF>NVR2SkjyBQ}*62&EuIeO2za}#Le8~|TMc6;cN zBen9;@T%No_Z;<0TqT3xP33zvyV+O_aag&Yh`}{q8g+ZQmVnZ! zaHoC+@9ktD7Tl$bI+>dOfm*H_vPj@)f#e+7j@767K#j5F04O13!kul-J*__8UBqHW zxihy95c)M`YWXoxB40+Q2ilgI_*1#Tn6MW8Eo=Gqktjt`dIDW#sEtz)ICl|qCA;{? z{Wc!_;UFjM*pNCiO40ucId2-v-bR)w-UQ0e<+{2Q?B!|Y{Y{QlvJ*d5n#rE^z>PsT zYJhXxNH$?3S-)Ndmwg^s^ksQN2J2rEVE31=W1tr=Xki=>rUe@@f+<0kwr`byn_AsI z;7m-o8{D@jt}Fz|?!@t7RiQ1l|KEAGtStHyQ+c?-|MZ9!l^ef_Vo<5(S=g^CEaj%R zbNZNtta!7!WMT&Is*<3#WPPcvS6I_a5#|no6#@)1*)^99r<)w7>SFyUY^#$Jv#H1S zfTR^4>RlzZ(3t2cKs?wFUUCzK{f$^#TG+0;=#5KYuvXQqQdSxF=`kP7J-iV85TN=1 zTH0j3t1Cti)xMuWKMl{_dT{E>{Q>eN6RW~i-tE3L3xV!mKIYfHd^UQ*f(?Rhc|mt+ zdh7G?)~F*cU5b`S6K${QpGgxi-h3}6`fjj)^4^xP=d0HEdV98o9qO|XPogLPS>iIrND<%AJE8ZDszALVtC#72h1?8$){b;ftbRrIZ) z!dPC*HCU+?Y^(ja9rwgx*ZBk*-Fv?e8#a2mW_>D{fgSfiTm2!T*W5U@_xmAz{-pyJ zIoy_g1mSxE!Ce}se{Wb5h7PLrG3(xyt^@Y()ii8}f?avVXLZ@aT(st!62wNNJ-$+R z*m^kqVd(qbjG+Ca9}~(ew23Oc9fczc03rOZO8$)qzY8nFJy`#%zj&wInUG z>mGntFfvQ@JVPn5QF_rkX3*X`a0m3{2OaW)uJ+!XD)oARP3?GDy3%9VFKOqai%MS_ z_!NG2S?7!j%e<>hyPL41%P-xF9<6OO@3tZzh^O3ED=834TiC-0$6?wOzNwOJ5Na$k zD?QL!`K1D$aD1PeYsJAlgF4?Y_fRET;Vt2Qn4&6!DC#Inles50XWNeL7<3F6AtnX{ zBAWQW8^IL$NmjF55BreeR#g_4Np*U`IL@65*7xal^@*S8cygaqDXP$DpidFg;RuVN zHyiWIPyHz4rkYq`?vRh;MMEdO$=k1+}{zC7~Aqcf1|Zbh*So(?XcmJ*%&Ca>vF=CAT; zM_dL&h=H;MYBVjUa8tJHWUqg5s83+_5(a8g)5(U+zSLj!K6JK18Hjq|E~~J-!E6Gj zRH{t>j!Pr_R2VVzL`jSeU~%tVso{egGyDo1YiH_^3jYwW0ASk1AOB7T*+EYROq znMliuE6apt5uQ|KVah%(Ov5#N&C zL;Z1>FeP*nEs@NFSAiTHn0sR{!3lKu=`XXr27&T}qGs}h*zS##G=Jg|s{}p~grr!Z>rfTgh!wLW zX0I_-gh(;U9K-J!F83k1OK}5f@mdy#f6dP!>hmXt3#>!B=15m_cv5)H=!IYDoSJus zDaj(4rT~iUv>JWj6YW3;xUqFSWz0u^UD`X|E+fk+fX>@rR9LD)8gss?l;^; zYUoAu!rfmB+MWoBkpkF*locawSkRp{`Hp*!MPKZCk8yw(Rw7SwTOJqceT|(&WU8N} zPOj**RspUz=mt+roMvY2^Iw-VxRIEOTTW z?^GJqc?!)|93_RZ1munhZfOCq zai2}rX7wZ!$?p$D<>W@-mTFt-#IT6nuf~H;@(B8Pq7UHS8>awpD{RN;iTzT5;xjf+ zv_l}&N8oUqP#NePj7{BND|SURgOZ!;4~zkK9HPYOXb^^2u;&oY25X24-jQ};pYo|alP$nG9X!SeivOVOzzsS$_JY8) zr$%pW2#fmYN#0rJ);e_2#aC~~buSvLqn@PmJt{e&@n}DpZ={IFYvkzTNs5Mw8D)4GqeF_qo@A`cq@@O~Vn`(Cf%+RoYoDvd#i1_EqwI{V`FtKlfEA*gY3Uag{)DNR#rDQ;AS z>$EKajinBhLUjt^qP4@hvuuE8i!74nDv09keC4IZLPEI+tlVLJ?Tdrf!$v{R_nY%= zGG$~$R&H{LJU2^eF0N9GhiOg#xD$s;pv9Hg5wgff4g=z}O3UeXXb$&%TFC}kC@ zmuVIDfYgKPVsGx8NL`O7d+Di0*@4(5=k93C&&BwbPGMiF_xztkV&wA5ZPwuFaoxU< ztLFfm+AU0&Vee|U@8&;>%$8zrZM}E6lI9JDKFpkXHI1SU-}?ex$^N6?NZdXX(fKUK z5O5XLXt6Y0SIK*-@0^5=QK%}aGIev=GrnDU}je4K@po`~l1%9)S!QmOY4(mAv;1?%+Ip|Z_!$PxO)o<%vuEzx> z)t(QKN=>!wn4Xn?)%@-hvBJ%pi>HxZZ8Y!0UHjk?54-mS1f0jSspvMJ{ov@1FamSc@4m9rQB`#$06m*x5lznU3loVC- zd$#cuKdPv@d{f8q)j1b)r!|+jpAF3*0LexubX; z1=YK4wtcp46E0)dYDG{?M=z7!#$l$qIg4>DVV-NM5Bk+XEltgav?%$7pA{4E z1uk}74poA?K&Jm2JYUGQf`xwKkX+zW==59pZ%_K(BzIlz-o4z{ZZ&nnGY!YrNON_z zqAh7MR;oF&4Bizw+oUs+d^*!E_gHB_Wfsxf4bUgV0c@Z{*Fa!3(3v1;rG2^`$Q?-5 zv*4-r52j%$<`LxJg<7!F%RMeR;%epEe`@!=UHM2Ay#`f8Y3BoB*H9GKVU;;j4AFqT zL3_bXNKlCx&;`!ztmxum)jgT9+jk>ww7|0U26c*3KxBvR;XrwNcPXHMoL=aA)`-ojxu+i%3VJWM zUIuQiqfMTEyccdp<{EK#t8a_BeJ&%lsb$L21rMw-^r(RWp^3Ayp2T33q}NmoohSN{ksZKUZg581z9y zV}Jgxm7bDSQ0KRuu~d>}ZEH8EKU-LC4hac@h@!rZUq1HNJ~qGs2G%DDvzX=)?{!h! z1IW}WKsZ%Mow<6Ow9ek2wM-SPL@mMCxAo!prKZ6D#uTG4!?@7fHH z7)MG5brZZPwSp;cB{9P@kO6**rq<>T3I^o(q71pGfsb5YDqFr))GAeCyA>ry5F}J+vDZ|Q`QC(dq<`S4 z0-X*7Hn)97L4>Oi;7_#Mq-mfJ20EP}Yf(vsPaQdPbMjBLV#CbMc>2yaKrt=%X}|Nt zOVQ7{G2hthm3xw9bPVn;a@7vr%)#B{Qm@99Adt&vFwAKg^wGW!p#Zs`7qx{`r9n&B zg?Sli?1MwUT2Clk?m);IJjSxsR-Kng=b-Z~-&6VhGsg{(>pP>$I>)-@iw_>;${JKPYN$%_fua1#cj2E_icZhTtw<)UKUdhJERd;nv zAT&}Fl?|W@#sS}RIgOCv`8yrit)d3Rm$+JyhI09FgfQH__T3rKb{w?27tDC<%M7oF zgI|1LdaZAmz?wP7;dN#9K3RJCOmm7Aw9Qo>JYuuH$96Sc*BZH`A8f}vtea=#IN}J< zin!}zRpqSeFX`-7n|dD2r7ox3AIrZUkCY&;cnW;O0lCt*C>RHdc<@5{9pmjlVS_NEdG*YAqZuI6!kG&*%0G?Mmv|U>|3< zlk67|@B+2djCTrv`4ViXSI=Z`iF8}1E5e@K`=wIe=*4Ct(b2yej&nH?t^oGOgB@wf zD9^a&6vEH#0ejPDwuH&5^9+ol0iEKNlr*vmQgmd7I9|G%@KuY2l^p{JA$SVF;N+<9rJ+0I&wvuTGlvd5@hcget-RR)2R8tJ;N*!T=Ny;y zW7JQ|(u^WyK*`!O!i<{Ul_{6s0Pj|ew7e&!MQwlSdhcxkU`XW!m?SZud@6ECO9WDR z1<|s3tB(PC7KN~G_xRmHybyk=P%6iLK1SdpYj4}%BV1&C;Si&YKSu%gM1l7!o)_Cn z_Q7ga$sUgg;YMd>RD&RX%0vw0laNCxj&G$}}sjN9OFl&4S#c;7Sydy+; zTT`zbl!se8%e&zTa*u<*gmv%WR#gxs6IgI>dBNFDoBC-GWAY#Jp9B-(NW@xtCeG0q zXDwp>T|(dU%TSyJA76U!w~StcC_KKreRDHI|86YC;M;JLWM6L0f9+x7wi!29qTRrm$Be z%TW9ApehEN8rFM?D2rhQQO0?sTWB%7iv6?g{qWBP{*7PX|7py4qa86oHpMSMG{MqM zz|_ygu9|uc$ALB+PF$J%iQ(A|zL*|2YF5364iSeRW30vyBUsrfZj>YyjFohM;Q%#| ze;?64NM>6xiIq9w8mb#15+fl2%K9zHVdo#1q>dF*Q%{wAcnUs=6Hr<$D&gja&fz1C z?_5e_^&hhYk@jh~>G(f$UiLh&!qVMx9)4h5P0}Iw+T&xJ|1=T7)Te5sPP2_n8X0DAP;-7>|CSokmc%!oQjDX`+n%~{KRrs)CNEKkH(w*^VID)4%QhI@2Mq* zn|PZtt1R@wQMUo3*Zf`=p^xF*fO*>;D_%6^%}VQTw8oys8rPg1dMkCD^SQL}9FzzP z)mOj8bH89;vJd?#iuxwB3N9y1{D)zGyq(DvPsznfMY00}4A@IT$E61-5B^Sp>DERnys0Iqt5v>EgX$Lief5+mmzTsI$FEO>*$ zb09{rXroyr{XcxEN#kS!pHBp~+jn!_j;>E1|BZJCTxv>c-8OtCG4$4`nT7A0fS5c&(C0 zkD+Uq)FHgfR-5E@r-;X zTu{%1ZdJoKT(~xmnam4XlF~{rMduASi*jz|_Ky1{!kN8fpTx!8@ru3eSc zw+R;(cjt3?UMv1AzA(AsS9cyq^hmWc7Pu|AaxJIr&oMLV-oxWKr+(z0^YgUMdYlnPIlKI+c{j4e_#m|iL&ubxF*`t?uF-66FswiyxnCV#5sOtg{|$E0-yeF zzod*RK_=FfAUB1bi=M{UFi2$Y3r6p)ozZRn%-xcY6FfgK@w)gVF-s~3%a{)(^HpE7 zdO9$vB3;-24}BRN=4W_If&S z;lo&O&PCu#IUzOq_d%DHIGHVPyQy%I-lspu-T!8~{LX;SaA2lYhlRHmQp(peN_f5yxI$cChZ zHD7i82Sd@BY1Q?*HG*co+;lcwir%4?b9^#yVmXsLUzy+&)Yk~0dMoLiw~1ZVEGcR3 zH$x(d^Ukm^S9*hy_7w!nJ?)(bbzL7#RUTFL`ai_^uO7QkLmp>d*W#cDwb03Jze{SZ z_K%>uZ_s;D7#6JUZd&kijCX{Lv?RutNh1bPb=qi@M|;w^K?cwOXLB3wB(mgV<;jdI zCI>r@P#mr%G_C@4D3u+OZorr{;_Y82a_j!R8dbV%z z#fK4kPVtg~1ACrnoqhr`vfb&|mx^U=&dIq&TJZE)CIOo3#TGFe=W_KLv8g6FXGgtB zE}rNRZfY243zOXsA~~wC>UI(`O$p}r-djY_T7dzpA@rBCUZ#~%zHtNHUNz?%_2(U9 z0Jf1fgS09&BB>~H-NiyY8;{OD^C1toW>GsbbRk!Cwh>lyzNbCzBHy?K&$IZK>6xak zrQtjd3^J+{J*3}2w8V>mUs9F~LKfnPeyBWiu9G2qk={B7MkZ;a3Zx}9`ASKT95 znf~+@izEPZT_o1KC!t%&E#qJIG>Z^R~Z)N_q3PpMnI5I>28o( z5CmxzrID2GkY))5MB+zx2@=xXEDh2K(y(;H(z(0OyZ*2D%YNMJob#NyXXc(c_i*W+ zcl1tMGV!!5K^LLy4ogD1e?}^!F&}6N|5gKqDzO0ZRHc#KOQW}yimiqqCJm~p7zS%CQm*-Q$x!l=Q`dMpI;G+7VRh++n`S z3Qqkc4~uoD^tu?AkZ{{vJIbz>ZqQyh*6xtk%nR)m z@O^SFO!V46=+H5@|D08)-k}NcO2Vas_yhJ|? z|6)%-D>zFkPUf_SQ2ae;xz?sCTDFRQdfpwa;2R*-uj1o8pM#We5<%B8iGzRZV$xB; zD!Zj$APMfE->gBSr$Q#XS>CHJc3VSG-q{|rZ+D->dzc~_H%8skx|7PLO1PD8RVRLX_tzj0XemsPc50c_@BHW|3OP{QGC%7Pl*@e- zo?!$+3x1*LZl|Ssr1k*^#t@}}qV+O~sFi>aLmE?+71^XOkn8&wzK4SQLeL%Tv#sNw zXW!YB9!Bm#A7FQ0;0vN$WEBJvK8uCu(*}Mpho4BbhC}mj)jsoPXCZx?&i` zie^F(LL`iYM>x+L-{Xo9{6}EIfX{jb$I;T-=i~TuOvCNt8fE_CgPXJ)RFBtf);IX2 zn+b(MDvouR$a99SezY}jZ(cS5O4l&xg78Dt+q?{9G5=>>s@LQK-;fJN5jV&u>Rr9v@*WdZcoZ5BVx0 z2c@%_=TK6+8R#J-9@$MMHgPisDvi&7#75@>Q@~npF5>G=(3u|0q=+Kwc3&HHKD|C) z@5GEFdZOlxtL9p80`L(K!UJf17U&twV%Mu0Foqj^QkqCUY3c`zg_@x!noP|3S1u^? zI*q2MXtnNbQ!&=`jLIk(o9c%0y}$1Z{;znsonDlIrL6=<`;>iJC2varP104yWJ-)b z&N+xN?;I4fw$XWY4GkKu*B=ZpPWwH%0LfWkh>}+MvG&A>b1p{gWncPU6Nu zT>3pIszO<qk_sS}e=YGnzdM4Zlm%5^v9~tAoMkDc7}_xy|H8R5RuhcMlA9ks0wWlu z09%^uUE21SkKJ3frzd51PPIg2X7#Vd5Ajn z`~gCNEwe*RtrT>7ry9&T$ju;Msv@A3Mevq8V*ux?fUSI>qU^7oSJuA?&l5x8>4`3X z#!z_Bjx=Zw?i&0vuQ2dgf#8m@{P3MG0}EgCBymZaMRTb9q`3+hUqUozq^>o=Y z6q_8qb(Ib9-6TQb>I3`BWcI5%{6y_IgEZ-EnlChe&1CHr`4Xyw`$teKH8P*L!ia^# z*(W~V!f6BpsWVEv`=-sX{%c5KKY=A))Vua6w(l#>@7#eLcD%X+XQHoGjSjOtVwy~L zhsM5>*osTyK1RD`yv|#qRRlty!~0sdq-!GZK(B)+MI6s4Z-+{k7F3b{$!KO|5k!P2 zPS&SPW`0+Ha3Ahs{PXksKa3Hjt%tHIg1PAgSwz}xt~9wUjVMQG&dahvL~7M^_yn4K zCRi+fHUsAGR$c#;jrwm+Q@on~(|~!FbR%mO-aL&2dW?}?_Z6)7v7NSFH;P9qD+K9b z?3uAX1=w z2EMd`+!pgs5wjtB1R#F5Sqs(QWTU++!83jS}gq{dimv?+*)IO3tptdixBm-6k-gDXUI)$a^Mxahwy`G@_>Ef zt2XyefSxGiJa?T>bI*tCHUg(KLcInN(w0u@YiO$(9$B8=A~{wR5|e$I*e_oPtcF(x zEM3RHN^!jH)fypogswHiAoWf=khb;j*vPmdz#Dp2vt&sAk20L#WOEdNI zMvU6Dngj|1NmKH!ejGKXWOwr-+IW2L>{9T+0N26The++jMf{&U*-j5Hxbo(WVGKEB z?+71xBobYT%hL$I|D~mZpVt)6UT`lV4{$m3QtKUC z28Q4bm@(6=?i^9SPJqUOg<{lW;l(r!P1gGvu%sP>z-)`N%r~-gw|vQQAKm!#M6P*< zV%a|oU$D;ZlDEp%!hOb9u+S>zbn5;z6|k~T0MQ$RcI&@zZlUFQ_#lP((SlvLv}{ zeDTWkG^DX+(D^LYP+|~#d%0w%t|JnG@mO)eqlS_}MxM2@RF)|8CXv#x!|EwokLk`_ zW4oWv;D>ki&cd|4ljM5;*P1&wV^$LCKTStRP^-1F3lFiP-HS%ezr(pDzr?x`eZx^= z-HwfZgK6)HV8RE(W;!AU*y#7LAbS>1rXZ|qXrAdW-cEOMaj!#Op$`aSbuh!2@2>&E zl!sF*wla0M!MfZy>u#N4zL)e8Vov=WUmTJr0q}5aXqe-41diCW&9{hKmZf^9i~I>l z-NrBRf4gW~eX?AJzR)>E*!LE_fo>szP)$@r%IHi7kx1q^R>#Ne*7xg}pThWQ$HH_Q++! z)oEGpbmpON7VNKccYZPCj0CWa%1`CiB%BLAYXf?Psk3OuL@4ZMbYhK|G-&F8JEfC1 zR&2%U1mQn1J`)n#AD#Nv?tF)yA`dGqj1Y@9tD00GS#}1q!(X!og$j2bXLY1S=m0Y}(qK7C^iXfe-BREGr=9)$fSjn?V|9mmjPU%~SO-j8kvvt$V1{%I zbpKu=fgT2#=?iJ*$Ia<@)(g^VQ$4okv~$~Y>@VD2a6=zCT2mI%gc_#WVm!*fe)(5C zcs71Y7}B5754sZX0-27!{nqYN8=;hvEwn@AxCMw(AJ_-Wui<&CYSv+GX=$ONB0 zL;S4erGzB|ihPy@o5BVGNwjM+BMkAS|t7TbTs}}Q?$M{wme*vi*_vG{$noTr& zI?b{A=_z$7oIjQGnRn{XhNeeeRY$zOQyY_u9M;!r;`7u8#3Cgr!Mp{}w$l|pN7Nia zLg!|=1`v>CmZ8{0jNZ73yDm*SkPCC%>LrkAgBdNArIAX=V4rza{vjLkR|Zsfsaa39J2rgJ z^u_j){r!uwOE!#!m!C9nK;MH+f`hT?0}TzW68%Q*xUu+oq;A$(u8H{>x8CLJFfk7m zaoV)9jj5(>6=FTwT(=Y_S`>f7({s>Je1#+C#dDIteDm?!ImKP6PzUoqlz|49j0kHe z0f~O{a(;5(2I>fWkURz4aLs-I^;6>VnCH8@;VcY3HH+u31gj#NQ{UEbQRco%CVI{} zdR&(H5%2Euhe$<0QbJD7O~%4ee!6Hei`@akE^o%4e2bTScY$XuS&u^d1D5N|E1aCB zGxG4B{|K5}n;tbevx~oqAORJk`R!WQ%I-OVw4obNH6 zSlb=<0T-7?sRt^-MCht^-N4u8C3hHD#n$U=^ZkCk0eQ@mR5ZDgZyX4` z&xvN@Tqc`i2DGVlC=a<7JD=)NFYxk9zHneO~8@7nE92GUyp>7F%J7b!>_{xVP6Io ze+Mh^-;;{IjF7Ks3mb_@XTDR z*mxRV7OOHanq8Y}?6bbB{>%Akb=hgm2lro||D|M<&h8|1x{E!eDqsU$_aag4`Vc`m zx#)oTt&~!`8vh+OmU|atRHjn@8O$n5mkpch(bF#&nnnH8Q$!$FDOshs1LWo7;H&rd z7x%ql7fCS6>Ze;8uNG2yc9j3}M2B2qb{Ec3{eWcsRxFaWrj++%XPNy`-nI3do7-r& z#Z1%`j|VNXYA~7LsY?^=)oWJqDnOXsdmXPBBlv@?!uR8@MbOV2A0vjmP0j!9W3caO zEO7p^58MUfucWk$zOl?2G4%EeYILsNx|jD2NlH3kLFq_<=dsh87u9$y)I5?bGPx1W zTG`unB@3!L2bVtT3g6zycm0lUK^L*Z>S(%P{%edKWN{Ohd~1IxI=RC+`>q%x`AE0( z=25=Enu!D#BTeL-HMk;6Z3C?GT;+&9$y(pk-^A{e@D#%TNci+0@`Eg64ao+iE%owAKZMI#_biE<7yH?qFc+ECw^^8@bOn2 zL^*8Iw)^s48p4xs-liwlRlV;XVx^UJ-|AP~yDPY)Wz>5m0g@7x zv_G{?Q=AvGI9vu6*O)2oILlsg=*Af5FXH3BOfcd-Y-iv;g35R`%yNP^o^;;%cFHIr z;ye=lE|~HtPQbNID@mDX!G zjrb5<(zo}4!CRH%L*oKVz*WV)_MuT!KhLxF(%7vNw?4zn0Ig#PRZ05gW5qZCNzy5mon{ zL8CK*bJ;04mb(?>v?l(>-Qqt#_Y8P^2K8i|6+;+=KB3eHcL(}t3OK@lgU_}RTDl&r z<%O^9H+suVqjER0fmpQW-OCe#BFLC}K)06~0w;cwig|>rbE3}O3c5Ez_?~@gCAoX0`v2FPbAjT)R zS|79UcYcZuy)9bKa`iu*=w@y6q_8;W(E7>w_zz^}>Nb*D2Zmj_b~`7Fd*tzO19YcK zKa#{w!Z@|vl!1-EK1C_!jBYxDaJp^YeGqH5r~c34!y{P&mTPV$A%i~wLWEYTaRr}^JiN9su%j9TKuAvrw z$%_8eNdAiz_`t3+-wfsr&?;wrdG*YMO{60T<~6Qt65Z`45=_riA-iOH1_)Klfc_*F6CX+ z4h;36$5l@OL;SU|oNe@tepN3bcyR+xaAp-{nA=(b{hbgL*i!cBtV|QKhO2>en|z?c zzv7A&ACp!8oj$r`b?=BgaQ^A|O;cQ+D!AOx-+Tpk?@a;vx;!Se0=#VHV6}=ePaWrG zbH&c0sIwk&lf>g{sp@myagu%uLFB*Ar#bwkAR63c1C%i>bjXYDsUQSU;{v7)#%%0Z zW`3)Lg9kD3A}C7Dq)VoWid)ICxM$@Y#A6wAP%-tjqKA+js?0B;-0)#SsYz4*Ioo9L z4hD-J=96V7&ORs7g?(}P!5~kpJWH%3=Dr`s^qPSiQ{CY7TihYbM;z{Y5S~C9l}fD5 zAmjdI;3%vO&|}SQ$>-WGG7nhdVA**AvYbTFLUy5@(PU?g!Bv6N9F`jHL@pi@@%J}o z^R}ltEm8Lh6+K^t*bOls7| zb=GZZKJ^hW8npa(LWGX{+8_9tpL-g%%|mGhTLMTeA5m;Lou9kaui%y>slN-cO&xU+%@Ci>c*|c-bloUIYS-oMW`4WO} zzSP7^J?_0P2)V0NN(xo~Q%JY)Vq10C`1liim;=vLYwfY!s zbTv3ELi~){k3#A_l;N!j98MhDyNS>I+jDq6rCJFNH)A&D_p6V%fafU z?$Q7#D8SGx;M^2wi*c{S6x>-t542BtE@)tT(c6maV*j-VcU8k?st;l2SM-iQqp_*E zT0GWS^(_9|1~OeTFY2`HY^K#pBazqSb$XoefbCiQ`WL2-LA$EpnP^Oq1Ie4Gbmfaj zPB=Q1Y6p8I|Gwz6#fA5YSv33i^%+Ug-|`baT4$M5{Eqftb+22qHjf8Xi?UOjgG z=!f+#IlA}egg7h(7gxOHnN7OotgSxT`)i3`y+hN4hp9|tk3P?EV3h2~kHz&F!|gwI z7~l-B{9qPHJ#%Lo9I?RtvH>dh3S-c6ri-n`pnG-CDj)I18RRKAU$Ubp8n(Vfiw>y8>v~vEOW5W9d)`gz=DTH9tXOmZ(M$ySpdnqNmhhJv6+~or`|vJqH`qN}0XA)7wCE#xCqX>pyn4 zvYQ?rq`Q)0T7lFX?`q9`^URcN}k8dtZpg< zvr-bx>%U1rR^?j^{cq2}6jAbd!XXs0U1tG3crQn;0ZGUMErqOBHy8!J2M}Y>+g#MH z-uoH~7$9q!`AlXu~on0KJlIZAs5a(9xL{%war5K|5_t;x}Vqf3A;joNmlTWx{G*_Y8nbgMimr&8z!RJGkc zt)vMiI|HMDTNji+WYYx+G}`(zBGzt#jzuLwZAusw);y7o_Yr+XWAewHpFmW7??e{W zjTd`RnN8QvzP}lme-ctq_z?`Uh7cT!7@*6amID9qlgen{DT!>#AChm$Hb^mbC?%Tw z09B~Crc775`ibM4VzPthf)$zT68Wx^`8~H$HdQ+KT&(IaQx@3kxB*Ub0d>d@45=Nf za}7cakp*O?o1g?4Fw|Klm!1M--R70W*vw@nKSR19l;Gw^86V>i-vrcoXbu)$I7VW= z!}D%f{n^NkKCWwvw)O`oJEIFNoU^iZQ*P}A&ZBo){uLZ4ViC$4tz&9Ari_SQ?w^ZB z9@FQhYTvIM%84qcEsNRI!@TMJ)KErLYewtQnW>R1{wg%r%uM}jS1OmQ{+Fl;016xc z>)ObLn6Dp6z~^BwT#A&7)9Yp6K*G=C=dF<`kNZP2SBk-$bw-3*L;fmkUqX2I_e}>4 zyXeH2Zi5W=>lK3!%o>A%vm)ZnCM6g@B-|aW+67Hk+p)-zDWmXl!%O-t$EN z%O1K-?Y;^%Yzjz2?y8U#(DOOewJGWl;vZ1W7-SVJ1l=Em3RnQV>h=q1U9!BOgmA- zRcJLP=>45u(DsNaDtQ933rC*APy)aq^uil*2SxZp13IH`%9cO=v+)P-fJ42C2AcAQ z_zQ3bMw%XGJD-eTi0|kpP{f+}NkHuvtf?xrGhkQav641sjga&)U6+LLaKqBl<7aN;<2 zynZ^5NS~knx2Pv^xI;#BbEOiaR#up>u5L%A**` zsmy<`e*-p|F>l&Bx!x+;fX(o%o`KN#ffwTNnbm{2qadQ=X2`!GPi>6O7e4GQL*044 ze;J1?Vm59^&99Xk%xHAp zOLO}WUaxSg%n*EADVGUlXtmg$rnIi8buaJb8MmM$@7j+EEYH*1ohli0%jvnp z(bkQ(xDv(~14jCbdm94`S9n*r#P&{H_pfEV$;Wd?rPM3?d`uju>5itrjGs--nG%d7 zdu<>uW7_u-o}=OtWYq?-m-}HPd3TTXCh$rigMnu{(9MY4TTG=+==gQsc>vIpZC@l@HtzMS%t?e343h~MzmL01Ah$&w0bPTCdEXDKAj;qe>ny}wblYp zXTy=#3O6Q#CSRFehQv7~($h1;PlvzeQ+kgjaT6 zo>pwo$DDm+kgU?XjFxpw_A)(qr={y^DuM-)CN3^U%e{lynIG5BX6Jwnyj!>Nr0xS= zs(kwgbwoM{bTI8LB@02f-{gKy8qdy=TQJv`5QQi$TT#k$T0!mtU40yG;fNwQkPm;z z@EVu0sUQM(jfr=i^Ho>hY2TkG!QV-G=sOND^e5XxHkCincn|Riyl#G5!F>G$pBq6t*#L9E;lyB1{Q5qT_{dVq z+r+~X?$6hzcOSq1t4L%s|6--FTdgRojcoU~ma2?GZ+g#7GQEo&CE}f=ts2)FfnguM}34eD= zyG+yoAo1WAwiA;QqU}Mfh-=ubEc8kupb9N6+-+d9vSv(;DLM#@!Z7_E1SX9Jk4vF) zmSu>+drX3;2r@ENSd(sQtoIibV`A)0aKygS*N}r%DQ7d#jc&{HfjH!36G{iTjg#EX zVmPX<2FyQVJDT6oxgMb4XJEa-@zm{KN02b*xT_8Dvj+%1o0Qb`Kkwv?2TN_dtR1P5HN(9YOR5QE4aQB*- zJAt;&doaQ#2&o56n};%F8L?v&#RQi(kalT_fS9L{!0g7~>o)+|Fbc+gvW9{@#LT+! zT)ygJ)aw%(F*7QZIT&7-t{~eP_MNdl7&DmA)6ffM`tU@L-nDR$1RoY%7DHC{%V4uJWM=CFSa4-t+(!on3R3)4 z)I7p%9@C{l+yVtmI%{!ns?1bW&|c4dIgaF<4W5oIFADO)h-_LB3e*u{uCyvfk$vl4 z09N6*V$iEWsK4Qn^ymq&PR)x!>jWY^Tr?$uPa}bvg+Cu0AiNJSXVukH8lEI2?uE>c z4-Yk_`K0v!ZX}-mGynd)e3BniYhneZ%wd5y@$22EjIeXWL(usgI^zqPZby@w&4~;> z&~6gNdW^j&yJMF$Tpzt701Im~SI_4y^Y#ifg=vXCX4K=qsOYpNQJlH6AavLld-x-M zi%EoFGA97tDBeYC5%v3Rq~bd&3M(0JfB@*?cHi0#*_o4U=#f1x!Mc4!ciJmXn&@q6Xj z4Qr*QPX}vShhfr8#4w~&E~*rErwHBg@AO?=hPd_$Hg}=0;NY)UARWX+?X?6pkmmgu zVd?zpi+FM<%2tf#_Z(%w^E<l6AJHcR!I@Yg zmG-@_d*T`}qK;lo(LWT%u}bvn$&vg^MOR_>htO$~Ft7E)kG*TUf0$=c%HJ_wUINsY z&`MOGUOQmzv#Z@2E=y%c>iM^h@`QryIU&^gbmGw$FVCZYArAg>4-p?9mS7wsz9&g( zAk*6OjeRS~QXD#-x^)Sfpv~1Xe^uoXW)cPo#Sp9kypF3Fdlq2}NV1vVT(A!eoNe86tEWEB{~`@tB6J~} z;CNUHLP$d%Ae(K_`+hsZ9k=ntJ84rzw8&J&2q?T;6NY5mna{Pweagi`4FVlSsVXVF z?@GI&LwK-}d+s55Bbz^H(leadUPF!_O)X`-jil(g8@vqtXo-2fBGbnx@sE&V!c$kV zi#D+3PYbMhp>mAxNww_4)CIQv-VP3Un9G42Rspe)%fZgg>1hyEqPP0i#C-*9U9{N; zYjWX+x${BV2}uC=giTn_;4k&M7x;62Zb&8~t~`DKg9dSdFU1s71veM1HqqOB57*9o z`30Mxe-IRu<{P;97FV97&{L&_((=)(RZA>mUye7g?y}RMM3Q*m`!E7Ix|YJ^rp8_SJ4W z=SB3O=EGgH`NGZg3!4b@g=3p2M0yj_U;)y4dXt@jv2?qk=>t3vFmSu-|6SZ(ZvT*c zWLic0LkM^3;q3e#9iLAKo=hLGgIa!^^42z=RLimYM2U#xn_ zceyL7k{OE*j9np%g&daGbvJ+qfo8T@2x=DIy183_ATdf(=7igBz$q82SjaOy!^fsK0^)`+7OyKWxp$ zcf-kHcNG(0L?G<)ISkMQCU1hKr+=r*|K&(UDy?%|!;gj^ip4--j5Mq8EE4sgHQ9so5KyFFC;pw8B1`6TBE;e{-83g!KXWk+3R3N`aBlcWmEOh z>wvP{-^hHMIivYs_KT;5`TA$x4XVd8>G_Y`>o=S@O_R>flxtHzj_!U(J@8SSn7Ps) z-2^-oqlbA`p@+pouJHaf{NH(o66;Kk(_fN_+%yrvbLT+35zdBmW^>1K#r=ZWNUG)@ zy%=c`hnLw%@LFoI-8Jl&4by!tQn_y*?n%ian=eq(21t`CqfO5lJEc;KvCv#jBW=Q6`gQ;xb;9O4Up;0b$cL{o%;{sfwJpk9n7S*_vI=3Ra0$w1JkpcN)^LFpxEW@e4T?J^XZ0?11&FRF8WS6u4PfOd?%c2+a3oo5di zpz^!O2P7vkQP zG-82I?A@VXf6m19pFeIv+cV?4B7&3|i@X9;2pZk6$)3~rJTp9d{+=BXJh8moX}LIytc+@J;w7`hfC zBdY*|!y!GkD(mr|RNpP++9*-1i-Ubz5abhq@)O|Ob+L8V!I~*=h^@h?sEHs^{dE1y zgIfva@5n%Fs$ai}s712ZC;iK)Ynv|Wpk#+H*!9(YbD?sIYwJjPdR=KzU zY%bX`O8g5|>0OgZCWoYr{P-0-{Y&R8KF-Bp8h#=#gcupmV0E>a8@y?I;}p#KW_2>W zj7!qfQ~w<3+lTfc3Xi})j%&nvl(EOCa48<*%A*?kH2%h^A&o+%K2K&C@iDQeQ#5bC z&6jq%n@)(!{5($$c<^StVy5zfO7DjpK?}nE#TT5X#8*PLmSJoOPsZ@L{WRGY8>w#% zOBg8AxpTge_T1fU#UfUC~6GxDdU$)zn=#&8DQ2h{&(un+5RAo zvK#fYrSY(sEz|+L@k9-++jct~#>&23aQPEx%~$vl$51G=?H;~J6iq|^J#^h2*WvC& zBQ58@>v}I~Mj1@0=5aeZp=mlt>mIptv*c1N)v9B_yNHLv=(N8u(e@D_Migu~>@~G%lx&XZvvIEv2qZ-Jah7%Ch1o@e?K$Dn%uf z)TQ{`N2(#q=GU9E6I^$0`n{g+L!c^NHoz+fwsY^yuDWtFtjl^}!c%+Y7VVHd(A-Nm=n>dX)coa7; zciz?pxK*+e+G6jbM~QH}<_-C}7g09LZ+b8$z~)OrSDk)Ub8tCT3SnTn!um*GAzHId z3@fMU3y&$Jl~=6w@Mj?=DA#D6KkNrs{8@4wY4}(z7od$>pGhA3hiIbGs#FIR^Ux;s zEJN>1*fY_zzD-*gQY%i7M^-DqnlJRG1|yP6FB{v|{b9x7fi__^In%~#(^62XaoZED!@Gg;DfZk%h)}vLj?tgMJ;c#P~U!SADuh8`gY`gElc|)_rwDu>>QQC6}NIio7A#`+8_itH%H$3eUd}>T%+Nyt_Lmy>BRv*x8C@RxLGFs^=#YA@Z(p;#Q4U0qc}LIm*ME27xT!D& z*g9_1uC-~VB;#D`a_UwV_fCLVdi25MchQ)NsVY^860k^}_v^EWlC(KRU4Sxouomg9 zOe1ng;$33G!b#+_J;Zsj=yrZ~nY)a!juw^B^8!LV0@^?!LT+d8u*LJZ>)wX@?@M-} zISbi3@9Vvl{Pl%ZW}r?xbT+k$=ha$H6lJ`6wY09)=H`MVN43B`%x1yWq-46He*P_MdM&SF8rg~i9H!_h`dmPwpQ9` z160jq7GHA40utk_;nGz-6*x0xQI3&qhwt8r&D`1;;{_f{){aQt?h%uq<{@5%Uo z2Fr$SmOf#?FU-V#vj~b`^mha?()-)zO6)fV7iJq8KLYLBG+H~b5MmfwdRpsSo`(-P z2KUM6=Z3Jx$$YIYv3cJPR&K%=Rg|?dT7VSHnQkEYKG{0v^PK?bS+Cz`<=SK16vW;> zx~?*QD06cDgES>T5v)QavU21CKS|1(zhnBM3X;g$$m?dmf!F|y5&zc2>ch}Q-aQt8 zyEBYY17C6qKCb6!!a%z%t%0;HM<>eQCwu9t!HxW~Q>5P=$mHZSM*OMfeJ27V=hl>c zqf|d1%noX*kB-z0liOxi3hneRfnu~zvkL&b_{Des$I&%7)Y(SyTdkXIw`^nCE!(zR z%d1uQX4|&S)pE<$vaKPz``vy2!E^8XzUMjT{B)A+gkyB-e=sO1g2WzuvI8$j;=7ym zZ0bl?>gRX$gvaFZxEgcLqr6_*UoLxLELzuuOmmRIr8!UCL9oo2Dq`P z+$c4~Q_NYMf@|UC90sm*ZTDU*MP|^7=xI4sQ3HpcEvOzF@i=k~+m2??0CX3Lvq);tVhpPgB_ctQ6V2aV(J!D2eavmdKYN+cxiZp=B|gK5xTpN@)|*$M+wd#D*`NU ziP6Q&{(sjL5h|~MlQIWee-fPm3*KmMEP-Nc;;#!oS-%d!Qzvj&$^4-zhokI;7qJAb z)P}0bsleR`U=l~_(!jL&q9>RxdUth^_}5MMKAO0V3+v1mTGnqJG^dTdRLJ)b@@vt! z#>!q2KRe9ilq;gvRlA` zJ)rMPDp$v8 z0tbBOTOvPGI9%kTcTi1NqIKghO)u4syn0`VLwgz?I~#gm5YUtOH{&}13@!)obQ{?03gQ5o6L6A za*08b)mmFsz|V(#xW2`@tHt4NaAa@o*f)>#C zMJlq`BG8V6(of#=yJ#*NL>}jy4#wel`JCh!W>GlX;%TDb2QJ&BIyJ1g=5kMpd0AEC%o76CSlq z%BP&A!h*z#EJ=Zw*Za}@IDb3$8n3Rel*s;!%b|)s1_v4TiM9{6eS}dLXFAI7H#?Ur ze+foSc#h|q57xMf4obtQc+?Ts%UHg^pwHFJ@^c$Fxji)s6)t}I12femA2HtU6TG5P zPqG0Y`rI39#5bvac>L-7t$qm7GQlkPfl56oNIxYekuPMlXx)i#rb%&tGx?9~MSdvH zQPWUQZeGv6>_03HW)8SB1x{N;8$TIebArSCI>G1+HmaNC_TUM!e*)f>kcKnqlY7^U z2FGL*jvNKGSyU&CI75_IG;$GE9zU8VvKzxU%e+u;C=4BmS*Km4d`GBq+x9hOh!Kzd zHP!$+`(&K|+H6N8LVLEIQM~^@{gDU#f{WM*-2F@sz3IONeJJ>ejJHjImsn0V}exOfx5~GhA>9M+&$=chk_bD-Wr2MA<&0sgcT*(Lw`Y? z?d%DcaCfW1dJeZ1_TFSq@(x9)su3U>PFbze5x;d8PDX0D0thrsl`|_^!pS8AoGuN&TxUrUc2sT8*MxWL7=}^e42dPb1$NXf%<;Q1Y?| zZyMSu>_i70u`HjrP~Xa*^$ulpP&Z-Lj-ywP1r(f-_^=d>L_00rcR#BF6VbOWD-H9Q z#$RXDKzXcbA*o6w%|Cy>?MHn!Lj9^Cw@zoNtY@Mu$?P(F@apLw z-vOL{yjM}9IL1*(Z}ZT8Q2${+g~s$`zdhc~On#uy-Lx^X*!Vjcf4a2+f}d-k`LR~U zJ8bz2c@&AK)yEcm@%*u`_Uc@^Y%_0+iSM4bg|Cc4U-`pc8{oY>aR2X7yui?m_gCX- zbdmyAOon82+f5QDu$|NJfswDE^@;9D8)pZv49sDqU}6BN*2Y&>)W7D{`f9d*;)-#a ze+pA#3g3rq4Ap3${RZK+Ov!o^^rVFu$n=rVp59a0rFw+ugJM=2o2V48J{xnZF|3N| zC)8DM@VFE+zN!5180nP6fH|WKG-{9KWZf0l0vfbXNHKty`@E|hBP@4xHo=C0Zf}evcQVvMDQb1@(WF&is6KI3YNcC zYA2{4-eTWxKzOe~7!0ZsnR2=ppYD2Le*K0FQ1o+J?FO6i-qovxOi!|ZsUBUMdE>ns zPn#cPA)_Gm2NneX08+rwN1m{eeE<7vonH6%PmH31UGqZM5`4n9;32-T zRKF67)&R+=w@Pr%8i&CB55l&z@#-JEY>cT=-Y?!x;`uNq2@*oGMB6uK*^5?YesxXE zZMI3togX#3hC{y6F<(1e z^m4)%eT;7x)phKKIvW92WGM`*6@ROnOva7;!yBs1oB>iJTpTuiTy zJ0QI^z}QcVkCX%R5#GnEW^j~yq8d+a2uOub@`fV_L(1)NnW8-SCjl$3e}yCd zngxwiUHJg`*lU#)LGbvzg~OWzdy>~*nnyMmMg@@ zwM5jp{VxK&0gtiQ=h64+A0k_SLlN^+6u6zf(myWHj+X!5U#QVwEC||-f;uu5C2=r1 zfq9UGCe)GMDY)%t)hbXwzOzHMd1+}IXrETVpv=MuItr}yB;Q#Ic_Jj~+t4|uJ{fZM zhfqD2gT9@Y)20}1KpdKU#jVtTrZDlHCEjBv^vaREyrhmSTfPzF;CAnvAV<|0Sd{tc zq+8ixcJ~@hAJ5dYZ~skq?O-(PL!Sn_mecc>$PO76ngbBV7d=I50?SIJIH%E)T+>dz7!uciDI&=wX&-ED* z`F|NQuO*H%Qz3M9Jfxxb#WQV4LuFK@ddPRG-W<*>DSKSg?>xN$+krl2Hj!Tm_Qd$3 zi$il3oHO7N3sh3tN3{3sSrZojMl*UT#?mMj^UAF73HEGD)MB})CP?_1KgCOpUDpW) zzVIk{QiNd}YslZ}=FBAh_4p1Iz9-k!1c!qR7L)5>y#(Hp*VBUoT28|evykV81KVpF zOTKA3NJH_KFVHsBqLO#BNpD+R;fjh2F@X2Nmb2!pPNlcT7FVMuEl`@smxujzlQj=TdGH8p*C+ly^`yO~4cVr%w& zo=;;zO^QO6^P1IgYqvO1O;nkB`*~t6Mt1)XdUPmQ75!SsNje%nq`__);N$ap@k(fF zgs-Uoc}KjN984~pwG5ZhhuaRjY9W}&Fbt(?9sSC@duUHw@- zXj2#9?3&2ywFlIDAZ^;cRV~@qUT52`XwbfdTB6Z<%C`_27CR0U=l+v+_jr3qvTtql zz0SIE@w4xMXQS38u)w2hJj+a4l^rpUSuM{pJDpJgnN%_sjf2yy@VU;q2Ppo5r#`s5=gV!jVzTD5Gw_-u0n^CXF_#<9*vIinTuZ|+ej-d{3aR&qp8<17c zm;h)vg*Yb;Nq+Zwm8pc5l!@T8`US+{ScQv zR~}u8+M!pa6SKzKX-!VaT#oGtcMYIT6c}Kj3K#upC81V{&vcVEM$6T^F3n&O_}SHp zk5{K`;Vvv-{Tt(e=v}V>_k>6$y`7^3(_Q`e_q-9FpUPhhd2dW>Wf$Mgtrr(=R*KFE zgA=UeC3;x0{UGDGBQtiVt74`+o~4=)bnKyz41$91%r>jja*Doz_WdTWEf@!9TQxWF zO9RWO%d{)oU*tG7hJ#SmWcVrrGj&mwYkOjiMMO+4aD9#CR14+p0%@oO3+}=3%ItMC z@XiWvt-H#s17&k2p<(#@>!$s0{y5LN!di(v(ipmSC6*8#ntQQjx5vy_{;=*S{Um+b zw!9%uZY>Uc{6dL|l|d7Yu0>5QN!*|QcIrG4SEn`;e@-g?P3}COZT1d$`EY#Fey6_|<~YL|@nbVnbO>=uFh8N8!L0^U?|CBaKT zW`$^nFcXbQNtAoJJ6g${%&XJ;^U4^cguPb(wC~7t`>VRBSpG}*9382Nu^Q{%P5Mvh zs{Ld<6H6YMonA9#FQd|OZc3|jN&)VNr5>uan}h}md(1y4C-23{v}euF3l;74i8i`| z+NubeYccbKsM&Lj|B>5;2D;Uuo#u9see;OD}%6(t^8B%&B#-|!=)4fpQG z@@Ab3BSsW%R{i7i6>si>G;SiT1*vqZMFtl$9L*mk15G;nhR+|dLeuC!)MwP~qnmu2 zdJ&$|cY|09#f3Kl;o=s*r;+8*4WJ#e!*-=EW4UDZiIGRFqcvl6wI`5*@%aocDj z8sMDoVlu>5(_3+UtlgQq>WJ*}aX<-jX|nNtZy0|^+cZ35`HMBCdw{G~Y+VupPkT2f z>>L&{rtSU48G9gcDdF!%BvJT(%8*KeR(&FdUWndMl!`wWTGfmXg^ zqS)>jjuR*5Tw&crx$+8jf99E=X&6;u$q%+e=wobrE}nsd?c}C-c>Zz?OfJX2iO>qC zsZy^#^${7z!n54x1hlcf;7zDEgKC*qM=%JJ{4cPeJGLinF5-hb)1#0js$7nV&8Jj= zGO6lRWN}jEc|YD3&h6Jm(5_PX8yU$z8V{>oJ_4mM|JrZ$^NLUR=a^$3Ff{#o256Z4 z?2ycIHD#U;BL}He^Q-kDMsv+60EfRcWRdRP-SD+$W~Idkn7it<;GUPfA z5{A{*42g4wR$3@I0|SA}mmqRURr7TF)X0;hU^2yJDBW6T6`-y@wAVAhPsgvtNm#zm zto&)L%LR_=+Xu-Wf+L(3xaZo>UnyQ{6*oxycbj+e$;|SQt5DQun@(M)Djm~YKWZ6N zse38VG@Y@@=$hEGmiU{+zS?SVeB-<#0HMbocnUgrtc<*kE!+r%J$c`4HxfbD%W`uk zy(v0Qm1MNpLW-$q1)1Wr({O`-U4Lbts#(B=Kqh`@dkv<3YZDqSFN*yhDO8+g2m+WH zRQpm{Rt@WAafh-`*EvU9DVD)APl&wZw>B?#iQ)4 z)Y34q_vdT9d4)VA;rDsGG?RM%9-b98yL-N~Dy|LMCqENu&l)v!hA_u97y>y7+OCG)kdi ziNmV9Z$aP zPjHw?(d7AQffMA3WvPFLYI6C~#7}-Pl9-j&XQt&mTBTJFmGTSO(cD|-6ZV>C@m$Mp zSx=}0Hn~4R1D&?uKb~k#?>YtVIjc27Ql4oOAgGlciwt zQ9dG47@q=(ugV83Pnea$ezbrrlS@ZZu&0^x8{p0?Ab6!#Ko(mwo+7}j+xx^C#v?(Q zsFU$7q416AI`*%fbp9Y6ttGmOGfMSX@6>B=_N%3b^-vEKaR@LqNzDcv6*H~!RelxS z#$#-yMV3K?C>o&;LbE?z{>Es+>uNerl=`1r(i0SR9d$MNgzo5TPH&VVe|_x`a=M+J9Z zaB`kqKU=w*-=(Wii9%XuLA=lG`QOiqGwAA?Akr+JfjY%`iPLfVhbZQbOkuA$s}D&y zyv<7tEIma`;T~@WclCpwD`MOx_bL_C|N2X}1}CU$rXY-&u6CSX*Y;Wq$urlpRRO`L zO>Yfox24*k_fys?cX&^dY!z6+R_SM0EIkA)sFT4Pyvav2?V#rEY7Fb9Xyem*3Ddco zD*W^HT(PRNheiQeL3edN$j_@oPNh-%RGto*wI0fB^B?^ML2)kqs-fXps7Sr#GdW{i zxPDwA=FDBdY5vLbQ7ifs#F}pON#%BPgZ#=!GiduA?^+qYtI1v~NJkA* zb`sc?sUtls^Qq8v*ctRf<31P+diKrkD7tg953Vwi@y8o@c=RQ|hdJEMxEp2CEg7>o zv&NsQRvMbp*89`QXT4XQN$YH5`W$A+(so8K$aMk|K>EivXS3CxMq3huV4mU-7=n8p+Z`$(kc%tN=M8 zlDFxTzWo(zYW@fpsvA-EAVGN-*m43S`ghj-<4qw0cEs zo{{moW4ak-m&Y=1{2_a`W<5X?tV%~GDDLMEa$Y@L4w6-PFLxOC`wM*~d8_#iTo8^Ze@j=O9) zNv!yAy+#8Ltxda>E9s6%Nul2enGs7An`N`0T0NdYg3-YsgaO(qLsn7V6ygiu&Esp4hoz+V}h3B!A*g$=Xv;peOGAUp#{yedhlPd2EdTV9xoXL zV?Ek?<6lLilvt=>hWc^=KKw)b!WM93MTHSLQ$8W)VUOD?#7bZn+D>aXC-e(={ML}A zW51ALd_E$wP|)F@Su=xM1kd$8!(GoM5@#KYH&HR;AzHPyVfb>Z!XB8u*T{YsJ@ZVh z3h(s!C7s3P%@DeDw(ld5#0?GWlKe30iATKxN)#yUMQ*%}#Uk!&favjUGP@T&g!=~& zUZE!^8yYe;1ddZA(5k?j^=EdSXMHas5qhI2!ua!r=ikO!YRPiSR-kR03lDQ0kX$}! zAZ%T1`Zq=3O&Dto3nVxU?sOF-LU!b6qfG=|;qkmz zjo;_;jMJYiQC}Kzh~j7qAG0AgSQSI=5?;u>24_%GK2g1ReH@!Al5*3td&SoE_X0 zWdTt+sHCgCzp;vH^8s_bD{mCnQ%BXD;`E%-Y;n2uqTQn+0W6=_z$ey{nnlSi>V1)Y zfwS#-Wf=WJCUhY2>4!wXBmQjq4U99GdKJM8KZ$Xp_$^> z3Xe-~B^W!-Cgtc7F)aRFxX`Yo>s;AT&}Hb#fsTDW)BKYTlTAFn>$_)qdmo=Dk4wxk zxtomqIH|c_xY-nbP^ogbH`I<|;it4_YdB zbQ+AqX2E7ew?Dr=T(aO4V{uX(%@Pj!UJSya(S)LbFaAc3&5UMMiZRucgrt2rV=0$} zA6?#m-TiP!@%uRNH^`_gBPm-2r{f}z^CF*FN0D$z|;OG0WJ}nq5jJsC~+^1fKcJ0Ej_25Ha<_~a=HSmk57 zQ?xrq#{>Kc)v;G$q}jQJ$iaT4%aGdy2G_kXkD|ck?uv5AtxqfO=Si4#v_uyt9i$8c3S0@O#xyJV zh3MhwM$xZ55w;j)2%d={3pnyoX3DnEy6qcEJtgfOQFpAwUi=~qZewKs88yMgrWe0* zv3lr41eRpP)%dGL-cA!mwcc8!t=@};#wiA^=sd+UP~uA8G!PT%4;P-=vT_zdS<~QY zVgke0Du!Bx<;5x)=jW@zvEkn}x{FbE8p9%1awb#kjj*_O>9*`yGg+aAYNv0L24KRdE_s<>tLjl`>|+$avXrj%t+-_uuSZ3WA%QJ=yG zuJ;9)ACC^C7iB}L8zI#KZ;VJ}WdFe4MDXhO-H1?_b%pY$qa@YkV1Hc3qyJQ zI9%Pach_ZWlFw$G>eH)$CVWEe#GWx1`@RD55x5%y;=PsQ-|$AzPd*H^@GR zRkOb{O_MdVox~tXyP^A<6n9=24)8n}Z99o%n^hMbCj7}KF<6;4AL+&$$nVT}bt4yo zK^w;?iVyW>PD3iIb}a03hF{olEN_*J`C(Z^tbQL$k8tuTo9`HNeLm3^gPuXAvDa%A zWk8APYQCN)e1g{Q%T%|hP#0F;CIZW+78{0|y#ZYkyL^VEGxA*YGFiQ~c5d=DKw3ct z0inhs%PLV!^2H>V8?u50?{9{9oc*b}k0d>`S8`$}2GT3sUT_%OG(kEgDw0yCWE80X z@DL#P7h_+_z~A9wRI z@E`t(dt(Ppf=;-=Kdl7h5{XC9Bd5Yc-?2CkUZim|KCMCwlI!MZ&ZMr%Q6^%Rm;v?Q zBRTIXV!~Z>89NheaX>JMj$-6?F`Orv_muMYHdA#!3LHWh)nBVotV}M=ZP?zs`dgle z>9)_*lQsO+rFfCL8+IyOC0QDNOSg*HO{Q+AskxsoMOTt&MHfh)9e?w*MDBk>X|xu& zu`pGjC?mUBgamu@lfgd`gX3Tevnl!Bm%t_#tKCZ?U;4}L?e|A%!+ckktX70o!Y9ig zzAqjNmTHVBBM`-7`9#H7BPvb>Lp%dI<<}+I^G)R;+)Ht~UA{;b#SqZ>8&bPc#kZlX zyy2HAl-L}g<0Iegm#2bFfmmJYx%Fzl_XXU$dxVBoMw{(wC78}(XZJ86 zMbdlW6QZ|T#FDX(V##`5P8I6_8$P%?!m%c}`4+zI64&Zklpsew@ibxCA=CmJeUvwV zr#g#4O|E4Fe@nsp-rw0xhJ<>t^pP=H0?HagHwRc3z0lm)J9eXbB_@rby)LuF^M%SM71P&!K;aSpNPud0eL+M8D|8X^t)5H;D^`*$ z)jQzIM+x;oc}liZYm&afVI^gv5CfgCmNbW*q$JC9#YAKhN+UldlD>oY<7E~qhaHu+ zbl^1g)sS*h>*SUgG;t{_o5a{c06wvn5JjN!UK-6eD~M2xlDhcEks#=K zt;{}fxW;k8rTvb~KpP$Vl&4?|cMyvGowmFJDQvz>9%_&H$nUneBrsQB5RS1==U?_kmH58}7krK9->gu0yozsWZR8&rDU-v7(2~$Nkf6*#ApF8DIN-!85Qj-m+K$CAc?!|5pE!$S5M%Dz?k+S92PBxv>1xVr$6FPzA1Rb;|{HrPyQNXL#574iDe6ZM2{`(*N6JE)~nkyj4I{ z)v-Ev&T9feRR(gxPbdk5xON8nqXHLSAHFhGzgytE#K@ElTxW%g>@N=0P4fi?s);Ol-s8wI zI;p_u#oMA*XviaiRp@9m)iNsn=-@9$p{Hn0>BlSvPZfoaLrL4n9v2jIszPe!!YxFW zQ2TVfGgf-2d=t#*HbEdL$^|WU{ehQqXHx(#b1*3{_j4=@CM^=}Q54?D)I<7cTkpk&ukx7S>}u4$O~-8O-!8~Tu{eD zNjc%#hj9r4$97o;|Do9LX zEc)@q^XLbwdacbS=izd`lZYf~s)acyFPK7&!1aZ*>Eo>g6+6)cXiM+D)YwI8Y#;6kd{{WIv2#?5WDx2i&ok?X@lx}itF3F zU)JO06#*b%Gv4nQa+LpEdFe*Pp6-eYk-gb^W*o)SrQZUAGwF!|HLZXpeaBpqjh(RF zgRq6qg31RVJ~+DDzKlhls;v&`Dc|d(F8ElOVA(IE^^u*pRU_SUK;j?L$K1zj$(Tsi z6u;a!H^a*Z-Wop?YtVKVnLJOhybN8mGY~r1TUbZHW>nK)8Y^RNEWMRe6zcSzw^4xG@OqDkX0CBq#* z>4+dtkQn0E-z*JgB6 zaWpBt#ujq6ba9gLe^-OsT-edgNxUGAgM}Ld7}MmsFLK^Sp}vtPc)=X-5S?eXxp)Q4Y;Wsca&667$5_aX{ z6kh{OoP0B3Emw*$XK{2Dd!!-{Pv3NY)OyOXW)T9M-+^MfDp}x0<#v>%7$r3 zIxTn}gtn8@ihsd{1hC;!Nm>Os@`x(l%|)0Ncf$*}r4n$$86Y zYy8x#PM@jawxK0EeqL?{rQsfnm}W&M2^=$gdna>rkAGLMY}BpXx!QXsakALEA8-Qf z8v$C8$x6(|^T9<_f(^E%z0Z{?kSQ4qq0gJqWvE{X+p+B6K>8D$m4baGnK`A*g^8M{ z2+_wkO3H$M+P@=3GA(vwRKx@WpJe~ycVUni+ol=npy5ek&f;#m?8^g=fhf?0otFlZ zB;uQq0yj*WDEh1wSHkd$tbV5xz}=ABj6Hj0;El1aIdF+X;LeXD(Auf#8KQP)3N!U4 z9~gu#L)$X*Wf2{3oar0b!^ra9^I7^s;lq=sHdShAhFoDDj$rc{ZmC|t3^{97!+sdF z(u7A8Pl3npg9TU>;!L>V5J@PP*^alz2)^B*?_Nn;&x@xF8ckL84#Q$m=jJ;DEFIR@ zW|qzDXIFuUah?rkShEJ<=u=7%5rCdccP>6)hx9Tl1E35yp_NSwMC|+i0drK)5h~~a z01y#}+3+~bkuCG&GY!M(o)ByK0E*300Nj<}fk4z{kDrnCX^XKdgCe{s-qe`z3d8AC z%^>xi4Q8jy<+GaTsf*r*o}sSyc}eV%T|tSYMaMXs5Vy^r-kj{+g)z5JCAE6E{e3c* zr5`4z#LzxTz6uFqLoW!az*ie_PbTqlnT~L^U^H>Nq$SPnB4diDcx$>2|S=051iBI;7s8E!KBYaKVBA>?h{ zidMXZWwquj3@z_$*bz5I;fvEaa9yGxcD-yjXN}XpV;IknpfgotBQ9F&u_ms60o#K$ zq;-%N((>JCnhEP_{DLCdbiy>IuEaPj>2)H;|59w$H??&KokP2ED2eKJt?zC5V;?hp zrN>3DCL}HxS`y-eg*VEHzD@%6-f0l6YeQEoblIOV8I5+nr+zN*fSZ%_5VT^_DU%&w z8g&M!29Opg5mWh{%oc;~Vfwn>n|!VaZr$l_^-{)@1a*B|B59U|P;+5O8ol%W)ZJhJ zuxSNsyqMbdE5nEfKEnO@sotr4T&73)MBFR-m3c7@HPdIm)p5s8aK7g(y}QT?(&BGu z^RcLaN*NkC+>b3KiDN=n7LE&<-k?$%P{zE7j3O#TdLk&Zxz5(Fjxl?jTsL5mNRl7+ z+`2Wd1Yw{)VM0GAp@C0f^^(~{YVn8~G5ykFAiEE0uF&`x25tST2={c;Wg`9CB|q~N z{4cgLstlkoTZRU=cke7&GR9yt_~9i{mJBH*Ay_}K_x9-K51aVFnZe+JEh!t50Mw<> zQHloqGS9wqVR1W^X&{Fpw>&Or-4}3G)J|iDv$y+#)iYE!;^~(VB<2}BRBYHW!KRT^ zv7TeCc$`L2d`I;@yGhQ@T-XzT&96hNv@s|zVnr*l8rY9L_Aq@bOpjb;BK zzWMobZv^TssieiTtdBb>Vt2(^Jdbx-_Pq!l26} zoHx`*r_0?b1JCZW)hB3ilqTlId+D&(7ME`-ow+GfP*i_gj0_XhvL;mWju)5STCR_# z(Vu|j_`pt{=L870tK~%`pB*HD6jyG4;Lzvgxdt>xiw(Y?VuP&S_hP?|b}^DYD!`fq z6v@3WqI!OaDA50Xvp>fY=;#(5yaHm9`yRJEW%7S&!+$S5tIF4RBLD@6PJ-CI zyxxB7y{2o+lc;k6CM^IgHelXiB|dPZ6CB2l&AP?fWB8htPABCD1}riA1Y_nVE&L_| z9eoO=>;}wAUj_JJRMql{PXYq&P2BbU)zF?%YD!D2OCtUy%!w_l+K#1!ZCC7sbE zG~3Y#$)-uSMY}tbGEHPl)76o$%lQH&zrTKVm{iQZVhbOg?S3QE{YQqRX~~3<;TlF| zP~o{(=jHdE0&+l$g>({UO{m%mK(Df<4R!->379}7jYw-xVwTH}Y;tE!>l}drD;^BC z31CaYs=JDzNspF?NzTcr=m~w~pH!Dv>T@6^bWtLH_^|@WD7Q>lmCEt;#l2_yfu6i0 zQ3i`%Od#YFr(;8nF+=vOunp>&3sA;l0iAuR-8&yJTowCvC6T7v03z=6gM?a!Dlu-K zcHy@k7p1;lXOQ8i5$k$R6@6tEy!}%FwY+p8F8JepC+-O3C5z@EqeLvp-}e7@olRpxp4bQiSsH zFsDXJD+3x~o{_ASkHqa5Gt0!;%Q<Ajg~vm8mmfwAXYg=!jk#i+N}<=sX8lqqRsop({Savc{zWJ=Y%wetE(@AzeJj1Fpr zxAsy7Yxdw&B`AO0uNqqCo(FhYgG3}Ga1V8v3_R)whtp9t>s^aC%vC)GNSwja^xdrm zxd>*hDK&XsBxZcDMQeQ)2qCBEy!*nMeJP)cv&DS$YJ-^UxzNH+2QxizRc6jhu7n!oE&WW{ z40FU`toQo&THEq{3u%cHpltG`S=}h;g}yVwIvYBCrFdeFf*p`?W=XyDV&xw=`kvu3SUz(>I>WR_m=; zdnooElEU@OubHmb&h`Z+c_6_^@kyI0+lT<~d1R+pF20z0l_sT9thA7>j!|{sTd^Kr z>e&;tqucx^Oh-z2>0xE4;KDO2`5o|G>9OLf60nL_V?v~KKgQib!!-EtwC|+&-s2Qj z8}}>~A3we1`GdHGI}S;ok|O|r4QmyKo8I)3;=q2js*JU>(=5{(C3`+21K(?o6F^Br zn^p67cgtse!b*xE9V_ea84@yduVY-`g0j+Y?+5q(FnZA;U|lLOa$2k2Xs!WT9s?&zJ=Zg9`NV-4(b+lL zi8!C{mfZ!uO~XmkM+u1%C{*OKAjVPTjuNVgcvWK_l!ehu6j^wZLK++HVaX&C$$lK1 z;%`a0FG@n~GnyrtZ3VCtPcZT=f-lf6K%NLnAWvK)GYDD_sGBhZgZxw-?zx_op0cu6 zOHkcR8G&zKDirA1CGmfyowqjn%2%LV{iG9*1jo#lW&^|CWp;Sx1K8y@uS&_NX49$~ z_&}0));Vqx^a&qcU&wZBQVhFedNBs`;Ho;>KUz63N;dSN9_YM{l&7-vE5^~Ec4w-{ zxg5gv3vU%9amRZ>p_p66qc+rw=L3J|^8v-l46=L3pwU>nn9wvZ*G%0V*ckiDzq7Uo zH6cU)M@$-3o88KO2QNRSnLZAq2_xy6`FRUS*(4iURs1VGMT@&-1a?TEL#Oe zt7;JwaNK6?WAXbNv7RIx4_U9Oj-{MgM0P`6`6*^jW29JAke z5g33C@RRq~0_y&_TXV8MSiR@>Z_NVxe8Ys?pydWwYcAtHn-7t|ZpM#GYH-kTI?Nf$>zlYAmcAM^<;;m1$<`xlgt!XX~#rB3AH9fW-} z0iPtgFOpi8HEEY*y5Oa)Bb!!uQPpikvrN&vYzqPQ@?Z}ig_}#Npt^$xe(kDqDY}qk zIe19Ih9W~{;OnyAFw@<3`zd{N$J6H}aP?pqwQS}0FpnDR-} zkF17Fh2AmTUQm4GwA%i`iej;Mwh)?yK#r(HIqL7~$xZ3Rh|n;d78XkZ(-*`G#1bh| zrLx9$V5&!!T7{%k5rtUA8p;Nt$+usZC?@KS?!;S_CNod^g3eqacIPI|g3gjG@s72TTzw!JXHT zi1)v3)80ZDHa86aNjTiprl-#{Vaxz5;MQPIHW~y#!;(J2lE86|XTn^a7MZWM_>Xb3 zF`(MhDUykAz7JTM7Mfi)ddLr)nn-a1KN$F9_l-Itou!sK1BxR*qk6ZS%T@i#rm=bb zTMb)Be7$h-tP3<%PZay!L`*uf=$UsgeqlOV?nssQs+j3f+4N1^p0e_`QlBJ;uGU=4 z?LA;v5CI<*ozU1c4OZzl8~KO^M5=5^aEk)EdzIL?fyVXeK~3e5UYosxInAFI_4)OV zYg(t#m(Z`@m|%TW@aXK`s{9l`sC{xCv`A8fMH?12G(fXtXpMx4%0$>f#eg1O;Zq|_ zi=C$n!C?x0gdU}4^z~fHD*E2x3XKi<+;%eU=L5m;3{^5TG}X1Xk`nF1U#w+T5JNvm zq`2}1s1T+}H$!7kve~T}Wl~pHjuzf{e&UH;1Zs{B9Rx?;wbw2~hqHfeemgTc=Slf^ zwO1yLH8F=~I}JeT4MeijXOLl5jK-jU<;3;I^zrdH#=uP`I>0o|onR7=h=fw4e-+@@ z@%5h^@^4^+5{y)pGL5qlYQg7c=DoO-M)BD9O24*|;@A=F=ss`Bt0iPjFeM?qZEXGq zSs%p9YQ67ZRh$?weSf&y1A>1}=V_;Vo?%DCh!THgKCO-e^_Xx(3xZ$&6u-qUmkQ^} zQb3Jr+EK_OAJV^L9;9%?DUI`<+W-=QW$>CiHJrd0c#dJY1Av7#3S7Ma-mp5?wnT83}w^EWrST|K;=^Mdn_Z}6uUFCy~RkVUO*61uSqY_K_6PDBYp zk?`EAiJUv0$_5@X!&ega;Jb`sw~yDAE!#rYgF)k3SC{0Yg^r$i1N^f~#Ebcd zo)W`=AmxtD`d*2X;eOnju@`UvBS-Zzr z1JaI!yzOhAqIip25^0%WP_uM6XrjoRN=7@Dbgd-A6Z~!QV&H-Pr!UIPEErphgli%k zYBNQtRQvLRMKh;g@%OhgVKv3swRv`m`l5l7hKrwMR-6EyYII!zF~LT#V-uu?f3^{# zRl|3MaBYe=_stqI)M?sz>Z2HG>`}Sc*56nIxc$uJl=_c~w?Ts8;G{_wHI6D?MPDN% zkkw@x%0?Ahhq@(ywgF7o&@~_dmp}b#sppl-k~b{i+SDN_m?lWglWkdr)%2XioQ(2HJa|HH;NR9AFc=7&TH{4v~h$yL%1!K7WZ*q&9>r`>e^%UTu!d8 zCR*nz)p)fvFjH@jd^&PuQ?Vprri8_Z`ei-u{4}kv0fIv(Sl?%5u{DfV?cq{X3kTd) zp$vlX( z6PXLP@4IV3lYr#(8LdmwZI6Z)_AVv{e1>;y7> z5z>dNeB3)b5tRKvj>DDYs39I&A;7p4JT0IJOR0iA*9k{La2B}pF8eU-Krzk!()qR^ zzg+>JDUb_)^kj96<^b`V2baya!>&l8hSro{w@mMN+nTSYcsCGwafx|tvuo8>)LngTI*cL;k}6d zue(fuXz26DwryuhVF~*mrWCUu$$#(lTn)HqaYjyBOy|eSdv@4G2i}S|caBcK_Re~J zi960KWQDjS3j!ODXNs^Gzumb7gDkVyzmS4?dzF=>CHM_0x(yteTf|ZG)L&Gx$J8A2 zLr2YwEjemO^aA^9618=hnUlA$2y$KC_mf|8P&F#&l}rRtC0lW(#^9^Xe2Hc`@Z&By zKRBF!IpnWDRyxsa0@4JhjKxc(R%+%k&1wH^b!$Qn%GB4_eHYX7HXEbhhD{1dU&Dq_ zb}T8Z9(q_B-##8+gmt=j_iQp*+7jI0SrL zau5!*G6#*=r7L74j`fnIHXDuh2g^w}j>T*h(IRFmGH}XN4Ixpfa%`oBk17ow?{nJ`hBV z@u(T6KF+RQ7P=a!@A*PBm$=5fA{zexAJT3n5_iZY*BBl@(3|ZJ$UfpxEJn0-5-3l2>A{uzHQ4 zQhUE72N+MlKwuhsWlZ{Fj%6=l6P#B0&zZd3oL%6j?^SsL#G4b$@bYScblZL_#$&3d zjbDZ1${3C-od<*NK_)5j1MMd1p{@UQK*^p6I2QJOK)hE#7Xlk{KuR(!%^mI2kK);x zOu2z31yY@-2En9dw9d5HmIC&LvAqtOavWaIzk;S5inhhZ2-SH3wl9Y5yV4rZ61y$J z>ueTl%oA=rdwfBdCPk9yfe%2_o>ijayn)to&Kq^Y8MArLT^WNWdB%ky{tj@Eu2#zS z;&q`5=TBhGt$p7tSJh6uYzoSSpNz#w6VClWoXcDqL9OJNi&fTD^spR!)@<8`wZi2m z`NjK27exBmu;uDREW+XZqUuxqHR=(HI{#GZ^St}BTSWz{Ba%X5!5(Q)m(?UUbJ$wO{p>Qv_W_h;k@=U<>RDR{*QWkXA9r}yjM zwE@n*a!S+EMy9E$MO1^qn!s>XJh#EQsvy4&29^`EhdFP6WU4_LE^q?bi0|+yWKPEX z=S-CS6y&k{Lb{e%PSvJJ*x_Kgn8O$`pL=K_(~%DKsvd{`?6CD3+AWJuQdaEihDj=g zRCVPa9r7dz_rmSZrI*i#WYO%6V!f{nM_vNVokSCowVbw%k{>aryM+>$%ahDXA}mDW zAJ`q;{9dH|(x=L$BsxMQC`#5boJAMFe7+#%a5atK0fnCrWgK^Zw?vk!l? zrJSS2@IsUv%5Mh{r!}jcf^JQ%gKJm2Dq{(@;{*Day5AeC_bdzFUk27ys3n%@z^EQ8 zz~mZl4!id8Gg^Ui1}rn^AIBe2VF!B5%~e6Lo{PpNhpNDnXv^c8T(`lywCN~h;7fY{s#cOAi4R#IeHJ;XSCHD^vux8A$ieeLw_!@u%W08PX0!s#XX%z={D#!&$d zZw|G>l?d^$u}&~fdM8F~sG9nH`rql!wFdp1J4Pj~*s^V)e|Vu*zJi=?15n}Hq@w0< z0PZ!!lF8VJX8*DUy3Ra~3eDyw8;v*GQ3q z76TcuHa!h@AJ%M!E2ppt14%ZOLjWnA!hCLg#6R_`WT94HpH1c4ZCf2AThvfZ9-y2g z#HUx2!-phgDu1;+MGyWZNJ`Emaj}(~`zO+W(yO|oWw32x2poi%(m$(l11t&PqL`jm zT5@Vtj1U`s5?>+sqhI?6j|a|a=ImcnoKmZGPJb*!rV7nHc;rnr(<4#I|U& zr6^6KFaS<|t*BdX>ea#DH|U4B9JuHMj9{?ZBu?{j0ZSM&%N&H+;GgP2v*_Fb^|G4t z_e<#Btb`Y84Zc#`tcpavjW0M-*uz2|(Rg<9VpXzpd@=X{eFJF0i|`-6lz@mkfq~nB z@7-IpPwMrQ4xOQ3)V0$`Kd592^si#s>hh!9a|sWCCrHM|uX{N8F3K;h;txA8O~si8 zgPjI{N71Sd0)V%{&(6+{oI1MvPKFT)AWgto29%nnDddXzll$`sOg-PAWn=M=42^)N ztGA;gH^imK9$`YHga@J`>!V<8GnR(54{L_P#B##9ZelPpKUWRrUlmBgUgIBUNS`K;`!h^&?>RJ14IDF=mQ20sw-$m6c zapmONSgjtJSzmn;1`Em0m5n?B9D*sBX5~w0KrqQ;w{j0{32h)4{#{v;Bx(q0&Q`^d z$%U3qeaS>Q0fznvG%&=3Lo7D{ZT5#^mSE7rEKW{;%l9_ZOG%!3!w?4*f)oyR>g9H#Rr zfC$Vw<^$ze=?WKvy;4n|isiI4X>?pB|{+utLI*BDvJDR1L!Sa53O8dMl*@nbKcJByaq9ffet%=KHCluBHr|zE>aM35&p{ z2%5|N@oXe4=7)tGf%$Hme#8f1Ij5=MhUAJUa}1`T=hlV!So6zKTE@?f4oEc97?UNn zu)Xf32DGoR$t8DB|AqFv_|}iO%M?B~xSoH}D49C7gQN+?QM(Wyc`rzM>+VhTPj5>Q3i4 zUgSoP{%#ut@75 zQj$XS2emxQeZc+a?uX?`TI)Xmlj0$#JP%;6T)>qi2&^)W>9i*a!s>6euBCH=tRH14 z$#Z}d^Y^4L9S=v~_?bjEZp0`CUP&|sDc?JaX9B=)2x2DZY}<#1*nfDUst{VQ_q$wg zX3b53`*rjmSXD89R|0J+W1n!ZhJ}Q~E`l0+nVe!A7eocM6@<%aPrbUqlOWgu7{QKX z*(l7GE}Fe{Dcs1SZYV?mN)Jvfa-R&MDMrum$5pyBS%16=nP=W?SqDlkau0m<)6}`) zpkGm@#?t+>RCXVk*=b+Q!YV+XK@J)>(bDvj-rdIUceHF$gO#Om8^!r?evd5)BznBD zCnNq}aAX^Z?31`T9V;_aXuNrGy=4KvkmC4K=7q@906OmTQJ%MzjQ1JV)#@L!P>EC@ zed|cf8REpn0cH?;vPV=x1dM4)RWl!pye5M8ON9U^UMJtKH*q%XcU)J{u(aC00cQ-3 zik-@(qS4VyYI#l^4?Ycksv1c`*MbdnFtgRjXxfhRHeze=yRR`+O%9M2<2xpwiN{ZC zgosk=VcJ$51lhlVWZaMpsP8U2VuH&s6Hcc>u-ixQLEwGN&|wGq*Q}2^;+T=os(IPh z&l5zU%uD~&z3yYcEFNiF+3h8IA8$|j4%7D^?yhX}e^>9l&sy{`?Ydiq;+H%Ut zcy5kK6^5Tm8MQ@a8~UdWd0>s}K{ z%@}fq9H3l}UEyC+J}e}pxw8A#&GVM{`rD6fcU3eLM;3#?M=l#(Lcf1FhOF|w`Zj>EgnjKImoYk zY~5}*qu04KrH_c#0~Qja?vWRxZq4{J-{6&g^je~rAYA0Wh2&H!*UtpLfTbJy`W zuMQky?)0CiCUSl?=JcR0Wnk z)EB-AOzQmKz~^ZK=ZE$L3KS?gaQo%$ye1QbGw4L9dN<_G&9!b8YPa&`hJhPS(cv|194 zxTBlS^8A@>AX@0w=qwWJdS9%lgMyRQQJ-?iks$Ft&6RYpg|d3~KIpC$-VyzE%pon>DgvX>h_es&{fzKbQ`K@`#0v>fT18*Vx_>FD|6m8L zlu9*`#=rGkds0J`jQ0-MN($AO$r87=^|xJF4EzsY&;Z6AV!56Jm(d8bm> zI5`DWk9gVQnmGQ?Vc?1_?@SqvL^Rbyoj0dk#`@JKWMCNM0XblxE`jhr(}R!JSCm6K zP7?z`%PP7;GK)_wwt0VAo**A}-D>!+@JknIVM7`!C1HQv80ZgyHoT~UwOWore*y-M z=R$V>AusNG@3~IOSy(3OxI~{6RTFC37y;67ZwDEEXw(MrvDmyE(PlW(aq+oU#I8sN z33!DixV?gQU&#b`*>A0_K-^p1^!rj*_8jc7cz@@sToI?%&tVv3kSSK1-!o!9d7EIz z*MRBd=QgL)sJ+C*4B)<-xmnX>ydkd^6(a{XR)|RtOKOEQRua3_s?X~7yJ>ctD3#&{ zm?ut=;NW!bz|A9;9V}u)mDG%!6y!s3EH3aMvx)P;zZA_#+DT;d&0UkmdHx7?A`a2ycH`|MTkL_!QiJ|*OB0&k5JO= z)<0M#mRgJ)4|P=uF(Z|e4h1ri8D{+`=ZsPtW)^! z&QDGb`GYm;Nsrh^-fGCAU|6&CFqt(=KAZ8RixU#STT|Pn_20>AkG2f^RNs3=?UYu3 z*;ql&L4`+3T#HB0ksU?UN4w%kMw=2}TN?TQz|xq4oPgB-!l&lHnO>v2*g)Vt8BXH= z@TUTjsKlDQQ?0=>J?mCSSt~8t=1Nc{V+1?Yknk{+(HKw_WS5X@x3Vvyo`PLifzqh) zjopN`c0KyjxO*U>HGcG_TZq{kq}DYw>$W)b>CI?{%^XkJd@UE?6HW{f^UxIwot6kb z6!M7dHTpS_AN^~_^;7(~gCH$N=>cQzzmsK8q-$;iB{3M>7bi}9 zXHfg5UQ9|c0iYK*g-oJ^3Tjv~;9aSYH=rOzM;~?}Vm&_kSSHZ-f}czYszQ@XNT-ON zi%weA%eOFR14oa-*&GU*fb~jb3t1_+s4?=JEf20{N!9rd$kko4Kg0^NU&Qgs+n-bv zCxa28Vif$~rY7~giHWh6YJXaQR=;T9he5oluJW&T4%n_xce{Lta*xzbTNhzr`z?!C zw9fWt#}*GPe9rY)(yyOt?`=y3E8Wub4QTe@N6lzj5P{TJ*2AH^gX{v|qDUYX(8+1D z8SCSoM+JXZE*A(gjYOLeh@0$nXg4QN;MkOa8ST++j>^&qw$}U>O30YBG7J-Kpm5(c zh-xp2KfnBRz-?2rAV(GK$$4k8zTK&BKzF&G7ZCm%F6j-!+jPouip9I&1rrxWMwhZJ zwuMq|gK*!ryHDek@=oRFZYS?1p?MEKZ*C1?Idc$Suse12LP4b%vvf_HU)vsJ(KJAa z4#i6z)qgs07xh!Uk6t|oxl#%$>z0lm#=NFwNx#AOK1 zOrq2YRnFJ&(`{0A=>9}e7JH}E5`UV{U(jb za1GHst6E~an7ic=hj%BnKkPP~Z32YVcOTaCO=b1DAGn+& z;RgIm^u`SQXVGX61uQ_Z;|^j91M8*4@*;Man)nTnazfx5dRjG$(dDrkxb`5$!Jea+ zG=^b}Np5ge`btMg))_kRQP9Mp$%w6y)4Z9B zR?J~nlIE!qoHeet&y^|M>!)MoG$TEuJpn`ABlfxE?w$t7Y%k9uMRclPd+IK)ef{)q zA5Xd|g?mr|A>ox9t)Sev%v~&czN9fW1i#YqsDEk(Z&Lcv-E#vHV;^-JMjCb;51ZZx zNZ-Mo@Ea485pzlRq`5&oz~Sx;9>6}*lusDkb5K>C?w`M76KdPpH+j|2J9b(1P>X&( zqy{<|zMUSCuM1-B8KsR#2xcU6uOUlf!c8HCLl?RVGpn%I=vW?)o84K&iQWK~5HNXP zO(w-nxqgNGimLOYSNhAa{U;o#uvb<>>4P{5OMTINU~L}VS@mfrz>7MMlu!hm8h18H6V1i5*n7ONKhe@2ZB89&zbI#dmA}Yr3%++c|YolgZg8r zy@C{T+EjT&i?Xtha~(o5P2x&dY>bu$maLkQn4~)U!Ion&+GW2llTuG1&cCnl=H}+` z=he-}lG=E~4jeYg(?;sa1?U#YliF<+HzF_uMQ8wXM^qQG?#ZGKcIm(By3>uc`aPi^ zr*Xtkc}IKH`5LJnr|IP2(q8iwCQ$u%X+M+jK>fRuY)J{`vywrZ_(A75iMYhxwxgOa z9_$o!hSnR&HWU%WxpMD9Vpu3zZw|UN%!>tIaV7>JOqp+KTz^n&%b7V$V1U0rMz?>c zuvwtW%1TEC6Rw%THZwD=`ZEo~#yTSVj7b>Kf$=5Spc@WXV@R}ErbV(cEpLj3`{{nI zj|htE<+C3O@a_Z}5%uOTV9mHY@FM}QYITG;5fnYe4r58rzp1Ju-A~2}*)#V=Z?I|`4h8w{!x&M^Li3HI6ZN#WeCz?LQ ziFD%DbTAwwXr1Tw4J@JK|4#_|e2gIW+`dR83IjM*7Bxo=0mDy|X~*P5&r8Q;dOu%a zZF%2VBQ%0PS|i>nWMkiqk9qNf+&g*lb$bzye-Gr%flu9nhrpmNz^bztj>4Z6;4Hx5kGCARmB#e~-yImlfr0f} zL6APTTde%0H36=0yYD4U%*R*{mro9N@7&6fp8dk-dv)2CPswyjo|JVDfWRDQS`@Hy zEiP>Rt97ZwZCntVX9BR2CA8rNBj#LcBzb&eDIZd+!SA?ZWp+IdiOwdBzofFItG^V} z^Pbz^WLIV)?lpRhl=7E+@EdBUdfdvT6%?GyDPT5N|)){A+i#3d=pb345G*<>H_PuxuvU z0d60>IRKkqO;dleKKgAsA3Q(Gt5*N;1s);O}**vX{0@FapdP+i0VI>MqrH{kP&8;D==Oe$LZL?Y~ydly@v zy*<8}bOd)JduT0OOt@K2#^80zch>EN{t3!#MaJ86x_QX4&y&4=*U+;a%Fkxn)F zYt=Q7rLHtZJ|RF+Cj8B~WnUL>kLUUG$J|(Hv85W$DE+lB_=SoUAQ#Hc>jSs!Q2~`^ zRN&|;P#Y`)dHo5{#q+1m3TO44LZ_Wofu>sip$YuqOy;A{cF}^94xQQ<557-JXky<1 z*x)N3th+Fg-00_o#IVo%ECqx{(zJ)3(=!hOW*zx_uUr*}YK8~!Y7HP&;P#wnjv*{! z)vz;)CnfT~qKn5j3DeL);+5w+yIxK=h~i0f%@#@WQu6?|JEf5n0UhF)%pk|__N1w` z+}3#vSI0yROr|A5dFs+>xL?txmL>~Nu)pE}@gUv8y7OoudZsWT?~r=h(K$IOX1W3? zEPs5=f8L!74=CU&YXD=*7nv5+6U@_o2#-?8&Z|!bOM{%_(>dZedd&5 zkof&*PwNyWa5#Ud3YD>NWi;ZU4GJ59dJ)1~7&JI?Y~(SiLrj*7JcJm6@448x=v+Lk zQh0TH|I&1=mms4AEmJC3;EFa*7R3s0(RVZ&xK}NTP8u9@3i;``&rhJGVy9}5ZHBgi z36G$*OB}I|iN{;_laoA~Y_NeSM+;Uw<`zLV>F2p9Kbo9Xvp@hha>)+>=b^Zwx9G`b0g20wumfuI;!7uWDmxQ86=KpVE@hW^ z*KTvfl@&f$XJe!Lqr*wGz7^1969w*2I2b3?M4tu#7@sThBmaadRA!z76s`pNum~t9 zNbWkg8reG~NlRtwB@Ibi$$uu>^w+AvZ}v0 z9Fi;q-@F8FQEq_AuhAzDy=bJFUI32K!MF{x^K@>aCWeka_oUI>ZjPT~?e4E3V;YnX;?1&eg9qET4xnr*)ju zTF^w4Ca6|(At>tN;1hF1S_qBp!86Rt2iBYPS8qj+>LZfWpe~TQ6oKTDPs;R!?Q9u; zhna*{sw`?d8Xl`X)Kk?Gm6bK07TURFe{pNHC3nlS>3;iv#MB@<6}w zT88j!1;&+immc9Ke7Vhe{_G?jzHz+?fCA6!mjvp6uT7?u5fR}^1+C+kewY4kvXbz&8(9jq)FzVQa%La*{9x$ZRuYCw} zlnzZR%6$jWG(V_4C`QKZW#d*cDi%UTt5W@5X#3b>cBFu&4MrU(rnf=4U@tsXrlF(I z^ZHqd0h{oG#!>@ygSA2!@`%d_%9-T`v#HLdWw7A>5th&n8<=-U#{#x{Lt8xIKX|S3 zXS1bS+lCb<4SpoXE+LksUVaCSFZ|k0JPGb=O&P6Fn7@`bLa35#v;Sg z6UDd!3d?-Og;rLOGt4cVi$0ge>;0_Tdy~BaABnD9;{ezx>{ zXnOKrqUdqm3TqMSpP4E)t!XN6q)6avWg%amlwML3*n{u@MZ+WJ%H0Z&K?q*tAMZnd zI!=-0B9XoN81zMj0UOWR0D72B{Ub1Xh&zseCJu&(Qx`BnAITmvNl&{h>O1pyEj32L>e>=A7@oGi`qD{#4Mqp~B zrJ$mYMaiu>OYs3lUS)dmz#xW4V_tx3(V%vMqZ?G(fj8G1t_|rh6Jbcm?lKObXNB#J zyY7{HytSvY^}E$o^N&(ZN^6i3%hZ!``p?y}NVoTCY45-`LvX<1?q|1Q$I}p*kJATU z`3wxF9&hGq%K$;NuG>2)f6#ZUPjzq^Pl8a&Y5gzO$b$r~1oL~gv+PHf=m)E;EOsPI zYfYn&3aXeJ^wH$hFnEh|oX=>+7l5at+CNWw1T(gURF>P37S4um5U<6ALF=Mya5!w| zG8eLKxOz;araOPGS6Z028%C2&I3T@>h8Qx=u%AW07gSyHYzcr!_hP7YKbex~P}_+q zmES;Gxf`vgYxncGHpjwgzW%hFFYs3Z*{!Hf*4nUi9{)_jN%8o#I2xbZvzRv~H70@L zEy7LBpaV?K7&E}+^)EmJgt)-nX`w51Pjh&$4~lFgm;0yuyB-&29_-ug!|x<)RcW0-N`a))uMANiv1 zqKT~adyN17CJ_hp(}UTIYs?9e|2Z<)PG10e?dO$uam1T^v?Zzz1qdcFaJuCqhu6_1 zHylPR0UchV6g~iJK##JoLDLyWR$@S@ z=xuxFQh!xquJhQaQ{-WQ1kL(u^go1WrA!DrzyO^xLuO6gFKaCV;Cc8?vvsgfe?E!{T2g zPF5IHKy$3`@^<1C`@!245MCL(PUn@ToyZYz zEulvUppKEr)qzT9<4UVmZyyV()pNU6<-XrSo5aYX?WHgbAmqe{`16i~9nF#iBUarQ z@uA7dRR$!>uzbd02&eP`pP#$4qr35=8$Xm@QS}{@UxLEmufUxM>%Rg&>{=rbC4!?0~*sa)T`?hjf;FB_-Q+@f$7p>Pk_$Q|z_Xjx; zLzW*?{KppSLfKc7+f`jOYZxOabx4mCsMRi_G4PF2+;}OSU{6jM_V*R&#i#mbT)iCA zhg0-1W=volR@kt5$W&C5e&3v4nkBc{j$yS=Q zTj^39gBBwSpI3zTq-r7cne;V0S4jlUpGn5LvNnTyCL(vdcURWJQLO}+ZKepBeKzE6 zwsSz)VvwQzXw0?&W*S)HSF^(}X6(uY;!oEdWn|PpSc{~I;VG$r9qRq_O&*e;7_E^X zHcO1d1T5X&Pk{%O9b%pl*_s^BMveb45<|crHXbp)%1ccNVSH{vG~@iiqzt%aN-SLr zR8jy0K+k%^q(YVFFb7YCRDf9aOeh4lkRol>8z3R+>NJC8FL|;IS-LvXh@kE2_(CYv zBD4ph^oR|-jRhho&;J|T40zl#j9P_Q|Jaskm59uP#C|b3=Hz9vV(S`&30md*Vw}1S zDnR8LtkSms2}f^o6<=sHv@6YDnmGAF_#uV(5i5U*^ujhL)#?nAOg8cOTlW>VDrA?_ z8|h6|1wH1{0u)y3r(g5CMw^G33ek9QDn+?ug6+?IDltqw&TbuM<0LsgMD<;&*GE&8 zpMLFlpWtvHCZCtZ#Y#M^uZ#Ps#U`BOvuzR~RbRJ3->&<>*FJ_VQ!j#uRJW6af(D75 zH`!xhj!nfiX4$hepKm%o=-M5JxK`!URly0@wd|8vLsWXhUOpzKMCL6`4^YhW^ZEuf zo46bWX0x{r(H0wt*N#7+;dN^r+E}kAgP&p2nKw)7I>*3raAQTc6;Y_reQLKTzme&c zgZKAoz)<9gXxMQzPzi$&Eat$lVH2kM1`#VSeCp?}rYM%Lh#JTKa)$X0?gbBh1LE$~ zG&$G`B(>D>y=*10c<%MocBNaE6e;MMxkCd8I9q2!QtiLq&&|1#VM+DhnKz{(eGV(t z8rDPk!$cCNCR8XviVv=F)m@@QPiw>FG=b3ahZ|uv(_St2368fsT{-Sa)#gp-G58>Yv zS$q5WeX~qRU@mV6U0#AYEsM51uYWfCO^dYDdRlQC)Im9(_Z++%N^B{boN z6%G~(gyhkl7K`WC7SjJ_bpb^En@Z6Skfx&1y?OcT`=7Mg(q(E^4N<6zJ3F7$-v)*S zF9PMjZ-wu1yLp0#)KD6DN2!J75H|Z86!W90o0LIl`6o4kt8DOSk-x(>KU}*ySbqN1 zBK|3)q_KJ2GVnsXb&M!lz&ZR7NkMyc%%D9P100eZd@0!196z3dt43KoHwTP=Se)FZ z84I!u(d&MZk`wjDAJh$hME~~i$T_F9Pb0K#Z`dmR)var(ST6x(xr}`2SnK{;8kzxn z+$foYaw75jixpv)l}m=2{{{oAXBk{vpqao%ij|2^DxFh^*Cp9S0NkUom>FmSl7@f* zs~ouN*VWOLkYN^-DU;FLp6Ui}Nxk=HZOWV!jb%rw=%ciVeXb)($Q)f=VS+iB=DjV4 zkTF$3Q6^}d&V>2vpCDEXm`)=x9g{_ZXPvD zdCgk3il!%aRj?j6n#YU%(v_7K-%cadAp1Y+ zQJ*f~GFL^((MAhipn0^5URsbpWv^rezJ-GKOz;h3^GWsVrCd%6kiOV0aB2B#o*xkX1Y_wC)Dom2FaeFljOm2L zaH4t~Eww*7Bt|9x{6%7^d~+xZPyf{{b+UZJhrS^&Bsa#kq#lST{Okax31PZK$u>bH zgsNq;H~cJMe;||*?1EOJO^jVxH|)znD(f@KmzWjL@(XJ6I8^BKdX2WxC%Q$sy=~? ziE87%&J`-V4X@=WB*IX~K;Y^Yq|}9cul3mN=e{Z^H;aZ{pEMI|Y?NQ|R!Zq8mhTH7 z6!B;9FGW70spR|&#q$%>So1vuxM@uReC0y}IcFE_KlPz4jfyF!BNZMb+9}Z_3?e|v zANy4DE#{M`&OxR`k5c07xJ7i(qc}(ZOxyGfosgov!;TB%1!lOh-;iHzO9;DR4nGlE zJ71aT(fqxBX?b&prfR@TV_oSoi=Pd0YZvpYR;A+lYG+lsGl^P*O4DENXms(&|G8QNi`CkphpO~?H<0{U&28ecuXk{B+ zhFEs!Kt^k1VhT>q_y^#4OO_E)M)^uF0l{N#F;!2Y&CzzcK_AMU0YRS4i z61hJ7-f8E|m)uW*M@1?h=MGPkCxqd-BB>!A4Hsilhh4%uOCMtHokrV~uemra^8@qH zF_$EI6*%{#dil~nqC7CJx7rP^&+u8vFF_%h-#*|K&!x3w3v?@bu8F0aE%k z7Sd}!cP~n68}9&TTk9_Aamfo_a+NAJEQe8%-c!TWaWf5LCQCoMu#=gn2u&DR1sLjw z?8x27SrjS=H>Td4eed=ZR&(K}G1TI)6jiVMbMSFkF+@{L<2@17#a zXld)Jtv^ufl3Rl^-=pUb!ku;0Sc0BD?3IJ1Ario{DQ_}9--0V7;80M@nVK0HN^;st zp!7cp-_e_bG@W)3wL8O>Iyh3giSZbAM#P*xnd7N}%^9Rz)dGau0hl1`@vhj;Em;Nc z9ZN2Q1XckhW+Y_Uy%n__Yb>G2gWTXsBqmD3`q*@(-kkKSL6P43bZNId@hueJV(_?h zwIP3<>hOj3w>a_8OlY?nX6i}?)M*UoSpTM9h5rU2Hp*-Alq>$&t98*1V5TTI1{8W! z+4YJB!OR7a2XsTpNvqsZb~L(W15xmwm|ZSYRDnew&m zSo{j*z_Q7Q{gZ3_aS*b}s#0jjq&8`etBCfV*(cPq|7$N-G3;C4!D&$SSVKPcCAGgs zf@bzqs=nnK#gf2ZiAde7&`4^d9R%QBDKVBnVsy=Gcitd9SGV9BYLv3KoQfhU){wh) z5=-p~l`4x9^}M?*G-yED(FAm$U9Fh=$>tW^l2~R!RWWBv)fM>lJ8WuV;2I(ed9~-- zi#1+MJP2`PEX2#vJpAXGe=351C;js(){6p@H% z=^B*v+leqj{!d%Y-;i4P*}|vIMAPvZcNz(>%rb3Kj;<@RN2^B)Ipc-y?p~cSZJxz@ zFJmm-!w{OHjggMl_Lpb><@2DHq`tc&iOFcHR-_M~4sqs4VjWLR;+jz&-Bj!i4g2$GMO6P|I(7=yljf{@83>c%dWJ)15 z4nhzve|Mbg>BvZ5BueFKU8xnzHMAiT6JheD7nSez^0PPc%q1_aiV+ARAmt)=7DCCX zLOWP)QCp|6^W=cQ5^WUPp1?7GS_O%2Pk0oqO|f1evvY(=E31=|s}wf$tf6 z!82z5-}5n5qm-YGTCwojH+$YFJRQvOAF6DvNLp(wdx#{MG*f|h-aV#vwmW4>Sz+J~ zd(ni1J@F*ArbqUh;~882Ah(@2nKdisiD+O*atw2&#n)NOR9v*qu$z0;@ zh|~VOsOzBn7#l> zxPTEh=@8w5xmW=L&q=Rr_W8^7`lSTf%gDj6yqC|roi{s}N39+lgOyvmb9x(If>7Px z3-*fQ7rVAPx>JzOESpR%>`ebKtGmH{r+?vo-v6?b8;SPuVYp!KekeM~($<5diNP!d_)Ut$Bk>t^lOUtV&@G>45rwS=g!lL@u zE7jqEt<^?VAmt~?)4*n52RD_a*Gx-E)3i0$cb8fvpD()vIt$}RqKD>-&a&YjN|xDc zdBSKh^w8vYzW8h3?-`dZKu8b9fHQqEOtz|{KZQw#n18T7N|7#olKwgvoQ<+GfAxTl za+5;I9z*n$_N=u-2$Q7z5^{P%c#wjcO`13pLFB&oVh1lW$-P3!>N^GrR>wTzEXP>-bHw=`YE zmb4QxSh7YGKggzTWT=s*elHxFe&0(V(1oR45RAt<%7%;%?dY#J$s;s$28rq)*=z-hAh?{1vP5ZW;XNt^2_69(Jo1c3{ z|C!qIl8S?nX1?Nacjr52g+_1cd`X=P-dq1C7E+JvmiVDm440O2;H3b2Sv3_2B zxYBs&F^J%2jR`Iy%5yw_=^s(z_K7B4Y0(A?ixJ^=rhEg>U8x9T9H^~8IrzqlztX!8 z%<+=3eD%;MCZLoJ0uolkK$dQMHxqS+l-W6Gdohl03ERKr%U=t&DMx<_s-9sOoV=1Y zqNWMnaiLWaNmrW#R~M)MiSt?i;x;L1XBdK5Qb1n`)PUsJ;c|a`E=c$Z)*tv25n(|$ zGit$Eqpxf4Y7j7EmhFU5o25gtE$KM}aL{`tn*VZ^v(wnh=pRtzQ24sW7-LT8Nj0*l zD=o|^{Yf8$DP`+rGqctEa!x3Xa1&C_xT+oHT3F9$lgwPLlB?1lJK$LvgGYN|x2-8(6WdbU<8jMX9#-1Pr40Nutx@34j|`x1mYw4Z z>zU%qt175thS(_^w$j_uH7h&^mPw7IBd=G;_3KtQtFE=J);3OijhX zCfKrKr2q|f0o~t|wv%-NqDVj7TPk=-kNAgoHiA#OhlfiTm6Q)`Z*Nqw@E|<|seDcD zHvCWYyYvPKtRf55q4kZKbsF)6qN8?4*(Irimm&LcwQAHT) zClHOH@MmNe@u`;$I1JuIyJVYjpi@rl;mw00SEUXW+FwHbHag8#d)MfdeidpX_mzl{Q57o@y z`MC#(_3_=fOO&gkJlCu%^Uo#}-Cui<+b5K#9y!d3kxi>vzva+rCPSq|6&SY~b)Byb z+TFY*uHvEj=da!MBK?0fU3FBH-Pe8SEq#L9gq`Re4B&9o~LArYgX^@w0 zq+98RneXwp*0c7Rau4SA2n0q$w=kp8J`&+Ty6A`lF{V{P2M-}uG}Ye zM`DY7Nk(0UN2gR~wDK67$G98gj#-BpZVWe09j){+N$(Z9pHJ!>vwC>I-fo@9Amwe` zCUc8dHeaEPBPOq-fOy=u6;zgLx}Wb!Zij7DNd9b-WpxF~FhEY*brS~JBDA!QU!l&* z2qc#4Y#BN7xc=Xpu_c}j0>byS zk%sjdvC~7JDc;=4zpT*k0b^X{;%i}yMSb>Ib2PJhZeivmPNA%kK?UhUnEjZQae122 z-|gAPF>bBw+RtQpY9iu0H|D2wBv~2M8%c7}$LR#3Yocqhk(qOUYPe+`e8L?Kuf1ue zNWKY0ry+#dYq7oa4`41$h+H7LEMO>bc-1SHYl~jG_{*1dI`boBCaKMez~Xq<@N#8C zA3v5k=QZBi&Kz_#ql9b7)C)56UzuU#{t4d|@1vVE@$ryhW@8&!DDGH$rZrt|OxK6( z4?9wHa2mWDXxOzQEu}T<3oG0M5jpinZ|6HLM?KYDCN-_Y(Y0>g=^#0>S#?1OICAat zEvN<)We%TK@VY744;Dewa5phO@xp_&xI{9h|Lo@Y(oio2Ruzfo4z(JdOhT!l)@23p zcV9q6#*F-`^v!Kg*oTdOg~@$lo5INK9D2GSSSLSoeXaeoiZEpLicuPVIK{Nk@d!c$`n7(kzjDym6wg1G_`I@q|J8(G++BaQUPW?AKZt z|4T#a`;v*p-$I1VR8x<~SM|vI$2++Is`R~)i>j0f_l@T-GNt4Tt^DmK2M&x=arKHr z^4*b~kqMm}{gSi%g}CBh6DioFJjl~K0>*3yi{O}MF@5S_aS8-tz#%4kVdXl3nheQ=$Uzm0F26*dD!s4MbDV<~7R#oz&S%OcrPr>V;av8}^Il)VO_3 zX-XL=-o*N2F2)!&?JRtKhh_Y@D*c&(hN1Ah_aK=9LKh3+{k zVA8RuGI}QATcVa3Xa9T&8->>VtomZOLJu?*QQF9D!e0^E{mi6s;#Bf^l~SQHgGiPf z#7BHZAt0QnbG>0emhs@iwSZ~*btCE%ti=4mbFZjTz#~ILmvguSmYqZ%{swmC1@=N> zR5fDs4A%D*`411vXTzRRyPNOZBTy+Z6-E>PjwN5d;-B9&z7+Ff2YH6I8wpW)wX}jZ zcsI&K2z3MR8+Vc!{J%i-1fkP1(l<$k(LYF8M7mq-&8re<>fES_Jc|d6kz%0~^zyJX zJRWCOUDs?mE=JV3*Y>e`{!flK@xLRwt{9^$4hJr6Sx2b#pYq21_>(R44VDQQ*gyZ9 z>SLb6e3#a<-@Y&X)f|APe?KqV20^9lC65Hy(Kj&2TH}Ue z;VbEiggZO1dJ7wB*Vd-cWlAGz2hJy;Qz3T=Z-=fgg^Yg%l23mTm<1i2-?ughZ6kKP zXJ8kZYOBKMcpwy)8^V}JmPh+uaLM`IM%I;W+5&;Bwia>3H{>$!(_(9Qg_bJ{S2vo@ zjOKi0LxQguT@vciNp$L94Gxn9p8FJAyOkP~k38e;r)y@zt|MLxbImVBm?(u- zELUn6IDT$dIu-x6 zs@y?xOt5spH1$XZ*VsEI_Gl!grf+ubW6F(v9;sM7(RQrJSO3AOxNddf!0k{`RvJGr z{?s5Z459X^kQ^x#g_q(y4I5ias4S zUGh-9%cF~b`8T2KOHx*k`7`%Mf(Sbsl0{y}Ll4Ur9$X`4!RS4;HdnM@RfUmRk-|M7{PQO|JT`}?y zM}`sX{V^6w4+nw;5U2py**}AlP`TQXgtXSwe2AQW# z^-<|+$3x9LZL6s5jHXd)C|%&RPmgX8EygI1-sD7T8^TvDJGk|?v)f!X6Tgy`6a!66 zz^|NQH_4k~-|>;WUhW9NeY5)#Rg~_eca1o%EO1_RJIs3wx&=N} z{)Ch6W~R~V!GXsw&2X<=5G{%nKjdx1iO5A2fm!*S-GPaSt%k1m;pX|#4dnb>#pBy_ z@i^xZb3DjNeVRZf^U@J66e{s{H{1L4Rp4WkLJ8re?T=Urgz+D}!=hEL1jVoP3C85+ z&ec@b9nx5?zfTjliMD)$dL0K9tJ81jz5Ehy=K2WNT#VPM5^eEJDZ}U%)StrCmHO$3 zS^+C1AfgmfD95*)k4rLr-wTyu0DXn``~xZ2t*AUNE|AfbXJRt!xhRMhz3eEt-hQYt<{wGEK)7{;D*Jf#wQZA7wNfoGatZsZW-C@J zmPmD6)S~oy@QAVX`yFhX+K~?f@(TZbd@kjB-QHI7wIPJWbtOv;PdPH+WKlx281=L3 zd!j?iJp&;Id059hPb2K=ZHeyMt{&< zqz5L_q8Z^=_b*p2{I>}B>q=})y{6A__muCrcIV4c{g7MK1y{n^EXuHmdMd*$hMU7O z7`~}qw^?-q0yZ1PVdT`LJVos+GC(krMK9r#Tav<##-BE%7NlIF_j{TTyzSb_tdh!X ztT>Ie=_c*IC2ua@N32uXYm*JuRB3)8M7QqTp9@h#TtWrJDn@uo(irXDKCIf)XVqJ; zf(FM~zppyBR7rf$0L?i_vy%W;S(%wYh$qhz`)iA)%B-RiNv&s@%g4g|%Y0-9FWB9E zKDLEYKAaC_+kB^Au6%I*i?7A~7VX8jJ2v4l%OA#sAcYzDbZ?LiQ#SjrkIy0NT`{*h zAowO^Y`jw)A_9@J`LU>Wf|W`{Cim6g%V+9^3>~%Cc@u)`_tb~vpQl_sN7!f2Vi_Sb zuOj5{n;HLLjjt@LMGI>u@s^C6M8VQ>b~NlrQ(H?J-q*uR)P*u#(UZ7qD+g8l{+ac( zMr;u)tLk*G*$`C6=dl9L@jX71Mrtm755f98@mI_&c7&U3SAOQ#=#?^p2^P)?^o(#o z_@6qNFg^~Rsd())R|PA42Kk$?RWgngsS35%7!4x|aIkh{>+@MmG=MZOFP98(I2yr; zfgiehxP1I92r*`>{YTPqv&eElC=QWOvRWBleBYMhkXL9Xg(NLKE&bjhX_{Cn6suRk zO5Fra?dr;rT8bax7;41$MDbr!hlP9+CDC1zIvS_0|A(|7BaqqqtH7d&Y zoo0Ocy$k&v%iIq0d!8cu=Tpw4hU9!65Z%B{+uIQ!*LRw=zy#sWz+404>b3Mf0Nhn6Q6;o*&pQxG_&QoSQ* zl}?P0PWwxm*=0s6VJ;B8VZK7m7)R@9l^w9!X7$Ix=ol;G<>Mq-R@CN-Ha?eXJ{Na& z%VW6w8;x&-}8yN~D#m-%&r)6S&TkSRwjf!|jvKghW zbl`dHP2Rv|JEZ>jDQi?FHJ9y|DiWJ&z-zvH7$pBzxglqoFNVQ+uVo#CUgj_SL6YI{ zxYgsm5a$*L`vZlFlYk&mxj1AmsN%J{x=Q!T!-7A*7mPBzwbU6U4s6u-UA{0BWUIgz zmw&i^r4GERo6*!P423hx&GlC-D`Tu7a0|_I!n)=P;Pw%j$}c29c9Udo`EVI8L5g;y z01O^(c(Drv=vG&f9)6UXdv)|K@4?+JwsRIMBLW~b;etdzTy)< z3PwTb%xY5?YPeYCCgrj8sgPM?LLhlr0>=XXGn(iXM^05^qy;Qh46=ekXQy+9Zglla=nBhPezquWtXTmrvrNT$tVB^UCJ4#EIfuRu8wU2L;QWc; zD{#%w&CS)b;O88V*YF`4)#0StZ(~2L&{lf{yLFNB_bYuYDyA9$4jnvO1T~ZfvU-PV#Ywph?94Leb4PfQ0>kBZ?Jm2I5h%+^ZC? z)FBV_;F=#QqkNLo+WSyGDrMmjn&k3#6));eDJlG@NBTL8V2r?s+vaO$#qWnQqoTM{ z-gGS?M(*qKBpk)2_G#}M zu-E2zHQhWBp>03UmZ5OMmfwVR&gZ}7OsIw7+~&IK1>JJ0B}UQ=sI>ZuBBIjZs*(G^ zBEwPg^F<@gXs2Pc8Q!87UKgX~zG|#vYibUsb8ECC_C>a2p&P%l?>+ZVmsyO_g0wjq zmUgV%An&H{?C^m)OC1DlBeG~3Xb1T74c3dCdR$|*)44cGyos3m+y5srwm1YyeDRjJd3kqf3#lUqquTAKB1O!e+gAq9sAM+3~$@ zOJ-%r%(2$nxmFg@Zd%#eN2|23DcbBGRmgl~goqFf!;=Bj!nu$3-;GHv%99sSJzG+e zt`t4b6G{bMYdd4Su(OpA;#wpFO2QmK#<}y~y?!T{6LCLzRjwFRJK?#)R-2#TFxdbT zv*ZbKl$EhdbqA9@t=wP-$6{oI_39GB1WWlzxrkKjn-xJN^KW&EQ)Tc}j4dW!1s*S) zYyOi$0}HfpA@9B%g_`*^IMu27%Q~2ZJ28FrPK)W+&T4Y!jSieZaCCg${Tr#aLOAXn zHXz1ojTYj}CZuD@&)mJYJAh~Prq+?`qI8{dw%d)!Np@SstU%3T z(mTvPC3UFR);(y=ZP?laR++DSFGn~n9NaG~Cm$hE z(e~NV_K?6;MA=*p0gVue2a=Gr|87>tM1C&pS5Ch#$hwJYSh#e$wsOMvv0~vwzl1Pb zLHPIxSr=M03N~ohSPb9Be@gaiE*QwTe_lRCnExZ^oGH;(1bu#^ zGR0sXdmSL0j3l2SMuQg4CHJ3bhce;Y<@2Mp-Q!X z1>9^295qCkPTJ9ka!-BZb^t7L$yCZTb)&pKzvv#bnvr*W@BF z2KgIiq~G7RdKjYp9f_XP|IUl7SC-Mxnj+9XwcJ(Fccz%~!-waS$)NJ{wM-_-1?_AT z!Q$=tIAcjny$Bl)eq|5Dx$jx}@rQXx>2e=2#==S;7CpZR>q)>r=`N#xa(x%4G=z0Z zhQk7mKYk?ggOh1pPY|*J$`w}Neb002KO2v@rFIH6R>i#76Ny3H0*V1*eg}e`6@U2N zUZuPINL%lM0}iQx&!6*_#DgV!kO<<3QcC%CzP(Aa!YF5J|zj%f| zHnpSUX3KkGBQvi$VaeQh=negvPZ-}AtttCu{V{s9pgKjmqr?O&gQW3LE$Y`-rid7h z+FQ}we)He6N6WdP;)5X}w$pe=^m;*V1JcYhTM}*sN~Xx{qc8dN_^J$-9v*Vw;}QND z$R4VXn{Ph;n)FY-1Wmaa^+hYAFLI)T(JKzj0ml1sP&F@x1=i2xy2&+kX^~QIsiMZD z=b}&%vj!RDM9;!3eZH+p_tPjP$`Jo_*BeXp=f)Cts3;f1-hHQsft4y+uE+QLKh_YD zd7s3`PPtp;aCs>!*58`D>|e89_le)sNooAP!8lH&lfKk&#N2o-%wi^fy{HpSEtr{T z`4NUJ^_}ywEY!Xl4x^dxGTkwqaX+<>7r~_g{3}XbQjm$rBM!U!^MU#?xh<**_V#NTF<~G%!#y1``S_B zH@%&o)8Daly6r}X_L+hY62yWF>tvi8sWSut_;!9j1m6xBNR_rH!}sgr5yGfd$h7wT zpj9MwYp$=xjaUb8U(-lt*{9N;!s*uS7cyF3Y<^5t3KE(!$ zxdV;O{!0Mm>Esu3I+#hBm$ltr3Or`C(1m43ROM;H`8P?~pky*I$d|nGB9J2QbJ7N- z3D&Z+UnZktrk~)hY@+`VwaBhU@?KF@!z$B((PFtXi-7rcPyFx0usetAoxvx(;Ui)5 ztPok*cAlblqpi}`f_o10sCcimn2W9t{%+tMe)w5$LAiSrtbbN;>Cc?^JMI0^w+wWm z`5s&U@6kLz9AvVnQ1C!K2uKwI`{lDA=!hHtmk{l(eN*!`=qI~tjTf{s z#(x(l%kF$C@h|L2aI5nPW(3*}oe}aIqWxElNO`E)`DPj5z0{9&WDSuhCjZz_6{1$t zL=1q|A{SusAPV`%-HT0cq!caWFW0{^>FoHw$ z?JS00LWro2L1;G`(m8RPv0iH>j@t&1))BW zy!<->Xj3_rz6cg9yw7Cp2cYh0!y+@0fWc`eZG2N>`i~I~v_kRMlttW&VxPDK)KZ0~ zKo;g5^)G7sMd{(35DWc`I&I=f(wliO6dzGE)&KaX7nKPhlgMfh_N|hNjY9Phj3$2Toj=o} zeA;1Y@@wA;^k9aG;Ztws)TO)E2u^qYC&mGp>%VIAO-y=3z`dMe|MW`3cmqoNK9Xm) z0W_;|92O(kLh15G`<-KMu?4blW>iac=XbbkQ!l?ST0qk#@8B%%07!3GE5eExn(3G> z)qt8^C4#Dv@T74`iv-m+Pnx)5CNc8N)yc!&)x$G*8|_`-W%xgUYf-QMwLke`!jlO_ zl(J{+Q5(cJD`R%A%*cl>iC7Co%J=_U$J~=)p~$L-f0PGaHWDjY0$vrM2PPgSg4ETt z0R6FHMxm+Ktjr%3C2pBJME*>}9G3lG@z7PBb%z_c`1Y5KPT%V!OemC4BQ=}+0=C31H(je`3j;As8J0_dvt0V z|94ZK1D(w)2t zRubjE(a}2Lf5%HMYUue`ljW3CQ-$ngxcSsVh{!*z_w{JrK1fEV)lk|FK&92*S#F!# zQg|z@DuI!_hHHyV?X1uy|CF;ppEJTluq5(1=ps=1O!PDPFel3`#jtJkj0N4AzHm-n zzIXtqNPyb{2`~oW>KBmX_ynO_sBctoKMjskgcQJ( zy&l{G=ha58Jrv%;6WFxz3CNg7E=ZTeP|DrUxnP_@W_b8K>Z-};TgakqR?pG{1KFc> zL5AIlW=8eA;7ZcEU-}rPpl~{O%>}Hw7WAz9XigH`w@3avzjq$naVMjyBoX9E(O_(3 zfEf7C$}LBIV7sPP)Hy#gjJ5&l4(>Jh@E(Wx5%}2Zam^d%f1!>7HhND6Z7@L+h7vli zVr0NGHI2RzCWuolN8+s6;Angh`ci~L5Y!c$ekGuSk=%86D0>#5iyWL%Vo~i`R#tYv z4i@QaE(#m5TC|l&Olu+)RNBK0Y+CT&5uFO8G!vfHNrBI|`G%UBFL6dFXIj43J>^Bw zZ{V+gx$X+$3%Lg(L2nE5=3%551GVrm{sp=fHrtk9Yjq}>}j^UQN z-+ik|x$RAj?v;H}>*Xb|cM$D+`mYyS8;S7de&1R?8fuM&W~_UdXAFjtRQ}b!usTTx zbI}f%B6406@?;+QhdX0bq$0phHfMm)WeN^54FVkkzM^N)wpJl_DlG-K3A~~Va}jDZugx){H%r`m+-HP zrX7L!tIE#wQB0qQ%YR21Fg0w3<4Y*b%&`+1?PT1WA);B&J}KZ~)_aZ3kDw&oc4mWV zsEXoBfh3P{7I62KW;Dhq`Qs-I9lL}6H_hbN8zyW!AdX7k@?q8C{RGsF6w%+P{XjK< zpwsJo>Vro->@S!`@4`_s5d~~Y)ZHYL7|*cu^9)s^pnQGJjLyii2jwN%3+HU__RhgQ z#!cs~=4e%)dVK;Zh8tBOE$~pmc?t_OdL_66=PlMY?4a+SQ}^{ zD)H83RX(`1>+^giA21_gyzAJYlP9sdM01txpMiq$Pw{hf{mfXE2CA<*XBq1em%MaC zIjK%bXm~UFT{CQqAK__NYU#}qpY`R^ps$*B-SI_>7|kA77?mZM^vds1nh@`!TSY`- ze+V<%V4!ayYC8DPUHz~MHT<;7zQmN$&uL{+=#mhNp(F(aPtP1 z(~73^hO@9k$H8zN9_2d2tVn3}A~q)j-lCmxertT}?A!niH|Bcmu15_r*2mkvU2XmN z7NC zEz%%Rw8egE>Iwg)K1sl)&n${#u7bdczjt@pWaoa-fA+QgdJ|{8`vUgDitWMnyaNN1 zjwYPF`0}oPlsV&X$VZp;mmr&S)#O*zhsb??(!Y_fUqW)5*Mw}61DlTpwG)ijL=MdDL=2=?+(=^5` zpjmfrUXKrS`$t)sH9w}UD~-L$4W{37+zzcYF6*hVG@Tx}tjRsC9}ZPh5iyD5gQvph^?L&n>nscX)28Uelo> z0^x~%ZZqs;q_Kq5>W53Gj%(b1JvY|< z5z68BsYVD*O}y3Wm1z;zQL`@-5UaOdFMucCaiw6?ACAF)SNEaEHuZ-eYw#UTnNEMX zEoQK1)S*%x{Llqg?2c8}#L4*tPowxj{X)b&649u>GjskeWrzz zw^+jfqOj@+F+N_*F*p9-yU*^e=8u8Wm62~+)r&}P8`m&$W|E8%hky+FdQ_z`b!fXFlzkDnf-)hyTn1`>l)KS!8$Vnc+**$zt8CS(uWo19!93Jg->gb@=@%-lM>sWvQ*wzM+f2QV6aqhWeI|gGX~`+*tqxr zCPbWY3c|jYDgy_AFF_?@1o_@XsSF*PME%cA7I7xNR2vQ5?9W{3{V^q6`k)R_SR!*) z@IarO$_ivMifY1L&33Jo{3oPcvM+Zb#uINH<8~w+$Z(| z1p8cyH4a^V#BKh?|5&h8?1;R`@}uHSh%qgLFn05x5(4X>JK%2Q+ag2Pqhi}8|CGrD zBI$%1&bsp+gOf-w$qTyM?d2Z62;_lRa^aT7r&|RNfQtXm19qmxLq3_5Xfr?*jyXcF zL>oyNy)|-O%ehO;9ym4J3K5wyX)k;&r-~1u6Mq1!p@5A;Ubzyp5CPEh{$IUn(oH!j zB>u7ZO7qavRu@iJ#8+70Ky^q@StqUKbubp$>l{MRBI0n?00)~TWyoE_D`JPYcm8Tpl%b_6@8?y@5`UoX|O(j5g@h5AhfA=#vp$op;n*%{ zvjN4oL0~Tav7qnrkRoU7w)Qb93hMT+xzU-b&42Ov?lv+Hcd|BnMYNA$k^kdKV6W2W z8Vt|@onu1yhBwM6Q03-l ziT-R*p=Vzb{>BsD2VHoCP(66SRAmz}x3Kgl2i!S0_a%dJIYGle7BCHEY+du@=WU?gvB$rCiP9$n$KoQRB`7DXm`hNu%8>&|b8`)XfIn0(y z8A>7eh+*b>clb4o9-G&DZg?u6&D;;{&J&6;Q>)BIoe`dXA3~kKZ!fyQyfrtwK}WC_m#kI8ihUH>!>whAJ zNJ^iu2yrVlt%u@|&~?88>c2Z5p!gV~a{!vSq4iEK1EVXOPD;^?X~rxE-ITIHB*Pb7 z!zT{0-+Q9SGi^rysNgGmoNyWcTEzq`l%mY-ied%MmNHU$evmwq3eLuc%}0eUl6hq4 zTY%a5!fm;9Xbm;=EHkL?0LL*lUFyw%^Xnf-7-?_xzH#oKh(BG8pXsAioJnLa!3ce23*DjKao{9Hrz=7} z*fHX0caSfb^FAi%SQYoHMgqqknNuU207DXP1;N#80pZP@J`M&lhKCmlP>9%tv!QS% z%VQ&Qv5wcr8!sFc&xs6IDiLKR=xXj@Q^;;Ex;m#z_bSUx9X*`MArF~z)!^KP7hAp9 z2wR%nUD=1Bpp8#d&X01Wf-3Xx$MVxaZxxQRXNNB~Ptj4n!>mk414_SbS+G*yNAFTU z9b5&xSO@(gPL}4kbMZe{^@e#H;=M?jo)Le0VPzxNi}J~0|JGJ`cb9oR)^|0+X!0H(Ezyhv5tPHV53QdduYMTMw15qIWJ5&#b8 z4(cdv#_2KyY|V4Y*4#cJ^s`B%J$v=hcy+&~>z%|6w)wH>|QRq`V11&v+fM*`uCkD>9Akuy7ph{D&~F}1@EIY znD-Kq5@(!VSzE+j)2XmeG5SY%Cs|_#_nk|EfRNEWG08*>w`KE5V^$Uus&xaFVzVy! zvqR4@Q;f`1E$n)Vq;aBNsr*Lo+lv*Oj@$U+l2&e-_vt_Cj;IqaTAdVmvXSJuXT-i2 z>gHn5;{iAWX1Ny1L=k8S+S|u1B-r(mFsECQ{_a69j`}SZGyeh}0|8s!J;FI|i`ZtE zDR2^FJim^UQ5S@Ta)kG;(`!c>5~%tq=2*NkGsIUjad+cCOdUKM{g0Rd` z#pvlTwe+U>Q&w>r z%L1G5plAxjfCnUDNOr6 z=hwSyYORMc@B80fSoXLKFh)S@1uSJy=H!Z@<1;{ZXoIA#H!KLV7YDZF+}?^!v4||_tB|)u(?W;M`*n#K{ovcL zpp+k+QwSM8JZwTe6N-7{1egL)w4b96;qs*i%i9It(&)4;<#ok0BMcWV6RD&*-adNm zBPH)PJVpVUO!lm8;m+!DhUAFu7S}s_B9Hfqz0bj8m?m{X)A`%%a2WhNJ)|wvMi4|p zF+-%(iR_tB_p&YUWWa*D(2(q?>owX2#`X|}NEqP>z0dkf528pmPYh{v1SGN57s_-c zkThiNJ4Q0-$957>PJXMWADum97xBlB z`g0x$aFw%edDj2D6*Gt+t<`YP;O5_EIlRlT7~{~UpH{rBCp1wzD@DwYKt-s-bNTTW za@+K5WxRCp7xqV~`=9qP=)XkO7Kv4G`AWSk+W`pFrPVlc4EBtD7&N)Z*LFhJ`hrRj zmJmc}{#I4L9xjRVgEfMSURp{aoW#hO73t4@K$pEp&#N~)$%$!|1W!J1IKJXrv$I=5 zZ%6DE+MH-R&k>$3)s2n%-`#fCi7uMiUw^0`5XZC02#r~?Rqdkb@V0ADD?gAi*;O4K z6Nr2)`UDURZ+!W4!q`)GJn)`ez2i(~C}3O(WAOSgfJ4^TJ>)2CTLuOoOC82#y~w`O zaQoYcXtQnfvMBCG>x)2T1*5Aq$$i1lqq!A_v5VZ%*Tv`KrFaLJ97 z>6S5SYd#4?HT;JR_?DJemPZzdOEzVbk~DgpN+@iQVGIgJ9&#U;@yTM|XAe;sN4GuiZGK*69J)g3_f<&^sfBzF6wcHSH|HtpSsU+q67L#<#E#yc z<}3RM-$INWh7G_a&|=tf;^@AGMy2n`rw94vx+AbmZHPPvLDR%9X;{HprYZSM43jNw zsk5Q;dSFW%S*@etwGNE@=;d?!=rf7D8OKe{=^7ziyWtuc>%sI2O;*e9=FsoS(G`9U zIoKR0bByz?tj_1Hty0BA%wUB#6i?}LE;&)B>b!_qv|}j5fYxhf_R_5%J!jsenKEf9ovn=E+N00@YZ(anSjkCvbW^!T!>p zpA#s*HfNb@u@1=5zNS4V7rN`evh3DM-~u!pGr7?bsMI5QP3`!CdlL1om^pzco>&}X zTY$I(tF8~&oMM!gM_)sgt%t3Q|B9Yrp`%4*GeW22?7w4dfRm^X(l;JccBjg3wS+|; zA}lL>JPiSj{%EudVMP;3H%Z3Stc4wL1$MijJYKwIzm4;$HswO?G^leaoId~e}tx$)x)!(7tk z2Ev}v#~`yNwrsI7n;CD^ift4gS>MImYBMq9N%a3t^A%YdV1_QaFN>)&$R^sM=@~f; z!!VZ)M++F%l1RtVQjkRw8TJXq88&D+QuO^)qa8o{=fY20dbq=w4%Y&%+lwsevcMxE zs3nP-Iif0$AdV^F6a36<3W3)fs!>J1h$~;1wJ9n@1>D`j!+gBw2%OK%ow()Cce=0ns@n} zxo`{wcvUDHK-5XLZ3dGRdlq4%+G+kR*Lf;+D*M=&-(C14Ss}v0E0;fHES_3!e+lnk zx2?tm3(GKBNl;Rm;c~7B@lDd|C_gFmsG)eDd^q>dn+e~jac*;%1)ZRQpV$abv0Rmr zK<|1_dU?J*DY`W!^*=!S!$n>bkF^SmTc2?Epi*{_k~8Fu$CHD9aQZ1?h)VaK^0u$V zdssheJhA5xR;{(Bydx&@g7{qVjTz|*|#@hy&C>%r2 zGx>QsNVu-Yp_HPoZaN=(_T3&r+k62tUYDBS&&Z~P!;`?Soy#LX3zG5NJGQxW_yctJ zl61$x@+7SqKAVp}7}J`)k%QGY^=%rOwM4^A?UI-6&zcEDG=(6(ykqv&*{}&Bdqv}A zJ3D^Z)+aztC%_T8;LX^SBlMl6{LO(sj2CI_xDM%MmlA5>yK*-G?ReBM!{`CV4_vzCgw%)U7Ju?K6CoekKr-OGPQ~ar%6?Qp9=5uV_%~$X zp662cRpGn6XUyQwp9=D-J&tD4W@deVuXa65`Bz>B7&1W;{b8=g6RNvj%FABOESeAn zuQBX;vnLHj4?&Avfs zsT&RlK&*}vGW39cl<~U6H5wl=olUOsoPUv}J$ic{7UbvMvOBIZ?<%r-{v8tuv5QAb z3z@PX6-6tDjRbMn9?7cjF`6d#Y+-g-gJw_^5%H z`%Xc`q|}e|a+1~4A`M4})3xKVb?Okdjghyv-mIFREdR_k`l7|q3RTs3(fL#QIIPzO zU&nPr^de6L=f)j#kjElwV1k1B_IVNFKYPJb=d&O9+epN*=gDE!+E3n0{Gunutz{z+ zX;W}NFq!9*|D_**i*{`RRmg9ufR|Bb;KACrIXkHMOwVI+ z_TuVrYG*!p^3=}9CtzL?#!>osmnYLWzb=IU%)_El0p^&XRXVQsKRCVi7N2IOEH z8Uvti{iPNWID*9x0zLP<-PbtN6{%XY<@4|7gXTxt*x-A&cP)~7CdC`fq#q_SeCOP` zB~Z8M#qU3MabD3#e8W+4ed(!WKC68GVT=u|Iu~%95kYQsE{1Y7D`C${k+fLlP)CfuZakpi zQ*Z}Rfax-eiOa<)feuLCzh=?SwF6~?#OZzNMD5)c`&zg*Brj>&}Rk&(?6)jtU2zAa#5P^0o8eiUw*2tW~sv~#_NA<{sZ(?_Q z@q41N>nlGje8<}gMjP{+b4xJ_LBi~avK;E3TLJBp>h+<|R~7}g2#_pHENEFzId|_Q zxrr)$DZxz{KHcX2q;PhSU*k_mY1NwiDNjHCt|xlfx!QZVsJAozQRxUT;YMwrpuyD^ zd8~bgByTNO^Ei9Q(?{8JmdY&49syL>-!@~JOCU|Ya+6aZiW5pPK$p{X$^LN*_?U_+>fXP-` z#{KD2X~iY!q?F$lwZyi%sache(veE`wI^`(B!P3qtf(M9&&)z4t}_Zjqf%<35ONoM zzxs)+y0b+GI5v5vbkG4b61n_V|AV3jbJ;l6{Co}gzSO!R+?b4N?*}^grHU;Kt(_*- z0L5=4#aNU&C~cm6FBSTq4)$lnMqAgb zo^SKQ>`8!PIN?gaJxE>i{!hrI!w->kT7bb5R=;TPb{3f6^Va|iI68)PpMC$huz1?` zB#!<13`-eK;`72HIS{^V5~uS~s^GEJ6U;OSaDekqcaD|F=sx$b4HkrOT(O74)dZZ> zO3}x-Dl5PqSmK!!6|F)J-Wf6kaMQjU^~pi7<%GnJ?w*N|f_G~CfhezTM_28`FW<4? zdRt~`bMf^z@={0`NdW)W=shMcML=%tL6ZQODT9SE1C~r@->`wSbXXUTp9mZ~W;x4F z-M!%rG?R!X z992|=(KExjpZEo4fL1ygLU2n`15kIAnC0L24my@R9=~Xc4!DN7!u2j6D=k0>jNpT3 zYN-L5H-A#g<`8->SVDz?!`7oGc!^^Z*lUE~qYD#8KrS6&oBb^|&@&@02jQJupQ%=@ zhF%pJ(BD=eolpU$iubJ54h$5_eB~}f>w;bhn7F)d`5Z61MQ7m!Ib4gsOljeJ_oZ}z zrKuj)ARHODmLx(k7F0lecxUqnz;&KvT_pwzL-zP+&ybiffs)BicUWjS7O)?5ty6oz z3?H2LFBG4k`ThWIa#VxE%bxgFBKc24GgP2L;=0FeEB(Q#f1T(iq82K1zM=!$hmast z!c2{p4rgJFFOR)~sKC;(H=YE^CNfFy%Go+SWdOY|g{jG079|q)mxh@O1{WVhOi{pL zc+EK)nu5^l#Ce4I4IOxT*rV8klhD&Im5c!jRIG`Y7rQb$3WFd;vcNeH2TJg6fj*nh z`tz{yi}(`_yu`Kou|Mo~_GdfHrtbbCEa1ms%u=Q5WM#&u?iHJydFUgzJ3at z1HK8~!Ir?YjH0*<@{ayVwSJ=kx9M2U?!PyM5fobFj@JX>7+uhU>B7MQ_4hhmE#J*u z2HJWa@XLslU#3QZx8bTm_xx`U;izbsjw}HXmBVzx0JaHV_Ohol2G0<*v32&zLNMVZ zGDN!}LO_(Lfe|_}STd`>v<5sR{FxM=NqRN?77s8?fQgAYoZn8lJS(Rvyg5!=%h0%C zZ^lsvqTCg`n8Emhr8=j;HuDT#4h|ZttEFz(?^+}9sv?2lrSljde$t`_ftc^4BIApR z+0k~=0#^VXguRB7VC^f3AVrXHD2EVu0be}Vz>YBQX-XR=i3U6( zAslH`p54GQ?_l@v63_Q%PL?`ARL{ieIxM?Gd@lDM=du>rK(#Vx^D6+vxm-uzYU3EU z`R-%l&K&Un*$H^OxO-0rgx|%PU%c4Z1z}RnAp))>Sn$Df-kqXPYz0(-7(cC25Q8_z zfXhRB6ky6vJnXZRLEhZue!feUZN!WTiOvaqz7!)WaMT*)1g%2>svi@O?Jw@nz<*eNKMv*ExA`rbAr0fzY0lZ}XsX}<2)dvUHsBC~l!XWkJOybS9L-xn9MuqlZ#)8-0CKlsd(?M}(x{uc8A#3@^|JUAiJv8+M>rFxz1SuMn5|yq}M4AE#*Z>7W zw*&(s3Q`p*N=XE4AVs<$-O!tYG?h@K2?$a}s(_Txq(cHZ_x$cZabNCv$ay)tJF`3A z&bPDK9dQj7@V+n^@y>;EDK;J%{KG93TE9%X#|3PO+cg}EYSzI3Jsbqi3UD&vc()B!S!f6P{+hfjjarQKGw98Sbybj!D%{uygbWRS?5n zrRB0!O4?(-D7+0gRy+X_M5@v37Mlg-kqhObSbbKI?o#&N5`jr<>&XexZ3bK(9;x4lM1VVa)=+j4s}ef5x_qxBrOgeMnD0Z6;$S*Ofd=xO`0G&j(nx7 zb^H;tI46#D<^z$j)f{QgCq&0omn@XYnH8SBKGs;q%2rUofm(hbc8b^D^&;#<#{XPmuuOVFtf>Yw|6s1={Ev4D{kacr6;G;s#N5JTB z(&iN+X)Gr1!~7L!5@Qa9|9HoI!Giz??~os#CMyP)m!(46%UHiVJmBa8%~^%_ebd>1 z?T;>BMv^(fPrPpP`Ak&==J0X= zu|1}^9Vq#_x2AMg5NNMau-e?CAJ$3YWEwVtCJy6U(ZHU}rn2ZLj1h@=+OH!ofJzBq zxB>>GVkm znC9-vp>KTwWrpxLAGCRln5{`M(zg4zYqra3vmYi)L_)9_n8@d%t|pOWPA-rC-4XZq zR{HBE(MR>Q`Agu$90RrXuec+{8&eVQvkK)|TQUU;m3Eg&yU}PcVLYb#m4wdVy~#>W z6Z`cIxH}8F;Zu*Q7rd7MEN*OkFSZps{88k!M#z@fD~CcOamR$WZ$aXx5g30usi#K( z4EI6Io&h2=49jO36`7lVbGNrIWt(@ zGW5m@{+KaGnj0WN>L$WFAt~J%UU5qlY8lkjM+%btk9vJ+0k=aFx&Qs@KgAYYA6x#g z9!@~QQ#`dQCk?tA_%NxU2vb&9hty1NGN8cPAQqWItb-m1- z_jM}@cSzcm47Tb-5B`ZjeRYf7@|F~f)?=;h$@Lscs3xhkzZ8H>q7&zZXRM&-Cb`T% zIDoSY)762vBk!+%>;QHAsP;FuLyp+hwH!;R__?g|LY9Uk=gWN>81#74UAdoEpFR4_ zO6ADhxhzhafPT#u&`4S0qQkJ8XFYug;TngyiW0Ks!e0vDRNz_WFm2m)jH=a{P*iKA zliyWc`Bdi#F>HdCcHSfom(gd|v2uI>Kdmc_nQ~({Rj&0V_E_~!o7URhY(uBDmku7t zx`I?<9B}Pc09IZr$o%cz$3gnd$=eZ$y~F)e*H!zlMX)2kZPf~%e|mpA5b{dH`^aMM zk0`LU9Ou$c``de*=bXr`)z2OZG1}FKRvHLBab~I4==1)&(oE OeTZ7SNlZh6)yN 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 ThJU*JXN~$7jV=`F+64X&1Cm5a literal 0 HcmV?d00001 diff --git a/cmd/pad/main.go b/cmd/pad/main.go index b27b44e..6f73450 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -371,10 +371,27 @@ func run(w *app.Window) error { }) } case key.EditEvent: + if focusedID == "editor_text" { + // The commit range is in absolute file runes (the + // coordinate space of the pushed snippet, whose + // Range.Start is the context start). The logic maps + // it to bytes against the whole buffer — exact + // even mid-fling — and applies the drift guard + // (see editor.HandleIMECommit). events = append(events, ui.InputEvent{ - Handler: reg.Handler, - Data: k, + Handler: editor.HandleIMECommit, + Data: editor.IMECommit{ + StartRune: k.Range.Start, + EndRune: k.Range.End, + Text: k.Text, + }, }) + break + } + events = append(events, ui.InputEvent{ + Handler: reg.Handler, + Data: k, + }) case key.SnippetEvent: // Handle snippet event if necessary, or ignore case key.FocusEvent: @@ -387,8 +404,22 @@ func run(w *app.Window) error { } } } + // Flush the IME snippet/selection for the focused editor field + // (see Renderer.FlushIME): after the key events above were drained + // and mirrored into the IME model, and before e.Frame below flushes + // the ops to the OS (which is when gioui compares the pushed state + // against the IME's own state. + if focusedID == "editor_text" { + for _, el := range frame.Elems { + if tf, ok := el.(ui.TextField); ok && tf.ID() == "editor_text" { + renderer.FlushIME(gtx, tf) + break + } + } + } e.Frame(&ops) mu.Unlock() + if newScale != curScale || newFontScale != curFontScale { logic.ConfigChan() <- editor.ScaleEvent{Scale: newScale, FontScale: newFontScale} } diff --git a/go.mod b/go.mod index 9e5afab..60b5f4e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.2 require ( gioui.org v0.10.2 golang.org/x/image v0.26.0 + golang.org/x/sys v0.39.0 ) require ( @@ -12,6 +13,5 @@ require ( github.com/go-text/typesetting v0.3.4 // indirect golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect ) diff --git a/internal/editor/frame.go b/internal/editor/frame.go index 77d2718..2e91ff5 100644 --- a/internal/editor/frame.go +++ b/internal/editor/frame.go @@ -60,6 +60,7 @@ type Frame struct { // poison the WrapIndex for the window's lines. The main goroutine // drops the feedback for such frames. ViewportDegenerate bool + } // frameOf wraps a computed element tree with the current view-state diff --git a/internal/editor/ime_range_test.go b/internal/editor/ime_range_test.go index 49fb886..44627ac 100644 --- a/internal/editor/ime_range_test.go +++ b/internal/editor/ime_range_test.go @@ -209,17 +209,21 @@ func TestHandleReplaceRange_SwappedBounds(t *testing.T) { } // TestHandleReplaceRange_WindowOffset_Chunked verifies the scrolled-viewport -// path: the IME snippet is a window that does NOT start at buffer byte 0, so -// the EditEvent.Range is window-relative and must be offset by the window -// start to address the buffer. +// path: the IME snippet starts mid-file, so the EditEvent.Range (absolute +// file runes, addressed from IMEOffsetRune) must be mapped through the +// leading context and the window to address the buffer. func TestHandleReplaceRange_WindowOffset_Chunked(t *testing.T) { newChunkedState(t, "Hello World, this is a test.", 8) // Simulate a scrolled viewport: layout would have set the visible window - // to start at byte 6 ("World, this is a test."). + // to start at byte 6 ("World, this is a test."), with no leading context + // (all ASCII, so rune offset == byte offset). TheState.Editor.IMEWindowStartByte = 6 TheState.Editor.IMEWindowText = "World, this is a test." - // Replace runes [0,5) of the window ("World") with "There". - HandleReplaceRange(0, 5, "There") + TheState.Editor.IMEContext = "" + TheState.Editor.IMEContextStartByte = 6 + TheState.Editor.IMEOffsetRune = 6 + // The IME reports absolute file runes: "World" is runes [6,11). + HandleReplaceRange(6, 11, "There") cb := TheState.Editor.ChunkedBuffer got, err := cb.FullContent() if err != nil { @@ -234,17 +238,110 @@ func TestHandleReplaceRange_WindowOffset_Chunked(t *testing.T) { } } -// TestHandleReplaceRange_WindowOffset_String verifies the same window-relative -// logic on the small-file (string) buffer. +// TestHandleReplaceRange_WindowOffset_String verifies the same absolute-rune +// mapping on the small-file (string) buffer. func TestHandleReplaceRange_WindowOffset_String(t *testing.T) { st := newStringState("Hello World, this is a test.") st.Editor.IMEWindowStartByte = 6 st.Editor.IMEWindowText = "World, this is a test." - HandleReplaceRange(0, 5, "There") + st.Editor.IMEContext = "" + st.Editor.IMEContextStartByte = 6 + st.Editor.IMEOffsetRune = 6 + HandleReplaceRange(6, 11, "There") if want := "Hello There, this is a test."; st.Editor.Buffer != want { t.Fatalf("windowed replace: got %q, want %q", st.Editor.Buffer, want) } - if cp := st.Editor.CursorPosition; cp != 11 { + if cp := TheState.Editor.CursorPosition; cp != 11 { t.Fatalf("cursor = %d, want 11", cp) } } + +// TestHandleReplaceRange_WithContext verifies the leading-context mapping: +// the snippet is context+window and the IME's absolute rune offsets may land +// inside the context (before the window). The context contains a multi-byte +// rune so rune != byte there, which pins the rune->byte walk. A second, fresh +// state covers a range that spans the context/window boundary. +func TestHandleReplaceRange_WithContext(t *testing.T) { + // File "héllo world, this" (18 bytes): h(0) é(1-2) l(3) l(4) o(5) + // sp(6) w(7) o(8) r(9) l(10) d(11) ,(12) sp(13) t(14) h(15) i(16) s(17). + // Window starts at byte 6 (the space), so window = " world, this"; the + // context is the preceding runes "héllo" (5 runes, 6 bytes), starting at + // byte 0. The snippet spans absolute runes [0, ...), so IMEOffsetRune=0. + st := newStringState("héllo world, this") + st.Editor.IMEWindowStartByte = 6 + st.Editor.IMEWindowText = " world, this" + st.Editor.IMEContext = "héllo" + st.Editor.IMEContextStartByte = 0 + st.Editor.IMEOffsetRune = 0 + // Replace absolute rune [1,2) ("é", bytes 1..3) with "@": inside context. + HandleReplaceRange(1, 2, "@") + if want := "h@llo world, this"; st.Editor.Buffer != want { + t.Fatalf("context replace: got %q, want %q", st.Editor.Buffer, want) + } + if cp := TheState.Editor.CursorPosition; cp != 2 { + t.Fatalf("cursor = %d, want 2", cp) + } +} + +// TestHandleReplaceRange_SpansContextWindow verifies a replace range that +// straddles the context/window boundary maps correctly through both. +func TestHandleReplaceRange_SpansContextWindow(t *testing.T) { + st := newStringState("héllo world, this") + st.Editor.IMEWindowStartByte = 6 + st.Editor.IMEWindowText = " world, this" + st.Editor.IMEContext = "héllo" + st.Editor.IMEContextStartByte = 0 + st.Editor.IMEOffsetRune = 0 + // Absolute runes [4,7): rune4='o'(context, byte5) + rune5=sp(window, + // byte6) + rune6='w'(window, byte7) -> bytes [5,8) = "o w". Keeping + // bytes 0..4 ("héll") and 8+ ("orld, this"). + HandleReplaceRange(4, 7, "ZZ") + if want := "héllZZorld, this"; st.Editor.Buffer != want { + t.Fatalf("spanning replace: got %q, want %q", st.Editor.Buffer, want) + } + if cp := TheState.Editor.CursorPosition; cp != 7 { + t.Fatalf("cursor = %d, want 7", cp) + } +} + +// TestRuneAnchorTracking verifies the incremental rune-count cache: it must +// follow edits (imeAnchorEdit) and agree with a naive full count after a +// sequence of mutations at various positions. +func TestRuneAnchorTracking(t *testing.T) { + st := newChunkedState(t, "hello wörld, this is a test", 8) + check := func(what string) { + t.Helper() + full, _ := st.Editor.ChunkedBuffer.FullContent() + for _, pos := range []int{0, 3, 9, len(full)} { + if got, want := st.imeRuneOffsetAt(pos), countRunes(full[:pos]); got != want { + t.Fatalf("%s: imeRuneOffsetAt(%d) = %d, want %d", what, pos, got, want) + } + } + } + check("initial") + testInsertAt(t, st, 3, "é") + check("insert mid") + testInsertAt(t, st, int(st.Editor.ChunkedBuffer.FileLen()), "é") + check("insert end") + testDeleteAt(t, st, 2, 4) + check("delete") +} + +// testInsertAt inserts text at absolute byte pos on the test state, adjusting +// the IME rune anchor the same way the production edit paths do. +func testInsertAt(t *testing.T, st *State, pos int, text string) { + t.Helper() + st.Editor.ChunkedBuffer.Insert(pos, text) + st.Editor.ChunkedBuffer.UpdateLineIndexAfterInsert(pos, text) + st.Editor.CursorPosition = pos + len(text) + imeAnchorEdit(pos, "", text) +} + +// testDeleteAt deletes n bytes at absolute byte pos on the test state. +func testDeleteAt(t *testing.T, st *State, pos, n int) { + t.Helper() + del := st.Editor.ChunkedBuffer.Content(pos, pos+n) + st.Editor.ChunkedBuffer.Delete(pos, n) + st.Editor.ChunkedBuffer.UpdateLineIndexAfterDelete(pos, pos+n) + imeAnchorEdit(pos, del, "") +} diff --git a/internal/editor/logic.go b/internal/editor/logic.go index fd7e6da..fe33780 100644 --- a/internal/editor/logic.go +++ b/internal/editor/logic.go @@ -73,6 +73,7 @@ type Logic struct { openFileChan chan string retryChan chan string // auto-save retries autosaveChan chan struct{} // auto-save debounce ticks (timer -> owner) + // flushSession: one-shot request to persist the session snapshot now // (the OS activity onStop hook, see FlushSession). Buffered 1 so the // requester never blocks, even if an earlier flush is still queued. @@ -434,6 +435,14 @@ func (l *Logic) openFile(path string) { cb := NewChunkedBuffer(path, chunkSize, l.mockFS, "") cb.SetWorkerPool(l.workerPool) TheState.Editor.ChunkedBuffer = cb + // New file: the previous file's IME rune-count cache and window bookkeeping + // are meaningless. Reset the anchor so the next imeRuneOffsetAt recomputes + // from scratch (the window/context fields are recomputed by buildFrame). + TheState.Editor.imeRuneCacheByte = 0 + TheState.Editor.imeRuneCacheCount = 0 + TheState.Editor.IMEContext = "" + TheState.Editor.IMEContextStartByte = 0 + TheState.Editor.IMEOffsetRune = 0 // Dispatch stat task to get file size l.workerPool.Dispatch(pool.NewStatFileTask(path, l.mockFS)) @@ -753,7 +762,7 @@ func (l *Logic) handleWorkerResult(res pool.Result) { } else if res.TaskType == pool.TypeReadFile { if res.Success { if content, ok := res.Data.([]byte); ok { - if l.state.Editor.ChunkedBuffer != nil { + if l.state.Editor.ChunkedBuffer != nil { // Full-load the in-range file: set the whole content (split into // resident chunks). No lazy load, so no stale-disk re-read. l.state.Editor.ChunkedBuffer.SetFileSize(int64(len(content))) @@ -804,10 +813,19 @@ func (l *Logic) handleWorkerResult(res pool.Result) { if res.Success { if chunk, ok := res.Data.([]byte); ok { - for len(cb.chunks) <= res.ChunkIdx { - cb.chunks = append(cb.chunks, nil) + // A full-file load (SetContent) may have finished while this + // lazy chunk read was in flight; its resident chunks are the + // authoritative content and must not be clobbered by a stale + // lazy result (which can also be a short read). Applying it + // would truncate/shift the chunk and move every byte offset + // after it — taps and IME commits landing at the wrong + // position (text corruption). + if !cb.fullyLoaded { + for len(cb.chunks) <= res.ChunkIdx { + cb.chunks = append(cb.chunks, nil) + } + cb.chunks[res.ChunkIdx] = chunk } - cb.chunks[res.ChunkIdx] = chunk } } else { log.Printf("Logic: ReadChunkTask failed for %s chunk %d: %v", res.FilePath, res.ChunkIdx, res.Error) diff --git a/internal/editor/state.go b/internal/editor/state.go index ccd3ee3..4d7cc99 100644 --- a/internal/editor/state.go +++ b/internal/editor/state.go @@ -83,12 +83,31 @@ type EditorState struct { // IMEWindowStartByte is the absolute byte offset in the buffer where the // visible window (IMEWindowText) begins. For small (string) files it is 0 // and the window is the whole buffer; for large (chunked) files it is the - // viewport start. The IME snippet is the window, so an EditEvent.Range is - // relative to the window and must be offset by this to address the buffer. + // viewport start. HandleReplaceRange uses it to convert IME offsets to + // absolute file offsets. IMEWindowStartByte int // IMEWindowText is the visible window text shown to the IME (the snippet). - // It is set during layout and is what an EditEvent.Range indexes into. + // It is set during layout. IMEWindowText string + // IMEContext is up to 10 runes immediately before IMEWindowStartByte. It + // is prepended to the window when the snippet is pushed to the IME, so the + // IME always sees real text preceding the caret — auto-cap must never + // treat the top of the visible window as the start of a sentence. + IMEContext string + // IMEContextStartByte is the absolute byte offset where IMEContext begins + // (IMEWindowStartByte - len(IMEContext)). + IMEContextStartByte int + // IMEOffsetRune is the rune count of file[0:IMEContextStartByte): the + // absolute file rune offset where the pushed snippet starts (its + // Range.Start). A non-zero value tells the IME the window sits + // mid-document, keeping its sentence/word-boundary logic honest. IME edit + // ranges are addressed from this offset (see imeRuneToByte). + IMEOffsetRune int + // imeRuneCache{Byte,Count} memoizes "rune count of [0, Byte)" so + // IMEOffsetRune stays O(delta) while the window scrolls; edits adjust it + // (imeAnchorEdit) and a file switch resets it to (0,0). + imeRuneCacheByte int + imeRuneCacheCount int // EditSeq counts content edits (incremented by markDirty). The shaped // glyph layout arriving via layoutChan is only applied to the WrapIndex // when its EditSeq matches, so a layout shaped before an edit can never @@ -949,6 +968,7 @@ func deleteRange(start, end int) { if end <= start { return } + del := TheState.fileContent(start, end) if buf := TheState.Editor.ChunkedBuffer; buf != nil { buf.Delete(start, end-start) buf.UpdateLineIndexAfterDelete(start, end) @@ -956,6 +976,7 @@ func deleteRange(start, end int) { str := TheState.Editor.Buffer TheState.Editor.Buffer = str[:start] + str[end:] } + imeAnchorEdit(start, del, "") TheState.Editor.findEdit(start, end, 0) } @@ -1949,6 +1970,7 @@ func HandleDelete() { if w := utf8AdvanceWidth(seg); w > 0 { buf.Delete(pos, w) buf.UpdateLineIndexAfterDelete(pos, pos+w) + imeAnchorEdit(pos, seg[:w], "") e.findEdit(pos, pos+w, 0) markDirty() } @@ -1965,6 +1987,7 @@ func HandleDelete() { } w := utf8AdvanceWidth(str[pos:end]) TheState.Editor.Buffer = str[:pos] + str[pos+w:] + imeAnchorEdit(pos, str[pos:pos+w], "") e.findEdit(pos, pos+w, 0) markDirty() } @@ -1990,6 +2013,7 @@ func HandleInsert(s string) { str := e.Buffer e.Buffer = str[:pos] + s + str[pos:] } + imeAnchorEdit(pos, "", s) e.findEdit(pos, pos, len(s)) e.CursorPosition = pos + len(s) markDirty() @@ -2019,6 +2043,7 @@ func HandleBackspace() { w := utf8BackspaceWidth(buf.Content(segStart, pos)) buf.Delete(pos-w, w) buf.UpdateLineIndexAfterDelete(pos-w, pos) + imeAnchorEdit(pos-w, buf.Content(pos-w, pos), "") TheState.Editor.CursorPosition = pos - w e.findEdit(pos-w, pos, 0) markDirty() @@ -2032,6 +2057,7 @@ func HandleBackspace() { } w := utf8BackspaceWidth(str[segStart:pos]) TheState.Editor.Buffer = str[:pos-w] + str[pos:] + imeAnchorEdit(pos-w, str[pos-w:pos], "") TheState.Editor.CursorPosition = pos - w e.findEdit(pos-w, pos, 0) markDirty() @@ -2058,17 +2084,165 @@ func runeIndexToByteStr(s string, n int) int { return len(s) } -// HandleReplaceRange replaces the text in the rune range [startRune, endRune) -// with text and places the cursor at the end of the inserted text. +// countRunes counts the runes in s. +func countRunes(s string) int { + n := 0 + for i := 0; i < len(s); { + _, w := utf8.DecodeRuneInString(s[i:]) + i += w + n++ + } + return n +} + +// lastRunesStart returns the byte offset where the last n runes of s begin +// (0 if s has fewer than n runes). It stops at rune boundaries, so the result +// never splits a UTF-8 sequence. +func lastRunesStart(s string, n int) int { + start := len(s) + for i := 0; i < n && start > 0; i++ { + start-- + // Walk back over continuation bytes (0x80-0xBF) to the lead byte. + for start > 0 && s[start]&0xC0 == 0x80 { + start-- + } + } + return start +} + +// fileContent returns file[lo:hi) from the active buffer (chunked or small). +func (s *State) fileContent(lo, hi int) string { + e := &s.Editor + if e.ChunkedBuffer != nil { + return e.ChunkedBuffer.Content(lo, hi) + } + if lo < 0 { + lo = 0 + } + if hi > len(e.Buffer) { + hi = len(e.Buffer) + } + if lo > hi { + lo = hi + } + return e.Buffer[lo:hi] +} + +// bufferLen is the active buffer length in bytes. +func (s *State) bufferLen() int { + if e := &s.Editor; e.ChunkedBuffer != nil { + return int(e.ChunkedBuffer.FileLen()) + } + return len(s.Editor.Buffer) +} + +// imeLeadingContext returns up to 10 runes immediately before byte position +// start in the active buffer ("" at file start, or for too-large files). At +// most 64 bytes are scanned (10 runes * 4 bytes + margin). +func imeLeadingContext(start int) string { + if start <= 0 || TheState.Editor.TooLarge { + return "" + } + a := start - 64 + if a < 0 { + a = 0 + } + span := TheState.fileContent(a, start) + return span[lastRunesStart(span, 10):] +} + +// imeRuneOffsetAt returns the rune count of the file prefix [0, bytePos). +// It scans only the delta from the cached anchor (imeRuneCacheByte/Count), so +// repeated calls with a slowly moving byte position (scrolling while typing) +// are O(delta); a large jump (file switch, long scroll) does one full scan +// and then anchors again. Must be called on the logic goroutine (owner). +func (s *State) imeRuneOffsetAt(bytePos int) int { + e := &s.Editor + fileLen := s.bufferLen() + if bytePos > fileLen { + bytePos = fileLen + } + if e.imeRuneCacheByte > fileLen { + e.imeRuneCacheByte, e.imeRuneCacheCount = 0, 0 + } + if bytePos == e.imeRuneCacheByte { + return e.imeRuneCacheCount + } + delta := bytePos - e.imeRuneCacheByte + const maxDelta = 256 * 1024 + if delta > maxDelta || delta < -maxDelta { + n := countRunes(s.fileContent(0, bytePos)) + e.imeRuneCacheByte, e.imeRuneCacheCount = bytePos, n + return n + } + lo, hi := e.imeRuneCacheByte, bytePos + if hi < lo { + lo, hi = hi, lo + } + n := e.imeRuneCacheCount + if delta > 0 { + n += countRunes(s.fileContent(lo, hi)) + } else { + n -= countRunes(s.fileContent(lo, hi)) + } + e.imeRuneCacheByte, e.imeRuneCacheCount = bytePos, n + return n +} + +// imeAnchorEdit adjusts the IME rune-count cache after a buffer mutation at +// absolute byte position editByte that removed delText and inserted insText. +// An edit at or after the cached position does not change the rune count of +// [0, cacheByte), so only edits strictly before it move the anchor. +func imeAnchorEdit(editByte int, delText, insText string) { + e := &TheState.Editor + if editByte < e.imeRuneCacheByte { + e.imeRuneCacheByte += len(insText) - len(delText) + e.imeRuneCacheCount += countRunes(insText) - countRunes(delText) + } +} + +// runeToByteWhole maps an absolute file rune offset (the IME's coordinate +// space, in which the pushed snippet starts at IMEOffsetRune) to an absolute +// buffer byte offset by scanning the WHOLE active buffer, in 8 KiB steps. +// Unlike a window-relative mapping it does not depend on the visible window, +// so it stays exact while the window moves (a fling in flight): scrolling +// moves the window, not the buffer. One pass over the file is ~1-2 ms for a +// 1 MB file — negligible next to the disk save a commit triggers. +func runeToByteWhole(runePos int) int { + if runePos <= 0 { + return 0 + } + total, off, fileLen := 0, 0, TheState.bufferLen() + const step = 8 * 1024 + for off < fileLen { + hi := off + step + if hi > fileLen { + hi = fileLen + } + n := countRunes(TheState.fileContent(off, hi)) + if total+n >= runePos { + return off + runeIndexToByteStr(TheState.fileContent(off, hi), runePos-total) + } + total += n + off = hi + } + return fileLen +} + +// HandleReplaceRange replaces the text in the rune range [startRune, +// endRune) with text and places the cursor at the end of the inserted text. // // This implements the IME replacement contract (key.EditEvent.Range): a swipe // or autocorrect commit replaces the selected region instead of blindly // inserting at the cursor, so the old text is removed (no duplication). It // also covers plain inserts (start==end) and range deletes (text == ""). // -// startRune/endRune are RUNE indices (the IME's text model), while the buffer -// is byte-based, so they are converted to byte offsets first. Must be called -// on the logic goroutine (owner). +// startRune/endRune are ABSOLUTE file rune offsets (the IME's coordinate +// space, in which the pushed snippet starts at IMEOffsetRune), while the +// buffer is byte-based, so they are converted to absolute byte offsets first +// (runeToByteWhole, against the whole buffer). Used by the test harness +// (editor.HandleKeyDown with a key.EditEvent); the real app routes commits +// through HandleIMECommit. Must be called on the logic goroutine (owner). func HandleReplaceRange(startRune, endRune int, text string) { // A too-large file is not editable: ignore IME commits. if TheState.Editor.TooLarge { @@ -2077,88 +2251,108 @@ func HandleReplaceRange(startRune, endRune int, text string) { if startRune > endRune { startRune, endRune = endRune, startRune } - // The IME snippet is the visible window (IMEWindowText), so startRune/ - // endRune are relative to that window. Resolve them to window-byte - // offsets, then add the window's absolute start to address the buffer. - // For small (string) files the window is the whole buffer (start 0), so - // this reduces to absolute addressing. If IMEWindowText is empty (tests - // that never run layout), fall back to the whole buffer as the window. - windowStart := TheState.Editor.IMEWindowStartByte - windowText := TheState.Editor.IMEWindowText - if windowText == "" { - windowStart = 0 - if cb := TheState.Editor.ChunkedBuffer; cb != nil { - windowText, _ = cb.FullContent() - } else { - windowText = TheState.Editor.Buffer + applyIMECommitBytes(runeToByteWhole(startRune), runeToByteWhole(endRune), text) +} + +// IMECommit is an IME commit whose range is in ABSOLUTE FILE RUNES — the +// coordinate space of the pushed snippet, whose Range.Start is IMEOffsetRune +// (see editorLayout). The logic maps it to bytes against the whole buffer +// (runeToByteWhole), so it stays exact while the visible window moves +// (a fling in flight): scrolling moves the window, not the buffer. +type IMECommit struct { + StartRune int + EndRune int + Text string +} + +// HandleIMECommit applies an IME commit from the real app (cmd/pad/main.go +// routes editor_text key.EditEvents here; the test harness uses +// HandleReplaceRange). In addition to the whole-buffer mapping it applies +// the drift guard: Android's IME contract (InputConnection) says commit +// positions are relative to the text the IME last received; if a +// restartInput is dropped (observed with Gboard during flings), the IME's +// text is stale and its reported position is in the stale text's +// coordinates. A small commit (range ≤ 2 runes) is always anchored at the +// caret the IME was last told about — an insertion is [c,c), a composing +// replacement [c-1,c). If the reported range ends elsewhere, the IME's text +// is stale: snap the commit to the cursor, the only position it cannot +// drift from. Must be called on the logic goroutine (owner). +func HandleIMECommit(data any) { + c, ok := data.(IMECommit) + if !ok { + return + } + // A too-large file is not editable: ignore IME commits. + if TheState.Editor.TooLarge { + return + } + startRune, endRune := c.StartRune, c.EndRune + if startRune > endRune { + startRune, endRune = endRune, startRune + } + if endRune-startRune <= 2 { + caretRune := TheState.imeRuneOffsetAt(TheState.Editor.CursorPosition) + if endRune != caretRune { + log.Printf("IME DRIFT-SNAP range=[%d,%d) caret=%d -> snap to caret", startRune, endRune, caretRune) + startRune, endRune = caretRune, caretRune } } - absStart := windowStart + runeIndexToByteStr(windowText, startRune) - absEnd := windowStart + runeIndexToByteStr(windowText, endRune) + absStart, absEnd := runeToByteWhole(startRune), runeToByteWhole(endRune) + log.Printf("IME COMMIT range=[%d,%d) text=%q -> [%d,%d)", startRune, endRune, c.Text, absStart, absEnd) + applyIMECommitBytes(absStart, absEnd, c.Text) +} + +// applyIMECommitBytes replaces [startByte, endByte) with text and places the +// cursor at the end of the inserted text. Both byte addresses are absolute +// file offsets. Shared by HandleReplaceRange (rune-translated) and +// HandleIMECommit (model-translated). Must run on the logic goroutine. +func applyIMECommitBytes(startByte, endByte int, text string) { + if startByte > endByte { + startByte, endByte = endByte, startByte + } // A live selection is always replaced by the commit: union the IME range // with the selection so the outcome is deterministic no matter what the // IME reports (some IMEs send the full selection range, others send an // empty range at the caret expecting the app to consume its reported // selection). if selActive() { - if absStart > TheState.Editor.SelectionStart { - absStart = TheState.Editor.SelectionStart + if startByte > TheState.Editor.SelectionStart { + startByte = TheState.Editor.SelectionStart } - if absEnd < TheState.Editor.SelectionEnd { - absEnd = TheState.Editor.SelectionEnd + if endByte < TheState.Editor.SelectionEnd { + endByte = TheState.Editor.SelectionEnd } } - if imeDebugLog { - fmt.Printf("IME DEBUG HandleReplaceRange: startRune=%d endRune=%d text=%q windowStart=%d windowLen=%d -> absStart=%d absEnd=%d\n", - startRune, endRune, text, windowStart, len(windowText), absStart, absEnd) - } + // Capture the deleted text (before the mutation) for the rune-anchor. + delText := TheState.fileContent(startByte, endByte) var newCursor int if buf := TheState.Editor.ChunkedBuffer; buf != nil { - if absEnd > absStart { - buf.Delete(absStart, absEnd-absStart) + if endByte > startByte { + buf.Delete(startByte, endByte-startByte) } - buf.Insert(absStart, text) - if absEnd > absStart { - buf.UpdateLineIndexAfterDelete(absStart, absEnd) + buf.Insert(startByte, text) + if endByte > startByte { + buf.UpdateLineIndexAfterDelete(startByte, endByte) } - buf.UpdateLineIndexAfterInsert(absStart, text) - newCursor = absStart + len(text) + buf.UpdateLineIndexAfterInsert(startByte, text) + newCursor = startByte + len(text) } else { s := TheState.Editor.Buffer - if absEnd > absStart { - s = s[:absStart] + s[absEnd:] + if endByte > startByte { + s = s[:startByte] + s[endByte:] } - TheState.Editor.Buffer = s[:absStart] + text + s[absStart:] - newCursor = absStart + len(text) + TheState.Editor.Buffer = s[:startByte] + text + s[startByte:] + newCursor = startByte + len(text) } - TheState.Editor.findEdit(absStart, absEnd, len(text)) + imeAnchorEdit(startByte, delText, text) + TheState.Editor.findEdit(startByte, endByte, len(text)) TheState.Editor.CursorPosition = newCursor // A commit consumed any selection it overlapped (see the union above). ClearSelection() - if imeDebugLog { - dbgBuf := currentEditorText() - if len(dbgBuf) > 40 { - dbgBuf = dbgBuf[:40] - } - fmt.Printf("IME DEBUG -> newCursor=%d buffer=%q\n", newCursor, dbgBuf) - } markDirty() } -// imeDebugLog enables verbose per-commit IME logging. Keep false in normal -// use; enable when debugging IME commit/cursor sync on device. -const imeDebugLog = false - -// currentEditorText returns the current editor buffer contents (full for -// chunked files). Used only for debug logging. -func currentEditorText() string { - if cb := TheState.Editor.ChunkedBuffer; cb != nil { - s, _ := cb.FullContent() - return s - } - return TheState.Editor.Buffer -} func markDirty() { // Every content edit funnels through here, so EditSeq is the universal @@ -2430,7 +2624,6 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { // range [start, end) so a tall viewport is filled (a fixed 2000-byte // window could leave the lower rows blank). visibleContent = cb.Content(start, end) - // Adjust cursor position to be relative to visibleContent. It may be // NEGATIVE (the cursor's line scrolled above the window) or exceed // len(visibleContent) (scrolled below): the renderer treats an @@ -2481,12 +2674,16 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { TheState.VisibleStart = start TheState.VisibleEnd = end - // Record the visible window for IME: the snippet is this window, so an - // EditEvent.Range (relative to the window) is offset by IMEWindowStartByte - // to address the buffer. start is 0 for small (string) files, so the - // window is the whole buffer there. + // Record the visible window for IME: the snippet pushed to the IME is + // the leading context plus this window, addressed from IMEOffsetRune (the + // absolute file rune offset where the context begins). start is 0 for + // small (string) files, so the window is the whole buffer there and there + // is no leading context. See imeRuneToByte / Renderer.FlushIME. TheState.Editor.IMEWindowStartByte = start TheState.Editor.IMEWindowText = visibleContent + TheState.Editor.IMEContext = imeLeadingContext(start) + TheState.Editor.IMEContextStartByte = start - len(TheState.Editor.IMEContext) + TheState.Editor.IMEOffsetRune = TheState.imeRuneOffsetAt(TheState.Editor.IMEContextStartByte) // Window-relative selection for the TextField (byte offsets into // visibleContent); -1 means nothing visible is selected. The IME push and // the in-app highlight consume this; the logic side keeps absolute @@ -2571,6 +2768,13 @@ func EditorLayout(screenWidth, screenHeight ui.Dp, wordWrap bool) []ui.Element { {Gesture: ui.Pinch, Handler: HandleFontPinch}, }, ) + // Lead the IME snippet with up to 10 runes before the window and address + // it from its true file position (see imeLeadingContext / IMEOffsetRune + // above and Renderer.FlushIME): this is what stops the keyboard from + // auto-capitalizing mid-sentence at the top of the visible window. + editorElem.IMEContext = TheState.Editor.IMEContext + editorElem.IMEOffset = TheState.Editor.IMEOffsetRune + editorElem.WindowStartByte = start // While the find bar is open, key focus belongs to the main-owned // "find_bar" widget.Editor: the editor stops its per-frame IME sync, and // regaining "editor_text" focus re-issues key.FocusCmd on close (see @@ -2776,6 +2980,8 @@ func SetCursorFromPoint(x, y float64) { ClearSelection() if pos, ok := textPosFromLocalPoint(x, y); ok { TheState.Editor.CursorPosition = pos + log.Printf("IME TAP local=(%.0f,%.0f) cursor=%d winStart=%d offsetRune=%d", + x, y, pos, TheState.Editor.IMEWindowStartByte, TheState.Editor.IMEOffsetRune) } } diff --git a/internal/io/pool/real/filesystem.go b/internal/io/pool/real/filesystem.go index 813661c..82198aa 100644 --- a/internal/io/pool/real/filesystem.go +++ b/internal/io/pool/real/filesystem.go @@ -1,6 +1,7 @@ package real import ( + "errors" "io" "os" "path/filepath" @@ -64,11 +65,23 @@ func (fs *RealFileSystem) ReadFileAt(path string, offset, size int) ([]byte, err } defer f.Close() buf := make([]byte, size) - n, err := f.ReadAt(buf, int64(offset)) - if err != nil && err != io.EOF { - return nil, err + // ReadAt may return a SHORT read with a nil or io.EOF error (POSIX + // allows it; the Android FUSE layer does it), so loop until the whole + // range is read or the file truly ends. A silently short result would + // truncate a chunk and shift every byte offset after it — taps and IME + // commits would then land at the wrong buffer position (text corruption). + total := 0 + for total < size { + n, err := f.ReadAt(buf[total:], int64(offset)+int64(total)) + total += n + if err != nil { + if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) { + break + } + return nil, err + } } - return buf[:n], nil + return buf[:total], nil } func (fs *RealFileSystem) WriteFile(path string, content []byte) error { diff --git a/internal/test/e2e/real_file_fuzz_test.go b/internal/test/e2e/real_file_fuzz_test.go index 1d32f34..1d07864 100644 --- a/internal/test/e2e/real_file_fuzz_test.go +++ b/internal/test/e2e/real_file_fuzz_test.go @@ -170,11 +170,15 @@ func TestRealFile_Fuzz_EditSaveReload(t *testing.T) { t.Fatalf("batch %d op %d: no frame: %v", batch, j, ferr) } type win struct { - Start int - Text string + Start int + Text string + CtxStart int + Context string + OffsetRune int } raw, err := h.Inspect(func(st *editor.State) any { - return win{st.Editor.IMEWindowStartByte, st.Editor.IMEWindowText} + return win{st.Editor.IMEWindowStartByte, st.Editor.IMEWindowText, + st.Editor.IMEContextStartByte, st.Editor.IMEContext, st.Editor.IMEOffsetRune} }) if err != nil { t.Fatalf("batch %d op %d: inspect: %v", batch, j, err) @@ -191,17 +195,39 @@ func TestRealFile_Fuzz_EditSaveReload(t *testing.T) { batch, j, w.Start, len(w.Text), compactMismatch(w.Text, model[w.Start:min(len(model), w.Start+len(w.Text))])) } - wr := countRunes(w.Text) - ra := rng.Intn(wr + 1) - rb := ra + rng.Intn(wr-ra+1) + // Context consistency: it is the slice immediately before the + // window, and the snippet's Range.Start is the rune offset of + // the context start. + if len(w.Context) > 0 { + if w.CtxStart+len(w.Context) != w.Start || + w.Context != model[w.CtxStart:w.CtxStart+len(w.Context)] { + t.Fatalf("batch %d op %d: IME context desync: ctxStart=%d", batch, j, w.CtxStart) + } + } + if w.OffsetRune != countRunes(model[:w.CtxStart]) { + t.Fatalf("batch %d op %d: IME offset desync: got=%d want=%d", batch, j, w.OffsetRune, countRunes(model[:w.CtxStart])) + } + // The real IME reports ABSOLUTE file runes: the pushed snippet + // (context+window) starts at OffsetRune, so a snippet-local + // range [sa,sb) is absolute [OffsetRune+sa, OffsetRune+sb). + ctxRunes := countRunes(w.Context) + total := ctxRunes + countRunes(w.Text) + sa := rng.Intn(total + 1) + sb := sa + rng.Intn(total-sa+1) + absByte := func(snipRune int) int { + if snipRune <= ctxRunes { + return w.CtxStart + runeToByte(w.Context, snipRune) + } + return w.Start + runeToByte(w.Text, snipRune-ctxRunes) + } text := randomFuzzText(rng, 0, 12) if err := h.WithState(func(st *editor.State) { - editor.HandleReplaceRange(ra, rb, text) + editor.HandleReplaceRange(w.OffsetRune+sa, w.OffsetRune+sb, text) }); err != nil { t.Fatalf("batch %d op %d: replace: %v", batch, j, err) } - bs := w.Start + runeToByte(w.Text, ra) - be := w.Start + runeToByte(w.Text, rb) + bs := absByte(sa) + be := absByte(sb) model = model[:bs] + text + model[be:] } } diff --git a/internal/test/e2e/real_file_large_ime_test.go b/internal/test/e2e/real_file_large_ime_test.go new file mode 100644 index 0000000..ae0c4aa --- /dev/null +++ b/internal/test/e2e/real_file_large_ime_test.go @@ -0,0 +1,219 @@ +package e2e_test + +import ( + "fmt" + "strings" + "testing" + "time" + "unicode/utf8" + + "gioui.org/io/key" + "pad/internal/editor" + "pad/internal/ui" +) + +// largeFileContent generates a deterministic ~1.6 MB text file: 40,000 lines +// of ~40 unique ASCII bytes each, with a multibyte rune ("é", "中") sprinkled +// through every 500th line so rune offsets and byte offsets genuinely differ. +// Generating in-test (t.TempDir, via realFileHarness) keeps the repository +// small while still exercising the >500 KB chunked/IME path the same way a +// real multi-hundred-kb note would. +func largeFileContent(lines int) string { + var b strings.Builder + for i := 0; i < lines; i++ { + b.WriteString(fmt.Sprintf("large file line %05d pad", i)) + if i%500 == 0 { + b.WriteString(" é 中") + } + b.WriteString(strings.Repeat(string(rune('a'+i%26)), 12)) + b.WriteString("\n") + } + return b.String() +} + +// TestRealFile_LargeFile_IMECommits pins IME commit correctness on a large +// (~1.6 MB, well over the 500 KB threshold that the on-device "needs a +// gioui fork" claim referred to) file: +// +// - the IME window invariants (window text, leading context, absolute +// rune offset) hold for a window deep in the file; +// - a replacement commit (a selection, as a swipe/autocorrect would send) +// lands exactly in the scrolled window and nowhere else; +// - an insert commit (empty range at the caret, a plain typed char) in a +// second, deeper window lands exactly at the caret. +// +// The full-content comparison after each commit catches the classic +// corruption mode (the commit mapped through a stale/zero window start and +// clobbering text near the top of the file). +func TestRealFile_LargeFile_IMECommits(t *testing.T) { + const ( + lines = 40000 + scrollLn = 20000 // scroll deep into the file + editLn = 20008 // comfortably inside the ~18-line viewport window + ) + model := largeFileContent(lines) + h, path := realFileHarness(t, "large.txt", model) + defer h.Cleanup() + + h.SendConfig(780, 400) + if _, err := h.WaitForFrame(5 * time.Second); err != nil { + t.Fatalf("wait for frame: %v", err) + } + + // Scroll deep (5 dp into the line: avoid the float32/float64 line-pitch + // boundary fragility, see TestRealFile_WindowSelectionAtScroll) and put a + // selection on line editLn, 6 bytes into its filler. + selStart := byteOffsetOfLine(t, model, editLn) + 31 + selEnd := selStart + 6 + if err := h.WithState(func(st *editor.State) { + st.ScrollOffset = ui.Dp(16.8*scrollLn + 5) + st.Editor.SelectionStart = selStart + st.Editor.SelectionEnd = selEnd + st.Editor.CursorPosition = selEnd + }); err != nil { + t.Fatalf("WithState: %v", err) + } + prev := h.FrameCount() + h.SendConfig(780, 400) + if _, err := h.WaitForFrameCount(prev+1, 5*time.Second); err != nil { + t.Fatalf("wait for scrolled frame: %v", err) + } + + // 1) IME window invariants for a window deep in a large file. + var win struct { + Start int + Text string + CtxStart int + Context string + OffsetRune int + } + v, err := h.Inspect(func(st *editor.State) any { + e := &st.Editor + return struct { + Start int + Text string + CtxStart int + Context string + OffsetRune int + }{e.IMEWindowStartByte, e.IMEWindowText, e.IMEContextStartByte, e.IMEContext, e.IMEOffsetRune} + }) + if err != nil { + t.Fatalf("Inspect: %v", err) + } + win = v.(struct { + Start int + Text string + CtxStart int + Context string + OffsetRune int + }) + if want := model[win.Start : win.Start+len(win.Text)]; win.Text != want { + t.Fatalf("IME window text desync at start=%d", win.Start) + } + if win.CtxStart+len(win.Context) != win.Start || + win.Context != model[win.CtxStart:win.Start] { + t.Fatalf("IME context desync: ctxStart=%d ctx=%q", win.CtxStart, win.Context) + } + if want := utf8.RuneCountInString(model[:win.CtxStart]); win.OffsetRune != want { + t.Fatalf("IME offset rune = %d, want %d", win.OffsetRune, want) + } + + // 2) Replacement commit: absolute file runes (the coordinate space the + // real IME uses; the multibyte sprinkles make runes != bytes). + absStart := utf8.RuneCountInString(model[:selStart]) + absEnd := utf8.RuneCountInString(model[:selEnd]) + wantModel := model[:selStart] + "ZZ" + model[selEnd:] + h.SendInput([]ui.InputEvent{{ + Handler: editor.HandleKeyDown, + Data: key.EditEvent{ + Range: key.Range{Start: absStart, End: absEnd}, + Text: "ZZ", + }, + }}) + time.Sleep(100 * time.Millisecond) + + got, err := h.FullContent() + if err != nil { + t.Fatalf("FullContent: %v", err) + } + if got != wantModel { + t.Fatalf("after replacement commit: content differs (first diff at %d)", firstDiff(wantModel, got)) + } + + // 3) Insert commit in a deeper window: caret-only (empty range), the + // plain "typed a character" case. + deepLn := 30000 + caret := byteOffsetOfLine(t, wantModel, deepLn) + 20 + if err := h.WithState(func(st *editor.State) { + st.ScrollOffset = ui.Dp(16.8*float64(deepLn) + 5) + st.Editor.SelectionStart = -1 + st.Editor.SelectionEnd = -1 + st.Editor.CursorPosition = caret + }); err != nil { + t.Fatalf("WithState: %v", err) + } + prev = h.FrameCount() + h.SendConfig(780, 400) + if _, err := h.WaitForFrameCount(prev+1, 5*time.Second); err != nil { + t.Fatalf("wait for second scrolled frame: %v", err) + } + absCaret := utf8.RuneCountInString(wantModel[:caret]) + wantModel2 := wantModel[:caret] + "Q" + wantModel[caret:] + h.SendInput([]ui.InputEvent{{ + Handler: editor.HandleKeyDown, + Data: key.EditEvent{ + Range: key.Range{Start: absCaret, End: absCaret}, + Text: "Q", + }, + }}) + time.Sleep(100 * time.Millisecond) + + got, err = h.FullContent() + if err != nil { + t.Fatalf("FullContent: %v", err) + } + if got != wantModel2 { + t.Fatalf("after insert commit: content differs (first diff at %d)", firstDiff(wantModel2, got)) + } + if cp, err := h.Inspect(func(st *editor.State) any { return st.Editor.CursorPosition }); err != nil { + t.Fatalf("Inspect: %v", err) + } else if cp.(int) != caret+1 { + t.Fatalf("cursor = %d, want %d", cp.(int), caret+1) + } + + // 4) Persistence: the on-disk file matches the in-memory content. + if err := h.Flush(); err != nil { + t.Fatalf("flush: %v", err) + } + if disk := readDisk(t, path); disk != wantModel2 { + t.Fatalf("disk content differs from in-memory (first diff at %d)", firstDiff(wantModel2, disk)) + } +} + +// byteOffsetOfLine returns the absolute byte offset of the start of line n +// (0-indexed) in model. The generated lines are unique, so a plain index of +// the line prefix is unambiguous. +func byteOffsetOfLine(t *testing.T, model string, n int) int { + t.Helper() + prefix := fmt.Sprintf("large file line %05d", n) + i := strings.Index(model, prefix) + if i < 0 { + t.Fatalf("line %d not found", n) + } + return i +} + +// firstDiff returns the byte offset of the first difference between a and b +// (min(len) when they are a common prefix), for useful failure messages. +func firstDiff(a, b string) int { + n := len(a) + if len(b) < n { + n = len(b) + } + for i := 0; i < n; i++ { + if a[i] != b[i] { + return i + } + } + return n +} diff --git a/internal/test/e2e/real_file_scroll_selection_test.go b/internal/test/e2e/real_file_scroll_selection_test.go index 7af42b2..d04642e 100644 --- a/internal/test/e2e/real_file_scroll_selection_test.go +++ b/internal/test/e2e/real_file_scroll_selection_test.go @@ -137,9 +137,11 @@ func TestRealFile_IMECommitAtScroll(t *testing.T) { t.Fatalf("wait for frame: %v", err) } - // The selection (absolute) and the matching window-relative EditEvent - // range: the IME reports offsets relative to the pushed snippet, which - // is the visible window. + // The selection (absolute) and the matching EditEvent range: the IME + // reports offsets in absolute file runes (the coordinate space of the + // pushed snippet, which now starts at the leading context's true file + // position). The content is ASCII, so rune == byte and the range is just + // the absolute byte offsets. selStart := editLn*lineLen + 5 selEnd := editLn*lineLen + 11 // Scroll 5 dp into line 10 (see TestRealFile_WindowSelectionAtScroll for @@ -159,12 +161,14 @@ func TestRealFile_IMECommitAtScroll(t *testing.T) { } // Typing replaces the selection: "ZZ" over the 6 selected filler chars - // (in-line offsets [5,11)) on line 12. The pre-fix code (windowStart=0) - // applied this at byte 47 — line 2 — corrupting the file. + // (in-line offsets [5,11)) on line 12. The commit range is in absolute + // file runes (== bytes here). The pre-fix code (windowStart=0, no + // leading context) applied a mis-mapped range near the top of the file, + // corrupting it. h.SendInput([]ui.InputEvent{{ Handler: editor.HandleKeyDown, Data: key.EditEvent{ - Range: key.Range{Start: selStart - scrollLn*lineLen, End: selEnd - scrollLn*lineLen}, + Range: key.Range{Start: selStart, End: selEnd}, Text: "ZZ", }, }}) diff --git a/internal/test/e2e/restore_test.go b/internal/test/e2e/restore_test.go index e0cd2ab..e4ec134 100644 --- a/internal/test/e2e/restore_test.go +++ b/internal/test/e2e/restore_test.go @@ -503,7 +503,18 @@ func TestRestore_ScrollSurvivesWrapState(t *testing.T) { h1.Run() h1.SendConfig(780, 1688) h1.SendScale(2.0) - defer h1.Cleanup() + // The harness runs against the package globals (TheState/TheLogic), so + // only ONE harness may be alive at a time: h1 is stopped (not deferred) + // as soon as its snapshot is in hand, before h2's NewLogic overwrites + // the globals out from under h1's still-running goroutines. + h1Cleaned := false + cleanup1 := func() { + if !h1Cleaned { + h1Cleaned = true + h1.Cleanup() + } + } + defer cleanup1() if err := h1.WithState(func(st *editor.State) { editor.OpenFile("/wrap.txt") }); err != nil { t.Fatalf("OpenFile: %v", err) @@ -563,6 +574,9 @@ got: t.Errorf("snapshot ScrollSub = %v, want ~0", snap.ScrollSub) } + // h1's job is done: stop it before h2 overwrites the package globals. + cleanup1() + // Second session (the relaunch): the WrapIndex is fresh, every count the // estimate (1). The restore must land on logical line 200, not on visual // line 500 mapped 1:1 (which would be line 500, far deeper). diff --git a/internal/ui/element.go b/internal/ui/element.go index a962a49..ce99597 100644 --- a/internal/ui/element.go +++ b/internal/ui/element.go @@ -213,6 +213,20 @@ type TextField struct { // is running (the IME insets dispatches redraw the app), so the keyboard // could not be dismissed. See TextField.Draw. ShowIMESeq uint64 + // IMEContext is the leading context (up to 10 runes immediately before + // the window start) that the renderer prepends to Value when pushing the + // IME snippet; IMEOffset is the absolute file rune offset where + // IMEContext begins (the pushed snippet's Range.Start). Computed by the + // logic layer at frame-build time; see Renderer.FlushIME and the IME + // fields on EditorState (IMEContext/IMEOffsetRune). + IMEContext string + IMEOffset int + // WindowStartByte is the absolute file byte offset where Value (the + // visible window) begins; -1/0 for whole-buffer fields. Used by the + // renderer's IME model to translate snippet positions back to file bytes + // (see Renderer.ModelTranslate). + WindowStartByte int + } func (tf TextField) Type() string { return "textfield" } @@ -231,14 +245,6 @@ func (tf TextField) String() string { // and draws display lines inline — one LayoutString call, no double-shaping. func (tf TextField) Draw(gtx layout.Context, r *Renderer) { if tf.Focused { - // (Re-)gained focus for this field (or a different field than the one the - // dedup state currently tracks): force a fresh snippet/selection push so - // the IME starts from a known state. - if r.lastIMEField != tf.id || !r.lastWasFocused { - r.lastSnippet = key.Snippet{} - r.lastSelStart = -1 - r.lastSelCaret = -1 - } r.lastIMEField = tf.id r.lastWasFocused = true // Issue key.FocusCmd only on a focus transition (see @@ -261,81 +267,28 @@ func (tf TextField) Draw(gtx layout.Context, r *Renderer) { r.lastIMEShowSeq = tf.ShowIMESeq gtx.Execute(key.SoftKeyboardCmd{Show: true}) } - // IME wiring. The visible window (tf.Value) is pushed as the snippet - // with Range {0, len}, so the IME treats the window as the document and - // reports EditEvent.Range window-relative. This lets swipe/autocorrect - // operate on the visible text without shipping the whole file to the IME. - // - // Item 4: tell the IME this is a text field (enables the text keyboard, + // Tell the IME this is a text field (enables the text keyboard, // autocorrect, and suggestions). key.InputHintOp{Tag: tf.id, Hint: key.HintText}.Add(gtx.Ops) - // Item 2: push the snippet (the visible window) for swipe/autocorrect, - // but only when it changed. Re-pushing an unchanged snippet every frame - // resets the IME's composition/cursor, which desyncs fast commits (see - // widget.Editor's updateSnippet dedup). - snippet := key.Snippet{ - Range: key.Range{Start: 0, End: runeCount(tf.Value, len(tf.Value))}, - Text: tf.Value, - } - if snippet != r.lastSnippet { - r.lastSnippet = snippet - gtx.Execute(key.SnippetCmd{Tag: tf.id, Snippet: snippet}) - } - // Item 1: sync the caret/selection so the IME's selection matches. - // Window-relative rune indices (tf.CursorPosition and the selection - // bounds are byte offsets into tf.Value). With a selection, push the - // full range so the IME highlights it and a commit replaces it (the - // logic side unions the commit range with the selection, so the - // replacement is deterministic regardless of what the IME reports). - // Push only when the (start, end) pair changes, so a static selection - // does not reset the IME every frame. - var selStart, selEnd int - if tf.SelectionStart >= 0 && tf.SelectionEnd > tf.SelectionStart { - // Clamp to the window (the element may be built from a window that - // does not fully contain the selection). - s := tf.SelectionStart - if s < 0 { - s = 0 - } - e := tf.SelectionEnd - if e > len(tf.Value) { - e = len(tf.Value) - } - selStart = runeCount(tf.Value, s) - selEnd = runeCount(tf.Value, e) - } else { - selStart = -1 - selEnd = runeCount(tf.Value, tf.CursorPosition) - } - if selStart != r.lastSelStart || selEnd != r.lastSelCaret { - // While a selection/caret handle drag is in progress, defer the - // IME selection sync (see Renderer.selDragActive): pushing a - // SelectionCmd every frame the drag moves the selection triggers the - // input router's immediate-command replay of the frame's pointer - // events, which re-injects the drag into every gesture and makes the - // selection jump. The final selection is pushed on the first frame - // after the drag ends (lastSelStart/lastSelCaret were not updated, - // so the mismatch persists until then). - if !r.selDragActive { - r.lastSelStart = selStart - r.lastSelCaret = selEnd - rng := key.Range{Start: selStart, End: selEnd} - if selStart < 0 { - rng = key.Range{Start: selEnd, End: selEnd} - } - gtx.Execute(key.SelectionCmd{Tag: tf.id, Range: rng, Caret: key.Caret{}}) - } - } + // The IME snippet and selection are NOT pushed here. They are pushed + // by Renderer.FlushIME, which the main loop calls after this frame's + // key events were drained (and mirrored into the renderer's IME model) + // and before the ops are flushed to the OS. Pushing the snippet from + // Draw runs before the logic has applied the frame's IME edit, so the + // commit frame would push the pre-edit text; gioui then sees the + // mismatch with the IME's own post-commit snippet and calls + // imm.restartInput() — resetting the keyboard's auto-cap state (the + // random mid-word capitals) on every keystroke. See FlushIME. } else if r.lastIMEField == tf.id { - // This (previously-focused) field lost focus: forget it so the next focus - // pushes a fresh snippet/selection. Resetting lastIMEShowSeq makes the - // next focus re-raise the keyboard even without a fresh pulse. + // This (previously-focused) field lost focus: forget it so the next + // focus pushes a fresh snippet/selection. Resetting lastIMEShowSeq + // makes the next focus re-raise the keyboard even without a fresh + // pulse; resetting lastSnippet makes the next focus re-push the + // snippet even if the text is unchanged. r.lastIMEField = "" r.lastWasFocused = false - r.lastSnippet = key.Snippet{} - r.lastSelStart = -1 - r.lastSelCaret = -1 r.lastIMEShowSeq = 0 + r.lastSnippet = key.Snippet{} } r.drawWrappedText(gtx, tf.Value, tf.region, tf.WordWrap, tf.WrapWidth, tf.ScrollOffset, tf.CursorPosition, tf.SelectionStart, tf.SelectionEnd, tf.CaretDrag, tf.MatchRanges, tf.CurrentMatch, tf.Focused) } diff --git a/internal/ui/render.go b/internal/ui/render.go index 19838ed..ef65bef 100644 --- a/internal/ui/render.go +++ b/internal/ui/render.go @@ -110,10 +110,40 @@ type Renderer struct { lastIMEField string lastWasFocused bool lastSnippet key.Snippet - lastSelStart int // window-relative rune index of last-pushed selection start; -1 = no selection - lastSelCaret int // window-relative rune index of last-pushed selection end/caret + lastSelStart int // absolute rune index of last-pushed selection start; -1 = no selection + lastSelCaret int // absolute rune index of last-pushed selection end/caret lastIMEShowSeq uint64 // last ShowIMESeq value that issued SoftKeyboardCmd{Show:true} + // IME model (main-owned, persistent across frames). The renderer keeps a + // local copy of the snippet the IME currently holds and updates it in + // place as IME edits arrive (ApplyIMEEdit / ApplyIMEKey, called from the + // key-event drain), so the snippet pushed each frame (FlushIME) is + // byte-identical to the IME's own post-commit snippet. Gioui calls + // imm.restartInput() whenever the pushed snippet differs from the IME's + // internal state (app/os_android.go EditorStateChanged); a restart makes + // the keyboard (Gboard) re-derive its auto-cap state from scratch, which + // is the root cause of the random mid-word capitals. The model is flushed + // AFTER the key events of the frame were drained and BEFORE e.Frame + // flushes the ops, so the commit frame pushes the post-edit text instead + // of the pre-edit frame text (which is what desynced the old push-from- + // Draw path and restarted the IME on every keystroke). + + + // Caret geometry drawn during the most recent frame, in view-local + // pixels (imeCaretPos is the caret/baseline intersection, per key.Caret). + // FlushIME attaches it to the pushed SelectionCmd: gioui only calls + // GioView.updateCaret (-> imm.updateCursorAnchorInfo, the update that + // actually reaches the IME and makes Gboard re-evaluate its auto-cap + // state) when the SelectionCmd's Caret changes. A zero caret never + // changes, so Gboard never saw a caret move and kept its stale + // auto-shift after the caret jumped (e.g. from a line start, where + // caps are legitimately on, to mid-word). + // lastIMECaretPos is the caret last shipped in a SelectionCmd. + imeCaretPos f32.Point + imeCaretAscent float32 + imeCaretDescent float32 + lastIMECaretPos f32.Point + // FocusCmd dedup (main-owned, persistent across frames). key.FocusCmd is // issued ONLY on a focus transition, never per frame: even a no-op FocusCmd // (same focus) takes the router's "immediate command" path, which re-queues @@ -233,6 +263,97 @@ func (r *Renderer) pointInHandleBox(p image.Point) bool { return false } +func (r *Renderer) FlushIME(gtx layout.Context, tf TextField) { + ctxRunes := utf8.RuneCountInString(tf.IMEContext) + // The snippet is the visible window with up to 10 runes of leading + // context prepended, addressed from the absolute file rune where the + // context begins (tf.IMEOffset): the IME sees the true document + // position, and Android's TextUtils.getCapsMode + // (GioView.getCursorCapsMode) can find real preceding context instead of + // treating the top of the visible window as the start of a sentence. + // + // The snippet is pushed whenever the frame's text differs from the last + // push (window moved, edit applied, file switched). gioui dedupes a + // SnippetCmd against its own cache, so only real changes reach the OS as + // imm.restartInput. After a commit the frame text (the buffer with the + // commit applied) equals what the IME already holds locally, so the + // dedupe naturally suppresses the restart; a fling moves the window and + // re-anchors the IME exactly once per text change. Commit positions + // arriving while a restart is dropped (Gboard during flings) are caught + // by the logic-side drift guard (editor.HandleIMECommit), which snaps a + // small commit to the cursor — the position the IME was last told. + snippet := key.Snippet{ + Range: key.Range{ + Start: tf.IMEOffset, + End: tf.IMEOffset + ctxRunes + utf8.RuneCountInString(tf.Value), + }, + Text: tf.IMEContext + tf.Value, + } + if snippet != r.lastSnippet { + r.lastSnippet = snippet + // The snippet change resets the IME's selection: force the + // selection re-push below so the caret is re-anchored in the new + // text in the same frame. + r.lastSelStart, r.lastSelCaret = -1, -1 + gtx.Execute(key.SnippetCmd{Tag: tf.id, Snippet: snippet}) + } + // Push the caret/selection (deduped) in absolute file runes, the same + // coordinate space as the snippet (see widget.Editor's updateIMEState). + var selStart, selEnd int + if tf.SelectionStart >= 0 && tf.SelectionEnd > tf.SelectionStart { + s, e := tf.SelectionStart, tf.SelectionEnd + if s < 0 { + s = 0 + } + if e > len(tf.Value) { + e = len(tf.Value) + } + selStart = tf.IMEOffset + ctxRunes + runeCount(tf.Value, s) + selEnd = tf.IMEOffset + ctxRunes + runeCount(tf.Value, e) + } else { + selStart = -1 + selEnd = tf.IMEOffset + ctxRunes + runeCount(tf.Value, tf.CursorPosition) + } + selectionJumped := selStart != r.lastSelStart || selEnd != r.lastSelCaret + caretMoved := r.imeCaretPos != r.lastIMECaretPos + if selectionJumped || caretMoved { + // Skip while a selection/caret handle drag is in flight (see + // Renderer.selDragActive): a mid-gesture updateSelection makes the + // IME's selection jump and aborts the gesture. key.SelDragActive is + // stale here (renderer-owned), so use the renderer's own flag. + if !r.selDragActive { + r.lastSelStart = selStart + r.lastSelCaret = selEnd + r.lastIMECaretPos = r.imeCaretPos + rng := key.Range{Start: selStart, End: selEnd} + if selStart < 0 { + rng = key.Range{Start: selEnd, End: selEnd} + } + // The SelectionCmd must reach the IME on every caret jump: it is + // the signal (GioView.updateSelection -> imm.updateSelection, + // delivered as onUpdateSelection) that makes Gboard re-derive + // its auto-shift state from the new position — mid-word: off; + // line/sentence start: on. It must NOT be shadowed by a snippet + // push in the same frame: a snippet change makes gioui's + // EditorStateChanged take the restartInput branch instead (which + // Gboard does not use to re-derive caps), so a caret jump with an + // unchanged snippet is exactly the case that works. + // The Caret field carries the real position (view-local px, Pos + // on the baseline) so gioui also fires updateCaret + // (imm.updateCursorAnchorInfo) for the jump. + gtx.Execute(key.SelectionCmd{ + Tag: tf.id, + Range: rng, + Caret: key.Caret{ + Pos: r.imeCaretPos, + Ascent: r.imeCaretAscent, + Descent: r.imeCaretDescent, + }, + }) + } + } +} + // New creates a new Renderer. func New(th Theme, shp *text.Shaper) *Renderer { r := &Renderer{ @@ -1095,9 +1216,19 @@ func (r *Renderer) drawWrappedText(gtx layout.Context, str string, reg Region, w // byte and len(str) is the window's last insertion point. if focused && cursorPos >= 0 && cursorPos <= len(str) { // Determine cursor position from `layout` and `cursorPos` - cursorX, cursorY := caretPoint(cursorPos) - cursorX = reg.X + cursorX - cursorY = reg.Y - scrollOffset + cursorY - ascent + caretX, caretY := caretPoint(cursorPos) // y is the line's baseline, region-relative + cursorX := reg.X + caretX + cursorY := reg.Y - scrollOffset + caretY - ascent + + // Save the caret geometry (view-local px; Pos on the baseline) for + // FlushIME's SelectionCmd so the IME is told where the caret moved. + // See imeCaretPos. + r.imeCaretPos = f32.Point{ + X: float32(r.toPx(cursorX)), + Y: float32(r.toPx(reg.Y - scrollOffset + caretY)), + } + r.imeCaretAscent = float32(r.toPx(ascent)) + r.imeCaretDescent = float32(r.toPx(lineH - ascent)) // Draw the cursor (thin vertical bar) cursorRegion := Region{ diff --git a/scripts/make_icon.py b/scripts/make_icon.py new file mode 100644 index 0000000..e9e72cc --- /dev/null +++ b/scripts/make_icon.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +"""Generate cmd/pad/appicon.png, the Pad app icon. + +Design: a soft, light cream pillow on a dark ink field, with "P_" (the +underscore standing in for the text cursor) embroidered on it in slate +thread. gogio picks appicon.png up automatically from the main package +(cmd/pad) and builds the Android mipmap/adaptive-icon set and the iOS +icon set from it. + +Usage: scripts/make_icon.py +Requires: python3 with PIL. +""" +import pathlib + +from PIL import Image, ImageDraw, ImageFilter, ImageFont + +SIZE = 1024 +INK = (32, 38, 50) # background +PILLOW = (243, 231, 211) # base fabric +PILLOW_LIT = (252, 246, 234) +PILLOW_RIM = (206, 187, 158) +THREAD = (92, 104, 134) # embroidered slate thread +THREAD_DARK = (58, 67, 92) + +FONT = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf" + + +def rrect_mask(w, h, r): + m = Image.new("L", (w, h), 0) + ImageDraw.Draw(m).rounded_rectangle([0, 0, w - 1, h - 1], radius=r, fill=255) + return m + + +def main() -> None: + img = Image.new("RGB", (SIZE, SIZE), INK) + + # --- pillow geometry (kept inside the adaptive-icon safe zone) --- + pw, ph, pr = 672, 612, 195 # pillow size + corner radius + px0, py0 = (SIZE - pw) // 2, (SIZE - ph) // 2 + 6 + + # --- soft drop shadow (slightly below, so light comes from above) --- + shadow = Image.new("L", (SIZE, SIZE), 0) + d = ImageDraw.Draw(shadow) + d.rounded_rectangle([px0 + 16, py0 + 30, px0 + pw - 16, py0 + ph + 36], + radius=pr + 10, fill=120) + shadow = shadow.filter(ImageFilter.GaussianBlur(42)) + img = Image.composite(Image.new("RGB", (SIZE, SIZE), (18, 21, 29)), img, + shadow) + + # --- pillow body: plump radial shading --- + base = Image.new("RGB", (SIZE, SIZE), INK) + base.paste(Image.new("RGB", (pw, ph), PILLOW), (px0, py0)) + sharp = rrect_mask(pw, ph, pr) + inner = sharp.filter(ImageFilter.GaussianBlur(pr * 0.8)) + lit = Image.new("RGB", (pw, ph), PILLOW_LIT) + rim = Image.new("RGB", (pw, ph), PILLOW_RIM) + base.paste(lit, (px0, py0), inner) # bright center + rim_mask = Image.composite(Image.new("L", (pw, ph), 0), sharp, + inner) # edge only + rim_mask = rim_mask.point(lambda v: min(255, v * 2)) + base.paste(rim, (px0, py0), rim_mask) # shaded edge + + # top sheen: an out-of-focus light patch + sheen = Image.new("L", (SIZE, SIZE), 0) + d = ImageDraw.Draw(sheen) + d.ellipse([px0 + 110, py0 + 30, px0 + pw - 110, py0 + ph // 2 + 40], + fill=80) + sheen = sheen.filter(ImageFilter.GaussianBlur(75)) + base = Image.composite(Image.new("RGB", (SIZE, SIZE), PILLOW_LIT), + base, sheen) + + # corner dimples: soft darkening where a puffed pillow pinches + for cx, cy in [ + (px0 + pr - 20, py0 + pr - 20), (px0 + pw - pr + 20, py0 + pr - 20), + (px0 + pr - 20, py0 + ph - pr + 20), + (px0 + pw - pr + 20, py0 + ph - pr + 20), + ]: + dip = Image.new("L", (SIZE, SIZE), 0) + ImageDraw.Draw(dip).ellipse([cx - 70, cy - 70, cx + 70, cy + 70], + fill=55) + dip = dip.filter(ImageFilter.GaussianBlur(30)) + base = Image.composite( + Image.blend(base, Image.new("RGB", (SIZE, SIZE), + (214, 196, 168)), 0.5), + base, dip) + + # --- dashed seam line, like a quilted pillow --- + # Sample the rounded-rect path finely, then draw dash segments along it. + m = 44 # margin from the pillow edge + r = pr - m # corner radius of the seam + import math as _m + # Corners clockwise: center, angle start -> end (screen coords, y down). + # Each arc is followed by the straight side to the next arc's start. + arcs = [ + (m + r, m + r, _m.pi, 1.5 * _m.pi), # top-left + (pw - m - r, m + r, 1.5 * _m.pi, 2 * _m.pi), # top-right + (pw - m - r, ph - m - r, 0, 0.5 * _m.pi), # bottom-right + (m + r, ph - m - r, 0.5 * _m.pi, _m.pi), # bottom-left + ] + samples = [] + for i, (cx, cy, a0, a1) in enumerate(arcs): + nx, ny, na0, _ = arcs[(i + 1) % 4] + n = max(2, int(abs(a1 - a0) * r // 3)) + for k in range(n + 1): + a = a0 + (a1 - a0) * k / n + samples.append((cx + r * _m.cos(a), cy + r * _m.sin(a))) + # straight side from this arc's end to the next arc's start + end = (nx + r * _m.cos(na0), ny + r * _m.sin(na0)) + last = samples[-1] + n = max(2, int(_m.hypot(end[0] - last[0], end[1] - last[1]) // 3)) + for k in range(1, n + 1): + samples.append((last[0] + (end[0] - last[0]) * k / n, + last[1] + (end[1] - last[1]) * k / n)) + # cumulative arc length + cum = [0.0] + for i in range(1, len(samples)): + cum.append(cum[-1] + _m.hypot(samples[i][0] - samples[i - 1][0], + samples[i][1] - samples[i - 1][1])) + total = cum[-1] + dash, gap = 26.0, 18.0 + seam_ring = Image.new("L", (pw, ph), 0) + d = ImageDraw.Draw(seam_ring) + s0 = 0.0 + while s0 < total: + s1 = min(s0 + dash, total) + pts = [(x, y) for (x, y), c in zip(samples, cum) if s0 <= c <= s1] + if len(pts) > 1: + d.line(pts, fill=255, width=9, joint="curve") + s0 += dash + gap + seam_ring = seam_ring.filter(ImageFilter.GaussianBlur(1.2)) + seam = seam_ring + seam_full = Image.new("L", (SIZE, SIZE), 0) + seam_full.paste(seam, (px0, py0)) + seam_col = Image.new("RGB", (SIZE, SIZE), (203, 183, 154)) + base = Image.composite(seam_col, base, seam_full) + + # composite pillow onto background via its sharp mask + full_sharp = Image.new("L", (SIZE, SIZE), 0) + full_sharp.paste(sharp, (px0, py0)) + img.paste(base, (0, 0), full_sharp) + + # --- embroidered "P_" --- + # Center the CAP of the P on the pillow (optical center); the + # underscore hangs below the baseline like a cursor on the next line. + font = ImageFont.truetype(FONT, 300) + # Drawing "P" at (x, y) puts its cap top at y + pb[1]; center the cap + # on the pillow, keep the whole "P_" string on the same baseline. + probe = ImageDraw.Draw(img) + pb = probe.textbbox((0, 0), "P", font=font) + cb_h = pb[3] - pb[1] # cap height + ty = SIZE // 2 - cb_h // 2 - pb[1] + tb = probe.textbbox((0, 0), "P_", font=font) + tw, th = tb[2] - tb[0], tb[3] - tb[1] + tx = (SIZE - tw) // 2 - tb[0] + + # stitch texture: fine diagonal thread lines, tiled + tile = 6 + pat = Image.new("L", (tile, tile), 160) + ImageDraw.Draw(pat).line([(0, tile - 1), (tile - 1, 0)], fill=255, + width=1) + pat = pat.filter(ImageFilter.GaussianBlur(0.5)) + mw, mh = tw + 40, tb[3] + 80 + stitch = pat.resize((mw, mh), Image.NEAREST) + + # text mask in local coords + # local (0,0) == the absolute drawing origin (tx, ty); paste happens at + # (tx-20, ty-20), so text goes at (20 - tb[0], 20). + mask = Image.new("L", (mw, mh), 0) + ImageDraw.Draw(mask).text((20 - tb[0], 20), "P_", font=font, + fill=255) + + # drop shadow of the stitches on the fabric (thread sits on top of it) + sh = mask.filter(ImageFilter.GaussianBlur(5)) + sh_full = Image.new("L", (SIZE, SIZE), 0) + sh_full.paste(sh, (tx - 20, ty - 20 + 7)) + img = Image.composite(Image.new("RGB", (SIZE, SIZE), (182, 164, 140)), + img, sh_full) + + # thread color modulated by the stitch pattern (subtle per-stitch depth) + stitch_full = Image.new("L", (SIZE, SIZE), 0) + stitch_full.paste(stitch, (tx - 20, ty - 20)) + mod = Image.composite(Image.new("RGB", (SIZE, SIZE), THREAD_DARK), + Image.new("RGB", (SIZE, SIZE), THREAD), + stitch_full) + mask_full = Image.new("L", (SIZE, SIZE), 0) + mask_full.paste(mask, (tx - 20, ty - 20)) + img = Image.composite(mod, img, mask_full) + + out = pathlib.Path(__file__).resolve().parent.parent / "cmd" / "pad" / "appicon.png" + img.save(out) + print(f"wrote {out} ({SIZE}x{SIZE})") + + +if __name__ == "__main__": + main()