From 790941ef91121ab43581ed874e9208385c1ca9ab Mon Sep 17 00:00:00 2001 From: Matthieu Houdebine Date: Sun, 22 Dec 2024 11:10:25 +0100 Subject: [PATCH 1/2] PR 500 --- library/lcd/lcd_comm.py | 77 +++++++++++++++++++++++++++++++++++++++-- library/stats.py | 11 ++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/library/lcd/lcd_comm.py b/library/lcd/lcd_comm.py index faffcfaa..11297c62 100644 --- a/library/lcd/lcd_comm.py +++ b/library/lcd/lcd_comm.py @@ -420,6 +420,29 @@ def DisplayLineGraph(self, x: int, y: int, width: int, height: int, self.DisplayPILImage(graph_image, x, y) + def DrawRadialDecoration(self, draw: ImageDraw, angle: float, radius: float, width: float, color: Tuple[int, int, int] = (0, 0, 0)): + i_cos = math.cos(angle*math.pi/180) + i_sin = math.sin(angle*math.pi/180) + x_f = (i_cos * (radius - width/2)) + radius + if math.modf(x_f) == 0.5: + if i_cos > 0: + x_f = math.floor(x_f) + else: + x_f = math.ceil(x_f) + else: + x_f = math.floor(x_f + 0.5) + + y_f = (i_sin * (radius - width/2)) + radius + if math.modf(y_f) == 0.5: + if i_sin > 0: + y_f = math.floor(y_f) + else: + y_f = math.ceil(y_f) + else: + y_f = math.floor(y_f + 0.5) + draw.ellipse([x_f - width/2, y_f - width/2, x_f + width/2, y_f - 1 + width/2 - 1], outline=color, fill=color, width=1) + + def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int, min_value: int = 0, max_value: int = 100, @@ -436,7 +459,12 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int font_color: Tuple[int, int, int] = (0, 0, 0), bar_color: Tuple[int, int, int] = (0, 0, 0), background_color: Tuple[int, int, int] = (255, 255, 255), - background_image: str = None): + background_image: str = None, + custom_bbox: Tuple[int, int, int, int] = (0, 0, 0, 0), + text_offset: Tuple[int, int] = (0,0), + bar_background_color: Tuple[int, int, int] = (0, 0, 0), + draw_bar_background: bool = False, + bar_decoration: str = ""): # Generate a radial progress bar and display it # Provide the background image path to display progress bar with transparent background @@ -449,6 +477,9 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int if isinstance(font_color, str): font_color = tuple(map(int, font_color.split(', '))) + if isinstance(bar_background_color, str): + bar_background_color = tuple(map(int, bar_background_color.split(', '))) + if angle_start % 361 == angle_end % 361: if clockwise: angle_start += 0.1 @@ -500,6 +531,23 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int ecart = 360 - angle_start + angle_end else: ecart = angle_end - angle_start + + # draw bar background + if draw_bar_background: + if angle_end < angle_start: + angleE = angle_start + ecart + angleS = angle_start + else: + angleS = angle_start + angleE = angle_start + ecart + draw.arc([0, 0, diameter - 1, diameter - 1], angleS, angleE, fill=bar_background_color, width=bar_width) + + # draw bar decoration + if bar_decoration == "Ellipse": + self.DrawRadialDecoration(draw = draw, angle = angle_end, radius = radius, width = bar_width, color = bar_background_color) + self.DrawRadialDecoration(draw = draw, angle = angle_start, radius = radius, width = bar_width, color = bar_color) + self.DrawRadialDecoration(draw = draw, angle = angle_start + pct * ecart, radius = radius, width = bar_width, color = bar_color) + # # solid bar case if angle_sep == 0: @@ -533,6 +581,25 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int ecart = angle_start - angle_end else: ecart = 360 - angle_end + angle_start + + # draw bar background + if draw_bar_background: + if angle_end < angle_start: + angleE = angle_start + angleS = angle_start - ecart + else: + angleS = angle_start - ecart + angleE = angle_start + draw.arc([0, 0, diameter - 1, diameter - 1], angleS, angleE, fill=bar_background_color, width=bar_width) + + + # draw bar decoration + if bar_decoration == "Ellipse": + self.DrawRadialDecoration(draw = draw, angle = angle_end, radius = radius, width = bar_width, color = bar_background_color) + self.DrawRadialDecoration(draw = draw, angle = angle_start, radius = radius, width = bar_width, color = bar_color) + self.DrawRadialDecoration(draw = draw, angle = angle_start - pct * ecart, radius = radius, width = bar_width, color = bar_color) + + # # solid bar case if angle_sep == 0: if angle_end < angle_start: @@ -568,10 +635,14 @@ def DisplayRadialProgressBar(self, xc: int, yc: int, radius: int, bar_width: int font = ImageFont.truetype("./res/fonts/" + font, font_size) left, top, right, bottom = font.getbbox(text) w, h = right - left, bottom - top - draw.text((radius - w / 2, radius - top - h / 2), text, + draw.text((radius - w / 2 + text_offset[0], radius - top - h / 2 + text_offset[1]), text, font=font, fill=font_color) - self.DisplayPILImage(bar_image, xc - radius, yc - radius) + if custom_bbox[0] != 0 or custom_bbox[1] != 0 or custom_bbox[2] != 0 or custom_bbox[3] != 0: + bar_image = bar_image.crop(box=custom_bbox) + + self.DisplayPILImage(bar_image, xc - radius + custom_bbox[0], yc - radius + custom_bbox[1]) + # self.DisplayPILImage(bar_image, xc - radius, yc - radius) # Load image from the filesystem, or get from the cache if it has already been loaded previously def open_image(self, bitmap_path: str) -> Image: diff --git a/library/stats.py b/library/stats.py index 0617ff2e..191fa0cb 100644 --- a/library/stats.py +++ b/library/stats.py @@ -177,7 +177,12 @@ def display_themed_radial_bar(theme_data, value, min_size=0, unit='', custom_tex font_size=theme_data.get("FONT_SIZE", 10), font_color=theme_data.get("FONT_COLOR", (0, 0, 0)), background_color=theme_data.get("BACKGROUND_COLOR", (0, 0, 0)), - background_image=get_theme_file_path(theme_data.get("BACKGROUND_IMAGE", None)) + background_image=get_theme_file_path(theme_data.get("BACKGROUND_IMAGE", None)), + custom_bbox=theme_data.get("CUSTOM_BBOX", (0, 0, 0, 0)), + text_offset=theme_data.get("TEXT_OFFSET", (0, 0)), + bar_background_color = theme_data.get("BAR_BACKGROUND_COLOR", (0, 0, 0)), + draw_bar_background = theme_data.get("DRAW_BAR_BACKGROUND", False), + bar_decoration = theme_data.get("BAR_DECORATION", "") ) @@ -313,8 +318,8 @@ def temperature(cls): cpu_temp_line_graph_data['SHOW'] = False display_themed_temperature_value(cpu_temp_text_data, temperature) - display_themed_progress_bar(cpu_temp_radial_data, temperature) - display_themed_temperature_radial_bar(cpu_temp_graph_data, temperature) + display_themed_progress_bar(cpu_temp_graph_data, temperature) + display_themed_temperature_radial_bar(cpu_temp_radial_data, temperature) display_themed_line_graph(cpu_temp_line_graph_data, cls.last_values_cpu_temperature) @classmethod From 6ffab0440cd463c08b2d23f112017cc483d455d6 Mon Sep 17 00:00:00 2001 From: Matthieu Houdebine Date: Sun, 22 Dec 2024 11:11:40 +0100 Subject: [PATCH 2/2] Add theme --- res/themes/AdvancedRadialsTest/Credits.md | 4 + res/themes/AdvancedRadialsTest/background.png | Bin 0 -> 1382 bytes res/themes/AdvancedRadialsTest/preview.png | Bin 0 -> 9786 bytes res/themes/AdvancedRadialsTest/theme.yaml | 306 ++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 res/themes/AdvancedRadialsTest/Credits.md create mode 100644 res/themes/AdvancedRadialsTest/background.png create mode 100644 res/themes/AdvancedRadialsTest/preview.png create mode 100644 res/themes/AdvancedRadialsTest/theme.yaml diff --git a/res/themes/AdvancedRadialsTest/Credits.md b/res/themes/AdvancedRadialsTest/Credits.md new file mode 100644 index 00000000..06895998 --- /dev/null +++ b/res/themes/AdvancedRadialsTest/Credits.md @@ -0,0 +1,4 @@ +### Credits for graphical resources used from freepik + +Acknowledgements for resources : + diff --git a/res/themes/AdvancedRadialsTest/background.png b/res/themes/AdvancedRadialsTest/background.png new file mode 100644 index 0000000000000000000000000000000000000000..fbfa294383387c5fefbc7744a5a1bfb88f39f70c GIT binary patch literal 1382 zcmeAS@N?(olHy`uVBq!ia0y~yV0-|?4jgPik(3kbtAP|_age(c!@6@aFM%AEbVpxD z28NCO+3W}Nhsc_~oMZ%-G;kczmsR}bj0y?|7z6|o7&tf#7+6>&7?_xN7#K&T nM}uWFIgDn5p;9`mnf;T2V+C`4(~(9{iOS&V>gTe~DWM4fg=o$} literal 0 HcmV?d00001 diff --git a/res/themes/AdvancedRadialsTest/preview.png b/res/themes/AdvancedRadialsTest/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b668a8007b06fd407237d86e2ec1df1ed8a4e5 GIT binary patch literal 9786 zcmbt)g;!Kx*zOD|sdR%NBHa=L3?SV|cgG+S!qDAHO6R};64D6LF@&OYNY@N19YYN* zm*4mO1NW}G*E;L0bJp4W?DekaecpKX2VHFy5<+@H002OurmCn1005B>m)s+)hc|mw z61)Ha2T)B>-oU@$U^SqCa<=>8SZCrp0tpy++TZX2POe}e8o#Ob%0e(8k$&FdIs5Am zuhVn(LfDiA=5uK6SM1o^O36fXERu-c)3-v1@k)`@eI!b>S+*1(9ci7)i)2Bwm`NF# zRm){b!}W^IT@tdLoucnrjxyqH^8dLjX_ag&93|!3hdhEk?M)EiGW{2uV_j%=dW`lZE zw&G7Yh3a1D3s?&A*}?pY-{GW>^UsmXVcXvlJh!W^G@gtvBjzqs+9%&3G}|diSfe5{ zR}_b5D!^ufp4r84lD^{%C4{3$KeS(^pFIDzu%o=sm?|~KzXIj=$&X!l^Q@y-_325R zm-8#)JqRvz`)gASFiF^~q^{XwoH|%fg%e(@wMFUJ{C=w|ep6HaJJbLRbO!x?7_Lmm zH?^T6>P*N{{_GvK%JyO`{xNkRI1kMq@85p}C3T`wERGaoHy-WGw{&@RU-0DJvz(RD zKdxME0l03jKA}vja-Sr}c9pU{e*}izS$!S@x~XRQ(^o$jaXLC}JhXmkiItQ>Z!qVG z{Va-rg^CTAPzW}2OS{+56iFIpe#vZ8yj4nY)Tqj;sp@>|5*q_#x^6L{)eYt@RqAk1 z1yyL$iy)S9m#8O;JrZp6_38N1;$H7ahoHE-#v#@2S(GDEMxW*x=c-xk5nIvkSwIXg z`B^XYclIT#Ny%f$U&qFzuVJTVq>XI#r)*NS#qUEg-1P?p+sO2vNR#P&Rw0@{Av-7a z)lIw@_EJ8&jYkq66Lm@ru%m*FMJHK}yuC>+cH1TGmnu?|L>agtW!_t7YpTRl=HMWo zSg*}Mq7P0N$8O7}j6Hw$jPT4%j;)bHc>?zG zmgEbc|1<$0&_MlW=vgaoN3$*cjfug-Z(c9S8Rwb`ab!ETdn0#a`AJD;;5IabyX2b# zFxb{c@Y$4dvOEv0yJ2!mTra!sz0x?i{9pNLoRfC@#o~Foy9J>^){9V~MpWWEpY9+d zUbB{v;ZdrIt`^Cc6SzYcz1tZw!{oP{#pQb}7EHlSTD9;yiVEEiGp?E2-rh$Y5h2y# z7siK|LmPab!;Wg-YTxbdHsM+N+uaWj4>D-9G6{AK4P+AbG?6(M%B!aZ=ErRg0!`I>A*#)mg7*)yeUOke>jeTTVC z%AE-^%7(q{0q6c1q3J$d8{TvsXNLA2--V&iS0`YYDKv1EoWM!CyY6e!{lCp{GVG2d z+Q|b~{EO=y%cRd++SYBurwnpOY}!;%izfEEdh{f~&+`T+1|#{sg>*Lq#RMw6 zIYTV{38pU-@ZFaTe&haucP}5;xV!3LK7R&?dARC~wY+m>nDlEUMBZge@%L1}JK+jq zC)dnv-}p3e^Y-$S+fuU;lVtJTiT)w^FE z8i(%0@I{U!`I`(Db>}#pqi8Q{Vq>eUZA<;ax{&otPbtARYpMpXJ8ihJR=}=tx9e~O zNl~*C_-_CBF81*@CLcfF{cdXVVehx!YBFgG7w)&QU9lnUYCt}=ji|o6Dx(Py{J8M$ zVq2ep5nT16A=r%w-M7nRp0^x#R!uM2Krdl;Z)VeXvphXfdt~_TTPp*{`q!=&l>o;8 zyG@ZjkD#|&L20QZv%uz+99GiIi3qd{Tp^C|n}bHa?`VL_?{{&S(7)d&c&&Qw8eYpp ztdF^4wH!ctuclt6>n#4ha>WExGzRSV$e1suiOXK^q#_hBb-AtE={JSTy7NtT&gLf% z4uPLi!==&SFq;f8b058f-@PYsRDN;hl#sNt!u46D^~=aOt^X3u-foa#a6{nGFO|H* zKrhQ(upn!`FI8p;=b>-_oq7VdEx#8B|16rv&=I%n6-WUu1&|pvAJU9 zu@R(Ap`Tn``KDv~!BRX|_mx(}eC_`ZwnlZaW#g0@aE%5OJXOEX_(kQz$&3(glOy#s zsXH>No7WeMi5&OjYcWg>%$tZ}ru<+Y(LVYdE4lQSoUBuVA*d>Z9Vpx#YwMAMHPP8~`CU_ymcuTg-!KBK4CpDSL$D$?! zJ#pcWF1`N<{)osx*Rh}Z?zfelCTh=@==9ZCh!h<1qC`#b5r7<=C}aYf0~+^jk`UoV z3pn?bPK^1vRv{wzBLL05lJ6Qzpq;KW3^|b$r0v1+kn=OinEDf;`*=Nk0D1pc-Pr)6 zPFdFft2&1?G*JpKJFsHLU8!z8OLCy&Nb)uQEfWW@LH3rUMFHF3l@xI-HD{D{KLgzW zQz8An^bfj!j)L^zej<6dSOwS!AX@ruoIF#$1*vi0p5SaYM?%iX-|k`sX<2qWAWXaS z6zRK7G=c(OzHCOg>k=TY6DxxErMIyU&`&kPY2Z|N2y9QPv)JD-`q@^F`zMFdfhKrO z$l>w)uD$?W`~x&VGdfI;hdBB^rn*@C*19*3*DhkTa*Smz*5a1Du%N=Q?P@-k2b6nH z2H1tM6pi~23ylx*04rn6A8^$VLz6%v@^6D&FAZN*c#ity6UA4b9z&Ou4>G?ecSw1fHB!1tlAdPlp2ujgpSK2Yy76|)HxE8Ivz``8)wE>4e z%1dB9KMDo9Br3ovMLIyBkw-cGSb^Cb0h^B-hZO{?U%#;ynL1QRD>BSr7? z7!)E5d$=2avvUJs6*c}u;MlUsvD~yC!$Eb`{yjiq^eSv8PU^AMQCsC_T|C*yUsEw< zfST}@y1E#yX{iQWdXc<>o+9U}pY^~7KjrjZq_ltS?-KV&VTpB@lQ@9rD|6%depUHb zkB282o=#DY0z^F%L?$dG=t3W9jb~s3FN;rquOst4x!y)mn@qpU!B9dXMgW(*%?Jv> zSse9cMfTCy!MN(7HPWCBASR)&$HGq|A=~q)Vt{giK<%>9K3yk@{gyF&>LXNn=|(|# z@5DVxo~G~g8E4-<5nBvljXowy+)L(f_Be4SI(W{b)yYB}cEW;YG{Dv+B`U^cj+-yW z<;R?K@8BnRm8}`lX%%LlFH>K>8TwlQ44Z(QFno-zK=P^fKWB0_lJny`LmTR8$VKia zycEQ>iqUGoD~wHVa%x*?H=U->S)HAC1r8K6Nr0oR{ll8S;_*j`+cE?bPxO5@6|q3A z@b1|A6O-<}$qnjfTzE8s8m{5sfu9n{xKYhCQm*k)NFRYf9S4zHsr0Eq?-aR^%~2+t z-1r1aT~ysCO-k#ZY>IS=m^}OdRFC-m&E=OZ!79AjO-ZwNy|u-JX6y=oX6SP2^_J+X z;J60z6e*DL3=nah-qKs4wqNG#y=_X~aO&f$X%kJq{Np$qb zf|=Zq57i6G-rC${NtADJYhVB7{9+F<+tIA6hv{><6G_b%fVID)r|h=LUX^+O z%$O=Bl!7bWmP38j=$Hy0;;rwG{z1E71sH1zzeiwUc_QftAh7mza;2bqu)@wht0R<_ z_z^@FGO@3IHsX99aZ4yrG?OtEkpPj?80CV6lq8a7358`|H>$XxKT(8cOng6MZ<=XG zgLn3-Ig5kGKe?Jj+KsxGg_G)LU@uidF)jFk=hGJD-toeqC*}J_p$Epp+q~c;FvCtjypr2mvS;x*lz1OBdjymw#+nU35QA^KICDbE?FVCxZKNin~O{@r@k*(+)bOzo|(!io!5`0~Pac$Z%4d7a#1D@js4}2^2jm z80QLge)!W7E#qV# zZ6i8qOG^|dLA+QW{I5V4bTjhN%7Ok{r7bTpByq9xmab6Dp=L1yD)#4b`CA2~mp_3~ zAJln6QoH`gl?NWc(G#~O+7I=o<{N3$9+GbXXAkM&2{ynxFS#vE-A*%ki3j5vJl?i! z_P>g-eI;tI4Z0b;!+?m~qus|-aC@xHftO&-SaVy>73>7tRFQ!xPnKUGS>L!uL2B(d zd)uZxCmOrr{$Ee$@I=W*A!SWW~VO;7@8A6aG5qz zo)A*CbsCbqk$n6m1&2clY`ih}k2P6T;X8=e0SXH0G_~Zf zCEkVMd!q~_Sd4&zpn3VO1i%8JUx&E|PGvB^ngRDVCB-genHDyVr`~PNf>0Z00gu>{ z0#y2$=)6O5Nr5(aZ};Cc<`>|9z1k$qAf=*z`icq~zq@U>kUDPVrhhTwGVy-ttl?Mn zcodk)x#vf~@DZWO3CIK+@pQ`~xp~iquO>!6!L82KDzlrrNQ0_99IuLU#!_P;ZM-~y zE}T{HN3IwA^yw*M8#am)u-Tv1pH%=df2de8gc_-}W2`1MglJS!*VLj|4jcYFEzUGw z{X2KID{|xS4+szIJDfD{lB zap6b=X-Hk0&EGn+=x~vx*zgwr^z5#e-FFFZ=nK+c9`j4g2S{W6!*xV+Gn$R2>4XP~ z)IHS+V(Q7;6YW7{6ilf*`|+-sCp^*AWC&x1DvJ5OHlL~JtNY-|zmM;R7HDJo_wjgn z8L0{AA!>@ZI02(6B(Sa(M0jIwP+|)x!x>9X?sBRztM@;^%U=P)OZ(x;zq0r}U3Yb( zw+R1p0u{Ee%f4g1IbxSiYOmM6ONv)|NdhF(&D!_BavpWTPM(z~5pSEgTc&Qm-*EA@ zJn8B`fkdorex5cR^gUinkl`xpP7b>8fp&>ksX`&vVw-8zvuc9I~KnR(u%OmYEpT@-WvYufY^Sj2Ebh3 z5$wYjsCMk?$O{cfMEw93jLOa0L1v7f&bPrEvW>C*Gm@Q74cM_g#9E zT6(M$N1y2S=B9^DU)&tgRpsb)We-@o!XUrBCQSXGuD)5ETG)ptlEJCTs81pReE{Mg$*+ z&w3IQM|8s(YJ4}l7Sy&}k#^1-J`IoPk+R5*nwSJCQ9Qo#(>G5f&-%7K6i5Ks4rSij z6gH0lz3<8`2r|)R(nJFN*yCsEKuBD*NMR4%Cg5YZK=v=2dc4$^w3fmY|Mn?<4h`6l zw07Q8fFB>Au_f@q*!d{3ORoQ+7mq$p6Vid=+1xdn9 zmb?j?GdUWu((lMHq?{B=lg*H#a5qumfRDguO>Y=4WzM2TYyp5R(7>3>DN1*NFv`Y$ z?Kuzp8E+vz0ntv-)Q)!9UoXte7*{o4rux=%&&bohCSqoMWMwii1c#9janz~qEbLS{ zCX&9u#qm(5zd;PY^`aY-#ATdG{S1#tDHi9AAREtEEhY$eM*T>XEQ&5IX2L{V5Se&U z?321p&eNr6w*$vWGyS&{YTNxg|;s&M8NK8Y18I7Zg`02Aek?fi`9XR*V$AXl2 zQZKs8#Pv2MJ?)VYz$RtZIO1&%&R?|sWsJxJ?RabVnj>2d$dfCLCPRxB?4i?MfzO#sFX;4@1mIE9Thnd)G@7E$CsO65l#>WPPxraO9kk5Wv8q4AunjAt!2|n5 z-w6N5J|xM*KJ?bigb;53AQ~2j0k&gVlv0MdYqtxSWBLSkWxi>X`VNe z3j`*(0@XeiC=KaQl)|!H_f~hFSqK$KMn`ceNF)id!j7Bhz|UQ_TQ>!h$W~QBUqEn@ z{nzs1KR-gXg{p+?9%W9rFEC7gcan#bh#Ukv6bYLtHLr`d*>Y&$ZzO7MHOmE+w~8-% z(tQDkI9<||XB}cQpLqD`$#pu|Z;8sn^dk=oaPeGGKr=7Bo7AZuqfW6yJgZ}$|M|jl zqWL*Ye{^_z+H0mD2>JOvCTiF@eQ{RgKz)nSI&ddvoCYSMvpVG1DCA}=XTMyCq{^I# zZK^a6T+Tl6D||X{*lbCk-F;^H&@-qpP*zEtUJPz2_=YmHHru{Eb!fr5GQUjuX(sO^ z9GH_<4inmFH-Y`K{cT+;66saZNPQPu(Jo-L zKjM~27n^?wb=68Wk)&xL|@7De}>sRlEkyu5j&$($7zjR}mqvJ+W z*)l2Z+Hcf|aw78_6Rl*!^@A?3dOWyf&SD+(*N+~Q zChP=rv?*%%pkh4801+tcj_2f~{(-BOVW#+r5AFZx#_nEU&-(1O#XK{o?NUY@s|B-+ zDWHn?r6p@gBGX3OVt{9;?P=VC0(GACXVG8Az-qDm702t)fevHiOg^`z|J4IdAH)z@ z|08;vpH-UvNo*X9cI+8XM>ow>KYj>!aEfSN3SqHBeWVybtUdfg4@tyg#sD;?JUEQ*j z-L2GEcX4gqt`ALcDPsY4mIT>USxWYO@re;0$hZ|AW|-wdyjyaCh{R{d$(C3$Mz&o>5nFU_)NyH7;XWL{kd*lrcRIGHYVDYdP0--&=-@{<gy8`(DQ~*{8x_U zRW9{*W;=`k`-u(C0@l01k4}Fcgo@c+Q+(02rhDcP?uY@EbDXStHz3%g+g`)_=3}Vg zGkha#m3?a>UjH!j>-O_*?z&6gfj^yD^BB&0UY*fkr#rWlS{EJRFU6n&nsnpsQW7q; zFzu1kH;qIcg=Marz88?j*2t_^4^R=>DoYw-TDr+(kZtn6!VD;%xhq&s#KYWvihQ4ClKxW8+m~i2 z^(sgvAjV3?dDvYI#lqUtLzq3lq`rlq)$?{34u}>^Y3yk(q5M{eG_a5r2;Ny7Ihd;5 z|004{i22rWZAHaWvnF(&THaY`k$r9G7_hNL!N8GNU0q#k(p7WyCCEs+(MEs#TjJg_K-AUT@U@tH8`eRGvFt$Y6q{k`!A zy$u{y<#GG(W0W$+r1SH6xumOmAXKY$cyP3cSoRvR)XI>rbAO(eGDqpNT<(W1Zk}hnhT1UcGw&8B2liM%BILxy}|Ls4?=jJ<}8%N@~@G zb$w?zrtegzsv~(TB6C4Md63oa%CWvE;c!vqcSI|L;t9FGyp3i5IoIM4l%^QT!ZhDo z-m(AJ&rN*`F#e@wgOp)-`!=5bgApi>jUNb8ot$iaGlFSx6agE*PW8Y2t3|Wc6AbC) zMh?vKAx^&X9jL{gA7K`F@;AG`{>}VcWZKnrRbLL9x}2}kimjF=C#)u%TT`bl+S*)nUHx3{!5$v- zhf zSKYO80j5~0?5gbK%jV$j_`@_0Qj@Q?&aUQOi-{A4887(n2ZonftQpjQe|zo-{h2Y2 zibuq7<1TtYE_P&@eXrX<@G7>~S@06+#^%qswA1z6=V7LiDS7`ad09kDhCUsiZ)x3Z zFR4Y<-FVO3uQY8eHQ`s3tWbkV$WtOtNh%W(9A~mbVngyJy*BjZ>n+y3_F21iq`M}7 z13`>!#wqb9gLeYKwb=HZQN|Fn?@sFgQ*!t>BmUlGb|{MXdtXP48Z)rLniR zvQrR{-}3T3dgb~)x4oq7WiI$};A;7tIg5GF?`K+_(t8h(?%wT;8Xmd&8&WMS(6|@f zyCt99kUVXyzYusj*z5v?mFH-W1W?Bn=ksORQBFoYVuL4GCRM+rYWQA2=l1r$#OpO^ z8G#YnwyP()$ZW5`>%0o=;4i9X-TBerp`Z-QWtyF41Hn()qcbyGHtdX3`O8QIibTG( zWUQ41w>U+8OT=1Ih`@t3?(uZtg8Al5jz_mg!&}_bzp3DiZ{&C=D^Ii@R%4Gcr4krb zseUljte7#KdfWC0r&%)cUm6W|ulC9s6BsN6Q+Ez(HK^f)-#xL=W5}E$Ti7ZMscR;5 z-pi51OLA*HIQsInll|+E)O!Ltrp6F(pipi9qH+~HDz)nK9e1TdeQ9ayvWm-~!qnUP z9|n`r#)|pVzZ!%=q(MF1YHs_?{4s(7r@7?80lBk3kRl+`odPFg&HOG8jfl6sX`VDd zYZJ&XpF{u=w&v*5dB^wBrxOlFqI!f5UUK=KzD-+^{;z?5oK{K#Xz*g;5nALInxxy) zI7YMd#$e}@`mQ8fRDsHNBW)n|pz)^1Vw(44uh}eu0WlOcyz)hMr&6WKeWs&_})twqw)OW5)-I0L@pR>|4r9p@op6K_XBGecKnUo3rBrRj)cQH*x zUV9HYdk0GT1$MH=%voTPZ3M)?UU4wc7lsd0X{%7>$x|8(YB8R4VO9P#ZK@bdmP-iY z`=Mtx#mfecx00*4WSWV0B0IJ;zy^Iw$uSa5P?alTnshmQDEN8V