From d0e72a3ab654326bc1e5de07199cee229066ee19 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Mon, 27 Jul 2026 09:09:12 -0700 Subject: [PATCH] font/sprite: update to z2d 0.12.1, use native path insetting This change updates z2d to 0.12.1 and changes the sprite font path insetting functionality to use the new path offset abilities released in the update. In addition, there has been a slight change to the drawing of E0B5 and its respective reflection; we now add a 1-pixel horizontal line segment to each end to force them to be perpendicular. This is because offsetting pre-expands the curves and ultimately causes the end segments of the curve itself to have slight non-horizontal angles, which produce small artifacts at the ends without the forced horizontal ends. --- build.zig.zon | 4 +- build.zig.zon.json | 6 +- build.zig.zon.nix | 6 +- build.zig.zon.txt | 2 +- flatpak/zig-packages.json | 6 +- src/font/sprite/canvas.zig | 80 ++---------------- src/font/sprite/draw/powerline.zig | 10 ++- .../testdata/U+2500...U+25FF-11x21+2.png | Bin 2409 -> 2412 bytes .../testdata/U+2500...U+25FF-9x17+1.png | Bin 1780 -> 1792 bytes .../testdata/U+E000...U+E0FF-11x21+2.png | Bin 1009 -> 1030 bytes .../testdata/U+E000...U+E0FF-12x24+3.png | Bin 1131 -> 1144 bytes .../testdata/U+E000...U+E0FF-18x36+4.png | Bin 1942 -> 1978 bytes .../testdata/U+E000...U+E0FF-9x17+1.png | Bin 832 -> 834 bytes 13 files changed, 26 insertions(+), 88 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 182616ea9..23321a5f7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -30,8 +30,8 @@ }, .z2d = .{ // vancluever/z2d - .url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz", - .hash = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR", + .url = "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz", + .hash = "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-", .lazy = true, }, .zig_objc = .{ diff --git a/build.zig.zon.json b/build.zig.zon.json index 092224ebc..459064b6f 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -154,10 +154,10 @@ "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR": { + "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-": { "name": "z2d", - "url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz", - "hash": "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA=" + "url": "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz", + "hash": "sha256-Fjr1ORn0ozxU7QY09o2ZOZQHIJsXfVw0UCdZxTKmZJ0=" }, "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW": { "name": "zf", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index fd4a8d918..527ff73e5 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -377,11 +377,11 @@ in }; } { - name = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR"; + name = "z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-"; path = fetchZigArtifact { name = "z2d"; - url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz"; - hash = "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA="; + url = "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz"; + hash = "sha256-Fjr1ORn0ozxU7QY09o2ZOZQHIJsXfVw0UCdZxTKmZJ0="; unpack = true; }; } diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 500162cfb..c3cc4f82b 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -30,9 +30,9 @@ https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaP https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz -https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz +https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index a5d536655..a11f52e92 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -187,9 +187,9 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz", - "dest": "vendor/p/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR", - "sha256": "d48b117307f767ca379bf31bd171880e66dcafb89eca4a8f923222619992fcb4" + "url": "https://github.com/vancluever/z2d/archive/7dbae85c81784dba9988320bf9543ed9a81350c8.tar.gz", + "dest": "vendor/p/z2d-0.12.1-j5P_Hsw8EQAKyZTQICCQnAH2xYkLDW8k9uefbsYdfPZ-", + "sha256": "e55c9d0b156edaddfea32fb1c0e3597e1b6306b9a4ec1888e2f8008c598bced8" }, { "type": "archive", diff --git a/src/font/sprite/canvas.zig b/src/font/sprite/canvas.zig index d400c9cf0..da54ac270 100644 --- a/src/font/sprite/canvas.zig +++ b/src/font/sprite/canvas.zig @@ -375,85 +375,17 @@ pub const Canvas = struct { ); } - /// Do an inner stroke on a z2d path, right now this involves a pretty - /// heavy workaround that uses two extra surfaces; in the future, z2d - /// should add inner and outer strokes natively. + /// Do an inner stroke using a 1/2 stroke width inset of the supplied + /// `path`. pub fn innerStrokePath( self: *Canvas, path: z2d.Path, opts: z2d.painter.StrokeOptions, color: Color, - ) (z2d.painter.StrokeError || z2d.painter.FillError)!void { - // On one surface we fill the shape, this will be a mask we - // multiply with the double-width stroke so that only the - // part inside is used. - var fill_sfc: z2d.Surface = try .init( - .image_surface_alpha8, - self.alloc, - self.sfc.getWidth(), - self.sfc.getHeight(), - ); - defer fill_sfc.deinit(self.alloc); - - // On the other we'll do the double width stroke. - var stroke_sfc: z2d.Surface = try .init( - .image_surface_alpha8, - self.alloc, - self.sfc.getWidth(), - self.sfc.getHeight(), - ); - defer stroke_sfc.deinit(self.alloc); - - // Make a closed version of the path for our fill, so - // that we can support open paths for inner stroke. - var closed_path = path; - closed_path.nodes = try path.nodes.clone(self.alloc); - defer closed_path.deinit(self.alloc); - try closed_path.close(self.alloc); - - // Fill the shape in white to the fill surface, we use - // white because this is a mask that we'll multiply with - // the stroke, we want everything inside to be the stroke - // color. - try z2d.painter.fill( - self.alloc, - &fill_sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = 255 } }, - } }, - closed_path.nodes.items, - .{}, - ); - - // Stroke the shape with double the desired width. - var mut_opts = opts; - mut_opts.line_width *= 2; - try z2d.painter.stroke( - self.alloc, - &stroke_sfc, - &.{ .opaque_pattern = .{ - .pixel = .{ .alpha8 = .{ .a = @intFromEnum(color) } }, - } }, - path.nodes.items, - mut_opts, - ); - - // We multiply the stroke sfc on to the fill surface. - // The z2d composite operation doesn't seem to work for - // this with alpha8 surfaces, so we have to do it manually. - for ( - std.mem.sliceAsBytes(fill_sfc.image_surface_alpha8.buf), - std.mem.sliceAsBytes(stroke_sfc.image_surface_alpha8.buf), - ) |*d, s| { - d.* = @intFromFloat(@round( - 255.0 * - (@as(f64, @floatFromInt(s)) / 255.0) * - (@as(f64, @floatFromInt(d.*)) / 255.0), - )); - } - - // Then we composite the result on to the main surface. - self.sfc.composite(&fill_sfc, .src_over, 0, 0, .{}); + ) (z2d.Path.OffsetError || z2d.painter.StrokeError)!void { + var inset_path = try path.offset(self.alloc, -opts.line_width / 2.0); + defer inset_path.deinit(self.alloc); + return self.strokePath(inset_path, opts, color); } /// Fill a z2d path. diff --git a/src/font/sprite/draw/powerline.zig b/src/font/sprite/draw/powerline.zig index 8658d8553..35445cddb 100644 --- a/src/font/sprite/draw/powerline.zig +++ b/src/font/sprite/draw/powerline.zig @@ -292,8 +292,13 @@ pub fn drawE0B5( const radius: f64 = @min(float_width, float_height / 2); - var path = canvas.staticPath(4); + // Note on the first/final lineTo moves: this is to enforce horizontal + // lines on both ends. This ensures that when the path is offset and + // stroked, the ends are butt-capped entirely perpendicularly; curveTo + // lerping would produce very slight slopes otherwise. + var path = canvas.staticPath(6); path.moveTo(0, 0); + path.lineTo(1, 0); path.curveTo( radius * c, 0, @@ -308,9 +313,10 @@ pub fn drawE0B5( float_height - radius + radius * c, radius * c, float_height, - 0, + 1, float_height, ); + path.lineTo(0, float_height); try canvas.innerStrokePath(path.wrapped_path, .{ .line_width = @floatFromInt(metrics.box_thickness), diff --git a/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png b/src/font/sprite/testdata/U+2500...U+25FF-11x21+2.png index ddb07747bc68ac02b4b7a969e66cb9aad5899118..eb0032ebb979d672608046cbb0c43a804d645ae8 100644 GIT binary patch delta 293 zcmaDU^hRic3ZwBxRYQ*Y-Sr2u&eUAKC3*Wuf0X&cy(L>N96EDcXzoVmKP$cp+~RI` z*F97Fp+s~4#QrGr+Ul1T8(Mevi|Ie*{}lLoVZOc$!*+w;4?phwv%K!gFGLl`5XL{+G^k#Qr^*cmIP~?Uh}!Jg?80om=_be2&he%FgP;nfcuHil1|y zTr*hXoc1@mzSiy>VZA(r-8GM(_E&zXMnAo1W!(ul=ms%f51HNxt&ax+kpP zf>f{iJV9*Qr|Z|=y01-~bk_S%)PDm;28ny!{~51wimdKQF;in;U|{fc^>bP0l+XkK DvQe3? delta 290 zcmaDO^ipVo3ZubBRYQ*Y>X*khx6aIuvQD1;Bk;A_mg#+BQ8RuS7 ziq@U{^YosYe+qoPEMH%if1APgi;tH6akR^_{qnYz`GHuPy^Y=U|5y5>?3EACRQ@NH zR&P_FSAV5Hs{h~z!`hC^hvwh;EPZcTf7JHo#Je|-S$_S~HrdrizcAn8ZEn3{%Dmn? zQf;Lt*3@%ftWG!EH2>1W$kBoh;3=9kmp00i_>zopr00t40 A`Tzg` diff --git a/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png b/src/font/sprite/testdata/U+2500...U+25FF-9x17+1.png index 2903ca051b1388cc7b2fa01f536cdf3487143f77..5c6ef4db87d2980ee85bb6b5e61ec8cd9455b69b 100644 GIT binary patch delta 1741 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 E06<1Y;{X5v diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png b/src/font/sprite/testdata/U+E000...U+E0FF-11x21+2.png index bf369fdcb351d15f9aa3021ef894b688a5854b21..905adfdcba5d5f6d5808507c2374ae744ea56358 100644 GIT binary patch delta 934 zcmey!-o`OO#qO-9i(^Q|oVT~Lr#|u!VR&HC!J@K-MP*BihXm)uh87P8CLtw8PLCC> zElXB8d0Z|?+su9cd%B;mZT{Uo{IL@YohCN$)gQPYIGx{A`*r&)+cj1;*Vb8t>|e82 za_32}`wRC97hYSJbNKtu`4`@;UQ)@?emCvb`ZeEjJC2@xC$Q_0S#~MUcczoKLm%Go zHL*BzMK0;$``x45Q#ZC-_gsFLD^s60-LFPc?Pj-u z<#xWrcUBQg-NORogROXG$Jf6yeCTWTKIO^33n#uM2o+X(h}}Ip`|Dnh+4a2Au{EtZ zk8;g&Dm0EBH+f*#dU(TUPQM@<^G{z_%yE<1zJHJ0^xBnPk9ry3U%vM>;LQ1@#fEoZ ze(>m8U3jnV!QmU_ngzulq z)!U2aZoRg!a)O1ldH$hafBZAP%})8Tpi+9*P4k5pVp13PJq(;sb4N`^U%HF0^`7(n z*eFY$$#bS{ZnC=h^5&;2a??-GT3)x;ar52w%ejXSr)7qFe=23aEzal+d+PYSJCkbe8Zm)m=_Kddd^ z=Ffb8t;{^nnB}2emAiS(PoDUynQNxVT@z<@Oh)EMsD&_)ph9*S$NcCt3Zj?`-S|ruUax4?nnL)pDt{pnh`QrnIYTm)za- z^Xu|1*7cj;@`dHuOWib-P?;w`Y3{yfXP?C_IXr#i&Yr)`eP!>ARqd8FCUfpiJgXuSG8N(_<8E? z+Nuc_&v?IA%HNfnwQc5oxpQZ~JSv+zd-@i+DQ|+dm=xTmZQ=4yM?z=s zIDh-zWv=J{L(87t-+DIJ{cM+$ zv1t3W&h&dW{}!1W{yQej00j;84==ud8uj!2Q#+^m8G$ciYYHC3Zmj;kvGKIM$Gd+Y zo-i}p|B8I}%$eDopWS4>WX0El1Fw{s-@CiLu{Zdzvc&t9&L;aAzwfWsySCW*+I7Rl z&OhG&+Qn6W*TwSB-0+w0ck|cZpM34Qt#|$X?Mv7Hw)<0?Z?o^|TrsHiFv{mKf4K5@ TWs%~!%Rxe(u6{1-oD!M<%s<=b diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png b/src/font/sprite/testdata/U+E000...U+E0FF-12x24+3.png index f1983c172a0ce94132d51f303e025e164d5d0dde..c19a7bc012cc18a0706d3fa548fb398b1d3c20bb 100644 GIT binary patch delta 995 zcmaFO@q=T6imkn;i(^Q|oVRywmp-x*VFAOqpNjYLPqw`Cdnv;I`l-&#uJ{NY!)g02eoEn7`z-3kv8?uA zYc|*vcuo1ow(Ij+n~?dw8CECW%G}x%dj3v;iAI=&_x%?Yn_9w(d*8~w+I*;M!vxLU zZ)Db2p1;@4Y0FpTd+yBHCa>6yizYKUFFM=Cf3!xdsP}9=Na?8s6JAb>U#0)@#K$n7 z7j~0USA|TG?qXB@+Uhd<@4Hh!&lLBrm3_6D`K*)EvPb{-#!jB--P5IfeY?rNu%K1m z&x5C!KV7+{TH~4V`K=LGo!Wo>aoF4t@oR?uLcKnj$qOtdeP0|GY@+g6bM@>`A-nEA z{TzA1lr_XEBrYbMx4viT^zUmGd*)19U=Xxr@xm3J)6b-b#oT7<^3M2GIZgXa#b)Q) zz#D%=WEP+Q<4`N{)2Hdsi9?$AceJ!s%+U;7vo@FUsHzCtshGl3(z36*4~gjo8Yb;| zw!S3s$t;%EiFpq#e7#qA_ZjQ2Eo5v}6{*uJRxOTu8*pj9VRhn9i^qTJmu{VL^uaMz z6Oo;tPDzOeL^u^LI5G9i&q}E+&WY8gzbySX%KG(Qv%Bthb@?8T?Nh>b?=ai;vwpVt z?|>V|cb`RPt-d>N_O3r0ewHQ9x?sFFFelRKs(FR3@4TG74{MIO_fAs{sdZWL-QreN zO68&UtJ73N>QCHxz3@BdcI&8V0kyO1lh)@>etz44yKei%t=Es;{+zp$ZU1epyV;G-^-+yub(f-e^w{<*V_Kq zf%o}ILiRlD3`pRIdQR=h+@QLXZyb<6UpuDr{`$Q1+4lOc3%1{s z=dF-k_~~-k70>2v`^;)~)Cc}JDsA%nT}j?I@yIy0w_^61|CgOVz2fd8_2bv`E*#UX zE2_K5R(mJ+&Lj2w{u5OS6AHRQcX)*FI2&HEIO1X8;v}s*hhKHAEVwPJ?ex^_%(T{1 vxjXf`m)_nIKjq51WO*b9PORaQNbvv9cwWfrN==I{x$O-!t*ufKM`?uxVbkEAnq+Ez)uf4n1TVG;ZE!kYd6 z4dU$2FV6|S`|0}2Kb5l6qjo9nkUxGqF;cQ_NAKg>o@5i9qdcA$lHx;6|1H@Qv(V08 zhjZWeB2Vt1?RVztB{~%AcHcaseb~9r^If;;`!1%l-$KKampau>ixB(u;@NB6yfxAL z_SV*`GSAA4^h@^(|8qF0%kb;eN$SBF8`saw(g|B}Ug$LQ^1ey`9j^1co=z68Y<#Ed zY=1RXcmAya5sfs7;P{mlMy+DMrEY6qN$(U*T(~^vSjqN1PuEIZSp0O_lMS;1R%)k} zOiFl>vGswo;k=pqjy>c08kv+D@ZzPm^=p~+nWtZ_mZ-n5_i2^SzXavQcLGE-PU@Fz ziF&_I#{cg18&M1GZU>4ARcZZuSK%F{XQ;L9&ZS>6+k(7GUsiN|SypFu%2wp@%HOlj zW-W87-M3gvVVT`C@pXFJ=1f{}W773x>*X09$AZ($zitkEt0#ZnCqDRqTkEOG*`Wt! z{Hi$j-K1q&v5-hqSACI*is{QqA45)V^<7}my6pbyeTO>2wXcYGUTuFMQTxpLmGkLz zA(5ywzMsc5O^*Eakv)3nmRi6Zr-cjOX!BJrb*i0s!nNtkWX5_MN=* zdf9i*>DE!pE_^dO8h2Xl{PqN~KY0tY>({q#e;!@LTem%A*Y!(pFYDeqex-i3y8sFRpvx_uW&fbwxEU zdF)J_?KjxeU-IgGmaYl7^B^qw@RS|1ud)U|nr1E5az=N?S?-z9MRUc2w`bUEpS+ta gKe14BasmsBNZ6GOpSW|o=7L=8>FVdQ&MBb@0PrQ{ivR!s diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png b/src/font/sprite/testdata/U+E000...U+E0FF-18x36+4.png index 29b3f0fafd03f6ce083ec3d3dcdea8037991d520..052ee7e721abb0307f6bc98ab2f68e90c6c59efb 100644 GIT binary patch delta 1660 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 E0Ahwv=>Px# diff --git a/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png b/src/font/sprite/testdata/U+E000...U+E0FF-9x17+1.png index 8f002439f1aaace12e2fa86c096c75fcd6177f0d..159c7721de7219a1647e8364fd46ecdaa434bbb8 100644 GIT binary patch delta 761 zcmX@Wc8G0)iXEq?i(^Q|oVT}f3s*acFa*dbC<-Yl3TY_{X)$t6ZD{f6U{P^#QhAfj zu|eD;-fr39Js-~fyvTX?{O;Wci~=SWYE5k5tN*}lwtvE7M)}>lBg^FZm-}R`KK%aE z7X4>QeScT}`8Rvk`WR2I>b6rbvhWgRZ+q^Z+6c-fczLnx_jx`Il zuy}S+Z`H!fv8js5f#vL>IV-rFr@94iv29mdZ_BH_utz)5+NAR&%e3- ziaQ^FEBBt&f?sYye~&Sq-B-3lV>iEB-n8H8XYIvLuG-G~;@Rmhi~gQ@X3M%&7w z|M@b{ir-Xy{5y9>X_&e5YMb_J$YBf`}ErO+i~YV3xKSF;16v77!G|f Ujc40?A_*ko>FVdQ&MBb@05__DH~;_u delta 759 zcmX@ac7Sbyiaoohi(^Q|oVT~KCoc98VF-{>P!v*96moM?abXftV&qg-6q?Y`;_+gx zfZX)sIdWTFYad!aKYs1Yx6ftf7eY=iI-J>7ht^eF{ zDzV_^$6e=d_3it-@6t*+A^Xjr?v|DKUs=A)%S`G@YUZP)&c{n%KYwO$+OnlVZS7Ka z!Ks-XD|(;b3lyzyvf%IBF5Oqw zYQOpJai;Yur;Wk$SG4uj2`%5}BK39G2A9%1oYOD(cU{k&J^8JZ!{ckSY^BP5LR*fc zO2mYHIeo!Qj4SPa?xGG^(ZsyV8J*v*UwB%(BvJiIp?c4=-KSqpq5E9duC-o6!)KX>@vGa zjH3Pb)?o8>`_5_hJ>9m*uMcdw2An z`l6z`=Pmng{SGjH6@D-GUS+#f^vm9@-(S4_ymHRH_xU>+z~GO(?!RZ(I`;3Zosf6s zv;5mKdD(!S6GH0>95%0)&HMDNdE(Kk|B;{1yRscU&bH-8a@Dn(w9hjcb!&GN6#u!< z@ND~=b5B1r-I{ZD?FF^<3*X*-YW7{`);-U}Vy*O@Z|82?{@IQNY!8^IX|TU)Y$7l7 R-lY=6_jL7hS?83{1ONyXg);yE