From d7b5928f579fdb0f8e5cfb03ce9ff5bcb184de41 Mon Sep 17 00:00:00 2001 From: Alex Butler Date: Fri, 10 Apr 2020 12:54:15 +0100 Subject: [PATCH] Document ab_glyph_rasterizer * Add license * rustfmt * oxipng the refrence images --- LICENSE | 201 ++++++++++++++++++++++++++ README.md | 8 + dev/benches/draw.rs | 56 +++++-- dev/tests/reference_otf_tailed_e.png | Bin 3240 -> 2421 bytes dev/tests/reference_ttf_biohazard.png | Bin 9938 -> 7496 bytes dev/tests/reference_ttf_iota.png | Bin 544 -> 402 bytes dev/tests/reference_ttf_tailed_e.png | Bin 2851 -> 2132 bytes dev/tests/reference_ttf_w.png | Bin 201 -> 169 bytes rasterizer/Cargo.toml | 5 + rasterizer/README.md | 51 +++++++ rasterizer/src/geometry.rs | 8 + rasterizer/src/lib.rs | 22 ++- rasterizer/src/nostd_float.rs | 1 + rasterizer/src/raster.rs | 70 +++++++++ rustfmt.toml | 1 + 15 files changed, 411 insertions(+), 12 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 rasterizer/README.md create mode 100644 rustfmt.toml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..44ea687 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Alex Butler + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..33e1a3f --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# ab-glyph +Glyph stuff. + +## [ab_glyph_rasterizer](rasterizer) [![](https://img.shields.io/crates/v/ab_glyph_rasterizer.svg)](https://crates.io/crates/ab_glyph_rasterizer) [![](https://docs.rs/ab_glyph_rasterizer/badge.svg)](https://docs.rs/ab_glyph_rasterizer) +Zero dependency coverage rasterization for lines, quadratic & cubic beziers. + +## Minimum supported rust compiler +All crates maintained with [latest stable rust](https://gist.github.com/alexheretic/d1e98d8433b602e57f5d0a9637927e0c). diff --git a/dev/benches/draw.rs b/dev/benches/draw.rs index d34fd21..1bff936 100644 --- a/dev/benches/draw.rs +++ b/dev/benches/draw.rs @@ -21,8 +21,11 @@ macro_rules! bench_draw { }); }); // ensure the byte array has changed (and not discarded by optimization?). - assert_ne!(&target as &[u8], &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8]); - }} + assert_ne!( + &target as &[u8], + &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8] + ); + }}; } /// Calls draw_* outline functions without timing the `for_each_pixel` (accumulation). @@ -45,8 +48,11 @@ macro_rules! bench_draw_outline { // draw into the byte array rasterizer.for_each_pixel(|idx, alpha| target[idx] = (alpha * 255.0) as u8); // ensure the byte array has changed (and not discarded by optimization?). - assert_ne!(&target as &[u8], &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8]); - }} + assert_ne!( + &target as &[u8], + &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8] + ); + }}; } /// Calls `for_each_pixel` on a pre-outlined rasterizer. @@ -68,8 +74,11 @@ macro_rules! bench_accumulate { }); }); // ensure the byte array has changed (and not discarded by optimization?). - assert_ne!(&target as &[u8], &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8]); - }} + assert_ne!( + &target as &[u8], + &[0u8; $const_dimensions.0 * $const_dimensions.1] as &[u8] + ); + }}; } fn draw_ttf_w(c: &mut Criterion) { @@ -85,23 +94,48 @@ fn accumulate_ttf_w(c: &mut Criterion) { } fn draw_ttf_tailed_e(c: &mut Criterion) { - bench_draw!(c, "draw_ttf_tailed_e", dev::rasterize_ttf_tailed_e, (98, 158)); + bench_draw!( + c, + "draw_ttf_tailed_e", + dev::rasterize_ttf_tailed_e, + (98, 158) + ); } fn draw_otf_tailed_e(c: &mut Criterion) { - bench_draw!(c, "draw_otf_tailed_e", dev::rasterize_otf_tailed_e, (106, 183)); + bench_draw!( + c, + "draw_otf_tailed_e", + dev::rasterize_otf_tailed_e, + (106, 183) + ); } fn draw_ttf_biohazard(c: &mut Criterion) { - bench_draw!(c, "draw_ttf_biohazard", dev::rasterize_ttf_biohazard, (294, 269)); + bench_draw!( + c, + "draw_ttf_biohazard", + dev::rasterize_ttf_biohazard, + (294, 269) + ); } fn draw_outline_ttf_biohazard(c: &mut Criterion) { - bench_draw_outline!(c, "draw_outline_ttf_biohazard", dev::rasterize_ttf_biohazard, (294, 269)); + bench_draw_outline!( + c, + "draw_outline_ttf_biohazard", + dev::rasterize_ttf_biohazard, + (294, 269) + ); } fn accumulate_ttf_biohazard(c: &mut Criterion) { - bench_accumulate!(c, "accumulate_ttf_biohazard", dev::rasterize_ttf_biohazard, (294, 269)); + bench_accumulate!( + c, + "accumulate_ttf_biohazard", + dev::rasterize_ttf_biohazard, + (294, 269) + ); } criterion_group!( diff --git a/dev/tests/reference_otf_tailed_e.png b/dev/tests/reference_otf_tailed_e.png index 274ad1c84d9fdf503e0c1a1e9187ca177c29654b..a517498813316222930e5db9f0a9110de2c3760a 100644 GIT binary patch delta 2411 zcmV-x36%Dz8TAs7BYz1zNkl%~ zm0E%Pv5NYEqS|smOOhSixjhh6I!Cl6*>Q*tUQrp8YfZA_C@sC9GB^?B2VUn4pRt8i zEM^|FnZZ;Fnax7}${IFM%x;cyHRw)0Srt{Ey#ap634h+K?xLaKl~vA}sUQ`i$iZZtJwL)JT^jIvlO{#R8) zZ52`TqE52-t@sbpuo9I@g^0@30M$-xWp*X%_u|DLYCy6>N-+$oM6D4~d77Zs3+;Ty zkBZ|v;nEA#(*!kFXz43{RJQn>-qnO;F;-~l0zYb)h#IL4>ItEx%l)V~#Evpe8`MDg zJAdW7aF1}|K5a-AeTBCE%ZF+x4(gX`gz6)-b)yf}Q~Zy5S|ikhYESA(5!FQ_)BvHa zANWu&iUZk1jZlNcB=il`N^xRnX%zWFYv1vqb{g};YsX}vwTpeIGs0!FwL&ctT06@| zrHI|cD6LQ%gx3DqM*Unw_1B0L!&h>i)_>wxBI<69P?=(Dd)TNa#LIr95o)y9+EzBI zK)9%ac#Tl+3a$Odmw1_`a+xO4UL2(s`B1MLG(uk>e)(STp``J|7Hvqa6qarK`B3i* zm#x=?l%SBAJnt)Wu*8zLziEP6sIYvo*1wcfYQO|BR_xAv@t7Y~rhT%xotW$M_kYug ze$){;j~2zqX1;Q9&&r3tQ7F4TkohuMX!1}v@yMEamcqf z1?dZ#R7G992}sv{Kf0BuYc~OER?X5l^HG1VUBqbm*YEw>i%h0>6pKl4DhxKaOF!?H z5|V#>b&Vq0x$hu2Y#`{Lc6z0*y?-8MMsfBriH`DCqf+f5S2@f^<}j4jsyp3Egj;?Z z@d2$ry3?7CwDO+KvM3Ta%~_^}#c~1lwTURH*y37PE6>A(Li|T%Qg*_ps@P!nrSGD6$+<5q?v}wRrmtFVHzq~Vc+~!GR#7$RMXk3 zLNR8cGL?>p9Ay?tWuy5ITz}*zW}$wqc<@MblTdLSRATM0_dT0}vKXRVz0lAcl*LBn zGr-!KgUV7a>^a5#rl90X^{TrI=96Lus+ou5vh3&e2X50JWzkJMp`&_~GhU~evD+WU zc&>Ih$1(=FAI=$;Xi7E@^E5M9Np~5gG8J(y&t1bfdW7LjgvMmhlYb#R#Z(ruj=h|7 zJLAv&CXK`mn023m^)V|bB%j`Npn1(QL&u0Gg>>#Ehk@i%0L*3yYuLsig&D*m6_kAr z`bQxv#i`&l2l*h>VGKTl;j%&P@}z}EYjGbJ!+m;ls(t_ou>^Yk&n&ZQFhd0Jpa`e zbt*8F9VxuPNxe5m1iI;QFz+dxUKYt%Rauu9%xJ6)li04wUXkLeqs`z4nkoE-g<-F5 zui$gmF_S0h;gEDKtDfHq-6*;>4>5sd>=9r4qjm)KK{m6Tmwy?{5VHRN9AcQ|&_Ud( zHlGS6ZlfbP(`jV2^=lGl+2z-Gc`cWA8F*>@EvK)V#F4D$aRIE!D ze{yi^Q_&6Qqux)bu72rAs^^kl924wHVl?AVj-=y?Z+{-s64k)v?HwLYE!Gb;+GP({ zlDVjOmt{TRhuimII@`KoTH1o8D2avacals+K@WhL!(LpApFVs_Ub0NtkZ{e!O8 zU9V$`Y)Vk1tGuOWhIU+lPir5~_}&(y=K{e1k7T8G<-ATe{nw|Gn5eLetAdYsj(cjB zALzX18%qwavAx!NQ7=)(3dWJ+HYPNo;w6@Lbf)W#V_ff|sl3tMoGLPd)6Tb0w@|lG dw@{IZ{{fQDF_gxV-uwUn002ovPDHLkV1oErrJeu) delta 3237 zcmV;W3|jN`5~vxFBYzBUNkl2bdMr8HT@EsvspOU<3qId=L?c;$sU@DT*4z z#1b)r2^KVpEm%;Kj2f(oF=&jzCZZP=?{iuJChY7!Q+AbV3SG)(8y zI=hlWP1A#VRevSyM<6>>O?0tt(4Oq&f<^N6k0{2Xo64hwZc$hEa!I~^rRk`VsHS@8 zQ5}N1z{?!|E6`A)nYuvts2S=CTXdx+q9NE#wbBHgiMqyQjZgua0h_5VdPV!AF7m!k zAs5Mj+0?#tqn%M#S*F2SjHY2S)eTD>QI}axg0>n>A%EOdH@%_nqBWN+yI z9c)2no~Pz?InB$qK)<8C$=z#1zK-GllR}uOW_phVwI;05JNif;vn|$k-rH!X4jke= z)SIo3cH@(5naM#|j8+h4YLbQ%v!PHg@z2k~OeybbMiU*S0Xi9T*>SagN30;TsZn}> z7@d#w5PyU72KMTPo+Qg7)t0?zY}cXuGAqbrst=iQ6Eqt0bsgP)-P24LY7B>FH0J8? z%B--uDNnOG2%@nt&M6QeMVLu3pKj!F@Ic-5h2~wP5L#_#!j83Dd?`Em0r{L zP&4z?mk6n*_F#mzXX87WSxrWFCGF_Ex}s(dO7wxLrmoQ)L>nVbX@9+OG#QSq%ebA6 z3#yuGK;5k~YUXeHnR3JJx|V2Sxw-`Bc~w(Cig2DRX&QI)gv-d$|A+rp06_6F%QZ^O&qKbiMgdKorRe`%1@da z$>hPzZ1p4No)wl7!)cn5G&M~_i6+kBB!6^)7jz;qoNIKK=L;HWGiknw0xIa+(A;yc zE+vNZdyPq$>a7omCZ4nOI=hZ5^ao-%6ErGe>H>DnTuk5R1|vw+OgyQx6Q&-dBXdn}l81!6W0EIXcuj{zLRDVCc zDKn#WAJGj4YX&i#@|HTEbIQyS)N5T~2>G9hS2Z9obsLpC6NThcJ5V?Hv89wSQ-cCi zPgy1u3w0pT6@IRViQ!DqX@RM^6m3n=NfKS*H}Yj-qDBU$HmS8t{E<=I9Udb2HgS)x z^!zr~3uP`N-*Sgn7^zI$sBxa(SATQZHF1)q6Wx_~%=_A-;ZUdb{F6Cn%=FTKiEhx- z`m^-YY|pnPh+>iSbD|qu#u;v=lU92EmAaeVa7tLKDni|0x_(FuXCw1I3NOFa4ZI1b z?5{b({*0OCEcIob5SY53xsnN&GW5T&x@hJ$T_2dTs3n{VW`72=oQ|oP zB=aU_(51UTV^Y1IoJ zmf)v)f{3Zb>V}4s`(krf{mD}6OR$jz?;R+nKBK5&f;t*8SM=srIwIo5J$LJuMDYAo ztbUt#)G~jmdptE)3)<&E9e+c95S0Q(u{9`o{(5?hZvuBz&fh(M3zPNuvx-!AGg^k{ z7eqW_U8aURw3>k#YA01T{4Y0 zcj;R-HgieHKKQ!I%e2ulGy|T$OkET=Ueq`i)WhZ+u`VIzf}JX0+e(2h%W9VAzog^Qa2BaMF;;l~6B>qwvq5c% zvBL9h7h4ol@+d)Lgy;LL_?X#~iE^y){LvbZn&{4X6)Qa7e#gMEdV?4vJpTm7XEV9E zWV;r4{=p1uW^RdeSAT&+cz$zzMg7J^*~PYT!tF(EJ|z0UfAnM3q%VoXSwx>HvZONHk%&40MFMjwHMsgBkQw2PSk zZboB4!c=hIQ757acP_WLk~H;a>YZlhM!JF636iF47e)v4Q9dySBuzEedh6=P{W+@u zW=YbNDCho!Xkxn#uy!a;P-)7Z0ZM<3a4C%gc)rSM#q^>XYNALz^a<)Xm8POxO&6>R z4R${ZtZJ$!C4U%mwIaZ+3%WB>)l{%3+@EM>8_DfL)L~Lh4b?QFjfLvRvVp^-nrc9S zc|X*~99r~i)KOARNvG>+qMcbf)m>ZZrh+@gj#U)$vGtjpq`IkIn$KYvh4ng9uggKI zn+kHgi-}Pv(Syv1zhKWPGMU23Q(zBYvT2}%4yNd52uX~E}7syyQ_+*AuX_e0T|u~Q#vv6gCyKGhn!l1;4g+i1uS-kPlyuV71R zV5Gfu27mFe&Lfr)ZYp>V`inXotqV#x<2GprW(#>0VW4rfts#G}=De#TiDj5f1x@}! zY!=+M&wpklf3EF4? zZ8k_3{BOkNj9q*0&U`pzcgn(n$MiZzc%IZ4WgeFA2V?DbPlwvx%>23SuEYY>?`1suI^? zWMjtSb{?gMQD{{PDA&D*R-ryj)fWwzUW`DiQoxke z7$4J_r4uquKI3BQdfFtNRV-DkL#yHWihltYvUZlvP90ZSYDvZOOn=xhnjL zjGiyY*`E%(4VubiosqHAFT-_HtthB;M^jlsa`q(}g4?D%%A=>Esg&qgy@iJ2wy8@p zJ}hxO6L%vno9fM>*Az`Hn}}B2G?n>L?`s%{3s5VbFXz}zi!#<1udqG_uN)Um zU8~!P>8#d%O!6b)o~hZ!%Z0o08tGYN<+x_* zR81wOQ$&4g1sW08OijgBbtda{Vt-`ZG8OiFWP#_)Au@lJ;TlG*Vl*;tnacc~ z9rw|lXN60qT2K{lf~GT6&l0o1B~xMdo3v+?$_kfEU6=80x{5{5=(uF+Nu5PZ=OdPf zvcx4*nV$>vL4NJs^Jz60@O@f9Z#8hn_H$#-%z_y7hx?kc{+^r>O}g^Jj~B< z%arYli012`EVC`+a4cpr*DwLlZ~D1D=2 z9YvBMEP-}WbXID*9%H4X95+n`>xftB7wW*Ci4vwO&#~o`_Ey1FQvrE8hI(IL_0aCT zt7JQc&G{_K%_C;a30*doB#%0JYuc^}`)fI8Hm9~rb>d(uYrZuV7aqPf6&JoW6&L;w Xn;QXT!jGcJ00000NkvXXu0mjfS$@&OtJTNHZ#uF@!{#G?2oLQc{wJ`wI;YDfLdND8rSm6s~J%K&MpHrJG73 zm4>S#AyHAr%Iuu|`{Oyzd4@gjwV!A0;r02C^XzBs?|MIbueH9Df)XiBszS|R-(k3> zGn>|&l)>+RkY_Ub{Si!pREAp^O0^7+Vj>5aM6C!W|Ee-WSjJ^iJR4s@Nk5DFp$v--d$sFVxT1z#mqgjTEipn>%iD2@_8fh@QDdb5B zfB#kZUlC3POrSyplicdU52){q98MomBZ5gLb!P`ED*L#_7vmig2b0QAG>c%8L*XR{NF*5RFj3p)3AVYKD>kG>`lVyb#fz^(ytEuRy+lBe7n8AJqae2d_Usk zZJGYx96Rk^|YS2quxbKJHveWoKYh8lTsINo80BlQ^Bv z{siOp3n)qoRpMMkB`V;&2qrPA$Myu%e&F*HZ)4r8`D|J%`^w25#yyJF31;0-y#S-6 zkd<)!p~g9lBA8rjwExx*E_JykOa-)$U~;E6N&A~MLCEAV)hC)bt(PV<*_SYt{No8>albh#~Dp>`%bSQ@{ zx;R}G!Q}k8w9{{?KSDG{7p66`W!CxWx%6rXQBr893zJI!2qtIGC6(Jk3DZ%Q38_XIz_ebbb)<~OtEXwUk3$PnX9Jj2 znQG0+XuRQ?M*DYYVXB&tNRgTbl@-CH@80}D(`XNd7AA$C44+L`L@?>8m5#}-4KYm1 z4Pp8qf=N%~bxhVQ#4zO>!lZI;1d~1YLB$ZmG{FR>c@a!nYO7v-Q zl+A2IboVS_hUq2~m{e|vU`o|I9b+vGHB22$VVV@dlzJ@in~t#-h8m`GO=0>jf+<;N z=o%|O)G)O-g{e@6L<_l$)?3$DGosg=iMmBFC2EqcsV0URrcM@M8W+KosIPTR^;D=~ z>S_U|l@Uzws-#(fFQ!LB4b!z2U^<-nCPXrsthIru#)cZE8!f=3a(V<)iMksY>R+LT zDbEs2?IV~3QO)Cet1H&}$}#|Wk) zs%>Jb4xxo=2;T*dT{cTLUAQ z;#I|>*}8=QrUf=pQfU*xl&C-a;T`u}=i5eU8>J$c61B>r>8|nExq2EGaWfC`1S5Hx zv5eBE(@qK>B(HnUh}8>U$@%%5B^&9`W}ozxaSfoE|W(^<~1Cb8%xt*1Be zCL`!hJ;6_fF1AsUq)&ldFpaWoyiWxk5tO1O_c4!c4koZfds)F`ZsSA&BPz@Hw!i-{ zRU?>^)g!HL%?6BQvnz;F>$k1s5n8xvK=H7H@BK)Q`I8H#(``*$R~O#+0h*noMCliv z=Q2k}7a@HkP|FlxfWAxdb>nvKT|C&&C9dN z(e?9WnLXKH>S^1QR$7OZV@#S}ZF>5H{-y^AV;t@zt41(stDXaM4luc98`fkLq>`6% zQ-e`sdwMyH_Ag1wGm zLav#U5vJjeja#5m#i<&rOuko$0?ehYr=NNce{=DG>>IMLVxu3?L`t!8Sj;y1!+zICtfTNfB_^SjZHP zNxJG~>|mM%Ddv~BjOLk#Iwxj8}_6r0OT;UI5X|{T84)m<(xR(#eU6j>IWaDY#P&_S6w*~-2Z}K zra>C-`Qi-4d49!E;d#$ez*LH6+|BErtQnjEHHUN3aNn(XEu;5Y$S2$*B+pn9n40lV z$04a+=e0e+sh+I@uiyh|eme6!@A=gT?JMNbKIYI_NGXi^Jj_OE@AG#a<9I*8ltmBL`Ua!K!@&1=i1Q6R{bm@g z^1)pmWEPG50aJU!9^ig?PQAy&mZ2&|Q=ecIzlwU%i$h$I7dwkLeGgJ1RCgGjlwHXP z-s4Z7-rXTy;3PR=I)TX&B|D1JpT-)cdAAJ$ zb&r4Yn5f;X`%6@?UH;rZ8WRGTR8)TDVi{m+=F^>gGoi@Q>QK$XO4qkLV=RX%b|oxWgT_W15;Ce3kgbkDMbxJaOb}|@!#1G_u3H} zlvL)07ABRQ_M48``}1}dn|K99^Fs`i${sHF4yNNm^n#0Np>JR+;1^bq&v^3aMLz~J zlINMjfBDW~CB9g=%5~nrl*L;iLrLX5o%$tmIC&4Uf^qbu4W}5^yQ@fh1~HrUjv(#h z1x&Yx4yBKER}GDs?c(?DRy&x_1GLvE0&SdN)|+>1_GviCC8A+EgTI6s;}2FcmnlqO z6p+VV+(Iwp3Bj+=?6e;!F5@oFa^3K{I)CRQ8&B6= zoFx*b96oVrh^^%F1bt{nO&fUwGEpDooTghy( z@|7(ebwm1z$2iY+A!I9i%HF*18B$-`&ZIlp!~Eor=Vqoc>I(a&I{B@@`wMviqbmJXi0f|puf__|g(?zT|L{7LsP+3NuJBf~f$O(w{t zEAs_Lp6{c%!;w5Jd>`2%?PVuwGS_rm9o@lnmW>_NCT{Zhd89qUHv~;#AID?J<_BRQ zE#tC`6~*adQnvNylq#XNV0zD@33f9ygY_cY^0Dhl{LIw|1_}Y`2hDau=~QK&H(ZZS za}LwB7S~A6icFYs7((2|Wrf}wTz!8f*ZDaGj59fgOjo(-_N+4)XD4ScRj{<}T}nMk z3K!;Zr{gKz?0VPqyepK}=$^J`QHSdo%To5ZKCZN?V+^{O8@%d_E;-7=uj#S0{9ek_ zz@i~fW4WWre9HA0Z*~mQVMb}5;v7p~-b^@nD4(_;W+_JKzV4>khv`hyrT5Raj`!TC z5h~kxlOeRS@O*m2!MWItuE%(`LzGt0QsXS`4*sig7wKJ0bkz+w>SPzDMJ7gg)6toX zGqp~LQ0`{>lxl5zch$`G7|S|3+8v!z$fpt?>YiKGO7p^@zD|3!AGMPX^e)rM#O0Wm z*4`Z#Cw>2F`j{L-RA8E|#jl8+=xUoF?bobNUc=xM4br|2t)vrpbh{mxJ~e#D_et|D zU8NbgX7*$pYT4^YQr%^Dj||<))w<^W+QO$#H8~qEV5*Q4oZBpT-Vd8TOS23< z-6VrY!nw>zIT||345mwTZ)JGKZls05$+eZwpTGu#6a41F##x;yhT$j$++=!<(gxdx z%r-mANhwt_&wJ}V3ZQxfr$b}GjxUYX8Xmwo8lDb6gWw=N8luciaVAw*f{g39B!QE5XTQy6H zdT(h2(^Spd*dn?vt8MAKoUM=S!Z<^`!YLlRN}oyk0o?66rR-BF-06v%p=b4Bs-Tn5 zbalpWK|edj&WfiShf!JSaU1#)k77ypl);uiHB$FZ-;g9aB2HE&{; z1UE2C*Tns7`u1;4V>DSqV>6X&Kr0`Ij7ow^bOyeYd#l>D2zv*tc#dW1^Uwn#vh%r<&`RntG>Uthvgs}Mc zi*-@@%kvC6J1Q5qCAu>erZO7I!ds+x#l`fh#~!O1Oiwy4+{#FuUbk7aWk$kZZ*W(q zwpdT=-sK{RP*PCnomzOP=sN1mA_|M}09;jd0xK7nrsfJ$4AgAAXd-+N{Be)yYcU&Yq)JA2$*DX;U>Gh(~ z5LIT`V7Tl%VKUU#jOEt=}A%vdO6KFCixO504}b{yb@i{ zRN-C%ad(i?NpCSE8BN`oNI8sMHwMOL&r}|H8486QT1VMq#V(fnx4Ac2G{fz|%g?>)*4NT9mm#oBtf&RW3{7{4G z{KeK1xSddx4ja@r(fU!Wa?=q&BK1J!SOujx4C8>nXln2GbUzrDNVFb4Q$;9Dt8DqT zjGByXqq2`+szD*O6qMq?hkPFm-puF!e>SePDMQQeJi{yX_tk7DtfFCjJ)q7$8~!ZA zJ}9j%UuS_pnEqk;TQ4z;@cc02MJnuSI;|9{73X5)`fT_$*kLdoJ7Y067Bo^IOhYYu z1-BV?>8tItxln+M6zcfsgvol$bgb>4wRWI42!zR6>DUUaG`vbbAI?QDI>!pO%NeF? zu58^ZcSrVhR$7sY+|OBwJgQ>Sq;`RH(f3x=hU*kgFNzJ!_TddEG&HHFyU{}FoJIm+ zYHs1>{FUJqwsX8MXQC#36^=(m3yEjVvlasOhMrnX8ZUS6cp+hc-~7YhU=#b?GOf26`gOe(rfBxPW1IG z9HXG{5j}klli3mAk;&3a++HsXChf(ul`eRDXyNx+h{efO@;yun_gb3l*Qu-V*_Faz z(rV&$i-rE2g0Uz45l+}#Vg5MFvl@WK~WAfEl&9^ zh9vB}-W3K@)09zdweYE#zC5N3G)xNFHrg)79~Z0;22-+oxuS*7%#f-MRs>VrHh+x8 zX^(GevsxHT$txf8&bU#(QObQCaElqoT`BQOKnjTCVOLvpM9~F!jTySAB3p`lR{a>>*nxZ8uxd#gr*dnkT8bhH2>!947VYSU~;66 znw<>}Bsf%?2!$!pWmAfMDc^l>kQUuxf=Qt!3sWY1Nn+@PP?#P_`u^9GqN|z**Ve&@ zNgew_LSb5x^zFUkGT{bES0TnUeIKrNUy} zu)&n7=E5t-{T$TYS_G5%S-JQ^gpDJZ!l`i{!1QDUQ#iHQ1DJj{NqLH33ZyEq-;=`f zH%2gpPk-~M;l=U@rtoRHM=+^DcAG>nX(`7-@}^TwjbI9w+I!f(a4%IOn8Ktn9>NsD z1A=hFw9ZqQ_F1S66~Pn`HSiQAt@MHjrod{r=P(t9Yfl$ml*(F4+;*$?BbXuwuQ**5 z!4!05nO2(TWN&pF9&_iBba=nx&~d|ywGbAOu^N& zj*!^q!Y$k>f+?7)=BUYY0hd#rpB%S=E*HTRM7`n&hv6>cpGHJ51y8*kp|GepVQHa5 zm=4e~f+<)!gXaTf+^1t z0t1q+ax@zqUm33aY!;bt?9v{9_9X-a8kJ6cP_2qu51hl7beoK%U|Tzy>z&h?03l3iImpxd3m zXlk9r9o_FT9colis$K@wox8}vv~F>n<>od6Z&5H7<3W5T-O6r96_wnaOh4rkr%iN= zV3JKI@s1;p*(jF@Nm)#A9Vz|}r$;c!^Gv$gQMA(5gdR`MzkRZRQWuZ-xgkbyQ0l;^ zjwbM3Y6^>%n&}!Rm34ISYt&OPKx*x^3RW^(H&fxmR#jFkzMqlayW|%(pClgTwZdRPI-XGJOlqMS5B6A z?Pn^NP}yEgPFsoqHp*~$QDHLZn7UDpTBuh0i+4fxp<&@%aA*-i&N6!*Qb2BX* zrE}(TCS7@uS$vVwSAD9ePIz7#v>Kz?$aJn37Q0iAfh=?7x|os1IHF61z3!4v#nzI~ zlMJFSUAcfJoJ?h^P=k}GOqPPERJV+1DwzSwQLtU<5 z2#Y!B`nNof3p2D~uH_>6z&mZ{e#-mzoT(+#5`Lw>@Bxzcy2xt1ji&dRrox%Y#vAMU@_z9Lo**XGChUc$afUTXTHRKi#%GTyJ)n0CeEe~T^Pg&o@FL)vV^bM z$S(e3GvBb31-!^ao*<7a15w2`m-Bg?l>*MoxAHMF7(mlZz2t)#1MWwap%!hqjzNrL zF3Z`(9~?Faqua(tzGV@U$fFxg+`DNbn1Zg-RHi0%XuvtNrZJ~clZvu#Fa966!#zxI S{fw0W000098rfPjFsD59XD*Cg~J5_+hiqm%%F0E$A8CLka!bg2owg$`1cCO2J5 zXbDwNkzSP&r1SB7?~nblvpchMcIWI~bInE>=xNZ?0%$2HDCo5`p+*!G*ML`z{??5v z$t^rZAq52xs|8gt_Q~ANx|x30WHP)RXZJ1A;SSYfLBw?nL$mJ4GeP8C-VNO3mra~_ zgDB#8&zCj8fA3|BA-vZ&F!||Ehi*`*gz(tet^`Hftr<_en0PTC&@%fuTh<0b3nHFR zw5?{tJ1}dEdr;2_V!X4nfCwlBz-MKTF#oEcxVO49EsuOG&Fx4 zd$}$NrBUTGi*hxy;ojax=u98iXS~O=`_F{^!TX!T$3o69E5ITQgrX?cYN!CR)lzg|m#S*w~OHhqYe~m>& zs+V&%U+0~V#SK$6W6TGIfvb$8`A-y(E)^O4*QVbUsHXqMT+{itsac=8Tp{0=r>1Q_ zM2cX{lNW5OR>sQ9?MdCe= z+!4b15^}6rS(xuI4MU2Or!RXGTG>!l*YwvZu7;eQYi9Z}CC;)Df}g`?=kn`}W7&j6 z4icr*?R{Yy&JauR-Eyq3sp92R*Dv~g%+VRw>VvisuEOz>Z`c#FFhm$@^5b}Q*Yv{q zSl4A_XUbhF=BAAP7H58S5Cn0Za#kSL7o_He!=EFYHF+Z&6ja*e-)-ea392soK35$Q3%HwvjVtW4 zwle$3R_>%JhH8cLF|#xoUp@xJvKl6SincaYtWQnyC3b0f>e~F|!5H<(i!(ve5fVhC zaT2be0X9mpA8{Td8z*HbX_Kfcq8eKyGemj{pP;OCeB!=|23MH-@!`*ymf6bmsNwgK zCutXYF-A9zRa0+!2>TivZgPv+3s8f$y9-UjC-;gPg~#E#XMZ0|6ghmIFb#i>-FknF zbkQ{Vau4vNwbWbqwE6k2v}G}KY&YiH>sN(>_XkHdN|v3cYxr#rRai>#ZF>NhhxiPX zlgEc?0lnsDX-qo7=VY)1mb*R-3(uNCx^&;2VvVg0d4p22q%}WDFezsVe_(*jKoyEU z?)6Bs_mZi|ER>JzKdaiog#yA^6Km=nw#PYQjVi5A?u)QW71|kd>mn<{`(v>;>kLSlB%jq|| zula>L-@)VhQO_-5VgV3K%emZkrmUlnw`-Xp)NsNQSX8kzG^$8Fs7(#MB0LWRQ-l5| zoMAVV7fbHarBq*PE@9c9=r4Tr9*dOIm||gBkLl4))LO#4%EW(<3b(~$l?>bw3UuGn zwUb=EM8~l10ycmo4V*Kca6l!I=xuaAX94+oz~p-(Km&Z8g@6tdeFS6O=JG129JC(^!v7i_N!IgnvzVHn@1ZuBpk#pfH z7DS={Q;^=gjgb7Fn3^W2zwtC*UVf|rOhFu4`k~}pR@6PdWU5V3>SKD!LKlZZ&SF)* zNBO$w2%Cg&YUj&OY?N3y2iNdOVvhU|HL&=~sGDbn@mnZ#6W7rd({;(t~mcW^4`-vV4vVH4`YHt**U zS;r2sdhtP6pG!=p@B4~ew3aeKD`JfXq4K}k@@VEUn$T){0_7{Sf4v^?&Ym)=|Aaxh z#8fC>mHwD4qw-B(4llf1CMwYhCJBe1=Sz~Jm=0F7)4IraBp|N!!GRidky^;v;Op=2 z6+9pLAW9WCW{!|+Ch~~!1FtT^2an`H>U~T)Df?)839c5GR$2^eqJ&INg;f_3Wtp`A zya=Cob+?uF)km!Qx^O;dQFcc=jpjg@=owtRD-@CLfaf!B__CYW_#{76*1;N5o+_P_nVs z-m~L3M0MlF&$|!aRqF}zucVjOeFZjy465(wF#Q9<6@t=Him%w2Ngml%ys11tOL-*} zZF=W@+9}fQ@s$S>V_7#jcXSdT;Peijb&RM(`d}Fw_wwa)nx%rf2-xPRwXlC&?y5N& zLlSVEa~QhVNVlF@1d=_a1QmlF7|#wpx>_*X^bUN?3?5merYK^}OQNfGWkg()=z|?9 ze%)l6Z-SZK(TP?JsIap+PzYbXl{9n!g1->dt|{94#K7yf7*FeS`)9(3Vm#^9zX{7a zV|H>cK>)Dq+wm9_0P}qaSzV(>tiS$R1@v1sjs>#axzcaEfWg!{ri6TpVk8~dNkWuH zbJ`HGt4-}A4j-G$mVA&yTZ_KN6zS$wXM*}+QH=Utzvu2c*@wp(uX;bJ6DRCFVN7=b ziZ%R@y?rJ$_Lpb@GQLU1ToZV&g_L`$&qUm1pVz&u%($kR?|rejI~Q^v@e$idKFG}H z+|b&&`?hhVjbF8GUpkhD?QYT#Dbx2S9rbgQ98YV~!+h?G%tJi^**?s-hpFR3y80b0 zi?UAav1!U|wW(I2Y&p|pCSJ1&T(spTU2TdO5>h>56aE<1wo>4Xdt#b&yfO&Exk9-Uu|4h zwy(@Z8nsmQf-(GYI^YfWGpcql=v?dMFykiosR6WvWsunZaUO_TTMdW&yXcbwzP^j# zGF!1JSn-}GH$0cydi)2p-A#1`Nnr|A-^EjZlm7#1t$x-v{vvJT^#}d!^0N z4EUMt9CuR6n^_7NC@Br;F8p~NozE^c@nlKUYkE*x$O83Z;}OKYLabh5BW>T}3r8>t zDff(#`TW=Nm>vCbke(Tpf7oxc7cskV_B(SqEZLZ4QrB(s{uy&7&ai#BINb9su8#ig zxFYOz*lIvlMQI7>bi=|!{c>(`UES8V1AK+@zW%}JH(YdDZNMncS?W^ioUWJGoI4ti zC#}O|^EfwhehA#gIbFGTa7UolW>Q>H2?HG0|0g+#?KWcL=;sa$5L9b3yyPImIQNH^ zwuft@DiBSVJb)2rVCt56UXxq7NzJSBL9@D;KdfYVy35aF(AN37@hD!IqnHi_P{Tr( zkm`AD=GPxSJ@AO}g}@T-)OD`LKgR+qpUyiU<6!s5=IvgG3Q<)gTUu=Y{hZzPK;<%) zh(w8WC!oCHeB4^jPZ_-&ReMy)6n3lilQ9b<_$5ZrcR6bX0Nrc#o*i?c35)8^8OB%t z;Hevo8N7;ybZ6kI#+ZMeHv8lGp3*14_2A~ZXYuRG4&1KOBgwP_#iw>|NNLlWPd5MF239do--O zG|p9PP%GzqUsnrY`NIdcO`~jt_Xxn0w+0_Ias+>AVZEL*lO4v-DA5DtL$gY`--Iu4NpN%s}+o& z@7tDPhv^GhE(-K`tuRH8!n!&UcDv=1aS2%XUwH{x`!ejTbE@FzmT%@($tbU3vieeJ zMBa=+sddFO`YQlrkX@+4e7im2cDx#$zEf|1<>;1qRXjNbS@k{jFJHKA6}DAjXWCKbWdpaFf8N{ah!L2lZS~^5p!Dg!?oy0|uCd#~iFe$yY9D9~tn(6X72v+aO z;1Lch;c06hUWCup=5a$RDMz0f>PnGsnHVct>Wue=?L)las6GGqrHAe_R~_vMG3#O{Ph5XI+>3j3T~s))YbP`?^UTE_O8ffl9o z3AaO}OQ-Sc#{l(L4*5F3=g8V)EsaeGSf>^O=hi;#`QowPTPYWsu6Z=%P`qT8^syQ# zMprA5R8(i~FzF~(5i?oZ4efLWW<<&Hw0uzT1T~P_@+@0q-=yh11~Qw1W{Q~}6g!RG zuHhMoWbjk$CR&NuLtyo<Ow? zaw95bHg92-$+EHk!#wOz#O&YrREhps2FePMUpZ>@-|@%%zZw0aY_|`7w(q;*1O3Ln zc_w*u)!jboPY%IuwN(7s{721W3RPOGUKKD(We{`WM(`Bb0Z)~@Bi5dfK!HP~)Zl7k z;1_`P6K~C4D98krjjaA2XiSNWUwJq4hS$rC^HKw7ykV~R!LPdoE3CNclNpYcZn!6r z_~0bOyWp?!2Cweq<$)qKZcs>C%D|oM_+2k!Z-Cpxcy>ocabX@-JQ^w_@?)$?)S_#$ zIg->G)AsCBWR9Dp^317auaI*6_$8|3t&R5|es^QZ53Lx#|1jTJ=E-@0Je9xYUlF@a z&_Qg+eFB7eR!v5aWRkFT>|%npZ?+j^&*0`TCy0T{Lf%} z%@_S5KkI-K$0o$XNLr9NV!_GoAG0H!fl|2sTF^wqT^+8MAw_bop7ge0{zd)04wo0X z8IkLHef9t=&v1A_! z;^9HkX6{9L(dmIrV5Ih8Xrah*{UX27Qm+yBCPkd8GJ5nTxXrWc@r%LV3Txit+3Ip8 zmj?F^jrL`3TCn0m4v;Q|r5oXXP7pFyUSb?@=&9zN^bqJAoheknH?(_@PKWb`MoqWaZTbCq@27POl=v@0 zvR2Z}b<3_vce@(!ycr-(0 zxHi~xr*Jnx)@H+J@|B+XyH7!uEib$HjCyjGvXOT+{;6kaN=#TVHF=0^e;^qrRzvq* zw}MUi0*1YH^v!zHwhh~#=Nx+L_PybBdhUv*6gcxZ{EaF)pE>Ed`v7O~d603r`58OH zf$w>-oHyvD9--*Emy;;G>z?z@3?^rpz4t#YfPM%LCnro9&T{@n*raA_O?NVdCP$*S zJ)(C!)%fU+3o5EzEu$64auap?(5ut2td7TH!DL$nz3NMiyo6wfrmyi%NUE7_mVygN z3>AVcxj$w^RfV9X`Xe&-&+LGdv=I#4Q}p_3;(YxT zQafOA$z{N7ifO;+fw_DtC#=IB`(O_BOwM0;BUbR@^5@uA`p!{rOWjAK1#@t=)Pd;y zv}F9h-xYZRTpaHjN1yvOWb!wctj65!S#<-sZl#H&@Jd30?g)*GkV6>fz;>HV{m1IBG%V90h z(LYq?W!1e)uj}e77TbO2BXY>LO2$$y$;;LRr{2k$(RF9ukFCqnn|j$TQxSRJC(Zsp zEbHTNvE1Y3$MY&MWQyKcTZz2?Sgu9(pCv zd)ndt3Xn7uUK2jPa^)JEa{RsTAJbSdw6XT47)Q~FUmj@1e&j5%!RB25g|_gGfEO$y zva{*wafi6bqD*W>9l!JqRaeZHuIH&_DHBq1FF(W!s~}8@YP25q9^&+pDg38a@L0iP z7Q-L>ne2p+)L%2`#dnTc*G1`77*Hbon)m%1)EBXp`zJ@$6A4Rv|)UK66h8^-YVW7 zFx`U!Kwi}SH3RUF*qEWO1m;TX<3b@1A4jyl$ zFp@IXx|`<1Jt6NVHhSBm-UBV|pL4CnT z!Z?vBkfhu`VA&x_#?;=p>*OAz1yiN{r|d_)i$0Uu!7|Z_L~LSnWIDZLVI!|VzwEA3 z!G1A%2Pt7`Vz54QgO?sIeWAlMvIhz*-g@oMcB=JzUxo7z%$@X=>ghgCf{1bE`fQ)& zgG_L)R43S41!$swE7nH2t*JT|i{WtEB0LZ=`;n}l*qth;*}-H60T-JqK4%E=-wFP* z*-g|1?_Mt;ksA7qM99nZa?~M3WuJtk)96A%LQ68+a?|zpF63}DuZm}yf~k?BQ)bTBxX`eECrH9anqDps1fMW5|UvXwCkFwAp5wi;I0mOEz! z=zu1uUVQJZl7Rn~aM@sNXJS#2#lfCLA0C$|XP09)DOVZ%E(2vlE(-3UUOB=IL<>u5 zhb2s*uVqbQ%->Ez^P^gq{OI2SwZ8cGiOEwpXIE~-o%%9)lE(hYq$$rD4wvMzjTTM9 z;x}y(A~TaOJ)%wnEKEP~oL?iW^5Ii|SwJCe%;eDTT2WHGXqvI-TaY!E@j`7Op7i0H zOv~bm+TbL=6ATRjTLS>Lzx!In&i?q{n9JY60=eWhDC@+byMbqE(?D&;*II&cPKZyF z%WznDV~=EA+rdi05TNDaPZi zQBS`xV3f}_LT;B?BTCsNtx;uMj^MZ%l?+$jjFqb^?;S+%Ozx7--RlVG(w|5*Zzr)PduTMx~;s?-xz2FA* ztRv0ur*8Fn$d5E1HTq@GM?Z%9d6J?au(_OEyuA(Pu;TDD;h^FWF7kcNK7aiSyV!Z! zE%i+Y#Oou)CaH;Sx<8I5{0zuXf!mI1-7)iK5SXrncLvnH7oS?fVq%YWuNQxvx~bg{o;x+qw`p&Pzx$=Vno*g{l{7@Y z0QQ4>wXJf83j=uGU$I7mpBq?fex5bfTJ+3)rVyb9BV-SpPnXR*jaYik+X!9`SCduQSIOB(lf6?z`woO#ytauPCq6oYTFUNmF8cm zSyl~-t$4mO&F@jY7oZaUyBFc2$DP0arv2e}W%kPLyyD^{d`&)gm;)ZHpk z6y4&6$?HsxJe4I%(aAI`s}k+f=0ZBZcfG?U9Jog}FZ}uF4U)0csfY~ddV6nQkEpB9 zN$iL#xu@*HPJqH$mi&eXu^I&IuZs`OyUZr;)q^)Zr?t2$G^GRjcNAV4J9wh$tu`o4 za$lXjH~FdoH}sdGQaSOjtsfJ5r{NvA;EjuBySH5*&pM;&Io+ni#~ht#%*vNh?RfUb|pP$YxPYh`-D2E{?|{B{J3#t zSC^{B;#2EZa2)|`_}MiRlRK%mt4J=OBRkG}!MmAdo z@m2G`9kvtH83D=JfJS|kU>4-vP@-z9UT*jlhprmrWqRLeeyPi^v1()8LAK%TsUizxYxKj;0mLH?Ui` zdEg&Y!`SfuXQ(nu<{aqna8(j9wf}xSogQ-Vq#FEt0FG@sM{TbM&v-_u-zm5~#tZeX zvRAniLQ@qkjuV(iIZ4s+6V)y7;oT2?3^bSupzgdIQ2y~9ep6`PlFz)hYgbl3y2rN-r0t7o< zfu|2>r#8?=9(;#!vZ3$bg1DrGkTP5Kp=)KfD z$d}j$*cy%JgZNCKUjTSO&<`!$9c!KUXbV}ZKMT-V70r&C#hOPGw+}$8h!3rGpR#>u;c#F z)|@6(LszEB6GhDVBm!EqQ&Nc^7C4|f%G%_v#0M0=y?CBAwBn=WMPTu0$P3BQqCi}hP)L5OO# zwmw|C>a@OKPNAPTfeTcwYfr(**pP9XFIgh)sQ238;}EzgpjGewoi(ivb$A)Fc5Tx= zY9*WVU5j=lh3y4-_`8II$nZPo zdsirb=m;ts97JH~~y*fad?Q%*J&!QJLeDR;R_#UN_u5*lq& zHqaSW;#EmzgmI&Q>i@nytkZ9YoD>+f^eQU7yehRKJB0wy9ahVVNbSgj&wS|LZu8KX z@knqhC62~yO%kCg@B~CI-q;Qb@sR7;eyFH2bi-fwh#8=*-3PN8>-c$x+}G}fD;tAm zkJ%!C!lbv!z-L;e=_q(@_S+KWohk8NWc``u^wMj%=fvnzI*6kt#j}zx_`nY4OkvqTza+IL8*4irtEgtrRCOlMB51qu>+~H!r#l{5Z*f|1 z!BHJB-l#mTJPKhY*85MF>Dfuco!W8<20;q2s$@Uovb<7!i4cwx-Vib935rgBoq^I& zFETArt}5a%i1o9x(lreLgTCf}`%430?sUU?_j#@9kmv<^*b;m1%vc_B$*Q?_c9e3wC(8cLx>9h;V~T2Y;#`t^h!| zgTgyxZMsj@6!q5eqM~}ypJailb?)vVtnN*5B3rD)2Ldc3q2o66+^{HUoyVj$O(6AE z-tI>(v7~AOG&)rr+mBEQ;vMvAADWVQmoS5{TVxq^Gw;yea}jZ>=BJ6r9*(Ef8=UVq z$Gv?QlNL09&gm;oEB<{CION136rJ!o9d&aOPPvpb(HxWb@C##AjPW}ed*j9-+YfEk zN9rZH1k%Gl?j5FpymN!gca`=K8v4{wuxad=n?>$evxxilujXW%?;AZ8d%gb=j5QS3 z#@f5Kav0w}cI4{x`ITc*(Oxx_xcfPG=LahxI|Wc2aloehvT7Q7KKbhVMj(F5<~T|C zij$QhmnwcHCY^jde_nHKre7dN8@nqN^v@G)1}26AK2UcVrQRG_f6`lK%TL75-XC<2 zh>}`q0PFDWDb@nnk+n1LoBO5in~gX~zm}_|HmzuXsn5atTzIAQi zsubCNwd!CSr#DIG{BUU;CUpW#{TS!-CyVQ|@8t*2YO%HsX9~`K zmfmfn>%aqwI!Y|go(-;V7=ydn(0L?KZDYhs}Kys(6nJ4XRsQd87;JV_0feok>ylJd6Iu5%4U^@) zM3xoNh0mIu_s4`d0+%D5G|8SvctTs z;ndL7l@{oyZu7}quF@ibk+3`MRu>bxVNw=QH#0&RCIuHE)IZsjIxSDD_E_Qx7?O&u z9@!&x(!|BN4sx}?k8a_54({`P3P`XM0II?y;CEZ5+gY_v!rgRdTo7={xE>O!z$Tg_ zRB4j&EcQ1(gkkZiTLb-!!`mUJj$O#&{tbqz5ZK-dw$!+^IM#f7#>)Te)^xJmTiMH= zC{~!=p;Uy+m#fm2j!n~`L{NIa+g7h;sF%(1!IB&0jFpOdHu_9i_x;66JhG4Z>aQAw NmYN>4>X~iG{{t4%aOMC2 diff --git a/dev/tests/reference_ttf_iota.png b/dev/tests/reference_ttf_iota.png index 5f46ab656830c388bda017d949e6d90b49b5d5cf..a7b8f7b4b62cdb5c34e27f7bd6076744bbda7b0a 100644 GIT binary patch delta 375 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 bNnha!sCXhWlH(^v00000NkvXXu0mjf{3=c* diff --git a/rasterizer/Cargo.toml b/rasterizer/Cargo.toml index b029096..4711ce7 100644 --- a/rasterizer/Cargo.toml +++ b/rasterizer/Cargo.toml @@ -3,6 +3,11 @@ name = "ab_glyph_rasterizer" version = "0.1.0" authors = ["Alex Butler "] edition = "2018" +description = "Coverage rasterization for lines, quadratic & cubic beziers" +repository = "https://github.com/alexheretic/gfx-glyph" +keywords = ["text", "ttf", "otf", "font"] +license = "Apache-2.0" +readme="README.md" [dependencies] # no_std float stuff diff --git a/rasterizer/README.md b/rasterizer/README.md new file mode 100644 index 0000000..72337e6 --- /dev/null +++ b/rasterizer/README.md @@ -0,0 +1,51 @@ +ab_glyph_rasterizer +[![crates.io](https://img.shields.io/crates/v/ab_glyph_rasterizer.svg)](https://crates.io/crates/ab_glyph_rasterizer) +[![Documentation](https://docs.rs/ab_glyph_rasterizer/badge.svg)](https://docs.rs/ab_glyph_rasterizer) +=================== +Coverage rasterization for lines, quadratic & cubic beziers. +Useful for drawing .otf font glyphs. + +Inspired by [font-rs](https://github.com/raphlinus/font-rs) & +[stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h). + +## Example + +```rust +let mut rasterizer = ab_glyph_rasterizer::Rasterizer::new(106, 183); + +// draw a 300px 'ę' character +rasterizer.draw_cubic(point(103.0, 163.5), point(86.25, 169.25), point(77.0, 165.0), point(82.25, 151.5)); +rasterizer.draw_cubic(point(82.25, 151.5), point(86.75, 139.75), point(94.0, 130.75), point(102.0, 122.0)); +rasterizer.draw_line(point(102.0, 122.0), point(100.25, 111.25)); +rasterizer.draw_cubic(point(100.25, 111.25), point(89.0, 112.75), point(72.75, 114.25), point(58.5, 114.25)); +rasterizer.draw_cubic(point(58.5, 114.25), point(30.75, 114.25), point(18.5, 105.25), point(16.75, 72.25)); +rasterizer.draw_line(point(16.75, 72.25), point(77.0, 72.25)); +rasterizer.draw_cubic(point(77.0, 72.25), point(97.0, 72.25), point(105.25, 60.25), point(104.75, 38.5)); +rasterizer.draw_cubic(point(104.75, 38.5), point(104.5, 13.5), point(89.0, 0.75), point(54.25, 0.75)); +rasterizer.draw_cubic(point(54.25, 0.75), point(16.0, 0.75), point(0.0, 16.75), point(0.0, 64.0)); +rasterizer.draw_cubic(point(0.0, 64.0), point(0.0, 110.5), point(16.0, 128.0), point(56.5, 128.0)); +rasterizer.draw_cubic(point(56.5, 128.0), point(66.0, 128.0), point(79.5, 127.0), point(90.0, 125.0)); +rasterizer.draw_cubic(point(90.0, 125.0), point(78.75, 135.25), point(73.25, 144.5), point(70.75, 152.0)); +rasterizer.draw_cubic(point(70.75, 152.0), point(64.5, 169.0), point(75.5, 183.0), point(105.0, 170.5)); +rasterizer.draw_line(point(105.0, 170.5), point(103.0, 163.5)); +rasterizer.draw_cubic(point(55.0, 14.5), point(78.5, 14.5), point(88.5, 21.75), point(88.75, 38.75)); +rasterizer.draw_cubic(point(88.75, 38.75), point(89.0, 50.75), point(85.75, 59.75), point(73.5, 59.75)); +rasterizer.draw_line(point(73.5, 59.75), point(16.5, 59.75)); +rasterizer.draw_cubic(point(16.5, 59.75), point(17.25, 25.5), point(27.0, 14.5), point(55.0, 14.5)); +rasterizer.draw_line(point(55.0, 14.5), point(55.0, 14.5)); + +// iterate over the resultant pixel alphas, e.g. save pixel to a buffer +rasterizer.for_each_pixel(|index, alpha| { + // ... +}); +``` + +Rendering the resultant pixel alphas as 8-bit grey produces: + +![reference_otf_tailed_e](https://user-images.githubusercontent.com/2331607/78987793-ee95f480-7b26-11ea-91fb-e9f359d766f8.png) + +## no_std +no_std environments are supported using `alloc` & [`libm`](https://github.com/rust-lang/libm). +```toml +ab_glyph_rasterizer = { default-features = false, features = ["libm"] } +``` diff --git a/rasterizer/src/geometry.rs b/rasterizer/src/geometry.rs index ea8b36a..50bd7e9 100644 --- a/rasterizer/src/geometry.rs +++ b/rasterizer/src/geometry.rs @@ -1,14 +1,22 @@ +/// An (x, y) coordinate. #[derive(Clone, Copy, Debug, Default)] pub struct Point { pub x: f32, pub y: f32, } +/// [`Point`](struct.Point.html) constructor. +/// +/// ``` +/// use ab_glyph_rasterizer::{point, Point}; +/// let control: Point = point(0.1, 23.2); +/// ``` #[inline] pub fn point(x: f32, y: f32) -> Point { Point { x, y } } +/// Linear interpolation between points. #[inline] pub(crate) fn lerp(t: f32, p0: Point, p1: Point) -> Point { point(p0.x + t * (p1.x - p0.x), p0.y + t * (p1.y - p0.y)) diff --git a/rasterizer/src/lib.rs b/rasterizer/src/lib.rs index 88c57d2..e6870f4 100644 --- a/rasterizer/src/lib.rs +++ b/rasterizer/src/lib.rs @@ -1,3 +1,23 @@ +//! Coverage rasterization for lines, quadratic & cubic beziers. +//! Useful for drawing .otf font glyphs. +//! +//! ``` +//! use ab_glyph_rasterizer::Rasterizer; +//! # let (width, height) = (1, 1); +//! let mut rasterizer = Rasterizer::new(width, height); +//! +//! // draw outlines +//! # let [l0, l1, q0, q1, q2, c0, c1, c2, c3] = [ab_glyph_rasterizer::point(0.0, 0.0); 9]; +//! rasterizer.draw_line(l0, l1); +//! rasterizer.draw_quad(q0, q1, q2); +//! rasterizer.draw_cubic(c0, c1, c2, c3); +//! +//! // iterate over the resultant pixel alphas, e.g. save pixel to a buffer +//! rasterizer.for_each_pixel(|index, alpha| { +//! // ... +//! }); +//! ``` + #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(feature = "std"))] #[macro_use] @@ -9,5 +29,5 @@ mod nostd_float; mod geometry; mod raster; -pub use geometry::{Point, point}; +pub use geometry::{point, Point}; pub use raster::Rasterizer; diff --git a/rasterizer/src/nostd_float.rs b/rasterizer/src/nostd_float.rs index 1a650f0..8da4739 100644 --- a/rasterizer/src/nostd_float.rs +++ b/rasterizer/src/nostd_float.rs @@ -1,3 +1,4 @@ +/// Basic required float operations. pub(crate) trait FloatExt { fn floor(self) -> Self; fn ceil(self) -> Self; diff --git a/rasterizer/src/raster.rs b/rasterizer/src/raster.rs index 83bbde5..ab9dd3b 100644 --- a/rasterizer/src/raster.rs +++ b/rasterizer/src/raster.rs @@ -15,6 +15,12 @@ pub struct Rasterizer { } impl Rasterizer { + /// Allocates a new rasterizer that can draw onto a `width` x `height` alpha grid. + /// + /// ``` + /// use ab_glyph_rasterizer::Rasterizer; + /// let mut rasterizer = Rasterizer::new(14, 38); + /// ``` pub fn new(width: usize, height: usize) -> Self { Self { width, @@ -23,10 +29,24 @@ impl Rasterizer { } } + /// Returns the dimensions the rasterizer was built to draw to. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// let rasterizer = Rasterizer::new(9, 8); + /// assert_eq!((9, 8), rasterizer.dimensions()); + /// ``` pub fn dimensions(&self) -> (usize, usize) { (self.width, self.height) } + /// Adds a straight line from `p0` to `p1` to the outline. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// # let mut rasterizer = Rasterizer::new(9, 8); + /// rasterizer.draw_line(point(0.0, 0.48), point(1.22, 0.48)); + /// ``` pub fn draw_line(&mut self, p0: Point, p1: Point) { if (p0.y - p1.y).abs() < core::f32::EPSILON { return; @@ -80,6 +100,13 @@ impl Rasterizer { } } + /// Adds a quadratic Bézier curve from `p0` to `p2` to the outline using `p1` as the control. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// # let mut rasterizer = Rasterizer::new(14, 38); + /// rasterizer.draw_quad(point(6.2, 34.5), point(7.2, 34.5), point(9.2, 34.0)); + /// ``` pub fn draw_quad(&mut self, p0: Point, p1: Point, p2: Point) { let devx = p0.x - 2.0 * p1.x + p2.x; let devy = p0.y - 2.0 * p1.y + p2.y; @@ -102,6 +129,19 @@ impl Rasterizer { self.draw_line(p, p2); } + /// Adds a cubic Bézier curve from `p0` to `p3` to the outline using `p1` as the control + /// at the beginning of the curve and `p2` at the end of the curve. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// # let mut rasterizer = Rasterizer::new(12, 20); + /// rasterizer.draw_cubic( + /// point(10.3, 16.4), + /// point(8.6, 16.9), + /// point(7.7, 16.5), + /// point(8.2, 15.2), + /// ); + /// ``` pub fn draw_cubic(&mut self, p0: Point, p1: Point, p2: Point, p3: Point) { self.tesselate_cubic(p0, p1, p2, p3, 0); } @@ -144,6 +184,17 @@ impl Rasterizer { } } + /// Run a callback for each pixel index & alpha, with indices in `0..width * height`. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// # let (width, height) = (1, 1); + /// # let mut rasterizer = Rasterizer::new(width, height); + /// let mut pixels = vec![0u8; width * height]; + /// rasterizer.for_each_pixel(|index, alpha| { + /// pixels[index] = (alpha * 255.0).round() as u8; + /// }); + /// ``` pub fn for_each_pixel(&self, mut px_fn: O) { let mut acc = 0.0; self.a[..self.width * self.height] @@ -155,12 +206,31 @@ impl Rasterizer { }); } + /// Run a callback for each pixel x position, y position & alpha. + /// + /// Convenience wrapper for `for_each_pixel`. + /// + /// ``` + /// # use ab_glyph_rasterizer::*; + /// # let (width, height) = (1, 1); + /// # let mut rasterizer = Rasterizer::new(width, height); + /// # struct Img; + /// # impl Img { fn set_pixel(&self, x: u32, y: u32, a: u8) {} } + /// # let image = Img; + /// rasterizer.for_each_pixel_2d(|x, y, alpha| { + /// image.set_pixel(x, y, (alpha * 255.0).round() as u8); + /// }); + /// ``` pub fn for_each_pixel_2d(&self, mut px_fn: O) { let width32 = self.width as u32; self.for_each_pixel(|idx, alpha| px_fn(idx as u32 % width32, idx as u32 / width32, alpha)); } } +/// ``` +/// let rasterizer = ab_glyph_rasterizer::Rasterizer::new(3, 4); +/// assert_eq!(&format!("{:?}", rasterizer), "Rasterizer { width: 3, height: 4 }"); +/// ``` impl core::fmt::Debug for Rasterizer { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("Rasterizer") diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7d2cf54 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +merge_imports = true