From 20a35493c8022dfef61fc33067b51b31d9cc4096 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 4 Dec 2025 14:03:55 +0100 Subject: [PATCH] [11.3] SystemFontCollection fixes (#20207) * Prevents repeated platform calls for matches that are already found and cached. Ensures family-name normalization logic correctly maps platform-provided family names. * Add missing test font --------- Co-authored-by: Gillibald --- .../Media/Fonts/SystemFontCollection.cs | 13 +- .../Assets/NotoSansTamil-Regular.ttf | Bin 0 -> 78988 bytes .../Media/CustomFontManagerImpl.cs | 52 ++++- .../Media/FontCollectionTests.cs | 179 ++++++++++++++++++ 4 files changed, 235 insertions(+), 9 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Assets/NotoSansTamil-Regular.ttf diff --git a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs index 3b0c71ce20..948c3918f5 100644 --- a/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/SystemFontCollection.cs @@ -162,17 +162,20 @@ namespace Avalonia.Media.Fonts public override bool TryMatchCharacter(int codepoint, FontStyle style, FontWeight weight, FontStretch stretch, string? familyName, CultureInfo? culture, out Typeface match) { - //TODO12: Think about removing familyName parameter - match = default; + // First try to match via base implementation + if (base.TryMatchCharacter(codepoint, style, weight, stretch, familyName, culture, out match)) + { + return true; + } if (_fontManager.PlatformImpl is IFontManagerImpl2 fontManagerImpl2) { if (fontManagerImpl2.TryMatchCharacter(codepoint, style, weight, stretch, culture, out var glyphTypeface)) { - AddGlyphTypefaceByFamilyName(glyphTypeface.FamilyName, glyphTypeface); + match = GetImplicitTypeface(new Typeface(glyphTypeface.FamilyName, glyphTypeface.Style, glyphTypeface.Weight, + glyphTypeface.Stretch), out var matchedFamilyName); - match = new Typeface(glyphTypeface.FamilyName, glyphTypeface.Style, glyphTypeface.Weight, - glyphTypeface.Stretch); + AddGlyphTypefaceByFamilyName(matchedFamilyName, glyphTypeface); return true; } diff --git a/tests/Avalonia.RenderTests/Assets/NotoSansTamil-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoSansTamil-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1776798018398bf8b339a1c55f81cfb604d36315 GIT binary patch literal 78988 zcmd>ncYIvcweH?$Mk7sC*QjgMdmVMjvU)GJBujFUZCR2f*=ne`VZhj!YQUIoz%eC2 z44B>w7YLykL=vilX)(qjbQ3To1f=(^ea?(V1;WkmzWe@obAI1h>#W`PUVH6w$}r9t zbHho^SVd`Bxp}==&zO`C=@%7?8k*jX`8t8I@-fDe>noa;6o2`h@n*(M&5Svp+0c}n z#)FQuGsfS6|J6;+Ma|#*;XQ=s*WkLebD(4R+iM>KA8cXFV}@??KtF6?>cL{|{5MzOAV7q ziXX-9BCfo|If6+WdwRzx6C1n6dO5kfq1A{=+{C)zpaLXZ?FM-qyjpq{yc4c2l-h7z zBU8STrSV!^+ke~Kwy7j$DX|GE8X79tQ;bbb$>me6ko50_ULt?ieE){#1y@&JrI8^L74$eMcAA$cG2WLO9Nv7hQ|BZ3}gQS9-15D)8 z0_ui%B;kv}or!i;NnDcPmouX%LmW#${xXr?Z&9w>*zHL7Lxg20g?!|)dw4wmt8}~c zwklWkq1viG$En!qd(C0Z>&^+zJ)Tgd+H1c~DQ#XwX?f z7X@7%baT+%L5~GJAM{$#UxPjk`Yu=%>>3;p935;8UKm^x+#38$NJYqhg?wi@!*YRT zBD6NNEwn4lDeTU$&%$%UuZswXh>nbiiit{(x;Hu_x+r>oj6TL3b0qeL*w5mmxYoEo z#;3;LpAeC-GT~UlXNd)g2NMq^MUK>(X78^{UiJS><@B$b1HLA&v`I6FgGW+KKG2=iQJ>P-{<+}W#!f6b>t1??a14c z_d?$BeE0n3{4Mzh^S@c(ykPZ$+ZX(4!Kni00{?=9f`NiF3ob6$S8#8^e-(UL@YBMS zh0AcPSva(C>%z+y-m&o2g+CM~6;>9G7j7?nwD7sY*9wmno+{E5RTb?lx~ynl(FaBU zEDkBID&ARqTk$=`j}||T+}{?k{RB3CnpV29 zbVuo>rPr0-R(gNwlcf`-e<}T-^kkVwSxwp2vh&NXDBE9lXW7eTKbE_cyOl?mTgw-g z*Oa%Gca{&8pI&}m`E})gE`PiH@8w@t=qi#cDk_>PR#mL8*jTZn;?j!iDsHWKw&Hlj z7ZpEMYAd}fLo4$tJ1ciqURHTSPgYGwQ;q5wd1u{ z)V^7JqV~($AM0G|!s~MC%IX%^wb%94ZL8Z=cTL?bb;s+zsQYKVy56 zX?xQpP1iQP+^lMLZT4@DY));?Z?0(G(|kkow@a)`MwT2{^5-RQFZpoEKUz{+@>?oe z8e3Mj+~4w6%ZDvrwfeP2wx+b^wU)OwwzjwSwvM%KYu(p+y!DILAKIMS+}q;Xmbdk^ zjkKN7c7EG$+U{t3sO{;t$)!0g&MEIY95#pTJ%cP&4$ z!fQp%itAVWy*;44puN1kp}noWqkUcbX#45yd)oK5-_(A%{aE|=E5lZntn6C3edX;d zUtam%D%Gl}Rn}FttH=^Z^S8+IsI}m)QD36)oCT|g6<%T{^%{j&u|jp2!mC-5>XgDe zF@KdU9u13A-J{5zS*faD;awmvR(LHdm0jF+k?^tUx+b;SDTbviUdiFm6%g zCfK`A6yA^dvzKKa0{%_K`rvo!+H__bogmmGhF(RvsRqD;HMW{ zw><=vXAEdU4k-7{pj4)Q5#JbEX_Vz+luiciME?6A83UgLkgp_!9YN~JtbvsyCvs|| zm>rbEWe|QXh* zsg%<(B*0e-{EUdaQR)`VEs_K*4mq2dt}QnmYyPDoo5qTUCwMA{QS(GygU|9L(uJH1*;AYuwg)alXamag zbhsW|jUoIX+^B_V`CN z6jG|0#Ccb7j{Z3?!h@}U0OD#S!M|0AUgA^R;4*9UfLZz|w6U(f4ro-hSPYpOKSe`u& zTMX0TXXj&PuBPk27Sb_ovOL3RL+Y2Z1+cI*}?U5;E>RdeY%a9or7+YQYUYXgMKD^(=9;9q zI>VLbGt?W%%@=S?jUyiv-UBT~^CUH*6m7}(Fi*aX5Aqef8k!Ky4OnG<#ExM0bRXN# zE=!#N%>pmf&?`CcKHC07Z_)k0zmeYucb zA!L`~90yQnXbe)@E#$IfmylfSAh|$DE_9HbD(?POD zNKSW$ej2z8ZTHw4b5I z=p&)864zk>~yxM*P9Vz2>Jlk@lD8q%-twB34Kn3{$!KxvvW7Yce*{?HFo)xc5aWI zyTs02WarMeb35(ab~|^Lo!e^XV272wZLo8rc5cYd4cfW&cCOdXbqh{{o%3MfsNYD` zw}+^2AGR8Y8Fph0?9g=G`=e%PZ;<-<-$37ICvbijQrZh+*y~$~c(5!|u$pD7wy{KY z2MclvWkKq-ELHjgYg5Ow9F3ags6rJ?vW2RnES=zjHdW=YRJ9jtlU`$~D!8c+VSV`^ zYtszFe-2Ald9!5otKfC4O|=X_oYFSXZkDL}hUKV};I<6myue?|8^=;RSWE7Inv84TYWzK+yTEIvTT>NEC+sWQ;)NBich+b<%xX7vjXI^ zO?4OY7=ZQg7sw;ZL1k3$0KXn?8-Zt$&!do^Vr?#Wvr@H+MLDIg9Ot_r^T+iQEZI2^ z{(r8lr~QXzMcvxUYL7pPl{%%%bxQS$w3}2eNV^lVJ5UCy<42LF7g>}hgGH%=kblB= ztU`4ztI*tve5bM~mmA=Q`d62rEO&#ab_fH1Dd3EB_aOWv4)SZZTf|Y8D;g8x>iA%ICweJ&f zFG0I$W|rIO6Vc8~(AH-GL=W+gCNEbNOkU27tdbvK<#YGFJcRT5Gk6) z;4Xv}vJhU(VlbLk@(Y+ZWNE-MAQeaiNVX8j1QGypPZ9nNh)2H8Ax_<|!6~-TD}e;sX2@$N*IYtAxKIz)JpsO6cfwfH(Hm z?~$-O4E{X+S6D?hiF+46eF;3o*JBRNCJz9OJe#@lDxeo|;HlqY=8IlDwTj9urLi1Q zmLBx>yJhg7kdL1*nz5--#N`P*27Wux&mM-`oh(Z_3wGdFKy}*G6#7($w23X4gSQ}m z3w2|o3Ap}uaE6~Z5tk$W2HD?Wi<(fLF60js;6Sj#)3vwY-KXx8*26!+AkeB%x;CmV zg6#YGp)#@s{}%g^w|0aF$geaA-Vu>#)7f|y`j)_ScD#-2Ujy88QQgE0vmsUE&P*oS zXEyR#6m)4C*u9W`1yrlyuZJz+$5_^EK%RxNW(nE{n*Q&>+nB5HPho9X$CgOzn3hYd z9#{g@a%t*K;2U5{Qj>h@Gspu#Um!?62m?&}7km-izEj;M;-fg{g7W)spt=wJ9c5OK z#cx2|GAcj|(D$sfA#H@8KHTk8fV3Na4fu~}M-Qf-4auE(@JZBJd02BKA zAAzMnE6@#G0bAe=dN1%4un#yWm9i_jAJQO@eYi=%d7ufvlfbE|pD>Rwv2Va{rfc>D zdz5UEn0KhKG%i(mdCf%EDzTc5fK|fJWT7*9d4f#k3!IG#Xw_^{fcRJ_5Dvf|Vty{b zzXgp{a2F`-CxM>?g}q}7frCJy4E_aZkqlaFV!xDOhgH6xOZf#jt=`IHa39cepaSv` zP}pq(r}V32a7wcpsDV5gl-7*^PXnz3>cMA$Hpsw;!mzFr;1u6tpb>s3jwTtL{Nf%o zfiD47fS$&%^Teg22xMT?_Pp-wjG>0Q^eOJ{kOK&~-BSe$e$YpfQZ~%fJZ8u=~caOUL-{K!;>- zjCt6N65xkGPXmxIPIsa)P7(O;LC0ipD$BSG>_{-|zv9+5{Jajj3D^w&Ezm78_%YDa zfir=Zfc*+y2E9=R{}7b&3h-}0ZD z^bQ3S59uZ072u!@{x#^`G9)7?)dfH*iPb-2_X8gT56F--pbyF5UxGd?gMS42hz!XD zO7#ffZZ^YyiVVvClQQ_@@&w&Z%#|A%mBL9+83BAY*?~a1``S8N3XXt^tl`P#Eb7z?Xu)4ZH)M`pvsC z_&U({WMJmS*k5IED)%uN9Qp?p z2tEcnC4-}0IamQ_P%r|UK%t8QDo`87z^fIwfI2DI460E8{pS=H;VeL>z#SAiDzF{Y zpuiQ>s9+l?g(r9c$pOjW z3BLhS6p-Ik1=oP4DWJHm3T^<+R6u^S6xMsTTA<)o z&>{ttPO*a9K}!@+I;9Hk04-BM>69zD3$#K3<)u==ouJhUDE%4*cZ1d{pt$Q4JOsK( z0j0lK!GoZU3Mic>1&@L@E1>w6D0l+2ML|7itAeLM+Z0fmOBFl`x=aD3zg)r7peq$n z`l}Q?1G-uP<*`G-A3(blQ2N~p4uh^!Kyj>B@Cs4J5e1a5Q3bDpjwztLj4SvH=mrIpmyHVE1>K~8^0iq3rN2eN`=F;Qp!ByY_yF__ z1(e4#6&%Nk3w@G6?I!uj;Ln2k%b@m=0%UNEb5fQJek15}!1Dm?l|*g?H{eYLuusxa z8Dtl+YL=k~eOrMmC~T;J0TksFaHhu?0FKkYKqVQRcsLL^;#8?+ko{F@WMC(NsVF?a ze*i`P2xQB#`y+rA3R58;1hVBSq)kv(Hnh6I1|nopKn!@~LkoSaPX%g7*ZyUIz09-3R;zav#u}fm^_% zZS=PT2f+J-9t0i*9|ZaY@HF^XP_%{qF!&_U7lA*5PX&ek5m*K&bc=vpd#3*oKz-oJ zLKgUz3>xQyx5?l=pl8eAXv1KXkwASY7-c13MaP2Ak-^u3qU;2Y_76r`2xJ3-zmh?H zDEMm`Scx(V>fT~R9!`QnZwNGgTTm|+FYvE|qWl&=@UMfS`~?09Xdr+(pgEBxSO)(T zG)xBn7&J--&5tZOKrX_528w=1pm~M`WwM|QG}o})DT99piaI9HoGBD>60ibfVIDGQ zJ{9?r44P})u7IAt^u(?+K=UK+i92Qh&4ajS9*__I?@XXYvsh*8frevi>jn9|GSB-Wj|v_-l*N z;%4!(_*;T4VU{RMu4T+}j^$3vqoHA89+5BI{x$A4)6d8(SZNkwrLiA*yA3n_`!G{K z#Gd3j+(Qk<9gR4i#4~vgFT#DkMSLah{jQUIk+R;?sYazk-lDXNwO#t{`kneq z^n3KzB5(WjH|uZLAJjjge@_3R$lHg=+cxCwD~ld^^R)O{0!7|(khkr#^2VpWLGI22 z&@V~Pe(i5+-Bj^Zh0XWm7n5&)x9hvpPi;SS_Ng;ZopEZ*sm-T0ofJKHOIn_z53n@Z%4PDWYqGE zT?On%zMwCnmLfQI7En)5@R#{p`~?4yf5gAyr}(5~kUXRqDM?C^+9)7wO`EhvT8BNj zU;UFR5o(d_U8=Ql^8{C64R;5-6T1p`@ss=u+yysqBkuV5@DOg{5opU~+^J22rezCV ztL8PlfiJ;*@-@7Z_wqiBea~b4^8(g76Ot3|o}a+T_#;>myu}~p zf94bXkNi*k9sU@95B+ZwSMf3K!Z&jhKa0Eb?c5djw7vLF?#<8TLHrW*&t2#RmvUcz z0T1PS&|9y-QxBK(NPZPh~)F1i3FoI6< zpD;%LfbsAr{v+?Ce)i7>dymh=7qZRK}-6IwhTb42B8K?14B`B;n2fKXkxU|#_?$F zMARZ_W-4^kikizn&5@4gpw{xB0}G(7qyL+E)I>TxaVg7keI>a!npF^C!+Mvaa@qsE|98=za8 zpcPx7U0X#jI174lHuPf$v}z}G<2>lv1wz|)LGLbshEN~b1I@SsJ>@F&m1`6&ydIjd z4_dOH--zCGGy2bOp&PeBH>e-o0ll~jdU`i{6lv=H(AWo|BM(DoA4MN~O!TrRpdnA8 zuRRUzcn0nL9D3bh{sMa51ayQn`p?jjSD+)WiN1IQn(`)EoBHHC(3to5U->cU%yH<< z3H~?q(Z9n&eat`MpYqT6=dcwg(KG*n9{V+OXAfcoco=&_2VlXz;otJ_u%Gk}+ksun z7qFZ3Ja*ph;y+-n)`|U~-Ppg3!W?UgbzycD%|3?4P2&DhH?;3Cdl70iL2nyi78A$5 zmsHqwipNe~0(OL6Vt-_R!mRDjk~8L*iP+QE;y$SkI&8o=n}YjYM#&_(vbC6XreSC0 z73}1_Cb>%<>@w^<-Gh5&pX2^_hU6)EN#58U_rVUuza%r}yIGPSX1Lkxb!esI4tstB zu=8cC96bp;e^NYlQa_Oru-30+!%`yl`$nZCcAAtdrC>j`n*CEsmD2Fui4{Aje_>;l}$tiZU?#?F+gFwZ!foh4OEH5eW0u%Eh2 zs+St1Mbcun98X()CN;7{QWI;Jn%Uo_CF}+~yL>Zt4F7=9^%?AlJ%Qb|aHhqR%jdF- z*oABtJBK+-Eo>*dN@~SuwNzRrEtghE?b1qVm9!d9_LQ)TF*AM>Gs?FyTRtjvV4vw2 zMzv0G5(rqGI8!uF8hQR=RTQg#^uX# znQFS_%Wj2sD87bd@sL70lu+yCI9&#nw46JY#MapyHY?27m`yUMcu@q-%C&ZI(B?9% z_!v;A5{I)=I+qcJamI6hR9u&Sh0*rgIOh$D(`c`a8C7DlQN_WnqhrI+#_s;nzMer5 zn`__9Yq!2x0@v|b9>!-0T)Ss^=$uQNtPn%91s$`}&~NA)=};=jFw{F>&%S%d zcz^d`&sgt3$H;n7Anlg!5xL!5h6d$!bs5_zOLSwsBT8Fq*A9)3$N|>&ZBYD;_H7a( z-DvlQZn=JR-FEHS~@Tc8zp*33uaI@6h;Y$Dn*==<3*P&!%Z$XmG68 zerep?-7#XnGIe#Y=^L|Ox^^N$hikWv(T+7k8@lZtOe5pN-P4zb!SU(((2jK~Ep1xg zfmibIJm*@GIaesHm#%}GXI`TQ97DT8dmXP$Go4K{UEN68XLui;d5KpW z9kObgnYL+UhN~<3!wld3GcH}xCmfyKsFOGfOdDogx}wWCI=haJ&bT(sx}53TjjG!* zxX1dA*&WDz{cLT9r`esz6U{R_pa*2L`T=>H9n_<9PDuC8*#Y5cPPlb*!g-?E=jLMb z?8wO5oYAe&t#63v!DVdeydY#wj3gL=$O)O}8x@H8jf+!WpCB)iwctAFfx7k76`)4bZ2PCuf37%#}1y8eM@`MSP9nb@^Ss}^W?4Vv` zK4yo7$2oz8$JwDhHp~g@zF~H<@H9J|CydbSMBO2pIc$2&_2khzC!80VsM(QeM|(%) zxsKa(r0HUya1dB$3({r_QiZ@{x*|LJ(VH+V|9 zFJHQ05L?qN#za|Q9PS?J#3(CYxeMQ;eSC@zDD~yMtfymM{^39)nZbi zaF|snUd6OR@k;Xw8;gmB;ukXu#jluJD7=_kD4uC@p@=(&`iGP(xsG-Z^f_GX`nyMM z#pph5yhEuzW6wx;2Z}34fV%A-9qY5j;l5_1qjP=t*mPyNu7SXzd>)Dqhw5|h813xq z>+Bop99LS*4O1PoQ0LHytqCYp_jIhH}4{=VVSKBXp21LOT; zeZ&2mmGry%HuTvtsvqdXT4Oq!X(`1y@|80cAtxhMp=qKJIhpCxep7NXGp7BfmXsEe z-=gett7bek)tZv((}~&QNXMGK!LIRD6xS-zH)e<_m@Jk9$ziR<;>ubgQco#PQ>c|_ zT1r;BOtXbNEhSr#XDjlQV%a@KgilLJDV48NQv}T@k>jv zE3>;hmnlKZG*A0VOD)nY z5!GN^G9%(8N_LmX*>zen0xPCz6%p!N5!`)iz89>9ZJ=APW$K*nbdR(qU*LhICm>yx*aPr&8q9Mdr|jNo%gMC z?o+(>Ie1O8W+}CtrRkSzt$(^cQmrMLL0LNJAWhFyVYy};no&hVa_kzI6KI%b#KC_? zmS$8)mAcQ-j7__yrJ@(ebv`~L!f_?L<4T>6i#p#ZBGPZ1u2$_vd!28T>wNRHqh_;Q z=UYTD-InQEDH|Ud68W*F$W>rXk^7%j?oa3wnTmUnRv10%Ke8lM@tLOhNK;}+%hqCz zC!1HToc)2JE;mtFFKh$njpAG_ z&V}o{M+R9gEy!8=!1{spECFF8@x~#AMVvU(oemYGln%KPM{+rS+#rovY1!fx;r^oA zIc{TkYUEFN_ejIs;6n$$9Q$VbaUR6|Q3H0NTCm$yjs3L(?8~Jgy(oq^B5{*<5?@#0 zlP}?pF=AGw;>@M@ahC9AKb?QVnF$G(ZUdi5^2skrK6xdb@ht8ebPv`Q_uO1@|I`)t zQ(bQYaPQR>_i0>rE0nH@$NgJZdbexlS5a&^r*g~RbBRK%wzsyBhbelyQ8h*7lcvI{ zE?g;9)D-|1<(n#U?ckj|*BH|nb26QSSUxnZ!Lb6hr~FJUCgpyPsoKt!+PP_2!922T zyDZH_?S`Kz$}Y3mIe(Lv;%YLvD4b;cQRa-_&gD#!@k_<^6T9mP<9mwiO(X8u($DzP zJhJDEPYFk6JoJm)19sPgc5XVv?RMF7MzkUQjMp3KZX0pCjc7Z;onz;A8_!f^o9wa? zW4|IBF|Jj()y8EC*KDj;xJtWhnk%-;^6lKTYo=Yc+L)rm5@(E1xL_mhXVcFZY{Z>G z!5Qo&(%89anSpMQ&-7>b!Ej2JnIyv(itD#?Ik^dOYGbc!}*GAyJ4%sZJ3AKZWvZv*BQDLZsk1O zI)ifG)UeLbsQ9Zjlq+1J0eAiBXGphmg@zw%&lK*%dAMWq$llVwu7voLc)EjP#+`gcHqAXgkL)4+J&L~r^KiG= zW&8BkDE{`?xm|W{T6XR{vTb(RKK&LYmNC0*(9ZSh*C?(l^eqav$nIJ_54T8Ps`x9g zyJqXt6j_2zoAlGND7{5-_17zQO>g=|&c*I3**SX%-H&#ezwTQ(X5E)MJbOYv-3gsy z^>lCAxmWGnOLp!#-BTg}(>-U)pYD+E0Y!FDce}#fG!J+EJhH2FyOj_Z+FcLn&QWBC zbc(Ih9kg?sbR&xECY@sSbenXF)zhuEbJMa-I>iz?_-ocFHc?k;FGuk_T%|5wNiEav zTB%D>WO24S)kVz11`X**n@J z3it9n+&kJA?f#yz`MFID$ga9#8>c_F3CD57#)4tX8YoPHmx9v7OrVdAOu` zWHDOBo@xW<;e6~eH?2;|pW20HagX3xMb4hUJdNY&lx=ht{HJ1G_oL|kz72r&$+YUFCahs0`h}1g`$}a&cZc=W|BOL=4?De`mH#V{|xB_ z$%W4&n8k6n7jM^bdd^Jnm&27b9A`1}{8IS&Qsm)F;pa?QBJxJPk+UyFiXW06o-X)w zg{PR$7JRLc9~Chl6@0SvSNKmBc}o^$Nfu>E7Ooj0REF@AK`A2VRMHILN>BN5e2tGv zeYVKY*{b)57jnJuuNQecTa?a+Vq-hR4EAi1qOXX5hp7LvMQxugO0-i6MKR+UK#G(6 z(6i9sw^M99R!Cw+equ#^#);TsMSiXoK7XXz=6#$$CC<+YKM5pfyC^K%B_!3tXSMLtOp>V|DR1lo;@JVA zs|TpA*a2!6oat+$2Sn;=BE>Y>C&j=Hh&V?@Y{TL_DkN8mHr*rAx>D5k9^rbWa8(PQ zzVFF-f@qyo(K@LjRH`U%sz@q~O~bK_$LrcMa$sU>9&MunpLvP`W053>d`v zzZX~|K&xe{fBK;X`EIe6C+i_mpTf$W&Rm=`>5P>-ov)-bR_?yYu`^<#mZ0yPay(P= zK2rIB{SC6e;~gSrJRvlR_i*XkrY4FJ!0MV;GWFzsaPqapvynUcikk$98+Jdc@pMlD z_Du|sr~z9(oxszs#?0&KayY@o(Un{r{Ze{rA(NN|`yI~U>6?BW&wG0!Bz-TH<9Tl% z@Mav;3RFt^b`EC&IA}M=0zQ<5fe**Q@nlsD_*fkDC7?K@Ab;bBzA3B1vqZIUU5hsr zIm$SIGkuGUFosIJjJiEiue&^wQ0lN_AU3gZ9s zJmsqv-*{Ewib_LuCV!s^Z)$_L;5njl#{=(LUBXV;C9_;1uXPAf3%?vCbVl79@idt) z${d2S#-dD_c%rF=Eo1E{%V~HUl1gwoo{K&UxjzT_y$H{fUBRxzGfKCk#+;@;XPT+i z%o*^QdYgFxK2x7CKhRpB4rl-tL)Hx11*`?%1G*mQ2L^CG1Z)O>F6b`cD&Sh+CditR z%CksiKT_Gx3{&qjBj7sqF>?dl0gtKIkn(Ft`2*$)H$U(Jpccds3K|AP08v0R5CfzD znLsW4)&UK`VuWvktQmX@=yHT#0oo3%0y=;-Kqt@zKWpK?2XsBq4-DXX2pER@X~5Xj zKiD|10sKZ_6R;Wnwg9ICXW@Dqa5iu*!kstu5la67yBPEmq;rxS><4ZHZbDigqqWd3c;?;@ZBHeq^I>R~8#tN`ay(Z~ zy49@Tq94}R)2A;nm;X?^Uwa{4X)n}Xi1Rjr?M&Ri^`UY3QeW?~LBB=g;`EhP_t$e{xUQj=r!{^y2eHpP)W`33|pJ(KoI_U%m!?c`v(8Jj=JA-AI-Q zE#(Xxz|QI&z+J#W0N+c&vyxGGmM{v>bVjj9fJcEtz+=GU0KOB#o&cT%o&uf&o(B#C zF93f6UI*R)jsSlF-T^*Ful@x16u1ukXg6}P9=TZ0T&5q z2+&B-D9~si21o&F5l0wxvR?gs|oHi+vX&|!o*4H&`oC@?nlIQsD8 z=)-rS58sJCd?)(wKJ?*z=)?QiR`@vsI1@MvZrgyff$g~70h|Nu1kOd==S|(u&If-1 za3OFJunY2wA-@D=xfIw9T!!#_fXjg^fGdHk;O}bqy9Qyd1-}>kb&y{V+yLwYego_W zZUk;Z-fo89|6OgHwlS*}t9>K1?MA!R_Lw>fO*#rKdRwv7M}?*SB`aME|8+nEuo&TF z3w=~r=%2UFN73SMqs5P+m5-v8kD`^|Mhm}fx6U)I@=>(tQMBmMSyuUNw9wl>V}ajB zOT7JWEbmdYfEoSWjQ(y$e>bDQo6+CR=1Cs(cjJJ?`HIO zGy1z3{oRcIZbpAMqraQc-_7XnX7qP6`nwtZ-HiTjp3&c3(2t)(KYkAV_&Js|^$<&* zx{G~0bqo6h_!Mx0^c6^-gY;@huZHw$VJ9YFC%Vyc-SgOq3D}7V#ZF8h{t3iC0XuO> zu@i?BJ29cyi3!+=LyDc4n8!{`z)noSPE06vVnVSK6R;B#uoDxo6BDo#hoA+A6gzQ9 zu@e)DotS`~IHcH#iFxeA1nk5F?8Jm(Cngj-F#$U<0Xs1PJ8?*{6BE#yL$mC}A!yJc zXwZaWCnlgp6R;B#4t8P!c47i{Vghzz0(N2ocH$7U>JYT*&@4MK0Xs1PJ25ejotS`~ zn3%^-Ou$Y|z)noSPE5c~Oh79qU?(PECnjJgCSWHfU?(PECngj-F#$U<0S%piotS`~ zn1G#_fSs6totS`~n1G!)^#8z4Opi*z|H~F?I%U%Sm!SR6L;D|w_CE~mf0+Gm=cg3o zb1BB>Qq06`GX%=nTgcfPGjiq*_r2(od(qcU!it_mzdDJ2aT5LFB&_2}^nsJmibF5_k*bKQE1vIG>yK>sF}JI?RhKiW*aaQHv+Cxk08t=2=fS9`2D7_%wN{D5d625w1Hdl3H)SjxlDh!fC=6VQkg7%LA$E55^6c^DS(Ff8C< z%#sdchIAN~?=URichHy<(3lg@m=n;L6R=>1p*7!O96SsQa~KxpFf7YqSdqi9B8Oo` zj>C!^h7~ysD{>fCs`k$7fE+2hn_ zlq0wyEt~t)pTG+#=y6*NHrH7RD3{Z&Hck$Q_#Mhr9GyCiqqwFS2rkT#~`84}tV`<}kNgS|;SAB6Xky_o^;d}iW1hy}cy1>$|qY8HuiH_yP=*zk@eTTJgA zvnISvc_nM7_j%b$yhpi@t;XAvx3UhrJ$VOPgLfzI!n=ld^T*g)yp8-68^xQEFR+bx z3-YgQ3*K-19P4Na>+|n%xFh|4;v06911G$>s>c_sjrbmp#4S7==K@}Vb2aiIAs?;y za$Xz0FrvX*qdnMx?86rhba+$rG~|1PZ(z=RBfj6k@t){b@Mj?3F7&oEBs=+~IA4aZ z+89OdUGU!LZE&R=t3-}f$no9a@8OTa=OK=N8Nr*LkHOF5_w$h?0DagMUoh%I=r=%VjL+H;C`ZI+7457b8=x-7FTZH}=p}$4w zZ|6;$^Q-XgE9WPX4?2H_I{6-cG3G#zdJ)@6a0br7(bq6lfEsWDG=MYU0%!pp@CnNP zDXgmm?ptu*BGzQ=)YF;neITzmBVN(sVo%!XN2jZp%IOKT#c59GIGuyB{!AzF9E8&* z@FPxm{=uo=sUPRHPHS;qjbj;|!8bcK<6Q4lZ$DQ$RpOt^@^NH3rQnEjiog*}KAn7> zJa8DCG&q?02OOu!r}_)^M>vkF-@$Q2{W6Xh)z9E~Lj4Gi`_y;hxK+I$$6oanI4)72 zk7K*=vsF$-y#dFtdL51~^-3IV>P8&3WFyt(;#}y+t8>-4;yN8U7r6yR8L1pXo+QpO z;v5F4C^abR2QdbM6QR^TINa1a9BQS!qSUHM9N$yfRbNrgRiCLo#Bt2A9#n7PcwO}; z9IvYm<9J$}AID>%58*BpQyoyPP}PfL zja-we6*yW{i*Vo$ny6LOt*Tm8ilacTVO2JcG*tqQDEOh;7IiM<7NzE6)G;c5l^2fh zQ4$lrmO`N<=|>#jDlH<~MfwuQCsgy&3Av4=_i(%^y^7-{xxFZ?^c^(q(jgoV z%xFP}c9afE2dULVU!isvE`&qad2UgR*F zTvc>`m-A&J)G`rjgD88ukWWp%1XmFc&J?Fk#Hkam*Nafs6YumH@oFyQpNsR9IPVsI zb`!6X=qz~)d57Sw;(VDnr{Ij$B@VJa^q-dD_!gBF-+`e2vBZ}j@NI!Ke8J&3zK*Je z57aVQb=>`vDe*ES+(RJn$xkTt$xpb2&XeE6$32u5!mGu7;rDSz_yq0=(;Z=SGWy;> z|0nK#t8j;gjk<@rM~1qGuH@HEUc)mcC!`mD%Ct&d@Ruzu<1wll{1=WHAE!;vw5IuZ zduhVMqO!bvtZA7U=~3Zfn(UGcyiNn;hX(kEh4}}BO0Q;=mI`4&cz8fycsPCWD{<-@ zRTJWJ#l4?+l(ho)d01;$R8&TKCPhp!YocwDdVBde{eqk77u?>?ep z+t1{a=oJzc*;eb^vZE#~!IJ9kl^GRVld|HD4W9oK75QxIq9ALiURUI-^ELa%`Nucs zC8W6<%M2b7RoS&;h0jX2{j}j-4aDN3nhWhL?=nmsEl-IT$&F_ z-NW5&T4sVG`H;|z+LM({)TXmd{h+5dt(>ElatdSsY;8fk7FvnWoE5dFRxQZe+q7s$ zT25``Ku`J3g7h^_K}o53n;$*%^m}uo()?O{QlgX3UDbT~&_a(0KXZL=e*98v^@4)b zl+6p$94E*U@_GISr+yiGvp{pL~M5t-k8C znA8+&*Phh{Ju6dF*Dka8dX{8ZoBjOE;ekOlF7D#?|*1Ey@aX_f&QL_}k zm{vl?AizG7Nq}8dEJC#C7SPZv_pA(ClT4R9B3$q0>16rgW4Y*+qAWbMVTp;@oH|4@%IdLkFf-$n_Yb?WAZAh_Zs;X8ye4Zs3;Zw z*V&<_oCBkie1D;;ChKEbN}AVMgMF7;VxvkD{gSeL-GaSCBcsD}BNEGV3#$#hBsiyZ z@&Iqae}hOYXX=#74Z4>|U$IoiQyOcm-ZZ4h`Y~^%pjN`Os8;@&(PmAHw=@pd54>KoN z(lX*Kh2e>9HCc<|4E%T{v{>XULF7z}(Vmq>+mchp^8UtC+>`nN2{EW>^tkw!NgL(OL4uhfHd8mPz7`E4D0%3<+O3oWHbo ztl8hs990|}l<4m1*6I;kk`@x?6&?`k8lM`S8d(%qm{b+tUYlJPU442*)pD!R!_``y zQnNNACL(J6S!FAWyDQ9|?$(y{xP?CcUadNV`+`(UcDPBu#AwP|l~=SlQSaeaxP0=- zf}RCg4gNvFdcD7|ximSWJi^`6ydZOAMPgM(pp&!76rJcBpOFw9ZVgCMM;9ejw!~K@ zmUpH{<%VeE7g+LoOG{RUgoYVie9fL|mdw&n1TV-eh=?in3yTOS$P6qu2ZfcVmaR&S z$q!b?=ZDZ7G#z#SThx6wJt^HScjhd!7=v~$D&mZ8hKzJCPp__si<`92j*5z$qtiw(xm z)W8VOmg{;~?C$W@b5&q!NO)XwbYQ00!<-Y4P!btkm7d&e%}Y+~JH6tp2eZR1u2F@P zeVHq=fACu3sTM5?&_e%w1_M(?*K!Ei^i*@B{{XF z(l6RCF)-X3lonDqm>XJ{6sPmhCN2n0icd%mFt@n7N0g>ot7DRvmlXF#gvGd*6-2H| z4lT_In>;JBenCu2R77O1B{s=DBrD84+9gC6Zp^5So>|w|{BP8?lRTSucD8pSj6-Ns zG{|(Fqi;lsSxP3}N&O$Meq&BWn73z}*~cwB#68^CjDf5yIy>H5=jyyzXL3O^lzC?? zv-k(cR{dXSkBYdWFpCv?M=Ad1XtOCiI4IIvD`|q#BHYa`W}UwY?c(d_ofnf(6hZGF zqc1prC1t9kusUK`lZC0?0L4rXw%rA`y}&-3&V+Ts^i3EIsz8_zF}}k(DYi-s_?a2* z;oi<-8iM&5CTC9htFv>qu6-cAHplEAyu8VrY4&uD_DoIUZeQo>@roDC*pWKdFaKyf}dKH$a;l~b&w z);}r)eH!y1)#J!-7_Du^EXdp5E1j+0p&3^D*eoVU5MZL-CaDj46(*!rMtQkynN0te zyH{GYCC{hjfRube3#LT-4E|A-)`;SO%1XZlVHpYj?rVADWN;-v{?ieqmZUq7DyCw} zS}RjbCzQcYn2q_XgRh$0`}1Wlx0SRcZ&pdCM~=PnOy(EOex@))VyM9UOPI777EK;m zFqy;ZPO(z3T>t4P#A|+bC`c%>BowCm$JH(`j3~*wGUl8q?6jl-z5}D``ZuX5y z@^K9^nd4);LaW+x8+%jcABj9;OH+P!B$6`c9~N>;gZz?$k_#-Ju?rItN+Nt?)BKVP zgG_$emasI}u;AcGZx@xyIVjdQwjgX_Z=O6N=>leqNNp*#=>fkmB4M6ciq+AD(1uj} z15S^9o~c6jNK$3UqX}l5!X8=E zepq!>MquV?_ii<=zHz+BKQ%nq>W$A^PgZBNrq7&a1!vL(CL=bQrde&NRrYBXUzpWb zyY$>uc>(DwHl1JYU3Sr#X$d)FW4XrWOV{T|FWR~!vog}pwc6x6`QYps+1!baZAQk$ zI^`DBjT7}wE6hV%3;$M%Lthf>i>XtZRTz;kvd<&Mh-#LtjeY4rn-;rz@5MaSk8MOUTeR)zZ{gr+197A4k{ zgd~Nf21Hj`bE?9U-uUuc3kF2WLS3Qi4*Tb`7?Dls(1BM$0p zT9BJOTv$C=q~p>&(_BNw@UqU!8eBvCBGS)mDp{JCzBDIeb#r)Zc!X<+IVQBJBTE)q zSG2`OdicAF1)Ey$^+DnPbDE$p(OvBwOW%WuEuU{^lyfJ}Q9Jep1r9^8}T6(etC8?QRlJ>@)LbI1& zKt^<6j??3uZ%>_4{~rCJ z{onP6pV#~Uo{lGF6v{eoP6|sC>KuQdr8qPF6M924HPTYX)$(MOGHge%7?` z_LiS^MrL{HJ@qXvUb+C!Aghn$nQJSG-p|K1s=&%uPd=Db&=7l^_iv z6}%7HN-J0#Wa9pw^ZVboNah1#y*U{yvEmg^Ei!m)8PyAKDH@90_HpCZ<6lfUOhsbxN{9_}We@`TAp z`I6M)6fbY@z^oYO&ZZK2jjT9Md6}tdyrz0CzfEd zH)@2%PiY=lkkSl&FHofqV1ElqIMdW445#7FCW z7If76CAIi_2DS1_-6QkDq$S+b#}pm1?br16pWaa6>)qThI3P&pn-JpejSrk_!~NnG zhIxCKQo?de`m`<)g{eWYdX00XqMMJSmh5V* z9ViP8^y2!K$qKbOC~bK`c^9>xfAq{vCu+F$yDJkLdh!ap638+x4DTte=t`fr4N1FN zpj`#9Dhc?CI;~mIIcC@_WD({}tlX5=$Oxy6c8w-7jm$y@b~A;6-0RUY>4= zL4k?xUY^qKK>vWn8A7}(ARu6|GB!^u&xtuKbx6UQ&-8P{l&3pM!<$on<{>%gIt={Vq44ZBV3z zJX)A0*))LB&ftvEK)L*7l_kTO-c;frq>U>|jLh@#_jmJijSCFRw1j3y))!RC_R~8U zADGZZV3DtLHMNBK2j)b|mUNC@E0#1UKUo#NAkxPi&{W!nW!YSN>a}2TT2xWBkFU3f z(K91v@9Ol_#^mX^n3&#epoBML3 zYec~!RZV!fxon_m(#-!-xpIM*zi((>bYM|XaDGT;eVQL~Gr2@^XD&zu-+DkQ=mY2i z)btpQ6#J}962!Hv7zNVpEz4@Na?3pftZuGufgVAY>;xAdZC$0w(5%&kEL@|iDa&5C zATQlq5a{C>?dcO}wD^TvICoi+7ntaw@k$9uXjp*f68Kak{u$@bs13|tyXV=Q5v2{y z2$ef6Z6rtK*2KB%G>sbPl&s)rPY*Y@Oe{6{lckFT{Vjfp1xF+E%0pxFJY9VfLvt(4 zv3`D$Mw6)$oql0bWPY5FNYkkDlzycD5T>1Py4~-v7lFGBqO7nDiXCuFAJrr3$G)kA zEbS@Vye1+pWO47pqJhT5oT0|TRrv;$+9@+NAwkaHLjIa9f05^v1CUpHWCtZ zdv_WA<9y3}V*SEuwk~ZvvnswS!QU(1yQIJxH7o0Rt1`S|y@HB+^R274FIso?$^^yM zsS?qGr8Jh2xwp>-9rT4({Cbon_Q zlO23u#rEYPLBTQ0_N*`HuFrH;J8N=s6Z5juB8vUp{2TJ)DiUHFvWptg=$vIwosu3z z{_#ADL;h{%*g+ZP2Eo)J%-N(8#c`Yi&R3j2EWUK0plI}(*8I-AgxXYVWmJ@Zj*n+j zWNc!1UZg(QEvLI7F{YwD(>=@>lOGnJ7;W|SEeME?sm)BSO~~&tWcBaw?cG0+6;Zb^ zy(%iYEFq!X$HOl(I57!#d6LYM*4lRZ;)c_gX6V)2&l;VOmys@tSeKPl6&F({iYTvS zD`9UtZKIKG2gg3>P$|XZD7Kbxo}MTDr@J;Siy|WymdGnwzr^79n3b6;H)IFInky~w z2~GJ)CE=D>&pE5w%+P?SnYSKodosbkA=cf>sy44HiRLT=6`|MKGrzi?Om7hKf zDhm1LfA0NG%Wk&d^S#gequET(-0v;to_qSaU(4WS{TnJ?|GkK_kzbAXM#FdQO^IPF9%E4#Q{D9-vdo>UnSq;dtUJOG3I;rCh|QzUp2ns z`QMT6&735(NA!KvXP13!FSZ+V6>)$1w>rCB2S3zjZF>As57*#uvHzcwgZaKtkSj+Izv(WM4DV{v1P{3A`FN-m4_f1qlt{bTch5C*UzivTw61X}=2z;gy+$Lrx1gmToPzy;YW zZ4*1{96GP9{oKLHvzyz_9!d_mT|>#lpvN;H=(>Dr>XOc(%laoT??!)*F0{5T#G?!C zZSzqWvk*OkF&i*V;wTcyAAy``MuR_9{zyTgSMUq^dt2w0y!j*vBmS|}*o3j!*%od$ zH|?R~=H@}~NwY+9wtS1VJt?thItHBSxiFp?jvKuMG8k|}G?I#_5#)s&A2q6s7a{%9 zjTHf-lLm`0&M`jL5lIhX>r5j>u?WPdis3W#5qfVBV!Fjx8T#9OI97(v9-Jt|%T5|I zLc;tn=pVu?AU?tVEl8V2lE;JPB?wG5KAnJTwgo)r*fc0*wqZlHoZ1E+Q+7#ZqXL&a zb#9}j@BGj2o4n@iq~Fpvv9W3D_H)|Czwpu8O}9^~LKbaEDoNTPLeY8t?PK4gY95|= zn0{vBwwLw=yYD=5c84);>QcIm4cSYdxkPaNPcQ7cX?LvKCKq-KWsTdqwtaj<0OiUm zA)8?EM+Adz%s0;vhc>YS3~xmVi_}M#cC6xQMwa1ohEIn^0g_bFeEQdLCNxihV8^!xjooYktv0TX%4--+Ri-b>G3wy za}*sD!IqgOo!Qu-RyaHS&UQ6dt_s>LMqiux9JIDxE#f-bBHH%T?V_nWWk@LMA$((K z?DZz60v=1dBbYIY4uw$MP_O6+p)CDxu`FcUnEEbWyYI-T zSs)d3*2@GEm2UA%!R;|*uZ;;-7y!DLk8SIsb>MS0W zUCdDjmj2G9BB14^f2>Ap(UFI28J9mRHz`l_Q;QpfR+TO$s?7@PpJFl;PRel^R#ET> zz67!svd{|oab-7@?2Oz{EB%2x>84Zpl~%d~Pv$8qSmPvjl=DlVYaBs=Tk+1!gBvLv zbjBx*U4sq?SwjXiIocH*0{(X3!;Tr9%mH!+A9_aXKaE58Eo+k*>L4)maQcVq*=)T@uCJ-B(W~6{kg}Dn4R`r8-cUomNEbd6m#(ojTq~k&^wqk;euXoQfr)&vs@mM#?-{so zZJS$a?_Rv<=3(2+XFs|%C3hxFzHR&V#T@CtYEhK!9UE@h>oWRu!Vms>M($CD=-Dhg z@FkqF;{whU!TZR#G{8t9E_%$Jhz9+w*gAr8V@*vpbjp?fT>}RMWGKK`X?e1jH~I= zqw9A*@ezltUbtr7HypKOM*LETWb-9W5cm-{(D%c^^7P00;{DFB+v<}`L<;NTjT`b; zZqLe{NmEv!s$(2d66v5=<`_s3hZHCDA;K1d#;rF^?f(2(&FvRYHhE?P+b*zA>eW8&@RW;6}vQ5tJP;iYcnqJ@NU$}WwVogt6ETyQ%l(#tH-|oa*~Dw6F#wNR!Sz! zohy}4{5L#U+(dR143(}6?wXWd_=UZ3|HQf7HLY8keCux6T)XqGsbowal_E)so+D;V z)D>4zj;0R7;@Uc~5-X!Z)51|7 z%-EPXB-YW($Sa5(;@)Kr$Uh<4KYW%9wfCQs_mEw)*!!WgYtZk{SO=&Tuhap+3Z>uT z{TJwSQMeuQ7L4nEJ{pOuPEKb|VgAL80LB;C6R>N?6EY}RF-*mbX2rewL!AEiA1|oG zHu{y-)Zw4Hx_plA#g^=v7M)uB%?A39oH}XUn%=~)j$cy~2cMLq&3(;cswX7XnR`EE zO!G7vv}3X}z1ZWlW=H&?acx&d988&pXPiN~K4@x*2F$*gB3~;L%G$iWlSO|M+sm^P ze-ob-rCuPuQf%*hmB1xnr30J|b`R5@I!fU9-l1}PQk6mAvO;^X?x8)&>?Ck`0e+y^ zKXng*!%-yt0N;Z4*vGq$?FApNl7Vvu37q8Hk@gUPBJE!;!l}0j98^sL|8%h*&T|Bg z?=Q#mzl6YHUDRU)4)+C+_O}<|Y#o8?3h?I{IL=ria7dyd?f+JQLyi%FgD;T4p`IV+ zPqK*!+*p9WMBuP4xIqBp8EVbQe8fdKhZz_981X!P1dj2^z#-s8`e){Y`(gIQT4--1 z?MZe5=^x+Gj{BKs`o}o{1fTW-{Ag)hsQ8EZI12FF2psUtlMuL*fpanhPR=iZ_Z8rG zGjNzsj@?_L9}gRHIKY0x5(%=6s*KGZ zxFjPRx$(lNH+jy%w50dyt@JMnku(BlNDwo$*V_@?E>`4lLm{y%98g;0&F? zN)fEI(%=34rJt;r{mG{W19g1x6^!3eh%P+|RMrT0DbGX1xuAC!bh@%8jTIC)G6mI+ z=;#Gmhn+zsgPKuFR@oKoG3A(@9qw#8deaj-B3);#?Ns}0rv6-D$XsviwM1R!sZSoP z>$!OQq$!#7I<$d$B#D1;s`V`z+ahiAMvYm-6TtgyVkT|aD5&bHssp25tEl4(WkO{> z;I+%^x`hpkC&$Lk)YD6?jLcz+NZgK*IUaTu@E9gG&*7|omW37A6&PQ+YspO}yw-Xs#CRg`sa;_<*N4lK$K;i}`*ICGB1#jEZZ zm4ED;x!#O3YwiJt7uoZ~W%WbX>>6P;LtyG1f^-S!r#vw1KaNQ!DRf`&8m)WPn6q~h?11Yjh; zhfNd7fF=LKWY-wU@8R4ByT-wO1kOId7$3Cmc5J8T#3oS1OyW`nTG4PJ0n7-PnoLPF zQNSxSaQMhBRKpffNwVxuTLr?|M}KwvZOTbM1*VHaZi{^UOAGS&;!uFNCeXQj`YM59 z>!N7n>OH=YMpG&V`Bbr{^@Ng;nE3?vje9B8MP2kxd~7TlqY~qHxeS-sRiCAhe*?g9y zp$n4k?7)<8soU#n$W`>M4bWTUNF-`cZc2EAsYGzBLuYf0&ZokIkfy*y7MSs4Si0cx zK};6p+HKg#^<2^AN+mgs3&&BR;CG&yqbz+ zsIr4VNU>o>=yM~N&jo|i=N%pyInQeeYU?IM!JuI>)qG%Mzt`HazNKj*K@J*he?yZa zKI#vQ@BPRm{d)W5PoJ+n@3|Y-1>JUC@7e=h^wnr!?8pJDQez*y__4FjI=U-n&2MV! z9%l|xeDBQSwWBt8VG{^>j9t8jgRq7fl4)ID12GPY9yBo|}&NLh*z5 zymi;-&%S4)4|u)Z!+p1keqjIm-w-IQb9Y}Hzv6{k_J_J`5sfy|=FqnqoNANEWBKs) zXxy=hV*1nSC22bA)W$+dT6F!4!(uWG+_r1s&UD6;aZ4L)P0pgAs8==A)GDmXoO6C} zrfYLnFP2;Nox}crqGs(z>V+k^UJve`f@sVkvh)A;|HGX(6-3bg?fs{!ADVdh|0E4` zEnNlymCJxLV2=u8xicOW#&Re0!F~tiA%163S*tF((->`mIP%F+mstvdA2{ha8Ic&j zBz9SH8Xs@PlO&BoZTlzMw)Z(3L@N7%Pt4cl&+Q_|lFj~mD;*RlCa3GV_e~isg8C}R zk~*@AQA-Wu9G+Ng&|~RX%(;6FEr~vdp*>MNKRSopV@S*VVQqfKb26L0@?>y7NmdRP z*KuA863PNS>A$)1BKa0gkpK4j8A|(C;9v!1P&Co#8K0Vm`fE{Uu#W+L;;bz8;4(Jl z@dSYtm{KedXrf$eveIQvib54~onzlk;|M^QFbxe zTqm17v8+J_vWQn-$A7YyS*x!jLywUPSLy|-=Ans4z!iVOa==13#aiy5UMOr;!C%U) zC-R-~b9%V^;$Cr6z~w&zZ$mpH`HrJL$D0BxaZxzk{*`y?RLJo&723Eo+}zm3BpIs% zI)`;_Y6{EeVEX7c_h`Eh&V~s!aE7Ysn479GW#Lm zVb!Dt5qu_0>jdG&Wjh`#$_2+@(KA>I`hw%B*!iNP!_#)~=oZJqRik!uZ`#&rtk>qu zdYed@**jf*;e#`7yT)HHiQIfMGl!+tzZ=`*Z5!Lna%ZC%ZagAhz3K)bcI){kT3kM9 zZFemMHi&+dwSxK5I7{8bo;q{%YApNnE78CUggiuW6Ic356 zDRu}-jby5*36~nt@0>wdJC44;%?ZG1*Fs)Q8?2SFGC>FkeR1m z8v5+uRR78ygkJm;|?4~9j9Cp{dRccEgMY3H(wI+4n4alnz^vw5}9us zzoLgddg?TCBqQndCoOK!j7*@G=-FQn`_ zHSH>|cqt|Vfw(eU$Xb6YB7Ii=*i7Y*OvP&k=2NO7q z(KKt)J0)U~w8c>lV$^FOh*7VoSLOW*6#H|{V~>Hog>R+!8qw64h_Q!y zoM>t!cL#8Tb9V|_HN(k^A<`lfDfmK&#{{rZ&ORYIINcY0dP=(aj-71=d&bt2YWJ); zvZZ$C-80340(yp|;Gj(7j=t@;t@A~u_g$KkjNWij(mVdhHgWfrTblDX>}c$@lS%@* zvZ|D1aB^_vOCH}I!D#*^mk;ATJ2`_WIDv|JGb?9)TVli?ojd=AKJ`amyeM@FrjXow z-|qT}BRiVG9j^^Sfkb&dy9TDNoJ*=Fp+aI6R^Vx~0V~-4;00~wzJpV%tq=AFN9}hK zi^m9i!e@Edi)3%CK(`85_c&|1xNedSwbBqYRhYmf3z$h!Azo4-Z|uN=eZ+r)C}2Ei z3WyXVAZc<>h|6rqKX&D$6s_%WpeA*ahb~{4>UY_ zCbz7xcV#xawI0=k*rd?*IUu>Q!5TJD=}k=?2bR7E?$A}PV3FE2gF3hTjP77z_nL9p z_ZXCYe`|0<6O?^hTYc7qQ^n2mlk4+6>%$-1zxp*T*~LkZCA@i8^O@h%!hR317=X%2 zjK$Mvh6JM|CE#C;(e8=kRQiOEQR!nD12-|BhdhG$r9b^EPkiFk@yF-yB~bpkHkGqfiuKkYq31iQRWg5417vNOgmDtN9f;?t1v{}hgS zCU8nmeDIb3Q8crw3hWZleeA!FZ6<=n*-rE(&{nZ*=4^o9BK{P@K1tBRXF9Ro_9^VDt8qmr8Vx` z9TVmcZtQl5&553c=I-Ib^^qU)(kX%~UIXB&mvtVzW&Jo@oz}!-YITYj#0p%QNqjgA za_1lsAiCzFTkamqUj3UVugJ@S{fQIIK%G;`=W+d$=im0&21n10SL_+}H%3WS>{8Q* z!)km}_vl5l&DMIy*i}zonA|g$IL=UXWGoOLSaW!1Z$MV74t8(N_YX4)@Z%p6$&f#Q zb2iI9kAv_Y`!kSkAJXrEN}iok!7;>K2pg}orH;D>?7!zM`KPW4``39`KHOzuU~S^e zBkYd}L4Hgh&dTaw{9~)!CrfxkF*_V6n-4~0F)`-UUK;kWHEFQtMrXa=#rrRx4S>I< z*V(Ao<+k6k)$SVYaOt(?c1c6mh2!f^>BI1=0%muUT~=-F-g$UVRL(|i-o3WdQ7thl z6*7~sTVOoz;fJZ8o`EBWSwHsUbi#RGk29=RuK)Ds{l9^gC#(quE0F`w-@c=9Rf|sV zQh}Q%F#C~5ce*jAbXs%AnPLh`3#)xIS3SC`*?;bV^5x3F+DB~7QD}m zBo7wf(g&A$o=}yGoimW5QLyuiwSXr|RBZBo$aN=32!bgOn(603M`#32@Fbjs zMzGh)d2--r`jC5SD||H%Omsd2dD{^gC(N{zg$_kcq(K}K$q)X3W-w9w7y2B4*0eC8_8zY?DsW;N5S$U@(zLRu? zL43xS>ahn`n%;e`vgs`-D7l*E(1{oB6)fX|+4iAs++$Ck&K$=l67Y}jVcQBf5EV}3 z{{sF`&)yV%Vz2*??1lRbjQ0PzO*uKgTxrPT*gUSKnI&FDrk2LTgWJjg4z)D=7=bgj zG#)}md*TMYNRCY5eN-t5u*Ak*AkZCB>A> z36F%5+xyd>_`;^0JMY|Kb(&3mxgeAWFigoZd;eA5_6v5ecb|1I?YG4hNS%8BvI%s6 zxy#rVlcV?H4jahSagsZZsV5+G7jG)u1E_1z8Is;=S;akoXj^zg*Vwf?90r$6pcF~N z2FCYZVo_Bhl{oO}{R4SzL%&#sF8~McyD&&@Jl5#Eh0Au)kgY3JxDWZ%+9*#NfU5hsPdKPsrUyZKq>qtDqKM)-!ksyH{;9j-5YC zx1gu`cJ(V{lAb|zSSd@XhSvL`|BDO#gDtyC|BU}o#`q5}JD%xCtPulo_pRot^IH=R zCAYKSs{3D9$y0@u7*Dp60;1(%`9E~iBgN`kewXz;d2cAZr{)}Xgw3zG25y^by9(5A zqh4n+zf7T}s}4K6oY%w8#fS7)-%LJ7Z_r== zy|%mBegOZ$ukU^Ddq{d0zG)BN0Vbqgg?ZrjkUH?~6Z=sFu`AY5`3_R>PJnjCs0)?w zFQ7yH^nsRqUw3!cKwl5`U_zoI`j3wO6#)DuRU|#IbI+awJ9h8O3=a>%|A$8CPXlQ8 zf$e+tW`;(F;J*EKAH)rd6nU%oHj{lF7z)_nQ%{ap`y6`nT0(;VjM9OylK3zJ&ui0CQAG3LU> z$Kj?2B%c5BXFGfDhsVG4AUqBgWQgTNbBKoLg&;}za8ICu@8CNUj)cO2I%LlZzjFTM z=*ZN0PYIuuv5n8h*9t!s_~I7>pAxQ(KZ~D^uy#|`)F{3S@thd!C%#b-E@%12i!XM) z_~PzwcYfzPo!`do49gco4*3CA4YY$gCp^oEQQTJ3AmSdVwG0p2oRec_G9w;R zqbb-&)+qGn{1f7hi~yWaDEO&iEU+p{dv}%EYE{FJz-YDW^>!;T8pM(TRt_-uo&*|@ zp_*~}ukUiC@BcrX+Z=`)`f7nvL@cO|EoE>r)<5MSml9)r9p{?^7WMtxVv(&w{oCTY zrs?KzvmS1tZ3;!2bk((nlwj=Mb1%GmXzcFu)?7F1?~P4tiG(*z#bT42!>MhfP`QC{ zek|GuR4;%iIu7h5urJ+_h@g!V6yi$+8~o38qd&V^Wc-Gjk*1Hq6Cs#nHOvpB%l=p~ zR~Dvk9#?Xezjz?|=OWEPTvk=QM0YxmdUW5YpvTxT&gX>AhE zcqX}FK6->hXwe6&=Rog%f=5TJn5)55x|cG57+soCEo6*}@u#K@4*!gr$X z!FvEzV3dpcHc$k7T8Noq2$Fb!bZq!~@p!@lOGG>9`_a(cmM!o?M1&}cQza9ArWd_Q zTLwS#$2X-RsAF+JsUN(tiLB@tu9*hPD6FVtQc9rIkI%$XG3NfBnm4*Ody)h0nQ2Y4 z#j8e}>9@uDgtB92rtMsq{-{D}&4;?X`BhppJoFtNH?TH8xIG4Qj>9+tz?>QHP~ip? ze7&DS#)S3wk@5zWSPBu=$dz4_(yF6_TXSk}LxV@vza`1xHAFJD*11q@Q+IoJzTKa9 z${dXXh`49Qu3Bs}bGW@cUiy-auz`v7xz3#}JyV071NLTqP^0ei@d=^_X- z=E00DaNkCGH-TMPz|qF!RTm6%AzOlLznSacfMY%_PKu>oWz$rXkPCokY}digv{=gwY@O%h+9?b_KAncCevaqXryGgpA4V?Pf$P8x1eJWJQmzzv5avTGcoBM_%#|p@1)rzvK=V-RSmJ|lrAmpJRq(`k zp|3J;MD9{&_&wnVfJe;Z-CwlDY%(sFs-|7&b^0Ud+ymbUzccXICt>!DtRGQq^q-7u zJ_l&T20xAH7#&B)e$?6dVrM5lKNWqQ6SLW3Tv-^M2PIUm9~ijv3j^=KMnJCW^1J9x zLP>CZM;Rj!{%R9l-UGf&S|_s$tkZNV;VK>-UA{;o?t_R$}Mw-7mb`&OX$R z-@!N?l|&>KHrw%L_{W3y97O{3bx_qe(;tT$zUk#n=;`IpV)@hw*rd?2pU7t6X+6xZ z2l_X|Ib(blmKa>~P1qS+dIla2xWJ6@bjTH`Cp_rOwWPDY!LSo&K!6-=>1x5!fi2tj*Al1nF&?qzhC>&@w z5fK^qoQSjlpEaIHA_gug;(Bw!@Y$A@!tjYi6SzFsMr7?xzt9rti?qDZ(r-cClr_bS z-;;gG;Sv#AK?bTCeFp1tImS+ezA+CZVk7|zg;R=zR`2h zeM5Oz&%)|K!x#lQiSsD=eqOcKBxYN<4kP!;4L6;b1^bvOADQImJ zwef|5T2QEnMxx8+TA;XrVmT=fx`6Z!(yG+IsW{MY%LCEBD1WXPv|{wi-hdC}iu#bH zI1zL7=26s7h-Rb!zPW-+!EJ&i=2H2&$g%!Z;4;PEB`vCW z@`}GP)^e;X(EC&xYhbwwO%c5uKKelTfzL&cem;6Xm5v;}KXUYF8pogD@^>&F zfNP-^pu{WiNx;2^(d)P|-Yuxxpb$t#9o7gI*l-&vPNo972W1}DB+D!Xx#y4|_zP)Y zsj)^9qB;?z@OkW6^yGKdTF+>j^9%YJ^dypXOQoWSE07e6>snh?-LcF}yrzbJ1HInb z3iF^}ppGmH;qGvlM-JE${X$n4d(#i_H?rlP}!ryd)fX>kq=z%8)$9-h^JBUZk z;&1RNeqQJqd#=%%>HFHA zXk}4!Gj;RwZ7@@8lR>sHrpLk*@EpjtB*-Kx=Rp5<%_(&4^s9 zmCJQH>gM4dp-v#IkyTlpq6U|xN>U>f=A~l==ev3F-(-18@iHm&@k^zu~jqWPy5U|Da0yd%~*MFSCdTB3+D zP>CRw5?CH^*5lxQc;9_bPr9Y2(_}E3Oh$vL^90CQiQb-MqNgXRwb`^9n+;i#@JA5< zuZ72y^T4uy*}eP_K51Z|luj`HTm0I1t=0B@czL|d25%w+{EjS#moHsDN@mUKsGM2F zXL`h}P@2t3h54(+4~Nwji&|x|s0zRFnW{&7k(+I20n-~TSRO0=>U^-#>-Rg|^)|ge zq_*ffJlSqd#5VkOv^T!T5pu|lMupU`(q|H;M5CoYP5Ru5($J@iP+-~KfRo73;EEB5 zh?MdU+p>Cdn?4$Bj5aog15UjG^dUoszq89}c6&$B*1Gx(-g&Dj;V>9=GO=HwP6r)f z0aX)mrn>P-!fTl!JR9z&8U^cA!dMs%a_2qR7vKN-H@aI|Ub>J%Tw^WTJ0C?F`ghIq zcdU^mm9-%;_QBRKJCK|86CR)S8y0Kn2?8;oG=coK1aY7l)@#t_9Si{t;g_Ik=%_!E za}TFimwK1|_6NZH(DFL?=F@B`wzfOPjGfKc++y9F^Stv4|DSu8?V5F-b(Zs-bMU{j zAH0s|vQkP`fo|tS?}2W|2hOSh4c`G2i!-NL=a^&BNLVIS9QT`D_*+u=UHrS)&QX9* z8;wS>(P-EMKM%nV4S(a`cNq-EVfg%?CWFEB5hMJSfWM5W8-JvKkN;&pKZhG!i~q(n z)wTR5*7J~20<>Itp1Q0mwaunhS>r%THmecZQY-;_70m+YCK__C;3$ZIo(UcU;NxhP z2=p3>n7j5d{4vWH*41*>QqNdMhOPF=aZ4@!pv~seY8V-MW8F=9V~rM60*e4YmaCSE zxa-i*Eh8fq{8U9-nB{13xVuSPetmfere~1gtR#93y$+gU9kge*Fv0wCelBz9Q08+l zJdi$gDE$BuzHB{u)b?^6^9!%h0nff#d^X1r0d@$0&jr28JoV!J_~H8zdf9gLs1+V2 zzm}Jm_fzMv4pBem@F@=c2Igk81g@C)2}J{hy9D$Hkgrf{m#-iY9{4B$vV0k}mh~F~ z;V*+X0w6m8atn1YJSAEN)fv|Br1r9Ipx(s0#Ax@x+JNuONeu?6T(76z)abPEpH2fS zNT5%nv#3`J61`-U6(k9fD~#RvTCmgU=nMtAoX#$P&=>HB{C@OlNKy88I2;{*Ux%Y9 z>h?yXUUw8@2L7WiqP_(b-2igiVd7h`77u|`*bsh%GC`fw_$*QkBRTyGwy=)Ft*zw@ z|Kg`VrM|Tkp}19UpFrv|3w{V^h4jRJgl9(xKJqCZ@OYeE&%i%cg4eLluYi}^SHXSF z<@Se4?Kz-Rl;P#}T-MDM@X1np5#+9t_RChhvJ$)=J&O0CFrQr|crEL5;14XqZz{nB zaEsVuEDC?ajsZp2vw){9m|_+{5x6&)|JlH(VaMo0|Jc3;jiO)Dze0Ta??}D$`0FXa z(Xqjw4J^Nb`hhDg0G`L7mj;l@Hjv2$Yfi?v0W21=gxN!jOom_WNel+$yXqQ4i`Uof zZro|&GsbrRkSSs zm6`^MF<=dVxEHYOFIWpeXG|EFZ?~f(jdNLZV&_O;%;_9&+IhI!H+jiuVw2Nq8m6vv z^lopPx-2ge4Acu}KD%f5s`W9sw69(Xcp)Q@9fuQ%cN`fA=8uP0vyK+vB0M9&FwLkivd+SI z5D^E8ft`SM=npVHobv?}Tq0BWANm8m7u`VrJF?I}ML=PgSeRbXA4soYsgQH@3h9;f zQUR}K&94ftS^>xBi0Ov|zE^xMn0_#}37oYII$9Zy&ll64%euM(j?WhZ7lBWiv|qO3 zRh8iN2-aP(+G_BT;n%y8|9xeKtkevCvSWiwgSZ{u_3ME?O|T~sF|lDW_rqGA0;m$W z8CIDI&BiR~*1x6B?%jRc;MBcmHE;Ku?du}r=VudV&j(sHcY@>OGm?hsd-v|SXI3s9 z5G(VSPLE&SUBiC`?-Ch@jros};IoGHJu?56WeXm^1V`v_i5jnGWBiczK#eJ&MzV|4 z<%L~@bwP0fF%+oFXXwK-GicWg>ZiW|2l4yJU*M~6L2C*2VeQAH27YyT_X?&qc+?~o zgLSg7uz(+gf9b<$7k!wj?K%hk>stB??hD)Tch=DmLRvqP-HO4fz@NfY`ZaNy7V1P=({qZRO~?TUEG5s*@Im_1&IGGQ@4}k;y3|7D~{V?s}Y!f(G?=UW=9|kU3{vFdE=m*wSfb*z& zNC!mAk1e;do`BUtWTvyiuMD_`^UqOQg(9m|<F#)zeh2pIE_1v2hxE(%Q~))=lQ5eg z#u|lC5Bz4%?w_GYSPiL9mcfoCArN)d5XSW+H2E{Z9XRaBSq+f2;WGwLL2nFP%=+5$ z_nA>vz!3tEGK>QJ5X15B{V~u>V7Z{#z|mO1_(ANUn!X(~Az=Z}9iR2hq*yxUTI24;2z49lVpIsI zqoQn|5gozn1CDqr!3R{#VSE%M!twB;M8H^wF#%6ZdxS0^w92%{vY3GjSa&jK5NI3E z(L`*tQql`i1RP3Y%%copl3 z3OGKKOnVNz;|%lS4gi3Ie!;IzI%9#Zc<($#s>SuL|E zU1Zy^HX@aUs8@MhER_d2yt&Wr-hI#PprdzNJM3U}6=kZb!ny^fRD22rIKDu%Iv^Fy z;T2d{A+8prKXi$%b=T0y%;U3N?K9pf>e#N0-r=l`{w;N^pZ=9D8|?stpxl=b*23W4 zLR{`35xGoP&@<)Mw(c4ko|&1y=_2|^`DxGO`e}5LZzN;8ojP{GuD|PY(T+CySGd2H zuV1ft?9*s9iY}~S9pIy$c&m%S zF7WG@ae{SQWS`!jbmy$kZ+-CknP)d`pB%FFQOA5U?TvlDI{I1UpnpUi8=(Kz(H2Om z@$O?huOla=mpLh{9}@~c9$sBI5ty^UJb+r5_6T*sa}YgW{ww+`XdOV^i&&!sORB_1 zU|#T&l2vpNNNYrYP46FYb~h`m+Z$tDR(*HQ-{DuJHn%mccWP~u0bjFT-=4}$`ku*7 z2dnC9Cq!y()M5#`6k>uwzO${YGcm>XGxcDW+f18X94A!`DKh)9oT$(oLbS3|Bm zKFyJ35uQ7&iD@rn-AK4)WZAL;4v|~3)*0YopMkZ4Xavdxt*T&bl;kSrUzQj|CW*4e z$PKimbzNF?eP(?!x^pVJ#^qY@j`vJVr0)J_*k=2j{+lZEKFSPrr0 z-u7%;?jE(JAut-J!|2;R=X3zBAud3ffEK`83Q>6C1V(K$Kcf_HPzMJG@vf2<1Owv) z1J!V!Azo<>>>fM`D`nt_=$;VQhZS7{J%WrX>>g7VPfi+0sY{vgT6_JCg)*Z=-YTi(gPWBiorP&lMxv>))E;cu1)X$MxtXeD)w4 zgMAA;4J*P=``Hd5o6mH^R)DVwlovgGfd1IGJUx3IPf){0h+9+3zk2p-Yj^W%s(EZS zzed2jXwBt(_>Rr45mxQOL!nn>UZ;-F*ExL(8XLoX3ZKU*k-B|;IeM5LPz1a#u`1DQ zKwrQ#K;}VcU_*fh7>nk3coiHYymt&7?={n&!+N9wj`x&-bCH8!i33>5!#Zm*-Lu&i z2_Q-mVN>}{Uwd}l@7~z>@FVMfisJM!bO24zzeY;>^B6lAw}3Pjh?N|)hSSK7Ak9+? zzn}U2dbAy_qkHLhkQhCQS3ug56>No%*w^wjnbGm^DppSg9It?B&sqLQ1stz{fpg&< zCD_&hO`Vd#T&iIOkS0Ph9ZZyr7Nc|No6(OT;C)#;6^Dd}V|X^CHJQz>!fd`ldOsds z4R_9Cd@^usVUhM=LA*$4-VeO_=adQjJx;>0pn%&^PC-Z{7C-tH%$@O}NeDBxMOC6!iH80KCmHd% zdk2De4b0dy%xPa?4PPa^m4Uxifb*!D@e^Cai)6aW&>xrDa^5RLi#_uw?(Q=5-cno9 z90RqYzf-2tEb5WBJPUTH1h2(!MJdhtXQf^QDL7(yEo>i!GiF-I{jgXH%S@HQVmr9^ zu;^oWNg1!$_J+Hgi`d2Qih>-d2na@MHn%oH-%kzpg!}By{;jZvLUASXz-Q69)AZW6V^Cz{2=unXjc%V8HI3uq*QMJ3vhrlP8^Y7 zS3xPf_8^<$Duyx}&r57<%?v5{4VkH18(X!xsjkjWDr0UEoBh{aJ9k0aZ)~UDqkrz1 z?%-_Lx^3g)CZPKSP~bCwIe61VMUL9>?g$9DBX8&KOzYk*SD#X&%xdF#v$;LuZPRLU zHL3I0ZMYyM7IasO{4;IY8Gl3FfUq9#7SlVXgo_F^_6*rY4F3jIg!8ECUZ%AO&!yB_ z2okB>%bP_woSx_KvB$kr4S*>{A;S*#7CXxn5uASjMj<{*INgZZZ9o^2Y&`e8jqbEx z1aGXI>a634{2A?dN9%NFXJ^fI*IxTknN?mFsef&$s!L&$cduD1+PHo1y7_H|u^iMA zj08^PQ5Xb_f|7Dk^x^;%6e?mnILH<;Ruj%1`k^tW+i&0C?{GHn8R*)bNp0=2H2Fmw zQLFnh`yQEJaWI)TnK~1reG+FAwN!7FwZ$9Vd#@c{cX6g^XK(NJlt3+|8t3i3iL6i~ zpuYz%I}Y`E7Um2I7xqkITs%U(2RMRr0{l^o&M)C;E#Sz2Gr3Apz5{dpK3{|bjrA31X$7>Z0*d(wNE4e6`vHRP7DQ=#U{vW}zkNQazW(ZVT^ zbwI8lDo@y}i+hDN#-NkB^d#uRQn()exg024344T17Eq7zzLcGk@Xl`3clFfTwXT7L zF(y%n+;XqyYV->2MX$K@hK>nmEx&_X)0Dw^+KFWmoJlj}Cq>AfDO@kAn*ycpUD$|+%7n$b|i_7>$!q_blXU3!EWD_>KKF+W9?{S%UNgb_hn4oc~{Eb zV9HZwr>k@6XMLNaV)3w4KAe{ceAO=J;ma?%wI?{&x?!u#mk-LiXWWEB@SLzjyNT>> z%_6g37>enGfi{H7P`u|D%7;DwHvC3#>kf-j!8uYFcyb{J6I&4WqAd{tM$8jOv*z5S zcQTc3Zr4psQ7Va3q_s+16CL#3Xfl`eO=iFm_a5#ObPdv{8TuqXdCcrr>lmNY2VZ%G z>D`i>DE8jhN4+=GIw^6G5hgq6FQGOVVIo7*)LiQf=8*$1`^O>ThBISWqcB~#pN~l? z0OUV7smlo7ZX?*_SpmBILx8#~ zpjD4x=wARTr0}`IZ6ThxJdaNh^ul7jRMDDCV$TC$?R|-Q6a2Wh;@0Rc1kpNZ{Qt;^ z_+&97ik=53OS5ccJxk%8!mYuZ`e#`0^2^I2vR)C6e3bKaiOs;{Nh*>RgMad=8q_0i2g`t`P_S|%sac2QJPr8m?({xAlKbB03n^CH5Q8{vOhv2a@aOxF2|EnDm3;JW8y=xXdkE zSkIN9&^x{j2iE*I_@@?T4rA@dnFNfNRy+`~PEKWQi%4f|O2RwMAN4l1(+@DSVNby6 zcNDc*=5!S9B`Td01w-Wu&}^tr&(2K7k^^m%N=PM7Yd|V<#i3?ndQ`&Itf9wr^XuU}PPgOE8)Df) zuW%SQt}XV3vA~>h%n?BEAT|b2^$Bp0BpV47c0@(A6{a;pv5MAML&b0&C72*{1iq*S z%z#b@Qn<(pE15?DDRLR{ER$KF){_fowdb=qfkLOM1j>Je0s%zWa zWwUi{Zks-s%N?A~tOgg9=w zkfq0Fyt$O4Wlz5?t97P)2GdphZLL~IlTT-+x7XJFMC?-o3Q~gFpNPFGKnbdwzAz^i zB^_z&V)n`#CE}z#W7&|l=Ts;xsN1p5mR8c=t`&sWl;?`+fSIdc2+uVRmdZt7@mWDW zHW7L~-coUtUATDWX^Kl;IfKK3BuH*6C`{D7?F&)*4r;VN*6(x<#M-wQ4Vzl?J2OqY z`Z62!(nJS+bTDsdi$z>ouEw3JGx`@Genh2CKAcqh0{|9bIG_hr0U+Z z*ftmN&$Q-e{b-=a-);w58VAY^0xcE2%{bK;TT%|3--sC)CR{AW`Gw#mo+TRFa9(0V zn=9+X>f=z@mKb)fGb`pDwq+?-^?_TU=k3^AGvvBMzat*o;U-3aS7 z7B-AmFw(Jk@f5}8DTcEP=Os7hGDAvEU4H7eM4KM9Pj=-yYa4=Ma}XP1nV_ZngZEIt zIht8^Ktqr0fk;Y?{8jVt^GwE&bk%;p`sYNe0=%f;7Bg_f=gC8R^#g-oS_Ao102`{by zdx>GLumaqgM+q;+>%uSc%c&Zz+pVp$+pJM-U@Q^nQj7gDbs{KsS=|At#^usl%a+(Mk% zYK}OJhK4%1PNfW{oQ+-P2GL-RRN!|iZB~WG<5e5HCZk>BwoCk;r7u%mZG#q54$o8s zSQv#oQYGv`MIH;*&>;^On{7aIRZ@4IFsg+2_4f5!qPO23Nb@;u{AyiS4kg`|eFxj= zW1#@X2kz}PBI92qI2E(*B2*y)Ji#b{JqC_&1}ls{2zZi#z5~(LT!mW-$E6uwB&K9w z5{lzlOLo#booZ?B&{3d9c51C+*F-zG@9xWjwwp!@JUY`GrpaSubRPIu%W$llV>o!5 z_LiZ=)*wNM#AW)x5`<~Xk)GHVGSpbtRrFl$gFjtvjU@=vhv-}eYNcTErPsr>Q2yXB{9{_GZr{2%_gne&dnZo+@E2 z#bH-f*K*g*&++R8@Lg?vRqxC#n^8dKvKeb@jdque{sD~q3tdx8BCtB0BIKvPuZV^u z;z)}g>0uhMHfA0eKd&-7Qt-ST53gcfOyA_(f^=-|JrMS7X+u6h9|%iHA>vAXg2$P;lv!;a(S) zou@bokFFC|%#46n{2$DUS)faw00(OGm!WvQxHYWzjWV>@2lzHIv&H%@dKl)u!g~yU z_;ZmF?tj@szlAI*^ojOXdKd14X^s2%d0`&clX zqbT)#kdDw|6s$U#r9yS6^c|N|@netXS_1-U(ryT-%$>2MTc-^x{C*d+@!9r4b_RhNEtLLMZgKHfEjlh#uX5cLGcZmAPnUEisW@PJ<-)R zHP#)-JO1Gfg}nHOat%*Iq3vMKcx z=+q!D6X5s6)*ZWB=FNKjF&?)eAL|>9uFHAHl{LtwGI$idmyeELG40P^y}o6)$Fn_m z_UEQkyYAdDaQQ-v=0`lq5QkUil!cnrXwg5_>4}+b;Z0+$i>dnRe{ZN~N6nG($eK&r z+T7ONaM%3P*n7a4CnopgT3$3~0NH#Urb|Hrs?8Q2E3?$c&AwR;#3KA)gqXD2S zf}cMTVNwDrVgXEMfTTn@Y@%<dc(fr;l|Kz5hw`Z29r>6^+#dQuu?w8a|A+vrPV zo~Sj^>ef+|*I{k3!HeRpa2p3$F_%&{u(g46b`U>YWrm=MvZXeq>K)?rTqLnEhZ-(s zXFhli-3S#kJ7(v1;{3a&<@dpRzQl^-2nf{p!>7>RZ{{5p$c|K`MqQJ%~idXkbvBHHWd)nsCFg-g?()_T!( z;g))7!r9y*sc#cX3}D0RBoc3T7y*TPQ{fDi?mAUF{_+5 z3Ez+RF9bGHJw)-dCBz5O z2w9?-@hmyrycL&>^ab??CN`$S4zJT2YAKFag8}&8d1XAoR%RyWT=h6YyBp`c*P3$|!`h@IDxF@z_?b?OeELc;Ev3U;o)&H`H zYBr&5XdVPyJ$=_Vs{#W2N#x{x54q!U8k9)HngyNnZP-Z*^bW*zT#_r^K$#`RoWMjZ zf)?x@Dz&;*6Yn%zIwIbjL6dT{HBAPCQ>k|93M8)Wt`YfWJ36M^;=2C2dTL=~b@RUI z*#qh7F-$k<ApgAQXS7?auoN(Qhyxne1? z#c19ZX&rL~CQ{khq*Y_=ceYOXwyeu$o8r=tH*57PBz|WIiR;HjN_4Ct&>jfoUF@kz zB&+YKldxZwcrxMqWXn6nm-|d_dU&sPdTK zN)_;d*dN85bq9EU?NQheeC-7|vMvZuxop~Ahb~5FUy<1`USZ;PAeD z{ZJRA{S4OS4^TglE4%8|ieOhTSJO~aEvwBXhuo67fjUWg|6ooas;iapawF%q!mNxS z6cjKkyi3JdL3j}+gGCn~vdd!FJCD84Akxr4V#;jLce@&!zxhpgI}y5suGV?g^Yh_3 z>IX`&X2P(u<0FENj`d@ufc8mdf=lDd&)2MA;lAa_u2K_yDfatndn43ItS5I|~|cVGxDBNZ(_1BhVhccjgu zpy_r&UNMjL*L#7Qm!AbMX?FSRSf`zYU9$jI5|J!NWoigkiG|c;z9Es63&gQT8J@)_ z!C$-)3jV+zy%GQY5csP{S@rOD{Z&{B-$I>(9-@8>y@6T<{F-4Dz?BmfpKsAhBwGCQ zRSok&L*1`dDAX$Wukah6GbgO(j}(pbHlQk7A+vyC5xc3@WUFryyIrPgik2s$5&>N1 z?-XJ>tZWbV2eXntR-0X}hE%=9T)T~WSLpOOgaS!4A&0p_dvpWoA6I0To`8347M?{n zROuZKaad?H)XYCp{H(8?r{1k|dJ7%Zls__aK<7iBAn%wt!@&ecl~fCMd3xL}Q9V59_G6uumWAz_3J&knyn(7*jx_}c7J5<=eiu%dYr5wOgZV5FvhXUD5P-QSGl|~)4HW>~l5}`;ktXHe` z_@}Uc=vL}yID>FB&BoFU3ph|#aONWRZ8_Outz9X1*)1Z4iXY}BLH}@A#7ZTfTB{G3 z8sJUhI$bJHegUN}QSYI@u~WbkSvg2$L4Tup=pWR3AWY#D0$siW76hEaNum{(9XoJe z3FKH0R8{MRR(s6uNvV}i#dX0Ln@Uh@iAt^HPQcX-W-@D_=x>3hE5$t#HIOOH z$6Nvz-|krvwhWrhDLBsUx)V-aZ@XgcI6pDjJ1XN9`^kQHh zM^K!j(ANjKHPw7-lKQWPc%xh(w7VRzg^2Ye&}j;23QlO@2{@rB1*ele$yJ&fLe!tc Mv822jKgIh00H_hAWdHyG literal 0 HcmV?d00001 diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 34dc32ac6b..c8b3ce34eb 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -7,14 +7,16 @@ using Avalonia.Platform; using SkiaSharp; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Collections.Generic; namespace Avalonia.Skia.UnitTests.Media { - public class CustomFontManagerImpl : IFontManagerImpl, IDisposable + public class CustomFontManagerImpl : IFontManagerImpl2, IDisposable { - private readonly string _defaultFamilyName; + protected readonly string _defaultFamilyName; private readonly IFontCollection _customFonts; private bool _isInitialized; + protected int _tryMatchCharacterCount; public CustomFontManagerImpl() { @@ -25,6 +27,8 @@ namespace Avalonia.Skia.UnitTests.Media _customFonts = new EmbeddedFontCollection(source, source); } + public int TryMatchCharacterCount => _tryMatchCharacterCount; + public string GetDefaultFontFamilyName() { return _defaultFamilyName; @@ -39,7 +43,7 @@ namespace Avalonia.Skia.UnitTests.Media _isInitialized = true; } - return _customFonts.Select(x=> x.Name).ToArray(); + return _customFonts.Select(x => x.Name).ToArray(); } private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName }; @@ -47,12 +51,14 @@ namespace Avalonia.Skia.UnitTests.Media public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, out Typeface typeface) { + _tryMatchCharacterCount++; + if (!_isInitialized) { _customFonts.Initialize(this); } - if(_customFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, null, culture, out typeface)) + if (_customFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, null, culture, out typeface)) { return true; } @@ -99,5 +105,43 @@ namespace Avalonia.Skia.UnitTests.Media { _customFonts.Dispose(); } + + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo culture, [NotNullWhen(true)] out IGlyphTypeface glyphTypeface) + { + _tryMatchCharacterCount++; + + if (!_isInitialized) + { + _customFonts.Initialize(this); + } + + if (_customFonts.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, null, culture, out var typeface)) + { + glyphTypeface = typeface.GlyphTypeface; + + return true; + } + + var fallback = SKFontManager.Default.MatchCharacter(null, (SKFontStyleWeight)fontWeight, + (SKFontStyleWidth)fontStretch, (SKFontStyleSlant)fontStyle, _bcp47, codepoint); + + if (fallback == null) + { + glyphTypeface = null; + + return false; + } + + glyphTypeface = new GlyphTypefaceImpl(fallback, FontSimulations.None); + + return true; + } + + public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList familyTypefaces) + { + familyTypefaces = null; + + return false; + } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs index 5c830df4fa..81653f4a9e 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/FontCollectionTests.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using Avalonia.Media; using Avalonia.Media.Fonts; using Avalonia.Platform; @@ -115,6 +116,61 @@ namespace Avalonia.Skia.UnitTests.Media } } + [Fact] + public void SystemFontCollection_Only_Calls_Platform_TryMatchCharacter_Once_On_Success() + { + var countingImpl = new CustomFontManagerImpl(); + + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: countingImpl))) + { + var fontManager = FontManager.Current; + + var systemFonts = fontManager.SystemFonts as SystemFontCollection; + + Assert.NotNull(systemFonts); + + // First call should invoke platform TryMatchCharacter and populate cache + Assert.True(systemFonts.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match1)); + + // Second call should be served from cache and should not call platform TryMatchCharacter again + Assert.True(systemFonts.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match2)); + + Assert.Equal(1, countingImpl.TryMatchCharacterCount); + + Assert.Equal(match1.FontFamily.Name, match2.FontFamily.Name); + } + } + + [Fact] + public void Should_Cache_Font_By_Normalized_Name_When_Platform_Returns_Regular_Suffix() + { + var impl = new RegularSuffixFontManagerImpl("Default"); + + using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface.With(fontManagerImpl: impl))) + { + var fontManager = FontManager.Current; + + var systemFonts = new TestSystemFontCollection(fontManager); + + Assert.NotNull(systemFonts); + + // Call TryMatchCharacter which should invoke platform TryMatchCharacter and add to cache + Assert.True(systemFonts.TryMatchCharacter('A', FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, null, null, out var match)); + + var normalized = fontManager.DefaultFontFamily.Name; + + // Ensure the cache contains the normalized name (without 'Regular') + Assert.True(systemFonts.GlyphTypefaceCache.ContainsKey(normalized)); + + // Ensure the raw returned name with ' Regular' is not used as cache key + Assert.False(systemFonts.GlyphTypefaceCache.ContainsKey(normalized + " Regular")); + + Assert.True(systemFonts.TryGetGlyphTypeface(normalized + " Regular", FontStyle.Normal, FontWeight.Normal, FontStretch.Normal, out var cachedGlyphTypeface)); + + Assert.Equal(match.FontFamily.Name, cachedGlyphTypeface.FamilyName); + } + } + private class CustomizableFontCollection : EmbeddedFontCollection { private readonly IReadOnlyList? _fallbacks; @@ -174,5 +230,128 @@ namespace Avalonia.Skia.UnitTests.Media return base.TryCreateSyntheticGlyphTypeface(glyphTypeface, style, weight, stretch, out syntheticGlyphTypeface); } } + + private class RegularSuffixFontManagerImpl : IFontManagerImpl2 + { + private readonly string _defaultFamilyName; + + public RegularSuffixFontManagerImpl(string defaultFamilyName) + { + _defaultFamilyName = defaultFamilyName; + } + + public int TryMatchCharacterCount { get; private set; } + + public string GetDefaultFontFamilyName() => _defaultFamilyName; + + public string[] GetInstalledFontFamilyNames(bool checkForUpdates = false) => new[] { _defaultFamilyName }; + + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, out Typeface typeface) + { + if (TryMatchCharacter(codepoint, fontStyle, fontWeight, fontStretch, culture, out IGlyphTypeface? glyphTypeface)) + { + typeface = new Typeface(glyphTypeface.FamilyName, fontStyle, fontWeight); + + return true; + } + + typeface = default; + + return false; + } + + public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, CultureInfo? culture, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) + { + TryMatchCharacterCount++; + + // Return a glyph typeface with ' Regular' appended so it will be normalized + glyphTypeface = new SimpleGlyphTypeface(_defaultFamilyName + " Regular", fontStyle, fontWeight, fontStretch); + + return true; + } + + public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) + { + glyphTypeface = null; + return false; + } + + public bool TryCreateGlyphTypeface(Stream stream, FontSimulations fontSimulations, [NotNullWhen(true)] out IGlyphTypeface? glyphTypeface) + { + glyphTypeface = null; + return false; + } + + public bool TryGetFamilyTypefaces(string familyName, [NotNullWhen(true)] out IReadOnlyList? familyTypefaces) + { + familyTypefaces = null; + + return false; + } + } + + // Minimal IGlyphTypeface implementation for testing + private class SimpleGlyphTypeface : IGlyphTypeface + { + public SimpleGlyphTypeface(string familyName, FontStyle style, FontWeight weight, FontStretch stretch) + { + FamilyName = familyName; + Style = style; + Weight = weight; + Stretch = stretch; + } + + public FontMetrics Metrics => new FontMetrics { DesignEmHeight = 10, Ascent = 5, Descent = 3, LineGap = 0, IsFixedPitch = false }; + + public int GlyphCount => 1; + + public FontSimulations FontSimulations => FontSimulations.None; + + public string FamilyName { get; } + + public FontWeight Weight { get; } + + public FontStyle Style { get; } + + public FontStretch Stretch { get; } + + public void Dispose() { } + + public ushort GetGlyph(uint codepoint) => 1; + + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 1; + return true; + } + + public int GetGlyphAdvance(ushort glyph) => 1; + + public int[] GetGlyphAdvances(ReadOnlySpan glyphs) + { + var arr = new int[glyphs.Length]; + for (var i = 0; i < arr.Length; i++) arr[i] = 1; + return arr; + } + + public ushort[] GetGlyphs(ReadOnlySpan codepoints) + { + var arr = new ushort[codepoints.Length]; + for (var i = 0; i < arr.Length; i++) arr[i] = 1; + return arr; + } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null!; + return false; + } + + public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) + { + metrics = new GlyphMetrics { Width = 1, Height = 1 }; + return true; + } + } } }