pax_global_header00006660000000000000000000000064141135207160014512gustar00rootroot0000000000000052 comment=30b21fa3e7d982bccdef3b9e253ceceba5e92498 chafa-1.8.0/000077500000000000000000000000001411352071600125625ustar00rootroot00000000000000chafa-1.8.0/.gitignore000066400000000000000000000006461411352071600145600ustar00rootroot00000000000000*~ \#*\# *.la *.lo *.o .deps .libs Makefile Makefile.in aclocal.m4 ar-lib autom4te.cache chafa.pc chafa/chafa-term-seq-doc.h chafa/chafaconfig.h chafa/chafaconfig-stamp compile config.guess config.h config.h.in config.log config.status config.sub configure depcomp docs/html docs/version.xml docs/xml gtk-doc.make install-sh libtool ltmain.sh m4 missing stamp-h1 tests/term-info-test tools/chafa/chafa test demo resource chafa-1.8.0/.travis.yml000066400000000000000000000010111411352071600146640ustar00rootroot00000000000000language: c compiler: - clang - gcc before_install: - sudo apt-get install -qq -y automake libtool libglib2.0-dev libmagickwand-dev gtk-doc-tools docbook-xml libxml2-utils # Needed for ImageMagick/clang runtime not finding libomp.so - export LD_LIBRARY_PATH=$(if [[ $CC == "clang" ]]; then echo -n '/usr/local/clang/lib'; fi) script: mkdir build && cd build && ../autogen.sh --prefix=/usr --enable-gtk-doc --enable-man && make -j4 && sudo make install && chafa --version && cd ../tests && ./postinstall.sh chafa-1.8.0/AUTHORS000066400000000000000000000016431411352071600136360ustar00rootroot00000000000000Chafa is principally written and maintained by Hans Petter Jansson . Feel free to contact him with any questions and comments. There is a growing list of contributors; these can be found in the git log. For a complete list, you could try: git log --pretty="format:%an <%ae>" | sort -f | uniq Per 2021-08-31, this yields the following (sans duplicates): alkahest begasus Biswapriyo Nath Daniel Eklöf Emanuel Haupt Felix Yan Hans Petter Jansson Michael Vetter Mo Zhou Ricardo Arguello Robert-André Mauchin Roman Wagner Sotiris Papatheodorou Tim Gates Øyvind Kolås chafa-1.8.0/COPYING000066400000000000000000001045151411352071600136230ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . chafa-1.8.0/COPYING.LESSER000066400000000000000000000167441411352071600146250ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. chafa-1.8.0/HACKING000066400000000000000000000064121411352071600135540ustar00rootroot00000000000000How to hack on Chafa ==================== Code formatting and structure ----------------------------- The code is mostly C99 with limited use of extensions. It should compile with most standards-compliant C compilers released in the last couple of years. GLib is our primary support library, and the code is in general very GLib-y. We use only the base library, no GObject or GIO. Formatting is done with spaces (no tabs) and four-space indenting stops. The directory layout is as follows: * Top level ............ Build scripts, README, etc. |- chafa ............. The Chafa library. All exported APIs are here. | `- internal ....... Chafa library internals. Internal APIs. | `- smolscale ... Private copy of a pixmap scaling library. |- docs .............. Built documentation (API and man pages). |- libnsgif .......... Private copy of a GIF library, used by tools. `- tools ............. Command-line tools. |- chafa .......... The Chafa command-line graphics viewer. `- fontgen ........ Experimental font generator. Making source releases ---------------------- Releases are made as compressed, signed tar archives ("tarballs"). We use semantic versioning. The following can be done multiple times and at any time during development, always on the master branch: 1) Write/edit NEWS section with a (TBA) placeholder for release date. Then right before the release, still on the master branch: 2) Update the soversion in chafa/Makefile.am (-version-info c:r:a): - If the library source code has changed at all since the last update, then increment revision (‘c:r:a’ becomes ‘c:r+1:a’). - If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. - If any interfaces have been added since the last public release, then increment age. - If any interfaces have been removed or changed since the last public release, then set age to 0. 3) If this is a minor (x.y.0) release, bump package to the next even version in configure.ac. 4) Make sure 'make distcheck' passes. Correct any issues. 5) Commit and push above changes. Wait for green CI and correct any issues. 6) Edit NEWS and replace (TBA) with today's date. 7) If this is a minor (x.y.0) release, edit README.md and update the CI links. They should reference master and the latest stable branch. 8) If this is a micro (x.y.z) release, switch to that release's maintenance branch (x.y) and cherry-pick all changes from the previous steps into it, then increment the micro version in configure.ac. 9) Commit above changes. 10) Tag and sign the release: 'git tag -s x.y.z'. Annotate with the appropriate NEWS item, without the --- underline for the heading. 11) If this is a minor (x.y.0) release, make a maintenance branch for it, rooted at the tag: 'git branch x.y'. But keep working on master. 12) Build tarball: 'make distcheck'. 13) Sign tarball: 'gpg --sign --detach --armor chafa-x.y.z.tar.xz'. 14) If this was a minor (x.y.0) release, bump package to the next odd version. 15) Commit the post-release version bump. 16) Push changes. Make sure to push tags and branches too. 17) Upload the tarball and signature to GitHub, and copy the NEWS item there. Add markdown formatting. That should do it. chafa-1.8.0/INSTALL000066400000000000000000000366141411352071600136250ustar00rootroot00000000000000Installation Instructions ************************* Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software Foundation, Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without warranty of any kind. Basic Installation ================== Briefly, the shell command './configure && make && make install' should configure, build, and install this package. The following more-detailed instructions are generic; see the 'README' file for instructions specific to this package. Some packages provide this 'INSTALL' file but do not implement all of the features documented below. The lack of an optional feature in a given package is not necessarily a bug. More recommendations for GNU packages can be found in *note Makefile Conventions: (standards)Makefile Conventions. The 'configure' shell script attempts to guess correct values for various system-dependent variables used during compilation. It uses those values to create a 'Makefile' in each directory of the package. It may also create one or more '.h' files containing system-dependent definitions. Finally, it creates a shell script 'config.status' that you can run in the future to recreate the current configuration, and a file 'config.log' containing compiler output (useful mainly for debugging 'configure'). It can also use an optional file (typically called 'config.cache' and enabled with '--cache-file=config.cache' or simply '-C') that saves the results of its tests to speed up reconfiguring. Caching is disabled by default to prevent problems with accidental use of stale cache files. If you need to do unusual things to compile the package, please try to figure out how 'configure' could check whether to do them, and mail diffs or instructions to the address given in the 'README' so they can be considered for the next release. If you are using the cache, and at some point 'config.cache' contains results you don't want to keep, you may remove or edit it. The file 'configure.ac' (or 'configure.in') is used to create 'configure' by a program called 'autoconf'. You need 'configure.ac' if you want to change it or regenerate 'configure' using a newer version of 'autoconf'. The simplest way to compile this package is: 1. 'cd' to the directory containing the package's source code and type './configure' to configure the package for your system. Running 'configure' might take a while. While running, it prints some messages telling which features it is checking for. 2. Type 'make' to compile the package. 3. Optionally, type 'make check' to run any self-tests that come with the package, generally using the just-built uninstalled binaries. 4. Type 'make install' to install the programs and any data files and documentation. When installing into a prefix owned by root, it is recommended that the package be configured and built as a regular user, and only the 'make install' phase executed with root privileges. 5. Optionally, type 'make installcheck' to repeat any self-tests, but this time using the binaries in their final installed location. This target does not install anything. Running this target as a regular user, particularly if the prior 'make install' required root privileges, verifies that the installation completed correctly. 6. You can remove the program binaries and object files from the source code directory by typing 'make clean'. To also remove the files that 'configure' created (so you can compile the package for a different kind of computer), type 'make distclean'. There is also a 'make maintainer-clean' target, but that is intended mainly for the package's developers. If you use it, you may have to get all sorts of other programs in order to regenerate files that came with the distribution. 7. Often, you can also type 'make uninstall' to remove the installed files again. In practice, not all packages have tested that uninstallation works correctly, even though it is required by the GNU Coding Standards. 8. Some packages, particularly those that use Automake, provide 'make distcheck', which can by used by developers to test that all other targets like 'make install' and 'make uninstall' work correctly. This target is generally not run by end users. Compilers and Options ===================== Some systems require unusual options for compilation or linking that the 'configure' script does not know about. Run './configure --help' for details on some of the pertinent environment variables. You can give 'configure' initial values for configuration parameters by setting variables in the command line or in the environment. Here is an example: ./configure CC=c99 CFLAGS=-g LIBS=-lposix *Note Defining Variables::, for more details. Compiling For Multiple Architectures ==================================== You can compile the package for more than one kind of computer at the same time, by placing the object files for each architecture in their own directory. To do this, you can use GNU 'make'. 'cd' to the directory where you want the object files and executables to go and run the 'configure' script. 'configure' automatically checks for the source code in the directory that 'configure' is in and in '..'. This is known as a "VPATH" build. With a non-GNU 'make', it is safer to compile the package for one architecture at a time in the source code directory. After you have installed the package for one architecture, use 'make distclean' before reconfiguring for another architecture. On MacOS X 10.5 and later systems, you can create libraries and executables that work on multiple system types--known as "fat" or "universal" binaries--by specifying multiple '-arch' options to the compiler but only a single '-arch' option to the preprocessor. Like this: ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ CPP="gcc -E" CXXCPP="g++ -E" This is not guaranteed to produce working output in all cases, you may have to build one architecture at a time and combine the results using the 'lipo' tool if you have problems. Installation Names ================== By default, 'make install' installs the package's commands under '/usr/local/bin', include files under '/usr/local/include', etc. You can specify an installation prefix other than '/usr/local' by giving 'configure' the option '--prefix=PREFIX', where PREFIX must be an absolute file name. You can specify separate installation prefixes for architecture-specific files and architecture-independent files. If you pass the option '--exec-prefix=PREFIX' to 'configure', the package uses PREFIX as the prefix for installing programs and libraries. Documentation and other data files still use the regular prefix. In addition, if you use an unusual directory layout you can give options like '--bindir=DIR' to specify different values for particular kinds of files. Run 'configure --help' for a list of the directories you can set and what kinds of files go in them. In general, the default for these options is expressed in terms of '${prefix}', so that specifying just '--prefix' will affect all of the other directory specifications that were not explicitly provided. The most portable way to affect installation locations is to pass the correct locations to 'configure'; however, many packages provide one or both of the following shortcuts of passing variable assignments to the 'make install' command line to change installation locations without having to reconfigure or recompile. The first method involves providing an override variable for each affected directory. For example, 'make install prefix=/alternate/directory' will choose an alternate location for all directory configuration variables that were expressed in terms of '${prefix}'. Any directories that were specified during 'configure', but not in terms of '${prefix}', must each be overridden at install time for the entire installation to be relocated. The approach of makefile variable overrides for each directory variable is required by the GNU Coding Standards, and ideally causes no recompilation. However, some platforms have known limitations with the semantics of shared libraries that end up requiring recompilation when using this method, particularly noticeable in packages that use GNU Libtool. The second method involves providing the 'DESTDIR' variable. For example, 'make install DESTDIR=/alternate/directory' will prepend '/alternate/directory' before all installation names. The approach of 'DESTDIR' overrides is not required by the GNU Coding Standards, and does not work on platforms that have drive letters. On the other hand, it does better at avoiding recompilation issues, and works well even when some directory options were not specified in terms of '${prefix}' at 'configure' time. Optional Features ================= If the package supports it, you can cause programs to be installed with an extra prefix or suffix on their names by giving 'configure' the option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'. Some packages pay attention to '--enable-FEATURE' options to 'configure', where FEATURE indicates an optional part of the package. They may also pay attention to '--with-PACKAGE' options, where PACKAGE is something like 'gnu-as' or 'x' (for the X Window System). The 'README' should mention any '--enable-' and '--with-' options that the package recognizes. For packages that use the X Window System, 'configure' can usually find the X include and library files automatically, but if it doesn't, you can use the 'configure' options '--x-includes=DIR' and '--x-libraries=DIR' to specify their locations. Some packages offer the ability to configure how verbose the execution of 'make' will be. For these packages, running './configure --enable-silent-rules' sets the default to minimal output, which can be overridden with 'make V=1'; while running './configure --disable-silent-rules' sets the default to verbose, which can be overridden with 'make V=0'. Particular systems ================== On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC is not installed, it is recommended to use the following options in order to use an ANSI C compiler: ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" and if that doesn't work, install pre-built binaries of GCC for HP-UX. HP-UX 'make' updates targets which have the same time stamps as their prerequisites, which makes it generally unusable when shipped generated files such as 'configure' are involved. Use GNU 'make' instead. On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its '' header file. The option '-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended to try ./configure CC="cc" and if that doesn't work, try ./configure CC="cc -nodtk" On Solaris, don't put '/usr/ucb' early in your 'PATH'. This directory contains several dysfunctional programs; working variants of these programs are available in '/usr/bin'. So, if you need '/usr/ucb' in your 'PATH', put it _after_ '/usr/bin'. On Haiku, software installed for all users goes in '/boot/common', not '/usr/local'. It is recommended to use the following options: ./configure --prefix=/boot/common Specifying the System Type ========================== There may be some features 'configure' cannot figure out automatically, but needs to determine by the type of machine the package will run on. Usually, assuming the package is built to be run on the _same_ architectures, 'configure' can figure that out, but if it prints a message saying it cannot guess the machine type, give it the '--build=TYPE' option. TYPE can either be a short name for the system type, such as 'sun4', or a canonical name which has the form: CPU-COMPANY-SYSTEM where SYSTEM can have one of these forms: OS KERNEL-OS See the file 'config.sub' for the possible values of each field. If 'config.sub' isn't included in this package, then this package doesn't need to know the machine type. If you are _building_ compiler tools for cross-compiling, you should use the option '--target=TYPE' to select the type of system they will produce code for. If you want to _use_ a cross compiler, that generates code for a platform different from the build platform, you should specify the "host" platform (i.e., that on which the generated programs will eventually be run) with '--host=TYPE'. Sharing Defaults ================ If you want to set default values for 'configure' scripts to share, you can create a site shell script called 'config.site' that gives default values for variables like 'CC', 'cache_file', and 'prefix'. 'configure' looks for 'PREFIX/share/config.site' if it exists, then 'PREFIX/etc/config.site' if it exists. Or, you can set the 'CONFIG_SITE' environment variable to the location of the site script. A warning: not all 'configure' scripts look for a site script. Defining Variables ================== Variables not defined in a site shell script can be set in the environment passed to 'configure'. However, some packages may run configure again during the build, and the customized values of these variables may be lost. In order to avoid this problem, you should set them in the 'configure' command line, using 'VAR=value'. For example: ./configure CC=/usr/local2/bin/gcc causes the specified 'gcc' to be used as the C compiler (unless it is overridden in the site shell script). Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an Autoconf limitation. Until the limitation is lifted, you can use this workaround: CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash 'configure' Invocation ====================== 'configure' recognizes the following options to control how it operates. '--help' '-h' Print a summary of all of the options to 'configure', and exit. '--help=short' '--help=recursive' Print a summary of the options unique to this package's 'configure', and exit. The 'short' variant lists options used only in the top level, while the 'recursive' variant lists options also present in any nested packages. '--version' '-V' Print the version of Autoconf used to generate the 'configure' script, and exit. '--cache-file=FILE' Enable the cache: use and save the results of the tests in FILE, traditionally 'config.cache'. FILE defaults to '/dev/null' to disable caching. '--config-cache' '-C' Alias for '--cache-file=config.cache'. '--quiet' '--silent' '-q' Do not print messages saying which checks are being made. To suppress all normal output, redirect it to '/dev/null' (any error messages will still be shown). '--srcdir=DIR' Look for the package's source code in directory DIR. Usually 'configure' can determine that directory automatically. '--prefix=DIR' Use DIR as the installation prefix. *note Installation Names:: for more details, including other options available for fine-tuning the installation locations. '--no-create' '-n' Run the configure checks, but stop before creating any output files. 'configure' also accepts some other, not widely useful, options. Run 'configure --help' for more details. chafa-1.8.0/Makefile.am000066400000000000000000000005401411352071600146150ustar00rootroot00000000000000SUBDIRS = chafa docs libnsgif tests tools EXTRA_DIST = \ HACKING \ README.md \ autogen.sh \ chafa.pc.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = chafa.pc all-local: @echo @echo --- @echo --- Success! You can now run tools/chafa/chafa, or install everything @echo --- using "make install" or "sudo make install". @echo --- @echo chafa-1.8.0/NEWS000066400000000000000000000263421411352071600132700ustar00rootroot00000000000000Chafa releases ============== 1.8.0 (2021-08-31) ------------------ This is a major feature release. * Added basic support for the Kitty and iTerm2 graphics protocols. These are enabled automatically when corresponding terminal support is detected, or manually with '-f kitty' or '-f iterm'. * Implemented an 8-color mode, selectable with '-c 8' (Øyvind Kolås). * Implemented a foreground-only switch, '--fg-only'. This produces character art using foreground colors only, and will avoid modifying or resetting the background color. Looks best with non-contiguous symbols (e.g. ascii). * Added builtins for Japanese kana fullwidth symbols. These can now be used without loading any external fonts (try '-c none --symbols wide'). * Added builtins for legacy computer symbols (mainly wedges and sextant blocks). These were widely used in PETSCII and Teletext, and can be enabled using --symbols or --fill with their respective tags: legacy, wedge and sextant (Øyvind Kolås). * Since there is a growing number of builtin symbols that may not be available everywhere, the default selection has been restricted to the widely supported block and border sets. * If possible, we now select a visually blank character from the specified symbol/fill sets instead of hardcoding ASCII space for featureless cells. One practical upshot of this is that the constant-width braille range can be used to produce consistent images even in contexts with variable-width fonts. U+2800 (BRAILLE PATTERN BLANK) will then be used in blank cells. * Improved terminal size detection when used with pipelines and redirection. This should now produce an image properly sized for your terminal: $ curl https://i.imgur.com/WFDEFVg.jpeg | chafa - | tee out * ChafaCanvas gained API functions for programmatically getting and setting character cell contents. These are used in a new example in tests/ncurses.c demonstrating ncurses integration. * Made --disable-rpath the default in order to simplify packaging. * Added a HACKING file featuring a much needed release checklist. * Improved terminal support: - Ctx will now use optimizing REP sequences at high -O levels. - foot now defaults to sixels (Daniel Eklöf). - iTerm2 now defaults to the iTerm2 protocol. - Kitty now defaults to the Kitty protocol. - st now defaults to truecolor symbols (Roman Wagner). * Bug fixes: github#44 Missing error handling on stdout writes (reported by Markus Elfring). [unfiled] Solid symbols erroneously replaced by fill in FGBG mode. [unfiled] Integer formatter was not using fast path for 8-bit values. [unfiled] Wrong default cell aspect used for sixel graphics. 1.6.1 (2021-06-03) ------------------ This is a bugfix release. * Add NOCONFIGURE variable to autogen.sh to skip configure (Biswapriyo Nath). * Bug fixes: github#50 SIGBUS while loading huge GIFs (reported by Grzegorz Koperwas). github#52 Produces small glitches in output with some images (reported by Sami Farin). github#54 Haiku port fails on 32bit (Luc Schrijvers). [unfiled] Exclude RTL code points that could break the output. [unfiled] Apple Terminal lacks truecolor support, so make it default to 256 colors (reported by Behdad Esfahbod). [unfiled] Fix typo affecting middle dot symbol. 1.6.0 (2021-01-15) ------------------ This release adds major features and important fixes to both libchafa and the chafa command-line tool. * Added support for fullwidth symbols that take up two character cells. These are common in East Asian scripts. Single-cell and double-cell symbols can be mixed, and -f symbols mode will use both if possible. * New symbol tags: Alpha, digit, alnum, narrow, wide, ambiguous, ugly, bad. "Ambiguous" symbols have uncertain widths and may render poorly in some terminals. "Ugly" denotes symbols that are unsuitable for Chafa's cell-based graphics (multicolor emoji, ideographic descriptors, etc). "Bad" is a superset of these two categories. Bad symbols are always excluded unless explicitly enabled with e.g. CHAFA_SYMBOL_TAG_BAD (--symbols +bad in the frontend). * The font loader (--glyph-file option) now does a better job with proportional fonts. * Added options for controlling lossless optimization of output. Currently, attribute reuse and character repetition (REP sequence) are implemented. * Added -O option to the frontend. This controls the optimization level. * Added a simple abstraction layer for terminal control sequences (ChafaTermInfo and ChafaTermDb). This allows for improved terminal support. * FbTerm is now supported with TERM=fbterm in the environment. * Bug fixes: github#43 Fix signal handler (reported by Markus Elfring). [unfiled] Crash when invalid font paths were passed on command line. [unfiled] Small typo in fontgen's README (Tim Gates). [unfiled] Bad contrast adjustment in images with transparency. 1.4.1 (2020-04-10) ------------------ This is a bugfix release. * Added configure option --disable-rpath. This allows packagers to prevent the hard-coded library search path from being embedded in the chafa command-line tool (Mo Zhou, github#39). * Added defaults for the yaft terminal. * Bug fixes: github#40 Fails to build on hurd-i386 + other i386 (reported by Mo Zhou). [unfiled] Rare failed assert with mostly transparent sixel image (reported by Reddit user spryfigure). [unfiled] Minor typo in function docstring. 1.4.0 (2020-04-01) ------------------ This release adds major features to both libchafa and the chafa command-line tool. * Added sixel graphics support. Chafa will automatically produce sixels if the connected terminal supports it. It can also be forced using the new -f or --format flag. This is a new implementation written from the ground up to maximize throughput. * Added the --glyph-file option, which loads glyph-symbol mappings from any file format supported by FreeType (TTF, PCF, etc). This allows for custom font support and improved symbol selection. * Added the --speed option specifying animation speed. It accepts a unitless multiplier, a specific number of frames per second, or "max" for maximum throughput. * There are now two ways to assign colors to symbol cells. Formerly, this was done based on the average color of the covered area. The new default is to use the median color, which produces sharper edges, but is slightly more prone to high-frequency noise. The new option --color-extractor selects the method to employ. * When displaying multiple files, the default delay between files has been changed from three seconds to zero. Animations will still play through once. This can be overridden on the chafa command line with -d or --duration. * Minor tweaks to built-in symbols. * Performance improvements: - Halved pixel storage requirements from 64 bits to 32 bits, resulting in significant memory savings. - Now builds with -ffast-math, yielding a big speedup for DIN99d. 1.2.2 (2020-03-02) ------------------ This minor release fixes a bug causing builds linking with libchafa to fail. * Bug fixes: github#34 Cannot compile example (found by Petr Bílek). 1.2.1 (2019-08-15) ------------------ This minor release fixes a few bugs and improves terminal detection. * Detect kitty and mlterm virtual terminals and apply optimal defaults accordingly. * Make Automake build more strict and fix a few compatibility nits. * Bug fixes: github#30 Failed to build on hurd-i386 (found by Mo Zhou). [unfiled] Crash on certain broken GIFs. 1.2.0 (2019-08-04) ------------------ This is a feature and performance release. * Added --dither, --dither-grain and --dither-intensity options. These allow for sub-cell dithering in quantized modes (none, 2, 16, 240 and 256). Especially beneficial when used with -p off. * Added API for ordered and diffusion-based dithering in quantization. * Added API support for multiple 8bpc pixel modes: RGB, BGR, RGBA, BGRA, ARGB, ABGR with either premultiplied or unassociated alpha channels. * Smooth scaling is now done internally, meaning libchafa consumers no longer need to prescale images. * Implemented a machine learning tool that can generate custom fonts for efficient image reproduction from a provided dataset. This is left uninstalled since it's still experimental (Mo Zhou). * Throughput improvements: - GIF animations now start playing instantly. - GIF CPU overhead reduced by 75%, peak memory use down 95%-99% (!). - XWD common case CPU overhead down 60-70%, peak memory down 70%. - Additional halving of CPU overhead in 240- and 256-color modes. - Streamlined pixel pipeline for better parallelization. * Installation instructions: - New: Guix (Guy Fleury Iteriteka). - New: openSUSE (Michael Vetter). * Bug fixes: [unfiled] Certain SVGs had transparency replaced with a white background (nemo). [unfiled] -lm dependency belongs in libchafa, not frontend. 1.0.1 (2018-12-18) ------------------ * Bug fixes: [unfiled] Bad geometry calculation when specifying one dimension and omitting the other. 1.0.0 (2018-12-16) ------------------ This release adds features, greatly improves performance and fixes several bugs. Programs written for the command line and libchafa interfaces in this release will be supported by future versions in the 1.y.z series without the need for modification or recompilation. * Added ability to specify "fill" symbols to use as halftone for better color approximation. This can be used to augment the regular symbol selection or to replace it (with --symbols none) for a different, purely intensity-based effect. * Improved preprocessing in 2-color and 16-color modes. This is optional (default: on) and consists of a contrast boost and, for the 16-color mode, an additional saturation boost. The new implementation lives in libchafa and does not rely on ImageMagick. It is multithreaded, and due to its specialized nature, much faster. * Added --watch option to continuously monitor a file. * Added more symbols: - Most of the ASCII range. - Braille range (github#2, thanks to Adam Borowski). - Miscellaneous math and scanline symbols. - More geometric symbols (black circle, triangles) (Mo Zhou). * Throughput improvements: - Fast symbol candidate set reduction by median cut. - Parallel processing with threads. - Prescaling using bilinear interpolation instead of lanczos. * Installation instructions: - New: Arch (github#12, Felix Yan). - New: Debian testing/unstable (github#9, Mo Zhou). - New: Fedora (github#14, Ricardo Arguello). - Mention missing dependency for Debian (github#13, @medusacle). * Bug fixes: github#1 -c none produces pointless \e[0m after every line (found by Adam Borowski). github#3 Compile error (found by Lajos Papp). github#4 AM_PROG_LIBTOOL is obsolete, replace it with LT_INIT (Robert-André Mauchin). github#5 Port chafa to i386 architecture (Mo Zhou). github#7 Chafa should not return an error when being asked for --version (Mo Zhou). github#10 Fix detection of MagickWand methods (Felix Yan). github#11 ImageMagick 7 support (found by Felix Yan). github#21 chafa.c:547: pointless assignment (found by @dcb314). 0.9.0 (2018-04-24) ------------------ Initial release. chafa-1.8.0/README000066400000000000000000000046751411352071600134560ustar00rootroot00000000000000Chafa ===== Chafa is a command-line utility that converts all kinds of images, including animated GIFs, into sixel or ANSI/Unicode character output that can be displayed in a terminal. It is highly configurable, with support for alpha transparency and multiple color modes and color spaces, combining selectable ranges of Unicode characters to produce the desired output. The core functionality is provided by a C library with a public, well-documented API. Both library and frontend tools are covered by the Lesser GPL license, version 3 or later (LGPLv3+). For the most up-to-date information, please see https://hpjansson.org/chafa/ Installing with package manager ------------------------------- Chafa is available as packages for many software distributions. A few are listed below, along with their command-line installation instructions: Arch Linux .... pacman -S chafa Brew .......... brew install chafa Debian ........ apt install chafa Fedora ........ dnf install chafa FreeBSD ....... pkg install chafa Gentoo ........ emerge media-gfx/chafa Guix .......... guix install chafa Kali Linux .... apt install chafa OpenBSD ....... pkg_add chafa openSUSE ...... zypper ar -f obs://graphics graphics && zypper in chafa Ubuntu ........ apt install chafa See https://hpjansson.org/chafa/download/ for more. Installing from tarball ----------------------- You will need GCC, make and the GLib development package installed to compile Chafa from a release tarball. If you want to build the command-line tool `chafa` and not just the library, you will additionally need the ImageMagick development packages. Prebuilt documentation is included in the release tarball, and you do not need gtk-doc unless you want to make changes/rebuild it. After unpacking, cd to the toplevel directory and issue the following shell commands: $ ./configure $ make $ sudo make install Installing from git repository ------------------------------ You will need GCC, make, Autoconf, Automake, Libtool and the GLib development package installed to compile Chafa from its git repository. If you want to build the command-line tool `chafa` and not just the library, you will additionally need the ImageMagick development packages. If you want to build documentation, you will also need gtk-doc. Start by cloning the repository: $ git clone https://github.com/hpjansson/chafa.git Then cd to the toplevel directory and issue the following shell commands: $ ./autogen.sh $ make $ sudo make install chafa-1.8.0/README.md000066400000000000000000000053401411352071600140430ustar00rootroot00000000000000


Master Build Status1.8 Build Status Latest Release

AboutGalleryPackagesDevelopment

Chafa is a command-line utility that converts all kinds of images, including animated GIFs, into sixel or ANSI/Unicode character output that can be displayed in a terminal. It is highly configurable, with support for alpha transparency and multiple color modes and color spaces, combining selectable ranges of Unicode characters to produce the desired output. The core functionality is provided by a C library with a public, well-documented API. Both library and frontend tools are covered by the Lesser GPL license, version 3 or later (LGPLv3+). The [official web pages](https://hpjansson.org/chafa/) and [C API documentation](https://hpjansson.org/chafa/ref/) can be found online. Check out the [gallery](https://hpjansson.org/chafa/gallery/) for screenshots. ## Installing Chafa is most likely packaged for your distribution, so if you're not going to hack on it, you're better off using [official packages](https://hpjansson.org/chafa/download/). If you want to build the latest and greatest yourself, read on. You will need GCC, make, Autoconf, Automake, Libtool and the GLib development package installed to compile Chafa from its git repository. If you want to build the command-line tool `chafa` and not just the library, you will additionally need the ImageMagick development packages. If you want to build documentation, you will also need gtk-doc. Start by cloning the repository: ```sh $ git clone https://github.com/hpjansson/chafa.git ``` Then cd to the toplevel directory and issue the following shell commands: ```sh $ ./autogen.sh $ make $ sudo make install ``` That should do it! chafa-1.8.0/TODO000066400000000000000000000053411411352071600132550ustar00rootroot00000000000000TODO ==== If you're particularly interested in any of this, send patches/pull requests or maybe just prod me a little. Minor Features/UX ----------------- - HTML output. - Symbols: Math. - Accept image on standard input. - Accept -o, --output to write to file. - Verbose output. Show file names. - Add a --test option to print a test page. - Add a --show-symbols op to print matching symbols. - Add a --ping-pong option for animations. - Strv building API in addition to GString? - If FG color is transparent, see if we can use an inverted symbol and swap with BG. - Avoid using transparent foreground due to XFCE Terminal (other terminals?) weird handling with background picture set? - Except in FGBG modes. - Test with more terminals. - iTerm? - Other OSX terminals? - PuTTY on Windows 8, 10? - Windows 7 fonts support half, solid, some borders. - Terminology. - Emulate tycat. - Emacs ansi-term. - Emacs shell (TERM=dumb). - Come up with some kind of support matrix. - More symbol aliases - CP437 Major features -------------- - Lossy/lossless intra-frame compression. Data rate regulated: - By desired output size. - By maximum desired per-cell error. - By total error? - Slide window over row, calculate mean colors, calc error? - Lossy/lossless delta compression. - Double-buffer with a checkpoint call to swap. - Dirty map not enough in case each frame is composited in multiple steps. - Emit difference between checkpoint state and current. - Optimization: Keep a rect or region of changed area. - Multiply previous symbol's new error with weight to increase or decrease stability (prevent flicker)? - Drawing context with clip rect/region, etc. - Potentially a context stack. - Getting into NCurses territory... - Video playback. - Interactive UI (may need to be in separate tool). Optimization ------------ - Avoid ImageMagick for remaining common formats like PNG, JPEG and directly use more efficient decoders instead. - Speed up alpha transparency code path. - Preload next image/frame in delay phase. - Don't calculate error if we're only using a single symbol (e.g. vhalf). The Fine Material ----------------- - Tips. - For scrolling, use e.g. chafa input.jpg -s 200 | less -S - Rate-controlled playback with e.g. cat input.txt | pv -qL 100k - Playback with awk + proper inter-frame delay. - X11 applications in terminal 1) Xvfb :99 -ac -fbdir t -screen 0 1440x900x24 & 2) DISPLAY=:99 lament -root & 3) chafa --watch t/Xvfb_screen0 - gnome-shell in terminal - XDG_SESSION_TYPE=x11 DISPLAY=:99 gnome-shell - Run as different user. - Using (unreleased) ffmpeg driver: - ./ffmpeg -i movie.mkv -pix_fmt rgba -f chafa -color 16 -symbols vhalf,space -fill ascii - chafa-1.8.0/acinclude.m4000066400000000000000000000073331411352071600147610ustar00rootroot00000000000000# From GLib. # # Checks the location of the XML Catalog # Usage: # JH_PATH_XML_CATALOG([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # Defines XMLCATALOG and XML_CATALOG_FILE substitutions AC_DEFUN([JH_PATH_XML_CATALOG], [ # check for the presence of the XML catalog AC_ARG_WITH([xml-catalog], AC_HELP_STRING([--with-xml-catalog=CATALOG], [path to xml catalog to use]),, [with_xml_catalog=/etc/xml/catalog]) jh_found_xmlcatalog=true XML_CATALOG_FILE="$with_xml_catalog" AC_SUBST([XML_CATALOG_FILE]) AC_MSG_CHECKING([for XML catalog ($XML_CATALOG_FILE)]) if test -f "$XML_CATALOG_FILE"; then AC_MSG_RESULT([found]) else jh_found_xmlcatalog=false AC_MSG_RESULT([not found]) fi # check for the xmlcatalog program AC_PATH_PROG(XMLCATALOG, xmlcatalog, no) if test "x$XMLCATALOG" = xno; then jh_found_xmlcatalog=false fi if $jh_found_xmlcatalog; then ifelse([$1],,[:],[$1]) else ifelse([$2],,[AC_MSG_ERROR([could not find XML catalog])],[$2]) fi ]) # From GLib. # # Checks if a particular URI appears in the XML catalog # Usage: # JH_CHECK_XML_CATALOG(URI, [FRIENDLY-NAME], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) AC_DEFUN([JH_CHECK_XML_CATALOG], [ AC_REQUIRE([JH_PATH_XML_CATALOG],[JH_PATH_XML_CATALOG(,[:])])dnl AC_MSG_CHECKING([for ifelse([$2],,[$1],[$2]) in XML catalog]) if $jh_found_xmlcatalog && \ AC_RUN_LOG([$XMLCATALOG --noout "$XML_CATALOG_FILE" "$1" >&2]); then AC_MSG_RESULT([found]) ifelse([$3],,,[$3 ])dnl else AC_MSG_RESULT([not found]) ifelse([$4],, [AC_MSG_ERROR([could not find ifelse([$2],,[$1],[$2]) in XML catalog])], [$4]) fi ]) # =========================================================================== # https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html # =========================================================================== # # SYNOPSIS # # AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) # # DESCRIPTION # # Check whether the given FLAG works with the current language's compiler # or gives an error. (Warnings, however, are ignored) # # ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on # success/failure. # # If EXTRA-FLAGS is defined, it is added to the current language's default # flags (e.g. CFLAGS) when the check is done. The check is thus made with # the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to # force the compiler to issue an error when a bad flag is given. # # INPUT gives an alternative input source to AC_COMPILE_IFELSE. # # NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this # macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. # # LICENSE # # Copyright (c) 2008 Guido U. Draheim # Copyright (c) 2011 Maarten Bosmans # # Copying and distribution of this file, with or without modification, are # permitted in any medium without royalty provided the copyright notice # and this notice are preserved. This file is offered as-is, without any # warranty. AC_DEFUN([AX_CHECK_COMPILE_FLAG], [AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], [AS_VAR_SET(CACHEVAR,[yes])], [AS_VAR_SET(CACHEVAR,[no])]) _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) AS_VAR_IF(CACHEVAR,yes, [m4_default([$2], :)], [m4_default([$3], :)]) AS_VAR_POPDEF([CACHEVAR])dnl ])dnl AX_CHECK_COMPILE_FLAGS chafa-1.8.0/autogen.sh000077500000000000000000000055551411352071600145750ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. srcdir=`dirname $0` test -z "$srcdir" && srcdir=. ORIGDIR=`pwd` cd $srcdir PROJECT=chafa TEST_TYPE=-f FILE=chafa/Makefile.am DIE=0 MISSING_TOOLS= MY_ECHO=$(which echo) [ x$MY_ECHO = x ] && MY_ECHO=echo (autoconf --version) < /dev/null > /dev/null 2>&1 || { MISSING_TOOLS="${MISSING_TOOLS}autoconf " DIE=1 } (automake --version) < /dev/null > /dev/null 2>&1 || { MISSING_TOOLS="${MISSING_TOOLS}automake " DIE=1 } (libtoolize --version) < /dev/null > /dev/null 2>&1 || { MISSING_TOOLS="${MISSING_TOOLS}libtool " DIE=1 } (pkg-config --version) < /dev/null > /dev/null 2>&1 || { MISSING_TOOLS="${MISSING_TOOLS}pkg-config " DIE=1 } if test "$DIE" -eq 1; then ${MY_ECHO} ${MY_ECHO} -e "Missing mandatory tools:\e[1;31m $MISSING_TOOLS" ${MY_ECHO} -e "\e[0m" ${MY_ECHO} "These are required for building Chafa from its git repository." ${MY_ECHO} "You should be able to install them using your operating system's" ${MY_ECHO} "package manager (apt-get, yum, zypper or similar). Alternately" ${MY_ECHO} "they can be obtained directly from GNU: https://ftp.gnu.org/gnu/" ${MY_ECHO} ${MY_ECHO} "If you can't provide these tools, you may still be able to" ${MY_ECHO} "build Chafa from a tarball release: https://hpjansson.org/chafa/releases/" ${MY_ECHO} fi GTKDOCIZE=$(which gtkdocize 2>/dev/null) if test -z $GTKDOCIZE; then ${MY_ECHO} -e "Missing optional tool:\e[1;33m gtk-doc" ${MY_ECHO} -e "\e[0m" ${MY_ECHO} "Without this, no developer documentation will be generated." ${MY_ECHO} rm -f gtk-doc.make cat > gtk-doc.make < /dev/null 2>&1 && autoheader ${MY_ECHO} "Running automake..." automake -a $am_opt ${MY_ECHO} "Running autoconf..." autoconf cd $ORIGDIR conf_flags="--enable-maintainer-mode" if test x$NOCONFIGURE = x; then ${MY_ECHO} Running $srcdir/configure $conf_flags "$@" ... $srcdir/configure $conf_flags "$@" || exit 1 else ${MY_ECHO} Skipping configure process. fi chafa-1.8.0/chafa.pc.in000066400000000000000000000004111411352071600145510ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: Chafa Description: Image to character art facsimile Requires: glib-2.0 Version: @VERSION@ Libs: -L${libdir} -lchafa -lm Cflags: -I${includedir}/chafa -I${libdir}/chafa/include chafa-1.8.0/chafa/000077500000000000000000000000001411352071600136245ustar00rootroot00000000000000chafa-1.8.0/chafa/Makefile.am000066400000000000000000000035301411352071600156610ustar00rootroot00000000000000SUBDIRS = internal DISTCLEANFILES = BUILT_SOURCES = ## --- Library --- lib_LTLIBRARIES = libchafa.la noinst_LTLIBRARIES = noinst_HEADERS = libchafa_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -DCHAFA_COMPILATION libchafa_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) -version-info 6:0:6 libchafa_la_LIBADD = $(GLIB_LIBS) internal/libchafa-internal.la -lm libchafa_la_SOURCES = \ chafa-canvas.c \ chafa-canvas-config.c \ chafa-features.c \ chafa-symbol-map.c \ chafa-term-db.c \ chafa-term-info.c \ chafa-util.c chafaincludedir=$(includedir)/chafa chafainclude_HEADERS = \ chafa.h \ chafa-canvas.h \ chafa-canvas-config.h \ chafa-common.h \ chafa-features.h \ chafa-symbol-map.h \ chafa-term-db.h \ chafa-term-info.h \ chafa-term-seq-def.h \ chafa-util.h \ chafa-version-macros.h # Generate header prototypes with docstrings (-CC to pass through comments) # for terminal sequence accessors. These will be processed by gtk-doc to # produce documentation. noinst_HEADERS += chafa-term-seq-doc.h chafa-term-seq-doc-in.h DISTCLEANFILES += chafa-term-seq-doc.h BUILT_SOURCES += chafa-term-seq-doc.h chafa-term-seq-doc.h: $(srcdir)/chafa-term-seq-doc-in.h $(CPP) -CC $< -o $@ # Generate chafaconfig.h # # The timestamp of the stamp file is used to indicate if chafaconfig.h is # up to date with respect to config.status. In the usual case, changes # to config.status will not result in changes to chafaconfig.h, so we # avoid touching its timestamp (so we don't rebuild the whole tree). DISTCLEANFILES += chafaconfig-stamp chafaconfig.h BUILT_SOURCES += chafaconfig-stamp configexecincludedir = $(libdir)/chafa/include nodist_configexecinclude_HEADERS = chafaconfig.h chafaconfig-stamp: ../config.status $(AM_V_GEN) cd $(top_builddir) && \ $(SHELL) ./config.status chafa/chafaconfig.h @touch chafaconfig-stamp ## -- General --- AM_CPPFLAGS = \ -I$(top_srcdir) chafa-1.8.0/chafa/chafa-canvas-config.c000066400000000000000000000670571411352071600175650ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* memset, memcpy */ #include "chafa.h" #include "internal/chafa-private.h" /** * SECTION:chafa-canvas-config * @title: ChafaCanvasConfig * @short_description: Describes a configuration for #ChafaCanvas * * A #ChafaCanvasConfig describes a set of parameters for #ChafaCanvas, such * as its geometry, color space and other output characteristics. * * To create a new #ChafaCanvasConfig, use chafa_canvas_config_new (). You * can then modify it using its setters, e.g. chafa_canvas_config_set_canvas_mode () * before assigning it to a new #ChafaCanvas with chafa_canvas_new (). * * Note that it is not possible to change a canvas' configuration after * the canvas is created. **/ /** * ChafaCanvasMode: * @CHAFA_CANVAS_MODE_TRUECOLOR: Truecolor. * @CHAFA_CANVAS_MODE_INDEXED_256: 256 colors. * @CHAFA_CANVAS_MODE_INDEXED_240: 256 colors, but avoid using the lower 16 whose values vary between terminal environments. * @CHAFA_CANVAS_MODE_INDEXED_16: 16 colors using the aixterm ANSI extension. * @CHAFA_CANVAS_MODE_INDEXED_8: 8 colors, compatible with original ANSI X3.64. * @CHAFA_CANVAS_MODE_FGBG_BGFG: Default foreground and background colors, plus inversion. * @CHAFA_CANVAS_MODE_FGBG: Default foreground and background colors. No ANSI codes will be used. * @CHAFA_CANVAS_MODE_MAX: Last supported canvas mode plus one. **/ /** * ChafaColorExtractor: * @CHAFA_COLOR_EXTRACTOR_AVERAGE: Use the average colors of each symbol's coverage area. * @CHAFA_COLOR_EXTRACTOR_MEDIAN: Use the median colors of each symbol's coverage area. * @CHAFA_COLOR_EXTRACTOR_MAX: Last supported color extractor plus one. **/ /** * ChafaColorSpace: * @CHAFA_COLOR_SPACE_RGB: RGB color space. Fast but imprecise. * @CHAFA_COLOR_SPACE_DIN99D: DIN99d color space. Slower, but good perceptual color precision. * @CHAFA_COLOR_SPACE_MAX: Last supported color space plus one. **/ /** * ChafaDitherMode: * @CHAFA_DITHER_MODE_NONE: No dithering. * @CHAFA_DITHER_MODE_ORDERED: Ordered dithering (Bayer or similar). * @CHAFA_DITHER_MODE_DIFFUSION: Error diffusion dithering (Floyd-Steinberg or similar). * @CHAFA_DITHER_MODE_MAX: Last supported dither mode plus one. **/ /** * ChafaPixelMode: * @CHAFA_PIXEL_MODE_SYMBOLS: Pixel data is approximated using character symbols ("ANSI art"). * @CHAFA_PIXEL_MODE_SIXELS: Pixel data is encoded as sixels. * @CHAFA_PIXEL_MODE_KITTY: Pixel data is encoded using the Kitty terminal protocol. * @CHAFA_PIXEL_MODE_ITERM2: Pixel data is encoded using the iTerm2 terminal protocol. * @CHAFA_PIXEL_MODE_MAX: Last supported pixel mode plus one. **/ /** * ChafaOptimizations: * @CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES: Suppress redundant SGR control sequences. * @CHAFA_OPTIMIZATION_SKIP_CELLS: Reserved for future use. * @CHAFA_OPTIMIZATION_REPEAT_CELLS: Use REP sequence to compress repeated runs of similar cells. * @CHAFA_OPTIMIZATION_NONE: All optimizations disabled. * @CHAFA_OPTIMIZATION_ALL: All optimizations enabled. **/ /* Private */ void chafa_canvas_config_init (ChafaCanvasConfig *canvas_config) { g_return_if_fail (canvas_config != NULL); memset (canvas_config, 0, sizeof (*canvas_config)); canvas_config->refs = 1; canvas_config->canvas_mode = CHAFA_CANVAS_MODE_TRUECOLOR; canvas_config->color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; canvas_config->color_space = CHAFA_COLOR_SPACE_RGB; canvas_config->pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; canvas_config->width = 80; canvas_config->height = 24; canvas_config->cell_width = 8; canvas_config->cell_height = 8; canvas_config->dither_mode = CHAFA_DITHER_MODE_NONE; canvas_config->dither_grain_width = 4; canvas_config->dither_grain_height = 4; canvas_config->dither_intensity = 1.0; canvas_config->fg_color_packed_rgb = 0xffffff; canvas_config->bg_color_packed_rgb = 0x000000; canvas_config->alpha_threshold = 127; canvas_config->work_factor = 0.5; canvas_config->preprocessing_enabled = TRUE; canvas_config->optimizations = CHAFA_OPTIMIZATION_ALL; canvas_config->fg_only_enabled = FALSE; chafa_symbol_map_init (&canvas_config->symbol_map); chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_BLOCK); chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_BORDER); chafa_symbol_map_add_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_SPACE); chafa_symbol_map_remove_by_tags (&canvas_config->symbol_map, CHAFA_SYMBOL_TAG_WIDE); chafa_symbol_map_init (&canvas_config->fill_symbol_map); } void chafa_canvas_config_deinit (ChafaCanvasConfig *canvas_config) { g_return_if_fail (canvas_config != NULL); chafa_symbol_map_deinit (&canvas_config->symbol_map); chafa_symbol_map_deinit (&canvas_config->fill_symbol_map); } void chafa_canvas_config_copy_contents (ChafaCanvasConfig *dest, const ChafaCanvasConfig *src) { g_return_if_fail (dest != NULL); g_return_if_fail (src != NULL); memcpy (dest, src, sizeof (*dest)); chafa_symbol_map_copy_contents (&dest->symbol_map, &src->symbol_map); chafa_symbol_map_copy_contents (&dest->fill_symbol_map, &src->fill_symbol_map); dest->refs = 1; } /* Public */ /** * chafa_canvas_config_new: * * Creates a new #ChafaCanvasConfig with default settings. This * object can later be used in the creation of a #ChafaCanvas. * * Returns: The new #ChafaCanvasConfig **/ ChafaCanvasConfig * chafa_canvas_config_new (void) { ChafaCanvasConfig *canvas_config; canvas_config = g_new (ChafaCanvasConfig, 1); chafa_canvas_config_init (canvas_config); return canvas_config; } /** * chafa_canvas_config_copy: * @config: A #ChafaSymbolMap to copy. * * Creates a new #ChafaCanvasConfig that's a copy of @config. * * Returns: The new #ChafaCanvasConfig **/ ChafaCanvasConfig * chafa_canvas_config_copy (const ChafaCanvasConfig *config) { ChafaCanvasConfig *new_config; new_config = g_new (ChafaCanvasConfig, 1); chafa_canvas_config_copy_contents (new_config, config); return new_config; } /** * chafa_canvas_config_ref: * @config: #ChafaCanvasConfig to add a reference to. * * Adds a reference to @config. **/ void chafa_canvas_config_ref (ChafaCanvasConfig *config) { gint refs; g_return_if_fail (config != NULL); refs = g_atomic_int_get (&config->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&config->refs); } /** * chafa_canvas_config_unref: * @config: #ChafaCanvasConfig to remove a reference from. * * Removes a reference from @config. **/ void chafa_canvas_config_unref (ChafaCanvasConfig *config) { gint refs; g_return_if_fail (config != NULL); refs = g_atomic_int_get (&config->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&config->refs)) { chafa_canvas_config_deinit (config); g_free (config); } } /** * chafa_canvas_config_get_geometry: * @config: A #ChafaCanvasConfig * @width_out: Location to store width in, or %NULL * @height_out: Location to store height in, or %NULL * * Returns @config's width and height in character cells in the * provided output locations. **/ void chafa_canvas_config_get_geometry (const ChafaCanvasConfig *config, gint *width_out, gint *height_out) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); if (width_out) *width_out = config->width; if (height_out) *height_out = config->height; } /** * chafa_canvas_config_set_geometry: * @config: A #ChafaCanvasConfig * @width: Width in character cells * @height: Height in character cells * * Sets @config's width and height in character cells to @width x @height. **/ void chafa_canvas_config_set_geometry (ChafaCanvasConfig *config, gint width, gint height) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (width > 0); g_return_if_fail (height > 0); config->width = width; config->height = height; } /** * chafa_canvas_config_get_cell_geometry: * @config: A #ChafaCanvasConfig * @cell_width_out: Location to store cell width in, or %NULL * @cell_height_out: Location to store cell height in, or %NULL * * Returns @config's cell width and height in pixels in the * provided output locations. * * Since: 1.4 **/ void chafa_canvas_config_get_cell_geometry (const ChafaCanvasConfig *config, gint *cell_width_out, gint *cell_height_out) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); if (cell_width_out) *cell_width_out = config->cell_width; if (cell_height_out) *cell_height_out = config->cell_height; } /** * chafa_canvas_config_set_cell_geometry: * @config: A #ChafaCanvasConfig * @cell_width: Cell width in pixels * @cell_height: Cell height in pixels * * Sets @config's cell width and height in pixels to @cell_width x @cell_height. * * Since: 1.4 **/ void chafa_canvas_config_set_cell_geometry (ChafaCanvasConfig *config, gint cell_width, gint cell_height) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (cell_width > 0); g_return_if_fail (cell_height > 0); config->cell_width = cell_width; config->cell_height = cell_height; } /** * chafa_canvas_config_get_canvas_mode: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaCanvasMode. This determines how colors (and * color control codes) are used in the output. * * Returns: The #ChafaCanvasMode. **/ ChafaCanvasMode chafa_canvas_config_get_canvas_mode (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_CANVAS_MODE_TRUECOLOR); g_return_val_if_fail (config->refs > 0, CHAFA_CANVAS_MODE_TRUECOLOR); return config->canvas_mode; } /** * chafa_canvas_config_set_canvas_mode: * @config: A #ChafaCanvasConfig * @mode: A #ChafaCanvasMode * * Sets @config's stored #ChafaCanvasMode to @mode. This determines how * colors (and color control codes) are used in the output. **/ void chafa_canvas_config_set_canvas_mode (ChafaCanvasConfig *config, ChafaCanvasMode mode) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (mode < CHAFA_CANVAS_MODE_MAX); config->canvas_mode = mode; } /** * chafa_canvas_config_get_color_extractor: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaColorExtractor. This determines how colors are * approximated in character symbol output. * * Returns: The #ChafaColorExtractor. * * Since: 1.4 **/ ChafaColorExtractor chafa_canvas_config_get_color_extractor (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_COLOR_EXTRACTOR_AVERAGE); g_return_val_if_fail (config->refs > 0, CHAFA_COLOR_EXTRACTOR_AVERAGE); return config->color_extractor; } /** * chafa_canvas_config_set_color_extractor: * @config: A #ChafaCanvasConfig * @color_extractor: A #ChafaColorExtractor * * Sets @config's stored #ChafaColorExtractor to @color_extractor. This * determines how colors are approximated in character symbol output. * * Since: 1.4 **/ void chafa_canvas_config_set_color_extractor (ChafaCanvasConfig *config, ChafaColorExtractor color_extractor) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (color_extractor < CHAFA_COLOR_EXTRACTOR_MAX); config->color_extractor = color_extractor; } /** * chafa_canvas_config_get_color_space: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaColorSpace. * * Returns: The #ChafaColorSpace. **/ ChafaColorSpace chafa_canvas_config_get_color_space (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_COLOR_SPACE_RGB); g_return_val_if_fail (config->refs > 0, CHAFA_COLOR_SPACE_RGB); return config->color_space; } /** * chafa_canvas_config_set_color_space: * @config: A #ChafaCanvasConfig * @color_space: A #ChafaColorSpace * * Sets @config's stored #ChafaColorSpace to @color_space. **/ void chafa_canvas_config_set_color_space (ChafaCanvasConfig *config, ChafaColorSpace color_space) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (color_space < CHAFA_COLOR_SPACE_MAX); config->color_space = color_space; } /** * chafa_canvas_config_peek_symbol_map: * @config: A #ChafaCanvasConfig * * Returns a pointer to the symbol map belonging to @config. * This can be inspected using the #ChafaSymbolMap getter * functions, but not changed. * * Returns: A pointer to the config's immutable symbol map **/ const ChafaSymbolMap * chafa_canvas_config_peek_symbol_map (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, NULL); g_return_val_if_fail (config->refs > 0, NULL); return &config->symbol_map; } /** * chafa_canvas_config_set_symbol_map: * @config: A #ChafaCanvasConfig * @symbol_map: A #ChafaSymbolMap * * Assigns a copy of @symbol_map to @config. **/ void chafa_canvas_config_set_symbol_map (ChafaCanvasConfig *config, const ChafaSymbolMap *symbol_map) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); chafa_symbol_map_deinit (&config->symbol_map); chafa_symbol_map_copy_contents (&config->symbol_map, symbol_map); } /** * chafa_canvas_config_peek_fill_symbol_map: * @config: A #ChafaCanvasConfig * * Returns a pointer to the fill symbol map belonging to @config. * This can be inspected using the #ChafaSymbolMap getter * functions, but not changed. * * Fill symbols are assigned according to their overall foreground to * background coverage, disregarding shape. * * Returns: A pointer to the config's immutable fill symbol map **/ const ChafaSymbolMap * chafa_canvas_config_peek_fill_symbol_map (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, NULL); g_return_val_if_fail (config->refs > 0, NULL); return &config->fill_symbol_map; } /** * chafa_canvas_config_set_fill_symbol_map: * @config: A #ChafaCanvasConfig * @fill_symbol_map: A #ChafaSymbolMap * * Assigns a copy of @fill_symbol_map to @config. **/ void chafa_canvas_config_set_fill_symbol_map (ChafaCanvasConfig *config, const ChafaSymbolMap *fill_symbol_map) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); chafa_symbol_map_deinit (&config->fill_symbol_map); chafa_symbol_map_copy_contents (&config->fill_symbol_map, fill_symbol_map); } /** * chafa_canvas_config_get_transparency_threshold: * @config: A #ChafaCanvasConfig * * Returns the threshold above which full transparency will be used. * * Returns: The transparency threshold [0.0 - 1.0] **/ gfloat chafa_canvas_config_get_transparency_threshold (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, 0.0); g_return_val_if_fail (config->refs > 0, 0.0); return 1.0 - (config->alpha_threshold / 256.0); } /** * chafa_canvas_config_set_transparency_threshold: * @config: A #ChafaCanvasConfig * @alpha_threshold: The transparency threshold [0.0 - 1.0]. * * Sets the threshold above which full transparency will be used. **/ void chafa_canvas_config_set_transparency_threshold (ChafaCanvasConfig *config, gfloat alpha_threshold) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (alpha_threshold >= 0.0); g_return_if_fail (alpha_threshold <= 1.0); /* Invert the scale; internally it's more like an opacity threshold */ config->alpha_threshold = 256.0 * (1.0 - alpha_threshold); } /** * chafa_canvas_config_get_fg_color: * @config: A #ChafaCanvasConfig * * Gets the assumed foreground color of the output device. This is used to * determine how to apply the foreground pen in FGBG modes. * * Returns: Foreground color as packed RGB triplet **/ guint32 chafa_canvas_config_get_fg_color (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, 0); g_return_val_if_fail (config->refs > 0, 0); return config->fg_color_packed_rgb; } /** * chafa_canvas_config_set_fg_color: * @config: A #ChafaCanvasConfig * @fg_color_packed_rgb: Foreground color as packed RGB triplet * * Sets the assumed foreground color of the output device. This is used to * determine how to apply the foreground pen in FGBG modes. **/ void chafa_canvas_config_set_fg_color (ChafaCanvasConfig *config, guint32 fg_color_packed_rgb) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->fg_color_packed_rgb = fg_color_packed_rgb; } /** * chafa_canvas_config_get_bg_color: * @config: A #ChafaCanvasConfig * * Gets the assumed background color of the output device. This is used to * determine how to apply the background pen in FGBG modes. * * Returns: Background color as packed RGB triplet **/ guint32 chafa_canvas_config_get_bg_color (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, 0); g_return_val_if_fail (config->refs > 0, 0); return config->bg_color_packed_rgb; } /** * chafa_canvas_config_set_bg_color: * @config: A #ChafaCanvasConfig * @bg_color_packed_rgb: Background color as packed RGB triplet * * Sets the assumed background color of the output device. This is used to * determine how to apply the background and transparency pens in FGBG modes, * and will also be substituted for partial transparency. **/ void chafa_canvas_config_set_bg_color (ChafaCanvasConfig *config, guint32 bg_color_packed_rgb) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->bg_color_packed_rgb = bg_color_packed_rgb; } /** * chafa_canvas_config_get_work_factor: * @config: A #ChafaCanvasConfig * * Gets the work/quality tradeoff factor. A higher value means more time * and memory will be spent towards a higher quality output. * * Returns: The work factor [0.0 - 1.0] **/ gfloat chafa_canvas_config_get_work_factor (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, 1); g_return_val_if_fail (config->refs > 0, 1); return config->work_factor; } /** * chafa_canvas_config_set_work_factor: * @config: A #ChafaCanvasConfig * @work_factor: Work factor [0.0 - 1.0] * * Sets the work/quality tradeoff factor. A higher value means more time * and memory will be spent towards a higher quality output. **/ void chafa_canvas_config_set_work_factor (ChafaCanvasConfig *config, gfloat work_factor) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (work_factor >= 0.0 && work_factor <= 1.0); config->work_factor = work_factor; } /** * chafa_canvas_config_get_preprocessing_enabled: * @config: A #ChafaCanvasConfig * * Queries whether automatic image preprocessing is enabled. This allows * Chafa to boost contrast and saturation in an attempt to improve * legibility. The type of preprocessing applied (if any) depends on the * canvas mode. * * Returns: Whether automatic preprocessing is enabled **/ gboolean chafa_canvas_config_get_preprocessing_enabled (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, FALSE); g_return_val_if_fail (config->refs > 0, FALSE); return config->preprocessing_enabled; } /** * chafa_canvas_config_set_preprocessing_enabled: * @config: A #ChafaCanvasConfig * @preprocessing_enabled: Whether automatic preprocessing should be enabled * * Indicates whether automatic image preprocessing should be enabled. This * allows Chafa to boost contrast and saturation in an attempt to improve * legibility. The type of preprocessing applied (if any) depends on the * canvas mode. **/ void chafa_canvas_config_set_preprocessing_enabled (ChafaCanvasConfig *config, gboolean preprocessing_enabled) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->preprocessing_enabled = preprocessing_enabled; } /** * chafa_canvas_config_get_dither_mode: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaDitherMode. * * Returns: The #ChafaDitherMode. * * Since: 1.2 **/ ChafaDitherMode chafa_canvas_config_get_dither_mode (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_DITHER_MODE_NONE); g_return_val_if_fail (config->refs > 0, CHAFA_DITHER_MODE_NONE); return config->dither_mode; } /** * chafa_canvas_config_set_dither_mode: * @config: A #ChafaCanvasConfig * @dither_mode: A #ChafaDitherMode * * Sets @config's stored #ChafaDitherMode to @dither_mode. * * Since: 1.2 **/ void chafa_canvas_config_set_dither_mode (ChafaCanvasConfig *config, ChafaDitherMode dither_mode) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (dither_mode < CHAFA_DITHER_MODE_MAX); config->dither_mode = dither_mode; } /** * chafa_canvas_config_get_dither_grain_size: * @config: A #ChafaCanvasConfig * @width_out: Pointer to a location to store grain width * @height_out: Pointer to a location to store grain height * * Returns @config's dither grain size in @width_out and @height_out. * * Since: 1.2 **/ void chafa_canvas_config_get_dither_grain_size (const ChafaCanvasConfig *config, gint *width_out, gint *height_out) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); if (width_out) *width_out = config->dither_grain_width; if (height_out) *height_out = config->dither_grain_height; } /** * chafa_canvas_config_set_dither_grain_size: * @config: A #ChafaCanvasConfig * @width: The desired grain width (1, 2, 4 or 8) * @height: The desired grain height (1, 2, 4 or 8) * * Sets @config's stored dither grain size to @width by @height pixels. These * values can be 1, 2, 4 or 8. 8 corresponds to the size of an entire * character cell. The default is 4 pixels by 4 pixels. * * Since: 1.2 **/ void chafa_canvas_config_set_dither_grain_size (ChafaCanvasConfig *config, gint width, gint height) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (width == 1 || width == 2 || width == 4 || width == 8); g_return_if_fail (height == 1 || height == 2 || height == 4 || height == 8); config->dither_grain_width = width; config->dither_grain_height = height; } /** * chafa_canvas_config_get_dither_intensity: * @config: A #ChafaCanvasConfig * * Returns the relative intensity of the dithering pattern applied during * image conversion. 1.0 is the default, corresponding to a moderate * intensity. * * Returns: The relative dithering intensity * * Since: 1.2 **/ gfloat chafa_canvas_config_get_dither_intensity (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, 1.0); g_return_val_if_fail (config->refs > 0, 1.0); return config->dither_intensity; } /** * chafa_canvas_config_set_dither_intensity: * @config: A #ChafaCanvasConfig * @intensity: Desired relative dithering intensity * * Sets @config's stored relative intensity of the dithering pattern applied * during image conversion. 1.0 is the default, corresponding to a moderate * intensity. Possible values range from 0.0 to infinity, but in practice, * values above 10.0 are rarely useful. * * Since: 1.2 **/ void chafa_canvas_config_set_dither_intensity (ChafaCanvasConfig *config, gfloat intensity) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (intensity >= 0.0); config->dither_intensity = intensity; } /** * chafa_canvas_config_get_pixel_mode: * @config: A #ChafaCanvasConfig * * Returns @config's #ChafaPixelMode. * * Returns: The #ChafaPixelMode. This determines how pixel graphics are * rendered in the output. * * Since: 1.4 **/ ChafaPixelMode chafa_canvas_config_get_pixel_mode (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_PIXEL_MODE_SYMBOLS); g_return_val_if_fail (config->refs > 0, CHAFA_PIXEL_MODE_SYMBOLS); return config->pixel_mode; } /** * chafa_canvas_config_set_pixel_mode: * @config: A #ChafaCanvasConfig * @pixel_mode: A #ChafaPixelMode * * Sets @config's stored #ChafaPixelMode to @pixel_mode. This determines * how pixel graphics are rendered in the output. * * Since: 1.4 **/ void chafa_canvas_config_set_pixel_mode (ChafaCanvasConfig *config, ChafaPixelMode pixel_mode) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); g_return_if_fail (pixel_mode < CHAFA_PIXEL_MODE_MAX); config->pixel_mode = pixel_mode; } /** * chafa_canvas_config_get_optimizations: * @config: A #ChafaCanvasConfig * * Returns @config's optimization flags. When enabled, these may produce * more compact output at the cost of reduced compatibility and increased CPU * use. Output quality is unaffected. * * Returns: The #ChafaOptimizations flags. * * Since: 1.6 **/ ChafaOptimizations chafa_canvas_config_get_optimizations (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_OPTIMIZATION_NONE); g_return_val_if_fail (config->refs > 0, CHAFA_OPTIMIZATION_NONE); return config->optimizations; } /** * chafa_canvas_config_set_optimizations: * @config: A #ChafaCanvasConfig * @optimizations: A combination of #ChafaOptimizations flags * * Sets @config's stored optimization flags. When enabled, these may produce * more compact output at the cost of reduced compatibility and increased CPU * use. Output quality is unaffected. * * Since: 1.6 **/ void chafa_canvas_config_set_optimizations (ChafaCanvasConfig *config, ChafaOptimizations optimizations) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->optimizations = optimizations; } /** * chafa_canvas_config_get_fg_only_enabled: * @config: A #ChafaCanvasConfig * * Queries whether to use foreground colors only, leaving the background * unmodified in the canvas output. This is relevant only when the * #ChafaPixelMode is set to #CHAFA_PIXEL_MODE_SYMBOLS. * * When this is set, the canvas will emit escape codes to set the foreground * color only. * * Returns: %TRUE if using foreground colors only, %FALSE otherwise. * * Since: 1.8 **/ gboolean chafa_canvas_config_get_fg_only_enabled (const ChafaCanvasConfig *config) { g_return_val_if_fail (config != NULL, CHAFA_OPTIMIZATION_NONE); g_return_val_if_fail (config->refs > 0, CHAFA_OPTIMIZATION_NONE); return config->fg_only_enabled; } /** * chafa_canvas_config_set_fg_only_enabled: * @config: A #ChafaCanvasConfig * @fg_only_enabled: Whether to use foreground colors only * * Indicates whether to use foreground colors only, leaving the background * unmodified in the canvas output. This is relevant only when the * #ChafaPixelMode is set to #CHAFA_PIXEL_MODE_SYMBOLS. * * When this is set, the canvas will emit escape codes to set the foreground * color only. * * Since: 1.8 **/ void chafa_canvas_config_set_fg_only_enabled (ChafaCanvasConfig *config, gboolean fg_only_enabled) { g_return_if_fail (config != NULL); g_return_if_fail (config->refs > 0); config->fg_only_enabled = fg_only_enabled; } chafa-1.8.0/chafa/chafa-canvas-config.h000066400000000000000000000161731411352071600175630ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_CANVAS_CONFIG_H__ #define __CHAFA_CANVAS_CONFIG_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif #include G_BEGIN_DECLS /* Color extractor */ typedef enum { CHAFA_COLOR_EXTRACTOR_AVERAGE, CHAFA_COLOR_EXTRACTOR_MEDIAN, CHAFA_COLOR_EXTRACTOR_MAX } ChafaColorExtractor; /* Color spaces */ typedef enum { CHAFA_COLOR_SPACE_RGB, CHAFA_COLOR_SPACE_DIN99D, CHAFA_COLOR_SPACE_MAX } ChafaColorSpace; /* Canvas modes */ typedef enum { CHAFA_CANVAS_MODE_TRUECOLOR, CHAFA_CANVAS_MODE_INDEXED_256, CHAFA_CANVAS_MODE_INDEXED_240, CHAFA_CANVAS_MODE_INDEXED_16, CHAFA_CANVAS_MODE_FGBG_BGFG, CHAFA_CANVAS_MODE_FGBG, CHAFA_CANVAS_MODE_INDEXED_8, CHAFA_CANVAS_MODE_MAX } ChafaCanvasMode; /* Dither modes */ typedef enum { CHAFA_DITHER_MODE_NONE, CHAFA_DITHER_MODE_ORDERED, CHAFA_DITHER_MODE_DIFFUSION, CHAFA_DITHER_MODE_MAX } ChafaDitherMode; /* Pixel modes */ typedef enum { CHAFA_PIXEL_MODE_SYMBOLS, CHAFA_PIXEL_MODE_SIXELS, CHAFA_PIXEL_MODE_KITTY, CHAFA_PIXEL_MODE_ITERM2, CHAFA_PIXEL_MODE_MAX } ChafaPixelMode; /* Sequence optimization flags. When enabled, these may produce more compact * output at the cost of reduced compatibility and increased CPU use. Output * quality is unaffected. */ typedef enum { CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES = (1 << 0), CHAFA_OPTIMIZATION_SKIP_CELLS = (1 << 1), CHAFA_OPTIMIZATION_REPEAT_CELLS = (1 << 2), CHAFA_OPTIMIZATION_NONE = 0, CHAFA_OPTIMIZATION_ALL = 0x7fffffff } ChafaOptimizations; /* Canvas config */ typedef struct ChafaCanvasConfig ChafaCanvasConfig; CHAFA_AVAILABLE_IN_ALL ChafaCanvasConfig *chafa_canvas_config_new (void); CHAFA_AVAILABLE_IN_ALL ChafaCanvasConfig *chafa_canvas_config_copy (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_ref (ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_unref (ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_get_geometry (const ChafaCanvasConfig *config, gint *width_out, gint *height_out); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_geometry (ChafaCanvasConfig *config, gint width, gint height); CHAFA_AVAILABLE_IN_1_4 void chafa_canvas_config_get_cell_geometry (const ChafaCanvasConfig *config, gint *cell_width_out, gint *cell_height_out); CHAFA_AVAILABLE_IN_1_4 void chafa_canvas_config_set_cell_geometry (ChafaCanvasConfig *config, gint cell_width, gint cell_height); CHAFA_AVAILABLE_IN_ALL ChafaCanvasMode chafa_canvas_config_get_canvas_mode (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_canvas_mode (ChafaCanvasConfig *config, ChafaCanvasMode mode); CHAFA_AVAILABLE_IN_1_4 ChafaColorExtractor chafa_canvas_config_get_color_extractor (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_4 void chafa_canvas_config_set_color_extractor (ChafaCanvasConfig *config, ChafaColorExtractor color_extractor); CHAFA_AVAILABLE_IN_ALL ChafaColorSpace chafa_canvas_config_get_color_space (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_color_space (ChafaCanvasConfig *config, ChafaColorSpace color_space); CHAFA_AVAILABLE_IN_ALL const ChafaSymbolMap *chafa_canvas_config_peek_symbol_map (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_symbol_map (ChafaCanvasConfig *config, const ChafaSymbolMap *symbol_map); CHAFA_AVAILABLE_IN_ALL const ChafaSymbolMap *chafa_canvas_config_peek_fill_symbol_map (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_fill_symbol_map (ChafaCanvasConfig *config, const ChafaSymbolMap *fill_symbol_map); CHAFA_AVAILABLE_IN_ALL gfloat chafa_canvas_config_get_transparency_threshold (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_transparency_threshold (ChafaCanvasConfig *config, gfloat alpha_threshold); CHAFA_AVAILABLE_IN_ALL guint32 chafa_canvas_config_get_fg_color (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_fg_color (ChafaCanvasConfig *config, guint32 fg_color_packed_rgb); CHAFA_AVAILABLE_IN_ALL guint32 chafa_canvas_config_get_bg_color (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_bg_color (ChafaCanvasConfig *config, guint32 bg_color_packed_rgb); CHAFA_AVAILABLE_IN_ALL gfloat chafa_canvas_config_get_work_factor (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_work_factor (ChafaCanvasConfig *config, gfloat work_factor); CHAFA_AVAILABLE_IN_ALL gboolean chafa_canvas_config_get_preprocessing_enabled (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_config_set_preprocessing_enabled (ChafaCanvasConfig *config, gboolean preprocessing_enabled); CHAFA_AVAILABLE_IN_1_2 ChafaDitherMode chafa_canvas_config_get_dither_mode (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_config_set_dither_mode (ChafaCanvasConfig *config, ChafaDitherMode dither_mode); CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_config_get_dither_grain_size (const ChafaCanvasConfig *config, gint *width_out, gint *height_out); CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_config_set_dither_grain_size (ChafaCanvasConfig *config, gint width, gint height); CHAFA_AVAILABLE_IN_1_2 gfloat chafa_canvas_config_get_dither_intensity (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_config_set_dither_intensity (ChafaCanvasConfig *config, gfloat intensity); CHAFA_AVAILABLE_IN_1_4 ChafaPixelMode chafa_canvas_config_get_pixel_mode (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_4 void chafa_canvas_config_set_pixel_mode (ChafaCanvasConfig *config, ChafaPixelMode pixel_mode); CHAFA_AVAILABLE_IN_1_6 ChafaOptimizations chafa_canvas_config_get_optimizations (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_6 void chafa_canvas_config_set_optimizations (ChafaCanvasConfig *config, ChafaOptimizations optimizations); CHAFA_AVAILABLE_IN_1_8 gboolean chafa_canvas_config_get_fg_only_enabled (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_1_8 void chafa_canvas_config_set_fg_only_enabled (ChafaCanvasConfig *config, gboolean fg_only_enabled); G_END_DECLS #endif /* __CHAFA_CANVAS_CONFIG_H__ */ chafa-1.8.0/chafa/chafa-canvas.c000066400000000000000000001774741411352071600163270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include "chafa.h" #include "internal/chafa-canvas-internal.h" #include "internal/chafa-canvas-printer.h" #include "internal/chafa-private.h" #include "internal/chafa-pixops.h" #include "internal/chafa-work-cell.h" #include "internal/smolscale/smolscale.h" /** * SECTION:chafa-canvas * @title: ChafaCanvas * @short_description: A canvas that renders to text * * A #ChafaCanvas is a canvas that can render its contents as text strings. * * To create a new #ChafaCanvas, use chafa_canvas_new (). If you want to * specify any parameters, like the geometry, color space and so on, you * must create a #ChafaCanvasConfig first. * * You can draw an image to the canvas using chafa_canvas_draw_all_pixels () * and create an ANSI text (or sixel) representation of the canvas' current * contents using chafa_canvas_build_ansi (). **/ /* Used for cell initialization. May be added up over multiple cells, so a * low multiple needs to fit in an integer. */ #define SYMBOL_ERROR_MAX (G_MAXINT / 8) /* Max candidates to consider in pick_symbol_and_colors_fast(). This is also * limited by a similar constant in chafa-symbol-map.c */ #define N_CANDIDATES_MAX 8 /* Dithering */ #define DITHER_BASE_INTENSITY_FGBG 1.0 #define DITHER_BASE_INTENSITY_8C 0.5 #define DITHER_BASE_INTENSITY_16C 0.25 #define DITHER_BASE_INTENSITY_256C 0.1 typedef struct { ChafaColorPair colors; gint error; } SymbolEval; typedef struct { ChafaColorPair colors; gint error [2]; } SymbolEval2; static ChafaColor threshold_alpha (ChafaColor col, gint alpha_threshold) { col.ch [3] = col.ch [3] < alpha_threshold ? 0 : 255; return col; } static gint color_to_rgb (const ChafaCanvas *canvas, ChafaColor col) { col = threshold_alpha (col, canvas->config.alpha_threshold); if (col.ch [3] == 0) return -1; return ((gint) col.ch [0] << 16) | ((gint) col.ch [1] << 8) | (gint) col.ch [2]; } static gint packed_rgba_to_rgb (const ChafaCanvas *canvas, guint32 rgba) { ChafaColor col; chafa_unpack_color (rgba, &col); return color_to_rgb (canvas, col); } static ChafaColor packed_rgb_to_color (gint rgb) { ChafaColor col; if (rgb < 0) { col.ch [0] = 0x80; col.ch [1] = 0x80; col.ch [2] = 0x80; col.ch [3] = 0x00; } else { col.ch [0] = (rgb >> 16) & 0xff; col.ch [1] = (rgb >> 8) & 0xff; col.ch [2] = rgb & 0xff; col.ch [3] = 0xff; } return col; } static guint32 packed_rgb_to_rgba (gint rgb) { ChafaColor col; col = packed_rgb_to_color (rgb); return chafa_pack_color (&col); } static gint16 packed_rgb_to_index (const ChafaPalette *palette, ChafaColorSpace cs, gint rgb) { ChafaColorCandidates ccand; ChafaColor col; if (rgb < 0) return CHAFA_PALETTE_INDEX_TRANSPARENT; col = packed_rgb_to_color (rgb); chafa_palette_lookup_nearest (palette, cs, &col, &ccand); return ccand.index [0]; } static guint32 transparent_cell_color (ChafaCanvasMode canvas_mode) { if (canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { const ChafaColor col = { { 0x80, 0x80, 0x80, 0x00 } }; return chafa_pack_color (&col); } else { return CHAFA_PALETTE_INDEX_TRANSPARENT; } } static void eval_symbol_colors (ChafaCanvas *canvas, ChafaWorkCell *wcell, const ChafaSymbol *sym, SymbolEval *eval) { if (canvas->config.color_extractor == CHAFA_COLOR_EXTRACTOR_AVERAGE) chafa_work_cell_get_mean_colors_for_symbol (wcell, sym, &eval->colors); else chafa_work_cell_get_median_colors_for_symbol (wcell, sym, &eval->colors); } static void eval_symbol_colors_wide (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, const ChafaSymbol *sym_a, const ChafaSymbol *sym_b, SymbolEval2 *eval) { SymbolEval part_eval [2]; eval_symbol_colors (canvas, wcell_a, sym_a, &part_eval [0]); eval_symbol_colors (canvas, wcell_b, sym_b, &part_eval [1]); eval->colors.colors [CHAFA_COLOR_PAIR_FG] = chafa_color_average_2 (part_eval [0].colors.colors [CHAFA_COLOR_PAIR_FG], part_eval [1].colors.colors [CHAFA_COLOR_PAIR_FG]); eval->colors.colors [CHAFA_COLOR_PAIR_BG] = chafa_color_average_2 (part_eval [0].colors.colors [CHAFA_COLOR_PAIR_BG], part_eval [1].colors.colors [CHAFA_COLOR_PAIR_BG]); } static gint calc_error_plain (const ChafaPixel *block, const ChafaColorPair *color_pair, const guint8 *cov) { gint error = 0; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { guint8 p = *cov++; const ChafaPixel *p0 = block++; error += chafa_color_diff_fast (&color_pair->colors [p], &p0->col); } return error; } static gint calc_error_with_alpha (const ChafaPixel *block, const ChafaColorPair *color_pair, const guint8 *cov, ChafaColorSpace cs) { gint error = 0; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { guint8 p = *cov++; const ChafaPixel *p0 = block++; error += chafa_color_diff_slow (&color_pair->colors [p], &p0->col, cs); } return error; } static void eval_symbol_error (ChafaCanvas *canvas, const ChafaWorkCell *wcell, const ChafaSymbol *sym, SymbolEval *eval) { const guint8 *covp = (guint8 *) &sym->coverage [0]; gint error; if (canvas->have_alpha) { error = calc_error_with_alpha (wcell->pixels, &eval->colors, covp, canvas->config.color_space); } else { #ifdef HAVE_SSE41_INTRINSICS if (chafa_have_sse41 ()) error = calc_error_sse41 (wcell->pixels, &eval->colors, covp); else #endif error = calc_error_plain (wcell->pixels, &eval->colors, covp); } eval->error = error; } static void eval_symbol_error_wide (ChafaCanvas *canvas, const ChafaWorkCell *wcell_a, const ChafaWorkCell *wcell_b, const ChafaSymbol2 *sym, SymbolEval2 *wide_eval) { SymbolEval eval [2]; eval [0].colors = wide_eval->colors; eval [1].colors = wide_eval->colors; eval_symbol_error (canvas, wcell_a, &sym->sym [0], &eval [0]); eval_symbol_error (canvas, wcell_b, &sym->sym [1], &eval [1]); wide_eval->error [0] = eval [0].error; wide_eval->error [1] = eval [1].error; } static void eval_symbol (ChafaCanvas *canvas, ChafaWorkCell *wcell, gint sym_index, gint *best_sym_index_out, SymbolEval *best_eval_inout) { const ChafaSymbol *sym; SymbolEval eval; sym = &canvas->config.symbol_map.symbols [sym_index]; if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.fg_only_enabled) { eval.colors = canvas->default_colors; } else { eval_symbol_colors (canvas, wcell, sym, &eval); } eval_symbol_error (canvas, wcell, sym, &eval); if (eval.error < best_eval_inout->error) { *best_sym_index_out = sym_index; *best_eval_inout = eval; } } static void eval_symbol_wide (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, gint sym_index, gint *best_sym_index_out, SymbolEval2 *best_eval_inout) { const ChafaSymbol2 *sym2; SymbolEval2 eval; sym2 = &canvas->config.symbol_map.symbols2 [sym_index]; if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.fg_only_enabled) { eval.colors = canvas->default_colors; } else { eval_symbol_colors_wide (canvas, wcell_a, wcell_b, &sym2->sym [0], &sym2->sym [1], &eval); } eval_symbol_error_wide (canvas, wcell_a, wcell_b, sym2, &eval); if (eval.error [0] + eval.error [1] < best_eval_inout->error [0] + best_eval_inout->error [1]) { *best_sym_index_out = sym_index; *best_eval_inout = eval; } } static void pick_symbol_and_colors_slow (ChafaCanvas *canvas, ChafaWorkCell *wcell, gunichar *sym_out, ChafaColorPair *color_pair_out, gint *error_out) { SymbolEval best_eval; gint best_symbol = -1; gint i; /* Find best symbol. All symbols are candidates. */ best_eval.error = SYMBOL_ERROR_MAX; for (i = 0; canvas->config.symbol_map.symbols [i].c != 0; i++) eval_symbol (canvas, wcell, i, &best_symbol, &best_eval); chafa_leave_mmx (); /* Make FPU happy again */ /* Output */ g_assert (best_symbol >= 0); if (canvas->config.fg_only_enabled) eval_symbol_colors (canvas, wcell, &canvas->config.symbol_map.symbols [best_symbol], &best_eval); *sym_out = canvas->config.symbol_map.symbols [best_symbol].c; *color_pair_out = best_eval.colors; if (error_out) *error_out = best_eval.error; } static void pick_symbol_and_colors_wide_slow (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, gunichar *sym_out, ChafaColorPair *color_pair_out, gint *error_a_out, gint *error_b_out) { SymbolEval2 best_eval; gint best_symbol = -1; gint i; /* Find best symbol. All symbols are candidates. */ best_eval.error [0] = best_eval.error [1] = SYMBOL_ERROR_MAX; for (i = 0; canvas->config.symbol_map.symbols2 [i].sym [0].c != 0; i++) eval_symbol_wide (canvas, wcell_a, wcell_b, i, &best_symbol, &best_eval); chafa_leave_mmx (); /* Make FPU happy again */ /* Output */ g_assert (best_symbol >= 0); if (canvas->config.fg_only_enabled) eval_symbol_colors_wide (canvas, wcell_a, wcell_b, &canvas->config.symbol_map.symbols2 [best_symbol].sym [0], &canvas->config.symbol_map.symbols2 [best_symbol].sym [1], &best_eval); *sym_out = canvas->config.symbol_map.symbols2 [best_symbol].sym [0].c; *color_pair_out = best_eval.colors; if (error_a_out) *error_a_out = best_eval.error [0]; if (error_b_out) *error_b_out = best_eval.error [1]; } static void pick_symbol_and_colors_fast (ChafaCanvas *canvas, ChafaWorkCell *wcell, gunichar *sym_out, ChafaColorPair *color_pair_out, gint *error_out) { ChafaColorPair color_pair; guint64 bitmap; ChafaCandidate candidates [N_CANDIDATES_MAX]; gint n_candidates = 0; SymbolEval best_eval; gint best_symbol; gint i; /* Generate short list of candidates */ if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG || canvas->config.fg_only_enabled) { color_pair = canvas->default_colors; } else { chafa_work_cell_get_contrasting_color_pair (wcell, &color_pair); } bitmap = chafa_work_cell_to_bitmap (wcell, &color_pair); n_candidates = CLAMP (canvas->work_factor_int, 1, N_CANDIDATES_MAX); chafa_symbol_map_find_candidates (&canvas->config.symbol_map, bitmap, canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.fg_only_enabled ? FALSE : TRUE, /* Consider inverted? */ candidates, &n_candidates); g_assert (n_candidates > 0); /* Find best candidate */ best_symbol = -1; best_eval.error = SYMBOL_ERROR_MAX; for (i = 0; i < n_candidates; i++) eval_symbol (canvas, wcell, candidates [i].symbol_index, &best_symbol, &best_eval); chafa_leave_mmx (); /* Make FPU happy again */ /* Output */ g_assert (best_symbol >= 0); if (canvas->config.fg_only_enabled) eval_symbol_colors (canvas, wcell, &canvas->config.symbol_map.symbols [best_symbol], &best_eval); *sym_out = canvas->config.symbol_map.symbols [best_symbol].c; *color_pair_out = best_eval.colors; if (error_out) *error_out = best_eval.error; } static void pick_symbol_and_colors_wide_fast (ChafaCanvas *canvas, ChafaWorkCell *wcell_a, ChafaWorkCell *wcell_b, gunichar *sym_out, ChafaColorPair *color_pair_out, gint *error_a_out, gint *error_b_out) { ChafaColorPair color_pair; guint64 bitmaps [2]; ChafaCandidate candidates [N_CANDIDATES_MAX]; gint n_candidates = 0; SymbolEval2 best_eval; gint best_symbol; gint i; /* Generate short list of candidates */ if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { color_pair = canvas->default_colors; } else { ChafaColorPair color_pair_part [2]; chafa_work_cell_get_contrasting_color_pair (wcell_a, &color_pair_part [0]); chafa_work_cell_get_contrasting_color_pair (wcell_b, &color_pair_part [1]); color_pair.colors [0] = chafa_color_average_2 (color_pair_part [0].colors [0], color_pair_part [1].colors [0]); color_pair.colors [1] = chafa_color_average_2 (color_pair_part [0].colors [1], color_pair_part [1].colors [1]); } bitmaps [0] = chafa_work_cell_to_bitmap (wcell_a, &color_pair); bitmaps [1] = chafa_work_cell_to_bitmap (wcell_b, &color_pair); n_candidates = CLAMP (canvas->work_factor_int, 1, N_CANDIDATES_MAX); chafa_symbol_map_find_wide_candidates (&canvas->config.symbol_map, bitmaps, canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.fg_only_enabled ? FALSE : TRUE, /* Consider inverted? */ candidates, &n_candidates); g_assert (n_candidates > 0); /* Find best candidate */ best_symbol = -1; best_eval.error [0] = best_eval.error [1] = SYMBOL_ERROR_MAX; for (i = 0; i < n_candidates; i++) eval_symbol_wide (canvas, wcell_a, wcell_b, candidates [i].symbol_index, &best_symbol, &best_eval); chafa_leave_mmx (); /* Make FPU happy again */ /* Output */ g_assert (best_symbol >= 0); if (canvas->config.fg_only_enabled) eval_symbol_colors_wide (canvas, wcell_a, wcell_b, &canvas->config.symbol_map.symbols2 [best_symbol].sym [0], &canvas->config.symbol_map.symbols2 [best_symbol].sym [1], &best_eval); *sym_out = canvas->config.symbol_map.symbols2 [best_symbol].sym [0].c; *color_pair_out = best_eval.colors; if (error_a_out) *error_a_out = best_eval.error [0]; if (error_b_out) *error_b_out = best_eval.error [1]; } static const ChafaColor * get_palette_color_with_color_space (ChafaCanvas *canvas, gint index, ChafaColorSpace cs) { if (index == CHAFA_PALETTE_INDEX_FG) return &canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG]; if (index == CHAFA_PALETTE_INDEX_BG) return &canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG]; if (index == CHAFA_PALETTE_INDEX_TRANSPARENT) return &canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG]; return chafa_get_palette_color_256 (index, cs); } static const ChafaColor * get_palette_color (ChafaCanvas *canvas, gint index) { return get_palette_color_with_color_space (canvas, index, canvas->config.color_space); } static void apply_fill_fg_only (ChafaCanvas *canvas, const ChafaWorkCell *wcell, ChafaCanvasCell *cell) { ChafaColor mean; ChafaColorCandidates ccand; gint fg_value, bg_value, mean_value; gint n_bits; ChafaCandidate sym_cand; gint n_sym_cands = 1; if (canvas->config.fill_symbol_map.n_symbols == 0) return; chafa_work_cell_calc_mean_color (wcell, &mean); if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { cell->fg_color = chafa_pack_color (&mean); } else { chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &mean, &ccand); cell->fg_color = ccand.index [0]; } cell->bg_color = transparent_cell_color (canvas->config.canvas_mode); /* FIXME: Do we care enough to weight channels properly here, or convert from DIN99d? * Output looks acceptable without. Would have to check if it makes a noticeable * difference. */ fg_value = (canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [0] + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [1] + canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [2]) / 3; bg_value = (canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [0] + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [1] + canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [2]) / 3; mean_value = (mean.ch [0] + mean.ch [1] + mean.ch [2]) / 3; n_bits = ((mean_value * 64) + 128) / 255; if (fg_value < bg_value) n_bits = 64 - n_bits; chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, n_bits, FALSE, &sym_cand, &n_sym_cands); cell->c = canvas->config.fill_symbol_map.symbols [sym_cand.symbol_index].c; } static void apply_fill (ChafaCanvas *canvas, const ChafaWorkCell *wcell, ChafaCanvasCell *cell) { ChafaColor mean; ChafaColor col [3]; ChafaColorCandidates ccand; ChafaCandidate sym_cand; gint n_sym_cands = 1; gint i, best_i = 0; gint error, best_error = G_MAXINT; if (canvas->config.fill_symbol_map.n_symbols == 0) return; chafa_work_cell_calc_mean_color (wcell, &mean); if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR) { cell->bg_color = cell->fg_color = chafa_pack_color (&mean); chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, 0, FALSE, /* Consider inverted? */ &sym_cand, &n_sym_cands); goto done; } else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_8) { chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &mean, &ccand); } else if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) { ccand.index [0] = CHAFA_PALETTE_INDEX_FG; ccand.index [1] = CHAFA_PALETTE_INDEX_BG; } else { g_assert_not_reached (); } col [0] = *get_palette_color (canvas, ccand.index [0]); col [1] = *get_palette_color (canvas, ccand.index [1]); /* In FGBG modes, background and transparency is the same thing. Make * sure we have two opaque colors for correct interpolation. */ if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG) col [1].ch [3] = 0xff; /* Make the primary color correspond to cell's BG pen, so mostly transparent * cells will get a transparent BG; terminals typically don't support * transparency in the FG pen. BG is also likely to cover a greater area. */ for (i = 0; i <= 64; i++) { col [2].ch [0] = (col [0].ch [0] * (64 - i) + col [1].ch [0] * i) / 64; col [2].ch [1] = (col [0].ch [1] * (64 - i) + col [1].ch [1] * i) / 64; col [2].ch [2] = (col [0].ch [2] * (64 - i) + col [1].ch [2] * i) / 64; col [2].ch [3] = (col [0].ch [3] * (64 - i) + col [1].ch [3] * i) / 64; error = chafa_color_diff_slow (&mean, &col [2], canvas->config.color_space); if (error < best_error) { /* In FGBG mode there's no way to invert or set the BG color, so * assign the primary color to FG pen instead. */ best_i = (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG ? 64 - i : i); best_error = error; } } chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, best_i, canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG || canvas->config.fg_only_enabled ? FALSE : TRUE, /* Consider inverted? */ &sym_cand, &n_sym_cands); /* If we end up with a featureless symbol (space or fill), make * FG color equal to BG. Don't do this in FGBG mode, as it does not permit * color manipulation. */ if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG) { if (best_i == 0) ccand.index [1] = ccand.index [0]; else if (best_i == 64) ccand.index [0] = ccand.index [1]; } if (sym_cand.is_inverted) { cell->fg_color = ccand.index [0]; cell->bg_color = ccand.index [1]; } else { cell->fg_color = ccand.index [1]; cell->bg_color = ccand.index [0]; } done: cell->c = canvas->config.fill_symbol_map.symbols [sym_cand.symbol_index].c; } static gint update_cell (ChafaCanvas *canvas, ChafaWorkCell *work_cell, ChafaCanvasCell *cell_out) { gunichar sym = 0; ChafaColorCandidates ccand; ChafaColorPair color_pair; gint sym_error; if (canvas->config.symbol_map.n_symbols == 0) return SYMBOL_ERROR_MAX; if (canvas->work_factor_int >= 8) pick_symbol_and_colors_slow (canvas, work_cell, &sym, &color_pair, &sym_error); else pick_symbol_and_colors_fast (canvas, work_cell, &sym, &color_pair, &sym_error); cell_out->c = sym; if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_8 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &color_pair.colors [CHAFA_COLOR_PAIR_FG], &ccand); cell_out->fg_color = ccand.index [0]; chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &color_pair.colors [CHAFA_COLOR_PAIR_BG], &ccand); cell_out->bg_color = ccand.index [0]; } else { cell_out->fg_color = chafa_pack_color (&color_pair.colors [CHAFA_COLOR_PAIR_FG]); cell_out->bg_color = chafa_pack_color (&color_pair.colors [CHAFA_COLOR_PAIR_BG]); } if (canvas->config.fg_only_enabled) cell_out->bg_color = transparent_cell_color (canvas->config.canvas_mode); /* FIXME: It would probably be better to do the fgbg/bgfg blank symbol check * from emit_ansi_fgbg_bgfg() here. */ return sym_error; } static void update_cells_wide (ChafaCanvas *canvas, ChafaWorkCell *work_cell_a, ChafaWorkCell *work_cell_b, ChafaCanvasCell *cell_a_out, ChafaCanvasCell *cell_b_out, gint *error_a_out, gint *error_b_out) { gunichar sym = 0; ChafaColorCandidates ccand; ChafaColorPair color_pair; *error_a_out = *error_b_out = SYMBOL_ERROR_MAX; if (canvas->config.symbol_map.n_symbols2 == 0) return; if (canvas->work_factor_int >= 8) pick_symbol_and_colors_wide_slow (canvas, work_cell_a, work_cell_b, &sym, &color_pair, error_a_out, error_b_out); else pick_symbol_and_colors_wide_fast (canvas, work_cell_a, work_cell_b, &sym, &color_pair, error_a_out, error_b_out); cell_a_out->c = sym; cell_b_out->c = 0; if (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_256 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_240 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_16 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_INDEXED_8 || canvas->config.canvas_mode == CHAFA_CANVAS_MODE_FGBG_BGFG) { chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &color_pair.colors [CHAFA_COLOR_PAIR_FG], &ccand); cell_a_out->fg_color = cell_b_out->fg_color = ccand.index [0]; chafa_palette_lookup_nearest (&canvas->palette, canvas->config.color_space, &color_pair.colors [CHAFA_COLOR_PAIR_BG], &ccand); cell_a_out->bg_color = cell_b_out->bg_color = ccand.index [0]; } else { cell_a_out->fg_color = cell_b_out->fg_color = chafa_pack_color (&color_pair.colors [CHAFA_COLOR_PAIR_FG]); cell_a_out->bg_color = cell_b_out->bg_color = chafa_pack_color (&color_pair.colors [CHAFA_COLOR_PAIR_BG]); } if (canvas->config.fg_only_enabled) cell_a_out->bg_color = cell_b_out->bg_color = transparent_cell_color (canvas->config.canvas_mode); } /* Number of entries in our cell ring buffer. This allows us to do lookback * and replace single-cell symbols with double-cell ones if it improves * the error value. */ #define N_BUF_CELLS 4 /* Calculate index after positive or negative wraparound(s) */ #define buf_cell_index(i) (((i) + N_BUF_CELLS * 64) % N_BUF_CELLS) static void update_cells_row (ChafaCanvas *canvas, gint row) { ChafaCanvasCell *cells; ChafaWorkCell work_cells [N_BUF_CELLS]; gint cell_errors [N_BUF_CELLS]; gint cx, cy; cells = &canvas->cells [row * canvas->config.width]; cy = row; for (cx = 0; cx < canvas->config.width; cx++) { gint buf_index = cx % N_BUF_CELLS; ChafaWorkCell *wcell = &work_cells [buf_index]; ChafaCanvasCell wide_cells [2]; gint wide_cell_errors [2]; memset (&cells [cx], 0, sizeof (cells [cx])); cells [cx].c = ' '; chafa_work_cell_init (wcell, canvas->pixels, canvas->width_pixels, cx, cy); cell_errors [buf_index] = update_cell (canvas, wcell, &cells [cx]); /* Try wide symbol */ /* FIXME: If we're overlapping the rightmost half of a wide symbol, * try to revert it to two regular symbols and overwrite the rightmost * one. */ if (cx >= 1 && cells [cx - 1].c != 0) { gint wide_buf_index [2]; wide_buf_index [0] = buf_cell_index (cx - 1); wide_buf_index [1] = buf_index; update_cells_wide (canvas, &work_cells [wide_buf_index [0]], &work_cells [wide_buf_index [1]], &wide_cells [0], &wide_cells [1], &wide_cell_errors [0], &wide_cell_errors [1]); if (wide_cell_errors [0] + wide_cell_errors [1] < cell_errors [wide_buf_index [0]] + cell_errors [wide_buf_index [1]]) { cells [cx - 1] = wide_cells [0]; cells [cx] = wide_cells [1]; cells [cx].c = 0; cell_errors [wide_buf_index [0]] = wide_cell_errors [0]; cell_errors [wide_buf_index [1]] = wide_cell_errors [1]; } } /* If we produced a featureless cell, try fill */ /* FIXME: Check popcount == 0 or == 64 instead of symbol char */ if (cells [cx].c != 0 && (cells [cx].c == ' ' || cells [cx].c == 0x2588 || cells [cx].fg_color == cells [cx].bg_color)) { if (canvas->config.fg_only_enabled) { apply_fill_fg_only (canvas, wcell, &cells [cx]); cells [cx].bg_color = transparent_cell_color (canvas->config.canvas_mode); } else { apply_fill (canvas, wcell, &cells [cx]); } } /* If cell is still featureless after fill, use blank_char consistently */ if (cells [cx].c != 0 && (cells [cx].c == ' ' || cells [cx].fg_color == cells [cx].bg_color)) { cells [cx].c = canvas->blank_char; /* Copy FG color from previous cell in order to avoid emitting * unnecessary control sequences changing it, but only if we're 100% * sure the "blank" char has no foreground features. It's safest to * permit this optimization only with ASCII space. */ if (canvas->blank_char == ' ' && cx > 0) cells [cx].fg_color = cells [cx - 1].fg_color; } } } typedef struct { gint row; } CellBuildWork; static void cell_build_worker (CellBuildWork *work, ChafaCanvas *canvas) { update_cells_row (canvas, work->row); g_slice_free (CellBuildWork, work); } static void update_cells (ChafaCanvas *canvas) { GThreadPool *thread_pool = g_thread_pool_new ((GFunc) cell_build_worker, canvas, g_get_num_processors (), FALSE, NULL); gint cy; for (cy = 0; cy < canvas->config.height; cy++) { CellBuildWork *work = g_slice_new (CellBuildWork); work->row = cy; g_thread_pool_push (thread_pool, work, NULL); } g_thread_pool_free (thread_pool, FALSE, TRUE); } static void differentiate_channel (guint8 *dest_channel, guint8 reference_channel, gint min_diff) { gint diff; diff = (gint) *dest_channel - (gint) reference_channel; if (diff >= -min_diff && diff <= 0) *dest_channel = MAX ((gint) reference_channel - min_diff, 0); else if (diff <= min_diff && diff >= 0) *dest_channel = MIN ((gint) reference_channel + min_diff, 255); } static void update_display_colors (ChafaCanvas *canvas) { ChafaColor fg_col; ChafaColor bg_col; chafa_unpack_color (canvas->config.fg_color_packed_rgb, &fg_col); chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_col); if (canvas->config.color_space == CHAFA_COLOR_SPACE_DIN99D) { chafa_color_rgb_to_din99d (&fg_col, &canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG]); chafa_color_rgb_to_din99d (&bg_col, &canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG]); } else { canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG] = fg_col; canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG] = bg_col; } canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [3] = 0xff; canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [3] = 0x00; /* When holding the BG, we need to compare against a consistent * foreground color for symbol selection by outline. 50% gray * yields acceptable results as a stand-in average of all possible * colors. The BG color can't be too similar, so push it away a * little if needed. This should work with both bright and dark * background colors, and the background color doesn't have to * be precise. * * We don't need to do this for monochrome modes, as they use the * FG/BG colors directly. */ if (canvas->config.fg_only_enabled && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG_BGFG) { gint i; chafa_unpack_color (0xff7f7f7f, &canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG]); for (i = 0; i < 3; i++) { differentiate_channel (&canvas->default_colors.colors [CHAFA_COLOR_PAIR_BG].ch [i], canvas->default_colors.colors [CHAFA_COLOR_PAIR_FG].ch [i], 5); } } } static void maybe_clear (ChafaCanvas *canvas) { gint i; if (!canvas->needs_clear) return; for (i = 0; i < canvas->config.width * canvas->config.height; i++) { ChafaCanvasCell *cell = &canvas->cells [i]; memset (cell, 0, sizeof (*cell)); cell->c = ' '; } } static void setup_palette (ChafaCanvas *canvas) { ChafaPaletteType pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; ChafaColor fg_col; ChafaColor bg_col; chafa_unpack_color (canvas->config.fg_color_packed_rgb, &fg_col); chafa_unpack_color (canvas->config.bg_color_packed_rgb, &bg_col); fg_col.ch [3] = 0xff; bg_col.ch [3] = 0x00; switch (chafa_canvas_config_get_canvas_mode (&canvas->config)) { case CHAFA_CANVAS_MODE_TRUECOLOR: pal_type = CHAFA_PALETTE_TYPE_DYNAMIC_256; break; case CHAFA_CANVAS_MODE_INDEXED_256: pal_type = CHAFA_PALETTE_TYPE_FIXED_256; break; case CHAFA_CANVAS_MODE_INDEXED_240: pal_type = CHAFA_PALETTE_TYPE_FIXED_240; break; case CHAFA_CANVAS_MODE_INDEXED_16: pal_type = CHAFA_PALETTE_TYPE_FIXED_16; break; case CHAFA_CANVAS_MODE_INDEXED_8: pal_type = CHAFA_PALETTE_TYPE_FIXED_8; break; case CHAFA_CANVAS_MODE_FGBG_BGFG: case CHAFA_CANVAS_MODE_FGBG: pal_type = CHAFA_PALETTE_TYPE_FIXED_FGBG; break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); } chafa_palette_init (&canvas->palette, pal_type); chafa_palette_set_color (&canvas->palette, CHAFA_PALETTE_INDEX_FG, &fg_col); chafa_palette_set_color (&canvas->palette, CHAFA_PALETTE_INDEX_BG, &bg_col); chafa_palette_set_alpha_threshold (&canvas->palette, canvas->config.alpha_threshold); chafa_palette_set_transparent_index (&canvas->palette, CHAFA_PALETTE_INDEX_TRANSPARENT); } static gunichar find_best_blank_char (ChafaCanvas *canvas) { ChafaCandidate candidates [N_CANDIDATES_MAX]; gint n_candidates; gunichar best_char = 0x20; /* Try space (0x20) first */ if (chafa_symbol_map_has_symbol (&canvas->config.symbol_map, 0x20) || chafa_symbol_map_has_symbol (&canvas->config.fill_symbol_map, 0x20)) return 0x20; n_candidates = N_CANDIDATES_MAX; chafa_symbol_map_find_fill_candidates (&canvas->config.fill_symbol_map, 0, FALSE, candidates, &n_candidates); if (n_candidates > 0) { best_char = canvas->config.fill_symbol_map.symbols [candidates [0].symbol_index].c; } else { n_candidates = N_CANDIDATES_MAX; chafa_symbol_map_find_candidates (&canvas->config.symbol_map, 0, FALSE, candidates, &n_candidates); if (n_candidates > 0) { best_char = canvas->config.symbol_map.symbols [candidates [0].symbol_index].c; } } return best_char; } /** * chafa_canvas_new: * @config: Configuration to use or %NULL for hardcoded defaults * * Creates a new canvas with the specified configuration. The * canvas makes a private copy of the configuration, so it will * not be affected by subsequent changes. * * Returns: The new canvas **/ ChafaCanvas * chafa_canvas_new (const ChafaCanvasConfig *config) { ChafaCanvas *canvas; gdouble dither_intensity = 1.0; if (config) { g_return_val_if_fail (config->width > 0, NULL); g_return_val_if_fail (config->height > 0, NULL); } chafa_init (); canvas = g_new0 (ChafaCanvas, 1); if (config) chafa_canvas_config_copy_contents (&canvas->config, config); else chafa_canvas_config_init (&canvas->config); canvas->refs = 1; if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) { /* ANSI art */ canvas->width_pixels = canvas->config.width * CHAFA_SYMBOL_WIDTH_PIXELS; canvas->height_pixels = canvas->config.height * CHAFA_SYMBOL_HEIGHT_PIXELS; } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) { /* Sixels */ canvas->width_pixels = canvas->config.width * canvas->config.cell_width; canvas->height_pixels = canvas->config.height * canvas->config.cell_height; /* Ensure height is the biggest multiple of 6 that will fit * in our cells. We don't want a fringe going outside our * bottom cell. */ canvas->height_pixels -= canvas->height_pixels % 6; } else /* CHAFA_PIXEL_MODE_KITTY or CHAFA_PIXEL_MODE_ITERM2 */ { canvas->width_pixels = canvas->config.width * canvas->config.cell_width; canvas->height_pixels = canvas->config.height * canvas->config.cell_height; } canvas->pixels = NULL; canvas->cells = g_new (ChafaCanvasCell, canvas->config.width * canvas->config.height); canvas->work_factor_int = canvas->config.work_factor * 10 + 0.5; canvas->needs_clear = TRUE; canvas->have_alpha = FALSE; chafa_symbol_map_prepare (&canvas->config.symbol_map); chafa_symbol_map_prepare (&canvas->config.fill_symbol_map); canvas->blank_char = find_best_blank_char (canvas); /* In truecolor mode we don't support any fancy color spaces for now, since * we'd have to convert back to RGB space when emitting control codes, and * the code for that has yet to be written. In palette modes we just use * the palette mappings. * * There is also no reason to dither in truecolor mode, _unless_ we're * producing sixels, which quantize to a dynamic palette. */ if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY || canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2 || (canvas->config.canvas_mode == CHAFA_CANVAS_MODE_TRUECOLOR && canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS)) { canvas->config.color_space = CHAFA_COLOR_SPACE_RGB; canvas->config.dither_mode = CHAFA_DITHER_MODE_NONE; } if (canvas->config.dither_mode == CHAFA_DITHER_MODE_ORDERED) { switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: dither_intensity = DITHER_BASE_INTENSITY_256C; break; case CHAFA_CANVAS_MODE_INDEXED_16: dither_intensity = DITHER_BASE_INTENSITY_16C; break; case CHAFA_CANVAS_MODE_INDEXED_8: dither_intensity = DITHER_BASE_INTENSITY_8C; break; case CHAFA_CANVAS_MODE_FGBG: case CHAFA_CANVAS_MODE_FGBG_BGFG: dither_intensity = DITHER_BASE_INTENSITY_FGBG; break; default: g_assert_not_reached (); break; } } chafa_dither_init (&canvas->dither, canvas->config.dither_mode, dither_intensity * canvas->config.dither_intensity, canvas->config.dither_grain_width, canvas->config.dither_grain_height); update_display_colors (canvas); setup_palette (canvas); return canvas; } /** * chafa_canvas_new_similar: * @orig: Canvas to copy configuration from * * Creates a new canvas configured similarly to @orig. * * Returns: The new canvas **/ ChafaCanvas * chafa_canvas_new_similar (ChafaCanvas *orig) { ChafaCanvas *canvas; g_return_val_if_fail (orig != NULL, NULL); canvas = g_new (ChafaCanvas, 1); memcpy (canvas, orig, sizeof (*canvas)); canvas->refs = 1; chafa_canvas_config_copy_contents (&canvas->config, &orig->config); canvas->pixels = NULL; canvas->cells = g_new (ChafaCanvasCell, canvas->config.width * canvas->config.height); canvas->needs_clear = TRUE; chafa_dither_copy (&orig->dither, &canvas->dither); return canvas; } /** * chafa_canvas_ref: * @canvas: Canvas to add a reference to * * Adds a reference to @canvas. **/ void chafa_canvas_ref (ChafaCanvas *canvas) { gint refs; g_return_if_fail (canvas != NULL); refs = g_atomic_int_get (&canvas->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&canvas->refs); } static void destroy_pixel_canvas (ChafaCanvas *canvas) { if (canvas->pixel_canvas) { if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) chafa_sixel_canvas_destroy (canvas->pixel_canvas); else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY) chafa_kitty_canvas_destroy (canvas->pixel_canvas); else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) chafa_iterm2_canvas_destroy (canvas->pixel_canvas); canvas->pixel_canvas = NULL; } } /** * chafa_canvas_unref: * @canvas: Canvas to remove a reference from * * Removes a reference from @canvas. When remaining references drops to * zero, the canvas is freed and can no longer be used. **/ void chafa_canvas_unref (ChafaCanvas *canvas) { gint refs; g_return_if_fail (canvas != NULL); refs = g_atomic_int_get (&canvas->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&canvas->refs)) { chafa_canvas_config_deinit (&canvas->config); destroy_pixel_canvas (canvas); chafa_dither_deinit (&canvas->dither); chafa_palette_deinit (&canvas->palette); g_free (canvas->pixels); g_free (canvas->cells); g_free (canvas); } } /** * chafa_canvas_peek_config: * @canvas: Canvas whose configuration to inspect * * Returns a pointer to the configuration belonging to @canvas. * This can be inspected using the #ChafaCanvasConfig getter * functions, but not changed. * * Returns: A pointer to the canvas' immutable configuration **/ const ChafaCanvasConfig * chafa_canvas_peek_config (ChafaCanvas *canvas) { g_return_val_if_fail (canvas != NULL, NULL); g_return_val_if_fail (canvas->refs > 0, NULL); return &canvas->config; } /** * chafa_canvas_draw_all_pixels: * @canvas: Canvas whose pixel data to replace * @src_pixel_type: Pixel format of @src_pixels * @src_pixels: Pointer to the start of source pixel memory * @src_width: Width in pixels of source pixel data * @src_height: Height in pixels of source pixel data * @src_rowstride: Number of bytes between the start of each pixel row * * Replaces pixel data of @canvas with a copy of that found at @src_pixels, * which must be in one of the formats supported by #ChafaPixelType. * * Since: 1.2 **/ void chafa_canvas_draw_all_pixels (ChafaCanvas *canvas, ChafaPixelType src_pixel_type, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride) { g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); g_return_if_fail (src_pixels != NULL); g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); if (src_width == 0 || src_height == 0) return; if (canvas->pixels) { g_free (canvas->pixels); canvas->pixels = NULL; } destroy_pixel_canvas (canvas); if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) { /* Symbol mode */ canvas->pixels = g_new (ChafaPixel, canvas->width_pixels * canvas->height_pixels); chafa_prepare_pixel_data_for_symbols (&canvas->palette, &canvas->dither, canvas->config.color_space, canvas->config.preprocessing_enabled, canvas->work_factor_int, src_pixel_type, src_pixels, src_width, src_height, src_rowstride, canvas->pixels, canvas->width_pixels, canvas->height_pixels); if (canvas->config.alpha_threshold == 0) canvas->have_alpha = FALSE; update_cells (canvas); canvas->needs_clear = FALSE; g_free (canvas->pixels); canvas->pixels = NULL; } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS) { /* Sixel mode */ canvas->palette.alpha_threshold = canvas->config.alpha_threshold; canvas->pixel_canvas = chafa_sixel_canvas_new (canvas->width_pixels, canvas->height_pixels, canvas->config.color_space, &canvas->palette, &canvas->dither); chafa_sixel_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY) { /* Kitty mode */ canvas->palette.alpha_threshold = canvas->config.alpha_threshold; canvas->pixel_canvas = chafa_kitty_canvas_new (canvas->width_pixels, canvas->height_pixels); chafa_kitty_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride); } else /* if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) */ { /* iTerm2 mode */ canvas->palette.alpha_threshold = canvas->config.alpha_threshold; canvas->pixel_canvas = chafa_iterm2_canvas_new (canvas->width_pixels, canvas->height_pixels); chafa_iterm2_canvas_draw_all_pixels (canvas->pixel_canvas, src_pixel_type, src_pixels, src_width, src_height, src_rowstride); } } /** * chafa_canvas_set_contents_rgba8: * @canvas: Canvas whose pixel data to replace * @src_pixels: Pointer to the start of source pixel memory * @src_width: Width in pixels of source pixel data * @src_height: Height in pixels of source pixel data * @src_rowstride: Number of bytes between the start of each pixel row * * Replaces pixel data of @canvas with a copy of that found at @src_pixels. * The source data must be in packed 8-bits-per-channel RGBA format. The * alpha value is expressed as opacity (0xff is opaque) and is not * premultiplied. * * Deprecated: 1.2: Use chafa_canvas_draw_all_pixels() instead. **/ void chafa_canvas_set_contents_rgba8 (ChafaCanvas *canvas, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride) { chafa_canvas_draw_all_pixels (canvas, CHAFA_PIXEL_RGBA8_UNASSOCIATED, src_pixels, src_width, src_height, src_rowstride); } /** * chafa_canvas_build_ansi: * @canvas: The canvas to generate an ANSI character representation of * * Builds a UTF-8 string of ANSI sequences and symbols representing * the canvas' current contents. This can e.g. be printed to a terminal. * The exact choice of escape sequences and symbols, dimensions, etc. is * determined by the configuration assigned to @canvas on its creation. * * All output lines except for the last one will end in a newline. * * Returns: A UTF-8 string of ANSI sequences and symbols * * Deprecated: 1.6: Use chafa_canvas_print() instead. **/ GString * chafa_canvas_build_ansi (ChafaCanvas *canvas) { g_return_val_if_fail (canvas != NULL, NULL); g_return_val_if_fail (canvas->refs > 0, NULL); return chafa_canvas_print (canvas, NULL); } /** * chafa_canvas_print: * @canvas: The canvas to generate a printable representation of * @term_info: Terminal to format for, or %NULL for fallback * * Builds a UTF-8 string of terminal control sequences and symbols * representing the canvas' current contents. This can e.g. be printed * to a terminal. The exact choice of escape sequences and symbols, * dimensions, etc. is determined by the configuration assigned to * @canvas on its creation. * * All output lines except for the last one will end in a newline. * * Returns: A UTF-8 string of terminal control sequences and symbols * * Since: 1.6 **/ GString * chafa_canvas_print (ChafaCanvas *canvas, ChafaTermInfo *term_info) { GString *str; g_return_val_if_fail (canvas != NULL, NULL); g_return_val_if_fail (canvas->refs > 0, NULL); if (term_info) chafa_term_info_ref (term_info); else term_info = chafa_term_db_get_fallback_info (chafa_term_db_get_default ()); if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) { maybe_clear (canvas); str = chafa_canvas_print_symbols (canvas, term_info); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_SIXELS && chafa_term_info_get_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS)) { gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; gchar *out; /* Sixel mode */ out = chafa_term_info_emit_begin_sixels (term_info, buf, 0, 1, 0); *out = '\0'; str = g_string_new (buf); g_string_append_printf (str, "\"1;1;%d;%d", canvas->width_pixels, canvas->height_pixels); chafa_sixel_canvas_build_ansi (canvas->pixel_canvas, str); out = chafa_term_info_emit_end_sixels (term_info, buf); *out = '\0'; g_string_append (str, buf); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_KITTY && chafa_term_info_get_seq (term_info, CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1)) { /* Kitty mode */ str = g_string_new (""); chafa_kitty_canvas_build_ansi (canvas->pixel_canvas, term_info, str, canvas->config.width, canvas->config.height); } else if (canvas->config.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) { /* iTerm2 mode */ str = g_string_new (""); chafa_iterm2_canvas_build_ansi (canvas->pixel_canvas, term_info, str, canvas->config.width, canvas->config.height); } else { str = g_string_new (""); } chafa_term_info_unref (term_info); return str; } /** * chafa_canvas_get_char_at: * @canvas: The canvas to inspect * @x: Column of character cell to inspect * @y: Row of character cell to inspect * * Returns the character at cell (x, y). The coordinates are zero-indexed. For * double-width characters, the leftmost cell will contain the character * and the rightmost cell will contain 0. * * Returns: The character at (x, y) * * Since: 1.8 **/ gunichar chafa_canvas_get_char_at (ChafaCanvas *canvas, gint x, gint y) { g_return_val_if_fail (canvas != NULL, 0); g_return_val_if_fail (canvas->refs > 0, 0); g_return_val_if_fail (x >= 0 && x < canvas->config.width, 0); g_return_val_if_fail (y >= 0 && y < canvas->config.height, 0); return canvas->cells [y * canvas->config.width + x].c; } /** * chafa_canvas_set_char_at: * @canvas: The canvas to manipulate * @x: Column of character cell to manipulate * @y: Row of character cell to manipulate * @c: The character value to store * * Sets the character at cell (x, y). The coordinates are zero-indexed. For * double-width characters, the leftmost cell must contain the character * and the rightmost cell must contain 0. * * If the character is a nonprintable or zero-width, no change will be * made. * * Returns: The number of cells output (0, 1 or 2) * * Since: 1.8 **/ gint chafa_canvas_set_char_at (ChafaCanvas *canvas, gint x, gint y, gunichar c) { ChafaCanvasCell *cell; gint cwidth = 1; g_return_val_if_fail (canvas != NULL, 0); g_return_val_if_fail (canvas->refs > 0, 0); g_return_val_if_fail (x >= 0 && x < canvas->config.width, 0); g_return_val_if_fail (y >= 0 && y < canvas->config.height, 0); if (!g_unichar_isprint (c) || g_unichar_iszerowidth (c)) return 0; if (g_unichar_iswide (c)) cwidth = 2; if (x + cwidth > canvas->config.width) return 0; cell = &canvas->cells [y * canvas->config.width + x]; cell [0].c = c; if (cwidth == 2) { cell [1].c = 0; cell [1].fg_color = cell [0].fg_color; cell [1].bg_color = cell [0].bg_color; } /* If we're overwriting the rightmost half of a wide character, * clear its leftmost half */ if (x > 0) { if (cell [-1].c != 0 && g_unichar_iswide (cell [-1].c)) cell [-1].c = canvas->blank_char; } return cwidth; } /** * chafa_canvas_get_colors_at: * @canvas: The canvas to inspect * @x: Column of character cell to inspect * @y: Row of character cell to inspect * @fg_out: Storage for foreground color * @bg_out: Storage for background color * * Gets the colors at cell (x, y). The coordinates are zero-indexed. For * double-width characters, both cells will contain the same colors. * * The colors will be -1 for transparency, packed 8bpc RGB otherwise, * i.e. 0x00RRGGBB hex. * * If the canvas is in an indexed mode, palette lookups will be made * for you. * * Since: 1.8 **/ void chafa_canvas_get_colors_at (ChafaCanvas *canvas, gint x, gint y, gint *fg_out, gint *bg_out) { const ChafaCanvasCell *cell; gint fg = -1, bg = -1; g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (x >= 0 && x < canvas->config.width); g_return_if_fail (y >= 0 && y < canvas->config.height); cell = &canvas->cells [y * canvas->config.width + x]; switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: fg = packed_rgba_to_rgb (canvas, cell->fg_color); bg = packed_rgba_to_rgb (canvas, cell->bg_color); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: case CHAFA_CANVAS_MODE_INDEXED_16: case CHAFA_CANVAS_MODE_INDEXED_8: case CHAFA_CANVAS_MODE_FGBG_BGFG: case CHAFA_CANVAS_MODE_FGBG: if (cell->fg_color == CHAFA_PALETTE_INDEX_BG || cell->fg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) fg = -1; else fg = color_to_rgb (canvas, *get_palette_color_with_color_space (canvas, cell->fg_color, CHAFA_COLOR_SPACE_RGB)); if (cell->bg_color == CHAFA_PALETTE_INDEX_BG || cell->bg_color == CHAFA_PALETTE_INDEX_TRANSPARENT) bg = -1; else bg = color_to_rgb (canvas, *get_palette_color_with_color_space (canvas, cell->bg_color, CHAFA_COLOR_SPACE_RGB)); break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); break; } *fg_out = fg; *bg_out = bg; } /** * chafa_canvas_set_colors_at: * @canvas: The canvas to manipulate * @x: Column of character cell to manipulate * @y: Row of character cell to manipulate * @fg: Foreground color * @bg: Background color * * Sets the colors at cell (x, y). The coordinates are zero-indexed. For * double-width characters, both cells will be set to the same color. * * The colors must be -1 for transparency, packed 8bpc RGB otherwise, * i.e. 0x00RRGGBB hex. * * If the canvas is in an indexed mode, palette lookups will be made * for you. * * Since: 1.8 **/ void chafa_canvas_set_colors_at (ChafaCanvas *canvas, gint x, gint y, gint fg, gint bg) { ChafaCanvasCell *cell; g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (x >= 0 && x < canvas->config.width); g_return_if_fail (y >= 0 && y < canvas->config.height); cell = &canvas->cells [y * canvas->config.width + x]; switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: cell->fg_color = packed_rgb_to_rgba (fg); cell->bg_color = packed_rgb_to_rgba (bg); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: case CHAFA_CANVAS_MODE_INDEXED_16: case CHAFA_CANVAS_MODE_INDEXED_8: cell->fg_color = packed_rgb_to_index (&canvas->palette, canvas->config.color_space, fg); cell->bg_color = packed_rgb_to_index (&canvas->palette, canvas->config.color_space, bg); break; case CHAFA_CANVAS_MODE_FGBG_BGFG: cell->fg_color = fg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; cell->bg_color = bg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; break; case CHAFA_CANVAS_MODE_FGBG: cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); break; } /* If setting the color of half a wide char, set it for the other half too */ if (x > 0 && cell->c == 0) { cell [-1].fg_color = cell->fg_color; cell [-1].bg_color = cell->bg_color; } if (x < canvas->config.width - 1 && cell [1].c == 0) { cell [1].fg_color = cell->fg_color; cell [1].bg_color = cell->bg_color; } } /** * chafa_canvas_get_raw_colors_at: * @canvas: The canvas to inspect * @x: Column of character cell to inspect * @y: Row of character cell to inspect * @fg_out: Storage for foreground color * @bg_out: Storage for background color * * Gets the colors at cell (x, y). The coordinates are zero-indexed. For * double-width characters, both cells will contain the same colors. * * The colors will be -1 for transparency, packed 8bpc RGB, i.e. * 0x00RRGGBB hex in truecolor mode, or the raw pen value (0-255) in * indexed modes. * * It's the caller's responsibility to handle the color values correctly * according to the canvas mode (truecolor or indexed). * * Since: 1.8 **/ void chafa_canvas_get_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, gint *fg_out, gint *bg_out) { const ChafaCanvasCell *cell; gint fg = -1, bg = -1; g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (x >= 0 && x < canvas->config.width); g_return_if_fail (y >= 0 && y < canvas->config.height); cell = &canvas->cells [y * canvas->config.width + x]; switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: fg = packed_rgba_to_rgb (canvas, cell->fg_color); bg = packed_rgba_to_rgb (canvas, cell->bg_color); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: case CHAFA_CANVAS_MODE_INDEXED_16: case CHAFA_CANVAS_MODE_INDEXED_8: fg = cell->fg_color < 256 ? (gint) cell->fg_color : -1; bg = cell->bg_color < 256 ? (gint) cell->bg_color : -1; break; case CHAFA_CANVAS_MODE_FGBG_BGFG: fg = cell->fg_color == CHAFA_PALETTE_INDEX_FG ? 0 : -1; bg = cell->bg_color == CHAFA_PALETTE_INDEX_FG ? 0 : -1; break; case CHAFA_CANVAS_MODE_FGBG: fg = 0; bg = -1; break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); break; } if (fg_out) *fg_out = fg; if (bg_out) *bg_out = bg; } /** * chafa_canvas_set_raw_colors_at: * @canvas: The canvas to manipulate * @x: Column of character cell to manipulate * @y: Row of character cell to manipulate * @fg: Foreground color * @bg: Background color * * Sets the colors at cell (x, y). The coordinates are zero-indexed. For * double-width characters, both cells will be set to the same color. * * The colors must be -1 for transparency, packed 8bpc RGB, i.e. * 0x00RRGGBB hex in truecolor mode, or the raw pen value (0-255) in * indexed modes. * * It's the caller's responsibility to handle the color values correctly * according to the canvas mode (truecolor or indexed). * * Since: 1.8 **/ void chafa_canvas_set_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, gint fg, gint bg) { ChafaCanvasCell *cell; g_return_if_fail (canvas != NULL); g_return_if_fail (canvas->refs > 0); g_return_if_fail (x >= 0 && x < canvas->config.width); g_return_if_fail (y >= 0 && y < canvas->config.height); cell = &canvas->cells [y * canvas->config.width + x]; switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: cell->fg_color = packed_rgb_to_rgba (fg); cell->bg_color = packed_rgb_to_rgba (bg); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: case CHAFA_CANVAS_MODE_INDEXED_16: case CHAFA_CANVAS_MODE_INDEXED_8: cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; cell->bg_color = bg >= 0 ? bg : CHAFA_PALETTE_INDEX_TRANSPARENT; break; case CHAFA_CANVAS_MODE_FGBG_BGFG: cell->fg_color = fg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; cell->bg_color = bg >= 0 ? CHAFA_PALETTE_INDEX_FG : CHAFA_PALETTE_INDEX_TRANSPARENT; break; case CHAFA_CANVAS_MODE_FGBG: cell->fg_color = fg >= 0 ? fg : CHAFA_PALETTE_INDEX_TRANSPARENT; break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); break; } /* If setting the color of half a wide char, set it for the other half too */ if (x > 0 && cell->c == 0) { cell [-1].fg_color = cell->fg_color; cell [-1].bg_color = cell->bg_color; } if (x < canvas->config.width - 1 && cell [1].c == 0) { cell [1].fg_color = cell->fg_color; cell [1].bg_color = cell->bg_color; } } chafa-1.8.0/chafa/chafa-canvas.h000066400000000000000000000057301411352071600163150ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_CANVAS_H__ #define __CHAFA_CANVAS_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif #include G_BEGIN_DECLS typedef struct ChafaCanvas ChafaCanvas; CHAFA_AVAILABLE_IN_ALL ChafaCanvas *chafa_canvas_new (const ChafaCanvasConfig *config); CHAFA_AVAILABLE_IN_ALL ChafaCanvas *chafa_canvas_new_similar (ChafaCanvas *orig); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_ref (ChafaCanvas *canvas); CHAFA_AVAILABLE_IN_ALL void chafa_canvas_unref (ChafaCanvas *canvas); CHAFA_AVAILABLE_IN_ALL const ChafaCanvasConfig *chafa_canvas_peek_config (ChafaCanvas *canvas); CHAFA_AVAILABLE_IN_1_2 void chafa_canvas_draw_all_pixels (ChafaCanvas *canvas, ChafaPixelType src_pixel_type, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride); CHAFA_AVAILABLE_IN_1_6 GString *chafa_canvas_print (ChafaCanvas *canvas, ChafaTermInfo *term_info); CHAFA_AVAILABLE_IN_1_8 gunichar chafa_canvas_get_char_at (ChafaCanvas *canvas, gint x, gint y); CHAFA_AVAILABLE_IN_1_8 gint chafa_canvas_set_char_at (ChafaCanvas *canvas, gint x, gint y, gunichar c); CHAFA_AVAILABLE_IN_1_8 void chafa_canvas_get_colors_at (ChafaCanvas *canvas, gint x, gint y, gint *fg_out, gint *bg_out); CHAFA_AVAILABLE_IN_1_8 void chafa_canvas_set_colors_at (ChafaCanvas *canvas, gint x, gint y, gint fg, gint bg); CHAFA_AVAILABLE_IN_1_8 void chafa_canvas_get_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, gint *fg_out, gint *bg_out); CHAFA_AVAILABLE_IN_1_8 void chafa_canvas_set_raw_colors_at (ChafaCanvas *canvas, gint x, gint y, gint fg, gint bg); CHAFA_DEPRECATED_IN_1_2 void chafa_canvas_set_contents_rgba8 (ChafaCanvas *canvas, const guint8 *src_pixels, gint src_width, gint src_height, gint src_rowstride); CHAFA_DEPRECATED_IN_1_6 GString *chafa_canvas_build_ansi (ChafaCanvas *canvas); G_END_DECLS #endif /* __CHAFA_CANVAS_H__ */ chafa-1.8.0/chafa/chafa-common.h000066400000000000000000000046261411352071600163350ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_COMMON_H__ #define __CHAFA_COMMON_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS /** * ChafaPixelType: * @CHAFA_PIXEL_RGBA8_PREMULTIPLIED: Premultiplied RGBA, 8 bits per channel. * @CHAFA_PIXEL_BGRA8_PREMULTIPLIED: Premultiplied BGRA, 8 bits per channel. * @CHAFA_PIXEL_ARGB8_PREMULTIPLIED: Premultiplied ARGB, 8 bits per channel. * @CHAFA_PIXEL_ABGR8_PREMULTIPLIED: Premultiplied ABGR, 8 bits per channel. * @CHAFA_PIXEL_RGBA8_UNASSOCIATED: Unassociated RGBA, 8 bits per channel. * @CHAFA_PIXEL_BGRA8_UNASSOCIATED: Unassociated BGRA, 8 bits per channel. * @CHAFA_PIXEL_ARGB8_UNASSOCIATED: Unassociated ARGB, 8 bits per channel. * @CHAFA_PIXEL_ABGR8_UNASSOCIATED: Unassociated ABGR, 8 bits per channel. * @CHAFA_PIXEL_RGB8: Packed RGB (no alpha), 8 bits per channel. * @CHAFA_PIXEL_BGR8: Packed BGR (no alpha), 8 bits per channel. * @CHAFA_PIXEL_MAX: Last supported pixel type, plus one. * * Pixel formats supported by #ChafaCanvas and #ChafaSymbolMap. * * Since: 1.4 **/ typedef enum { /* 32 bits per pixel */ CHAFA_PIXEL_RGBA8_PREMULTIPLIED, CHAFA_PIXEL_BGRA8_PREMULTIPLIED, CHAFA_PIXEL_ARGB8_PREMULTIPLIED, CHAFA_PIXEL_ABGR8_PREMULTIPLIED, CHAFA_PIXEL_RGBA8_UNASSOCIATED, CHAFA_PIXEL_BGRA8_UNASSOCIATED, CHAFA_PIXEL_ARGB8_UNASSOCIATED, CHAFA_PIXEL_ABGR8_UNASSOCIATED, /* 24 bits per pixel */ CHAFA_PIXEL_RGB8, CHAFA_PIXEL_BGR8, CHAFA_PIXEL_MAX } ChafaPixelType; G_END_DECLS #endif /* __CHAFA_COMMON_H__ */ chafa-1.8.0/chafa/chafa-features.c000066400000000000000000000104471411352071600166540ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-private.h" /** * SECTION:chafa-features * @title: Features * @short_description: Platform-specific feature support * * Chafa supports a few platform-specific acceleration features. These * will be built in and used automatically when available. You can get * information about the available features through the function calls * documented in this section. **/ /** * ChafaFeatures: * @CHAFA_FEATURE_MMX: Flag indicating MMX support. * @CHAFA_FEATURE_SSE41: Flag indicating SSE 4.1 support. * @CHAFA_FEATURE_POPCNT: Flag indicating popcnt support. **/ static gboolean chafa_initialized; static gboolean have_mmx; static gboolean have_sse41; static gboolean have_popcnt; static void init_features (void) { #ifdef HAVE_GCC_X86_FEATURE_BUILTINS __builtin_cpu_init (); # ifdef HAVE_MMX_INTRINSICS if (__builtin_cpu_supports ("mmx")) have_mmx = TRUE; # endif # ifdef HAVE_SSE41_INTRINSICS if (__builtin_cpu_supports ("sse4.1")) have_sse41 = TRUE; # endif # ifdef HAVE_POPCNT_INTRINSICS if (__builtin_cpu_supports ("popcnt")) have_popcnt = TRUE; # endif #endif } static gpointer init_once (G_GNUC_UNUSED gpointer data) { init_features (); chafa_init_palette (); chafa_init_symbols (); chafa_initialized = TRUE; return NULL; } void chafa_init (void) { static GOnce once = G_ONCE_INIT; g_once (&once, init_once, NULL); } gboolean chafa_have_mmx (void) { return have_mmx; } gboolean chafa_have_sse41 (void) { return have_sse41; } gboolean chafa_have_popcnt (void) { return have_popcnt; } /* Public API */ /** * chafa_get_builtin_features: * * Gets a list of the platform-specific features this library was built with. * * Returns: A set of flags indicating features present. **/ ChafaFeatures chafa_get_builtin_features (void) { ChafaFeatures features = 0; #ifdef HAVE_MMX_INTRINSICS features |= CHAFA_FEATURE_MMX; #endif #ifdef HAVE_SSE41_INTRINSICS features |= CHAFA_FEATURE_SSE41; #endif #ifdef HAVE_POPCNT_INTRINSICS features |= CHAFA_FEATURE_POPCNT; #endif return features; } /** * chafa_get_supported_features: * * Gets a list of the platform-specific features that are built in and usable * on the runtime platform. * * Returns: A set of flags indicating usable features **/ ChafaFeatures chafa_get_supported_features (void) { chafa_init (); return (have_mmx ? CHAFA_FEATURE_MMX : 0) | (have_sse41 ? CHAFA_FEATURE_SSE41 : 0) | (have_popcnt ? CHAFA_FEATURE_POPCNT : 0); } /** * chafa_describe_features: * @features: A set of flags representing features * * Takes a set of flags potentially returned from chafa_get_builtin_features () * or chafa_get_supported_features () and generates a human-readable ASCII * string descriptor. * * Returns: A string describing the features. This must be freed by caller. **/ gchar * chafa_describe_features (ChafaFeatures features) { GString *features_gstr = g_string_new (""); if (features & CHAFA_FEATURE_MMX) g_string_append (features_gstr, "mmx "); if (features & CHAFA_FEATURE_SSE41) g_string_append (features_gstr, "sse4.1 "); if (features & CHAFA_FEATURE_POPCNT) g_string_append (features_gstr, "popcnt "); if (features_gstr->len > 0 && features_gstr->str [features_gstr->len - 1] == ' ') g_string_truncate (features_gstr, features_gstr->len - 1); return g_string_free (features_gstr, FALSE); } chafa-1.8.0/chafa/chafa-features.h000066400000000000000000000027411411352071600166570ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_FEATURES_H__ #define __CHAFA_FEATURES_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS /* Features */ typedef enum { CHAFA_FEATURE_MMX = (1 << 0), CHAFA_FEATURE_SSE41 = (1 << 1), CHAFA_FEATURE_POPCNT = (1 << 2), } ChafaFeatures; CHAFA_AVAILABLE_IN_ALL ChafaFeatures chafa_get_builtin_features (void); CHAFA_AVAILABLE_IN_ALL ChafaFeatures chafa_get_supported_features (void); CHAFA_AVAILABLE_IN_ALL gchar *chafa_describe_features (ChafaFeatures features); G_END_DECLS #endif /* __CHAFA_FEATURES_H__ */ chafa-1.8.0/chafa/chafa-symbol-map.c000066400000000000000000001422571411352071600171230ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* memset, memcpy */ #include /* qsort */ #include "chafa.h" #include "internal/chafa-private.h" #include "internal/smolscale/smolscale.h" #define DEBUG(x) /* Max number of candidates to return from chafa_symbol_map_find_candidates() */ #define N_CANDIDATES_MAX 8 typedef enum { SELECTOR_TAG, SELECTOR_RANGE } SelectorType; typedef struct { guint selector_type : 1; guint additive : 1; ChafaSymbolTags tags; /* First and last code points are inclusive */ gunichar first_code_point; gunichar last_code_point; } Selector; typedef struct { gunichar c; guint64 bitmap; } Glyph; /* Double-width glyphs */ typedef struct { gunichar c; guint64 bitmap [2]; } Glyph2; /** * CHAFA_SYMBOL_WIDTH_PIXELS: * * The width of an internal symbol pixel matrix. If you are prescaling * input graphics, you will get the best results when scaling to a * multiple of this value. **/ /** * CHAFA_SYMBOL_HEIGHT_PIXELS: * * The height of an internal symbol pixel matrix. If you are prescaling * input graphics, you will get the best results when scaling to a * multiple of this value. **/ /** * ChafaSymbolTags: * @CHAFA_SYMBOL_TAG_NONE: Special value meaning no symbols. * @CHAFA_SYMBOL_TAG_SPACE: Space. * @CHAFA_SYMBOL_TAG_SOLID: Solid (inverse of space). * @CHAFA_SYMBOL_TAG_STIPPLE: Stipple symbols. * @CHAFA_SYMBOL_TAG_BLOCK: Block symbols. * @CHAFA_SYMBOL_TAG_BORDER: Border symbols. * @CHAFA_SYMBOL_TAG_DIAGONAL: Diagonal border symbols. * @CHAFA_SYMBOL_TAG_DOT: Symbols that look like isolated dots (excluding Braille). * @CHAFA_SYMBOL_TAG_QUAD: Quadrant block symbols. * @CHAFA_SYMBOL_TAG_HHALF: Horizontal half block symbols. * @CHAFA_SYMBOL_TAG_VHALF: Vertical half block symbols. * @CHAFA_SYMBOL_TAG_HALF: Joint set of horizontal and vertical halves. * @CHAFA_SYMBOL_TAG_INVERTED: Symbols that are the inverse of simpler symbols. When two symbols complement each other, only one will have this tag. * @CHAFA_SYMBOL_TAG_BRAILLE: Braille symbols. * @CHAFA_SYMBOL_TAG_TECHNICAL: Miscellaneous technical symbols. * @CHAFA_SYMBOL_TAG_GEOMETRIC: Geometric shapes. * @CHAFA_SYMBOL_TAG_ASCII: Printable ASCII characters. * @CHAFA_SYMBOL_TAG_ALPHA: Letters. * @CHAFA_SYMBOL_TAG_DIGIT: Digits. * @CHAFA_SYMBOL_TAG_ALNUM: Joint set of letters and digits. * @CHAFA_SYMBOL_TAG_NARROW: Characters that are one cell wide. * @CHAFA_SYMBOL_TAG_WIDE: Characters that are two cells wide. * @CHAFA_SYMBOL_TAG_AMBIGUOUS: Characters of uncertain width. Always excluded unless specifically asked for. * @CHAFA_SYMBOL_TAG_UGLY: Characters that are generally undesired or unlikely to render well. Always excluded unless specifically asked for. * @CHAFA_SYMBOL_TAG_LEGACY: Legacy computer symbols, including sextants, wedges and more. * @CHAFA_SYMBOL_TAG_SEXTANT: Sextant 2x3 mosaics. * @CHAFA_SYMBOL_TAG_WEDGE: Wedge shapes that align with sextants. * @CHAFA_SYMBOL_TAG_EXTRA: Symbols not in any other category. * @CHAFA_SYMBOL_TAG_BAD: Joint set of ugly and ambiguous characters. Always excluded unless specifically asked for. * @CHAFA_SYMBOL_TAG_ALL: Special value meaning all supported symbols. **/ /** * SECTION:chafa-symbol-map * @title: ChafaSymbolMap * @short_description: Describes a selection of textual symbols * * A #ChafaSymbolMap describes a selection of the supported textual symbols * that can be used in building a printable output string from a #ChafaCanvas. * * To create a new #ChafaSymbolMap, use chafa_symbol_map_new (). You can then * add symbols to it using chafa_symbol_map_add_by_tags () before copying * it into a #ChafaCanvasConfig using chafa_canvas_config_set_symbol_map (). * * Note that some symbols match multiple tags, so it makes sense to e.g. * add symbols matching #CHAFA_SYMBOL_TAG_BORDER and then removing symbols * matching #CHAFA_SYMBOL_TAG_DIAGONAL. * * The number of available symbols is a significant factor in the speed of * #ChafaCanvas. For the fastest possible operation you could use a single * symbol -- #CHAFA_SYMBOL_TAG_VHALF works well by itself. **/ /* Private */ #if 0 static gint compare_symbols (const void *a, const void *b) { const ChafaSymbol *a_sym = a; const ChafaSymbol *b_sym = b; if (a_sym->c < b_sym->c) return -1; if (a_sym->c > b_sym->c) return 1; return 0; } #endif static guint8 * bitmap_to_bytes (guint64 bitmap) { guint8 *cov = g_malloc0 (CHAFA_SYMBOL_N_PIXELS); gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { cov [i] = (bitmap >> (CHAFA_SYMBOL_N_PIXELS - 1 - i)) & 1; } return cov; } /* Input format must always be RGBA8. old_format is just an indicator of how * the channel values are to be extracted. */ static void pixels_to_coverage (const guint8 *pixels_in, ChafaPixelType old_format, guint8 *pixels_out, gint n_pixels) { gint i; if (old_format == CHAFA_PIXEL_RGB8 || old_format == CHAFA_PIXEL_BGR8) { for (i = 0; i < n_pixels; i++) pixels_out [i] = (pixels_in [i * 4] + pixels_in [i * 4 + 1] + pixels_in [i * 4 + 2]) / 3; } else { for (i = 0; i < n_pixels; i++) pixels_out [i] = pixels_in [i * 4 + 3]; } } static void sharpen_coverage (const guint8 *cov_in, guint8 *cov_out, gint width, gint height) { gint k [3] [3] = { /* Sharpen + boost contrast */ { 0, -1, 0 }, { -1, 6, -1 }, { 0, -1, 0 } }; gint x, y; gint i, j; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { gint sum = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { gint a = x + i - 1, b = y + j - 1; /* At edges, just clone the border pixels outwards */ a = CLAMP (a, 0, width - 1); b = CLAMP (b, 0, height - 1); sum += (gint) cov_in [a + b * width] * k [i] [j]; } } cov_out [x + y * width] = CLAMP (sum, 0, 255); } } } static guint64 coverage_to_bitmap (const guint8 *cov, gint rowstride) { guint64 bitmap = 0; gint x, y; for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) { for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) { bitmap <<= 1; if (cov [y * rowstride + x] > 127) bitmap |= 1; } } return bitmap; } G_GNUC_UNUSED static void dump_coverage (const guint8 *cov, gint width, gint height) { gint i; for (i = 0; i < width * height; i++) { if (cov [i] > 127) { DEBUG (g_printerr ("@@")); } else { DEBUG (g_printerr ("--")); } if (!((i + 1) % width)) { DEBUG (g_printerr ("\n")); } } DEBUG (g_printerr ("\n")); } static guint64 glyph_to_bitmap (gint width, gint height, gint rowstride, ChafaPixelType pixel_format, gpointer pixels) { guint8 scaled_pixels [CHAFA_SYMBOL_N_PIXELS * 4]; guint8 cov [CHAFA_SYMBOL_N_PIXELS]; guint8 sharpened_cov [CHAFA_SYMBOL_N_PIXELS]; guint64 bitmap; /* Scale to cell dimensions */ smol_scale_simple ((SmolPixelType) pixel_format, pixels, width, height, rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, (gpointer) scaled_pixels, CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 4); /* Generate coverage map */ pixels_to_coverage (scaled_pixels, pixel_format, cov, CHAFA_SYMBOL_N_PIXELS); sharpen_coverage (cov, sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS); DEBUG (dump_coverage (cov, CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS)); DEBUG (dump_coverage (sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_HEIGHT_PIXELS)); bitmap = coverage_to_bitmap (sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS); return bitmap; } static void glyph_to_bitmap_wide (gint width, gint height, gint rowstride, ChafaPixelType pixel_format, gpointer pixels, guint64 *left_bitmap_out, guint64 *right_bitmap_out) { guint8 scaled_pixels [CHAFA_SYMBOL_N_PIXELS * 2 * 4]; guint8 cov [CHAFA_SYMBOL_N_PIXELS * 2]; guint8 sharpened_cov [CHAFA_SYMBOL_N_PIXELS * 2]; /* Scale to cell dimensions */ smol_scale_simple ((SmolPixelType) pixel_format, pixels, width, height, rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, (gpointer) scaled_pixels, CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 4 * 2); /* Generate coverage map */ pixels_to_coverage (scaled_pixels, pixel_format, cov, CHAFA_SYMBOL_N_PIXELS * 2); sharpen_coverage (cov, sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS); DEBUG (dump_coverage (cov, CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS)); DEBUG (dump_coverage (sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS * 2, CHAFA_SYMBOL_HEIGHT_PIXELS)); *left_bitmap_out = coverage_to_bitmap (sharpened_cov, CHAFA_SYMBOL_WIDTH_PIXELS * 2); *right_bitmap_out = coverage_to_bitmap (sharpened_cov + CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 2); } static gint compare_symbols_popcount (const void *a, const void *b) { const ChafaSymbol *a_sym = a; const ChafaSymbol *b_sym = b; if (a_sym->popcount < b_sym->popcount) return -1; if (a_sym->popcount > b_sym->popcount) return 1; return 0; } static gint compare_symbols2_popcount (const void *a, const void *b) { const ChafaSymbol2 *a_sym = a; const ChafaSymbol2 *b_sym = b; if (a_sym->sym [0].popcount + a_sym->sym [1].popcount < b_sym->sym [0].popcount + b_sym->sym [1].popcount) return -1; if (a_sym->sym [0].popcount + a_sym->sym [1].popcount > b_sym->sym [0].popcount + b_sym->sym [1].popcount) return 1; return 0; } static void compile_symbols (ChafaSymbolMap *symbol_map, GHashTable *desired_symbols) { GHashTableIter iter; gpointer key, value; gint i; for (i = 0; i < symbol_map->n_symbols; i++) g_free (symbol_map->symbols [i].coverage); g_free (symbol_map->symbols); g_free (symbol_map->packed_bitmaps); symbol_map->n_symbols = g_hash_table_size (desired_symbols); symbol_map->symbols = g_new (ChafaSymbol, symbol_map->n_symbols + 1); g_hash_table_iter_init (&iter, desired_symbols); i = 0; while (g_hash_table_iter_next (&iter, &key, &value)) { ChafaSymbol *sym = value; symbol_map->symbols [i] = *sym; symbol_map->symbols [i].coverage = g_memdup (symbol_map->symbols [i].coverage, CHAFA_SYMBOL_N_PIXELS); i++; } qsort (symbol_map->symbols, symbol_map->n_symbols, sizeof (ChafaSymbol), compare_symbols_popcount); /* Clear sentinel */ memset (&symbol_map->symbols [symbol_map->n_symbols], 0, sizeof (ChafaSymbol)); symbol_map->packed_bitmaps = g_new (guint64, symbol_map->n_symbols); for (i = 0; i < symbol_map->n_symbols; i++) symbol_map->packed_bitmaps [i] = symbol_map->symbols [i].bitmap; } static void compile_symbols_wide (ChafaSymbolMap *symbol_map, GHashTable *desired_symbols) { GHashTableIter iter; gpointer key, value; gint i; for (i = 0; i < symbol_map->n_symbols2; i++) { g_free (symbol_map->symbols2 [i].sym [0].coverage); g_free (symbol_map->symbols2 [i].sym [1].coverage); } g_free (symbol_map->symbols2); symbol_map->n_symbols2 = g_hash_table_size (desired_symbols); symbol_map->symbols2 = g_new (ChafaSymbol2, symbol_map->n_symbols2 + 1); g_hash_table_iter_init (&iter, desired_symbols); i = 0; while (g_hash_table_iter_next (&iter, &key, &value)) { ChafaSymbol2 *sym = value; symbol_map->symbols2 [i] = *sym; symbol_map->symbols2 [i].sym [0].coverage = g_memdup (symbol_map->symbols2 [i].sym [0].coverage, CHAFA_SYMBOL_N_PIXELS); symbol_map->symbols2 [i].sym [1].coverage = g_memdup (symbol_map->symbols2 [i].sym [1].coverage, CHAFA_SYMBOL_N_PIXELS); i++; } qsort (symbol_map->symbols2, symbol_map->n_symbols2, sizeof (ChafaSymbol2), compare_symbols2_popcount); /* Clear sentinel */ memset (&symbol_map->symbols2 [symbol_map->n_symbols2], 0, sizeof (ChafaSymbol2)); symbol_map->packed_bitmaps2 = g_new (guint64, symbol_map->n_symbols2 * 2); for (i = 0; i < symbol_map->n_symbols2; i++) { symbol_map->packed_bitmaps2 [i * 2] = symbol_map->symbols2 [i].sym [0].bitmap; symbol_map->packed_bitmaps2 [i * 2 + 1] = symbol_map->symbols2 [i].sym [1].bitmap; } } static gboolean char_is_selected (GArray *selectors, ChafaSymbolTags tags, gunichar c) { ChafaSymbolTags auto_exclude_tags = CHAFA_SYMBOL_TAG_BAD; GUnicodeScript script; gboolean is_selected = FALSE; gint i; /* Always exclude characters that would mangle the output */ if (!g_unichar_isprint (c) || g_unichar_iszerowidth (c) || c == '\t') return FALSE; /* We don't support RTL, so RTL characters will break the output. * * Ideally we'd exclude the R and AL bidi classes, but unfortunately we don't * have a convenient API available to us to determine the bidi class of a * character. So we just exclude a few scripts and hope for the best. * * A better implementation could extract directionality from the Unicode DB: * * https://www.unicode.org/reports/tr9/#Bidirectional_Character_Types * https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt */ script = g_unichar_get_script (c); if (script == G_UNICODE_SCRIPT_ARABIC || script == G_UNICODE_SCRIPT_HEBREW || script == G_UNICODE_SCRIPT_THAANA || script == G_UNICODE_SCRIPT_SYRIAC) return FALSE; for (i = 0; i < (gint) selectors->len; i++) { const Selector *selector = &g_array_index (selectors, Selector, i); switch (selector->selector_type) { case SELECTOR_TAG: if (tags & selector->tags) { is_selected = selector->additive ? TRUE : FALSE; /* We exclude "bad" symbols unless the user explicitly refers * to them by tag. I.e. the selector string "0..fffff" will not * include matches for "ugly", but "-ugly+0..fffff" will. */ auto_exclude_tags &= ~((guint) selector->tags); } break; case SELECTOR_RANGE: if (c >= selector->first_code_point && c <= selector->last_code_point) is_selected = selector->additive ? TRUE : FALSE; break; } } if (tags & auto_exclude_tags) is_selected = FALSE; return is_selected; } static void free_symbol (gpointer sym_p) { ChafaSymbol *sym = sym_p; g_free (sym->coverage); g_free (sym); } static void free_symbol_wide (gpointer sym_p) { ChafaSymbol2 *sym = sym_p; g_free (sym->sym [0].coverage); g_free (sym->sym [1].coverage); g_free (sym); } static void rebuild_symbols (ChafaSymbolMap *symbol_map) { GHashTable *desired_syms; GHashTable *desired_syms_wide; GHashTableIter iter; gpointer key, value; gint i; desired_syms = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_symbol); desired_syms_wide = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, free_symbol_wide); /* Pick built-in symbols */ if (symbol_map->use_builtin_glyphs) { for (i = 0; chafa_symbols [i].c != 0; i++) { if (char_is_selected (symbol_map->selectors, chafa_symbols [i].sc, chafa_symbols [i].c)) { ChafaSymbol *sym = g_new (ChafaSymbol, 1); *sym = chafa_symbols [i]; sym->coverage = g_memdup (sym->coverage, CHAFA_SYMBOL_N_PIXELS); g_hash_table_replace (desired_syms, GUINT_TO_POINTER (chafa_symbols [i].c), sym); } } } /* Pick built-in symbols (wide) */ if (symbol_map->use_builtin_glyphs) { for (i = 0; chafa_symbols2 [i].sym [0].c != 0; i++) { if (char_is_selected (symbol_map->selectors, chafa_symbols2 [i].sym [0].sc, chafa_symbols2 [i].sym [0].c)) { ChafaSymbol2 *sym = g_new (ChafaSymbol2, 1); *sym = chafa_symbols2 [i]; sym->sym [0].coverage = g_memdup (sym->sym [0].coverage, CHAFA_SYMBOL_N_PIXELS); sym->sym [1].coverage = g_memdup (sym->sym [1].coverage, CHAFA_SYMBOL_N_PIXELS); g_hash_table_replace (desired_syms_wide, GUINT_TO_POINTER (chafa_symbols2 [i].sym [0].c), sym); } } } /* Pick user glyph symbols */ g_hash_table_iter_init (&iter, symbol_map->glyphs); while (g_hash_table_iter_next (&iter, &key, &value)) { Glyph *glyph = value; ChafaSymbolTags tags = chafa_get_tags_for_char (glyph->c); if (char_is_selected (symbol_map->selectors, tags, glyph->c)) { ChafaSymbol *sym = g_new0 (ChafaSymbol, 1); sym->sc = tags; sym->c = glyph->c; sym->bitmap = glyph->bitmap; sym->coverage = (gchar *) bitmap_to_bytes (glyph->bitmap); sym->popcount = chafa_population_count_u64 (glyph->bitmap); sym->fg_weight = sym->popcount; sym->bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->popcount; g_hash_table_replace (desired_syms, GUINT_TO_POINTER (glyph->c), sym); } } compile_symbols (symbol_map, desired_syms); g_hash_table_destroy (desired_syms); /* Pick user glyph symbols (wide) */ g_hash_table_iter_init (&iter, symbol_map->glyphs2); while (g_hash_table_iter_next (&iter, &key, &value)) { Glyph2 *glyph = value; ChafaSymbolTags tags = chafa_get_tags_for_char (glyph->c); if (char_is_selected (symbol_map->selectors, tags, glyph->c)) { ChafaSymbol2 *sym = g_new0 (ChafaSymbol2, 1); sym->sym [0].sc = tags; sym->sym [0].c = glyph->c; sym->sym [0].bitmap = glyph->bitmap [0]; sym->sym [0].coverage = (gchar *) bitmap_to_bytes (glyph->bitmap [0]); sym->sym [0].popcount = chafa_population_count_u64 (glyph->bitmap [0]); sym->sym [0].fg_weight = sym->sym [0].popcount; sym->sym [0].bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->sym [0].popcount; sym->sym [1].sc = tags; sym->sym [1].c = glyph->c; sym->sym [1].bitmap = glyph->bitmap [1]; sym->sym [1].coverage = (gchar *) bitmap_to_bytes (glyph->bitmap [1]); sym->sym [1].popcount = chafa_population_count_u64 (glyph->bitmap [1]); sym->sym [1].fg_weight = sym->sym [1].popcount; sym->sym [1].bg_weight = CHAFA_SYMBOL_N_PIXELS - sym->sym [1].popcount; g_hash_table_replace (desired_syms_wide, GUINT_TO_POINTER (glyph->c), sym); } } compile_symbols_wide (symbol_map, desired_syms_wide); g_hash_table_destroy (desired_syms_wide); symbol_map->need_rebuild = FALSE; } static GHashTable * copy_glyph_table (GHashTable *src) { GHashTable *dest; GHashTableIter iter; gpointer key, value; dest = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); g_hash_table_iter_init (&iter, src); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert (dest, key, g_memdup (value, sizeof (Glyph))); } return dest; } static GHashTable * copy_glyph2_table (GHashTable *src) { GHashTable *dest; GHashTableIter iter; gpointer key, value; dest = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); g_hash_table_iter_init (&iter, src); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert (dest, key, g_memdup (value, sizeof (Glyph2))); } return dest; } static GArray * copy_selector_array (GArray *src) { GArray *dest; gint i; dest = g_array_new (FALSE, FALSE, sizeof (Selector)); for (i = 0; i < (gint) src->len; i++) { const Selector *s = &g_array_index (src, Selector, i); g_array_append_val (dest, *s); } return dest; } static void add_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) { Selector s = { 0 }; s.selector_type = SELECTOR_TAG; s.additive = TRUE; s.tags = tags; g_array_append_val (symbol_map->selectors, s); } static void remove_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) { Selector s = { 0 }; s.selector_type = SELECTOR_TAG; s.additive = FALSE; s.tags = tags; g_array_append_val (symbol_map->selectors, s); } static void add_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) { Selector s = { 0 }; s.selector_type = SELECTOR_RANGE; s.additive = TRUE; s.first_code_point = first; s.last_code_point = last; g_array_append_val (symbol_map->selectors, s); } static void remove_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) { Selector s = { 0 }; s.selector_type = SELECTOR_RANGE; s.additive = FALSE; s.first_code_point = first; s.last_code_point = last; g_array_append_val (symbol_map->selectors, s); } static gboolean parse_code_point (const gchar *str, gint len, gint *parsed_len_out, gunichar *c_out) { gint i = 0; gunichar code = 0; gboolean result = FALSE; if (len >= 1 && (str [0] == 'u' || str [0] == 'U')) i++; if (len >= 2 && str [0] == '0' && str [1] == 'x') i += 2; for ( ; i < len; i++) { gint c = (gint) str [i]; if (c - '0' >= 0 && c - '0' <= 9) { code *= 16; code += c - '0'; } else if (c - 'a' >= 0 && c - 'a' <= 5) { code *= 16; code += c - 'a' + 10; } else if (c - 'A' >= 0 && c - 'A' <= 5) { code *= 16; code += c - 'A' + 10; } else break; result = TRUE; } *parsed_len_out = i; *c_out = code; return result; } typedef struct { const gchar *name; ChafaSymbolTags sc; } SymMapping; static gboolean parse_symbol_tag (const gchar *name, gint len, SelectorType *sel_type_out, ChafaSymbolTags *sc_out, gunichar *first_out, gunichar *last_out, GError **error) { const SymMapping map [] = { { "all", CHAFA_SYMBOL_TAG_ALL }, { "none", CHAFA_SYMBOL_TAG_NONE }, { "space", CHAFA_SYMBOL_TAG_SPACE }, { "solid", CHAFA_SYMBOL_TAG_SOLID }, { "stipple", CHAFA_SYMBOL_TAG_STIPPLE }, { "block", CHAFA_SYMBOL_TAG_BLOCK }, { "border", CHAFA_SYMBOL_TAG_BORDER }, { "diagonal", CHAFA_SYMBOL_TAG_DIAGONAL }, { "dot", CHAFA_SYMBOL_TAG_DOT }, { "quad", CHAFA_SYMBOL_TAG_QUAD }, { "half", CHAFA_SYMBOL_TAG_HALF }, { "hhalf", CHAFA_SYMBOL_TAG_HHALF }, { "vhalf", CHAFA_SYMBOL_TAG_VHALF }, { "inverted", CHAFA_SYMBOL_TAG_INVERTED }, { "braille", CHAFA_SYMBOL_TAG_BRAILLE }, { "sextant", CHAFA_SYMBOL_TAG_SEXTANT }, { "wedge", CHAFA_SYMBOL_TAG_WEDGE }, { "technical", CHAFA_SYMBOL_TAG_TECHNICAL }, { "geometric", CHAFA_SYMBOL_TAG_GEOMETRIC }, { "ascii", CHAFA_SYMBOL_TAG_ASCII }, { "alpha", CHAFA_SYMBOL_TAG_ALPHA }, { "digit", CHAFA_SYMBOL_TAG_DIGIT }, { "narrow", CHAFA_SYMBOL_TAG_NARROW }, { "wide", CHAFA_SYMBOL_TAG_WIDE }, { "ambiguous", CHAFA_SYMBOL_TAG_AMBIGUOUS }, { "ugly", CHAFA_SYMBOL_TAG_UGLY }, { "extra", CHAFA_SYMBOL_TAG_EXTRA }, { "alnum", CHAFA_SYMBOL_TAG_ALNUM }, { "bad", CHAFA_SYMBOL_TAG_BAD }, { "legacy", CHAFA_SYMBOL_TAG_LEGACY }, { NULL, 0 } }; gint parsed_len; gint i; /* Tag? */ for (i = 0; map [i].name; i++) { if (!g_ascii_strncasecmp (map [i].name, name, len)) { *sc_out = map [i].sc; *sel_type_out = SELECTOR_TAG; return TRUE; } } /* Range? */ if (!parse_code_point (name, len, &parsed_len, first_out)) goto err; if (len - parsed_len > 0) { gint parsed_last_len; if (len - parsed_len < 3 || name [parsed_len] != '.' || name [parsed_len + 1] != '.' || !parse_code_point (name + parsed_len + 2, len - parsed_len - 2, &parsed_last_len, last_out) || parsed_len + 2 + parsed_last_len != len) goto err; } else { *last_out = *first_out; } *sel_type_out = SELECTOR_RANGE; return TRUE; err: /* Bad input */ g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unrecognized symbol tag '%.*s'.", len, name); return FALSE; } static gboolean parse_selectors (ChafaSymbolMap *symbol_map, const gchar *selectors, GError **error) { const gchar *p0 = selectors; gboolean is_add = FALSE, is_remove = FALSE; gboolean result = FALSE; while (*p0) { SelectorType sel_type; ChafaSymbolTags sc; gunichar first, last; gint n; p0 += strspn (p0, " ,"); if (!*p0) break; if (*p0 == '-') { is_add = FALSE; is_remove = TRUE; p0++; } else if (*p0 == '+') { is_add = TRUE; is_remove = FALSE; p0++; } p0 += strspn (p0, " "); if (!*p0) break; n = strspn (p0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789."); if (!n) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Syntax error in symbol tag selectors."); goto out; } if (!parse_symbol_tag (p0, n, &sel_type, &sc, &first, &last, error)) goto out; p0 += n; if (!is_add && !is_remove) { g_array_set_size (symbol_map->selectors, 0); is_add = TRUE; } if (sel_type == SELECTOR_TAG) { if (is_add) add_by_tags (symbol_map, sc); else if (is_remove) remove_by_tags (symbol_map, sc); } else { if (is_add) add_by_range (symbol_map, first, last); else if (is_remove) remove_by_range (symbol_map, first, last); } } symbol_map->need_rebuild = TRUE; result = TRUE; out: return result; } void chafa_symbol_map_init (ChafaSymbolMap *symbol_map) { g_return_if_fail (symbol_map != NULL); /* We need the global symbol table */ chafa_init (); memset (symbol_map, 0, sizeof (*symbol_map)); symbol_map->refs = 1; symbol_map->use_builtin_glyphs = TRUE; symbol_map->glyphs = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); symbol_map->glyphs2 = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); symbol_map->selectors = g_array_new (FALSE, FALSE, sizeof (Selector)); } void chafa_symbol_map_deinit (ChafaSymbolMap *symbol_map) { gint i; g_return_if_fail (symbol_map != NULL); for (i = 0; i < symbol_map->n_symbols; i++) g_free (symbol_map->symbols [i].coverage); g_hash_table_destroy (symbol_map->glyphs); g_hash_table_destroy (symbol_map->glyphs2); g_array_free (symbol_map->selectors, TRUE); g_free (symbol_map->symbols); g_free (symbol_map->symbols2); g_free (symbol_map->packed_bitmaps); g_free (symbol_map->packed_bitmaps2); } void chafa_symbol_map_copy_contents (ChafaSymbolMap *dest, const ChafaSymbolMap *src) { g_return_if_fail (dest != NULL); g_return_if_fail (src != NULL); memcpy (dest, src, sizeof (*dest)); dest->glyphs = copy_glyph_table (dest->glyphs); dest->glyphs2 = copy_glyph2_table (dest->glyphs2); dest->selectors = copy_selector_array (dest->selectors); dest->symbols = NULL; dest->symbols2 = NULL; dest->packed_bitmaps = NULL; dest->packed_bitmaps2 = NULL; dest->need_rebuild = TRUE; dest->refs = 1; } void chafa_symbol_map_prepare (ChafaSymbolMap *symbol_map) { if (!symbol_map->need_rebuild) return; rebuild_symbols (symbol_map); } gboolean chafa_symbol_map_has_symbol (const ChafaSymbolMap *symbol_map, gunichar symbol) { gint i; g_return_val_if_fail (symbol_map != NULL, FALSE); /* FIXME: Use gunichars as keys in hash table instead */ for (i = 0; i < symbol_map->n_symbols; i++) { const ChafaSymbol *sym = &symbol_map->symbols [i]; if (sym->c == symbol) return TRUE; } for (i = 0; i < symbol_map->n_symbols2; i++) { const ChafaSymbol2 *sym = &symbol_map->symbols2 [i]; if (sym->sym [0].c == symbol) return TRUE; } return FALSE; } /* Only call this when you know the candidate should be inserted */ static void insert_candidate (ChafaCandidate *candidates, const ChafaCandidate *new_cand) { gint i; i = N_CANDIDATES_MAX - 1; while (i) { i--; if (new_cand->hamming_distance >= candidates [i].hamming_distance) { memmove (candidates + i + 2, candidates + i + 1, (N_CANDIDATES_MAX - 2 - i) * sizeof (ChafaCandidate)); candidates [i + 1] = *new_cand; return; } } memmove (candidates + 1, candidates, (N_CANDIDATES_MAX - 1) * sizeof (ChafaCandidate)); candidates [0] = *new_cand; } void chafa_symbol_map_find_candidates (const ChafaSymbolMap *symbol_map, guint64 bitmap, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) { ChafaCandidate candidates [N_CANDIDATES_MAX] = { { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE } }; gint *ham_dist; gint i; g_return_if_fail (symbol_map != NULL); ham_dist = g_alloca (sizeof (gint) * (symbol_map->n_symbols + 1)); chafa_hamming_distance_vu64 (bitmap, symbol_map->packed_bitmaps, ham_dist, symbol_map->n_symbols); if (do_inverse) { for (i = 0; i < symbol_map->n_symbols; i++) { ChafaCandidate cand; gint hd = ham_dist [i]; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = FALSE; insert_candidate (candidates, &cand); } hd = 64 - hd; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = TRUE; insert_candidate (candidates, &cand); } } } else { for (i = 0; i < symbol_map->n_symbols; i++) { ChafaCandidate cand; gint hd = ham_dist [i]; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = FALSE; insert_candidate (candidates, &cand); } } } for (i = 0; i < N_CANDIDATES_MAX; i++) { if (candidates [i].hamming_distance > 64) break; } i = *n_candidates_inout = MIN (i, *n_candidates_inout); memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); } void chafa_symbol_map_find_wide_candidates (const ChafaSymbolMap *symbol_map, const guint64 *bitmaps, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) { ChafaCandidate candidates [N_CANDIDATES_MAX] = { { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE } }; gint *ham_dist; gint i; g_return_if_fail (symbol_map != NULL); ham_dist = g_alloca (sizeof (gint) * (symbol_map->n_symbols2 + 1)); chafa_hamming_distance_2_vu64 (bitmaps, symbol_map->packed_bitmaps2, ham_dist, symbol_map->n_symbols2); if (do_inverse) { for (i = 0; i < symbol_map->n_symbols2; i++) { ChafaCandidate cand; gint hd = ham_dist [i]; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = FALSE; insert_candidate (candidates, &cand); } hd = 128 - hd; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = TRUE; insert_candidate (candidates, &cand); } } } else { for (i = 0; i < symbol_map->n_symbols2; i++) { ChafaCandidate cand; gint hd = ham_dist [i]; if (hd < candidates [N_CANDIDATES_MAX - 1].hamming_distance) { cand.symbol_index = i; cand.hamming_distance = hd; cand.is_inverted = FALSE; insert_candidate (candidates, &cand); } } } for (i = 0; i < N_CANDIDATES_MAX; i++) { if (candidates [i].hamming_distance > 128) break; } i = *n_candidates_inout = MIN (i, *n_candidates_inout); memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); } /* Assumes symbols are sorted by ascending popcount */ static gint find_closest_popcount (const ChafaSymbolMap *symbol_map, gint popcount) { gint i, j; g_assert (symbol_map->n_symbols > 0); i = 0; j = symbol_map->n_symbols - 1; while (i < j) { gint k = (i + j + 1) / 2; if (popcount < symbol_map->symbols [k].popcount) j = k - 1; else if (popcount >= symbol_map->symbols [k].popcount) i = k; else i = j = k; } /* If we didn't find the exact popcount, the i+1'th element may be * a closer match. */ if (i < symbol_map->n_symbols - 1 && (abs (popcount - symbol_map->symbols [i + 1].popcount) < abs (popcount - symbol_map->symbols [i].popcount))) { i++; } return i; } /* Always returns zero or one candidates. We may want to do more in the future */ void chafa_symbol_map_find_fill_candidates (const ChafaSymbolMap *symbol_map, gint popcount, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) { ChafaCandidate candidates [N_CANDIDATES_MAX] = { { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE }, { 0, 65, FALSE } }; gint sym, distance; gint i; g_return_if_fail (symbol_map != NULL); if (!*n_candidates_inout) return; if (symbol_map->n_symbols == 0) { *n_candidates_inout = 0; return; } sym = find_closest_popcount (symbol_map, popcount); candidates [0].symbol_index = sym; candidates [0].hamming_distance = abs (popcount - symbol_map->symbols [sym].popcount); candidates [0].is_inverted = FALSE; if (do_inverse && candidates [0].hamming_distance != 0) { sym = find_closest_popcount (symbol_map, 64 - popcount); distance = abs (64 - popcount - symbol_map->symbols [sym].popcount); if (distance < candidates [0].hamming_distance) { candidates [0].symbol_index = sym; candidates [0].hamming_distance = distance; candidates [0].is_inverted = TRUE; } } for (i = 0; i < N_CANDIDATES_MAX; i++) { if (candidates [i].hamming_distance > 64) break; } i = *n_candidates_inout = MIN (i, *n_candidates_inout); memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); } /* Assumes symbols are sorted by ascending popcount */ static gint find_closest_popcount_wide (const ChafaSymbolMap *symbol_map, gint popcount) { gint i, j; g_assert (symbol_map->n_symbols2 > 0); i = 0; j = symbol_map->n_symbols2 - 1; while (i < j) { gint k = (i + j + 1) / 2; if (popcount < symbol_map->symbols2 [k].sym [0].popcount + symbol_map->symbols2 [k].sym [1].popcount) j = k - 1; else if (popcount >= symbol_map->symbols2 [k].sym [0].popcount + symbol_map->symbols2 [k].sym [1].popcount) i = k; else i = j = k; } /* If we didn't find the exact popcount, the i+1'th element may be * a closer match. */ if (i < symbol_map->n_symbols2 - 1 && (abs (popcount - (symbol_map->symbols2 [i + 1].sym [0].popcount + symbol_map->symbols2 [i + 1].sym [1].popcount)) < abs (popcount - (symbol_map->symbols2 [i].sym [0].popcount + symbol_map->symbols2 [i].sym [1].popcount)))) { i++; } return i; } /* Always returns zero or one candidates. We may want to do more in the future */ void chafa_symbol_map_find_wide_fill_candidates (const ChafaSymbolMap *symbol_map, gint popcount, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout) { ChafaCandidate candidates [N_CANDIDATES_MAX] = { { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE }, { 0, 129, FALSE } }; gint sym, distance; gint i; g_return_if_fail (symbol_map != NULL); if (!*n_candidates_inout) return; if (symbol_map->n_symbols2 == 0) { *n_candidates_inout = 0; return; } sym = find_closest_popcount_wide (symbol_map, popcount); candidates [0].symbol_index = sym; candidates [0].hamming_distance = abs (popcount - (symbol_map->symbols2 [sym].sym [0].popcount + symbol_map->symbols2 [sym].sym [1].popcount)); candidates [0].is_inverted = FALSE; if (do_inverse && candidates [0].hamming_distance != 0) { sym = find_closest_popcount (symbol_map, 128 - popcount); distance = abs (128 - popcount - (symbol_map->symbols2 [sym].sym [0].popcount + symbol_map->symbols2 [sym].sym [1].popcount)); if (distance < candidates [0].hamming_distance) { candidates [0].symbol_index = sym; candidates [0].hamming_distance = distance; candidates [0].is_inverted = TRUE; } } for (i = 0; i < N_CANDIDATES_MAX; i++) { if (candidates [i].hamming_distance > 128) break; } i = *n_candidates_inout = MIN (i, *n_candidates_inout); memcpy (candidates_out, candidates, i * sizeof (ChafaCandidate)); } /* Public */ /** * chafa_symbol_map_new: * * Creates a new #ChafaSymbolMap representing a set of Unicode symbols. The * symbol map starts out empty. * * Returns: The new symbol map **/ ChafaSymbolMap * chafa_symbol_map_new (void) { ChafaSymbolMap *symbol_map; symbol_map = g_new (ChafaSymbolMap, 1); chafa_symbol_map_init (symbol_map); return symbol_map; } /** * chafa_symbol_map_copy: * @symbol_map: A #ChafaSymbolMap to copy. * * Creates a new #ChafaSymbolMap that's a copy of @symbol_map. * * Returns: The new #ChafaSymbolMap **/ ChafaSymbolMap * chafa_symbol_map_copy (const ChafaSymbolMap *symbol_map) { ChafaSymbolMap *new_symbol_map; new_symbol_map = g_new (ChafaSymbolMap, 1); chafa_symbol_map_copy_contents (new_symbol_map, symbol_map); return new_symbol_map; } /** * chafa_symbol_map_ref: * @symbol_map: Symbol map to add a reference to * * Adds a reference to @symbol_map. **/ void chafa_symbol_map_ref (ChafaSymbolMap *symbol_map) { gint refs; g_return_if_fail (symbol_map != NULL); refs = g_atomic_int_get (&symbol_map->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&symbol_map->refs); } /** * chafa_symbol_map_unref: * @symbol_map: Symbol map to remove a reference from * * Removes a reference from @symbol_map. When remaining references drops to * zero, the symbol map is freed and can no longer be used. **/ void chafa_symbol_map_unref (ChafaSymbolMap *symbol_map) { gint refs; g_return_if_fail (symbol_map != NULL); refs = g_atomic_int_get (&symbol_map->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&symbol_map->refs)) { chafa_symbol_map_deinit (symbol_map); g_free (symbol_map); } } /** * chafa_symbol_map_add_by_tags: * @symbol_map: Symbol map to add symbols to * @tags: Selector tags for symbols to add * * Adds symbols matching the set of @tags to @symbol_map. **/ void chafa_symbol_map_add_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) { g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); add_by_tags (symbol_map, tags); symbol_map->need_rebuild = TRUE; } /** * chafa_symbol_map_remove_by_tags: * @symbol_map: Symbol map to remove symbols from * @tags: Selector tags for symbols to remove * * Removes symbols matching the set of @tags from @symbol_map. **/ void chafa_symbol_map_remove_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags) { g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); remove_by_tags (symbol_map, tags); symbol_map->need_rebuild = TRUE; } /** * chafa_symbol_map_add_by_range: * @symbol_map: Symbol map to add symbols to * @first: First code point to add, inclusive * @last: Last code point to add, inclusive * * Adds symbols in the code point range starting with @first * and ending with @last to @symbol_map. * * Since: 1.4 **/ void chafa_symbol_map_add_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) { g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); add_by_range (symbol_map, first, last); symbol_map->need_rebuild = TRUE; } /** * chafa_symbol_map_remove_by_range: * @symbol_map: Symbol map to remove symbols from * @first: First code point to remove, inclusive * @last: Last code point to remove, inclusive * * Removes symbols in the code point range starting with @first * and ending with @last from @symbol_map. * * Since: 1.4 **/ void chafa_symbol_map_remove_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last) { g_return_if_fail (symbol_map != NULL); g_return_if_fail (symbol_map->refs > 0); remove_by_range (symbol_map, first, last); symbol_map->need_rebuild = TRUE; } /** * chafa_symbol_map_apply_selectors: * @symbol_map: Symbol map to apply selection to * @selectors: A string specifying selections * @error: Return location for an error, or %NULL * * Parses a string consisting of symbol tags separated by [+-,] and * applies the pattern to @symbol_map. If the string begins with + or -, * it's understood to be relative to the current set in @symbol_map, * otherwise the map is cleared first. * * The symbol tags are string versions of #ChafaSymbolTags, i.e. * [all, none, space, solid, stipple, block, border, diagonal, dot, * quad, half, hhalf, vhalf, braille, technical, geometric, ascii, * extra]. * * Examples: "block,border" sets map to contain symbols matching either * of those tags. "+block,border-dot,stipple" adds block and border * symbols then removes dot and stipple symbols. * * If there is a parse error, none of the changes are applied. * * Returns: %TRUE on success, %FALSE if there was a parse error **/ gboolean chafa_symbol_map_apply_selectors (ChafaSymbolMap *symbol_map, const gchar *selectors, GError **error) { g_return_val_if_fail (symbol_map != NULL, FALSE); g_return_val_if_fail (symbol_map->refs > 0, FALSE); return parse_selectors (symbol_map, selectors, error); } /* --- Glyphs --- */ /** * chafa_symbol_map_get_allow_builtin_glyphs: * @symbol_map: A symbol map * * Queries whether a symbol map is allowed to use built-in glyphs for * symbol selection. This can be turned off if you want to use your * own glyphs exclusively (see chafa_symbol_map_add_glyph()). * * Defaults to %TRUE. * * Returns: %TRUE if built-in glyphs are allowed * * Since: 1.4 **/ gboolean chafa_symbol_map_get_allow_builtin_glyphs (ChafaSymbolMap *symbol_map) { g_return_val_if_fail (symbol_map != NULL, FALSE); return symbol_map->use_builtin_glyphs; } /** * chafa_symbol_map_set_allow_builtin_glyphs: * @symbol_map: A symbol map * @use_builtin_glyphs: A boolean indicating whether to use built-in glyphs * * Controls whether a symbol map is allowed to use built-in glyphs for * symbol selection. This can be turned off if you want to use your * own glyphs exclusively (see chafa_symbol_map_add_glyph()). * * Defaults to %TRUE. * * Since: 1.4 **/ void chafa_symbol_map_set_allow_builtin_glyphs (ChafaSymbolMap *symbol_map, gboolean use_builtin_glyphs) { g_return_if_fail (symbol_map != NULL); /* Avoid unnecessary rebuild */ if (symbol_map->use_builtin_glyphs == use_builtin_glyphs) return; symbol_map->use_builtin_glyphs = use_builtin_glyphs; symbol_map->need_rebuild = TRUE; } /** * chafa_symbol_map_add_glyph: * @symbol_map: A symbol map * @code_point: The Unicode code point for this glyph * @pixel_format: Glyph pixel format of @pixels * @pixels: The glyph data * @width: Width of glyph, in pixels * @height: Height of glyph, in pixels * @rowstride: Offset from start of one row to the next, in bytes * * Assigns a rendered glyph to a Unicode code point. This tells Chafa what the * glyph looks like so the corresponding symbol can be used appropriately in * output. * * Assigned glyphs override built-in glyphs and any earlier glyph that may * have been assigned to the same code point. * * If the input is in a format with an alpha channel, the alpha channel will * be used for the shape. If not, an average of the color channels will be used. * * Since: 1.4 **/ void chafa_symbol_map_add_glyph (ChafaSymbolMap *symbol_map, gunichar code_point, ChafaPixelType pixel_format, gpointer pixels, gint width, gint height, gint rowstride) { g_return_if_fail (symbol_map != NULL); if (g_unichar_iswide (code_point)) { Glyph2 *glyph2; glyph2 = g_new (Glyph2, 1); glyph2->c = code_point; glyph_to_bitmap_wide (width, height, rowstride, pixel_format, pixels, &glyph2->bitmap [0], &glyph2->bitmap [1]); g_hash_table_insert (symbol_map->glyphs2, GUINT_TO_POINTER (code_point), glyph2); } else { Glyph *glyph; glyph = g_new (Glyph, 1); glyph->c = code_point; glyph->bitmap = glyph_to_bitmap (width, height, rowstride, pixel_format, pixels); g_hash_table_insert (symbol_map->glyphs, GUINT_TO_POINTER (code_point), glyph); } symbol_map->need_rebuild = TRUE; } chafa-1.8.0/chafa/chafa-symbol-map.h000066400000000000000000000106601411352071600171200ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_SYMBOL_MAP_H__ #define __CHAFA_SYMBOL_MAP_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS #define CHAFA_SYMBOL_WIDTH_PIXELS 8 #define CHAFA_SYMBOL_HEIGHT_PIXELS 8 typedef enum { CHAFA_SYMBOL_TAG_NONE = 0, CHAFA_SYMBOL_TAG_SPACE = (1 << 0), CHAFA_SYMBOL_TAG_SOLID = (1 << 1), CHAFA_SYMBOL_TAG_STIPPLE = (1 << 2), CHAFA_SYMBOL_TAG_BLOCK = (1 << 3), CHAFA_SYMBOL_TAG_BORDER = (1 << 4), CHAFA_SYMBOL_TAG_DIAGONAL = (1 << 5), CHAFA_SYMBOL_TAG_DOT = (1 << 6), CHAFA_SYMBOL_TAG_QUAD = (1 << 7), CHAFA_SYMBOL_TAG_HHALF = (1 << 8), CHAFA_SYMBOL_TAG_VHALF = (1 << 9), CHAFA_SYMBOL_TAG_HALF = ((CHAFA_SYMBOL_TAG_HHALF) | (CHAFA_SYMBOL_TAG_VHALF)), CHAFA_SYMBOL_TAG_INVERTED = (1 << 10), CHAFA_SYMBOL_TAG_BRAILLE = (1 << 11), CHAFA_SYMBOL_TAG_TECHNICAL = (1 << 12), CHAFA_SYMBOL_TAG_GEOMETRIC = (1 << 13), CHAFA_SYMBOL_TAG_ASCII = (1 << 14), CHAFA_SYMBOL_TAG_ALPHA = (1 << 15), CHAFA_SYMBOL_TAG_DIGIT = (1 << 16), CHAFA_SYMBOL_TAG_ALNUM = CHAFA_SYMBOL_TAG_ALPHA | CHAFA_SYMBOL_TAG_DIGIT, CHAFA_SYMBOL_TAG_NARROW = (1 << 17), CHAFA_SYMBOL_TAG_WIDE = (1 << 18), CHAFA_SYMBOL_TAG_AMBIGUOUS = (1 << 19), CHAFA_SYMBOL_TAG_UGLY = (1 << 20), CHAFA_SYMBOL_TAG_LEGACY = (1 << 21), CHAFA_SYMBOL_TAG_SEXTANT = (1 << 22), CHAFA_SYMBOL_TAG_WEDGE = (1 << 23), CHAFA_SYMBOL_TAG_EXTRA = (1 << 30), CHAFA_SYMBOL_TAG_BAD = CHAFA_SYMBOL_TAG_AMBIGUOUS | CHAFA_SYMBOL_TAG_UGLY, CHAFA_SYMBOL_TAG_ALL = ~(CHAFA_SYMBOL_TAG_EXTRA | CHAFA_SYMBOL_TAG_BAD) } ChafaSymbolTags; typedef struct ChafaSymbolMap ChafaSymbolMap; CHAFA_AVAILABLE_IN_ALL ChafaSymbolMap *chafa_symbol_map_new (void); CHAFA_AVAILABLE_IN_ALL ChafaSymbolMap *chafa_symbol_map_copy (const ChafaSymbolMap *symbol_map); CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_ref (ChafaSymbolMap *symbol_map); CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_unref (ChafaSymbolMap *symbol_map); /* --- Selectors --- */ CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_add_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags); CHAFA_AVAILABLE_IN_ALL void chafa_symbol_map_remove_by_tags (ChafaSymbolMap *symbol_map, ChafaSymbolTags tags); CHAFA_AVAILABLE_IN_1_4 void chafa_symbol_map_add_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last); CHAFA_AVAILABLE_IN_1_4 void chafa_symbol_map_remove_by_range (ChafaSymbolMap *symbol_map, gunichar first, gunichar last); CHAFA_AVAILABLE_IN_ALL gboolean chafa_symbol_map_apply_selectors (ChafaSymbolMap *symbol_map, const gchar *selectors, GError **error); /* --- Glyphs --- */ CHAFA_AVAILABLE_IN_1_4 gboolean chafa_symbol_map_get_allow_builtin_glyphs (ChafaSymbolMap *symbol_map); CHAFA_AVAILABLE_IN_1_4 void chafa_symbol_map_set_allow_builtin_glyphs (ChafaSymbolMap *symbol_map, gboolean use_builtin_glyphs); CHAFA_AVAILABLE_IN_1_4 void chafa_symbol_map_add_glyph (ChafaSymbolMap *symbol_map, gunichar code_point, ChafaPixelType pixel_format, gpointer pixels, gint width, gint height, gint rowstride); G_END_DECLS #endif /* __CHAFA_SYMBOL_MAP_H__ */ chafa-1.8.0/chafa/chafa-term-db.c000066400000000000000000000356031411352071600163710ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" /** * SECTION:chafa-term-db * @title: ChafaTermDb * @short_description: A database of terminal information * * A #ChafaTermDb contains information on terminals, and can be used to obtain * a suitable #ChafaTermInfo for a terminal environment. **/ /* This is a very naïve implementation, but perhaps good enough for most * contemporary terminal emulators. I've kept the API minimal so actual * termcap/terminfo subset parsing can be added later if needed without * breaking existing applications. */ struct ChafaTermDb { gint refs; }; typedef struct { ChafaTermSeq seq; gchar *str; } SeqStr; static const SeqStr vt220_seqs [] = { { CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, "\033[!p" }, { CHAFA_TERM_SEQ_RESET_TERMINAL_HARD, "\033c" }, { CHAFA_TERM_SEQ_RESET_ATTRIBUTES, "\033[0m" }, { CHAFA_TERM_SEQ_CLEAR, "\033[2J" }, { CHAFA_TERM_SEQ_INVERT_COLORS, "\033[7m" }, { CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT, "\033[0H" }, { CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT, "\033[9999;1H" }, { CHAFA_TERM_SEQ_CURSOR_TO_POS, "\033[%2;%1H" }, { CHAFA_TERM_SEQ_CURSOR_UP, "\033[%1A" }, { CHAFA_TERM_SEQ_CURSOR_UP_1, "\033[A" }, { CHAFA_TERM_SEQ_CURSOR_DOWN, "\033[%1B" }, { CHAFA_TERM_SEQ_CURSOR_DOWN_1, "\033[B" }, { CHAFA_TERM_SEQ_CURSOR_LEFT, "\033[%1D" }, { CHAFA_TERM_SEQ_CURSOR_LEFT_1, "\033[D" }, { CHAFA_TERM_SEQ_CURSOR_RIGHT, "\033[%1C" }, { CHAFA_TERM_SEQ_CURSOR_RIGHT_1, "\033[C" }, { CHAFA_TERM_SEQ_CURSOR_UP_SCROLL, "\033D" }, { CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL, "\033M" }, { CHAFA_TERM_SEQ_INSERT_CELLS, "\033[%1@" }, { CHAFA_TERM_SEQ_DELETE_CELLS, "\033[%1P" }, { CHAFA_TERM_SEQ_INSERT_ROWS, "\033[%1L" }, { CHAFA_TERM_SEQ_DELETE_ROWS, "\033[%1M" }, { CHAFA_TERM_SEQ_SET_SCROLLING_ROWS, "\033[%1;%2r" }, { CHAFA_TERM_SEQ_ENABLE_INSERT, "\033[4h" }, { CHAFA_TERM_SEQ_DISABLE_INSERT,"\033[4l" }, { CHAFA_TERM_SEQ_ENABLE_CURSOR, "\033[?25h" }, { CHAFA_TERM_SEQ_DISABLE_CURSOR, "\033[?25l" }, { CHAFA_TERM_SEQ_ENABLE_ECHO, "\033[12l" }, { CHAFA_TERM_SEQ_DISABLE_ECHO, "\033[12h" }, { CHAFA_TERM_SEQ_ENABLE_WRAP, "\033[?7h" }, { CHAFA_TERM_SEQ_DISABLE_WRAP, "\033[?7l" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr rep_seqs [] = { { CHAFA_TERM_SEQ_REPEAT_CHAR, "\033[%1b" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr sixel_seqs [] = { { CHAFA_TERM_SEQ_BEGIN_SIXELS, "\033P%1;%2;%3q" }, { CHAFA_TERM_SEQ_END_SIXELS, "\033\\" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr color_direct_seqs [] = { { CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, "\033[38;2;%1;%2;%3m" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT, "\033[48;2;%1;%2;%3m" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT, "\033[38;2;%1;%2;%3;48;2;%4;%5;%6m" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr color_256_seqs [] = { { CHAFA_TERM_SEQ_SET_COLOR_FG_256, "\033[38;5;%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_256, "\033[48;5;%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_256, "\033[38;5;%1;48;5;%2m" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr color_16_seqs [] = { { CHAFA_TERM_SEQ_SET_COLOR_FG_16, "\033[%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_16, "\033[%1m" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "\033[%1;%2m" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr *color_direct_list [] = { color_direct_seqs, color_256_seqs, color_16_seqs, NULL }; static const SeqStr *color_256_list [] = { color_256_seqs, color_16_seqs, NULL }; static const SeqStr *color_16_list [] = { color_16_seqs, NULL }; static const SeqStr color_fbterm_seqs [] = { { CHAFA_TERM_SEQ_SET_COLOR_FG_16, "\033[1;%1}" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_16, "\033[2;%1}" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "\033[1;%1}\033[2;%2}" }, { CHAFA_TERM_SEQ_SET_COLOR_FG_256, "\033[1;%1}" }, { CHAFA_TERM_SEQ_SET_COLOR_BG_256, "\033[2;%1}" }, { CHAFA_TERM_SEQ_SET_COLOR_FGBG_256, "\033[1;%1}\033[2;%2}" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr *color_fbterm_list [] = { color_fbterm_seqs, NULL }; static const SeqStr kitty_seqs [] = { { CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1, "\033_Ga=T,f=%1,s=%2,v=%3,c=%4,r=%5,m=1\033\\" }, { CHAFA_TERM_SEQ_END_KITTY_IMAGE, "\033_Gm=0\033\\" }, { CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK, "\033_Gm=1;" }, { CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK, "\033\\" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr iterm2_seqs [] = { { CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE, "\033]1337;File=inline=1;width=%1;height=%2;preserveAspectRatio=0:" }, { CHAFA_TERM_SEQ_END_ITERM2_IMAGE, "\a" }, { CHAFA_TERM_SEQ_MAX, NULL } }; static const SeqStr *fallback_list [] = { vt220_seqs, color_direct_seqs, color_256_seqs, color_16_seqs, sixel_seqs, kitty_seqs, iterm2_seqs, NULL }; static void add_seqs (ChafaTermInfo *ti, const SeqStr *seqstr) { gint i; if (!seqstr) return; for (i = 0; seqstr [i].str; i++) { chafa_term_info_set_seq (ti, seqstr [i].seq, seqstr [i].str, NULL); } } static void add_seq_list (ChafaTermInfo *ti, const SeqStr **seqlist) { gint i; if (!seqlist) return; for (i = 0; seqlist [i]; i++) { add_seqs (ti, seqlist [i]); } } static void detect_capabilities (ChafaTermInfo *ti, gchar **envp) { const gchar *term; const gchar *colorterm; const gchar *vte_version; const gchar *term_program; const gchar *tmux; const gchar *ctx_backend; const gchar *lc_terminal; const SeqStr **color_seq_list = color_256_list; const SeqStr *gfx_seqs = NULL; const SeqStr *rep_seqs_local = NULL; add_seqs (ti, vt220_seqs); term = g_environ_getenv (envp, "TERM"); if (!term) term = ""; colorterm = g_environ_getenv (envp, "COLORTERM"); if (!colorterm) colorterm = ""; vte_version = g_environ_getenv (envp, "VTE_VERSION"); if (!vte_version) vte_version = ""; term_program = g_environ_getenv (envp, "TERM_PROGRAM"); if (!term_program) term_program = ""; tmux = g_environ_getenv (envp, "TMUX"); if (!tmux) tmux = ""; ctx_backend = g_environ_getenv (envp, "CTX_BACKEND"); if (!ctx_backend) ctx_backend = ""; lc_terminal = g_environ_getenv (envp, "LC_TERMINAL"); if (!lc_terminal) lc_terminal = ""; /* Some terminals set COLORTERM=truecolor. However, this env var can * make its way into environments where truecolor is not desired * (e.g. screen sessions), so check it early on and override it later. */ if (!g_ascii_strcasecmp (colorterm, "truecolor") || !g_ascii_strcasecmp (colorterm, "gnome-terminal") || !g_ascii_strcasecmp (colorterm, "xfce-terminal")) color_seq_list = color_direct_list; /* In a modern VTE we can rely on VTE_VERSION. It's a great terminal emulator * which supports truecolor. */ if (strlen (vte_version) > 0) { color_seq_list = color_direct_list; /* Newer VTE versions understand REP */ if (g_ascii_strtoull (vte_version, NULL, 10) >= 5202 && !strcmp (term, "xterm-256color")) rep_seqs_local = rep_seqs; } /* The ctx terminal (https://ctx.graphics/) understands REP */ if (strlen (ctx_backend) > 0) rep_seqs_local = rep_seqs; /* Terminals that advertise 256 colors usually support truecolor too, * (VTE, xterm) although some (xterm) may quantize to an indexed palette * regardless. */ if (!strcmp (term, "xterm-256color") || !strcmp (term, "xterm-direct") || !strcmp (term, "xterm-direct2") || !strcmp (term, "xterm-direct16") || !strcmp (term, "xterm-direct256") || !strcmp (term, "xterm-kitty") || !strcmp (term, "st-256color")) color_seq_list = color_direct_list; /* Kitty has a unique graphics protocol */ if (!strcmp (term, "xterm-kitty")) gfx_seqs = kitty_seqs; /* iTerm2 supports truecolor and has a unique graphics protocol */ if (!strcasecmp (lc_terminal, "iTerm2") || !strcasecmp (term_program, "iTerm.app")) { color_seq_list = color_direct_list; gfx_seqs = iterm2_seqs; } /* Apple Terminal sets TERM=xterm-256color, and does not support truecolor */ if (!g_ascii_strcasecmp (term_program, "Apple_Terminal")) color_seq_list = color_256_list; /* mlterm's truecolor support seems to be broken; it looks like a color * allocation issue. This affects character cells, but not sixels. * * yaft supports sixels and truecolor escape codes, but it remaps cell * colors to a 256-color palette. */ if (!strcmp (term, "mlterm") || !strcmp (term, "yaft") || !strcmp (term, "yaft-256color")) { /* The default canvas mode is truecolor for sixels. 240 colors is * the default for symbols. */ color_seq_list = color_256_list; gfx_seqs = sixel_seqs; } if (!strcmp(term, "foot") || !strcmp(term, "foot-direct")) gfx_seqs = sixel_seqs; /* rxvt 256-color really is 256 colors only */ if (!strcmp (term, "rxvt-unicode-256color")) color_seq_list = color_256_list; /* Regular rxvt supports 16 colors at most */ if (!strcmp (term, "rxvt-unicode")) color_seq_list = color_16_list; /* 'screen' does not like truecolor at all, but 256 colors works fine. * Sometimes we'll see the outer terminal appended to the TERM string, * like so: screen.xterm-256color */ if (!strncmp (term, "screen", 6)) { color_seq_list = color_256_list; /* 'tmux' also sets TERM=screen, but it supports truecolor codes. * You may have to add the following to .tmux.conf to prevent * remapping to 256 colors: * * tmux set-option -ga terminal-overrides ",screen-256color:Tc" */ if (strlen (tmux) > 0) color_seq_list = color_direct_list; /* screen and older tmux do not support REP. Newer tmux does, * but there's no reliable way to tell which version we're dealing with. */ rep_seqs_local = NULL; } /* If TERM is "linux", we're probably on the Linux console, which supports * 16 colors only. It also sets COLORTERM=1. * * https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4 * * In theory we could emit truecolor codes and let the console remap, * but we get better results if we do the conversion ourselves, since we * can apply preprocessing and exotic color spaces. */ if (!strcmp (term, "linux")) color_seq_list = color_16_list; /* FbTerm can use 256 colors through a private extension; see fbterm(1) */ if (!strcmp (term, "fbterm")) color_seq_list = color_fbterm_list; add_seq_list (ti, color_seq_list); add_seqs (ti, gfx_seqs); add_seqs (ti, rep_seqs_local); } static ChafaTermDb * instantiate_singleton (G_GNUC_UNUSED gpointer data) { return chafa_term_db_new (); } /* Public */ /** * chafa_term_db_new: * * Creates a new, blank #ChafaTermDb. * * Returns: The new #ChafaTermDb * * Since: 1.6 **/ ChafaTermDb * chafa_term_db_new (void) { ChafaTermDb *term_db; term_db = g_new0 (ChafaTermDb, 1); term_db->refs = 1; return term_db; } /** * chafa_term_db_copy: * @term_db: A #ChafaTermDb to copy. * * Creates a new #ChafaTermDb that's a copy of @term_db. * * Returns: The new #ChafaTermDb * * Since: 1.6 **/ ChafaTermDb * chafa_term_db_copy (const ChafaTermDb *term_db) { ChafaTermDb *new_term_db; new_term_db = g_new (ChafaTermDb, 1); memcpy (new_term_db, term_db, sizeof (ChafaTermDb)); new_term_db->refs = 1; return new_term_db; } /** * chafa_term_db_ref: * @term_db: #ChafaTermDb to add a reference to. * * Adds a reference to @term_db. * * Since: 1.6 **/ void chafa_term_db_ref (ChafaTermDb *term_db) { gint refs; g_return_if_fail (term_db != NULL); refs = g_atomic_int_get (&term_db->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&term_db->refs); } /** * chafa_term_db_unref: * @term_db: #ChafaTermDb to remove a reference from. * * Removes a reference from @term_db. * * Since: 1.6 **/ void chafa_term_db_unref (ChafaTermDb *term_db) { gint refs; g_return_if_fail (term_db != NULL); refs = g_atomic_int_get (&term_db->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&term_db->refs)) { g_free (term_db); } } /** * chafa_term_db_get_default: * * Gets the global #ChafaTermDb. This can normally be used safely * in a read-only capacity. The caller should not unref the * returned object. * * Returns: The global #ChafaTermDb * * Since: 1.6 **/ ChafaTermDb * chafa_term_db_get_default (void) { static GOnce my_once = G_ONCE_INIT; g_once (&my_once, (GThreadFunc) instantiate_singleton, NULL); return my_once.retval; } /** * chafa_term_db_detect: * @term_db: A #ChafaTermDb. * @envp: A strv of environment variables. * * Builds a new #ChafaTermInfo with capabilities implied by the provided * environment variables (principally the TERM variable, but also others). * * @envp can be gotten from g_get_environ(). * * Returns: A new #ChafaTermInfo. * * Since: 1.6 **/ ChafaTermInfo * chafa_term_db_detect (ChafaTermDb *term_db, gchar **envp) { ChafaTermInfo *ti; g_return_val_if_fail (term_db != NULL, NULL); ti = chafa_term_info_new (); detect_capabilities (ti, envp); return ti; } /** * chafa_term_db_get_fallback_info: * @term_db: A #ChafaTermDb. * * Builds a new #ChafaTermInfo with fallback control sequences. This * can be used with unknown but presumably modern terminals, or to * supplement missing capabilities in a detected terminal. * * Fallback control sequences may cause unpredictable behavior and * should only be used as a last resort. * * Returns: A new #ChafaTermInfo. * * Since: 1.6 **/ ChafaTermInfo * chafa_term_db_get_fallback_info (ChafaTermDb *term_db) { ChafaTermInfo *ti; g_return_val_if_fail (term_db != NULL, NULL); ti = chafa_term_info_new (); add_seq_list (ti, fallback_list); return ti; } chafa-1.8.0/chafa/chafa-term-db.h000066400000000000000000000032711411352071600163720ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_TERM_DB_H__ #define __CHAFA_TERM_DB_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif #include G_BEGIN_DECLS typedef struct ChafaTermDb ChafaTermDb; CHAFA_AVAILABLE_IN_1_6 ChafaTermDb *chafa_term_db_new (void); CHAFA_AVAILABLE_IN_1_6 ChafaTermDb *chafa_term_db_copy (const ChafaTermDb *term_db); CHAFA_AVAILABLE_IN_1_6 void chafa_term_db_ref (ChafaTermDb *term_db); CHAFA_AVAILABLE_IN_1_6 void chafa_term_db_unref (ChafaTermDb *term_db); CHAFA_AVAILABLE_IN_1_6 ChafaTermDb *chafa_term_db_get_default (void); CHAFA_AVAILABLE_IN_1_6 ChafaTermInfo *chafa_term_db_detect (ChafaTermDb *term_db, gchar **envp); CHAFA_AVAILABLE_IN_1_6 ChafaTermInfo *chafa_term_db_get_fallback_info (ChafaTermDb *term_db); G_END_DECLS #endif /* __CHAFA_TERM_DB_H__ */ chafa-1.8.0/chafa/chafa-term-info.c000066400000000000000000000563201411352071600167360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-private.h" #include "internal/chafa-string-util.h" /** * SECTION:chafa-term-info * @title: ChafaTermInfo * @short_description: Describes a particular terminal type * * A #ChafaTermInfo describes the characteristics of one particular kind * of display terminal. It stores control sequences that can be used to * move the cursor, change text attributes, mark the beginning and end of * sixel graphics data, etc. * * #ChafaTermInfo also implements an efficient low-level API for formatting * these sequences with marshaled arguments so they can be sent to the * terminal. **/ /** * ChafaTermSeq: * @CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT: Reset the terminal to configured defaults. * @CHAFA_TERM_SEQ_RESET_TERMINAL_HARD: Reset the terminal to factory defaults. * @CHAFA_TERM_SEQ_RESET_ATTRIBUTES: Reset active graphics rendition (colors and other attributes) to terminal defaults. * @CHAFA_TERM_SEQ_CLEAR: Clear the screen. * @CHAFA_TERM_SEQ_INVERT_COLORS: Invert foreground and background colors. * @CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT: Move cursor to top left of screen. * @CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT: Move cursor to bottom left of screen. * @CHAFA_TERM_SEQ_CURSOR_TO_POS: Move cursor to specific position. * @CHAFA_TERM_SEQ_CURSOR_UP_1: Move cursor up one cell. * @CHAFA_TERM_SEQ_CURSOR_UP: Move cursor up N cells. * @CHAFA_TERM_SEQ_CURSOR_DOWN_1: Move cursor down one cell. * @CHAFA_TERM_SEQ_CURSOR_DOWN: Move cursor down N cells. * @CHAFA_TERM_SEQ_CURSOR_LEFT_1: Move cursor left one cell. * @CHAFA_TERM_SEQ_CURSOR_LEFT: Move cursor left N cells. * @CHAFA_TERM_SEQ_CURSOR_RIGHT_1: Move cursor right one cell. * @CHAFA_TERM_SEQ_CURSOR_RIGHT: Move cursor right N cells. * @CHAFA_TERM_SEQ_CURSOR_UP_SCROLL: Move cursor up one cell. Scroll area contents down when at the edge. * @CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL: Move cursor down one cell. Scroll area contents up when at the edge. * @CHAFA_TERM_SEQ_INSERT_CELLS: Insert blank cells at cursor position. * @CHAFA_TERM_SEQ_DELETE_CELLS: Delete cells at cursor position. * @CHAFA_TERM_SEQ_INSERT_ROWS: Insert rows at cursor position. * @CHAFA_TERM_SEQ_DELETE_ROWS: Delete rows at cursor position. * @CHAFA_TERM_SEQ_SET_SCROLLING_ROWS: Set scrolling area extents. * @CHAFA_TERM_SEQ_ENABLE_INSERT: Enable insert mode. * @CHAFA_TERM_SEQ_DISABLE_INSERT: Disable insert mode. * @CHAFA_TERM_SEQ_ENABLE_CURSOR: Show the cursor. * @CHAFA_TERM_SEQ_DISABLE_CURSOR: Hide the cursor. * @CHAFA_TERM_SEQ_ENABLE_ECHO: Make the terminal echo input locally. * @CHAFA_TERM_SEQ_DISABLE_ECHO: Don't echo input locally. * @CHAFA_TERM_SEQ_ENABLE_WRAP: Make cursor wrap around to the next row after output in the final column. * @CHAFA_TERM_SEQ_DISABLE_WRAP: Make cursor stay in place after output to the final column. * @CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT: Set foreground color (directcolor/truecolor). * @CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT: Set background color (directcolor/truecolor). * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT: Set foreground and background color (directcolor/truecolor). * @CHAFA_TERM_SEQ_SET_COLOR_FG_256: Set foreground color (256 colors). * @CHAFA_TERM_SEQ_SET_COLOR_BG_256: Set background color (256 colors). * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_256: Set foreground and background colors (256 colors). * @CHAFA_TERM_SEQ_SET_COLOR_FG_16: Set foreground color (16 colors). * @CHAFA_TERM_SEQ_SET_COLOR_BG_16: Set background color (16 colors). * @CHAFA_TERM_SEQ_SET_COLOR_FGBG_16: Set foreground and background colors (16 colors). * @CHAFA_TERM_SEQ_BEGIN_SIXELS: Begin sixel image data. * @CHAFA_TERM_SEQ_END_SIXELS: End sixel image data. * @CHAFA_TERM_SEQ_REPEAT_CHAR: Repeat previous character N times. * @CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1: Begin upload of Kitty image for immediate display at cursor. * @CHAFA_TERM_SEQ_END_KITTY_IMAGE: End of Kitty image upload. * @CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK: Begin Kitty image data chunk. * @CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK: End Kitty image data chunk. * @CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE: Begin iTerm2 image data. * @CHAFA_TERM_SEQ_END_ITERM2_IMAGE: End of iTerm2 image data. * @CHAFA_TERM_SEQ_MAX: Last control sequence plus one. * * An enumeration of the control sequences supported by #ChafaTermInfo. **/ /* Maximum number of arguments + 1 for the sentinel */ #define CHAFA_TERM_SEQ_ARGS_MAX 8 #define ARG_INDEX_SENTINEL 255 typedef struct { guint8 pre_len; guint8 arg_index; } SeqArgInfo; struct ChafaTermInfo { gint refs; gchar seq_str [CHAFA_TERM_SEQ_MAX] [CHAFA_TERM_SEQ_LENGTH_MAX]; SeqArgInfo seq_args [CHAFA_TERM_SEQ_MAX] [CHAFA_TERM_SEQ_ARGS_MAX]; gchar *unparsed_str [CHAFA_TERM_SEQ_MAX]; }; typedef struct { guint n_args; guint type_size; } SeqMeta; static const SeqMeta seq_meta [CHAFA_TERM_SEQ_MAX] = { #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ [CHAFA_TERM_SEQ_##NAME] = { n_args, sizeof (arg_type) }, #include #undef CHAFA_TERM_SEQ_DEF }; /* Avoid memcpy() because it inlines to a sizeable amount of code (it * doesn't know our strings are always short). We also take the liberty * of unconditionally copying a byte even if n=0. This simplifies the * generated assembly quite a bit. */ static inline void copy_bytes (gchar *out, const gchar *in, gint n) { gint i = 0; do { *(out++) = *(in++); i++; } while (i < n); } static gboolean parse_seq_args (gchar *out, SeqArgInfo *arg_info, const gchar *in, gint n_args, gint arg_len_max, GError **error) { gint i, j, k; gint pre_len = 0; gint result = FALSE; g_assert (n_args < CHAFA_TERM_SEQ_ARGS_MAX); for (k = 0; k < CHAFA_TERM_SEQ_ARGS_MAX; k++) { arg_info [k].pre_len = 0; arg_info [k].arg_index = ARG_INDEX_SENTINEL; } for (i = 0, j = 0, k = 0; j < CHAFA_TERM_SEQ_LENGTH_MAX && k < CHAFA_TERM_SEQ_ARGS_MAX && in [i]; i++) { gchar c = in [i]; if (c == '%') { i++; c = in [i]; if (c == '%') { /* "%%" -> literal "%" */ out [j++] = '%'; pre_len++; } else if (c >= '1' && c <= '8') /* arg # 0-7 */ { /* "%n" -> argument n-1 */ arg_info [k].arg_index = c - '1'; arg_info [k].pre_len = pre_len; if (arg_info [k].arg_index >= n_args) { /* Bad argument index (out of range) */ g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS, "Control sequence had too many arguments."); goto out; } pre_len = 0; k++; } else { /* Bad "%?" escape */ goto out; } } else { out [j++] = c; pre_len++; } } if (k == CHAFA_TERM_SEQ_ARGS_MAX) { /* Too many argument expansions */ g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS, "Control sequence had too many arguments."); goto out; } /* Reserve an extra byte for copy_byte() and chafa_format_dec_u8() excess. */ if (j + k * arg_len_max + 1 > CHAFA_TERM_SEQ_LENGTH_MAX) { /* Formatted string may be too long */ g_set_error (error, CHAFA_TERM_INFO_ERROR, CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG, "Control sequence too long."); goto out; } arg_info [k].pre_len = pre_len; arg_info [k].arg_index = ARG_INDEX_SENTINEL; result = TRUE; out: return result; } #define EMIT_SEQ_DEF(inttype, intformatter) \ static gchar * \ emit_seq_##inttype (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, \ inttype *args, gint n_args) \ { \ const gchar *seq_str; \ const SeqArgInfo *seq_args; \ gint ofs = 0; \ gint i; \ \ seq_str = &term_info->seq_str [seq] [0]; \ seq_args = &term_info->seq_args [seq] [0]; \ \ if (seq_args [0].arg_index == ARG_INDEX_SENTINEL) \ return out; \ \ for (i = 0; i < n_args; i++) \ { \ copy_bytes (out, &seq_str [ofs], seq_args [i].pre_len); \ out += seq_args [i].pre_len; \ ofs += seq_args [i].pre_len; \ out = intformatter (out, args [seq_args [i].arg_index]); \ } \ \ copy_bytes (out, &seq_str [ofs], seq_args [i].pre_len); \ out += seq_args [i].pre_len; \ \ return out; \ } EMIT_SEQ_DEF(guint, chafa_format_dec_uint_0_to_9999) EMIT_SEQ_DEF(guint8, chafa_format_dec_u8) static gchar * emit_seq_0_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq) { copy_bytes (out, &term_info->seq_str [seq] [0], term_info->seq_args [seq] [0].pre_len); return out + term_info->seq_args [seq] [0].pre_len; } static gchar * emit_seq_1_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0) { return emit_seq_guint (term_info, out, seq, &arg0, 1); } static gchar * emit_seq_1_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0) { return emit_seq_guint8 (term_info, out, seq, &arg0, 1); } static gchar * emit_seq_2_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1) { guint args [2]; args [0] = arg0; args [1] = arg1; return emit_seq_guint (term_info, out, seq, args, 2); } static gchar * emit_seq_2_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1) { guint8 args [2]; args [0] = arg0; args [1] = arg1; return emit_seq_guint8 (term_info, out, seq, args, 2); } static gchar * emit_seq_3_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1, guint arg2) { guint args [3]; args [0] = arg0; args [1] = arg1; args [2] = arg2; return emit_seq_guint (term_info, out, seq, args, 3); } static gchar * emit_seq_5_args_uint (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint arg0, guint arg1, guint arg2, guint arg3, guint arg4) { guint args [5]; args [0] = arg0; args [1] = arg1; args [2] = arg2; args [3] = arg3; args [4] = arg4; return emit_seq_guint (term_info, out, seq, args, 5); } static gchar * emit_seq_3_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0, guint8 arg1, guint8 arg2) { guint8 args [3]; args [0] = arg0; args [1] = arg1; args [2] = arg2; return emit_seq_guint8 (term_info, out, seq, args, 3); } static gchar * emit_seq_6_args_uint8 (const ChafaTermInfo *term_info, gchar *out, ChafaTermSeq seq, guint8 arg0, guint8 arg1, guint8 arg2, guint8 arg3, guint8 arg4, guint8 arg5) { guint8 args [6]; args [0] = arg0; args [1] = arg1; args [2] = arg2; args [3] = arg3; args [4] = arg4; args [5] = arg5; return emit_seq_guint8 (term_info, out, seq, args, 6); } /* Public */ G_DEFINE_QUARK (chafa-term-info-error-quark, chafa_term_info_error) /** * chafa_term_info_new: * * Creates a new, blank #ChafaTermInfo. * * Returns: The new #ChafaTermInfo * * Since: 1.6 **/ ChafaTermInfo * chafa_term_info_new (void) { ChafaTermInfo *term_info; gint i; term_info = g_new0 (ChafaTermInfo, 1); term_info->refs = 1; for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) { term_info->seq_args [i] [0].arg_index = ARG_INDEX_SENTINEL; } return term_info; } /** * chafa_term_info_copy: * @term_info: A #ChafaTermInfo to copy. * * Creates a new #ChafaTermInfo that's a copy of @term_info. * * Returns: The new #ChafaTermInfo * * Since: 1.6 **/ ChafaTermInfo * chafa_term_info_copy (const ChafaTermInfo *term_info) { ChafaTermInfo *new_term_info; gint i; g_return_val_if_fail (term_info != NULL, NULL); new_term_info = g_new (ChafaTermInfo, 1); memcpy (new_term_info, term_info, sizeof (ChafaTermInfo)); new_term_info->refs = 1; for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) { if (new_term_info->unparsed_str [i]) new_term_info->unparsed_str [i] = g_strdup (new_term_info->unparsed_str [i]); } return new_term_info; } /** * chafa_term_info_ref: * @term_info: #ChafaTermInfo to add a reference to. * * Adds a reference to @term_info. * * Since: 1.6 **/ void chafa_term_info_ref (ChafaTermInfo *term_info) { gint refs; g_return_if_fail (term_info != NULL); refs = g_atomic_int_get (&term_info->refs); g_return_if_fail (refs > 0); g_atomic_int_inc (&term_info->refs); } /** * chafa_term_info_unref: * @term_info: #ChafaTermInfo to remove a reference from. * * Removes a reference from @term_info. * * Since: 1.6 **/ void chafa_term_info_unref (ChafaTermInfo *term_info) { gint refs; g_return_if_fail (term_info != NULL); refs = g_atomic_int_get (&term_info->refs); g_return_if_fail (refs > 0); if (g_atomic_int_dec_and_test (&term_info->refs)) { gint i; for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) g_free (term_info->unparsed_str [i]); g_free (term_info); } } /** * chafa_term_info_have_seq: * @term_info: A #ChafaTermInfo. * @seq: A #ChafaTermSeq to query for. * * Checks if @term_info can emit @seq. * * Returns: %TRUE if @seq can be emitted, %FALSE otherwise * * Since: 1.6 **/ gboolean chafa_term_info_have_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq) { g_return_val_if_fail (term_info != NULL, FALSE); g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, FALSE); return term_info->unparsed_str [seq] ? TRUE : FALSE; } /** * chafa_term_info_get_seq: * @term_info: A #ChafaTermInfo. * @seq: A #ChafaTermSeq to query for. * * Gets the string equivalent of @seq stored in @term_info. * * Returns: An unformatted string sequence, or %NULL if not set. * * Since: 1.6 **/ const gchar * chafa_term_info_get_seq (ChafaTermInfo *term_info, ChafaTermSeq seq) { g_return_val_if_fail (term_info != NULL, NULL); g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, NULL); return term_info->unparsed_str [seq]; } /** * chafa_term_info_set_seq: * @term_info: A #ChafaTermInfo. * @seq: A #ChafaTermSeq to query for. * @str: A control sequence string, or %NULL to clear. * @error: A return location for error details, or %NULL. * * Sets the control sequence string equivalent of @seq stored in @term_info to @str. * * The string may contain argument indexes to be substituted with integers on * formatting. The indexes are preceded by a percentage character and start at 1, * i.e. \%1, \%2, \%3, etc. * * The string's length after formatting must not exceed %CHAFA_TERM_SEQ_LENGTH_MAX * bytes. Each argument can add up to four digits, or three for those specified as * 8-bit integers. If the string could potentially exceed this length when * formatted, chafa_term_info_set_seq() will return %FALSE. * * If parsing fails or @str is too long, any previously existing sequence * will be left untouched. * * Passing %NULL for @str clears the corresponding control sequence. * * Returns: %TRUE if parsing succeeded, %FALSE otherwise * * Since: 1.6 **/ gboolean chafa_term_info_set_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, const gchar *str, GError **error) { gchar seq_str [CHAFA_TERM_SEQ_LENGTH_MAX]; SeqArgInfo seq_args [CHAFA_TERM_SEQ_ARGS_MAX]; gboolean result = FALSE; g_return_val_if_fail (term_info != NULL, FALSE); g_return_val_if_fail (seq >= 0 && seq < CHAFA_TERM_SEQ_MAX, FALSE); if (!str) { term_info->seq_str [seq] [0] = '\0'; term_info->seq_args [seq] [0].pre_len = 0; term_info->seq_args [seq] [0].arg_index = ARG_INDEX_SENTINEL; g_free (term_info->unparsed_str [seq]); term_info->unparsed_str [seq] = NULL; result = TRUE; } else { result = parse_seq_args (&seq_str [0], &seq_args [0], str, seq_meta [seq].n_args, seq_meta [seq].type_size == 1 ? 3 : 4, error); if (result == TRUE) { memcpy (&term_info->seq_str [seq] [0], &seq_str [0], CHAFA_TERM_SEQ_LENGTH_MAX); memcpy (&term_info->seq_args [seq] [0], &seq_args [0], CHAFA_TERM_SEQ_ARGS_MAX * sizeof (SeqArgInfo)); g_free (term_info->unparsed_str [seq]); term_info->unparsed_str [seq] = g_strdup (str); } } return result; } /** * chafa_term_info_supplement: * @term_info: A #ChafaTermInfo to supplement * @source: A #ChafaTermInfo to copy from * * Supplements missing sequences in @term_info with ones copied * from @source. * * Since: 1.6 **/ void chafa_term_info_supplement (ChafaTermInfo *term_info, ChafaTermInfo *source) { gint i; g_return_if_fail (term_info != NULL); g_return_if_fail (source != NULL); for (i = 0; i < CHAFA_TERM_SEQ_MAX; i++) { if (!term_info->unparsed_str [i] && source->unparsed_str [i]) { term_info->unparsed_str [i] = g_strdup (source->unparsed_str [i]); memcpy (&term_info->seq_str [i] [0], &source->seq_str [i] [0], CHAFA_TERM_SEQ_LENGTH_MAX); memcpy (&term_info->seq_args [i] [0], &source->seq_args [i] [0], CHAFA_TERM_SEQ_ARGS_MAX * sizeof (SeqArgInfo)); } } } #define DEFINE_EMIT_SEQ_0_none_void(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest) \ { return emit_seq_0_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name); } #define DEFINE_EMIT_SEQ_1_none_guint(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg) \ { return emit_seq_1_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg); } #define DEFINE_EMIT_SEQ_1_none_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ { return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg); } #define DEFINE_EMIT_SEQ_1_aix16fg_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ { return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + (arg < 8 ? 30 : (90 - 8))); } #define DEFINE_EMIT_SEQ_1_aix16bg_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg) \ { return emit_seq_1_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg + (arg < 8 ? 40 : (100 - 8))); } #define DEFINE_EMIT_SEQ_2_none_guint(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1) \ { return emit_seq_2_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1); } #define DEFINE_EMIT_SEQ_2_pos_guint(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1) \ { return emit_seq_2_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0 + 1, arg1 + 1); } #define DEFINE_EMIT_SEQ_2_none_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1) \ { return emit_seq_2_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1); } #define DEFINE_EMIT_SEQ_2_aix16fgbg_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1) \ { return emit_seq_2_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0 + (arg0 < 8 ? 30 : (90 - 8)), arg1 + (arg1 < 8 ? 40 : (100 - 8))); } #define DEFINE_EMIT_SEQ_3_none_guint(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1, guint arg2) \ { return emit_seq_3_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2); } #define DEFINE_EMIT_SEQ_5_none_guint(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint arg0, guint arg1, guint arg2, guint arg3, guint arg4) \ { return emit_seq_5_args_uint (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2, arg3, arg4); } #define DEFINE_EMIT_SEQ_3_none_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1, guint8 arg2) \ { return emit_seq_3_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2); } #define DEFINE_EMIT_SEQ_6_none_guint8(func_name, seq_name) \ gchar *chafa_term_info_emit_##func_name(const ChafaTermInfo *term_info, gchar *dest, guint8 arg0, guint8 arg1, guint8 arg2, guint8 arg3, guint8 arg4, guint8 arg5) \ { return emit_seq_6_args_uint8 (term_info, dest, CHAFA_TERM_SEQ_##seq_name, arg0, arg1, arg2, arg3, arg4, arg5); } #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ DEFINE_EMIT_SEQ_##n_args##_##arg_proc##_##arg_type(name, NAME) #include "chafa-term-seq-def.h" #undef CHAFA_TERM_SEQ_DEF chafa-1.8.0/chafa/chafa-term-info.h000066400000000000000000000075061411352071600167450ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_TERM_INFO_H__ #define __CHAFA_TERM_INFO_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS #define CHAFA_TERM_SEQ_LENGTH_MAX 96 #ifndef __GTK_DOC_IGNORE__ /* This declares the enum for CHAFA_TERM_SEQ_*. See chafa-term-seq-def.h * for more information, or look up the canonical documentation at * https://hpjansson.org/chafa/ref/ for verbose definitions. */ typedef enum { #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) CHAFA_TERM_SEQ_##NAME, #include #undef CHAFA_TERM_SEQ_DEF CHAFA_TERM_SEQ_MAX } ChafaTermSeq; #endif /* __GTK_DOC_IGNORE__ */ typedef struct ChafaTermInfo ChafaTermInfo; /** * CHAFA_TERM_INFO_ERROR: * * Error domain for #ChafaTermInfo. Errors in this domain will * be from the #ChafaTermInfoError enumeration. See #GError for information on * error domains. **/ #define CHAFA_TERM_INFO_ERROR (chafa_term_info_error_quark ()) /** * ChafaTermInfoError: * @CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG: A control sequence could exceed * #CHAFA_TERM_SEQ_LENGTH_MAX bytes if formatted with maximum argument lengths. * @CHAFA_TERM_INFO_ERROR_BAD_ESCAPE: An illegal escape sequence was used. * @CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS: A control sequence specified * more than the maximum number of arguments, or an argument index was out * of range. * * Error codes returned by control sequence parsing. **/ typedef enum { CHAFA_TERM_INFO_ERROR_SEQ_TOO_LONG, CHAFA_TERM_INFO_ERROR_BAD_ESCAPE, CHAFA_TERM_INFO_ERROR_BAD_ARGUMENTS } ChafaTermInfoError; CHAFA_AVAILABLE_IN_1_6 GQuark chafa_term_info_error_quark (void); CHAFA_AVAILABLE_IN_1_6 ChafaTermInfo *chafa_term_info_new (void); CHAFA_AVAILABLE_IN_1_6 ChafaTermInfo *chafa_term_info_copy (const ChafaTermInfo *term_info); CHAFA_AVAILABLE_IN_1_6 void chafa_term_info_ref (ChafaTermInfo *term_info); CHAFA_AVAILABLE_IN_1_6 void chafa_term_info_unref (ChafaTermInfo *term_info); CHAFA_AVAILABLE_IN_1_6 const gchar *chafa_term_info_get_seq (ChafaTermInfo *term_info, ChafaTermSeq seq); CHAFA_AVAILABLE_IN_1_6 gint chafa_term_info_set_seq (ChafaTermInfo *term_info, ChafaTermSeq seq, const gchar *str, GError **error); CHAFA_AVAILABLE_IN_1_6 gboolean chafa_term_info_have_seq (const ChafaTermInfo *term_info, ChafaTermSeq seq); CHAFA_AVAILABLE_IN_1_6 void chafa_term_info_supplement (ChafaTermInfo *term_info, ChafaTermInfo *source); /* This declares the prototypes for chafa_term_info_emit_*(). See * chafa-term-seq-def.h for more information, or look up the canonical * documentation at https://hpjansson.org/chafa/ref/ for verbose * function prototypes. */ #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ CHAFA_TERM_SEQ_AVAILABILITY gchar * chafa_term_info_emit_##name(const ChafaTermInfo *term_info, gchar *dest __VA_ARGS__); #include #undef CHAFA_TERM_SEQ_DEF G_END_DECLS #endif /* __CHAFA_TERM_INFO_H__ */ chafa-1.8.0/chafa/chafa-term-seq-def.h000066400000000000000000000733471411352071600173440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ /* Terminal sequence definitions * ----------------------------- * * This file is #included in various contexts with CHAFA_TERM_SEQ_DEF() * expanding to different things. It allows us to keep all the terminal * sequence metadata in one place. * * We process this file with 'cpp -CC' to let the docstrings through to * gtk-doc. * * The generator macro is invoked with the following arguments: * * CHAFA_TERM_SEQ_DEF (name, NAME, n_args, args_proc, args_type, ...) * * Sequences are grouped by the library version they became available in, * with CHAFA_TERM_SEQ_AVAILABILITY expanding to the appropriate version * macro in each case. * * The actual sequence strings are not defined here; they belong to the * individual terminal model definitions. * * References * ---------- * * VT220 sequences: https://vt100.net/docs/vt220-rm/chapter4.html * Sixels: https://vt100.net/docs/vt3xx-gp/chapter14.html */ /* __VA_OPT__ from C++2a would be nice, but it's too recent to rely on in * public headers just yet. So we have this exciting trick instead. */ #define CHAFA_TERM_SEQ_ARGS , /* --- Available in 1.6+ --- */ #define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_6 /** * chafa_term_info_emit_reset_terminal_soft: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(reset_terminal_soft, RESET_TERMINAL_SOFT, 0, none, void) /** * chafa_term_info_emit_reset_terminal_hard: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_TERMINAL_HARD. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(reset_terminal_hard, RESET_TERMINAL_HARD, 0, none, void) /** * chafa_term_info_emit_reset_attributes: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_RESET_ATTRIBUTES. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(reset_attributes, RESET_ATTRIBUTES, 0, none, void) /** * chafa_term_info_emit_clear: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CLEAR. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(clear, CLEAR, 0, none, void) /** * chafa_term_info_emit_invert_colors: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_INVERT_COLORS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(invert_colors, INVERT_COLORS, 0, none, void) /* Cursor movement. Cursor stops at margins. */ /** * chafa_term_info_emit_cursor_to_top_left: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_TOP_LEFT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_to_top_left, CURSOR_TO_TOP_LEFT, 0, none, void) /** * chafa_term_info_emit_cursor_to_bottom_left: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_BOTTOM_LEFT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_to_bottom_left, CURSOR_TO_BOTTOM_LEFT, 0, none, void) /** * chafa_term_info_emit_cursor_to_pos: * @term_info: A #ChafaTermInfo * @dest: String destination * @x: Offset from left edge of display, zero-indexed * @y: Offset from top edge of display, zero-indexed * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_TO_POS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_to_pos, CURSOR_TO_POS, 2, pos, guint, CHAFA_TERM_SEQ_ARGS guint x, guint y) /** * chafa_term_info_emit_cursor_up_1: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP_1. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_up_1, CURSOR_UP_1, 0, none, void) /** * chafa_term_info_emit_cursor_up: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Distance to move the cursor * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_up, CURSOR_UP, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /** * chafa_term_info_emit_cursor_down_1: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN_1. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_down_1, CURSOR_DOWN_1, 0, none, void) /** * chafa_term_info_emit_cursor_down: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Distance to move the cursor * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_down, CURSOR_DOWN, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /** * chafa_term_info_emit_cursor_left_1: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_LEFT_1. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_left_1, CURSOR_LEFT_1, 0, none, void) /** * chafa_term_info_emit_cursor_left: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Distance to move the cursor * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_LEFT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_left, CURSOR_LEFT, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /** * chafa_term_info_emit_cursor_right_1: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_RIGHT_1. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_right_1, CURSOR_RIGHT_1, 0, none, void) /** * chafa_term_info_emit_cursor_right: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Distance to move the cursor * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_RIGHT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_right, CURSOR_RIGHT, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /* Cursor movement. Cursor crossing margin causes scrolling region to * scroll. */ /** * chafa_term_info_emit_cursor_up_scroll: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_UP_SCROLL. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_up_scroll, CURSOR_UP_SCROLL, 0, none, void) /** * chafa_term_info_emit_cursor_down_scroll: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_CURSOR_DOWN_SCROLL. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(cursor_down_scroll, CURSOR_DOWN_SCROLL, 0, none, void) /* Cells will shift on insert. Cells shifted off the edge will be lost. */ /** * chafa_term_info_emit_insert_cells: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Number of cells to insert * * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_CELLS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(insert_cells, INSERT_CELLS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /** * chafa_term_info_emit_delete_cells: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Number of cells to delete * * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_CELLS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(delete_cells, DELETE_CELLS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /* Cursor must be inside scrolling region. Rows are shifted inside the * scrolling region. Rows shifted off the edge will be lost. The cursor * position is reset to the first column. */ /** * chafa_term_info_emit_insert_rows: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Number of rows to insert * * Prints the control sequence for #CHAFA_TERM_SEQ_INSERT_ROWS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(insert_rows, INSERT_ROWS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /** * chafa_term_info_emit_delete_rows: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Number of rows to delete * * Prints the control sequence for #CHAFA_TERM_SEQ_DELETE_ROWS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(delete_rows, DELETE_ROWS, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /* Defines the scrolling region. */ /** * chafa_term_info_emit_set_scrolling_rows: * @term_info: A #ChafaTermInfo * @dest: String destination * @top: First row in scrolling area, zero-indexed * @bottom: Last row in scrolling area, zero-indexed * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_SCROLLING_ROWS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_scrolling_rows, SET_SCROLLING_ROWS, 2, pos, guint, CHAFA_TERM_SEQ_ARGS guint top, guint bottom) /* Indicates whether characters printed in the middle of a row should * cause subsequent cells to shift forwards. Cells shifted off the edge * will be lost. If disabled, cells at the cursor position will be * overwritten instead. */ /** * chafa_term_info_emit_enable_insert: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_INSERT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(enable_insert, ENABLE_INSERT, 0, none, void) /** * chafa_term_info_emit_disable_insert: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_INSERT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(disable_insert, DISABLE_INSERT, 0, none, void) /** * chafa_term_info_emit_enable_cursor: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_CURSOR. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(enable_cursor, ENABLE_CURSOR, 0, none, void) /** * chafa_term_info_emit_disable_cursor: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_CURSOR. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(disable_cursor, DISABLE_CURSOR, 0, none, void) /** * chafa_term_info_emit_enable_echo: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_ECHO. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(enable_echo, ENABLE_ECHO, 0, none, void) /** * chafa_term_info_emit_disable_echo: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_ECHO. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(disable_echo, DISABLE_ECHO, 0, none, void) /* When printing a character in the last column, indicates whether the * cursor should move to the next row and potentially cause scrolling. If * disabled, the cursor may still move to the first column. */ /** * chafa_term_info_emit_enable_wrap: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_ENABLE_WRAP. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(enable_wrap, ENABLE_WRAP, 0, none, void) /** * chafa_term_info_emit_disable_wrap: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_DISABLE_WRAP. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(disable_wrap, DISABLE_WRAP, 0, none, void) /** * chafa_term_info_emit_set_color_fg_direct: * @term_info: A #ChafaTermInfo * @dest: String destination * @r: Red component, 0-255 * @g: Green component, 0-255 * @b: Blue component, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fg_direct, SET_COLOR_FG_DIRECT, 3, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 r, guint8 g, guint8 b) /** * chafa_term_info_emit_set_color_bg_direct: * @term_info: A #ChafaTermInfo * @dest: String destination * @r: Red component, 0-255 * @g: Green component, 0-255 * @b: Blue component, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_bg_direct, SET_COLOR_BG_DIRECT, 3, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 r, guint8 g, guint8 b) /** * chafa_term_info_emit_set_color_fgbg_direct: * @term_info: A #ChafaTermInfo * @dest: String destination * @fg_r: Foreground red component, 0-255 * @fg_g: Foreground green component, 0-255 * @fg_b: Foreground blue component, 0-255 * @bg_r: Background red component, 0-255 * @bg_g: Background green component, 0-255 * @bg_b: Background blue component, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fgbg_direct, SET_COLOR_FGBG_DIRECT, 6, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_r, guint8 fg_g, guint8 fg_b, guint8 bg_r, guint8 bg_g, guint8 bg_b) /** * chafa_term_info_emit_set_color_fg_256: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_256. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fg_256, SET_COLOR_FG_256, 1, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_bg_256: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_256. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_bg_256, SET_COLOR_BG_256, 1, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_fgbg_256: * @term_info: A #ChafaTermInfo * @dest: String destination * @fg_pen: Foreground pen number, 0-255 * @bg_pen: Background pen number, 0-255 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_256. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fgbg_256, SET_COLOR_FGBG_256, 2, none, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) /** * chafa_term_info_emit_set_color_fg_16: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-15 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FG_16. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fg_16, SET_COLOR_FG_16, 1, aix16fg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_bg_16: * @term_info: A #ChafaTermInfo * @dest: String destination * @pen: Pen number, 0-15 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_BG_16. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_bg_16, SET_COLOR_BG_16, 1, aix16bg, guint8, CHAFA_TERM_SEQ_ARGS guint8 pen) /** * chafa_term_info_emit_set_color_fgbg_16: * @term_info: A #ChafaTermInfo * @dest: String destination * @fg_pen: Foreground pen number, 0-15 * @bg_pen: Background pen number, 0-15 * * Prints the control sequence for #CHAFA_TERM_SEQ_SET_COLOR_FGBG_16. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(set_color_fgbg_16, SET_COLOR_FGBG_16, 2, aix16fgbg, guint8, CHAFA_TERM_SEQ_ARGS guint8 fg_pen, guint8 bg_pen) /** * chafa_term_info_emit_begin_sixels: * @term_info: A #ChafaTermInfo * @dest: String destination * @p1: Pixel aspect selector * @p2: Background color selector * @p3: Horizontal grid selector * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_SIXELS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * All three parameters (@p1, @p2 and @p3) can normally be set to 0. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(begin_sixels, BEGIN_SIXELS, 3, none, guint, CHAFA_TERM_SEQ_ARGS guint p1, guint p2, guint p3) /** * chafa_term_info_emit_end_sixels: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_SIXELS. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(end_sixels, END_SIXELS, 0, none, void) /** * chafa_term_info_emit_repeat_char: * @term_info: A #ChafaTermInfo * @dest: String destination * @n: Number of repetitions * * Prints the control sequence for #CHAFA_TERM_SEQ_REPEAT_CHAR. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.6 **/ CHAFA_TERM_SEQ_DEF(repeat_char, REPEAT_CHAR, 1, none, guint, CHAFA_TERM_SEQ_ARGS guint n) /* --- Available in 1.8+ --- */ #undef CHAFA_TERM_SEQ_AVAILABILITY #define CHAFA_TERM_SEQ_AVAILABILITY CHAFA_AVAILABLE_IN_1_8 /** * chafa_term_info_emit_begin_kitty_immediate_image_v1: * @term_info: A #ChafaTermInfo * @dest: String destination * @bpp: Bits per pixel * @width_pixels: Image width in pixels * @height_pixels: Image height in pixels * @width_cells: Target width in cells * @height_cells: Target height in cells * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_IMMEDITATE. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * @bpp must be set to either 24 for RGB data, 32 for RGBA, or 100 to embed a * PNG file. * * This sequence must be followed by zero or more paired sequences of * type #CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK and #CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK * with base-64 encoded image data between them. * * When the image data has been transferred, #CHAFA_TERM_SEQ_END_KITTY_IMAGE must * be emitted. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(begin_kitty_immediate_image_v1, BEGIN_KITTY_IMMEDIATE_IMAGE_V1, 5, none, guint, CHAFA_TERM_SEQ_ARGS guint bpp, guint width_pixels, guint height_pixels, guint width_cells, guint height_cells) /** * chafa_term_info_emit_end_kitty_image: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_KITTY_IMAGE. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(end_kitty_image, END_KITTY_IMAGE, 0, none, void) /** * chafa_term_info_emit_begin_kitty_image_chunk: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_KITTY_IMAGE_CHUNK. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(begin_kitty_image_chunk, BEGIN_KITTY_IMAGE_CHUNK, 0, none, void) /** * chafa_term_info_emit_end_kitty_image_chunk: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_KITTY_IMAGE_CHUNK. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(end_kitty_image_chunk, END_KITTY_IMAGE_CHUNK, 0, none, void) /** * chafa_term_info_emit_begin_iterm2_image: * @term_info: A #ChafaTermInfo * @dest: String destination * @width: Image width in character cells * @height: Image height in character cells * * Prints the control sequence for #CHAFA_TERM_SEQ_BEGIN_ITERM2_IMAGE_IMMEDITATE. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * This sequence must be followed by base64-encoded image file data. The image * can be any format supported by MacOS, e.g. PNG, JPEG, TIFF, GIF. When the * image data has been transferred, #CHAFA_TERM_SEQ_END_ITERM2_IMAGE must be * emitted. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(begin_iterm2_image, BEGIN_ITERM2_IMAGE, 2, none, guint, CHAFA_TERM_SEQ_ARGS guint width, guint height) /** * chafa_term_info_emit_end_iterm2_image: * @term_info: A #ChafaTermInfo * @dest: String destination * * Prints the control sequence for #CHAFA_TERM_SEQ_END_ITERM2_IMAGE. * * @dest must have enough space to hold * #CHAFA_TERM_SEQ_LENGTH_MAX bytes, even if the emitted sequence is * shorter. The output will not be zero-terminated. * * Returns: Pointer to first byte after emitted string * * Since: 1.8 **/ CHAFA_TERM_SEQ_DEF(end_iterm2_image, END_ITERM2_IMAGE, 0, none, void) #undef CHAFA_TERM_SEQ_AVAILABILITY #undef CHAFA_TERM_SEQ_ARGS chafa-1.8.0/chafa/chafa-term-seq-doc-in.h000066400000000000000000000024161411352071600177440ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) \ gchar *chafa_term_info_emit_##name (const ChafaTermInfo *term_info, gchar *dest __VA_ARGS__); #include "chafa-term-seq-def.h" #undef CHAFA_TERM_SEQ_DEF typedef enum { #define CHAFA_TERM_SEQ_DEF(name, NAME, n_args, arg_proc, arg_type, ...) CHAFA_TERM_SEQ_##NAME, #include "chafa-term-seq-def.h" #undef CHAFA_TERM_SEQ_DEF CHAFA_TERM_SEQ_MAX } ChafaTermSeq; chafa-1.8.0/chafa/chafa-util.c000066400000000000000000000144621411352071600160140ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-private.h" /** * SECTION:chafa-util * @title: Miscellaneous * @short_description: Potentially useful functions * * Miscellaneous functions that may be useful to Chafa users. **/ /** * chafa_calc_canvas_geometry: * @src_width: Width of source * @src_height: Height of source * @dest_width_inout: Inout location for width of destination * @dest_height_inout: Inout location for height of destination * @font_ratio: Target font's width to height ratio * @zoom: %TRUE to upscale image to fit maximum dimensions, %FALSE otherwise * @stretch: %TRUE to ignore aspect of source, %FALSE otherwise * * Calculates an optimal geometry for a #ChafaCanvas given the width and * height of an input image, maximum width and height of the canvas, font * ratio, zoom and stretch preferences. * * @src_width and @src_height must both be zero or greater. * * @dest_width_inout and @dest_height_inout must point to integers * containing the maximum dimensions of the canvas in character cells. * These will be replaced by the calculated values, which may be zero if * one of the input dimensions is zero. If one or both of the input * parameters is negative, they will be treated as unspecified and * calculated based on the remaining parameters and aspect ratio. * * @font_ratio is the font's width divided by its height. 0.5 is a typical * value. **/ void chafa_calc_canvas_geometry (gint src_width, gint src_height, gint *dest_width_inout, gint *dest_height_inout, gfloat font_ratio, gboolean zoom, gboolean stretch) { gint dest_width = -1, dest_height = -1; g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); g_return_if_fail (font_ratio > 0.0); if (dest_width_inout) dest_width = *dest_width_inout; if (dest_height_inout) dest_height = *dest_height_inout; /* If any dimension is explicitly set to zero, width and height will * both be zero. */ if (src_width == 0 || src_height == 0 || dest_width == 0 || dest_height == 0) { if (dest_width_inout) *dest_width_inout = 0; if (dest_height_inout) *dest_height_inout = 0; return; } /* If both output dimensions are unspecified, make them 1/8 of their * corresponding input dimensions, rounding up and accounting * for font ratio. Both dimensions will be >= 1. */ if (dest_width < 0 && dest_height < 0) { if (dest_width_inout) { *dest_width_inout = (src_width + 7 / 8); *dest_width_inout = MAX (*dest_width_inout, 1); } if (dest_height_inout) { *dest_height_inout = (src_height + 7 / 8) * font_ratio + 0.5; *dest_height_inout = MAX (*dest_height_inout, 1); } return; } if (!zoom) { dest_width = MIN (dest_width, src_width); dest_height = MIN (dest_height, src_height); } if (!stretch || dest_width < 0 || dest_height < 0) { gdouble src_aspect; gdouble dest_aspect; src_aspect = src_width / (gdouble) src_height; dest_aspect = (dest_width / (gdouble) dest_height) * font_ratio; if (dest_width < 1) { dest_width = dest_height * (src_aspect / font_ratio) + 0.5; } else if (dest_height < 1) { dest_height = (dest_width / src_aspect) * font_ratio + 0.5; } else if (src_aspect > dest_aspect) { dest_height = dest_width * (font_ratio / src_aspect); } else { dest_width = dest_height * (src_aspect / font_ratio); } } dest_width = MAX (dest_width, 1); dest_height = MAX (dest_height, 1); if (dest_width_inout) *dest_width_inout = dest_width; if (dest_height_inout) *dest_height_inout = dest_height; } /* --- Internal; not part of public API --- */ static void fill_matrix_r (gint *matrix, gint matrix_size, gint sub_size, gint x, gint y, gint value, gint step) { gint half; if (sub_size == 1) { matrix [x + y * matrix_size] = value; return; } half = sub_size / 2; fill_matrix_r (matrix, matrix_size, half, x, y, value, step * 4); fill_matrix_r (matrix, matrix_size, half, x + half, y + half, value + step, step * 4); fill_matrix_r (matrix, matrix_size, half, x + half, y, value + step * 2, step * 4); fill_matrix_r (matrix, matrix_size, half, x, y + half, value + step * 3, step * 4); } static void fill_matrix (gint *matrix, gint matrix_size, gdouble magnitude) { gint maxval = matrix_size * matrix_size; gdouble maxval_d = maxval; gint i; fill_matrix_r (matrix, matrix_size, matrix_size, 0, 0, 0, 1); /* Recenter around 0 and scale so magnitude == 1.0 => -128..127 */ for (i = 0; i < matrix_size * matrix_size; i++) { matrix [i] = ((gdouble) matrix [i] - maxval_d / 2.0) * (256.0 / maxval_d) * magnitude + 0.5; } } gint * chafa_gen_bayer_matrix (gint matrix_size, gdouble magnitude) { gint *matrix; g_assert (matrix_size == 2 || matrix_size == 4 || matrix_size == 8 || matrix_size == 16); matrix = g_malloc (matrix_size * matrix_size * sizeof (gint)); fill_matrix (matrix, matrix_size, magnitude); return matrix; } chafa-1.8.0/chafa/chafa-util.h000066400000000000000000000027451411352071600160220ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_UTIL_H__ #define __CHAFA_UTIL_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif G_BEGIN_DECLS /* Miscellaneous functions */ CHAFA_AVAILABLE_IN_ALL void chafa_calc_canvas_geometry (gint src_width, gint src_height, gint *dest_width_inout, gint *dest_height_inout, gfloat font_ratio, gboolean zoom, gboolean stretch); G_END_DECLS #endif /* __CHAFA_UTIL_H__ */ chafa-1.8.0/chafa/chafa-version-macros.h000066400000000000000000000164021411352071600200070ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_VERSION_MACROS_H__ #define __CHAFA_VERSION_MACROS_H__ #if !defined (__CHAFA_H_INSIDE__) && !defined (CHAFA_COMPILATION) # error "Only can be included directly." #endif /* Our current version is defined here */ #include G_BEGIN_DECLS /* Exported symbol versioning/visibility. Similar to the versioning macros * used by GLib. */ #define CHAFA_VERSION_1_0 (G_ENCODE_VERSION (1, 0)) #define CHAFA_VERSION_1_2 (G_ENCODE_VERSION (1, 2)) #define CHAFA_VERSION_1_4 (G_ENCODE_VERSION (1, 4)) #define CHAFA_VERSION_1_6 (G_ENCODE_VERSION (1, 6)) #define CHAFA_VERSION_1_8 (G_ENCODE_VERSION (1, 8)) #define CHAFA_VERSION_1_10 (G_ENCODE_VERSION (1, 10)) /* Evaluates to the current stable version; for development cycles, * this means the next stable target. */ #if (CHAFA_MINOR_VERSION % 2) #define CHAFA_VERSION_CUR_STABLE (G_ENCODE_VERSION (CHAFA_MAJOR_VERSION, CHAFA_MINOR_VERSION + 1)) #else #define CHAFA_VERSION_CUR_STABLE (G_ENCODE_VERSION (CHAFA_MAJOR_VERSION, CHAFA_MINOR_VERSION)) #endif /* Evaluates to the previous stable version */ #if (CHAFA_MINOR_VERSION % 2) #define CHAFA_VERSION_PREV_STABLE (G_ENCODE_VERSION (CHAFA_MAJOR_VERSION, CHAFA_MINOR_VERSION - 1)) #else #define CHAFA_VERSION_PREV_STABLE (G_ENCODE_VERSION (CHAFA_MAJOR_VERSION, CHAFA_MINOR_VERSION - 2)) #endif /** * CHAFA_VERSION_MIN_REQUIRED: * * A macro that can be defined by the user prior to including * the chafa.h header. * The definition should be one of the predefined Chafa version * macros: %CHAFA_VERSION_1_0, %CHAFA_VERSION_1_2,... * * This macro defines the earliest version of Chafa that the package is * required to be able to compile against. * * If the compiler is configured to warn about the use of deprecated * functions, then using functions that were deprecated in version * %CHAFA_VERSION_MIN_REQUIRED or earlier will cause warnings (but * using functions deprecated in later releases will not). * * Since: 1.2 */ /* Make sure all exportable symbols are made visible, even * deprecated ones. */ #ifdef CHAFA_COMPILATION # define CHAFA_VERSION_MIN_REQUIRED (CHAFA_VERSION_1_0) #endif /* If the package sets CHAFA_VERSION_MIN_REQUIRED to some future * CHAFA_VERSION_X_Y value that we don't know about, it will compare as * 0 in preprocessor tests. */ #ifndef CHAFA_VERSION_MIN_REQUIRED # define CHAFA_VERSION_MIN_REQUIRED (CHAFA_VERSION_CUR_STABLE) #elif CHAFA_VERSION_MIN_REQUIRED == 0 # undef CHAFA_VERSION_MIN_REQUIRED # define CHAFA_VERSION_MIN_REQUIRED (CHAFA_VERSION_CUR_STABLE + 2) #endif /** * CHAFA_VERSION_MAX_ALLOWED: * * A macro that can be defined by the user prior to including * the chafa.h header. * The definition should be one of the predefined Chafa version * macros: %CHAFA_VERSION_1_0, %CHAFA_VERSION_1_2,... * * This macro defines the latest version of the Chafa API that the * package is allowed to make use of. * * If the compiler is configured to warn about the use of deprecated * functions, then using functions added after version * %CHAFA_VERSION_MAX_ALLOWED will cause warnings. * * This should normally be set to the same value as * %CHAFA_VERSION_MIN_REQUIRED. * * Since: 1.2 */ #if !defined (CHAFA_VERSION_MAX_ALLOWED) || (CHAFA_VERSION_MAX_ALLOWED == 0) # undef CHAFA_VERSION_MAX_ALLOWED # define CHAFA_VERSION_MAX_ALLOWED (CHAFA_VERSION_CUR_STABLE) #endif /* Sanity checks */ #if CHAFA_VERSION_MIN_REQUIRED > CHAFA_VERSION_CUR_STABLE #error "CHAFA_VERSION_MIN_REQUIRED must be <= CHAFA_VERSION_CUR_STABLE" #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_MIN_REQUIRED #error "CHAFA_VERSION_MAX_ALLOWED must be >= CHAFA_VERSION_MIN_REQUIRED" #endif #if CHAFA_VERSION_MIN_REQUIRED < CHAFA_VERSION_1_0 #error "CHAFA_VERSION_MIN_REQUIRED must be >= CHAFA_VERSION_1_0" #endif #ifndef _CHAFA_EXTERN # define _CHAFA_EXTERN extern #endif #define CHAFA_AVAILABLE_IN_ALL _CHAFA_EXTERN #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_2 # define CHAFA_DEPRECATED_IN_1_2 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_2_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_2 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_2_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_2 # define CHAFA_AVAILABLE_IN_1_2 G_UNAVAILABLE(1, 2) #else # define CHAFA_AVAILABLE_IN_1_2 _CHAFA_EXTERN #endif #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_4 # define CHAFA_DEPRECATED_IN_1_4 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_4_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_4 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_4_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_4 # define CHAFA_AVAILABLE_IN_1_4 G_UNAVAILABLE(1, 4) #else # define CHAFA_AVAILABLE_IN_1_4 _CHAFA_EXTERN #endif #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_6 # define CHAFA_DEPRECATED_IN_1_6 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_6_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_6 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_6_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_6 # define CHAFA_AVAILABLE_IN_1_6 G_UNAVAILABLE(1, 6) #else # define CHAFA_AVAILABLE_IN_1_6 _CHAFA_EXTERN #endif #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_8 # define CHAFA_DEPRECATED_IN_1_8 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_8_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_8 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_8_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_8 # define CHAFA_AVAILABLE_IN_1_8 G_UNAVAILABLE(1, 8) #else # define CHAFA_AVAILABLE_IN_1_8 _CHAFA_EXTERN #endif #if CHAFA_VERSION_MIN_REQUIRED >= CHAFA_VERSION_1_10 # define CHAFA_DEPRECATED_IN_1_10 G_DEPRECATED # define CHAFA_DEPRECATED_IN_1_10_FOR(f) G_DEPRECATED_FOR(f) #else # define CHAFA_DEPRECATED_IN_1_10 _CHAFA_EXTERN # define CHAFA_DEPRECATED_IN_1_10_FOR(f) _CHAFA_EXTERN #endif #if CHAFA_VERSION_MAX_ALLOWED < CHAFA_VERSION_1_10 # define CHAFA_AVAILABLE_IN_1_10 G_UNAVAILABLE(1, 10) #else # define CHAFA_AVAILABLE_IN_1_10 _CHAFA_EXTERN #endif G_END_DECLS #endif /* __CHAFA_VERSION_MACROS_H__ */ chafa-1.8.0/chafa/chafa.h000066400000000000000000000024561411352071600150460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_H__ #define __CHAFA_H__ #define __CHAFA_H_INSIDE__ #include G_BEGIN_DECLS /* Version macros go before everything else */ #include #include #include #include #include #include #include #include #include G_END_DECLS #undef __CHAFA_H_INSIDE__ #endif /* __CHAFA_H__ */ chafa-1.8.0/chafa/chafaconfig.h.in000066400000000000000000000005371411352071600166370ustar00rootroot00000000000000/* chafaconfig.h * * This is a generated file. Please modify 'chafaconfig.h.in'. */ #ifndef __CHAFACONFIG_H__ #define __CHAFACONFIG_H__ G_BEGIN_DECLS #define CHAFA_MAJOR_VERSION @CHAFA_MAJOR_VERSION@ #define CHAFA_MINOR_VERSION @CHAFA_MINOR_VERSION@ #define CHAFA_MICRO_VERSION @CHAFA_MICRO_VERSION@ G_END_DECLS #endif /* __CHAFACONFIG_H__ */ chafa-1.8.0/chafa/internal/000077500000000000000000000000001411352071600154405ustar00rootroot00000000000000chafa-1.8.0/chafa/internal/Makefile.am000066400000000000000000000042651411352071600175030ustar00rootroot00000000000000SUBDIRS = smolscale ## --- Library --- noinst_LTLIBRARIES = libchafa-internal.la libchafa_internal_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -DCHAFA_COMPILATION libchafa_internal_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) libchafa_internal_la_LIBADD = $(GLIB_LIBS) smolscale/libsmolscale.la -lm libchafa_internal_la_SOURCES = \ chafa-base64.c \ chafa-base64.h \ chafa-batch.c \ chafa-batch.h \ chafa-bitfield.h \ chafa-canvas-internal.h \ chafa-canvas-printer.c \ chafa-canvas-printer.h \ chafa-color.c \ chafa-color.h \ chafa-color-hash.c \ chafa-color-hash.h \ chafa-color-table.c \ chafa-color-table.h \ chafa-dither.c \ chafa-dither.h \ chafa-indexed-image.c \ chafa-indexed-image.h \ chafa-iterm2-canvas.c \ chafa-iterm2-canvas.h \ chafa-kitty-canvas.c \ chafa-kitty-canvas.h \ chafa-palette.c \ chafa-palette.h \ chafa-pca.c \ chafa-pca.h \ chafa-pixops.c \ chafa-pixops.h \ chafa-private.h \ chafa-sixel-canvas.c \ chafa-sixel-canvas.h \ chafa-string-util.c \ chafa-string-util.h \ chafa-symbols.c \ chafa-symbols-ascii.h \ chafa-symbols-block.h \ chafa-symbols-kana.h \ chafa-symbols-misc-narrow.h \ chafa-work-cell.c \ chafa-work-cell.h if HAVE_MMX_INTRINSICS noinst_LTLIBRARIES += libchafa-mmx.la libchafa_internal_la_LIBADD += libchafa-mmx.la libchafa_mmx_la_SOURCES = chafa-mmx.c libchafa_mmx_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mmmx -DCHAFA_COMPILATION libchafa_mmx_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) endif if HAVE_SSE41_INTRINSICS noinst_LTLIBRARIES += libchafa-sse41.la libchafa_internal_la_LIBADD += libchafa-sse41.la libchafa_sse41_la_SOURCES = chafa-sse41.c libchafa_sse41_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -msse4.1 -DCHAFA_COMPILATION libchafa_sse41_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) endif if HAVE_POPCNT_INTRINSICS noinst_LTLIBRARIES += libchafa-popcnt.la libchafa_internal_la_LIBADD += libchafa-popcnt.la libchafa_popcnt_la_SOURCES = chafa-popcnt.c libchafa_popcnt_la_CFLAGS = $(LIBCHAFA_CFLAGS) $(GLIB_CFLAGS) -mpopcnt -DCHAFA_COMPILATION libchafa_popcnt_la_LDFLAGS = $(LIBCHAFA_LDFLAGS) endif ## -- General --- ## Include $(top_builddir)/chafa to get generated chafaconfig.h. AM_CPPFLAGS = \ -I$(top_srcdir)/chafa \ -I$(top_builddir)/chafa chafa-1.8.0/chafa/internal/chafa-base64.c000066400000000000000000000062421411352071600177340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-base64.h" static const gchar base64_dict [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; void chafa_base64_init (ChafaBase64 *base64) { memset (base64, 0, sizeof (*base64)); } void chafa_base64_deinit (ChafaBase64 *base64) { memset (base64, 0, sizeof (*base64)); base64->buf_len = -1; } static void encode_3_bytes (GString *gs_out, guint32 bytes) { g_string_append_c (gs_out, base64_dict [(bytes >> (3 * 6)) & 0x3f]); g_string_append_c (gs_out, base64_dict [(bytes >> (2 * 6)) & 0x3f]); g_string_append_c (gs_out, base64_dict [(bytes >> (1 * 6)) & 0x3f]); g_string_append_c (gs_out, base64_dict [bytes & 0x3f]); } void chafa_base64_encode (ChafaBase64 *base64, GString *gs_out, gconstpointer in, gint in_len) { const guint8 *in_u8 = in; const guint8 *end_u8 = in_u8 + in_len; guint32 r; if (base64->buf_len + in_len < 3) { memcpy (base64->buf + base64->buf_len, in_u8, in_len); base64->buf_len += in_len; return; } if (base64->buf_len == 1) { r = (base64->buf [0] << 16) | (in_u8 [0] << 8) | in_u8 [1]; in_u8 += 2; encode_3_bytes (gs_out, r); } else if (base64->buf_len == 2) { r = (base64->buf [0] << 16) | (base64->buf [1] << 8) | in_u8 [0]; in_u8++; encode_3_bytes (gs_out, r); } base64->buf_len = 0; while (end_u8 - in_u8 >= 3) { r = (in_u8 [0] << 16) | (in_u8 [1] << 8) | in_u8 [2]; encode_3_bytes (gs_out, r); in_u8 += 3; } while (end_u8 - in_u8 > 0) { base64->buf [base64->buf_len++] = *(in_u8++); } } void chafa_base64_encode_end (ChafaBase64 *base64, GString *gs_out) { if (base64->buf_len == 1) { g_string_append_c (gs_out, base64_dict [base64->buf [0] >> 2]); g_string_append_c (gs_out, base64_dict [(base64->buf [0] << 4) & 0x30]); g_string_append (gs_out, "=="); } else if (base64->buf_len == 2) { g_string_append_c (gs_out, base64_dict [base64->buf [0] >> 2]); g_string_append_c (gs_out, base64_dict [((base64->buf [0] << 4) | (base64->buf [1] >> 4)) & 0x3f]); g_string_append_c (gs_out, base64_dict [base64->buf [1] & 0x0f]); g_string_append_c (gs_out, '='); } } chafa-1.8.0/chafa/internal/chafa-base64.h000066400000000000000000000026641411352071600177450ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_BASE64_H__ #define __CHAFA_BASE64_H__ #include #include "chafa.h" G_BEGIN_DECLS typedef struct { /* We turn 3-byte groups into 4-character base64 groups, so we * may need to buffer up to 2 bytes between batches */ guint8 buf [2]; gint buf_len; } ChafaBase64; void chafa_base64_init (ChafaBase64 *base64); void chafa_base64_deinit (ChafaBase64 *base64); void chafa_base64_encode (ChafaBase64 *base64, GString *gs_out, gconstpointer in, gint in_len); void chafa_base64_encode_end (ChafaBase64 *base64, GString *gs_out); G_END_DECLS #endif /* __CHAFA_BASE64_H__ */ chafa-1.8.0/chafa/internal/chafa-batch.c000066400000000000000000000056241411352071600177340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "internal/chafa-batch.h" void chafa_process_batches (gpointer ctx, GFunc batch_func, GFunc post_func, gint n_rows, gint n_batches, gint batch_unit) { GThreadPool *thread_pool; ChafaBatchInfo *batches; gint n_units; gfloat units_per_batch; gfloat ofs [2] = { .0, .0 }; gint i; g_assert (n_batches >= 1); g_assert (batch_unit >= 1); if (n_rows < 1) return; n_units = (n_rows + batch_unit - 1) / batch_unit; units_per_batch = (gfloat) n_units / (gfloat) n_batches; batches = g_new0 (ChafaBatchInfo, n_batches); thread_pool = g_thread_pool_new (batch_func, (gpointer) ctx, g_get_num_processors (), FALSE, NULL); /* Divide work up into batches that are multiples of batch_unit, except * for the last one (if n_rows is not itself a multiple) */ for (i = 0; i < n_batches; ) { ChafaBatchInfo *batch; gint row_ofs [2]; row_ofs [0] = ofs [0]; do { ofs [1] += units_per_batch; row_ofs [1] = ofs [1]; } while (row_ofs [0] == row_ofs [1]); row_ofs [0] *= batch_unit; row_ofs [1] *= batch_unit; if (row_ofs [1] > n_rows) { ofs [1] = n_rows + 0.5; row_ofs [1] = n_rows; } if (row_ofs [0] >= row_ofs [1]) break; batch = &batches [i++]; batch->first_row = row_ofs [0]; batch->n_rows = row_ofs [1] - row_ofs [0]; #if 0 g_printerr ("Batch %d: %04d rows\n", i, batch->n_rows); #endif g_thread_pool_push (thread_pool, batch, NULL); ofs [0] = ofs [1]; } /* Wait for threads to finish */ g_thread_pool_free (thread_pool, FALSE, TRUE); if (post_func) { for (i = 0; i < n_batches; i++) { ((void (*)(ChafaBatchInfo *, gpointer)) post_func) (&batches [i], ctx); } } g_free (batches); } chafa-1.8.0/chafa/internal/chafa-batch.h000066400000000000000000000023401411352071600177310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_BATCH_H__ #define __CHAFA_BATCH_H__ #include G_BEGIN_DECLS typedef struct { gint first_row; gint n_rows; gpointer ret_p; gint ret_n; } ChafaBatchInfo; void chafa_process_batches (gpointer ctx, GFunc batch_func, GFunc post_func, gint n_rows, gint n_batches, gint batch_unit); G_END_DECLS #endif /* __CHAFA_BATCH_H__ */ chafa-1.8.0/chafa/internal/chafa-bitfield.h000066400000000000000000000041501411352071600204330ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_BITFIELD_H__ #define __CHAFA_BITFIELD_H__ G_BEGIN_DECLS typedef struct { guint32 *bits; guint n_bits; } ChafaBitfield; static inline void chafa_bitfield_init (ChafaBitfield *bitfield, guint n_bits) { bitfield->n_bits = n_bits; bitfield->bits = g_malloc0 ((n_bits + 31) / 8); } static inline void chafa_bitfield_deinit (ChafaBitfield *bitfield) { g_free (bitfield->bits); bitfield->n_bits = 0; bitfield->bits = NULL; } static inline void chafa_bitfield_clear (ChafaBitfield *bitfield) { memset (bitfield->bits, 0, (bitfield->n_bits + 31) / 8); } static inline gboolean chafa_bitfield_get_bit (const ChafaBitfield *bitfield, guint nth) { gint index; gint shift; g_return_val_if_fail (nth < bitfield->n_bits, FALSE); index = (nth / 32); shift = (nth % 32); return (bitfield->bits [index] >> shift) & 1U; } static inline void chafa_bitfield_set_bit (ChafaBitfield *bitfield, guint nth, gboolean value) { gint index; gint shift; guint32 v32; g_return_if_fail (nth < bitfield->n_bits); index = (nth / 32); shift = (nth % 32); v32 = (guint32) !!value; bitfield->bits [index] = (bitfield->bits [index] & ~(1UL << shift)) | (v32 << shift); } G_END_DECLS #endif /* __CHAFA_BITFIELD_H__ */ chafa-1.8.0/chafa/internal/chafa-canvas-internal.h000066400000000000000000000036571411352071600217510ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_CANVAS_INTERNAL_H__ #define __CHAFA_CANVAS_INTERNAL_H__ #include #include "chafa.h" #include "internal/chafa-private.h" #include "internal/chafa-pixops.h" G_BEGIN_DECLS struct ChafaCanvasCell { gunichar c; /* Colors can be either packed RGBA or index */ guint32 fg_color; guint32 bg_color; }; struct ChafaCanvas { gint refs; gint width_pixels, height_pixels; ChafaPixel *pixels; ChafaCanvasCell *cells; guint have_alpha : 1; guint needs_clear : 1; ChafaColorPair default_colors; guint work_factor_int; /* Character to use in cells where fg color == bg color. Typically * space, but could be something else depending on the symbol map. */ gunichar blank_char; ChafaCanvasConfig config; /* Used when setting pixel data */ ChafaDither dither; /* Set if we're in sixel (ChafaSixelCanvas *) or Kitty (ChafaKittyCanvas *) mode */ gpointer pixel_canvas; /* Our palette. Kind of a big structure, so it goes last. */ ChafaPalette palette; }; G_END_DECLS #endif /* __CHAFA_CANVAS_INTERNAL_H__ */ chafa-1.8.0/chafa/internal/chafa-canvas-printer.c000066400000000000000000000456641411352071600216170ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-canvas-printer.h" typedef struct { ChafaCanvas *canvas; ChafaTermInfo *term_info; gunichar cur_char; gint n_reps; guint cur_inverted : 1; guint32 cur_fg; guint32 cur_bg; /* For direct-color mode */ ChafaColor cur_fg_direct; ChafaColor cur_bg_direct; } PrintCtx; static gint cmp_colors (ChafaColor a, ChafaColor b) { return memcmp (&a, &b, sizeof (ChafaColor)); } static ChafaColor threshold_alpha (ChafaColor col, gint alpha_threshold) { col.ch [3] = col.ch [3] < alpha_threshold ? 0 : 255; return col; } G_GNUC_WARN_UNUSED_RESULT static gchar * flush_chars (PrintCtx *ctx, gchar *out) { gchar buf [8]; gint len; if (!ctx->cur_char) return out; len = g_unichar_to_utf8 (ctx->cur_char, buf); if ((ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REPEAT_CELLS) && chafa_term_info_have_seq (ctx->term_info, CHAFA_TERM_SEQ_REPEAT_CHAR) && ctx->n_reps > 1 && ctx->n_reps * len > len + 4 /* ESC [#b */) { memcpy (out, buf, len); out += len; out = chafa_term_info_emit_repeat_char (ctx->term_info, out, ctx->n_reps - 1); ctx->n_reps = 0; } else { do { memcpy (out, buf, len); out += len; ctx->n_reps--; } while (ctx->n_reps != 0); } ctx->cur_char = 0; return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * queue_char (PrintCtx *ctx, gchar *out, gunichar c) { if (ctx->cur_char == c) { ctx->n_reps++; } else { if (ctx->cur_char) out = flush_chars (ctx, out); ctx->cur_char = c; ctx->n_reps = 1; } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * reset_attributes (PrintCtx *ctx, gchar *out) { out = chafa_term_info_emit_reset_attributes (ctx->term_info, out); ctx->cur_inverted = FALSE; ctx->cur_fg = CHAFA_PALETTE_INDEX_TRANSPARENT; ctx->cur_bg = CHAFA_PALETTE_INDEX_TRANSPARENT; ctx->cur_fg_direct.ch [3] = 0; ctx->cur_bg_direct.ch [3] = 0; return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_attributes_truecolor (PrintCtx *ctx, gchar *out, ChafaColor fg, ChafaColor bg, gboolean inverted) { if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) { if (!ctx->canvas->config.fg_only_enabled && ((ctx->cur_inverted && !inverted) || (ctx->cur_fg_direct.ch [3] != 0 && fg.ch [3] == 0) || (ctx->cur_bg_direct.ch [3] != 0 && bg.ch [3] == 0))) { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); } if (!ctx->cur_inverted && inverted) { out = flush_chars (ctx, out); out = chafa_term_info_emit_invert_colors (ctx->term_info, out); } if (cmp_colors (fg, ctx->cur_fg_direct)) { if (cmp_colors (bg, ctx->cur_bg_direct) && bg.ch [3] != 0) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fgbg_direct (ctx->term_info, out, fg.ch [0], fg.ch [1], fg.ch [2], bg.ch [0], bg.ch [1], bg.ch [2]); } else if (fg.ch [3] != 0) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fg_direct (ctx->term_info, out, fg.ch [0], fg.ch [1], fg.ch [2]); } } else if (cmp_colors (bg, ctx->cur_bg_direct) && bg.ch [3] != 0) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_bg_direct (ctx->term_info, out, bg.ch [0], bg.ch [1], bg.ch [2]); } } else { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); if (inverted) out = chafa_term_info_emit_invert_colors (ctx->term_info, out); if (fg.ch [3] != 0) { if (bg.ch [3] != 0) { out = chafa_term_info_emit_set_color_fgbg_direct (ctx->term_info, out, fg.ch [0], fg.ch [1], fg.ch [2], bg.ch [0], bg.ch [1], bg.ch [2]); } else { out = chafa_term_info_emit_set_color_fg_direct (ctx->term_info, out, fg.ch [0], fg.ch [1], fg.ch [2]); } } else if (bg.ch [3] != 0) { out = chafa_term_info_emit_set_color_bg_direct (ctx->term_info, out, bg.ch [0], bg.ch [1], bg.ch [2]); } } ctx->cur_fg_direct = fg; ctx->cur_bg_direct = bg; ctx->cur_inverted = inverted; return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_truecolor (PrintCtx *ctx, gchar *out, gint i, gint i_max) { for ( ; i < i_max; i++) { ChafaCanvasCell *cell = &ctx->canvas->cells [i]; ChafaColor fg, bg; /* Wide symbols have a zero code point in the rightmost cell */ if (cell->c == 0) continue; chafa_unpack_color (cell->fg_color, &fg); fg = threshold_alpha (fg, ctx->canvas->config.alpha_threshold); chafa_unpack_color (cell->bg_color, &bg); bg = threshold_alpha (bg, ctx->canvas->config.alpha_threshold); if (fg.ch [3] == 0 && bg.ch [3] != 0) out = emit_attributes_truecolor (ctx, out, bg, fg, TRUE); else out = emit_attributes_truecolor (ctx, out, fg, bg, FALSE); if (fg.ch [3] == 0 && bg.ch [3] == 0) { out = queue_char (ctx, out, ' '); if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) out = queue_char (ctx, out, ' '); } else { out = queue_char (ctx, out, cell->c); } } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * handle_inverted_with_reuse (PrintCtx *ctx, gchar *out, guint32 fg, guint32 bg, gboolean inverted) { /* We must check fg_only_enabled because we can run into the situation where * fg is set to transparent. */ if (!ctx->canvas->config.fg_only_enabled && ((ctx->cur_inverted && !inverted) || (ctx->cur_fg != CHAFA_PALETTE_INDEX_TRANSPARENT && fg == CHAFA_PALETTE_INDEX_TRANSPARENT) || (ctx->cur_bg != CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT))) { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); } if (!ctx->cur_inverted && inverted) { out = flush_chars (ctx, out); out = chafa_term_info_emit_invert_colors (ctx->term_info, out); } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_attributes_256 (PrintCtx *ctx, gchar *out, guint32 fg, guint32 bg, gboolean inverted) { if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) { out = handle_inverted_with_reuse (ctx, out, fg, bg, inverted); if (fg != ctx->cur_fg) { if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fgbg_256 (ctx->term_info, out, fg, bg); } else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fg_256 (ctx->term_info, out, fg); } } else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_bg_256 (ctx->term_info, out, bg); } } else { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); if (inverted) out = chafa_term_info_emit_invert_colors (ctx->term_info, out); if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_fgbg_256 (ctx->term_info, out, fg, bg); } else { out = chafa_term_info_emit_set_color_fg_256 (ctx->term_info, out, fg); } } else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_bg_256 (ctx->term_info, out, bg); } } ctx->cur_fg = fg; ctx->cur_bg = bg; ctx->cur_inverted = inverted; return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_256 (PrintCtx *ctx, gchar *out, gint i, gint i_max) { for ( ; i < i_max; i++) { ChafaCanvasCell *cell = &ctx->canvas->cells [i]; guint32 fg, bg; /* Wide symbols have a zero code point in the rightmost cell */ if (cell->c == 0) continue; fg = cell->fg_color; bg = cell->bg_color; if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) out = emit_attributes_256 (ctx, out, bg, fg, TRUE); else out = emit_attributes_256 (ctx, out, fg, bg, FALSE); if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT) { out = queue_char (ctx, out, ' '); if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) out = queue_char (ctx, out, ' '); } else { out = queue_char (ctx, out, cell->c); } } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_attributes_16 (PrintCtx *ctx, gchar *out, guint32 fg, guint32 bg, gboolean inverted) { if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) { out = handle_inverted_with_reuse (ctx, out, fg, bg, inverted); if (fg != ctx->cur_fg) { if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fgbg_16 (ctx->term_info, out, fg, bg); } else if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_fg_16 (ctx->term_info, out, fg); } } else if (bg != ctx->cur_bg && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = flush_chars (ctx, out); out = chafa_term_info_emit_set_color_bg_16 (ctx->term_info, out, bg); } } else { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); if (inverted) out = chafa_term_info_emit_invert_colors (ctx->term_info, out); if (fg != CHAFA_PALETTE_INDEX_TRANSPARENT) { if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_fgbg_16 (ctx->term_info, out, fg, bg); } else { out = chafa_term_info_emit_set_color_fg_16 (ctx->term_info, out, fg); } } else if (bg != CHAFA_PALETTE_INDEX_TRANSPARENT) { out = chafa_term_info_emit_set_color_bg_16 (ctx->term_info, out, bg); } } ctx->cur_fg = fg; ctx->cur_bg = bg; ctx->cur_inverted = inverted; return out; } /* Uses aixterm control codes for bright colors */ G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_16 (PrintCtx *ctx, gchar *out, gint i, gint i_max) { for ( ; i < i_max; i++) { ChafaCanvasCell *cell = &ctx->canvas->cells [i]; guint32 fg, bg; /* Wide symbols have a zero code point in the rightmost cell */ if (cell->c == 0) continue; fg = cell->fg_color; bg = cell->bg_color; if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg != CHAFA_PALETTE_INDEX_TRANSPARENT) out = emit_attributes_16 (ctx, out, bg, fg, TRUE); else out = emit_attributes_16 (ctx, out, fg, bg, FALSE); if (fg == CHAFA_PALETTE_INDEX_TRANSPARENT && bg == CHAFA_PALETTE_INDEX_TRANSPARENT) { out = queue_char (ctx, out, ' '); if (i < i_max - 1 && ctx->canvas->cells [i + 1].c == 0) out = queue_char (ctx, out, ' '); } else { out = queue_char (ctx, out, cell->c); } } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_fgbg_bgfg (PrintCtx *ctx, gchar *out, gint i, gint i_max) { gunichar blank_symbol = 0; if (chafa_symbol_map_has_symbol (&ctx->canvas->config.symbol_map, ' ')) { blank_symbol = ' '; } else if (chafa_symbol_map_has_symbol (&ctx->canvas->config.symbol_map, 0x2588 /* Solid block */ )) { blank_symbol = 0x2588; } for ( ; i < i_max; i++) { ChafaCanvasCell *cell = &ctx->canvas->cells [i]; gboolean invert = FALSE; gunichar c = cell->c; /* Wide symbols have a zero code point in the rightmost cell */ if (c == 0) continue; /* Replace with blank symbol only if this is a single-width cell */ if (cell->fg_color == cell->bg_color && blank_symbol != 0 && (i == i_max - 1 || (ctx->canvas->cells [i + 1].c != 0))) { c = blank_symbol; if (blank_symbol == 0x2588) invert = TRUE; } if (cell->bg_color == CHAFA_PALETTE_INDEX_FG) { invert ^= TRUE; } if (ctx->canvas->config.optimizations & CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES) { if (!ctx->cur_inverted && invert) { out = flush_chars (ctx, out); out = chafa_term_info_emit_invert_colors (ctx->term_info, out); } else if (ctx->cur_inverted && !invert) { out = flush_chars (ctx, out); out = reset_attributes (ctx, out); } ctx->cur_inverted = invert; } else { out = flush_chars (ctx, out); if (invert) out = chafa_term_info_emit_invert_colors (ctx->term_info, out); else out = reset_attributes (ctx, out); } out = queue_char (ctx, out, c); } return out; } G_GNUC_WARN_UNUSED_RESULT static gchar * emit_ansi_fgbg (PrintCtx *ctx, gchar *out, gint i, gint i_max) { for ( ; i < i_max; i++) { ChafaCanvasCell *cell = &ctx->canvas->cells [i]; /* Wide symbols have a zero code point in the rightmost cell */ if (cell->c == 0) continue; out = queue_char (ctx, out, cell->c); } return out; } static void prealloc_string (GString *gs, gint n_cells) { guint needed_len; /* Each cell produces at most three control sequences and six bytes * for the UTF-8 character. Each row may add one seq and one newline. */ needed_len = (n_cells + 1) * (CHAFA_TERM_SEQ_LENGTH_MAX * 3 + 6) + 1; if (gs->allocated_len - gs->len < needed_len) { guint current_len = gs->len; g_string_set_size (gs, gs->len + needed_len * 2); gs->len = current_len; } } static GString * build_ansi_gstring (ChafaCanvas *canvas, ChafaTermInfo *ti) { GString *gs = g_string_new (""); PrintCtx ctx = { 0 }; gint i, i_max, i_step, i_next; ctx.canvas = canvas; ctx.term_info = ti; i = 0; i_max = canvas->config.width * canvas->config.height; i_step = canvas->config.width; for ( ; i < i_max; i = i_next) { gchar *out; i_next = i + i_step; prealloc_string (gs, i_step); out = gs->str + gs->len; /* Avoid control codes in FGBG mode. Don't reset attributes when BG * is held, to preserve any BG color set previously. */ if (i == 0 && canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG && !canvas->config.fg_only_enabled) { out = reset_attributes (&ctx, out); } switch (canvas->config.canvas_mode) { case CHAFA_CANVAS_MODE_TRUECOLOR: out = emit_ansi_truecolor (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_INDEXED_256: case CHAFA_CANVAS_MODE_INDEXED_240: out = emit_ansi_256 (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_INDEXED_16: out = emit_ansi_16 (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_INDEXED_8: out = emit_ansi_16 (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_FGBG_BGFG: out = emit_ansi_fgbg_bgfg (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_FGBG: out = emit_ansi_fgbg (&ctx, out, i, i_next); break; case CHAFA_CANVAS_MODE_MAX: g_assert_not_reached (); break; } out = flush_chars (&ctx, out); /* Avoid control codes in FGBG mode. Don't reset attributes when BG * is held, to preserve any BG color set previously. */ if (canvas->config.canvas_mode != CHAFA_CANVAS_MODE_FGBG && !canvas->config.fg_only_enabled) { out = reset_attributes (&ctx, out); } /* Last line should not end in newline */ if (i_next < i_max) *(out++) = '\n'; *out = '\0'; gs->len = out - gs->str; } return gs; } GString * chafa_canvas_print_symbols (ChafaCanvas *canvas, ChafaTermInfo *ti) { g_assert (canvas != NULL); g_assert (ti != NULL); return build_ansi_gstring (canvas, ti); } chafa-1.8.0/chafa/internal/chafa-canvas-printer.h000066400000000000000000000023111411352071600216020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_CANVAS_PRINTER_H__ #define __CHAFA_CANVAS_PRINTER_H__ #include #include "chafa.h" #include "internal/chafa-canvas-internal.h" #include "internal/chafa-private.h" #include "internal/chafa-pixops.h" G_BEGIN_DECLS GString *chafa_canvas_print_symbols (ChafaCanvas *canvas, ChafaTermInfo *ti); G_END_DECLS #endif /* __CHAFA_CANVAS_PRINTER_H__ */ chafa-1.8.0/chafa/internal/chafa-color-hash.c000066400000000000000000000025351411352071600207100ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-color-hash.h" void chafa_color_hash_init (ChafaColorHash *color_hash) { guint i; guint32 j; /* Initialize with invalid entries */ for (i = 0, j = 0; i < CHAFA_COLOR_HASH_N_ENTRIES; i++) { while (_chafa_color_hash_calc_hash (j) == i) { j++; j %= 0x01000000; } color_hash->map [i] = j << 8; } } void chafa_color_hash_deinit (G_GNUC_UNUSED ChafaColorHash *color_hash) { } chafa-1.8.0/chafa/internal/chafa-color-hash.h000066400000000000000000000036151411352071600207150ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_COLOR_HASH_H__ #define __CHAFA_COLOR_HASH_H__ G_BEGIN_DECLS #define CHAFA_COLOR_HASH_N_ENTRIES 16384 typedef struct { guint32 map [CHAFA_COLOR_HASH_N_ENTRIES]; } ChafaColorHash; void chafa_color_hash_init (ChafaColorHash *color_hash); void chafa_color_hash_deinit (ChafaColorHash *color_hash); static inline guint _chafa_color_hash_calc_hash (guint32 color) { color &= 0x00ffffff; return (color ^ (color >> 7) ^ (color >> 14)) % CHAFA_COLOR_HASH_N_ENTRIES; } static inline void chafa_color_hash_replace (ChafaColorHash *color_hash, guint32 color, guint8 pen) { guint index = _chafa_color_hash_calc_hash (color); guint32 entry = (color << 8) | pen; color_hash->map [index] = entry; } static inline gint chafa_color_hash_lookup (const ChafaColorHash *color_hash, guint32 color) { guint index = _chafa_color_hash_calc_hash (color); guint32 entry = color_hash->map [index]; if ((entry & 0xffffff00) == (color << 8)) return entry & 0xff; return -1; } G_END_DECLS #endif /* __CHAFA_COLOR_HASH_H__ */ chafa-1.8.0/chafa/internal/chafa-color-table.c000066400000000000000000000244071411352071600210560ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-color-table.h" #include "internal/chafa-pca.h" #define CHAFA_COLOR_TABLE_ENABLE_PROFILING 0 #define DEBUG_PEN_CHOICE(x) #define POW2(x) ((x) * (x)) #define FIXED_MUL_BIG_SHIFT 14 #define FIXED_MUL_BIG (1 << (FIXED_MUL_BIG_SHIFT)) #define FIXED_MUL 32 #define FIXED_MUL_F ((gfloat) (FIXED_MUL)) #if CHAFA_COLOR_TABLE_ENABLE_PROFILING # define profile_counter_inc(x) g_atomic_int_inc ((gint *) &(x)) static gint n_lookups; static gint n_misses; static gint n_a; static gint n_b; static gint n_c; static gint n_d; static void dump_entry (const ChafaColorTable *color_table, gint entry, gint want_color) { const ChafaColorTableEntry *e = &color_table->entries [entry]; guint32 c = color_table->pens [e->pen]; gint err; err = POW2 (((gint) (c & 0xff) - (want_color & 0xff))) + POW2 (((gint) (c >> 8) & 0xff) - ((want_color >> 8) & 0xff)) + POW2 (((gint) (c >> 16) & 0xff) - ((want_color >> 16) & 0xff)); g_printerr ("{ %3d, %3d, %3d } { %7d, %7d } n = %3d err = %6d\n", c & 0xff, (c >> 8) & 0xff, (c >> 16) & 0xff, e->v [0], e->v [1], entry, err); } #else # define profile_counter_inc(x) #endif static gint compare_entries (gconstpointer a, gconstpointer b) { const ChafaColorTableEntry *ab = a; const ChafaColorTableEntry *bb = b; return ab->v [0] - bb->v [0]; } static gint scalar_project_vec3i32 (const ChafaVec3i32 *a, const ChafaVec3i32 *b, guint32 b_mul) { guint64 d = chafa_vec3i32_dot_64 (a, b); /* I replaced the following (a division, three multiplications and * two additions) with a multiplication and a right shift: * * d / (POW2 (b->v [0]) + POW2 (b->v [1]) + POW2 (b->v [2])) * * The result is multiplied by FIXED_MUL for increased precision. */ return (d * b_mul) / (FIXED_MUL_BIG / FIXED_MUL); } static gint color_diff (guint32 a, guint32 b) { gint diff; gint n; n = (gint) ((b & 0xff) - (gint) (a & 0xff)) * FIXED_MUL; diff = n * n; n = (gint) (((b >> 8) & 0xff) - (gint) ((a >> 8) & 0xff)) * FIXED_MUL; diff += n * n; n = (gint) (((b >> 16) & 0xff) - (gint) ((a >> 16) & 0xff)) * FIXED_MUL; diff += n * n; return diff; } static void project_color (const ChafaColorTable *color_table, guint32 color, gint *v_out) { ChafaVec3i32 v; v.v [0] = (color & 0xff) * FIXED_MUL; v.v [1] = ((color >> 8) & 0xff) * FIXED_MUL; v.v [2] = ((color >> 16) & 0xff) * FIXED_MUL; chafa_vec3i32_sub (&v, &v, &color_table->average); v_out [0] = scalar_project_vec3i32 (&v, &color_table->eigenvectors [0], color_table->eigen_mul [0]); v_out [1] = scalar_project_vec3i32 (&v, &color_table->eigenvectors [1], color_table->eigen_mul [1]); } static void vec3i32_fixed_point_from_vec3f32 (ChafaVec3i32 *out, const ChafaVec3f32 *in) { ChafaVec3f32 t; chafa_vec3f32_mul_scalar (&t, in, FIXED_MUL_F); chafa_vec3i32_from_vec3f32 (out, &t); } static void do_pca (ChafaColorTable *color_table) { ChafaVec3f32 v [256]; ChafaVec3f32 eigenvectors [2]; ChafaVec3f32 average; gint i, j; for (i = 0, j = 0; i < 256; i++) { guint32 col = color_table->pens [i]; if ((col & 0xff000000) == 0xff000000) continue; v [j].v [0] = (col & 0xff) * FIXED_MUL_F; v [j].v [1] = ((col >> 8) & 0xff) * FIXED_MUL_F; v [j].v [2] = ((col >> 16) & 0xff) * FIXED_MUL_F; j++; } chafa_vec3f32_array_compute_pca (v, j, 2, eigenvectors, NULL, &average); vec3i32_fixed_point_from_vec3f32 (&color_table->eigenvectors [0], &eigenvectors [0]); vec3i32_fixed_point_from_vec3f32 (&color_table->eigenvectors [1], &eigenvectors [1]); vec3i32_fixed_point_from_vec3f32 (&color_table->average, &average); color_table->eigen_mul [0] = POW2 (color_table->eigenvectors [0].v [0]) + POW2 (color_table->eigenvectors [0].v [1]) + POW2 (color_table->eigenvectors [0].v [2]); color_table->eigen_mul [0] = MAX (color_table->eigen_mul [0], 1); color_table->eigen_mul [0] = FIXED_MUL_BIG / color_table->eigen_mul [0]; color_table->eigen_mul [1] = POW2 (color_table->eigenvectors [1].v [0]) + POW2 (color_table->eigenvectors [1].v [1]) + POW2 (color_table->eigenvectors [1].v [2]); color_table->eigen_mul [1] = MAX (color_table->eigen_mul [1], 1); color_table->eigen_mul [1] = FIXED_MUL_BIG / color_table->eigen_mul [1]; for (i = 0; i < color_table->n_entries; i++) { ChafaColorTableEntry *entry = &color_table->entries [i]; project_color (color_table, color_table->pens [entry->pen], entry->v); } } static inline gboolean refine_pen_choice (const ChafaColorTable *color_table, guint want_color, const gint *v, gint j, gint *best_pen, gint *best_diff) { const ChafaColorTableEntry *pj = &color_table->entries [j]; gint a, b, d; a = POW2 (pj->v [0] - v [0]); profile_counter_inc (n_a); DEBUG_PEN_CHOICE (g_printerr ("a=%d\n", a)); if (a <= *best_diff) { b = POW2 (pj->v [1] - v [1]); profile_counter_inc (n_b); DEBUG_PEN_CHOICE (g_printerr ("b=%d\n", b)); if (b <= *best_diff) { d = color_diff (color_table->pens [pj->pen], want_color); profile_counter_inc (n_c); DEBUG_PEN_CHOICE (g_printerr ("d=%d\n", d)); if (d <= *best_diff) { *best_pen = j; *best_diff = d; profile_counter_inc (n_d); DEBUG_PEN_CHOICE (g_printerr ("a=%d, b=%d, d=%d\n", a, b, d)); } } } else { DEBUG_PEN_CHOICE (g_printerr ("\n")); return FALSE; } return TRUE; } void chafa_color_table_init (ChafaColorTable *color_table) { color_table->n_entries = 0; color_table->is_sorted = TRUE; memset (color_table->pens, 0xff, sizeof (color_table->pens)); } void chafa_color_table_deinit (G_GNUC_UNUSED ChafaColorTable *color_table) { #if CHAFA_COLOR_TABLE_ENABLE_PROFILING g_printerr ("l=%7d m=%7d a=%7d b=%7d c=%7d d=%7d\n" "per probe: a=%6.1lf b=%6.1lf c=%6.1lf d=%6.1lf\n", n_lookups, n_misses, n_a, n_b, n_c, n_d, n_a / (gdouble) n_lookups, n_b / (gdouble) n_lookups, n_c / (gdouble) n_lookups, n_d / (gdouble) n_lookups); #endif } guint32 chafa_color_table_get_pen_color (const ChafaColorTable *color_table, gint pen) { g_assert (pen >= 0); g_assert (pen < CHAFA_COLOR_TABLE_MAX_ENTRIES); return color_table->pens [pen]; } void chafa_color_table_set_pen_color (ChafaColorTable *color_table, gint pen, guint32 color) { g_assert (pen >= 0); g_assert (pen < CHAFA_COLOR_TABLE_MAX_ENTRIES); color_table->pens [pen] = color & 0x00ffffff; color_table->is_sorted = FALSE; } void chafa_color_table_sort (ChafaColorTable *color_table) { gint i, j; if (color_table->is_sorted) return; for (i = 0, j = 0; i < CHAFA_COLOR_TABLE_MAX_ENTRIES; i++) { ChafaColorTableEntry *entry; if (color_table->pens [i] == 0xffffffff) continue; entry = &color_table->entries [j++]; entry->pen = i; } color_table->n_entries = j; do_pca (color_table); qsort (color_table->entries, color_table->n_entries, sizeof (ChafaColorTableEntry), compare_entries); color_table->is_sorted = TRUE; } gint chafa_color_table_find_nearest_pen (const ChafaColorTable *color_table, guint32 want_color) { gint best_diff = G_MAXINT; gint best_pen = 0; gint v [2]; gint i, j, m; g_assert (color_table->n_entries > 0); g_assert (color_table->is_sorted); profile_counter_inc (n_lookups); project_color (color_table, want_color, v); /* Binary search for first vector component */ i = 0; j = color_table->n_entries; while (i != j) { gint n = i + (j - i) / 2; if (v [0] > color_table->entries [n].v [0]) i = n + 1; else j = n; } m = j; /* Left scan for closer match */ for (j = m; j >= 0; j--) { if (!refine_pen_choice (color_table, want_color, v, j, &best_pen, &best_diff)) break; } /* Right scan for closer match */ for (j = m + 1; j < color_table->n_entries; j++) { if (!refine_pen_choice (color_table, want_color, v, j, &best_pen, &best_diff)) break; } #if CHAFA_COLOR_TABLE_ENABLE_PROFILING gint best_pen_2 = -1; gint best_diff_2 = G_MAXINT; for (i = 0; i < color_table->n_entries; i++) { const ChafaColorTableEntry *pi = &color_table->entries [i]; gint d = color_diff (color_table->pens [pi->pen], want_color); if (d < best_diff_2) { best_pen_2 = i; best_diff_2 = d; } } if (best_diff_2 < best_diff) { profile_counter_inc (n_misses); g_printerr ("Bad lookup: "); dump_entry (color_table, best_pen, want_color); g_printerr ("\nShould be: "); dump_entry (color_table, best_pen_2, want_color); g_printerr ("\n"); } #endif return color_table->entries [best_pen].pen; } chafa-1.8.0/chafa/internal/chafa-color-table.h000066400000000000000000000037351411352071600210640ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_COLOR_TABLE_H__ #define __CHAFA_COLOR_TABLE_H__ #include "internal/chafa-pca.h" G_BEGIN_DECLS #define CHAFA_COLOR_TABLE_MAX_ENTRIES 256 typedef struct { gint v [2]; gint pen; } ChafaColorTableEntry; typedef struct { ChafaColorTableEntry entries [CHAFA_COLOR_TABLE_MAX_ENTRIES]; /* Each pen is 24 bits (B8G8R8) of color information */ guint32 pens [CHAFA_COLOR_TABLE_MAX_ENTRIES]; gint n_entries; guint is_sorted : 1; ChafaVec3i32 eigenvectors [2]; ChafaVec3i32 average; guint eigen_mul [2]; } ChafaColorTable; void chafa_color_table_init (ChafaColorTable *color_table); void chafa_color_table_deinit (ChafaColorTable *color_table); guint32 chafa_color_table_get_pen_color (const ChafaColorTable *color_table, gint pen); void chafa_color_table_set_pen_color (ChafaColorTable *color_table, gint pen, guint32 color); void chafa_color_table_sort (ChafaColorTable *color_table); gint chafa_color_table_find_nearest_pen (const ChafaColorTable *color_table, guint32 color); G_END_DECLS #endif /* __CHAFA_COLOR_TABLE_H__ */ chafa-1.8.0/chafa/internal/chafa-color.c000066400000000000000000000416101411352071600177640ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* abs */ #include /* pow, cbrt, log, sqrt, atan2, cos, sin */ #include "chafa.h" #include "internal/chafa-color.h" #define DEBUG(x) /* 256-color values */ static guint32 term_colors_256 [CHAFA_PALETTE_INDEX_MAX] = { 0x000000, 0x800000, 0x007000, 0x707000, 0x000070, 0x700070, 0x007070, 0xc0c0c0, /* 0x808080 -> */ 0x404040, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, 0x808080, /* Transparent */ 0xffffff, /* Foreground */ 0x000000, /* Background */ }; static ChafaPaletteColor palette_256 [CHAFA_PALETTE_INDEX_MAX]; static guchar color_cube_216_channel_index [256]; static gboolean palette_initialized; void chafa_init_palette (void) { gint i; if (palette_initialized) return; for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++) { chafa_unpack_color (term_colors_256 [i], &palette_256 [i].col [0]); chafa_color_rgb_to_din99d (&palette_256 [i].col [0], &palette_256 [i].col [1]); palette_256 [i].col [0].ch [3] = 0xff; /* Fully opaque */ palette_256 [i].col [1].ch [3] = 0xff; /* Fully opaque */ } /* Transparent color */ palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [0].ch [3] = 0x00; palette_256 [CHAFA_PALETTE_INDEX_TRANSPARENT].col [1].ch [3] = 0x00; for (i = 0; i < 0x5f / 2; i++) color_cube_216_channel_index [i] = 0; for ( ; i < (0x5f + 0x87) / 2; i++) color_cube_216_channel_index [i] = 1; for ( ; i < (0x87 + 0xaf) / 2; i++) color_cube_216_channel_index [i] = 2; for ( ; i < (0xaf + 0xd7) / 2; i++) color_cube_216_channel_index [i] = 3; for ( ; i < (0xd7 + 0xff) / 2; i++) color_cube_216_channel_index [i] = 4; for ( ; i <= 0xff; i++) color_cube_216_channel_index [i] = 5; palette_initialized = TRUE; } const ChafaColor * chafa_get_palette_color_256 (guint index, ChafaColorSpace color_space) { return &palette_256 [index].col [color_space]; } guint32 chafa_pack_color (const ChafaColor *color) { /* Assumes each channel 0 <= value <= 255 */ return (color->ch [0] << 16) | (color->ch [1] << 8) | (color->ch [2]) | (color->ch [3] << 24); /* Alpha */ } void chafa_unpack_color (guint32 packed, ChafaColor *color_out) { color_out->ch [0] = (packed >> 16) & 0xff; color_out->ch [1] = (packed >> 8) & 0xff; color_out->ch [2] = packed & 0xff; color_out->ch [3] = (packed >> 24) & 0xff; /* Alpha */ } void chafa_color_accum_div_scalar (ChafaColorAccum *accum, gint scalar) { accum->ch [0] /= scalar; accum->ch [1] /= scalar; accum->ch [2] /= scalar; accum->ch [3] /= scalar; } typedef struct { gdouble c [3]; } ChafaColorRGBf; typedef struct { gdouble c [3]; } ChafaColorXYZ; typedef struct { gdouble c [3]; } ChafaColorLab; static gdouble invert_rgb_channel_compand (gdouble v) { return v <= 0.04045 ? (v / 12.92) : pow ((v + 0.055) / 1.044, 2.4); } static void convert_rgb_to_xyz (const ChafaColor *rgbi, ChafaColorXYZ *xyz) { ChafaColorRGBf rgbf; gint i; for (i = 0; i < 3; i++) { rgbf.c [i] = (gdouble) rgbi->ch [i] / 255.0; rgbf.c [i] = invert_rgb_channel_compand (rgbf.c [i]); } xyz->c [0] = 0.4124564 * rgbf.c [0] + 0.3575761 * rgbf.c [1] + 0.1804375 * rgbf.c [2]; xyz->c [1] = 0.2126729 * rgbf.c [0] + 0.7151522 * rgbf.c [1] + 0.0721750 * rgbf.c [2]; xyz->c [2] = 0.0193339 * rgbf.c [0] + 0.1191920 * rgbf.c [1] + 0.9503041 * rgbf.c [2]; } #define XYZ_EPSILON (216.0 / 24389.0) #define XYZ_KAPPA (24389.0 / 27.0) static gdouble lab_f (gdouble v) { return v > XYZ_EPSILON ? cbrt (v) : (XYZ_KAPPA * v + 16.0) / 116.0; } static void convert_xyz_to_lab (const ChafaColorXYZ *xyz, ChafaColorLab *lab) { ChafaColorXYZ wp = { { 0.95047, 1.0, 1.08883 } }; /* D65 white point */ ChafaColorXYZ xyz2; gint i; for (i = 0; i < 3; i++) xyz2.c [i] = lab_f (xyz->c [i] / wp.c [i]); lab->c [0] = 116.0 * xyz2.c [1] - 16.0; lab->c [1] = 500.0 * (xyz2.c [0] - xyz2.c [1]); lab->c [2] = 200.0 * (xyz2.c [1] - xyz2.c [2]); } void chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99) { ChafaColorXYZ xyz; ChafaColorLab lab; gdouble adj_L, ee, f, G, C, h; convert_rgb_to_xyz (rgb, &xyz); /* Apply tristimulus-space correction term */ xyz.c [0] = 1.12 * xyz.c [0] - 0.12 * xyz.c [2]; /* Convert to L*a*b* */ convert_xyz_to_lab (&xyz, &lab); adj_L = 325.22 * log (1.0 + 0.0036 * lab.c [0]); /* Intermediate parameters */ ee = 0.6427876096865393 * lab.c [1] + 0.766044443118978 * lab.c [2]; f = 1.14 * (0.6427876096865393 * lab.c [2] - 0.766044443118978 * lab.c [1]); G = sqrt (ee * ee + f * f); /* Hue/chroma */ C = 22.5 * log (1.0 + 0.06 * G); h = atan2 (f, ee) + 0.8726646 /* 50 degrees */; while (h < 0.0) h += 6.283185; /* 360 degrees */ while (h > 6.283185) h -= 6.283185; /* 360 degrees */ /* The final values should be in the range [0..255] */ din99->ch [0] = adj_L * 2.5; din99->ch [1] = C * cos (h) * 2.5 + 128.0; din99->ch [2] = C * sin (h) * 2.5 + 128.0; din99->ch [3] = rgb->ch [3]; } static gint color_diff_rgb (const ChafaColor *col_a, const ChafaColor *col_b) { gint error = 0; gint d [3]; d [0] = (gint) col_b->ch [0] - (gint) col_a->ch [0]; d [0] = d [0] * d [0]; d [1] = (gint) col_b->ch [1] - (gint) col_a->ch [1]; d [1] = d [1] * d [1]; d [2] = (gint) col_b->ch [2] - (gint) col_a->ch [2]; d [2] = d [2] * d [2]; error = 2 * d [0] + 4 * d [1] + 3 * d [2] + (((col_a->ch [0] + (gint) col_b->ch [0]) / 2) * abs (d [0] - d [2])) / 256; return error; } static gint color_diff_euclidean (const ChafaColor *col_a, const ChafaColor *col_b) { gint error = 0; gint d [3]; d [0] = (gint) col_b->ch [0] - (gint) col_a->ch [0]; d [0] = d [0] * d [0]; d [1] = (gint) col_b->ch [1] - (gint) col_a->ch [1]; d [1] = d [1] * d [1]; d [2] = (gint) col_b->ch [2] - (gint) col_a->ch [2]; d [2] = d [2] * d [2]; error = d [0] + d [1] + d [2]; return error; } static gint color_diff_alpha (const ChafaColor *col_a, const ChafaColor *col_b, gint error) { gint max_opacity; gint a; /* Alpha */ a = (gint) col_b->ch [3] - (gint) col_a->ch [3]; a = a * a; max_opacity = MAX (col_a->ch [3], col_b->ch [3]); error *= max_opacity; error /= 256; error += a * 8; return error; } gint chafa_color_diff_slow (const ChafaColor *col_a, const ChafaColor *col_b, ChafaColorSpace color_space) { gint error; if (color_space == CHAFA_COLOR_SPACE_RGB) error = color_diff_rgb (col_a, col_b); else if (color_space == CHAFA_COLOR_SPACE_DIN99D) error = color_diff_euclidean (col_a, col_b); else { g_assert_not_reached (); return -1; } error = color_diff_alpha (col_a, col_b, error); return error; } /* FIXME: We may be able to avoid mixing alpha in most cases, but 16-color fill relies * on it at the moment. */ void chafa_color_mix (ChafaColor *out, const ChafaColor *a, const ChafaColor *b, gint ratio) { gint i; for (i = 0; i < 4; i++) out->ch [i] = (a->ch [i] * ratio + b->ch [i] * (1000 - ratio)) / 1000; } static void init_candidates (ChafaColorCandidates *candidates) { candidates->index [0] = candidates->index [1] = -1; candidates->error [0] = candidates->error [1] = G_MAXINT; } static gboolean update_candidates (ChafaColorCandidates *candidates, gint index, gint error) { if (error < candidates->error [0]) { candidates->index [1] = candidates->index [0]; candidates->index [0] = index; candidates->error [1] = candidates->error [0]; candidates->error [0] = error; return TRUE; } else if (error < candidates->error [1]) { candidates->index [1] = index; candidates->error [1] = error; return TRUE; } return FALSE; } static gint update_candidates_with_color_index_diff (ChafaColorCandidates *candidates, ChafaColorSpace color_space, const ChafaColor *color, gint index) { const ChafaColor *palette_color; gint error; palette_color = chafa_get_palette_color_256 (index, color_space); error = chafa_color_diff_slow (color, palette_color, color_space); update_candidates (candidates, index, error); return error; } static void pick_color_216_cube (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; g_assert (color_space == CHAFA_COLOR_SPACE_RGB); i = 16 + (color_cube_216_channel_index [color->ch [0]] * 6 * 6 + color_cube_216_channel_index [color->ch [1]] * 6 + color_cube_216_channel_index [color->ch [2]]); update_candidates_with_color_index_diff (candidates, color_space, color, i); } static void pick_color_24_grays (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { const ChafaColor *palette_color; gint error, last_error = G_MAXINT; gint step, i; g_assert (color_space == CHAFA_COLOR_SPACE_RGB); i = 232 + 12; last_error = update_candidates_with_color_index_diff (candidates, color_space, color, i); palette_color = chafa_get_palette_color_256 (i + 1, color_space); error = chafa_color_diff_slow (color, palette_color, color_space); if (error < last_error) { update_candidates (candidates, i, error); last_error = error; step = 1; i++; } else { step = -1; } do { i += step; palette_color = chafa_get_palette_color_256 (i, color_space); error = chafa_color_diff_slow (color, palette_color, color_space); if (error > last_error) break; update_candidates (candidates, i, error); last_error = error; } while (i >= 232 && i <= 255); } static void pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; for (i = 0; i < 16; i++) { update_candidates_with_color_index_diff (candidates, color_space, color, i); } /* Try transparency */ update_candidates_with_color_index_diff (candidates, color_space, color, CHAFA_PALETTE_INDEX_TRANSPARENT); } void chafa_pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { init_candidates (candidates); pick_color_16 (color, color_space, candidates); } static void pick_color_8 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; for (i = 0; i < 8; i++) { update_candidates_with_color_index_diff (candidates, color_space, color, i); } #if 0 /* Try transparency */ update_candidates_with_color_index_diff (candidates, color_space, color, CHAFA_PALETTE_INDEX_TRANSPARENT); #endif } void chafa_pick_color_8 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { init_candidates (candidates); pick_color_8 (color, color_space, candidates); } void chafa_pick_color_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; init_candidates (candidates); if (color_space == CHAFA_COLOR_SPACE_RGB) { pick_color_216_cube (color, color_space, candidates); pick_color_24_grays (color, color_space, candidates); /* This will try transparency too. Do this last so ties are broken in * favor of high-index colors. */ pick_color_16 (color, color_space, candidates); } else { /* All colors including transparent, but not bg or fg */ for (i = 0; i < 257; i++) { update_candidates_with_color_index_diff (candidates, color_space, color, i); } } } void chafa_pick_color_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates) { gint i; init_candidates (candidates); if (color_space == CHAFA_COLOR_SPACE_RGB) { pick_color_216_cube (color, color_space, candidates); pick_color_24_grays (color, color_space, candidates); /* Try transparency */ update_candidates_with_color_index_diff (candidates, color_space, color, CHAFA_PALETTE_INDEX_TRANSPARENT); } else { /* Color cube and transparent, but not lower 16, bg or fg */ for (i = 16; i < 257; i++) { update_candidates_with_color_index_diff (candidates, color_space, color, i); } } } /* Pick the best approximation of color from a palette consisting of * fg_color and bg_color */ void chafa_pick_color_fgbg (const ChafaColor *color, ChafaColorSpace color_space, const ChafaColor *fg_color, const ChafaColor *bg_color, ChafaColorCandidates *candidates) { gint error; init_candidates (candidates); error = chafa_color_diff_slow (color, fg_color, color_space); update_candidates (candidates, CHAFA_PALETTE_INDEX_FG, error); error = chafa_color_diff_slow (color, bg_color, color_space); update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); /* Consider opaque background too */ if (candidates->index [0] != CHAFA_PALETTE_INDEX_BG) { ChafaColor bg_color_opaque = *bg_color; bg_color_opaque.ch [3] = 0xff; error = chafa_color_diff_slow (color, &bg_color_opaque, color_space); update_candidates (candidates, CHAFA_PALETTE_INDEX_BG, error); } } chafa-1.8.0/chafa/internal/chafa-color.h000066400000000000000000000115551411352071600177760ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_COLOR_H__ #define __CHAFA_COLOR_H__ #include #include "chafa.h" G_BEGIN_DECLS #define CHAFA_PALETTE_INDEX_TRANSPARENT 256 #define CHAFA_PALETTE_INDEX_FG 257 #define CHAFA_PALETTE_INDEX_BG 258 #define CHAFA_PALETTE_INDEX_MAX 259 #define CHAFA_COLOR8_U32(col) (*((guint32 *) (col).ch)) /* Color space agnostic */ typedef struct { guint8 ch [4]; } ChafaColor; /* BG/FG indexes must be 0 and 1 respectively, corresponding to * coverage bitmap values */ #define CHAFA_COLOR_PAIR_BG 0 #define CHAFA_COLOR_PAIR_FG 1 typedef struct { ChafaColor colors [2]; } ChafaColorPair; typedef struct { union { guint32 u32; ChafaColor col; } u; } ChafaColorConv; static inline ChafaColor chafa_color8_fetch_from_rgba8 (gconstpointer p) { const guint32 *p32 = (const guint32 *) p; ChafaColorConv cc; cc.u.u32 = *p32; return cc.u.col; } static inline void chafa_color8_store_to_rgba8 (ChafaColor col, gpointer p) { guint32 *p32 = (guint32 *) p; ChafaColorConv cc; cc.u.col = col; *p32 = cc.u.u32; } static inline ChafaColor chafa_color_average_2 (ChafaColor color_a, ChafaColor color_b) { ChafaColor avg = { 0 }; CHAFA_COLOR8_U32 (avg) = ((CHAFA_COLOR8_U32 (color_a) >> 1) & 0x7f7f7f7f) + ((CHAFA_COLOR8_U32 (color_b) >> 1) & 0x7f7f7f7f); return avg; } typedef struct { ChafaColor col [CHAFA_COLOR_SPACE_MAX]; } ChafaPaletteColor; typedef struct { gint16 ch [4]; } ChafaColorAccum; typedef struct { ChafaColor col; } ChafaPixel; /* Color selection candidate pair */ typedef struct { gint16 index [2]; gint error [2]; } ChafaColorCandidates; /* Internal API */ void chafa_init_palette (void); guint32 chafa_pack_color (const ChafaColor *color) G_GNUC_PURE; void chafa_unpack_color (guint32 packed, ChafaColor *color_out); #define chafa_color_accum_add(d, s) \ G_STMT_START { \ (d)->ch [0] += (s)->ch [0]; (d)->ch [1] += (s)->ch [1]; (d)->ch [2] += (s)->ch [2]; (d)->ch [3] += (s)->ch [3]; \ } G_STMT_END #define chafa_color_diff_fast(col_a, col_b) \ (((gint) (col_b)->ch [0] - (gint) (col_a)->ch [0]) * ((gint) (col_b)->ch [0] - (gint) (col_a)->ch [0]) \ + ((gint) (col_b)->ch [1] - (gint) (col_a)->ch [1]) * ((gint) (col_b)->ch [1] - (gint) (col_a)->ch [1]) \ + ((gint) (col_b)->ch [2] - (gint) (col_a)->ch [2]) * ((gint) (col_b)->ch [2] - (gint) (col_a)->ch [2])) /* Required to get alpha right */ gint chafa_color_diff_slow (const ChafaColor *col_a, const ChafaColor *col_b, ChafaColorSpace color_space) G_GNUC_PURE; void chafa_color_accum_div_scalar (ChafaColorAccum *color, gint scalar); void chafa_color_rgb_to_din99d (const ChafaColor *rgb, ChafaColor *din99); /* Ratio is in the range 0-1000 */ void chafa_color_mix (ChafaColor *out, const ChafaColor *a, const ChafaColor *b, gint ratio); /* Takes values 0-255 for r, g, b and returns a universal palette index 0-255 */ void chafa_pick_color_256 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); /* Takes values 0-255 for r, g, b and returns a universal palette index 16-255 */ void chafa_pick_color_240 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); /* Takes values 0-255 for r, g, b and returns a universal palette index 0-15 */ void chafa_pick_color_16 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); /* Takes values 0-255 for r, g, b and returns a universal palette index 0-7 */ void chafa_pick_color_8 (const ChafaColor *color, ChafaColorSpace color_space, ChafaColorCandidates *candidates); /* Takes values 0-255 for r, g, b and returns CHAFA_PALETTE_INDEX_FG or CHAFA_PALETTE_INDEX_BG */ void chafa_pick_color_fgbg (const ChafaColor *color, ChafaColorSpace color_space, const ChafaColor *fg_color, const ChafaColor *bg_color, ChafaColorCandidates *candidates); const ChafaColor *chafa_get_palette_color_256 (guint index, ChafaColorSpace color_space) G_GNUC_CONST; G_END_DECLS #endif /* __CHAFA_COLOR_H__ */ chafa-1.8.0/chafa/internal/chafa-dither.c000066400000000000000000000060661411352071600201330ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "internal/chafa-dither.h" #include "internal/chafa-private.h" #define BAYER_MATRIX_DIM_SHIFT 4 #define BAYER_MATRIX_DIM (1 << (BAYER_MATRIX_DIM_SHIFT)) #define BAYER_MATRIX_SIZE ((BAYER_MATRIX_DIM) * (BAYER_MATRIX_DIM)) static gint calc_grain_shift (gint size) { switch (size) { case 1: return 0; case 2: return 1; case 4: return 2; case 8: return 3; default: g_assert_not_reached (); } return 0; } void chafa_dither_init (ChafaDither *dither, ChafaDitherMode mode, gdouble intensity, gint grain_width, gint grain_height) { memset (dither, 0, sizeof (*dither)); dither->mode = mode; dither->intensity = intensity; dither->grain_width_shift = calc_grain_shift (grain_width); dither->grain_height_shift = calc_grain_shift (grain_height); dither->bayer_size_shift = BAYER_MATRIX_DIM_SHIFT; dither->bayer_size_mask = BAYER_MATRIX_DIM - 1; if (mode == CHAFA_DITHER_MODE_ORDERED) { dither->bayer_matrix = chafa_gen_bayer_matrix (BAYER_MATRIX_DIM, intensity); } else if (mode == CHAFA_DITHER_MODE_DIFFUSION) { dither->intensity = MIN (dither->intensity, 1.0); } } void chafa_dither_deinit (ChafaDither *dither) { g_free (dither->bayer_matrix); dither->bayer_matrix = NULL; } void chafa_dither_copy (const ChafaDither *src, ChafaDither *dest) { memcpy (dest, src, sizeof (*dest)); if (dest->bayer_matrix) dest->bayer_matrix = g_memdup (src->bayer_matrix, BAYER_MATRIX_SIZE * sizeof (gint)); } ChafaColor chafa_dither_color_ordered (const ChafaDither *dither, ChafaColor color, gint x, gint y) { gint bayer_index = (((y >> dither->grain_height_shift) & dither->bayer_size_mask) << dither->bayer_size_shift) + ((x >> dither->grain_width_shift) & dither->bayer_size_mask); gint16 bayer_mod = dither->bayer_matrix [bayer_index]; gint i; for (i = 0; i < 3; i++) { gint16 c; c = (gint16) color.ch [i] + bayer_mod; c = CLAMP (c, 0, 255); color.ch [i] = c; } return color; } chafa-1.8.0/chafa/internal/chafa-dither.h000066400000000000000000000031171411352071600201320ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_DITHER_H__ #define __CHAFA_DITHER_H__ #include "internal/chafa-palette.h" G_BEGIN_DECLS typedef struct { ChafaDitherMode mode; gdouble intensity; gint grain_width_shift; gint grain_height_shift; gint bayer_size_shift; guint bayer_size_mask; gint *bayer_matrix; } ChafaDither; void chafa_dither_init (ChafaDither *dither, ChafaDitherMode mode, gdouble intensity, gint grain_width, gint grain_height); void chafa_dither_deinit (ChafaDither *dither); void chafa_dither_copy (const ChafaDither *src, ChafaDither *dest); ChafaColor chafa_dither_color_ordered (const ChafaDither *dither, ChafaColor color, gint x, gint y); G_END_DECLS #endif /* __CHAFA_DITHER_H__ */ chafa-1.8.0/chafa/internal/chafa-indexed-image.c000066400000000000000000000372311411352071600213520ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "smolscale/smolscale.h" #include "chafa.h" #include "internal/chafa-batch.h" #include "internal/chafa-private.h" typedef struct { ChafaIndexedImage *indexed_image; ChafaColorSpace color_space; ChafaPixelType src_pixel_type; gconstpointer src_pixels; gint src_width, src_height, src_rowstride; gint dest_width, dest_height; SmolScaleCtx *scale_ctx; guint32 *scaled_data; /* BG color with alpha multiplier 255-0 */ guint32 bg_color_lut [256]; } DrawPixelsCtx; static void gen_color_lut_rgba8 (guint32 *color_lut, ChafaColor col) { gint i; for (i = 0; i < 256; i++) { ChafaColor ncol; ncol.ch [0] = (col.ch [0] * (255 - i)) / 255; ncol.ch [1] = (col.ch [1] * (255 - i)) / 255; ncol.ch [2] = (col.ch [2] * (255 - i)) / 255; ncol.ch [3] = 0; chafa_color8_store_to_rgba8 (ncol, &color_lut [i]); } } static void post_scale_row (guint32 *row_inout, int width, void *user_data) { const DrawPixelsCtx *ctx = user_data; guint32 *row_inout_end = row_inout + width; /* Composite on solid background color */ for ( ; row_inout < row_inout_end; row_inout++) { ChafaColor c = chafa_color8_fetch_from_rgba8 (row_inout); *row_inout += ctx->bg_color_lut [c.ch [3]]; } } static void draw_pixels_pass_1_worker (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx) { smol_scale_batch_full (ctx->scale_ctx, ctx->scaled_data + (ctx->dest_width * batch->first_row), batch->first_row, batch->n_rows); } static gint quantize_pixel (const DrawPixelsCtx *ctx, ChafaColorHash *color_hash, ChafaColor color) { ChafaColor cached_color; gint index; if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (&ctx->indexed_image->palette)) return chafa_palette_get_transparent_index (&ctx->indexed_image->palette); /* Sixel color resolution is only slightly less than 7 bits per channel, * so eliminate the low-order bits to get better hash performance. Also * mask out the alpha channel. */ CHAFA_COLOR8_U32 (cached_color) = CHAFA_COLOR8_U32 (color) & GUINT32_FROM_BE (0xfefefe00); index = chafa_color_hash_lookup (color_hash, CHAFA_COLOR8_U32 (cached_color)); if (index < 0) { if (ctx->color_space == CHAFA_COLOR_SPACE_DIN99D) chafa_color_rgb_to_din99d (&color, &color); index = chafa_palette_lookup_nearest (&ctx->indexed_image->palette, ctx->color_space, &color, NULL) - chafa_palette_get_first_color (&ctx->indexed_image->palette); /* Don't insert transparent pixels, since color hash does not store transparency */ if (index != chafa_palette_get_transparent_index (&ctx->indexed_image->palette)) chafa_color_hash_replace (color_hash, CHAFA_COLOR8_U32 (cached_color), index); } return index; } static gint quantize_pixel_with_error (const DrawPixelsCtx *ctx, ChafaColor color, ChafaColorAccum *error_inout) { gint index; if ((gint) (color.ch [3]) < chafa_palette_get_alpha_threshold (&ctx->indexed_image->palette)) { gint i; /* Don't propagate error across transparency */ for (i = 0; i < 4; i++) error_inout->ch [i] = 0; return chafa_palette_get_transparent_index (&ctx->indexed_image->palette); } if (ctx->color_space == CHAFA_COLOR_SPACE_DIN99D) chafa_color_rgb_to_din99d (&color, &color); index = chafa_palette_lookup_with_error (&ctx->indexed_image->palette, ctx->color_space, color, error_inout) - chafa_palette_get_first_color (&ctx->indexed_image->palette); return index; } static void draw_pixels_pass_2_nodither (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, ChafaColorHash *chash) { const guint32 *src_p; guint8 *dest_p, *dest_end_p; src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); for ( ; dest_p < dest_end_p; src_p++, dest_p++) { ChafaColor col; gint index; col = chafa_color8_fetch_from_rgba8 (src_p); index = quantize_pixel (ctx, chash, col); *dest_p = index; } } static void draw_pixels_pass_2_bayer (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, ChafaColorHash *chash) { const guint32 *src_p; guint8 *dest_p, *dest_end_p; gint x, y; src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); x = 0; y = batch->first_row; for ( ; dest_p < dest_end_p; src_p++, dest_p++) { ChafaColor col; gint index; col = chafa_color8_fetch_from_rgba8 (src_p); col = chafa_dither_color_ordered (&ctx->indexed_image->dither, col, x, y); index = quantize_pixel (ctx, chash, col); *dest_p = index; if (++x >= ctx->dest_width) { x = 0; y++; } } } static void distribute_error (ChafaColorAccum error_in, ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1, ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3, gdouble intensity) { gint i; for (i = 0; i < 3; i++) { gint16 ch = error_in.ch [i]; error_out_0->ch [i] += (ch * 7) * intensity; error_out_1->ch [i] += (ch * 1) * intensity; error_out_2->ch [i] += (ch * 5) * intensity; error_out_3->ch [i] += (ch * 3) * intensity; } } static guint8 fs_dither_pixel (const DrawPixelsCtx *ctx, G_GNUC_UNUSED ChafaColorHash *chash, const guint32 *inpixel_p, ChafaColorAccum error_in, ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1, ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3) { ChafaColor col = chafa_color8_fetch_from_rgba8 (inpixel_p); guint8 index; index = quantize_pixel_with_error (ctx, col, &error_in); distribute_error (error_in, error_out_0, error_out_1, error_out_2, error_out_3, ctx->indexed_image->dither.intensity); return index; } static void fs_dither_row (const DrawPixelsCtx *ctx, ChafaColorHash *chash, const guint32 *inrow_p, guint8 *outrow_p, ChafaColorAccum *error_row, ChafaColorAccum *next_error_row, gint width, gint y) { gint x; if (y & 1) { /* Forwards pass */ outrow_p [0] = fs_dither_pixel (ctx, chash, &inrow_p [0], error_row [0], &error_row [1], &next_error_row [1], &next_error_row [0], &next_error_row [1]); for (x = 1; x < width - 1; x++) { outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], &error_row [x + 1], &next_error_row [x + 1], &next_error_row [x], &next_error_row [x - 1]); } outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], &next_error_row [x], &next_error_row [x], &next_error_row [x - 1], &next_error_row [x - 1]); } else { /* Backwards pass */ x = width - 1; outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], &error_row [x - 1], &next_error_row [x - 1], &next_error_row [x], &next_error_row [x - 1]); for (x--; x >= 1; x--) { outrow_p [x] = fs_dither_pixel (ctx, chash, &inrow_p [x], error_row [x], &error_row [x - 1], &next_error_row [x - 1], &next_error_row [x], &next_error_row [x + 1]); } outrow_p [0] = fs_dither_pixel (ctx, chash, &inrow_p [0], error_row [0], &next_error_row [0], &next_error_row [0], &next_error_row [1], &next_error_row [1]); } } static void draw_pixels_pass_2_fs (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx, ChafaColorHash *chash) { ChafaColorAccum *error_row [2]; const guint32 *src_p; guint8 *dest_end_p, *dest_p; gint y; error_row [0] = alloca (ctx->dest_width * sizeof (ChafaColorAccum)); error_row [1] = alloca (ctx->dest_width * sizeof (ChafaColorAccum)); src_p = ctx->scaled_data + (ctx->dest_width * batch->first_row); dest_p = ctx->indexed_image->pixels + (ctx->dest_width * batch->first_row); dest_end_p = dest_p + (ctx->dest_width * batch->n_rows); y = batch->first_row; memset (error_row [0], 0, ctx->dest_width * sizeof (ChafaColorAccum)); for ( ; dest_p < dest_end_p; src_p += ctx->dest_width, dest_p += ctx->dest_width, y++) { ChafaColorAccum *error_row_temp; memset (error_row [1], 0, ctx->dest_width * sizeof (ChafaColorAccum)); fs_dither_row (ctx, chash, src_p, dest_p, error_row [0], error_row [1], ctx->dest_width, y); error_row_temp = error_row [0]; error_row [0] = error_row [1]; error_row [1] = error_row_temp; } } static void draw_pixels_pass_2_worker (ChafaBatchInfo *batch, const DrawPixelsCtx *ctx) { ChafaColorHash chash; chafa_color_hash_init (&chash); switch (ctx->indexed_image->dither.mode) { case CHAFA_DITHER_MODE_NONE: draw_pixels_pass_2_nodither (batch, ctx, &chash); break; case CHAFA_DITHER_MODE_ORDERED: draw_pixels_pass_2_bayer (batch, ctx, &chash); break; case CHAFA_DITHER_MODE_DIFFUSION: draw_pixels_pass_2_fs (batch, ctx, &chash); break; case CHAFA_DITHER_MODE_MAX: g_assert_not_reached (); break; } chafa_color_hash_deinit (&chash); } static void draw_pixels (DrawPixelsCtx *ctx) { chafa_process_batches (ctx, (GFunc) draw_pixels_pass_1_worker, NULL, ctx->dest_height, g_get_num_processors (), 1); chafa_palette_generate (&ctx->indexed_image->palette, ctx->scaled_data, ctx->dest_width * ctx->dest_height, ctx->color_space); /* Single thread only for diffusion; it's a fully serial operation */ chafa_process_batches (ctx, (GFunc) draw_pixels_pass_2_worker, NULL, ctx->dest_height, ctx->indexed_image->dither.mode == CHAFA_DITHER_MODE_DIFFUSION ? 1 : g_get_num_processors (), 1); } ChafaIndexedImage * chafa_indexed_image_new (gint width, gint height, const ChafaPalette *palette, const ChafaDither *dither) { ChafaIndexedImage *indexed_image; indexed_image = g_new0 (ChafaIndexedImage, 1); indexed_image->width = width; indexed_image->height = height; indexed_image->pixels = g_malloc (width * height); chafa_palette_copy (palette, &indexed_image->palette); chafa_palette_set_transparent_index (&indexed_image->palette, 255); chafa_dither_copy (dither, &indexed_image->dither); return indexed_image; } void chafa_indexed_image_destroy (ChafaIndexedImage *indexed_image) { chafa_dither_deinit (&indexed_image->dither); g_free (indexed_image->pixels); g_free (indexed_image); } void chafa_indexed_image_draw_pixels (ChafaIndexedImage *indexed_image, ChafaColorSpace color_space, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride, gint dest_width, gint dest_height) { DrawPixelsCtx ctx; g_return_if_fail (dest_width == indexed_image->width); g_return_if_fail (dest_height <= indexed_image->height); dest_width = MIN (dest_width, indexed_image->width); dest_height = MIN (dest_height, indexed_image->height); ctx.indexed_image = indexed_image; ctx.color_space = color_space; ctx.src_pixel_type = src_pixel_type; ctx.src_pixels = src_pixels; ctx.src_width = src_width; ctx.src_height = src_height; ctx.src_rowstride = src_rowstride; ctx.dest_width = dest_width; ctx.dest_height = dest_height; gen_color_lut_rgba8 (ctx.bg_color_lut, *chafa_palette_get_color (&indexed_image->palette, CHAFA_COLOR_SPACE_RGB, CHAFA_PALETTE_INDEX_BG)); ctx.scaled_data = g_new (guint32, dest_width * dest_height); ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, (const guint32 *) src_pixels, src_width, src_height, src_rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, NULL, dest_width, dest_height, dest_width * sizeof (guint32), post_scale_row, &ctx); draw_pixels (&ctx); memset (indexed_image->pixels + indexed_image->width * dest_height, 0, indexed_image->width * (indexed_image->height - dest_height)); smol_scale_destroy (ctx.scale_ctx); g_free (ctx.scaled_data); } chafa-1.8.0/chafa/internal/chafa-indexed-image.h000066400000000000000000000035741411352071600213620ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_INDEXED_IMAGE_H__ #define __CHAFA_INDEXED_IMAGE_H__ #include "internal/chafa-palette.h" #include "internal/chafa-dither.h" G_BEGIN_DECLS typedef struct { gint width, height; ChafaPalette palette; ChafaDither dither; guint8 *pixels; } ChafaIndexedImage; ChafaIndexedImage *chafa_indexed_image_new (gint width, gint height, const ChafaPalette *palette, const ChafaDither *dither); void chafa_indexed_image_destroy (ChafaIndexedImage *indexed_image); void chafa_indexed_image_draw_pixels (ChafaIndexedImage *indexed_image, ChafaColorSpace color_space, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride, gint dest_width, gint dest_height); G_END_DECLS #endif /* __CHAFA_INDEXED_IMAGE_H__ */ chafa-1.8.0/chafa/internal/chafa-iterm2-canvas.c000066400000000000000000000176761411352071600213400ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "smolscale/smolscale.h" #include "internal/chafa-base64.h" #include "internal/chafa-batch.h" #include "internal/chafa-bitfield.h" #include "internal/chafa-indexed-image.h" #include "internal/chafa-iterm2-canvas.h" #include "internal/chafa-string-util.h" typedef enum { TIFF_TYPE_NONE = 0, TIFF_TYPE_BYTE, TIFF_TYPE_ASCII, TIFF_TYPE_SHORT, TIFF_TYPE_LONG, TIFF_TYPE_RATIONAL, TIFF_TYPE_SBYTE, TIFF_TYPE_UNDEF, TIFF_TYPE_SSHORT, TIFF_TYPE_SLONG, TIFF_TYPE_SRATIONAL, TIFF_TYPE_FLOAT, TIFF_TYPE_DOUBLE } TiffType; typedef enum { TIFF_TAG_NONE = 0, TIFF_TAG_NEW_SUB_FILE_TYPE = 254, TIFF_TAG_SUB_FILE_TYPE, TIFF_TAG_IMAGE_WIDTH, TIFF_TAG_IMAGE_LENGTH, TIFF_TAG_BITS_PER_SAMPLE, TIFF_TAG_COMPRESSION, TIFF_TAG_PHOTOMETRIC_INTERPRETATION = 262, TIFF_TAG_MAKE = 271, TIFF_TAG_MODEL, TIFF_TAG_STRIP_OFFSETS, TIFF_TAG_ORIENTATION, TIFF_TAG_SAMPLES_PER_PIXEL = 277, TIFF_TAG_ROWS_PER_STRIP, TIFF_TAG_STRIP_BYTE_COUNTS, TIFF_TAG_MIN_SAMPLE_VALUE, TIFF_TAG_MAX_SAMPLE_VALUE, TIFF_TAG_X_RESOLUTION, TIFF_TAG_Y_RESOLUTION, TIFF_TAG_PLANAR_CONFIGURATION, TIFF_TAG_EXTRA_SAMPLES = 338 } TiffTagId; #define TIFF_PHOTOMETRIC_INTERPRETATION_RGB 2 #define TIFF_ORIENTATION_TOPLEFT 1 #define TIFF_PLANAR_CONFIGURATION_CONTIGUOUS 1 #define TIFF_EXTRA_SAMPLE_ASSOC_ALPHA 1 typedef struct { guint16 tag_id; guint16 type; guint32 count; guint32 data; } TiffTag; typedef struct { ChafaIterm2Canvas *iterm2_canvas; GString *out_str; } BuildCtx; typedef struct { ChafaIterm2Canvas *iterm2_canvas; SmolScaleCtx *scale_ctx; } DrawCtx; ChafaIterm2Canvas * chafa_iterm2_canvas_new (gint width, gint height) { ChafaIterm2Canvas *iterm2_canvas; iterm2_canvas = g_new (ChafaIterm2Canvas, 1); iterm2_canvas->width = width; iterm2_canvas->height = height; iterm2_canvas->rgba_image = g_malloc (width * height * sizeof (guint32)); return iterm2_canvas; } void chafa_iterm2_canvas_destroy (ChafaIterm2Canvas *iterm2_canvas) { g_free (iterm2_canvas->rgba_image); g_free (iterm2_canvas); } static void draw_pixels_worker (ChafaBatchInfo *batch, const DrawCtx *ctx) { smol_scale_batch_full (ctx->scale_ctx, ((guint32 *) ctx->iterm2_canvas->rgba_image) + (ctx->iterm2_canvas->width * batch->first_row), batch->first_row, batch->n_rows); } void chafa_iterm2_canvas_draw_all_pixels (ChafaIterm2Canvas *iterm2_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride) { DrawCtx ctx; g_return_if_fail (iterm2_canvas != NULL); g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); g_return_if_fail (src_pixels != NULL); g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); if (src_width == 0 || src_height == 0) return; ctx.iterm2_canvas = iterm2_canvas; ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, (const guint32 *) src_pixels, src_width, src_height, src_rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, NULL, iterm2_canvas->width, iterm2_canvas->height, iterm2_canvas->width * sizeof (guint32), NULL, &ctx); chafa_process_batches (&ctx, (GFunc) draw_pixels_worker, NULL, iterm2_canvas->height, g_get_num_processors (), 1); } static void encode_tag (ChafaBase64 *base64, GString *gs, const TiffTag *tag) { chafa_base64_encode (base64, gs, tag, sizeof (*tag)); } static void generate_tag (ChafaBase64 *base64, GString *gs, guint16 tag_id, guint16 type, guint32 count, guint32 data) { TiffTag tag; tag.tag_id = GUINT16_TO_LE (tag_id); tag.type = GUINT16_TO_LE (type); tag.count = GUINT32_TO_LE (count); tag.data = GUINT32_TO_LE (data); encode_tag (base64, gs, &tag); } void chafa_iterm2_canvas_build_ansi (ChafaIterm2Canvas *iterm2_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells) { gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; ChafaBase64 base64; guint32 u32; guint16 u16; *chafa_term_info_emit_begin_iterm2_image (term_info, seq, width_cells, height_cells) = '\0'; g_string_append (out_str, seq); chafa_base64_init (&base64); /* Header and directory offset */ u32 = GUINT32_TO_LE (0x002a4949); chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); u32 = GUINT32_TO_LE (iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32) + sizeof (guint32) * 2); chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); /* Image data */ chafa_base64_encode (&base64, out_str, iterm2_canvas->rgba_image, iterm2_canvas->width * iterm2_canvas->height * sizeof (guint32)); /* IFD */ u16 = GUINT16_TO_LE (11); /* Tag count */ chafa_base64_encode (&base64, out_str, &u16, sizeof (u16)); /* Tags */ generate_tag (&base64, out_str, TIFF_TAG_IMAGE_WIDTH, TIFF_TYPE_LONG, 1, iterm2_canvas->width); generate_tag (&base64, out_str, TIFF_TAG_IMAGE_LENGTH, TIFF_TYPE_LONG, 1, iterm2_canvas->height); generate_tag (&base64, out_str, TIFF_TAG_BITS_PER_SAMPLE, TIFF_TYPE_SHORT, 1, 8); generate_tag (&base64, out_str, TIFF_TAG_PHOTOMETRIC_INTERPRETATION, TIFF_TYPE_SHORT, 1, TIFF_PHOTOMETRIC_INTERPRETATION_RGB); generate_tag (&base64, out_str, TIFF_TAG_STRIP_OFFSETS, TIFF_TYPE_LONG, 1, sizeof (guint32) * 2); generate_tag (&base64, out_str, TIFF_TAG_ORIENTATION, TIFF_TYPE_SHORT, 1, TIFF_ORIENTATION_TOPLEFT); generate_tag (&base64, out_str, TIFF_TAG_SAMPLES_PER_PIXEL, TIFF_TYPE_SHORT, 1, 4); generate_tag (&base64, out_str, TIFF_TAG_ROWS_PER_STRIP, TIFF_TYPE_LONG, 1, iterm2_canvas->height); generate_tag (&base64, out_str, TIFF_TAG_STRIP_BYTE_COUNTS, TIFF_TYPE_LONG, 1, iterm2_canvas->width * iterm2_canvas->height * 4); generate_tag (&base64, out_str, TIFF_TAG_PLANAR_CONFIGURATION, TIFF_TYPE_SHORT, 1, TIFF_PLANAR_CONFIGURATION_CONTIGUOUS); generate_tag (&base64, out_str, TIFF_TAG_EXTRA_SAMPLES, TIFF_TYPE_SHORT, 1, TIFF_EXTRA_SAMPLE_ASSOC_ALPHA); /* Next IFD offset (terminator) */ u32 = GUINT32_TO_LE (0); chafa_base64_encode (&base64, out_str, &u32, sizeof (u32)); chafa_base64_encode_end (&base64, out_str); chafa_base64_deinit (&base64); *chafa_term_info_emit_end_iterm2_image (term_info, seq) = '\0'; g_string_append (out_str, seq); } chafa-1.8.0/chafa/internal/chafa-iterm2-canvas.h000066400000000000000000000032441411352071600213270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_ITERM2_CANVAS_H__ #define __CHAFA_ITERM2_CANVAS_H__ #include "chafa.h" G_BEGIN_DECLS typedef struct { gint width, height; gpointer rgba_image; } ChafaIterm2Canvas; ChafaIterm2Canvas *chafa_iterm2_canvas_new (gint width, gint height); void chafa_iterm2_canvas_destroy (ChafaIterm2Canvas *iterm2_canvas); void chafa_iterm2_canvas_draw_all_pixels (ChafaIterm2Canvas *iterm2_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride); void chafa_iterm2_canvas_build_ansi (ChafaIterm2Canvas *iterm2_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells); G_END_DECLS #endif /* __CHAFA_ITERM2_CANVAS_H__ */ chafa-1.8.0/chafa/internal/chafa-kitty-canvas.c000066400000000000000000000124321411352071600212630ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "smolscale/smolscale.h" #include "internal/chafa-base64.h" #include "internal/chafa-batch.h" #include "internal/chafa-bitfield.h" #include "internal/chafa-indexed-image.h" #include "internal/chafa-kitty-canvas.h" #include "internal/chafa-string-util.h" typedef struct { ChafaKittyCanvas *kitty_canvas; GString *out_str; } BuildCtx; typedef struct { ChafaKittyCanvas *kitty_canvas; SmolScaleCtx *scale_ctx; } DrawCtx; ChafaKittyCanvas * chafa_kitty_canvas_new (gint width, gint height) { ChafaKittyCanvas *kitty_canvas; kitty_canvas = g_new (ChafaKittyCanvas, 1); kitty_canvas->width = width; kitty_canvas->height = height; kitty_canvas->rgba_image = g_malloc (width * height * sizeof (guint32)); return kitty_canvas; } void chafa_kitty_canvas_destroy (ChafaKittyCanvas *kitty_canvas) { g_free (kitty_canvas->rgba_image); g_free (kitty_canvas); } static void draw_pixels_worker (ChafaBatchInfo *batch, const DrawCtx *ctx) { smol_scale_batch_full (ctx->scale_ctx, ((guint32 *) ctx->kitty_canvas->rgba_image) + (ctx->kitty_canvas->width * batch->first_row), batch->first_row, batch->n_rows); } void chafa_kitty_canvas_draw_all_pixels (ChafaKittyCanvas *kitty_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride) { DrawCtx ctx; g_return_if_fail (kitty_canvas != NULL); g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); g_return_if_fail (src_pixels != NULL); g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); if (src_width == 0 || src_height == 0) return; ctx.kitty_canvas = kitty_canvas; ctx.scale_ctx = smol_scale_new_full ((SmolPixelType) src_pixel_type, (const guint32 *) src_pixels, src_width, src_height, src_rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, NULL, kitty_canvas->width, kitty_canvas->height, kitty_canvas->width * sizeof (guint32), NULL, &ctx); chafa_process_batches (&ctx, (GFunc) draw_pixels_worker, NULL, kitty_canvas->height, g_get_num_processors (), 1); } static void encode_chunk (GString *gs, const guint8 *start, const guint8 *end) { ChafaBase64 base64; chafa_base64_init (&base64); chafa_base64_encode (&base64, gs, start, end - start); chafa_base64_encode_end (&base64, gs); chafa_base64_deinit (&base64); } void chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells) { const guint8 *p, *last; gchar seq [CHAFA_TERM_SEQ_LENGTH_MAX + 1]; *chafa_term_info_emit_begin_kitty_immediate_image_v1 (term_info, seq, 32, kitty_canvas->width, kitty_canvas->height, width_cells, height_cells) = '\0'; g_string_append (out_str, seq); last = kitty_canvas->rgba_image + kitty_canvas->width * kitty_canvas->height * sizeof (guint32); for (p = kitty_canvas->rgba_image; p < last; ) { const guint8 *end; end = p + 512; if (end > last) end = last; *chafa_term_info_emit_begin_kitty_image_chunk (term_info, seq) = '\0'; g_string_append (out_str, seq); encode_chunk (out_str, p, end); *chafa_term_info_emit_end_kitty_image_chunk (term_info, seq) = '\0'; g_string_append (out_str, seq); p = end; } *chafa_term_info_emit_end_kitty_image (term_info, seq) = '\0'; g_string_append (out_str, seq); } chafa-1.8.0/chafa/internal/chafa-kitty-canvas.h000066400000000000000000000032221411352071600212650ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_KITTY_CANVAS_H__ #define __CHAFA_KITTY_CANVAS_H__ #include "chafa.h" G_BEGIN_DECLS typedef struct { gint width, height; gpointer rgba_image; } ChafaKittyCanvas; ChafaKittyCanvas *chafa_kitty_canvas_new (gint width, gint height); void chafa_kitty_canvas_destroy (ChafaKittyCanvas *kitty_canvas); void chafa_kitty_canvas_draw_all_pixels (ChafaKittyCanvas *kitty_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride); void chafa_kitty_canvas_build_ansi (ChafaKittyCanvas *kitty_canvas, ChafaTermInfo *term_info, GString *out_str, gint width_cells, gint height_cells); G_END_DECLS #endif /* __CHAFA_KITTY_CANVAS_H__ */ chafa-1.8.0/chafa/internal/chafa-mmx.c000066400000000000000000000033271411352071600174520ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include "chafa.h" #include "internal/chafa-private.h" void calc_colors_mmx (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint8 *cov) { __m64 accum [2] = { 0 }; const guint32 *u32p0 = (const guint32 *) pixels; __m64 m64b; gint i; m64b = _mm_setzero_si64 (); for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { __m64 *m64p1; __m64 m64a; m64p1 = &accum [cov [i]]; m64a = _mm_cvtsi32_si64 (u32p0 [i]); m64a = _mm_unpacklo_pi8 (m64a, m64b); *m64p1 = _mm_adds_pi16 (*m64p1, m64a); } ((__m64 *) accums_out) [0] = accum [0]; ((__m64 *) accums_out) [1] = accum [1]; #if 0 /* Called after outer loop is done */ _mm_empty (); #endif } void chafa_leave_mmx (void) { #ifdef HAVE_MMX_INTRINSICS if (chafa_have_mmx ()) _mm_empty (); #endif } chafa-1.8.0/chafa/internal/chafa-palette.c000066400000000000000000000521031411352071600203030ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* abs */ #include /* pow, cbrt, log, sqrt, atan2, cos, sin */ #include "chafa.h" #include "internal/chafa-private.h" #define DEBUG(x) /* FIXME: Refactor old color selection code into a common palette framework */ static gint find_dominant_channel (gconstpointer pixels, gint n_pixels) { const guint8 *p = pixels; guint8 min [4] = { G_MAXUINT8, G_MAXUINT8, G_MAXUINT8, G_MAXUINT8 }; guint8 max [4] = { 0, 0, 0, 0 }; guint16 diff [4]; gint best; gint i; for (i = 0; i < n_pixels; i++) { /* This should yield branch-free code where possible */ min [0] = MIN (min [0], *p); max [0] = MAX (max [0], *p); p++; min [1] = MIN (min [1], *p); max [1] = MAX (max [1], *p); p++; min [2] = MIN (min [2], *p); max [2] = MAX (max [2], *p); /* Skip alpha */ p += 2; } #if 1 /* Multipliers for luminance */ diff [0] = (max [0] - min [0]) * 30; diff [1] = (max [1] - min [1]) * 59; diff [2] = (max [2] - min [2]) * 11; #else diff [0] = (max [0] - min [0]); diff [1] = (max [1] - min [1]); diff [2] = (max [2] - min [2]); #endif /* If there are ties, prioritize thusly: G, R, B */ best = 1; if (diff [0] > diff [best]) best = 0; if (diff [2] > diff [best]) best = 2; return best; } static int compare_rgba_0 (gconstpointer a, gconstpointer b) { const guint8 *ab = a; const guint8 *bb = b; gint ai = ab [0]; gint bi = bb [0]; return ai - bi; } static int compare_rgba_1 (gconstpointer a, gconstpointer b) { const guint8 *ab = a; const guint8 *bb = b; gint ai = ab [1]; gint bi = bb [1]; return ai - bi; } static int compare_rgba_2 (gconstpointer a, gconstpointer b) { const guint8 *ab = a; const guint8 *bb = b; gint ai = ab [2]; gint bi = bb [2]; return ai - bi; } static int compare_rgba_3 (gconstpointer a, gconstpointer b) { const guint8 *ab = a; const guint8 *bb = b; gint ai = ab [3]; gint bi = bb [3]; return ai - bi; } static void sort_by_channel (gpointer pixels, gint n_pixels, gint ch) { switch (ch) { case 0: qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_0); break; case 1: qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_1); break; case 2: qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_2); break; case 3: qsort (pixels, n_pixels, sizeof (guint32), compare_rgba_3); break; default: g_assert_not_reached (); } } #if 0 static void average_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out) { guint8 *p = pixels + first_ofs * sizeof (guint32); guint8 *pixels_end; gint ch [3] = { 0 }; pixels_end = p + n_pixels * sizeof (guint32); for ( ; p < pixels_end; p += 4) { ch [0] += p [0]; ch [1] += p [1]; ch [2] += p [2]; } col_out->ch [0] = (ch [0] + n_pixels / 2) / n_pixels; col_out->ch [1] = (ch [1] + n_pixels / 2) / n_pixels; col_out->ch [2] = (ch [2] + n_pixels / 2) / n_pixels; } #endif static void median_pixels (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out) { guint8 *p = pixels + (first_ofs + n_pixels / 2) * sizeof (guint32); col_out->ch [0] = p [0]; col_out->ch [1] = p [1]; col_out->ch [2] = p [2]; } static void average_pixels_weighted_by_deviation (guint8 *pixels, gint first_ofs, gint n_pixels, ChafaColor *col_out) { guint8 *p = pixels + first_ofs * sizeof (guint32); guint8 *pixels_end; gint ch [3] = { 0 }; ChafaColor median; guint sum = 0; median_pixels (pixels, first_ofs, n_pixels, &median); pixels_end = p + n_pixels * sizeof (guint32); for ( ; p < pixels_end; p += 4) { ChafaColor t; guint diff; t.ch [0] = p [0]; t.ch [1] = p [1]; t.ch [2] = p [2]; diff = chafa_color_diff_fast (&median, &t); diff /= 256; diff += 1; ch [0] += p [0] * diff; ch [1] += p [1] * diff; ch [2] += p [2] * diff; sum += diff; } col_out->ch [0] = (ch [0] + sum / 2) / sum; col_out->ch [1] = (ch [1] + sum / 2) / sum; col_out->ch [2] = (ch [2] + sum / 2) / sum; } static void pick_box_color (gpointer pixels, gint first_ofs, gint n_pixels, ChafaColor *color_out) { average_pixels_weighted_by_deviation (pixels, first_ofs, n_pixels, color_out); } static void median_cut_once (gpointer pixels, gint first_ofs, gint n_pixels, ChafaColor *color_out) { guint8 *p = pixels; gint dominant_ch; g_assert (n_pixels > 0); dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels); sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch); pick_box_color (pixels, first_ofs, n_pixels, color_out); } static void median_cut (ChafaPalette *pal, gpointer pixels, gint first_ofs, gint n_pixels, gint first_col, gint n_cols) { guint8 *p = pixels; gint dominant_ch; g_assert (n_pixels > 0); g_assert (n_cols > 0); dominant_ch = find_dominant_channel (p + first_ofs * sizeof (guint32), n_pixels); sort_by_channel (p + first_ofs * sizeof (guint32), n_pixels, dominant_ch); if (n_cols == 1 || n_pixels < 2) { pick_box_color (pixels, first_ofs, n_pixels, &pal->colors [first_col].col [CHAFA_COLOR_SPACE_RGB]); return; } median_cut (pal, pixels, first_ofs, n_pixels / 2, first_col, n_cols / 2); median_cut (pal, pixels, first_ofs + (n_pixels / 2), n_pixels - (n_pixels / 2), first_col + (n_cols / 2), n_cols - (n_cols / 2)); } static gint dominant_diff (guint8 *p1, guint8 *p2) { gint diff [3]; diff [0] = abs (p2 [0] - (gint) p1 [0]); diff [1] = abs (p2 [1] - (gint) p1 [1]); diff [2] = abs (p2 [2] - (gint) p1 [2]); return MAX (diff [0], MAX (diff [1], diff [2])); } static void diversity_pass (ChafaPalette *pal, gpointer pixels, gint n_pixels, gint first_col, gint n_cols) { guint8 *p = pixels; gint step = n_pixels / 128; gint i, n, c; guint8 done [128] = { 0 }; step = MAX (step, 1); for (c = 0; c < n_cols; c++) { gint best_box = 0; gint best_diff = 0; for (i = 0, n = 0; i < 128 && i < n_pixels; i++) { gint diff = dominant_diff (p + 4 * n, p + 4 * (n + step - 1)); if (diff > best_diff && !done [i]) { best_diff = diff; best_box = i; } n += step; } median_cut_once (pixels, best_box * step, MAX (step / 2, 1), &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]); c++; if (c >= n_cols) break; median_cut_once (pixels, best_box * step + step / 2, MAX (step / 2, 1), &pal->colors [first_col + c].col [CHAFA_COLOR_SPACE_RGB]); done [best_box] = 1; } } static void gen_din99d_color_space (ChafaPalette *palette) { gint i; for (i = 0; i < palette->n_colors; i++) { chafa_color_rgb_to_din99d (&palette->colors [i].col [CHAFA_COLOR_SPACE_RGB], &palette->colors [i].col [CHAFA_COLOR_SPACE_DIN99D]); } } static void gen_table (ChafaPalette *palette, ChafaColorSpace color_space) { gint i; for (i = 0; i < palette->n_colors; i++) { const ChafaColor *col; if (i == palette->transparent_index) continue; col = &palette->colors [i].col [color_space]; chafa_color_table_set_pen_color (&palette->table [color_space], i, col->ch [0] | (col->ch [1] << 8) | (col->ch [2] << 16)); } chafa_color_table_sort (&palette->table [color_space]); } #define N_SAMPLES 32768 static gint extract_samples (gconstpointer pixels, gpointer pixels_out, gint n_pixels, gint step, gint alpha_threshold) { const guint32 *p = pixels; guint32 *p_out = pixels_out; gint i; step = MAX (step, 1); for (i = 0; i < n_pixels; i += step) { gint alpha = p [i] >> 24; if (alpha < alpha_threshold) continue; *(p_out++) = p [i]; } return ((ptrdiff_t) p_out - (ptrdiff_t) pixels_out) / 4; } static gint extract_samples_dense (gconstpointer pixels, gpointer pixels_out, gint n_pixels, gint n_samples_max, gint alpha_threshold) { const guint32 *p = pixels; guint32 *p_out = pixels_out; gint n_samples = 0; gint i; g_assert (n_samples_max > 0); for (i = 0; i < n_pixels; i++) { gint alpha = p [i] >> 24; if (alpha < alpha_threshold) continue; *(p_out++) = p [i]; n_samples++; if (n_samples == n_samples_max) break; } return n_samples; } static void clean_up (ChafaPalette *palette_out) { gint i, j; gint best_diff = G_MAXINT; gint best_pair = 1; /* Reserve 0th pen for transparency and move colors up. * Eliminate duplicates and colors that would be the same in * sixel representation (0..100). */ DEBUG (g_printerr ("Colors before: %d\n", palette_out->n_colors)); for (i = 1, j = 1; i < palette_out->n_colors; i++) { ChafaColor *a, *b; gint diff, t; a = &palette_out->colors [j - 1].col [CHAFA_COLOR_SPACE_RGB]; b = &palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB]; /* Dividing by 256 is strictly not correct, but it's close enough for * comparison purposes, and a lot faster too. */ t = (gint) (a->ch [0] * 100) / 256 - (gint) (b->ch [0] * 100) / 256; diff = t * t; t = (gint) (a->ch [1] * 100) / 256 - (gint) (b->ch [1] * 100) / 256; diff += t * t; t = (gint) (a->ch [2] * 100) / 256 - (gint) (b->ch [2] * 100) / 256; diff += t * t; if (diff == 0) { DEBUG (g_printerr ("%d and %d are the same\n", j - 1, i)); continue; } else if (diff < best_diff) { best_pair = j - 1; best_diff = diff; } palette_out->colors [j++] = palette_out->colors [i]; } palette_out->n_colors = j; DEBUG (g_printerr ("Colors after: %d\n", palette_out->n_colors)); g_assert (palette_out->n_colors >= 0 && palette_out->n_colors <= 256); if (palette_out->transparent_index < 256) { if (palette_out->n_colors < 256) { DEBUG (g_printerr ("Color 0 moved to end (%d)\n", palette_out->n_colors)); palette_out->colors [palette_out->n_colors] = palette_out->colors [palette_out->transparent_index]; palette_out->n_colors++; } else { /* Delete one color to make room for transparency */ palette_out->colors [best_pair] = palette_out->colors [palette_out->transparent_index]; DEBUG (g_printerr ("Color 0 replaced %d\n", best_pair)); } } } void chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type) { gint i; chafa_init_palette (); palette_out->type = type; for (i = 0; i < CHAFA_PALETTE_INDEX_MAX; i++) { palette_out->colors [i].col [CHAFA_COLOR_SPACE_RGB] = *chafa_get_palette_color_256 (i, CHAFA_COLOR_SPACE_RGB); palette_out->colors [i].col [CHAFA_COLOR_SPACE_DIN99D] = *chafa_get_palette_color_256 (i, CHAFA_COLOR_SPACE_DIN99D); } palette_out->transparent_index = CHAFA_PALETTE_INDEX_TRANSPARENT; palette_out->first_color = 0; palette_out->n_colors = 256; if (type == CHAFA_PALETTE_TYPE_FIXED_240) { palette_out->first_color = 16; palette_out->n_colors = 240; } else if (type == CHAFA_PALETTE_TYPE_FIXED_16) { palette_out->n_colors = 16; } else if (type == CHAFA_PALETTE_TYPE_FIXED_8) { palette_out->n_colors = 8; } else if (type == CHAFA_PALETTE_TYPE_FIXED_FGBG) { palette_out->first_color = CHAFA_PALETTE_INDEX_FG; palette_out->n_colors = 2; } if (palette_out->type == CHAFA_PALETTE_TYPE_DYNAMIC_256) { for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++) chafa_color_table_init (&palette_out->table [i]); } } void chafa_palette_deinit (ChafaPalette *palette) { gint i; if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256) { for (i = 0; i < CHAFA_COLOR_SPACE_MAX; i++) chafa_color_table_deinit (&palette->table [i]); } } gint chafa_palette_get_first_color (const ChafaPalette *palette) { return palette->first_color; } gint chafa_palette_get_n_colors (const ChafaPalette *palette) { return palette->n_colors; } void chafa_palette_copy (const ChafaPalette *src, ChafaPalette *dest) { memcpy (dest, src, sizeof (*dest)); } /* pixels must point to RGBA8888 data to sample */ void chafa_palette_generate (ChafaPalette *palette_out, gconstpointer pixels, gint n_pixels, ChafaColorSpace color_space) { guint32 *pixels_copy; gint step; gint copy_n_pixels; if (palette_out->type != CHAFA_PALETTE_TYPE_DYNAMIC_256) return; pixels_copy = g_malloc (N_SAMPLES * sizeof (guint32)); step = (n_pixels / N_SAMPLES) + 1; copy_n_pixels = extract_samples (pixels, pixels_copy, n_pixels, step, palette_out->alpha_threshold); /* If we recovered very few (potentially zero) samples, it could be due to * the image being mostly transparent. Resample at full density if so. */ if (copy_n_pixels < 256 && step != 1) copy_n_pixels = extract_samples_dense (pixels, pixels_copy, n_pixels, N_SAMPLES, palette_out->alpha_threshold); DEBUG (g_printerr ("Extracted %d samples.\n", copy_n_pixels)); if (copy_n_pixels < 1) { palette_out->n_colors = 0; goto out; } median_cut (palette_out, pixels_copy, 0, copy_n_pixels, 0, 128); palette_out->n_colors = 128; clean_up (palette_out); diversity_pass (palette_out, pixels_copy, copy_n_pixels, palette_out->n_colors, 256 - palette_out->n_colors); palette_out->n_colors = 256; clean_up (palette_out); gen_table (palette_out, CHAFA_COLOR_SPACE_RGB); if (color_space == CHAFA_COLOR_SPACE_DIN99D) { gen_din99d_color_space (palette_out); gen_table (palette_out, CHAFA_COLOR_SPACE_DIN99D); } out: g_free (pixels_copy); } gint chafa_palette_lookup_nearest (const ChafaPalette *palette, ChafaColorSpace color_space, const ChafaColor *color, ChafaColorCandidates *candidates) { if (palette->type == CHAFA_PALETTE_TYPE_DYNAMIC_256) { gint result; /* Transparency */ if (color->ch [3] < palette->alpha_threshold) return palette->transparent_index; result = chafa_color_table_find_nearest_pen (&palette->table [color_space], color->ch [0] | (color->ch [1] << 8) | (color->ch [2] << 16)); if (candidates) { /* The only consumer of multiple candidates is the cell canvas, and that * supports fixed palettes only. Therefore, in practice we'll never end up here. * Let's not leave a loose end, though... */ candidates->index [0] = result; candidates->index [1] = result; candidates->error [0] = 0; candidates->error [1] = 0; } return result; } else { ChafaColorCandidates candidates_temp; if (!candidates) candidates = &candidates_temp; #if 0 /* Transparency */ /* NOTE: Disabled because chafa_pick_color_*() deal * with transparency */ if (color->ch [3] < palette->alpha_threshold) return palette->transparent_index; #endif if (palette->type == CHAFA_PALETTE_TYPE_FIXED_256) chafa_pick_color_256 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_240) chafa_pick_color_240 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_16) chafa_pick_color_16 (color, color_space, candidates); else if (palette->type == CHAFA_PALETTE_TYPE_FIXED_8) chafa_pick_color_8 (color, color_space, candidates); else /* CHAFA_PALETTE_TYPE_FIXED_FGBG */ chafa_pick_color_fgbg (color, color_space, &palette->colors [CHAFA_PALETTE_INDEX_FG].col [color_space], &palette->colors [CHAFA_PALETTE_INDEX_BG].col [color_space], candidates); if (palette->transparent_index < 256) { if (candidates->index [0] == palette->transparent_index) { candidates->index [0] = candidates->index [1]; candidates->error [0] = candidates->error [1]; } else { if (candidates->index [0] == CHAFA_PALETTE_INDEX_TRANSPARENT) candidates->index [0] = palette->transparent_index; if (candidates->index [1] == CHAFA_PALETTE_INDEX_TRANSPARENT) candidates->index [1] = palette->transparent_index; } } return candidates->index [0]; } g_assert_not_reached (); } gint chafa_palette_lookup_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, ChafaColor color, ChafaColorAccum *error_inout) { ChafaColorAccum compensated_color; gint index; if (error_inout) { compensated_color.ch [0] = ((gint16) color.ch [0]) + ((error_inout->ch [0] * 0.9) / 16); compensated_color.ch [1] = ((gint16) color.ch [1]) + ((error_inout->ch [1] * 0.9) / 16); compensated_color.ch [2] = ((gint16) color.ch [2]) + ((error_inout->ch [2] * 0.9) / 16); color.ch [0] = CLAMP (compensated_color.ch [0], 0, 255); color.ch [1] = CLAMP (compensated_color.ch [1], 0, 255); color.ch [2] = CLAMP (compensated_color.ch [2], 0, 255); } index = chafa_palette_lookup_nearest (palette, color_space, &color, NULL); if (error_inout) { if (index == palette->transparent_index) { memset (error_inout, 0, sizeof (*error_inout)); } else { ChafaColor found_color = palette->colors [index].col [color_space]; error_inout->ch [0] = ((gint16) compensated_color.ch [0]) - ((gint16) found_color.ch [0]); error_inout->ch [1] = ((gint16) compensated_color.ch [1]) - ((gint16) found_color.ch [1]); error_inout->ch [2] = ((gint16) compensated_color.ch [2]) - ((gint16) found_color.ch [2]); } } return index; } ChafaPaletteType chafa_palette_get_type (const ChafaPalette *palette) { return palette->type; } const ChafaColor * chafa_palette_get_color (const ChafaPalette *palette, ChafaColorSpace color_space, gint index) { return &palette->colors [index].col [color_space]; } void chafa_palette_set_color (ChafaPalette *palette, gint index, const ChafaColor *color) { palette->colors [index].col [CHAFA_COLOR_SPACE_RGB] = *color; chafa_color_rgb_to_din99d (&palette->colors [index].col [CHAFA_COLOR_SPACE_RGB], &palette->colors [index].col [CHAFA_COLOR_SPACE_DIN99D]); } gint chafa_palette_get_alpha_threshold (const ChafaPalette *palette) { return palette->alpha_threshold; } void chafa_palette_set_alpha_threshold (ChafaPalette *palette, gint alpha_threshold) { palette->alpha_threshold = alpha_threshold; } gint chafa_palette_get_transparent_index (const ChafaPalette *palette) { return palette->transparent_index; } void chafa_palette_set_transparent_index (ChafaPalette *palette, gint index) { palette->transparent_index = index; } chafa-1.8.0/chafa/internal/chafa-palette.h000066400000000000000000000057531411352071600203210ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_PALETTE_H__ #define __CHAFA_PALETTE_H__ #include #include "internal/chafa-color.h" #include "internal/chafa-color-table.h" G_BEGIN_DECLS typedef enum { CHAFA_PALETTE_TYPE_DYNAMIC_256, CHAFA_PALETTE_TYPE_FIXED_256, CHAFA_PALETTE_TYPE_FIXED_240, CHAFA_PALETTE_TYPE_FIXED_16, CHAFA_PALETTE_TYPE_FIXED_8, CHAFA_PALETTE_TYPE_FIXED_FGBG } ChafaPaletteType; typedef struct { ChafaPaletteType type; ChafaPaletteColor colors [CHAFA_PALETTE_INDEX_MAX]; ChafaColorTable table [CHAFA_COLOR_SPACE_MAX]; gint first_color; gint n_colors; gint alpha_threshold; gint transparent_index; } ChafaPalette; void chafa_palette_init (ChafaPalette *palette_out, ChafaPaletteType type); void chafa_palette_deinit (ChafaPalette *palette); void chafa_palette_copy (const ChafaPalette *src, ChafaPalette *dest); void chafa_palette_generate (ChafaPalette *palette_out, gconstpointer pixels, gint n_pixels, ChafaColorSpace color_space); ChafaPaletteType chafa_palette_get_type (const ChafaPalette *palette); gint chafa_palette_get_first_color (const ChafaPalette *palette); gint chafa_palette_get_n_colors (const ChafaPalette *palette); gint chafa_palette_lookup_nearest (const ChafaPalette *palette, ChafaColorSpace color_space, const ChafaColor *color, ChafaColorCandidates *candidates); gint chafa_palette_lookup_with_error (const ChafaPalette *palette, ChafaColorSpace color_space, ChafaColor color, ChafaColorAccum *error_inout); const ChafaColor *chafa_palette_get_color (const ChafaPalette *palette, ChafaColorSpace color_space, gint index); void chafa_palette_set_color (ChafaPalette *palette, gint index, const ChafaColor *color); gint chafa_palette_get_alpha_threshold (const ChafaPalette *palette); void chafa_palette_set_alpha_threshold (ChafaPalette *palette, gint alpha_threshold); gint chafa_palette_get_transparent_index (const ChafaPalette *palette); void chafa_palette_set_transparent_index (ChafaPalette *palette, gint index); G_END_DECLS #endif /* __CHAFA_PALETTE_H__ */ chafa-1.8.0/chafa/internal/chafa-pca.c000066400000000000000000000110661411352071600174130ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "internal/chafa-pca.h" #define PCA_POWER_MAX_ITERATIONS 1000 #define PCA_POWER_MIN_ERROR 0.0001 static gfloat pca_converge (const ChafaVec3f32 *vecs_in, gint n_vecs, ChafaVec3f32 *eigenvector_out) { ChafaVec3f32 r = CHAFA_VEC3F32_INIT (.11, .23, .51); gfloat eigenvalue; gint i, j; /* Power iteration */ /* FIXME: r should probably be random, and we should try again * if we pick a bad one */ chafa_vec3f32_normalize (&r, &r); for (j = 0; j < PCA_POWER_MAX_ITERATIONS; j++) { ChafaVec3f32 s = CHAFA_VEC3F32_INIT_ZERO; ChafaVec3f32 t; gfloat err; for (i = 0; i < n_vecs; i++) { gfloat u; u = chafa_vec3f32_dot (&vecs_in [i], &r); chafa_vec3f32_mul_scalar (&t, &vecs_in [i], u); chafa_vec3f32_add (&s, &s, &t); } eigenvalue = chafa_vec3f32_dot (&r, &s); chafa_vec3f32_mul_scalar (&t, &r, eigenvalue); chafa_vec3f32_sub (&t, &t, &s); err = chafa_vec3f32_get_magnitude (&t); chafa_vec3f32_copy (&r, &s); chafa_vec3f32_normalize (&r, &r); if (err < PCA_POWER_MIN_ERROR) break; } chafa_vec3f32_copy (eigenvector_out, &r); return eigenvalue; } static void pca_deflate (ChafaVec3f32 *vecs, gint n_vecs, const ChafaVec3f32 *eigenvector) { gint i; /* Calculate scores, reconstruct with scores and eigenvector, * then subtract from original vectors to generate residuals. * We should be able to get the next component from those. */ for (i = 0; i < n_vecs; i++) { ChafaVec3f32 t; gfloat score; score = chafa_vec3f32_dot (&vecs [i], eigenvector); chafa_vec3f32_mul_scalar (&t, eigenvector, score); chafa_vec3f32_sub (&vecs [i], &vecs [i], &t); } } /** * chafa_vec3f32_array_compute_pca: * @vecs_in: Input vector array * @n_vecs: Number of vectors in array * @n_components: Number of components to compute (1 or 2) * @eigenvectors_out: Pointer to array to store n_components eigenvectors in, or NULL * @eigenvalues_out: Pointer to array to store n_components eigenvalues in, or NULL * @average_out: Pointer to a vector to store array average (for centering), or NULL * * Compute principal components from an array of 3D vectors. This * implementation is naive and probably not that fast, but it should * be good enough for our purposes. **/ void chafa_vec3f32_array_compute_pca (const ChafaVec3f32 *vecs_in, gint n_vecs, gint n_components, ChafaVec3f32 *eigenvectors_out, gfloat *eigenvalues_out, ChafaVec3f32 *average_out) { ChafaVec3f32 *v; ChafaVec3f32 average; ChafaVec3f32 t; gfloat eigenvalue; gint i; v = alloca (n_vecs * sizeof (ChafaVec3f32)); memcpy (v, vecs_in, n_vecs * sizeof (ChafaVec3f32)); /* Calculate average */ chafa_vec3f32_average_array (&average, v, n_vecs); /* Recenter around average */ chafa_vec3f32_set_zero (&t); chafa_vec3f32_sub (&t, &t, &average); chafa_vec3f32_add_to_array (v, &t, n_vecs); /* Compute principal components */ for (i = 0; ; ) { eigenvalue = pca_converge (v, n_vecs, &t); if (eigenvectors_out) { chafa_vec3f32_copy (eigenvectors_out, &t); eigenvectors_out++; } if (eigenvalues_out) { *eigenvalues_out = eigenvalue; eigenvalues_out++; } if (++i >= n_components) break; pca_deflate (v, n_vecs, &t); } if (average_out) chafa_vec3f32_copy (average_out, &average); } chafa-1.8.0/chafa/internal/chafa-pca.h000066400000000000000000000117661411352071600174270ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_PCA_H__ #define __CHAFA_PCA_H__ #include #include G_BEGIN_DECLS #define CHAFA_VEC3F32_INIT(x, y, z) { { (x), (y), (z) } } #define CHAFA_VEC3F32_INIT_ZERO CHAFA_VEC3F32_INIT (0.0, 0.0, 0.0) typedef struct { gfloat v [3]; } ChafaVec3f32; typedef struct { gint32 v [3]; } ChafaVec3i32; static inline void chafa_vec3i32_set (ChafaVec3i32 *out, gint32 x, gint32 y, gint32 z) { out->v [0] = x; out->v [1] = y; out->v [2] = z; } static inline void chafa_vec3i32_sub (ChafaVec3i32 *out, const ChafaVec3i32 *a, const ChafaVec3i32 *b) { out->v [0] = a->v [0] - b->v [0]; out->v [1] = a->v [1] - b->v [1]; out->v [2] = a->v [2] - b->v [2]; } static inline void chafa_vec3i32_from_vec3f32 (ChafaVec3i32 *out, const ChafaVec3f32 *in) { /* lrintf() rounding can be extremely slow, so use this function * sparingly. We use tricks to make GCC emit cvtss2si instructions * ("-fno-math-errno -fno-trapping-math", or simply "-ffast-math"), * but Clang apparently cannot be likewise persuaded. * * Clang _does_ like rounding with SSE 4.1, but that's not something * we can enable by default. */ out->v [0] = lrintf (in->v [0]); out->v [1] = lrintf (in->v [1]); out->v [2] = lrintf (in->v [2]); } static inline gint32 chafa_vec3i32_dot_32 (const ChafaVec3i32 *v, const ChafaVec3i32 *u) { return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; } static inline gint64 chafa_vec3i32_dot_64 (const ChafaVec3i32 *v, const ChafaVec3i32 *u) { return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; } static inline void chafa_vec3f32_copy (ChafaVec3f32 *dest, const ChafaVec3f32 *src) { *dest = *src; } static inline void chafa_vec3f32_add (ChafaVec3f32 *out, const ChafaVec3f32 *a, const ChafaVec3f32 *b) { out->v [0] = a->v [0] + b->v [0]; out->v [1] = a->v [1] + b->v [1]; out->v [2] = a->v [2] + b->v [2]; } static inline void chafa_vec3f32_add_from_array (ChafaVec3f32 *accum, const ChafaVec3f32 *v, gint n) { gint i; for (i = 0; i < n; i++) { chafa_vec3f32_add (accum, accum, &v [i]); } } static inline void chafa_vec3f32_add_to_array (ChafaVec3f32 *v, const ChafaVec3f32 *in, gint n) { gint i; for (i = 0; i < n; i++) { chafa_vec3f32_add (&v [i], &v [i], in); } } static inline void chafa_vec3f32_sub (ChafaVec3f32 *out, const ChafaVec3f32 *a, const ChafaVec3f32 *b) { out->v [0] = a->v [0] - b->v [0]; out->v [1] = a->v [1] - b->v [1]; out->v [2] = a->v [2] - b->v [2]; } static inline void chafa_vec3f32_mul_scalar (ChafaVec3f32 *out, const ChafaVec3f32 *in, gfloat s) { out->v [0] = in->v [0] * s; out->v [1] = in->v [1] * s; out->v [2] = in->v [2] * s; } static inline gfloat chafa_vec3f32_dot (const ChafaVec3f32 *v, const ChafaVec3f32 *u) { return v->v [0] * u->v [0] + v->v [1] * u->v [1] + v->v [2] * u->v [2]; } static inline void chafa_vec3f32_set (ChafaVec3f32 *v, gfloat x, gfloat y, gfloat z) { v->v [0] = x; v->v [1] = y; v->v [2] = z; } static inline void chafa_vec3f32_set_zero (ChafaVec3f32 *v) { chafa_vec3f32_set (v, 0.0f, 0.0f, 0.0f); } static inline gfloat chafa_vec3f32_get_magnitude (const ChafaVec3f32 *v) { return sqrtf (v->v [0] * v->v [0] + v->v [1] * v->v [1] + v->v [2] * v->v [2]); } static inline void chafa_vec3f32_normalize (ChafaVec3f32 *out, const ChafaVec3f32 *in) { gfloat m; m = 1.0f / chafa_vec3f32_get_magnitude (in); out->v [0] = in->v [0] * m; out->v [1] = in->v [1] * m; out->v [2] = in->v [2] * m; } static inline void chafa_vec3f32_average_array (ChafaVec3f32 *out, const ChafaVec3f32 *v, gint n) { chafa_vec3f32_set_zero (out); chafa_vec3f32_add_from_array (out, v, n); chafa_vec3f32_mul_scalar (out, out, 1.0f / (gfloat) n); } void chafa_vec3f32_array_compute_pca (const ChafaVec3f32 *vecs_in, gint n_vecs, gint n_components, ChafaVec3f32 *eigenvectors_out, gfloat *eigenvalues_out, ChafaVec3f32 *average_out); G_END_DECLS #endif /* __CHAFA_PCA_H__ */ chafa-1.8.0/chafa/internal/chafa-pixops.c000066400000000000000000000635041411352071600201760ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "internal/chafa-pixops.h" #include "internal/smolscale/smolscale.h" /* Fixed point multiplier */ #define FIXED_MULT 16384 /* See rgb_to_intensity_fast () */ #define INTENSITY_MAX (256 * 8) /* Normalization: Percentage of pixels to discard at extremes of histogram */ #define INDEXED_16_CROP_PCT 5 #define INDEXED_8_CROP_PCT 10 #define INDEXED_2_CROP_PCT 20 typedef struct { gint32 c [INTENSITY_MAX]; /* Transparent pixels are not sampled, so we must keep count */ gint n_samples; /* Lower and upper bounds */ gint32 min, max; } Histogram; typedef struct { ChafaPixelType src_pixel_type; gconstpointer src_pixels; gint src_width, src_height; gint src_rowstride; ChafaPixel *dest_pixels; gint dest_width, dest_height; const ChafaPalette *palette; const ChafaDither *dither; ChafaColorSpace color_space; gboolean preprocessing_enabled; gint work_factor_int; /* Cached to avoid repeatedly calling palette functions */ ChafaPaletteType palette_type; ChafaColor bg_color_rgb; /* Result of alpha detection is stored here */ gint have_alpha_int; Histogram hist; gint n_batches_pixels; gint n_rows_per_batch_pixels; SmolScaleCtx *scale_ctx; } PrepareContext; typedef struct { gint first_row; gint n_rows; Histogram hist; } PreparePixelsBatch1; typedef struct { gint first_row; gint n_rows; } PreparePixelsBatch2; static gint rgb_to_intensity_fast (const ChafaColor *color) { /* Sum to 8x so we can divide by shifting later */ return color->ch [0] * 3 + color->ch [1] * 4 + color->ch [2]; } static void sum_histograms (const Histogram *hist_in, Histogram *hist_accum) { gint i; hist_accum->n_samples += hist_in->n_samples; for (i = 0; i < INTENSITY_MAX; i++) { hist_accum->c [i] += hist_in->c [i]; } } static void histogram_calc_bounds (Histogram *hist, gint crop_pct) { gint64 pixels_crop; gint i; gint t; pixels_crop = (hist->n_samples * (((gint64) crop_pct * 1024) / 100)) / 1024; /* Find lower bound */ for (i = 0, t = pixels_crop; i < INTENSITY_MAX; i++) { t -= hist->c [i]; if (t <= 0) break; } hist->min = i; /* Find upper bound */ for (i = INTENSITY_MAX - 1, t = pixels_crop; i >= 0; i--) { t -= hist->c [i]; if (t <= 0) break; } hist->max = i; } static gint16 normalize_ch (guint8 v, gint min, gint factor) { gint vt = v; vt -= min; vt *= factor; vt /= FIXED_MULT; vt = CLAMP (vt, 0, 255); return vt; } static void normalize_rgb (ChafaPixel *pixels, const Histogram *hist, gint width, gint dest_y, gint n_rows) { ChafaPixel *p0, *p1; gint factor; /* Make sure range is more or less sane */ if (hist->min == hist->max) return; #if 0 if (min > 512) min = 512; if (max < 1536) max = 1536; #endif /* Adjust intensities */ factor = ((INTENSITY_MAX - 1) * FIXED_MULT) / (hist->max - hist->min); #if 0 g_printerr ("[%d-%d] * %d, crop=%d \n", min, max, factor, pixels_crop); #endif p0 = pixels + dest_y * width; p1 = p0 + n_rows * width; for ( ; p0 < p1; p0++) { p0->col.ch [0] = normalize_ch (p0->col.ch [0], hist->min / 8, factor); p0->col.ch [1] = normalize_ch (p0->col.ch [1], hist->min / 8, factor); p0->col.ch [2] = normalize_ch (p0->col.ch [2], hist->min / 8, factor); } } static void boost_saturation_rgb (ChafaColor *col) { gint ch [3]; gfloat P = sqrtf (col->ch [0] * (gfloat) col->ch [0] * .299f + col->ch [1] * (gfloat) col->ch [1] * .587f + col->ch [2] * (gfloat) col->ch [2] * .144f); ch [0] = P + ((gfloat) col->ch [0] - P) * 2; ch [1] = P + ((gfloat) col->ch [1] - P) * 2; ch [2] = P + ((gfloat) col->ch [2] - P) * 2; col->ch [0] = CLAMP (ch [0], 0, 255); col->ch [1] = CLAMP (ch [1], 0, 255); col->ch [2] = CLAMP (ch [2], 0, 255); } /* pixel must point to top-left pixel of the grain to be dithered */ static void fs_dither_grain (const ChafaDither *dither, const ChafaPalette *palette, ChafaColorSpace color_space, ChafaPixel *pixel, gint image_width, const ChafaColorAccum *error_in, ChafaColorAccum *error_out_0, ChafaColorAccum *error_out_1, ChafaColorAccum *error_out_2, ChafaColorAccum *error_out_3) { gint grain_width = 1 << dither->grain_width_shift; gint grain_height = 1 << dither->grain_height_shift; gint grain_shift = dither->grain_width_shift + dither->grain_height_shift; ChafaColorAccum next_error = { 0 }; ChafaColorAccum accum = { 0 }; ChafaColorCandidates cand = { 0 }; ChafaPixel *p; ChafaColor acol; const ChafaColor *col; gint x, y, i; p = pixel; for (y = 0; y < grain_height; y++) { for (x = 0; x < grain_width; x++, p++) { for (i = 0; i < 3; i++) { gint16 ch = p->col.ch [i]; ch += error_in->ch [i]; if (ch < 0) { next_error.ch [i] += ch; ch = 0; } else if (ch > 255) { next_error.ch [i] += ch - 255; ch = 255; } p->col.ch [i] = ch; accum.ch [i] += ch; } } p += image_width - grain_width; } for (i = 0; i < 3; i++) { accum.ch [i] >>= grain_shift; acol.ch [i] = accum.ch [i]; } /* Don't try to dither alpha */ acol.ch [3] = 0xff; chafa_palette_lookup_nearest (palette, color_space, &acol, &cand); col = chafa_palette_get_color (palette, color_space, cand.index [0]); for (i = 0; i < 3; i++) { /* FIXME: Floating point op is slow. Factor this out and make * dither_intensity == 1.0 the fast path. */ next_error.ch [i] = ((next_error.ch [i] >> grain_shift) + (accum.ch [i] - (gint16) col->ch [i]) * dither->intensity); error_out_0->ch [i] += next_error.ch [i] * 7 / 16; error_out_1->ch [i] += next_error.ch [i] * 1 / 16; error_out_2->ch [i] += next_error.ch [i] * 5 / 16; error_out_3->ch [i] += next_error.ch [i] * 3 / 16; } } static void convert_rgb_to_din99d (ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) { ChafaPixel *pixel = pixels + dest_y * width; ChafaPixel *pixel_max = pixel + n_rows * width; /* RGB -> DIN99d */ for ( ; pixel < pixel_max; pixel++) { chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); } } static void bayer_dither (const ChafaDither *dither, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) { ChafaPixel *pixel = pixels + dest_y * width; ChafaPixel *pixel_max = pixel + n_rows * width; gint x, y; for (y = dest_y; pixel < pixel_max; y++) { for (x = 0; x < width; x++) { pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y); pixel++; } } } static void fs_dither (const ChafaDither *dither, const ChafaPalette *palette, ChafaColorSpace color_space, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) { ChafaPixel *pixel; ChafaColorAccum *error_rows; ChafaColorAccum *error_row [2]; ChafaColorAccum *pp; gint grain_width = 1 << dither->grain_width_shift; gint grain_height = 1 << dither->grain_height_shift; gint width_grains = width >> dither->grain_width_shift; gint x, y; g_assert (width % grain_width == 0); g_assert (dest_y % grain_height == 0); g_assert (n_rows % grain_height == 0); dest_y >>= dither->grain_height_shift; n_rows >>= dither->grain_height_shift; error_rows = alloca (width_grains * 2 * sizeof (ChafaColorAccum)); error_row [0] = error_rows; error_row [1] = error_rows + width_grains; memset (error_row [0], 0, width_grains * sizeof (ChafaColorAccum)); for (y = dest_y; y < dest_y + n_rows; y++) { memset (error_row [1], 0, width_grains * sizeof (ChafaColorAccum)); if (!(y & 1)) { /* Forwards pass */ pixel = pixels + (y << dither->grain_height_shift) * width; fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0], error_row [0] + 1, error_row [1] + 1, error_row [1], error_row [1] + 1); pixel += grain_width; for (x = 1; ((x + 1) << dither->grain_width_shift) < width; x++) { fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0] + x, error_row [0] + x + 1, error_row [1] + x + 1, error_row [1] + x, error_row [1] + x - 1); pixel += grain_width; } fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0] + x, error_row [1] + x, error_row [1] + x, error_row [1] + x - 1, error_row [1] + x - 1); } else { /* Backwards pass */ pixel = pixels + (y << dither->grain_height_shift) * (width + 1) - grain_width; fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0] + width_grains - 1, error_row [0] + width_grains - 2, error_row [1] + width_grains - 2, error_row [1] + width_grains - 1, error_row [1] + width_grains - 2); pixel -= grain_width; for (x = width_grains - 2; x > 0; x--) { fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0] + x, error_row [0] + x - 1, error_row [1] + x - 1, error_row [1] + x, error_row [1] + x + 1); pixel -= grain_width; } fs_dither_grain (dither, palette, color_space, pixel, width, error_row [0], error_row [1], error_row [1], error_row [1] + 1, error_row [1] + 1); } pp = error_row [0]; error_row [0] = error_row [1]; error_row [1] = pp; } } static void bayer_and_convert_rgb_to_din99d (const ChafaDither *dither, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) { ChafaPixel *pixel = pixels + dest_y * width; ChafaPixel *pixel_max = pixel + n_rows * width; gint x, y; for (y = dest_y; pixel < pixel_max; y++) { for (x = 0; x < width; x++) { pixel->col = chafa_dither_color_ordered (dither, pixel->col, x, y); chafa_color_rgb_to_din99d (&pixel->col, &pixel->col); pixel++; } } } static void fs_and_convert_rgb_to_din99d (const ChafaDither *dither, const ChafaPalette *palette, ChafaPixel *pixels, gint width, gint dest_y, gint n_rows) { convert_rgb_to_din99d (pixels, width, dest_y, n_rows); fs_dither (dither, palette, CHAFA_COLOR_SPACE_DIN99D, pixels, width, dest_y, n_rows); } static void prepare_pixels_1_inner (PreparePixelsBatch1 *work, PrepareContext *prep_ctx, const guint8 *data_p, ChafaPixel *pixel_out, gint *alpha_sum) { ChafaColor *col = &pixel_out->col; col->ch [0] = data_p [0]; col->ch [1] = data_p [1]; col->ch [2] = data_p [2]; col->ch [3] = data_p [3]; *alpha_sum += (0xff - col->ch [3]); if (prep_ctx->preprocessing_enabled && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8) ) { boost_saturation_rgb (col); } /* Build histogram */ if (col->ch [3] > 127) { gint v = rgb_to_intensity_fast (col); work->hist.c [v]++; work->hist.n_samples++; } } static void prepare_pixels_1_worker_nearest (PreparePixelsBatch1 *work, PrepareContext *prep_ctx) { ChafaPixel *pixel; gint dest_y; gint px, py; gint x_inc, y_inc; gint alpha_sum = 0; const guint8 *data; gint n_rows; gint rowstride; dest_y = work->first_row; data = prep_ctx->src_pixels; n_rows = work->n_rows; rowstride = prep_ctx->src_rowstride; x_inc = (prep_ctx->src_width * FIXED_MULT) / (prep_ctx->dest_width); y_inc = (prep_ctx->src_height * FIXED_MULT) / (prep_ctx->dest_height); pixel = prep_ctx->dest_pixels + dest_y * prep_ctx->dest_width; for (py = dest_y; py < dest_y + n_rows; py++) { const guint8 *data_row_p; data_row_p = data + ((py * y_inc) / FIXED_MULT) * rowstride; for (px = 0; px < prep_ctx->dest_width; px++) { const guint8 *data_p = data_row_p + ((px * x_inc) / FIXED_MULT) * 4; prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum); } } if (alpha_sum > 0) g_atomic_int_set (&prep_ctx->have_alpha_int, 1); } static void prepare_pixels_1_worker_smooth (PreparePixelsBatch1 *work, PrepareContext *prep_ctx) { ChafaPixel *pixel, *pixel_max; gint alpha_sum = 0; guint8 *scaled_data; const guint8 *data_p; scaled_data = g_malloc (prep_ctx->dest_width * work->n_rows * sizeof (guint32)); smol_scale_batch_full (prep_ctx->scale_ctx, scaled_data, work->first_row, work->n_rows); data_p = scaled_data; pixel = prep_ctx->dest_pixels + work->first_row * prep_ctx->dest_width; pixel_max = pixel + work->n_rows * prep_ctx->dest_width; while (pixel < pixel_max) { prepare_pixels_1_inner (work, prep_ctx, data_p, pixel++, &alpha_sum); data_p += 4; } g_free (scaled_data); if (alpha_sum > 0) g_atomic_int_set (&prep_ctx->have_alpha_int, 1); } static void prepare_pixels_pass_1 (PrepareContext *prep_ctx) { GThreadPool *thread_pool; PreparePixelsBatch1 *batches; gint cy; gint i; /* First pass * ---------- * * - Scale and convert pixel format * - Apply local preprocessing like saturation boost (optional) * - Generate histogram for later passes (e.g. for normalization) * - Figure out if we have alpha transparency */ batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels); thread_pool = g_thread_pool_new ((GFunc) ((prep_ctx->work_factor_int < 3 && prep_ctx->src_pixel_type == CHAFA_PIXEL_RGBA8_UNASSOCIATED) ? prepare_pixels_1_worker_nearest : prepare_pixels_1_worker_smooth), prep_ctx, g_get_num_processors (), FALSE, NULL); for (cy = 0, i = 0; cy < prep_ctx->dest_height; cy += prep_ctx->n_rows_per_batch_pixels, i++) { PreparePixelsBatch1 *batch = &batches [i]; batch->first_row = cy; batch->n_rows = MIN (prep_ctx->dest_height - cy, prep_ctx->n_rows_per_batch_pixels); g_thread_pool_push (thread_pool, batch, NULL); } /* Wait for threads to finish */ g_thread_pool_free (thread_pool, FALSE, TRUE); /* Generate final histogram */ if (prep_ctx->preprocessing_enabled) { for (i = 0; i < prep_ctx->n_batches_pixels; i++) sum_histograms (&batches [i].hist, &prep_ctx->hist); switch (prep_ctx->palette_type) { case CHAFA_PALETTE_TYPE_FIXED_16: histogram_calc_bounds (&prep_ctx->hist, INDEXED_16_CROP_PCT); break; case CHAFA_PALETTE_TYPE_FIXED_8: histogram_calc_bounds (&prep_ctx->hist, INDEXED_8_CROP_PCT); break; default: histogram_calc_bounds (&prep_ctx->hist, INDEXED_2_CROP_PCT); break; } } g_free (batches); } static void composite_alpha_on_bg (ChafaColor bg_color, ChafaPixel *pixels, gint width, gint first_row, gint n_rows) { ChafaPixel *p0, *p1; p0 = pixels + first_row * width; p1 = p0 + n_rows * width; for ( ; p0 < p1; p0++) { p0->col.ch [0] += (bg_color.ch [0] * (255 - (guint32) p0->col.ch [3])) / 255; p0->col.ch [1] += (bg_color.ch [1] * (255 - (guint32) p0->col.ch [3])) / 255; p0->col.ch [2] += (bg_color.ch [2] * (255 - (guint32) p0->col.ch [3])) / 255; } } static void prepare_pixels_2_worker (PreparePixelsBatch2 *work, PrepareContext *prep_ctx) { if (prep_ctx->preprocessing_enabled && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 ||prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8 || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG)) normalize_rgb (prep_ctx->dest_pixels, &prep_ctx->hist, prep_ctx->dest_width, work->first_row, work->n_rows); if (prep_ctx->have_alpha_int) composite_alpha_on_bg (prep_ctx->bg_color_rgb, prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); if (prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D) { if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED) { bayer_and_convert_rgb_to_din99d (prep_ctx->dither, prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); } else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) { fs_and_convert_rgb_to_din99d (prep_ctx->dither, prep_ctx->palette, prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); } else { convert_rgb_to_din99d (prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); } } else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_ORDERED) { bayer_dither (prep_ctx->dither, prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); } else if (prep_ctx->dither->mode == CHAFA_DITHER_MODE_DIFFUSION) { fs_dither (prep_ctx->dither, prep_ctx->palette, prep_ctx->color_space, prep_ctx->dest_pixels, prep_ctx->dest_width, work->first_row, work->n_rows); } } static gboolean need_pass_2 (PrepareContext *prep_ctx) { if ((prep_ctx->preprocessing_enabled && (prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_16 || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_8 || prep_ctx->palette_type == CHAFA_PALETTE_TYPE_FIXED_FGBG)) || prep_ctx->have_alpha_int || prep_ctx->color_space == CHAFA_COLOR_SPACE_DIN99D || prep_ctx->dither->mode != CHAFA_DITHER_MODE_NONE) return TRUE; return FALSE; } static void prepare_pixels_pass_2 (PrepareContext *prep_ctx) { GThreadPool *thread_pool; PreparePixelsBatch1 *batches; gint cy; gint i; /* Second pass * ----------- * * - Normalization (optional) * - Dithering (optional) * - Color space conversion; DIN99d (optional) */ if (!need_pass_2 (prep_ctx)) return; batches = g_new0 (PreparePixelsBatch1, prep_ctx->n_batches_pixels); thread_pool = g_thread_pool_new ((GFunc) prepare_pixels_2_worker, prep_ctx, g_get_num_processors (), FALSE, NULL); for (cy = 0, i = 0; cy < prep_ctx->dest_height; cy += prep_ctx->n_rows_per_batch_pixels, i++) { PreparePixelsBatch1 *batch = &batches [i]; batch->first_row = cy; batch->n_rows = MIN (prep_ctx->dest_height - cy, prep_ctx->n_rows_per_batch_pixels); g_thread_pool_push (thread_pool, batch, NULL); } /* Wait for threads to finish */ g_thread_pool_free (thread_pool, FALSE, TRUE); g_free (batches); } void chafa_prepare_pixel_data_for_symbols (const ChafaPalette *palette, const ChafaDither *dither, ChafaColorSpace color_space, gboolean preprocessing_enabled, gint work_factor, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride, ChafaPixel *dest_pixels, gint dest_width, gint dest_height) { PrepareContext prep_ctx = { 0 }; guint n_cpus; n_cpus = g_get_num_processors (); prep_ctx.palette = palette; prep_ctx.dither = dither; prep_ctx.color_space = color_space; prep_ctx.preprocessing_enabled = preprocessing_enabled; prep_ctx.work_factor_int = work_factor; prep_ctx.palette_type = chafa_palette_get_type (palette); prep_ctx.bg_color_rgb = *chafa_palette_get_color (palette, CHAFA_COLOR_SPACE_RGB, CHAFA_PALETTE_INDEX_BG); prep_ctx.src_pixel_type = src_pixel_type; prep_ctx.src_pixels = src_pixels; prep_ctx.src_width = src_width; prep_ctx.src_height = src_height; prep_ctx.src_rowstride = src_rowstride; prep_ctx.dest_pixels = dest_pixels; prep_ctx.dest_width = dest_width; prep_ctx.dest_height = dest_height; prep_ctx.n_batches_pixels = (prep_ctx.dest_height + n_cpus - 1) / n_cpus; prep_ctx.n_rows_per_batch_pixels = (prep_ctx.dest_height + prep_ctx.n_batches_pixels - 1) / prep_ctx.n_batches_pixels; prep_ctx.scale_ctx = smol_scale_new ((SmolPixelType) prep_ctx.src_pixel_type, (const guint32 *) prep_ctx.src_pixels, prep_ctx.src_width, prep_ctx.src_height, prep_ctx.src_rowstride, SMOL_PIXEL_RGBA8_PREMULTIPLIED, NULL, prep_ctx.dest_width, prep_ctx.dest_height, prep_ctx.dest_width * sizeof (guint32)); prepare_pixels_pass_1 (&prep_ctx); prepare_pixels_pass_2 (&prep_ctx); smol_scale_destroy (prep_ctx.scale_ctx); } void chafa_sort_pixel_index_by_channel (guint8 *index, const ChafaPixel *pixels, gint n_pixels, gint ch) { const gint gaps [] = { 57, 23, 10, 4, 1 }; gint g, i, j; /* Since we don't care about stability and the number of elements * is small and known in advance, use a simple in-place shellsort. * * Due to locality and callback overhead this is probably faster * than qsort(), although admittedly I haven't benchmarked it. * * Another option is to use radix, but since we support multiple * color spaces with fixed-point reals, we could get more buckets * than is practical. */ for (g = 0; ; g++) { gint gap = gaps [g]; for (i = gap; i < n_pixels; i++) { guint8 ptemp = index [i]; for (j = i; j >= gap && pixels [index [j - gap]].col.ch [ch] > pixels [ptemp].col.ch [ch]; j -= gap) { index [j] = index [j - gap]; } index [j] = ptemp; } /* After gap == 1 the array is always left sorted */ if (gap == 1) break; } } chafa-1.8.0/chafa/internal/chafa-pixops.h000066400000000000000000000040351411352071600201750ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_PIXOPS_H__ #define __CHAFA_PIXOPS_H__ #include #include "internal/chafa-private.h" G_BEGIN_DECLS void chafa_prepare_pixel_data_for_symbols (const ChafaPalette *palette, const ChafaDither *dither, ChafaColorSpace color_space, gboolean preprocessing_enabled, gint work_factor, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride, ChafaPixel *dest_pixels, gint dest_width, gint dest_height); void chafa_sort_pixel_index_by_channel (guint8 *index, const ChafaPixel *pixels, gint n_pixels, gint ch); G_END_DECLS #endif /* __CHAFA_PIXOPS_H__ */ chafa-1.8.0/chafa/internal/chafa-popcnt.c000066400000000000000000000065771411352071600201660ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include "chafa.h" #include "internal/chafa-private.h" gint chafa_pop_count_u64_builtin (guint64 v) { #if defined(HAVE_POPCNT64_INTRINSICS) return (gint) _mm_popcnt_u64 (v); #else /* HAVE_POPCNT32_INTRINSICS */ const guint32 *w = (const guint32 *) &v; return (gint) _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); #endif } void chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n) { while (n--) { #if defined(HAVE_POPCNT64_INTRINSICS) *(vc++) = _mm_popcnt_u64 (*(vv++)); #else /* HAVE_POPCNT32_INTRINSICS */ const guint32 *w = (const guint32 *)vv; *(vc++) = _mm_popcnt_u32(w[0]) + _mm_popcnt_u32(w[1]); vv++; #endif } } void chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n) { #if defined(HAVE_POPCNT64_INTRINSICS) while (n >= 4) { n -= 4; *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); } while (n--) { *(vc++) = _mm_popcnt_u64 (a ^ *(vb++)); } #else /* HAVE_POPCNT32_INTRINSICS */ const guint32 *aa = (const guint32 *) &a; const guint32 *wb = (const guint32 *) vb; while (n--) { *(vc++) = _mm_popcnt_u32 (aa [0] ^ wb [0]) + _mm_popcnt_u32 (aa [1] ^ wb [1]); wb += 2; } #endif } /* Two bitmaps per item (a points to a pair, vb points to array of pairs) */ void chafa_hamming_distance_2_vu64_builtin (const guint64 *a, const guint64 *vb, gint *vc, gint n) { #if defined(HAVE_POPCNT64_INTRINSICS) while (n >= 4) { n -= 4; *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); } while (n--) { *vc = _mm_popcnt_u64 (a [0] ^ *(vb++)); *(vc++) += _mm_popcnt_u64 (a [1] ^ *(vb++)); } #else /* HAVE_POPCNT32_INTRINSICS */ const guint32 *aa = (const guint32 *) a; const guint32 *wb = (const guint32 *) vb; while (n--) { *(vc++) = _mm_popcnt_u32 (aa [0] ^ wb [0]) + _mm_popcnt_u32 (aa [1] ^ wb [1]) + _mm_popcnt_u32 (aa [2] ^ wb [2]) + _mm_popcnt_u32 (aa [3] ^ wb [3]); wb += 4; } #endif } chafa-1.8.0/chafa/internal/chafa-private.h000066400000000000000000000206031411352071600203240ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_PRIVATE_H__ #define __CHAFA_PRIVATE_H__ #include #include "internal/chafa-bitfield.h" #include "internal/chafa-color-hash.h" #include "internal/chafa-dither.h" #include "internal/chafa-indexed-image.h" #include "internal/chafa-iterm2-canvas.h" #include "internal/chafa-kitty-canvas.h" #include "internal/chafa-palette.h" #include "internal/chafa-sixel-canvas.h" G_BEGIN_DECLS /* Character symbols and symbol classes */ #define CHAFA_N_SYMBOLS_MAX 1024 /* For static temp arrays */ #define CHAFA_SYMBOL_N_PIXELS (CHAFA_SYMBOL_WIDTH_PIXELS * CHAFA_SYMBOL_HEIGHT_PIXELS) typedef struct { ChafaSymbolTags sc; gunichar c; gchar *coverage; gint fg_weight, bg_weight; guint64 bitmap; gint popcount; } ChafaSymbol; /* Double-width symbol */ typedef struct { ChafaSymbol sym [2]; } ChafaSymbol2; struct ChafaSymbolMap { gint refs; guint need_rebuild : 1; guint use_builtin_glyphs : 1; GHashTable *glyphs; GHashTable *glyphs2; /* Wide glyphs with left/right bitmaps */ GArray *selectors; /* Remaining fields are populated by chafa_symbol_map_prepare () */ /* Narrow symbols */ ChafaSymbol *symbols; gint n_symbols; guint64 *packed_bitmaps; /* Wide symbols */ ChafaSymbol2 *symbols2; gint n_symbols2; guint64 *packed_bitmaps2; }; /* Symbol selection candidate */ typedef struct { gint16 symbol_index; guint8 hamming_distance; guint8 is_inverted; } ChafaCandidate; /* Canvas config */ struct ChafaCanvasConfig { gint refs; gint width, height; gint cell_width, cell_height; ChafaCanvasMode canvas_mode; ChafaColorSpace color_space; ChafaDitherMode dither_mode; ChafaColorExtractor color_extractor; ChafaPixelMode pixel_mode; gint dither_grain_width, dither_grain_height; gfloat dither_intensity; guint32 fg_color_packed_rgb; guint32 bg_color_packed_rgb; gint alpha_threshold; /* 0-255. 255 = no alpha in output */ gfloat work_factor; ChafaSymbolMap symbol_map; ChafaSymbolMap fill_symbol_map; guint preprocessing_enabled : 1; guint fg_only_enabled : 1; ChafaOptimizations optimizations; }; /* Canvas */ typedef struct ChafaCanvasCell ChafaCanvasCell; /* Library functions */ extern ChafaSymbol *chafa_symbols; extern ChafaSymbol2 *chafa_symbols2; void chafa_init_palette (void); void chafa_init_symbols (void); ChafaSymbolTags chafa_get_tags_for_char (gunichar c); void chafa_init (void); gboolean chafa_have_mmx (void) G_GNUC_PURE; gboolean chafa_have_sse41 (void) G_GNUC_PURE; gboolean chafa_have_popcnt (void) G_GNUC_PURE; void chafa_symbol_map_init (ChafaSymbolMap *symbol_map); void chafa_symbol_map_deinit (ChafaSymbolMap *symbol_map); void chafa_symbol_map_copy_contents (ChafaSymbolMap *dest, const ChafaSymbolMap *src); void chafa_symbol_map_prepare (ChafaSymbolMap *symbol_map); gboolean chafa_symbol_map_has_symbol (const ChafaSymbolMap *symbol_map, gunichar symbol); void chafa_symbol_map_find_candidates (const ChafaSymbolMap *symbol_map, guint64 bitmap, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout); void chafa_symbol_map_find_fill_candidates (const ChafaSymbolMap *symbol_map, gint popcount, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout); void chafa_symbol_map_find_wide_candidates (const ChafaSymbolMap *symbol_map, const guint64 *bitmaps, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout); void chafa_symbol_map_find_wide_fill_candidates (const ChafaSymbolMap *symbol_map, gint popcount, gboolean do_inverse, ChafaCandidate *candidates_out, gint *n_candidates_inout); void chafa_canvas_config_init (ChafaCanvasConfig *canvas_config); void chafa_canvas_config_deinit (ChafaCanvasConfig *canvas_config); void chafa_canvas_config_copy_contents (ChafaCanvasConfig *dest, const ChafaCanvasConfig *src); gint *chafa_gen_bayer_matrix (gint matrix_size, gdouble magnitude); /* Math stuff */ #ifdef HAVE_MMX_INTRINSICS void calc_colors_mmx (const ChafaPixel *pixels, ChafaColorAccum *accums_out, const guint8 *cov); void chafa_leave_mmx (void); #else # define chafa_leave_mmx() #endif #ifdef HAVE_SSE41_INTRINSICS gint calc_error_sse41 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint8 *cov) G_GNUC_PURE; #endif #if defined(HAVE_POPCNT64_INTRINSICS) || defined(HAVE_POPCNT32_INTRINSICS) #define HAVE_POPCNT_INTRINSICS #endif #ifdef HAVE_POPCNT_INTRINSICS gint chafa_pop_count_u64_builtin (guint64 v) G_GNUC_PURE; void chafa_pop_count_vu64_builtin (const guint64 *vv, gint *vc, gint n); void chafa_hamming_distance_vu64_builtin (guint64 a, const guint64 *vb, gint *vc, gint n); void chafa_hamming_distance_2_vu64_builtin (const guint64 *a, const guint64 *vb, gint *vc, gint n); #endif /* Inline functions */ static inline guint64 chafa_slow_pop_count (guint64 v) G_GNUC_UNUSED; static inline gint chafa_population_count_u64 (guint64 v) G_GNUC_UNUSED; static inline void chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) G_GNUC_UNUSED; static inline guint64 chafa_slow_pop_count (guint64 v) { /* Generic population count from * http://www.graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel * * Peter Kankowski has more hacks, including better SIMD versions, at * https://www.strchr.com/crc32_popcnt */ v = v - ((v >> 1) & (guint64) ~(guint64) 0 / 3); v = (v & (guint64) ~(guint64) 0 / 15 * 3) + ((v >> 2) & (guint64) ~(guint64) 0 / 15 * 3); v = (v + (v >> 4)) & (guint64) ~(guint64) 0 / 255 * 15; return (guint64) (v * ((guint64) ~(guint64) 0 / 255)) >> (sizeof (guint64) - 1) * 8; } static inline gint chafa_population_count_u64 (guint64 v) { #ifdef HAVE_POPCNT_INTRINSICS if (chafa_have_popcnt ()) return chafa_pop_count_u64_builtin (v); #endif return chafa_slow_pop_count (v); } static inline void chafa_population_count_vu64 (const guint64 *vv, gint *vc, gint n) { #ifdef HAVE_POPCNT_INTRINSICS if (chafa_have_popcnt ()) { chafa_pop_count_vu64_builtin (vv, vc, n); return; } #endif while (n--) *(vc++) = chafa_slow_pop_count (*(vv++)); } static inline void chafa_hamming_distance_vu64 (guint64 a, const guint64 *vb, gint *vc, gint n) { #ifdef HAVE_POPCNT_INTRINSICS if (chafa_have_popcnt ()) { chafa_hamming_distance_vu64_builtin (a, vb, vc, n); return; } #endif while (n--) *(vc++) = chafa_slow_pop_count (a ^ *(vb++)); } static inline void chafa_hamming_distance_2_vu64 (const guint64 *a, const guint64 *vb, gint *vc, gint n) { #ifdef HAVE_POPCNT_INTRINSICS if (chafa_have_popcnt ()) { chafa_hamming_distance_2_vu64_builtin (a, vb, vc, n); return; } #endif while (n--) { *(vc++) = chafa_slow_pop_count (a [0] ^ vb [0]) + chafa_slow_pop_count (a [1] ^ vb [1]); vb += 2; } } G_END_DECLS #endif /* __CHAFA_PRIVATE_H__ */ chafa-1.8.0/chafa/internal/chafa-sixel-canvas.c000066400000000000000000000311551411352071600212460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "chafa.h" #include "smolscale/smolscale.h" #include "internal/chafa-batch.h" #include "internal/chafa-bitfield.h" #include "internal/chafa-indexed-image.h" #include "internal/chafa-sixel-canvas.h" #include "internal/chafa-string-util.h" #define SIXEL_CELL_HEIGHT 6 typedef struct { ChafaSixelCanvas *sixel_canvas; GString *out_str; } BuildSixelsCtx; typedef struct { /* Lower six bytes are vertical pixel strip; LSB is bottom pixel */ guint64 d; } SixelData; typedef struct { SixelData *data; ChafaBitfield filter_bits; } SixelRow; static gint round_up_to_multiple_of (gint value, gint m) { value = value + m - 1; return value - (value % m); } ChafaSixelCanvas * chafa_sixel_canvas_new (gint width, gint height, ChafaColorSpace color_space, const ChafaPalette *palette, const ChafaDither *dither) { ChafaSixelCanvas *sixel_canvas; sixel_canvas = g_new (ChafaSixelCanvas, 1); sixel_canvas->width = width; sixel_canvas->height = height; sixel_canvas->color_space = color_space; sixel_canvas->image = chafa_indexed_image_new (width, round_up_to_multiple_of (height, SIXEL_CELL_HEIGHT), palette, dither); return sixel_canvas; } void chafa_sixel_canvas_destroy (ChafaSixelCanvas *sixel_canvas) { chafa_indexed_image_destroy (sixel_canvas->image); g_free (sixel_canvas); } void chafa_sixel_canvas_draw_all_pixels (ChafaSixelCanvas *sixel_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride) { g_return_if_fail (sixel_canvas != NULL); g_return_if_fail (src_pixel_type < CHAFA_PIXEL_MAX); g_return_if_fail (src_pixels != NULL); g_return_if_fail (src_width >= 0); g_return_if_fail (src_height >= 0); if (src_width == 0 || src_height == 0) return; chafa_indexed_image_draw_pixels (sixel_canvas->image, sixel_canvas->color_space, src_pixel_type, src_pixels, src_width, src_height, src_rowstride, sixel_canvas->width, sixel_canvas->height); } #define FILTER_BANK_WIDTH 64 static void filter_set (SixelRow *srow, guint8 pen, gint bank) { chafa_bitfield_set_bit (&srow->filter_bits, bank * 256 + (gint) pen, TRUE); } static gboolean filter_get (const SixelRow *srow, guint8 pen, gint bank) { return chafa_bitfield_get_bit (&srow->filter_bits, bank * 256 + (gint) pen); } static void fetch_sixel_row (SixelRow *srow, const guint8 *pixels, gint width) { const guint8 *pixels_end, *p; SixelData *sdata = srow->data; gint x; /* The ordering of output bytes is 351240; this is the inverse of * 140325. see sixel_data_do_schar(). */ for (pixels_end = pixels + width, x = 0; pixels < pixels_end; pixels++, x++) { guint64 d; gint bank = x / FILTER_BANK_WIDTH; p = pixels; filter_set (srow, *p, bank); d = (guint64) *p; p += width; filter_set (srow, *p, bank); d |= (guint64) *p << (3 * 8); p += width; filter_set (srow, *p, bank); d |= (guint64) *p << (2 * 8); p += width; filter_set (srow, *p, bank); d |= (guint64) *p << (5 * 8); p += width; filter_set (srow, *p, bank); d |= (guint64) *p << (1 * 8); p += width; filter_set (srow, *p, bank); d |= (guint64) *p << (4 * 8); (sdata++)->d = d; } } static gchar sixel_data_to_schar (const SixelData *sdata, guint64 expanded_pen) { guint64 a; gchar c; a = ~(sdata->d ^ expanded_pen); /* Matching bytes will now contain 0xff. Any other value is a mismatch. */ a &= (a & 0x0000f0f0f0f0f0f0) >> 4; a &= (a & 0x00000c0c0c0c0c0c) >> 2; a &= (a & 0x0000020202020202) >> 1; /* Matching bytes will now contain 0x01. Misses contain 0x00. */ a |= a >> (24 - 1); a |= a >> (16 - 2); a |= a >> (8 - 4); /* Set bits are now packed in the lower 6 bits, reordered like this: * * 012345 -> 03/14/25 -> 14/0325 -> 140325 */ c = a & 0x3f; return '?' + c; } static gchar * format_schar_reps (gchar rep_schar, gint n_reps, gchar *p) { g_assert (n_reps > 0); for (;;) { if (n_reps < 4) { do *(p++) = rep_schar; while (--n_reps); goto out; } else if (n_reps < 255) { *(p++) = '!'; p = chafa_format_dec_u8 (p, n_reps); *(p++) = rep_schar; goto out; } else { strcpy (p, "!255"); p += 4; *(p++) = rep_schar; n_reps -= 255; if (n_reps == 0) goto out; } } out: return p; } static gchar * format_pen (guint8 pen, gchar *p) { *(p++) = '#'; return chafa_format_dec_u8 (p, pen); } /* force_full_width is a workaround for a bug in mlterm; we need to * draw the entire first row even if the rightmost pixels are transparent, * otherwise the first row with non-transparent pixels will have * garbage rendered in it */ static gchar * build_sixel_row_ansi (const ChafaSixelCanvas *scanvas, const SixelRow *srow, gchar *p, gboolean force_full_width) { gint pen = 0; gboolean need_cr = FALSE; gboolean need_cr_next = FALSE; const SixelData *sdata = srow->data; gint width = scanvas->width; do { guint64 expanded_pen; gboolean need_pen = TRUE; gchar rep_schar; gint n_reps; gint i; if (pen == chafa_palette_get_transparent_index (&scanvas->image->palette)) continue; /* Assign pen value to each of lower six bytes */ expanded_pen = pen; expanded_pen |= expanded_pen << 8; expanded_pen |= expanded_pen << 16; expanded_pen |= expanded_pen << 16; rep_schar = 0; n_reps = 0; for (i = 0; i < width; ) { gint step = MIN (FILTER_BANK_WIDTH, width - i); gchar schar; /* Skip over FILTER_BANK_WIDTH sixels at once if possible */ if (!filter_get (srow, pen, i / FILTER_BANK_WIDTH)) { if (rep_schar != '?' && rep_schar != 0) { if (need_cr) { *(p++) = '$'; need_cr = FALSE; } if (need_pen) { p = format_pen (pen, p); need_pen = FALSE; } p = format_schar_reps (rep_schar, n_reps, p); need_cr_next = TRUE; n_reps = 0; } rep_schar = '?'; n_reps += step; i += step; continue; } /* The pen appears in this bank; iterate over sixels */ for ( ; step > 0; step--, i++) { schar = sixel_data_to_schar (&sdata [i], expanded_pen); if (schar == rep_schar) { n_reps++; } else if (rep_schar == 0) { rep_schar = schar; n_reps = 1; } else { if (need_cr) { *(p++) = '$'; need_cr = FALSE; } if (need_pen) { p = format_pen (pen, p); need_pen = FALSE; } p = format_schar_reps (rep_schar, n_reps, p); need_cr_next = TRUE; rep_schar = schar; n_reps = 1; } } } if (rep_schar != '?' || force_full_width) { if (need_cr) { *(p++) = '$'; need_cr = FALSE; } if (need_pen) { p = format_pen (pen, p); need_pen = FALSE; } p = format_schar_reps (rep_schar, n_reps, p); need_cr_next = TRUE; /* Only need to do this for a single pen */ force_full_width = FALSE; } need_cr = need_cr_next; } while (++pen < chafa_palette_get_n_colors (&scanvas->image->palette)); *(p++) = '-'; return p; } static void build_sixel_row_worker (ChafaBatchInfo *batch, const BuildSixelsCtx *ctx) { SixelRow srow; gchar *sixel_ansi, *p; gint n_sixel_rows; gint i; n_sixel_rows = (batch->n_rows + SIXEL_CELL_HEIGHT - 1) / SIXEL_CELL_HEIGHT; srow.data = g_alloca (sizeof (SixelData) * ctx->sixel_canvas->width); chafa_bitfield_init (&srow.filter_bits, ((ctx->sixel_canvas->width + FILTER_BANK_WIDTH - 1) / FILTER_BANK_WIDTH) * 256); sixel_ansi = p = g_malloc (256 * (ctx->sixel_canvas->width + 5) * n_sixel_rows + 1); for (i = 0; i < n_sixel_rows; i++) { fetch_sixel_row (&srow, ctx->sixel_canvas->image->pixels + ctx->sixel_canvas->image->width * (batch->first_row + i * SIXEL_CELL_HEIGHT), ctx->sixel_canvas->image->width); p = build_sixel_row_ansi (ctx->sixel_canvas, &srow, p, (i == 0) || (i == n_sixel_rows - 1) ? TRUE : FALSE); chafa_bitfield_clear (&srow.filter_bits); } batch->ret_p = sixel_ansi; batch->ret_n = p - sixel_ansi; chafa_bitfield_deinit (&srow.filter_bits); } static void build_sixel_row_post (ChafaBatchInfo *batch, BuildSixelsCtx *ctx) { g_string_append_len (ctx->out_str, batch->ret_p, batch->ret_n); g_free (batch->ret_p); } static void build_sixel_palette (ChafaSixelCanvas *sixel_canvas, GString *out_str) { gchar str [256 * 20 + 1]; gchar *p = str; gint first_color; gint pen; first_color = chafa_palette_get_first_color (&sixel_canvas->image->palette); for (pen = 0; pen < chafa_palette_get_n_colors (&sixel_canvas->image->palette); pen++) { const ChafaColor *col; if (pen == chafa_palette_get_transparent_index (&sixel_canvas->image->palette)) continue; col = chafa_palette_get_color (&sixel_canvas->image->palette, CHAFA_COLOR_SPACE_RGB, first_color + pen); *(p++) = '#'; p = chafa_format_dec_u8 (p, pen); *(p++) = ';'; *(p++) = '2'; /* Color space: RGB */ *(p++) = ';'; /* Sixel color channel range is 0..100 */ p = chafa_format_dec_u8 (p, (col->ch [0] * 100) / 255); *(p++) = ';'; p = chafa_format_dec_u8 (p, (col->ch [1] * 100) / 255); *(p++) = ';'; p = chafa_format_dec_u8 (p, (col->ch [2] * 100) / 255); } g_string_append_len (out_str, str, p - str); } void chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, GString *out_str) { BuildSixelsCtx ctx; g_assert (sixel_canvas->image->height % SIXEL_CELL_HEIGHT == 0); ctx.sixel_canvas = sixel_canvas; ctx.out_str = out_str; build_sixel_palette (sixel_canvas, out_str); chafa_process_batches (&ctx, (GFunc) build_sixel_row_worker, (GFunc) build_sixel_row_post, sixel_canvas->image->height, g_get_num_processors (), SIXEL_CELL_HEIGHT); } chafa-1.8.0/chafa/internal/chafa-sixel-canvas.h000066400000000000000000000034501411352071600212500ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_SIXEL_CANVAS_H__ #define __CHAFA_SIXEL_CANVAS_H__ #include "chafa.h" G_BEGIN_DECLS typedef struct { gint width, height; ChafaColorSpace color_space; ChafaIndexedImage *image; } ChafaSixelCanvas; ChafaSixelCanvas *chafa_sixel_canvas_new (gint width, gint height, ChafaColorSpace color_space, const ChafaPalette *palette, const ChafaDither *dither); void chafa_sixel_canvas_destroy (ChafaSixelCanvas *sixel_canvas); void chafa_sixel_canvas_draw_all_pixels (ChafaSixelCanvas *sixel_canvas, ChafaPixelType src_pixel_type, gconstpointer src_pixels, gint src_width, gint src_height, gint src_rowstride); void chafa_sixel_canvas_build_ansi (ChafaSixelCanvas *sixel_canvas, GString *out_str); G_END_DECLS #endif /* __CHAFA_SIXEL_CANVAS_H__ */ chafa-1.8.0/chafa/internal/chafa-sse41.c000066400000000000000000000030671411352071600176110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include "chafa.h" #include "internal/chafa-private.h" gint calc_error_sse41 (const ChafaPixel *pixels, const ChafaColorPair *color_pair, const guint8 *cov) { const guint32 *u32p0 = (const guint32 *) pixels; const guint32 *u32p1 = (const guint32 *) color_pair->colors; __m128i err4 = { 0 }; const gint32 *e = (gint32 *) &err4; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { __m128i t0, t1, t; t0 = _mm_cvtepu8_epi32 (_mm_cvtsi32_si128 (u32p0 [i])); t1 = _mm_cvtepu8_epi32 (_mm_cvtsi32_si128 (u32p1 [cov [i]])); t = t0 - t1; t = _mm_mullo_epi32 (t, t); err4 += t; } return e [0] + e [1] + e [2]; } chafa-1.8.0/chafa/internal/chafa-string-util.c000066400000000000000000000107211411352071600211260ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include "internal/chafa-string-util.h" /* Generate a const table of the ASCII decimal numbers 0..255, avoiding leading * zeroes. Each entry is exactly 4 bytes. The strings are not zero-terminated; * instead their lengths are stored in the 4th byte, potentially leaving a gap * between the string and the length. * * This allows us to fetch a string using fixed-length memcpy() followed by * incrementing the target pointer. We copy all four bytes (32 bits) in the * hope that the compiler will generate register-wide loads and stores where * alignment is not an issue. * * The idea is to speed up printing for decimal numbers in this range (common * with palette indexes and color channels) at the cost of exactly 1kiB in the * executable. * * We require C99 due to __VA_ARGS__ and designated init but use no extensions. */ #define GEN_1(len, ...) { __VA_ARGS__, [3] = len } #define GEN_10(len, ...) \ GEN_1 (len, __VA_ARGS__, '0'), GEN_1 (len, __VA_ARGS__, '1'), \ GEN_1 (len, __VA_ARGS__, '2'), GEN_1 (len, __VA_ARGS__, '3'), \ GEN_1 (len, __VA_ARGS__, '4'), GEN_1 (len, __VA_ARGS__, '5'), \ GEN_1 (len, __VA_ARGS__, '6'), GEN_1 (len, __VA_ARGS__, '7'), \ GEN_1 (len, __VA_ARGS__, '8'), GEN_1 (len, __VA_ARGS__, '9') #define GEN_100(len, ...) \ GEN_10 (len, __VA_ARGS__, '0'), GEN_10 (len, __VA_ARGS__, '1'), \ GEN_10 (len, __VA_ARGS__, '2'), GEN_10 (len, __VA_ARGS__, '3'), \ GEN_10 (len, __VA_ARGS__, '4'), GEN_10 (len, __VA_ARGS__, '5'), \ GEN_10 (len, __VA_ARGS__, '6'), GEN_10 (len, __VA_ARGS__, '7'), \ GEN_10 (len, __VA_ARGS__, '8'), GEN_10 (len, __VA_ARGS__, '9') const char chafa_ascii_dec_u8 [256] [4] = { /* 0-9 */ GEN_1 (1, '0'), GEN_1 (1, '1'), GEN_1 (1, '2'), GEN_1 (1, '3'), GEN_1 (1, '4'), GEN_1 (1, '5'), GEN_1 (1, '6'), GEN_1 (1, '7'), GEN_1 (1, '8'), GEN_1 (1, '9'), /* 10-99 */ GEN_10 (2, '1'), GEN_10 (2, '2'), GEN_10 (2, '3'), GEN_10 (2, '4'), GEN_10 (2, '5'), GEN_10 (2, '6'), GEN_10 (2, '7'), GEN_10 (2, '8'), GEN_10 (2, '9'), /* 100-199 */ GEN_100 (3, '1'), /* 200-249 */ GEN_10 (3, '2', '0'), GEN_10 (3, '2', '1'), GEN_10 (3, '2', '2'), GEN_10 (3, '2', '3'), GEN_10 (3, '2', '4'), /* 250-255 */ GEN_1 (3, '2', '5', '0'), GEN_1 (3, '2', '5', '1'), GEN_1 (3, '2', '5', '2'), GEN_1 (3, '2', '5', '3'), GEN_1 (3, '2', '5', '4'), GEN_1 (3, '2', '5', '5') }; /* We need this because reg may contain garbage that will end up being * harmlessly dumped past end-of-output. Avoiding initialization saves * us approx. 3%, enough to matter. */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" gchar * chafa_format_dec_uint_0_to_9999 (char *dest, guint arg) { guint n, m; guint32 reg; gint i = 0; m = arg < 9999 ? arg : 9999; /* Reduce argument one decimal digit at a time and shift their * ASCII equivalents into a register. The register can usually be * written to memory all at once. memcpy() will do that if possible * while keeping us safe from potential alignment issues. * * We take advantage of the fact that registers are backwards on * x86 to reverse the result. GUINT32_TO_LE() will be a no-op there. * On BE archs, it will manually reverse using a bswap. * * With -O2 -fno-inline, this is approx. 15 times faster than sprintf() * in my tests. */ do { n = (m * (((1 << 15) + 9) / 10)) >> 15; reg <<= 8; reg |= '0' + (m - n * 10); m = n; i++; } while (m != 0); reg = GUINT32_TO_LE (reg); memcpy (dest, ®, 4); return dest + i; } #pragma GCC diagnostic pop chafa-1.8.0/chafa/internal/chafa-string-util.h000066400000000000000000000030651411352071600211360ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2020-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_STRING_UTIL_H__ #define __CHAFA_STRING_UTIL_H__ #include #include G_BEGIN_DECLS extern const char chafa_ascii_dec_u8 [256] [4]; /* Will overwrite 4 bytes starting at dest. Returns a pointer to the first * byte after the formatted ASCII decimal number (dest + 1..3). */ static inline gchar * chafa_format_dec_u8 (gchar *dest, guint8 n) { memcpy (dest, &chafa_ascii_dec_u8 [n] [0], 4); return dest + chafa_ascii_dec_u8 [n] [3]; } /* Will overwrite 4 bytes starting at dest. Returns a pointer to the first * byte after the formatted ASCII decimal number (dest + 1..4). */ gchar *chafa_format_dec_uint_0_to_9999 (char *dest, guint arg); G_END_DECLS #endif /* __CHAFA_STRING_UTIL_H__ */ chafa-1.8.0/chafa/internal/chafa-symbols-ascii.h000066400000000000000000000722131411352071600214340ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ /* This is meant to be #included in the symbol definition table of * chafa-symbols.c. It's kept in a separate file due to its size. * * The symbol bitmaps are derived from https://github.com/dhepper/font8x8 by * Daniel Hepper . Excerpt from the accompanying README: * * 8x8 monochrome bitmap font for rendering * ======================================== * * A collection of header files containing a 8x8 bitmap font. * * [...] * * Author: Daniel Hepper * License: Public Domain * * Credits * ======= * * These header files are directly derived from an assembler file fetched from: * http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm * * Original header: * * ; Summary: font8_8.asm * ; 8x8 monochrome bitmap fonts for rendering * ; * ; Author: * ; Marcel Sondaar * ; International Business Machines (public domain VGA fonts) * ; * ; License: * ; Public Domain */ { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_SPACE, ' ', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '!', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXX " " XXXX " " XX " " XX " " " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '"', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX " " XX XX " " " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '#', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX " " XX XX " "XXXXXXX " " XX XX " "XXXXXXX " " XX XX " " XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '$', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXXX " "XX " " XXXX " " XX " "XXXXX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '%', CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XX XX " "XX XX " " XX " " XX " " XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '&', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX XX " " XXX " " XXX XX " "XX XXX " "XX XX " " XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, 0x27, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " "XX " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '(', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, ')', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '*', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX XX " " XXXX " "XXXXXXXX" " XXXX " " XX XX " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '+', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX " "XXXXXX " " XX " " XX " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, ',', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_ASCII, '-', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXX " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '.', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '/', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XX " "XX " "X " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '0', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXX " "XX XX " "XX XXX " "XX XXXX " "XXXX XX " "XXX XX " " XXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '1', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXX " " XX " " XX " " XX " " XX " "XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '2', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " " XX " " XXX " " XX " "XX XX " "XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '3', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " " XX " " XXX " " XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '4', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XXXX " " XX XX " "XX XX " "XXXXXXX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '5', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " "XX " "XXXXX " " XX " " XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '6', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX " "XX " "XXXXX " "XX XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '7', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " "XX XX " " XX " " XX " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '8', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " "XX XX " " XXXX " "XX XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_DIGIT, '9', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " "XX XX " " XXXXX " " XX " " XX " " XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, ':', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX " " " " " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, ';', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XX " " " " " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_ASCII, '<', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XX " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '=', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXX " " " " " "XXXXXX " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '>', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '?', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " " XX " " XX " " XX " " " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '@', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXX " "XX XX " "XX XXXX " "XX XXXX " "XX XXXX " "XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'A', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XXXX " "XX XX " "XX XX " "XXXXXX " "XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'B', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " " XX XX " " XX XX " " XXXXX " " XX XX " " XX XX " "XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'C', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX XX " "XX " "XX " "XX " " XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'D', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXX " " XX XX " " XX XX " " XX XX " " XX XX " " XX XX " "XXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'E', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " " XX X " " XX X " " XXXX " " XX X " " XX X " "XXXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'F', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " " XX X " " XX X " " XXXX " " XX X " " XX " "XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'G', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX XX " "XX " "XX " "XX XXX " " XX XX " " XXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'H', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " "XX XX " "XXXXXX " "XX XX " "XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'I', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " XX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'J', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " XX " " XX " "XX XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'K', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX XX " " XX XX " " XX XX " " XXXX " " XX XX " " XX XX " "XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'L', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " " XX " " XX " " XX " " XX X " " XX XX " "XXXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'M', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XXX XXX " "XXXXXXX " "XXXXXXX " "XX X XX " "XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'N', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XXX XX " "XXXX XX " "XX XXXX " "XX XXX " "XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'O', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX XX " "XX XX " "XX XX " "XX XX " " XX XX " " XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'P', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " " XX XX " " XX XX " " XXXXX " " XX " " XX " "XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'Q', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " "XX XX " "XX XX " "XX XXX " " XXXX " " XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'R', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " " XX XX " " XX XX " " XXXXX " " XX XX " " XX XX " "XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'S', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " "XX XX " "XXX " " XXX " " XXX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'T', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " "X XX X " " XX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'U', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " "XX XX " "XX XX " "XX XX " "XX XX " "XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'V', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " "XX XX " "XX XX " "XX XX " " XXXX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'W', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " "XX XX " "XX X XX " "XXXXXXX " "XXX XXX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'X', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " " XX XX " " XXX " " XXX " " XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'Y', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " "XX XX " " XXXX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'Z', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " "XX XX " "X XX " " XX " " XX X " " XX XX " "XXXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '[', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " XX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '\\', CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " " XX " " XX " " XX " " XX " " XX " " X " " ") }, { CHAFA_SYMBOL_TAG_ASCII, ']', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX " " XX " " XX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '^', CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXX " " XX XX " "XX XX " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '_', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " "XXXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '`', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'a', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " " XX " " XXXXX " "XX XX " " XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'b', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX " " XX " " XX " " XXXXX " " XX XX " " XX XX " "XX XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'c', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " "XX XX " "XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'd', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX " " XX " " XXXXX " "XX XX " "XX XX " " XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'e', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " "XX XX " "XXXXXX " "XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'f', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX XX " " XX " "XXXX " " XX " " XX " "XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'g', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXX XX " "XX XX " "XX XX " " XXXXX " " XX " "XXXXX ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'h', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX " " XX " " XX XX " " XXX XX " " XX XX " " XX XX " "XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'i', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " XXX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'j', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " XX " " XX " " XX " "XX XX " "XX XX " " XXXX ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'k', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX " " XX " " XX XX " " XX XX " " XXXX " " XX XX " "XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'l', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX " " XX " " XX " " XX " " XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'm', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " "XXXXXXX " "XXXXXXX " "XX X XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'n', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXX " "XX XX " "XX XX " "XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'o', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " "XX XX " "XX XX " "XX XX " " XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'p', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XXX " " XX XX " " XX XX " " XXXXX " " XX " "XXXX ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'q', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXX XX " "XX XX " "XX XX " " XXXXX " " XX " " XXXX ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'r', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XXX " " XXX XX " " XX XX " " XX " "XXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 's', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXXX " "XX " " XXXX " " XX " "XXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 't', CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XX " " XXXXX " " XX " " XX " " XX X " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'u', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " "XX XX " "XX XX " "XX XX " " XXX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'v', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " "XX XX " "XX XX " " XXXX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'w', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " "XX X XX " "XXXXXXX " "XXXXXXX " " XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'x', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " " XX XX " " XXX " " XX XX " "XX XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'y', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XX XX " "XX XX " "XX XX " " XXXXX " " XX " "XXXXX ") }, { CHAFA_SYMBOL_TAG_ASCII | CHAFA_SYMBOL_TAG_ALPHA, 'z', CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXX " "X XX " " XX " " XX X " "XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '{', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX " " XX " " XX " "XXX " " XX " " XX " " XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '|', CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " " " XX " " XX " " XX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '}', CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX " " XX " " XX " " XXX " " XX " " XX " "XXX " " ") }, { CHAFA_SYMBOL_TAG_ASCII, '~', CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX XX " "XX XXX " " " " " " " " " " " " ") }, chafa-1.8.0/chafa/internal/chafa-symbols-block.h000066400000000000000000002005521411352071600214350ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ /* Block and border characters * --------------------------- * * This is meant to be #included in the symbol definition table of * chafa-symbols.c. It's kept in a separate file due to its size. */ { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF | CHAFA_SYMBOL_TAG_INVERTED, 0x2580, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2581, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " " " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2582, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2583, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_VHALF, 0x2584, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2585, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2586, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2587, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { /* Full block */ CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_SOLID | CHAFA_SYMBOL_TAG_SEXTANT, 0x2588, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x2589, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " "XXXXXXX " "XXXXXXX " "XXXXXXX " "XXXXXXX " "XXXXXXX " "XXXXXXX " "XXXXXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x258a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXX " "XXXXXX " "XXXXXX " "XXXXXX " "XXXXXX " "XXXXXX " "XXXXXX " "XXXXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x258b, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXX " "XXXXX " "XXXXX " "XXXXX " "XXXXX " "XXXXX " "XXXXX " "XXXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF | CHAFA_SYMBOL_TAG_SEXTANT, 0x258c, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXXX " "XXXX " "XXXX " "XXXX " "XXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x258d, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXX " "XXX " "XXX " "XXX " "XXX " "XXX " "XXX " "XXX ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x258e, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " "XX " "XX " "XX " "XX " "XX " "XX " "XX ") }, { CHAFA_SYMBOL_TAG_BLOCK, 0x258f, CHAFA_SYMBOL_OUTLINE_8X8 ( "X " "X " "X " "X " "X " "X " "X " "X ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_HHALF | CHAFA_SYMBOL_TAG_INVERTED | CHAFA_SYMBOL_TAG_SEXTANT, 0x2590, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXXX" " XXXX" " XXXX" " XXXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, 0x2594, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " " " " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_INVERTED, 0x2595, CHAFA_SYMBOL_OUTLINE_8X8 ( " X" " X" " X" " X" " X" " X" " X" " X") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, 0x2596, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXX " "XXXX " "XXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, 0x2597, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XXXX" " XXXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, 0x2598, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXXX " "XXXX " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, 0x2599, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXXX " "XXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, 0x259a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXXX " "XXXX " " XXXX" " XXXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, 0x259b, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXX " "XXXX " "XXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, 0x259c, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXX" " XXXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD, 0x259d, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXXX" " XXXX" " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, 0x259e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXXX" " XXXX" "XXXX " "XXXX " "XXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_QUAD | CHAFA_SYMBOL_TAG_INVERTED, 0x259f, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXXX" " XXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, /* Begin box drawing characters */ { CHAFA_SYMBOL_TAG_BORDER, 0x2500, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2501, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2502, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2503, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2504, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XX XX XX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2505, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XX XX XX" "XX XX XX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2506, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " " " X " " X " " " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2507, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " " " XX " " XX " " " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2508, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "X X X X " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2509, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "X X X X " "X X X X " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x250a, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " " " X " " " " X " " " " X " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x250b, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " " " XX " " " " XX " " " " XX " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x250c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x250d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXX" " XXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x250e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x250f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX" " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2510, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2511, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXX " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2512, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2513, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXX " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2514, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " XXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2515, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXX" " XXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2516, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2517, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" " XXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2518, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2519, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251b, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251c, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " XXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251d, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXX" " XXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251e, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x251f, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2520, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2521, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" " XXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2522, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXXX" " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2523, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" " XXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2524, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2525, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2526, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2527, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2528, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2529, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXX " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252a, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252b, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXX " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x252f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2530, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2531, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXX " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2532, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2533, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2534, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2535, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2536, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2537, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXXXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2538, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2539, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253b, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXXXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253c, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253d, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253e, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x253f, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXXXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2540, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2541, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2542, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2543, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2544, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2545, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXX " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2546, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " XXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2547, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXXXXX" "XXXXXXXX" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2548, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "XXXXXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x2549, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXX " "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x254a, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x254b, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " "XXXXXXXX" "XXXXXXXX" " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x254c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXX XXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x254d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXX XXX" "XXX XXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x254e, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " " " " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x254f, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " " " " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, 0x2571, CHAFA_SYMBOL_OUTLINE_8X8 ( " X" " X " " X " " X " " X " " X " " X " "X ") }, { /* Variant */ CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, 0x2571, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX" " XXX" " XXX " " XXX " " XXX " " XXX " "XXX " "XX ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, 0x2572, CHAFA_SYMBOL_OUTLINE_8X8 ( "X " " X " " X " " X " " X " " X " " X " " X") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, 0x2572, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX " "XXX " " XXX " " XXX " " XXX " " XXX " " XXX" " XX") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DIAGONAL, 0x2573, CHAFA_SYMBOL_OUTLINE_8X8 ( "X X" " X X " " X X " " XX " " XX " " X X " " X X " "X X") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2574, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2575, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2576, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2577, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2578, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXX " "XXXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x2579, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x257a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXX" " XXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER | CHAFA_SYMBOL_TAG_DOT, 0x257b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x257c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXXX" "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x257d, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " XX " " XX " " XX " " XX ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x257e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXX " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_BORDER, 0x257f, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " XX " " XX " " XX " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_STIPPLE, 0x2591, CHAFA_SYMBOL_OUTLINE_8X8 ( "X X " " X X " "X X " " X X " "X X " " X X " "X X " " X X ") }, { CHAFA_SYMBOL_TAG_STIPPLE, 0x2592, CHAFA_SYMBOL_OUTLINE_8X8 ( "X X X X " " X X X X" "X X X X " " X X X X" "X X X X " " X X X X" "X X X X " " X X X X") }, { CHAFA_SYMBOL_TAG_STIPPLE, 0x2593, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXX XXX" "XX XXX X" " XXX XXX" "XX XXX X" " XXX XXX" "XX XXX X" " XXX XXX" "XX XXX X") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb3c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " "X " "XXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb3d, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " "XX " "XXXXX " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb3e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "X " "XX " "XX " "XXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb3f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XX " "XXX " "XXXXX " "XXXXXXX " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb40, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X " "X " "XX " "XX " "XXX " "XXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb41, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXXX" " XXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb42, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXX" " XXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb43, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXXX" " XXXXX" " XXXXXX" " XXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb44, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X" " XX" " XXXX" " XXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb45, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXXXX" " XXXXX" " XXXXXX" " XXXXXX" " XXXXXXX" " XXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb46, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XXX" " XXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb47, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " X" " XXX" " XXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb48, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " XX" " XXXXX" " XXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb49, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X" " XX" " XX" " XXX" " XXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " X" " XXX" " XXXXX" " XXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4b, CHAFA_SYMBOL_OUTLINE_8X8 ( " X" " X" " XX" " XX" " XXX" " XXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4c, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXXXX " "XXXXXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4d, CHAFA_SYMBOL_OUTLINE_8X8 ( "X " "XXX " "XXXXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4e, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXXX " "XXXXXX " "XXXXXX " "XXXXXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb4f, CHAFA_SYMBOL_OUTLINE_8X8 ( "X " "XX " "XXXX " "XXXXXX " "XXXXXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb50, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXXXX " "XXXXX " "XXXXXX " "XXXXXX " "XXXXXXX " "XXXXXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb51, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXX " "XXXXX " "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb52, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXXXXX" " XXXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb53, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXXXX" " XXX" " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb54, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXXXXX" " XXXXXX" " XXXXXX" " XXXXX" " XXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb55, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXXXXX" " XXXXXX" " XXXX" " XXX" " X") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb56, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXXX" " XXXXXXX" " XXXXXX" " XXXXXX" " XXXXX" " XXXXX" " XXXX" " XXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb57, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXX " "X " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb58, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXX " "XXX " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb59, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXX " "XXX " "X " "X " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXX " "XXXXX " "XXX " "XX " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5b, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXX " "XXXX " "XXX " "XXX " "XX " "X " "X " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5c, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXX " "XXX " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5d, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXX " "XXXXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5e, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXX " "XXX " "X ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb5f, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXX " "XXXXXXX " "XXXXXX " "XXXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb60, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXX " "XXXXXX " "XXXX " "XXX " "X ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb61, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " "XXXXXXX " "XXXXXX " "XXXXXX " "XXXXX " "XXXXX " "XXXX " "XXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb62, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XX" " X" " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb63, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " XXXXX" " XX" " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb64, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXX" " XX" " XX" " X" " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb65, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXXXXX" " XXXXXX" " XXXX" " XXX" " X" " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb66, CHAFA_SYMBOL_OUTLINE_8X8 ( " XXXX" " XXXX" " XXX" " XXX" " XX" " XX" " X" " X") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb67, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" " XXXXX" " XXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb68, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " XXXXXXX" " XXXXXX" " XXXXX" " XXXX" " XXXXX" " XXXXXX" " XXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb69, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X X" "XX XX" "XXX XXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXX " "XXXXXX " "XXXXX " "XXXX " "XXXXX " "XXXXXX " "XXXXXXX ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6b, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXXXXXXX" "XXX XXX" "XX XX" "X X" " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6c, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "X " "XX " "XXX " "XXXX " "XXX " "XX " "X ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6d, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " XXXXXX " " XXXX " " XX " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " X" " XX" " XXX" " XXXX" " XXX" " XX" " X") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb6f, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XX " " XXXX " " XXXXXX " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb9a, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXX " " XXXXX " " XXX " " X " " X " " XXX " " XXXXX " " XXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_WEDGE, 0x1fb9b, CHAFA_SYMBOL_OUTLINE_8X8 ( " X" "X XX" "XX XXX" "XXX XXXX" "XXXX XXX" "XXX XX" "XX X" "X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb98, CHAFA_SYMBOL_OUTLINE_8X8 ( "X X X " " X X X" " X X " "X X X " " X X X" " X X " "X X X " " X X X") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb99, CHAFA_SYMBOL_OUTLINE_8X8 ( " X X X" "X X X " " X X " " X X X" "X X X " " X X " " X X X" "X X X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7c, CHAFA_SYMBOL_OUTLINE_8X8 ( "X " "X " "X " "X " "X " "X " "X " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7d, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" "X " "X " "X " "X " "X " "X " "X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7e, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " X" " X" " X" " X" " X" " X" " X") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7f, CHAFA_SYMBOL_OUTLINE_8X8 ( " X" " X" " X" " X" " X" " X" " X" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb80, CHAFA_SYMBOL_OUTLINE_8X8 ( "XXXXXXXX" " " " " " " " " " " " " "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb70, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb71, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb72, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb73, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb74, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb75, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X " " X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb76, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXXX" " " " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb77, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXXXX" " " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb78, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb79, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7a, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " "XXXXXXXX" " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb7b, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " "XXXXXXXX" " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb95, CHAFA_SYMBOL_OUTLINE_8X8 ( "XX XX " "XX XX " " XX XX" " XX XX" "XX XX " "XX XX " " XX XX" " XX XX") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb96, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX XX" " XX XX" "XX XX " "XX XX " " XX XX" " XX XX" "XX XX " "XX XX ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fb97, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " "XXXXXXXX" "XXXXXXXX" " " " " "XXXXXXXX" "XXXXXXXX") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba0, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "X " " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba1, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X" " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " X" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba4, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "X " "X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba5, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X" " X" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "X X" " X X " " X X " " XX ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba7, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " "X X" " " " " " " " ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba8, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "X " " X" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fba9, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X" "X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fbaa, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " " X" "X X" " X X " " X X " " XX ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fbab, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " X " " X " "X " "X X" " X X " " X X " " XX ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fbac, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " "X X" " X" " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fbad, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " "X X" "X " " X " " X " " X ") }, { CHAFA_SYMBOL_TAG_LEGACY, 0x1fbae, CHAFA_SYMBOL_OUTLINE_8X8 ( " XX " " X X " " X X " "X X" "X X" " X X " " X X " " XX ") }, chafa-1.8.0/chafa/internal/chafa-symbols-kana.h000066400000000000000000002055441411352071600212630ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ /* Japanese hiragana and katakana * ------------------------------ * * This is meant to be #included in the symbol definition table of * chafa-symbols.c. It's kept in a separate file due to its size. */ /* Hiragana */ { /* [ぁ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3041, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXXXXXXXX " " X X " " XXXXX XX " " XX X X XX " " X XX X " " XX XXX ") }, { /* [あ] freq=374 */ CHAFA_SYMBOL_TAG_NONE, 0x3042, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XXXXXXXXXXX " " X XX " " XXXXXXXX " " XX X X X " " X X X X " " X XX X " " XXX XXXX ") }, { /* [ぃ] freq=73 */ CHAFA_SYMBOL_TAG_NONE, 0x3043, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " X X " " X X " " X X " " X X X " " XX ") }, { /* [い] freq=12 */ CHAFA_SYMBOL_TAG_NONE, 0x3044, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " X XX " " X XX " " XX X " " X X XX " " XX X " " XXX ") }, { /* [ぅ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3045, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXX " " " " XXXX " " XX XX " " X " " XX " " XXXX ") }, { /* [う] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3046, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXX " " XXX " " XXXX XXX " " X " " X " " XX " " XXXX ") }, { /* [ぇ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3047, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXXXX " " " " XXXXXXX " " XX " " XX XX " " XX XXXX ") }, { /* [え] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3048, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXX " " " " XXXXXXXX " " XX " " XXXXX " " XX X " " X XXXX ") }, { /* [ぉ] freq=11 */ CHAFA_SYMBOL_TAG_NONE, 0x3049, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XXXXXX XXX " " X " " XXXXXXXXX " " X X X " " XXXX XXXX ") }, { /* [お] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x304a, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " X X " " XXXXXXX XXX " " X XX " " XXXXX XXX " " XX X X " " X X X " " XXX XXXX ") }, { /* [か] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x304b, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX X " " XXXXXXXXX XX " " X X XX " " X X X " " XX XX " " XX X " " X XXX ") }, { /* [が] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x304c, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X X " " X X " " XXXXXXXXX X " " XX XX X " " X XX X " " X X " " XX X " " X XXXX ") }, { /* [き] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x304d, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX XXXXX " " XXX X " " XXXXXXXXXX " " XX " " X XXX " " X " " XXXXXXXX ") }, { /* [ぎ] freq=48 */ CHAFA_SYMBOL_TAG_NONE, 0x304e, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X XX " " XX XXXXXX " " XXX X " " XXX XXXXXXX " " X X " " X XXXX " " XX " " XXXXXXXX ") }, { /* [く] freq=20 */ CHAFA_SYMBOL_TAG_NONE, 0x304f, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XX " " XX " " XX " " XXX " " X ") }, { /* [ぐ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3050, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XXX X X " " XX " " XX " " XX " " XX ") }, { /* [け] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3051, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX X " " X XXXXXXXXX " " X X " " X X " " XX X " " XX X " " XX ") }, { /* [げ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3052, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " XX X X " " X XXXXXXXXX " " X X " " X X " " XX XX " " XX X " " X XXX ") }, { /* [こ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x3053, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXX " " " " " " " " X " " XX " " XXXXXXXX ") }, { /* [ご] freq=7 */ CHAFA_SYMBOL_TAG_NONE, 0x3054, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " XXXXXXXXX X " " " " " " " " X " " X " " XXXXXXXXX ") }, { /* [さ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3055, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X " " XXXXXXXXXX " " X " " XX " " X XX " " XX " " XXXXXXX ") }, { /* [ざ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3056, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X X " " XX " " XXXXXXXXXXX " " X " " X X " " X XX " " X " " XXXXXXXX ") }, { /* [し] freq=21 */ CHAFA_SYMBOL_TAG_NONE, 0x3057, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " X " " X " " X " " X X " " X XX " " XXXXXXX ") }, { /* [じ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3058, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X XX " " X X " " X " " X " " X " " X XX " " XXXXXXXX ") }, { /* [す] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3059, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXXXXXX " " XX X " " XXXX " " X X " " XXXXX " " XX " " XXX ") }, { /* [ず] freq=10 */ CHAFA_SYMBOL_TAG_NONE, 0x305a, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X X " " XXXXXXXXXX " " XX X " " XXXX " " XX X " " XXXXX " " XX " " XXX ") }, { /* [せ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x305b, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " X XXXXX " " XXXXXXXXX X " " X XX " " X XX " " XX " " XXXXXXX ") }, { /* [ぜ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x305c, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X X " " X XXXX " " XXXXXXXXXX " " X X " " X XXX " " X " " XXXXXXXX ") }, { /* [そ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x305d, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXX " " XXX XX " " XX " " XX XXX " " XXX XXXX " " X " " X " " XXXXX ") }, { /* [ぞ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x305e, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXX " " XXXXXX X " " X XX " " XX XXXX " " XXXX XXXX " " X " " XX " " XXXX ") }, { /* [た] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x305f, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXXX " " X " " X XXXXX " " X " " XX X " " X X " " XX XXXXX ") }, { /* [だ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3060, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X " " X X X " " XXXXXXXX " " XX XXXXXX " " X " " X X " " XX X " " XX XXXXXX ") }, { /* [ち] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3061, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XXXXXXXXXXX " " X " " X X " " XXXX XXX " " X XX " " XX " " XXXXXX ") }, { /* [ぢ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3062, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X " " X X " " XXXXXXXXXX " " X " " XXXXXXXXX " " X X " " XX " " XXXXXX ") }, { /* [っ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x3063, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXXXXX " " XX XX " " X " " XX " " XXXX ") }, { /* [つ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x3064, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXX " " XXXXXXX XXX " " XX " " X " " XX " " XXXXXX " " ") }, { /* [づ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3065, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X " " XXXXXXXX " " XXXX X " " X " " XX " " XX " " XXXXX ") }, { /* [て] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3066, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " X " " XX " " X " " XX " " XX " " XXX ") }, { /* [で] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x3067, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " XX X " " XX XX X " " X " " XX " " XX " " XXX ") }, { /* [と] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3068, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " X XXXX " " XXX " " XX " " XX " " XX " " XXXXXXXX ") }, { /* [ど] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3069, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X XX " " X XXX " " XXXX " " X " " X " " XX " " XXXXXXXX ") }, { /* [な] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x306a, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX " " XXXX XX XXX " " XX X " " XX X " " XX X " " XXXXXXXXX " " XXXXX ") }, { /* [に] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x306b, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X XXXXXXX " " XX " " X " " X " " X X X " " XX X " " XX XXXXXX ") }, { /* [ぬ] freq=26 */ CHAFA_SYMBOL_TAG_NONE, 0x306c, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X XX " " XXXXXX XXX " " XXX X X " " X XX X X " " X XX XXXXXX " " XXX X XXX " " XXX ") }, { /* [ね] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x306d, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X " " XXXXXXXXX XX " " XX X " " XX X " " X X XXXXXX " " X X X X X " " XX XXX ") }, { /* [の] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x306e, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXX " " XX XX XX " " X X X " " XX XX XX " " X X X " " XXXX XX " " XXX ") }, { /* [は] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x306f, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " X XXXXXXXX " " X X " " X X " " XX XXXXXX " " XX X X XX " " XX XXXX ") }, { /* [ば] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3070, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X X " " X XXXXXXXX " " X X " " XX XX " " XXX XXXXXXX " " X X XX XX " " X XXX ") }, { /* [ぱ] freq=51 */ CHAFA_SYMBOL_TAG_NONE, 0x3071, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXX" " X X XX " " X XXXXXXXX " " X X " " XX XX " " XXX XXXXXXX " " X X XX XX " " X XXX ") }, { /* [ひ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3072, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXX X " " XX XX " " XX XX " " X X XX " " XX X " " X X " " XXXXXX ") }, { /* [び] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3073, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " XXXXXXX XX X " " X X " " X XX " " XX X X " " X XX " " XX X " " XXXXXX ") }, { /* [ぴ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3074, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXX " " XXXXXXX XXX X " " X X X " " X XX " " XX X X " " X X " " XX X " " XXXXXX ") }, { /* [ふ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3075, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXX " " XX " " X " " XX X " " X X X " " XXX XX X " " XXXX ") }, { /* [ぶ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3076, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXX XX " " XX X X " " X " " XX XX " " XX XX X " " XX X XX " " XXXX ") }, { /* [ぷ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x3077, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXX XXX " " XX X X " " X X " " XX XX " " XX XX X " " XX X XX " " XXXX ") }, { /* [へ] freq=25 */ CHAFA_SYMBOL_TAG_NONE, 0x3078, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXX " " X XX " " XX XX " " X XX " " XX " " ") }, { /* [べ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3079, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " XXX X " " XX XX " " XX X " " X XX " " XX " " ") }, { /* [ぺ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x307a, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXX " " XXX X X " " XX XX XX " " XX X " " X XX " " XX " " ") }, { /* [ほ] freq=9 */ CHAFA_SYMBOL_TAG_NONE, 0x307b, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX XXXXXXX " " X XX " " X XXXXXXX " " X X " " XX XXXXXX " " XX X X XX " " XX XXXX ") }, { /* [ぼ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x307c, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X XXXXXX X " " X X " " X XXXXXXXX " " XX X " " XXX X " " X XXXXXXXX " " X XXX ") }, { /* [ぽ] freq=181 */ CHAFA_SYMBOL_TAG_NONE, 0x307d, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXX" " X XXXXXX XX " " X X " " X XXXXXXXX " " XX X " " XXX X " " X XXXXXXXX " " X XXX ") }, { /* [ま] freq=7 */ CHAFA_SYMBOL_TAG_NONE, 0x307e, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXXXXX " " X " " XXXXXXXXXXX " " X " " XXXXXXX " " X X XX " " XXXXXX ") }, { /* [み] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x307f, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXX " " XX " " XX X " " XXXXX XXXXX " " X X XXXX " " XXXX XX " " XX ") }, { /* [む] freq=8 */ CHAFA_SYMBOL_TAG_NONE, 0x3080, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXXX XX " " X X " " XXXX " " X X " " XXXX X " " X X " " XXXXXXX ") }, { /* [め] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3081, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " XXXXXXXX " " XX X XX " " X X XX X " " XX XX X " " XXXX XX " " XX ") }, { /* [も] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3082, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " XXXXXXXX " " X " " XXXXXXXX XX " " X X " " X X " " XXXXXX ") }, { /* [ゃ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x3083, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XX " " X XXXXXX " " XXXX X " " X X XXXXX " " XX " " X ") }, { /* [や] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x3084, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X XX " " XXXXXX XX " " XXX X XX " " X XXXXX " " XX " " X " " XX ") }, { /* [ゅ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x3085, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X XX " " X XX XXXX " " X X X XX " " XX X X XX " " XX XXXXX " " XX ") }, { /* [ゆ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3086, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XX X " " X XXXXX XXX " " X X X X " " XX X X " " XX X XX XX " " XXXXX " " XX ") }, { /* [ょ] freq=14 */ CHAFA_SYMBOL_TAG_NONE, 0x3087, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XXXXX " " XX " " XX " " XXXXXXXXX " " XXXXX ") }, { /* [よ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3088, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XX " " XXXXXX " " XX " " XX " " XXXXXXX " " X XX XXXX " " XXXX ") }, { /* [ら] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3089, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXX " " X " " XX XXX " " XXXXX XX " " X XX " " XX " " XXXXXX ") }, { /* [り] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x308a, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X XXXX " " XXX X " " XX XX " " XX XX " " X " " XX " " XXXX ") }, { /* [る] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x308b, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXX " " XX " " XX " " XXX XXXXXX " " X XX " " XXXXX X " " XXXXXX ") }, { /* [れ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x308c, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X " " XXXXXXXXX XX " " XX XX " " XX X " " X X X " " X X X " " X XXXX ") }, { /* [ろ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x308d, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXX " " XX " " XX X " " XXX XX XXX " " X XX " " XX " " XXXXXX ") }, { /* [ゎ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x308e, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XX " " XXX XXXXXX " " XX X " " XXX X " " X X XX " " X XX ") }, { /* [わ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x308f, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " XXXXX XXXXXX " " XXX XX " " XX X " " X X X " " X X XXX " " XX X ") }, { /* [ゐ] freq=14 */ CHAFA_SYMBOL_TAG_NONE, 0x3090, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XXX X " " XXX " " XXXX XXX " " XX X X " " X XX X " " XX XX XXXXXXX " " XXX XXX ") }, { /* [ゑ] freq=17 */ CHAFA_SYMBOL_TAG_NONE, 0x3091, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXXXXX " " XX " " XXX XXX " " X X XX X " " XXXXXXX " " XX " " XX X XXXX " " XX XX XX ") }, { /* [を] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x3092, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXXXXX " " X " " XXXXXX XXXX " " X XXX " " XX X " " X " " XXXXXXX ") }, { /* [ん] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x3093, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX " " XX " " XX X " " X X X " " X XX X " " X XX X " " X XXXX ") }, { /* [ゔ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3094, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXX XX " " XXXX X " " XXXX XX " " X " " XX " " XXX " " XXX ") }, { /* [ゕ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x3095, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " X XXXX XX " " XXX XX X " " X XX X " " X X " " XX XXXX ") }, { /* [ゖ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x3096, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X XX " " X XXXXXXX " " X XX " " X X XX " " XX X " " X XX ") }, /* Katakana */ { /* [゠] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30a0, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXX " " XXXX " " " " " " ") }, { /* [ァ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30a1, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXXXXXXXXX " " XX " " X X " " X " " XX " " XX ") }, { /* [ア] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30a2, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXXX " " X X " " X XX " " X " " XX " " XX " " XX ") }, { /* [ィ] freq=7 */ CHAFA_SYMBOL_TAG_NONE, 0x30a3, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XX " " XXX " " XXXX X " " X " " X ") }, { /* [イ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30a4, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XXX " " XXX " " XXXX X " " X " " X " " XX ") }, { /* [ゥ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30a5, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XX " " XXXXXXXXXX " " X XX " " X " " XX " " XXXX ") }, { /* [ウ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30a6, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XX " " XXXXXXXXXXXX " " XX XX " " XX X " " XX " " XXX " " XX ") }, { /* [ェ] freq=20 */ CHAFA_SYMBOL_TAG_NONE, 0x30a7, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXXXXXXXX " " XX " " XX " " XX " " XXXXXXXXXXXX ") }, { /* [エ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30a8, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " XX " " XX " " XX " " XX " " XXXXXXXXXXXXXX " " ") }, { /* [ォ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30a9, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XXXXXXXXXXX " " XX " " XX X " " XX X " " X XXX ") }, { /* [オ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30aa, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " X " " XXXXXXXXXXXX " " XXX " " XX XX " " XXX XX " " X XX " " XXX ") }, { /* [カ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30ab, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " XXXXXXXXXXXX " " XX XX " " X X " " X X " " X X " " XX XXXX ") }, { /* [ガ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30ac, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X X " " XX X X " " XXXXXXXXXXX " " X X " " XX X " " X X " " XX XX " " X XXXX ") }, { /* [キ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30ad, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XXXXXX " " XXXXXX " " XX XXXX " " XXXXX XX " " XX " " X ") }, { /* [ギ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30ae, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X X " " XXXXXXXXXXX " " X " " XXXXXXXXX " " XXX X " " X " " ") }, { /* [ク] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30af, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXX " " XX X " " XX XX " " XX " " X " " XXX " " XX ") }, { /* [グ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30b0, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X X " " XXXXXXXX " " XX X " " XX XX " " X " " XXX " " XXX ") }, { /* [ケ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30b1, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " X " " XXXXXXXXXXX " " XX X " " XX " " X " " XX " " X ") }, { /* [ゲ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30b2, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XX X X " " XXXXXXXXXX " " X X " " XX XX " " X " " XX " " XXX ") }, { /* [コ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x30b3, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXX " " XX " " XX " " XX " " XX " " XXXXXXXXXXX " " XX ") }, { /* [ゴ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30b4, CHAFA_SYMBOL_OUTLINE_16X8 ( " X XX " " XXXXXXXXXXX " " X " " X " " X " " X " " XXXXXXXXXXX " " X ") }, { /* [サ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30b5, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XX XX " " XXXXXXXXXXXXXX " " XX XX " " XX X " " X " " XX " " XX ") }, { /* [ザ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30b6, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X X XXX " " XXXXXXXXXXXXX " " X X " " X X " " XX " " XX " " XX ") }, { /* [シ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30b7, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XXX XX " " XX " " XX " " XXXXXX " " ") }, { /* [ジ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30b8, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X X " " XXX X X " " XX X " " X X " " XXX " " XXX " " XXX ") }, { /* [ス] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30b9, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " XX " " XX " " XX " " XX XX " " XXX X " " X X ") }, { /* [ズ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30ba, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X " " XXXXXXXXXX " " XX " " XX " " XX " " XX XX " " XX XX " " XX XX ") }, { /* [セ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30bb, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " X XXXXXX " " XXXXXXX XX " " X XX " " X " " X " " XXXXXXX ") }, { /* [ゼ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30bc, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X " " X XXXXX " " XXXXXXXX XX " " X XX " " X " " X " " XXXXXXX ") }, { /* [ソ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x30bd, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX X " " XX XX " " X X " " X " " XX " " XXX " " ") }, { /* [ゾ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30be, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X X " " X XX " " XX X " " X " " XX " " XXX " " ") }, { /* [タ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30bf, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " XXXXXXX " " X X " " XX X XX " " X XXXXX " " XXXX " " XX " " XXX ") }, { /* [ダ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30c0, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X X " " XXXXXXXX " " XX XX " " XX X X " " XX X " " XXXX " " XX " " XXX ") }, { /* [チ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30c1, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXX " " X " " XXXXXXXXXXXXXX " " XX " " X " " X " " XX ") }, { /* [ヂ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30c2, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX X " " X X X " " XXXXXXXXXXXXX " " X " " X " " XX " " XX ") }, { /* [ッ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30c3, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " X X X " " X X XX " " XX " " XX " " XXX ") }, { /* [ツ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30c4, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX X " " XX X XX " " X X " " X " " XX " " XXXX " " ") }, { /* [ヅ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30c5, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X " " X X XX " " X XX " " XX " " XX " " XXX " " ") }, { /* [テ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30c6, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " " " XXXXXXXXXXXXX " " X " " XX " " XX " " XX ") }, { /* [デ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30c7, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " XXXXXXXXX " " " " XXXXXXXXXXXXX " " XX " " X " " XX " " XX ") }, { /* [ト] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30c8, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XXXX " " XX XXXXX " " XX " " XX " " ") }, { /* [ド] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30c9, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X X " " X X " " XXX " " X XXXXX " " X " " X " " X ") }, { /* [ナ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30ca, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " XXXXXXXXXXXXX " " X " " XX " " X " " XX " " X ") }, { /* [ニ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30cb, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " " " " " " " " " XXXXXXXXXXXXX " " ") }, { /* [ヌ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30cc, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " X " " XXX X " " XXX " " XXXX " " XX XX " " XX ") }, { /* [ネ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x30cd, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX " " XXXXXXXXXXX " " XX " " XX " " XXXX XX " " XXXX XX XXX " " XX " " XX ") }, { /* [ノ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x30ce, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XX " " XX " " XX " " XXXX " " ") }, { /* [ハ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30cf, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " X XX " " XX X " " X X " " X XX " " X XX " " ") }, { /* [バ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30d0, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " X X " " XX X " " X X " " X XX " " XX X " " ") }, { /* [パ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30d1, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXX " " X XX X X " " X XX X " " XX X " " X X " " X XX " " X X " " ") }, { /* [ヒ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30d2, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " X XXX " " XXXXXX " " X " " X " " X " " XXXXXXXX ") }, { /* [ビ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30d3, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X " " X XX " " XXXXXXX " " X " " X " " X " " XXXXXXXXX ") }, { /* [ピ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30d4, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXX " " X X X " " X XXX X " " XXXXXX " " X " " X " " X " " XXXXXXXXX ") }, { /* [フ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30d5, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " X " " XX " " XX " " XX " " XXX " " XX ") }, { /* [ブ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30d6, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " XXXXXXXXXXX " " X " " XX " " XX " " XX " " XXX " " XX ") }, { /* [プ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30d7, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXX" " XXXXXXXXXXX X" " XXX " " XX " " XX " " XX " " XXX " " XX ") }, { /* [ヘ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30d8, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXX " " X XX " " XX XX " " X XX " " XX " " ") }, { /* [ベ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30d9, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " XXX X " " XX XX " " XX XX " " X XX " " XX " " ") }, { /* [ペ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30da, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXX " " XXX X X " " XX XX X " " XX XX " " X XX " " XX " " ") }, { /* [ホ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30db, CHAFA_SYMBOL_OUTLINE_16X8 ( " X " " X " " XXXXXXXXXXXX " " X " " X X X " " XX X X " " X " " XXX ") }, { /* [ボ] freq=14 */ CHAFA_SYMBOL_TAG_NONE, 0x30dc, CHAFA_SYMBOL_OUTLINE_16X8 ( " XX X X " " XX X X " " XXXXXXXXXXXXX " " XX " " XX XX XX " " X XX X " " X XX " " XXX ") }, { /* [ポ] freq=32 */ CHAFA_SYMBOL_TAG_NONE, 0x30dd, CHAFA_SYMBOL_OUTLINE_16X8 ( " XXXX " " XX XX " " XXXXXXXXXXXXX " " XX " " XX XX XX " " XX XX XX " " XX " " XXX ") }, { /* [マ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30de, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " XX " " XX " " XX XX " " XXX " " XX " " X ") }, { /* [ミ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30df, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXX " " XXX " " XXXX " " XXXX " " X " " XXXXXXXX " " ") }, { /* [ム] freq=8 */ CHAFA_SYMBOL_TAG_NONE, 0x30e0, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " X " " XX " " X X " " X X " " XXXXXXXXXXX X " " ") }, { /* [メ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30e1, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " X XX " " XXX XX " " XXX " " XX XX " " XXX " " ") }, { /* [モ] freq=9 */ CHAFA_SYMBOL_TAG_NONE, 0x30e2, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " XX " " XXXXXXXXXXXXX " " XX " " XX " " XX " " XXXXXX ") }, { /* [ャ] freq=10 */ CHAFA_SYMBOL_TAG_NONE, 0x30e3, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XX XXX " " XXXXXXXXXX " " X XX " " X " " X ") }, { /* [ヤ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30e4, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " X XXXXX " " XXXXXXXX X " " X XX " " XX " " X " " XX ") }, { /* [ュ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30e5, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXXXXXX " " X " " X " " XXXXXXXXXXXX " " ") }, { /* [ユ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30e6, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXX " " X " " X " " XX " " XX " " XXXXXXXXXXXXXX " " ") }, { /* [ョ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30e7, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXXXXXXX " " X " " XXXXXXXX " " X " " X " " XXXXXXXXX ") }, { /* [ヨ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30e8, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXX " " X " " X " " XXXXXXXXXX " " X " " XXXXXXXXXXX " " X ") }, { /* [ラ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30e9, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXX " " " " XXXXXXXXXXXX " " X " " X " " XXX " " XXX ") }, { /* [リ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30ea, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " X X " " X X " " X X " " XX " " XXX " " ") }, { /* [ル] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30eb, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX XX " " XX XX " " X XX " " X XX X " " XX XX XX " " XX XXXX " " ") }, { /* [レ] freq=6 */ CHAFA_SYMBOL_TAG_NONE, 0x30ec, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " X " " X " " X XX " " X XXX " " XXXXX " " ") }, { /* [ロ] freq=653 */ CHAFA_SYMBOL_TAG_NONE, 0x30ed, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " XX XX " " XX XX " " XX XX " " XX XX " " XXXXXXXXXXXX " " XX XX ") }, { /* [ヮ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30ee, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XXXXXXXXXX " " X X " " X X " " XX " " XX " " XXXX ") }, { /* [ワ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30ef, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " XX XX " " XX X " " XX " " XX " " XXX " " X ") }, { /* [ヰ] freq=2 */ CHAFA_SYMBOL_TAG_NONE, 0x30f0, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X " " XXXXXXXXXXXX " " X X " " X X " " XXXXXXXXXXXXXX " " X " " X ") }, { /* [ヱ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30f1, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXXX " " X " " X XX " " X " " X " " XXXXXXXXXXXXXX " " ") }, { /* [ヲ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30f2, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XXXXXXXXXXX " " XX " " XXXXXXXXXX " " X " " X " " XXX " " XX ") }, { /* [ン] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30f3, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " XX " " XX " " XX " " X " " XXX " " XXXXXX " " ") }, { /* [ヴ] freq=0 */ CHAFA_SYMBOL_TAG_NONE, 0x30f4, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X XX " " X X " " XXXXXXXXXXXX " " X X " " X XX " " XX " " XX " " XXX ") }, { /* [ヵ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30f5, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XXXXXXXXX " " XX XX " " X XX " " XX X " " XX XXXX ") }, { /* [ヶ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30f6, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " XX " " XXXXXXXXX " " XX X " " XX " " XX " " XXX ") }, { /* [ヷ] freq=3 */ CHAFA_SYMBOL_TAG_NONE, 0x30f7, CHAFA_SYMBOL_OUTLINE_16X8 ( " X XX " " XXXXXXXXXXX " " X X " " X X " " X " " XX " " XX " " XXX ") }, { /* [ヸ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30f8, CHAFA_SYMBOL_OUTLINE_16X8 ( " X X " " X X " " XXXXXXXXXXXX " " X X " " X X " " XXXXXXXXXXXXXX " " X " " X ") }, { /* [ヹ] freq=1 */ CHAFA_SYMBOL_TAG_NONE, 0x30f9, CHAFA_SYMBOL_OUTLINE_16X8 ( " X XX " " XXXXXXXXXXXX " " X " " X XX " " X " " X " " XXXXXXXXXXXXXX " " ") }, { /* [ヺ] freq=4 */ CHAFA_SYMBOL_TAG_NONE, 0x30fa, CHAFA_SYMBOL_OUTLINE_16X8 ( " X XX " " XXXXXXXXXXX " " X " " XXXXXXXXXXX " " XX " " XX " " XX " " XXX ") }, { /* [・] freq=99 */ CHAFA_SYMBOL_TAG_NONE, 0x30fb, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXX " " XX " " " " " " ") }, { /* [ー] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x30fc, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " " " XXXXXXXXXXXX " " " " " " " " ") }, { /* [ヽ] freq=19 */ CHAFA_SYMBOL_TAG_NONE, 0x30fd, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " " " X " " XX " " XX " " XX " " " " ") }, { /* [ヾ] freq=5 */ CHAFA_SYMBOL_TAG_NONE, 0x30fe, CHAFA_SYMBOL_OUTLINE_16X8 ( " " " X X " " X X " " XX " " XX " " X " " " " ") }, chafa-1.8.0/chafa/internal/chafa-symbols-misc-narrow.h000066400000000000000000000222411411352071600226010ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ /* Miscellaneous single-cell symbols * --------------------------------- * * This is meant to be #included in the symbol definition table of * chafa-symbols.c. It's kept in a separate file due to its size. */ { /* Horizontal Scan Line 1 */ CHAFA_SYMBOL_TAG_TECHNICAL, 0x23ba, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXXX" " " " " " " " " " " " ") }, { /* Horizontal Scan Line 3 */ CHAFA_SYMBOL_TAG_TECHNICAL, 0x23bb, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " "XXXXXXXX" " " " " " " " ") }, { /* Horizontal Scan Line 7 */ CHAFA_SYMBOL_TAG_TECHNICAL, 0x23bc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " "XXXXXXXX" " " " " " ") }, { /* Horizontal Scan Line 9 */ CHAFA_SYMBOL_TAG_TECHNICAL, 0x23bd, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " " " " "XXXXXXXX" " ") }, /* Begin dot characters */ { CHAFA_SYMBOL_TAG_DOT, 0x25ae, /* Black vertical rectangle */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " XXXXXX " " XXXXXX " " XXXXXX " " XXXXXX " " XXXXXX " " ") }, { CHAFA_SYMBOL_TAG_DOT, 0x25a0, /* Black square */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXXXX " " XXXXXX " " XXXXXX " " XXXXXX " " " " ") }, { /* Has an emoji variant that may show up unbidden */ CHAFA_SYMBOL_TAG_DOT | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25aa, /* Black small square */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " " XXXX " " XXXX " " XXXX " " " " ") }, { /* Black up-pointing triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC, 0x25b2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XXXX " " XXXXXX " " XXXXXX " "XXXXXXXX" " " " ") }, { /* Black right-pointing triangle */ /* Has an emoji variant that may show up unbidden */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25b6, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXX " " XXXX " " XXXXXX " " XXXX " " XXX " " X " " ") }, { /* Black down-pointing triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC, 0x25bc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " "XXXXXXXX" " XXXXXX " " XXXXXX " " XXXX " " XX " " " " ") }, { /* Black left-pointing triangle */ /* Has an emoji variant that may show up unbidden */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25c0, CHAFA_SYMBOL_OUTLINE_8X8 ( " X " " XXX " " XXXX " " XXXXXX " " XXXX " " XXX " " X " " ") }, { /* Black diamond */ /* Depending on font, may exceed cell boundaries on VTE */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25c6, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XX " " XXXX " " XXXXXX " " XXXX " " XX " " " " ") }, { /* Black Circle */ CHAFA_SYMBOL_TAG_GEOMETRIC, 0x25cf, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXX " " XXXXXX " " XXXXXX " " XXXXXX " " XXXX " " " " ") }, { /* Black Lower Right Triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25e2, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XXX " " XXXX " " XXXXXX " " " " ") }, { /* Black Lower Left Triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25e3, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XXX " " XXXX " " XXXXXX " " " " ") }, { /* Black Upper Left Triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25e4, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXXXX " " XXXX " " XXX " " XX " " " " ") }, { /* Black Upper Right Triangle */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25e5, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXXXX " " XXXX " " XXX " " XX " " " " ") }, { /* Black Medium Square */ /* Has en emoji variant that may show up unbidden. See: * https://github.com/hpjansson/chafa/issues/52 */ CHAFA_SYMBOL_TAG_GEOMETRIC | CHAFA_SYMBOL_TAG_AMBIGUOUS, 0x25fc, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XXXX " " XXXX " " XXXX " " XXXX " " " " ") }, { CHAFA_SYMBOL_TAG_DOT, 0x00b7, /* Middle dot */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " XX " " " " " " ") }, { /* Variant */ CHAFA_SYMBOL_TAG_DOT, 0x00b7, /* Middle dot */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " XX " " " " " " ") }, { /* Variant */ CHAFA_SYMBOL_TAG_DOT, 0x00b7, /* Middle dot */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " XX " " XX " " " " " " ") }, { /* Variant */ CHAFA_SYMBOL_TAG_DOT, 0x00b7, /* Middle dot */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " XX " " XX " " " " " " " " ") }, { /* Variant */ CHAFA_SYMBOL_TAG_DOT, 0x00b7, /* Middle dot */ CHAFA_SYMBOL_OUTLINE_8X8 ( " " " " " " " " " XX " " XX " " " " ") }, { /* Greek Capital Letter Xi */ CHAFA_SYMBOL_TAG_EXTRA, 0x039e, CHAFA_SYMBOL_OUTLINE_8X8 ( " " " XXXXXX " " " " XXXX " " " " XXXXXX " " " " ") }, chafa-1.8.0/chafa/internal/chafa-symbols.c000066400000000000000000000342561411352071600203460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* memset */ #include "chafa.h" #include "internal/chafa-private.h" /* Standard C doesn't require that "s"[0] be considered a compile-time constant. * Modern compilers support it as an extension, but gcc < 8.1 does not. That's a * bit too recent, enough to make our tests fail. Therefore we disable it for now. * * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69960 * * Another option is to use binary literals, but that is itself an extension, * and the symbol outlines would be less legible that way. */ #undef CHAFA_USE_CONSTANT_STRING_EXPR #if CHAFA_USE_CONSTANT_STRING_EXPR /* Fancy macros that turn our ASCII symbol outlines into compact bitmaps */ #define CHAFA_FOLD_BYTE_TO_BIT(x) ((((x) >> 0) | ((x) >> 1) | ((x) >> 2) | ((x) >> 3) | \ ((x) >> 4) | ((x) >> 5) | ((x) >> 6) | ((x) >> 7)) & 1) #define CHAFA_OUTLINE_CHAR_TO_BIT(c) ((guint64) CHAFA_FOLD_BYTE_TO_BIT ((c) ^ 0x20)) #define CHAFA_OUTLINE_8_CHARS_TO_BITS(s, i) \ ((CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 0]) << 7) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 1]) << 6) | \ (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 2]) << 5) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 3]) << 4) | \ (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 4]) << 3) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 5]) << 2) | \ (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 6]) << 1) | (CHAFA_OUTLINE_CHAR_TO_BIT (s [i + 7]) << 0)) #define CHAFA_OUTLINE_TO_BITMAP_8X8(s) { \ ((CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 0) << 56) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 8) << 48) | \ (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 16) << 40) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 24) << 32) | \ (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 32) << 24) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 40) << 16) | \ (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 48) << 8) | (CHAFA_OUTLINE_8_CHARS_TO_BITS (s, 56) << 0)), 0 } #define CHAFA_SYMBOL_OUTLINE_8X8(x) CHAFA_OUTLINE_TO_BITMAP_8X8(x) #else #define CHAFA_SYMBOL_OUTLINE_8X8(x) x #define CHAFA_SYMBOL_OUTLINE_16X8(x) x #endif typedef struct { gunichar first, last; } UnicharRange; typedef struct { ChafaSymbolTags sc; gunichar c; #if CHAFA_USE_CONSTANT_STRING_EXPR /* Each 64-bit integer represents an 8x8 bitmap, scanning left-to-right * and top-to-bottom, stored in host byte order. * * Narrow symbols use bitmap [0], with bitmap [1] set to zero. Wide * symbols are implemented as two narrow symbols side-by-side, with * the leftmost in [0] and rightmost in [1]. */ guint64 bitmap [2]; #else gchar *outline; #endif } ChafaSymbolDef; ChafaSymbol *chafa_symbols; ChafaSymbol2 *chafa_symbols2; static gboolean symbols_initialized; /* Ranges we treat as ambiguous-width in addition to the ones defined by * GLib. For instance: VTE, although spacing correctly, has many glyphs * extending well outside their cells resulting in ugly overlapping. */ static const UnicharRange ambiguous_ranges [] = { { 0x00ad, 0x00ad }, /* Soft hyphen */ { 0x2196, 0x21ff }, /* Arrows (most) */ { 0x222c, 0x2237 }, /* Mathematical ops (some) */ { 0x2245, 0x2269 }, /* Mathematical ops (some) */ { 0x226d, 0x2279 }, /* Mathematical ops (some) */ { 0x2295, 0x22af }, /* Mathematical ops (some) */ { 0x22bf, 0x22bf }, /* Mathematical ops (some) */ { 0x22c8, 0x22ff }, /* Mathematical ops (some) */ { 0x2300, 0x23ff }, /* Technical */ { 0x2460, 0x24ff }, /* Enclosed alphanumerics */ { 0x25a0, 0x25ff }, /* Geometric */ { 0x2700, 0x27bf }, /* Dingbats */ { 0x27c0, 0x27e5 }, /* Miscellaneous mathematical symbols A (most) */ { 0x27f0, 0x27ff }, /* Supplemental arrows A */ { 0x2900, 0x297f }, /* Supplemental arrows B */ { 0x2980, 0x29ff }, /* Miscellaneous mathematical symbols B */ { 0x2b00, 0x2bff }, /* Miscellaneous symbols and arrows */ { 0x1f100, 0x1f1ff }, /* Enclosed alphanumeric supplement */ { 0, 0 } }; /* Emojis of various kinds; usually multicolored. We have no control over * the foreground colors of these, and they may render poorly for other * reasons (e.g. too wide). */ static const UnicharRange emoji_ranges [] = { { 0x2600, 0x26ff }, /* Miscellaneous symbols */ { 0x1f000, 0x1fb3b }, /* Emojis first part */ { 0x1fbcb, 0x1ffff }, /* Emojis second part, the gap is legacy computing */ /* This symbol usually prints fine, but we don't want it randomly * popping up in our output anyway. So we add it to the "ugly" category, * which is excluded from "all". */ { 0x534d, 0x534d }, { 0, 0 } }; static const UnicharRange meta_ranges [] = { /* Arabic tatweel -- RTL but it's a modifier and not formally part * of a script, so can't simply be excluded on that basis in * ChafaSymbolMap::char_is_selected() */ { 0x0640, 0x0640 }, /* Ideographic description characters. These convert poorly to our * internal format. */ { 0x2ff0, 0x2fff }, { 0, 0 } }; static const ChafaSymbolDef symbol_defs [] = { #include "chafa-symbols-ascii.h" #include "chafa-symbols-block.h" #include "chafa-symbols-kana.h" #include "chafa-symbols-misc-narrow.h" { #if CHAFA_USE_CONSTANT_STRING_EXPR 0, 0, { 0, 0 } #else 0, 0, NULL #endif } }; /* ranges must be terminated by zero first, last */ static gboolean unichar_is_in_ranges (gunichar c, const UnicharRange *ranges) { for ( ; ranges->first != 0 || ranges->last != 0; ranges++) { g_assert (ranges->first <= ranges->last); if (c >= ranges->first && c <= ranges->last) return TRUE; } return FALSE; } static void calc_weights (ChafaSymbol *sym) { gint i; sym->fg_weight = 0; sym->bg_weight = 0; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { guchar p = sym->coverage [i]; sym->fg_weight += p; sym->bg_weight += 1 - p; } } static void outline_to_coverage (const gchar *outline, gchar *coverage_out, gint rowstride) { gchar xlate [256]; gint x, y; xlate [' '] = 0; xlate ['X'] = 1; for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) { for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) { guchar p = (guchar) outline [y * rowstride + x]; coverage_out [y * CHAFA_SYMBOL_WIDTH_PIXELS + x] = xlate [p]; } } } static guint64 coverage_to_bitmap (const gchar *cov, gint rowstride) { guint64 bitmap = 0; gint x, y; for (y = 0; y < CHAFA_SYMBOL_HEIGHT_PIXELS; y++) { for (x = 0; x < CHAFA_SYMBOL_WIDTH_PIXELS; x++) { bitmap <<= 1; if (cov [y * rowstride + x]) bitmap |= 1; } } return bitmap; } static void bitmap_to_coverage (guint64 bitmap, gchar *cov_out) { gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { cov_out [i] = (bitmap >> (63 - i)) & 1; } } static void gen_braille_sym (gchar *cov, guint8 val) { memset (cov, 0, CHAFA_SYMBOL_N_PIXELS); cov [1] = cov [2] = (val & 1); cov [5] = cov [6] = ((val >> 3) & 1); cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; cov [1] = cov [2] = ((val >> 1) & 1); cov [5] = cov [6] = ((val >> 4) & 1); cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; cov [1] = cov [2] = ((val >> 2) & 1); cov [5] = cov [6] = ((val >> 5) & 1); cov += CHAFA_SYMBOL_WIDTH_PIXELS * 2; cov [1] = cov [2] = ((val >> 6) & 1); cov [5] = cov [6] = ((val >> 7) & 1); } static int generate_braille_syms (ChafaSymbol *syms, gint first_ofs) { gunichar c; gint i = first_ofs; /* Braille 2x4 range */ c = 0x2800; for (i = first_ofs; c < 0x2900; c++, i++) { ChafaSymbol *sym = &syms [i]; sym->sc = CHAFA_SYMBOL_TAG_BRAILLE; sym->c = c; sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); gen_braille_sym (sym->coverage, c - 0x2800); calc_weights (&syms [i]); syms [i].bitmap = coverage_to_bitmap (syms [i].coverage, CHAFA_SYMBOL_WIDTH_PIXELS); syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); } return i; } static void gen_sextant_sym (gchar *cov, guint8 val) { gint x, y; memset (cov, 0, CHAFA_SYMBOL_N_PIXELS); for (y = 0; y < 3; y++) { for (x = 0; x < 2; x++) { gint bit = y * 2 + x; if (val & (1 << bit)) { gint u, v; for (v = 0; v < 3; v++) { for (u = 0; u < 4; u++) { gint row = y * 3 + v; if (row > 3) row--; cov [(row * 8) + x * 4 + u] = 1; } } } } } } static int generate_sextant_syms (ChafaSymbol *syms, gint first_ofs) { gunichar c; gint i = first_ofs; /* Teletext sextant/2x3 mosaic range */ c = 0x1fb00; for (i = first_ofs; c < 0x1fb3b; c++, i++) { ChafaSymbol *sym = &syms [i]; gint bitmap; sym->sc = CHAFA_SYMBOL_TAG_LEGACY | CHAFA_SYMBOL_TAG_SEXTANT; sym->c = c; sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); bitmap = c - 0x1fb00 + 1; if (bitmap > 20) bitmap++; if (bitmap > 41) bitmap++; gen_sextant_sym (sym->coverage, bitmap); calc_weights (&syms [i]); syms [i].bitmap = coverage_to_bitmap (syms [i].coverage, CHAFA_SYMBOL_WIDTH_PIXELS); syms [i].popcount = chafa_population_count_u64 (syms [i].bitmap); } return i; } static ChafaSymbolTags get_default_tags_for_char (gunichar c) { ChafaSymbolTags tags = CHAFA_SYMBOL_TAG_NONE; if (g_unichar_iswide (c)) tags |= CHAFA_SYMBOL_TAG_WIDE; else if (g_unichar_iswide_cjk (c)) tags |= CHAFA_SYMBOL_TAG_AMBIGUOUS; if (g_unichar_ismark (c) || g_unichar_iszerowidth (c) || unichar_is_in_ranges (c, ambiguous_ranges)) tags |= CHAFA_SYMBOL_TAG_AMBIGUOUS; if (unichar_is_in_ranges (c, emoji_ranges) || unichar_is_in_ranges (c, meta_ranges)) tags |= CHAFA_SYMBOL_TAG_UGLY; if (c <= 0x7f) tags |= CHAFA_SYMBOL_TAG_ASCII; else if (c >= 0x2300 && c <= 0x23ff) tags |= CHAFA_SYMBOL_TAG_TECHNICAL; else if (c >= 0x25a0 && c <= 0x25ff) tags |= CHAFA_SYMBOL_TAG_GEOMETRIC; else if (c >= 0x2800 && c <= 0x28ff) tags |= CHAFA_SYMBOL_TAG_BRAILLE; else if (c >= 0x1fb00 && c <= 0x1fb3b) tags |= CHAFA_SYMBOL_TAG_SEXTANT; if (g_unichar_isalpha (c)) tags |= CHAFA_SYMBOL_TAG_ALPHA; if (g_unichar_isdigit (c)) tags |= CHAFA_SYMBOL_TAG_DIGIT; if (!(tags & CHAFA_SYMBOL_TAG_WIDE)) tags |= CHAFA_SYMBOL_TAG_NARROW; return tags; } static void def_to_symbol (const ChafaSymbolDef *def, ChafaSymbol *sym, gint x_ofs, gint rowstride) { sym->c = def->c; /* FIXME: g_unichar_iswide_cjk() will erroneously mark many of our * builtin symbols as ambiguous. Find a better way to deal with it. */ sym->sc = def->sc | (get_default_tags_for_char (def->c) & ~CHAFA_SYMBOL_TAG_AMBIGUOUS); sym->coverage = g_malloc (CHAFA_SYMBOL_N_PIXELS); outline_to_coverage (def->outline + x_ofs, sym->coverage, rowstride); sym->bitmap = coverage_to_bitmap (sym->coverage, CHAFA_SYMBOL_WIDTH_PIXELS); sym->popcount = chafa_population_count_u64 (sym->bitmap); calc_weights (sym); } static ChafaSymbol * init_symbol_array (const ChafaSymbolDef *defs) { ChafaSymbol *syms; gint i, j; syms = g_new0 (ChafaSymbol, CHAFA_N_SYMBOLS_MAX); for (i = 0, j = 0; defs [i].c; i++) { gint outline_len; outline_len = strlen (defs [i].outline); g_assert (outline_len == CHAFA_SYMBOL_N_PIXELS || outline_len == CHAFA_SYMBOL_N_PIXELS * 2); if (outline_len != CHAFA_SYMBOL_N_PIXELS || g_unichar_iswide (defs [i].c)) continue; def_to_symbol (&defs [i], &syms [j], 0, CHAFA_SYMBOL_WIDTH_PIXELS); j++; } j = generate_braille_syms (syms, j); generate_sextant_syms (syms, j); return syms; } static ChafaSymbol2 * init_symbol_array_wide (const ChafaSymbolDef *defs) { ChafaSymbol2 *syms; gint i, j; syms = g_new0 (ChafaSymbol2, CHAFA_N_SYMBOLS_MAX); for (i = 0, j = 0; defs [i].c; i++) { gint outline_len; outline_len = strlen (defs [i].outline); g_assert (outline_len == CHAFA_SYMBOL_N_PIXELS || outline_len == CHAFA_SYMBOL_N_PIXELS * 2); if (outline_len != CHAFA_SYMBOL_N_PIXELS * 2 || !g_unichar_iswide (defs [i].c)) continue; def_to_symbol (&defs [i], &syms [j].sym [0], 0, CHAFA_SYMBOL_WIDTH_PIXELS * 2); def_to_symbol (&defs [i], &syms [j].sym [1], CHAFA_SYMBOL_WIDTH_PIXELS, CHAFA_SYMBOL_WIDTH_PIXELS * 2); j++; } return syms; } void chafa_init_symbols (void) { if (symbols_initialized) return; chafa_symbols = init_symbol_array (symbol_defs); chafa_symbols2 = init_symbol_array_wide (symbol_defs); symbols_initialized = TRUE; } ChafaSymbolTags chafa_get_tags_for_char (gunichar c) { gint i; for (i = 0; symbol_defs [i].c; i++) { const ChafaSymbolDef *def = &symbol_defs [i]; if (def->c == c) return def->sc | (get_default_tags_for_char (def->c) & ~CHAFA_SYMBOL_TAG_AMBIGUOUS); } return get_default_tags_for_char (c); } chafa-1.8.0/chafa/internal/chafa-work-cell.c000066400000000000000000000266221411352071600205530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include "chafa.h" #include "internal/chafa-private.h" #include "internal/chafa-pixops.h" #include "internal/chafa-work-cell.h" /* Used for cell initialization. May be added up over multiple cells, so a * low multiple needs to fit in an integer. */ #define SYMBOL_ERROR_MAX (G_MAXINT / 8) /* Max candidates to consider in pick_symbol_and_colors_fast(). This is also * limited by a similar constant in chafa-symbol-map.c */ #define N_CANDIDATES_MAX 8 typedef struct { ChafaPixel fg; ChafaPixel bg; gint error; } SymbolEval; typedef struct { ChafaPixel fg; ChafaPixel bg; gint error [2]; } SymbolEval2; static void accum_to_color (const ChafaColorAccum *accum, ChafaColor *color) { gint i; for (i = 0; i < 4; i++) color->ch [i] = accum->ch [i]; } /* pixels_out must point to CHAFA_SYMBOL_N_PIXELS-element array */ static void fetch_canvas_pixel_block (const ChafaPixel *src_image, gint src_width, ChafaPixel *pixels_out, gint cx, gint cy) { const ChafaPixel *row_p; const ChafaPixel *end_p; gint i = 0; row_p = src_image + cy * CHAFA_SYMBOL_HEIGHT_PIXELS * src_width + cx * CHAFA_SYMBOL_WIDTH_PIXELS; end_p = row_p + (src_width * CHAFA_SYMBOL_HEIGHT_PIXELS); for ( ; row_p < end_p; row_p += src_width) { const ChafaPixel *p0 = row_p; const ChafaPixel *p1 = p0 + CHAFA_SYMBOL_WIDTH_PIXELS; for ( ; p0 < p1; p0++) pixels_out [i++] = *p0; } } static void calc_colors_plain (const ChafaPixel *block, ChafaColorAccum *accums, const guint8 *cov) { const guint8 *in_u8 = (const guint8 *) block; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { gint16 *out_s16 = (gint16 *) (accums + *cov++); *out_s16++ += *in_u8++; *out_s16++ += *in_u8++; *out_s16++ += *in_u8++; *out_s16++ += *in_u8++; } } void chafa_work_cell_get_mean_colors_for_symbol (const ChafaWorkCell *wcell, const ChafaSymbol *sym, ChafaColorPair *color_pair_out) { const guint8 *covp = (guint8 *) &sym->coverage [0]; ChafaColorAccum accums [2] = { 0 }; #ifdef HAVE_MMX_INTRINSICS if (chafa_have_mmx ()) calc_colors_mmx (wcell->pixels, accums, covp); else #endif calc_colors_plain (wcell->pixels, accums, covp); if (sym->fg_weight > 1) chafa_color_accum_div_scalar (&accums [1], sym->fg_weight); if (sym->bg_weight > 1) chafa_color_accum_div_scalar (&accums [0], sym->bg_weight); accum_to_color (&accums [1], &color_pair_out->colors [CHAFA_COLOR_PAIR_FG]); accum_to_color (&accums [0], &color_pair_out->colors [CHAFA_COLOR_PAIR_BG]); } void chafa_work_cell_calc_mean_color (const ChafaWorkCell *wcell, ChafaColor *color_out) { ChafaColorAccum accum = { 0 }; const ChafaPixel *block = wcell->pixels; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { chafa_color_accum_add (&accum, &block->col); block++; } chafa_color_accum_div_scalar (&accum, CHAFA_SYMBOL_N_PIXELS); accum_to_color (&accum, color_out); } /* colors must point to an array of two elements */ guint64 chafa_work_cell_to_bitmap (const ChafaWorkCell *wcell, const ChafaColorPair *color_pair) { guint64 bitmap = 0; const ChafaPixel *block = wcell->pixels; gint i; for (i = 0; i < CHAFA_SYMBOL_N_PIXELS; i++) { gint error [2]; bitmap <<= 1; /* FIXME: What to do about alpha? */ error [0] = chafa_color_diff_fast (&block [i].col, &color_pair->colors [0]); error [1] = chafa_color_diff_fast (&block [i].col, &color_pair->colors [1]); if (error [0] > error [1]) bitmap |= 1; } return bitmap; } /* Get cell's pixels sorted by a specific channel. Sorts on demand and caches * the results. */ static const guint8 * work_cell_get_sorted_pixels (ChafaWorkCell *wcell, gint ch) { guint8 *index; const guint8 index_init [CHAFA_SYMBOL_N_PIXELS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 }; index = &wcell->pixels_sorted_index [ch] [0]; if (wcell->have_pixels_sorted_by_channel [ch]) return index; memcpy (index, index_init, CHAFA_SYMBOL_N_PIXELS); chafa_sort_pixel_index_by_channel (index, wcell->pixels, CHAFA_SYMBOL_N_PIXELS, ch); wcell->have_pixels_sorted_by_channel [ch] = TRUE; return index; } void chafa_work_cell_init (ChafaWorkCell *wcell, const ChafaPixel *src_image, gint src_width, gint cx, gint cy) { memset (wcell->have_pixels_sorted_by_channel, 0, sizeof (wcell->have_pixels_sorted_by_channel)); fetch_canvas_pixel_block (src_image, src_width, wcell->pixels, cx, cy); wcell->dominant_channel = -1; } static gint work_cell_get_dominant_channel (ChafaWorkCell *wcell) { const guint8 *sorted_pixels [4]; gint best_range; gint best_ch; gint i; if (wcell->dominant_channel >= 0) return wcell->dominant_channel; for (i = 0; i < 4; i++) sorted_pixels [i] = work_cell_get_sorted_pixels (wcell, i); best_range = wcell->pixels [sorted_pixels [0] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [0] - wcell->pixels [sorted_pixels [0] [0]].col.ch [0]; best_ch = 0; for (i = 1; i < 4; i++) { gint range = wcell->pixels [sorted_pixels [i] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [i] - wcell->pixels [sorted_pixels [i] [0]].col.ch [i]; if (range > best_range) { best_range = range; best_ch = i; } } wcell->dominant_channel = best_ch; return wcell->dominant_channel; } static void work_cell_get_dominant_channels_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, gint *bg_ch_out, gint *fg_ch_out) { gint16 min [2] [4] = { { G_MAXINT16, G_MAXINT16, G_MAXINT16, G_MAXINT16 }, { G_MAXINT16, G_MAXINT16, G_MAXINT16, G_MAXINT16 } }; gint16 max [2] [4] = { { G_MININT16, G_MININT16, G_MININT16, G_MININT16 }, { G_MININT16, G_MININT16, G_MININT16, G_MININT16 } }; gint16 range [2] [4]; gint ch, best_ch [2]; const guint8 *sorted_pixels [4]; gint i, j; if (sym->popcount == 0) { *bg_ch_out = work_cell_get_dominant_channel (wcell); *fg_ch_out = -1; return; } else if (sym->popcount == CHAFA_SYMBOL_N_PIXELS) { *bg_ch_out = -1; *fg_ch_out = work_cell_get_dominant_channel (wcell); return; } for (i = 0; i < 4; i++) sorted_pixels [i] = work_cell_get_sorted_pixels (wcell, i); /* Get minimums */ for (j = 0; j < 4; j++) { gint pen_a = sym->coverage [sorted_pixels [j] [0]]; min [pen_a] [j] = wcell->pixels [sorted_pixels [j] [0]].col.ch [j]; for (i = 1; ; i++) { gint pen_b = sym->coverage [sorted_pixels [j] [i]]; if (pen_b != pen_a) { min [pen_b] [j] = wcell->pixels [sorted_pixels [j] [i]].col.ch [j]; break; } } } /* Get maximums */ for (j = 0; j < 4; j++) { gint pen_a = sym->coverage [sorted_pixels [j] [CHAFA_SYMBOL_N_PIXELS - 1]]; max [pen_a] [j] = wcell->pixels [sorted_pixels [j] [CHAFA_SYMBOL_N_PIXELS - 1]].col.ch [j]; for (i = CHAFA_SYMBOL_N_PIXELS - 2; ; i--) { gint pen_b = sym->coverage [sorted_pixels [j] [i]]; if (pen_b != pen_a) { max [pen_b] [j] = wcell->pixels [sorted_pixels [j] [i]].col.ch [j]; break; } } } /* Find channel with the greatest range */ for (ch = 0; ch < 4; ch++) { range [0] [ch] = max [0] [ch] - min [0] [ch]; range [1] [ch] = max [1] [ch] - min [1] [ch]; } best_ch [0] = 0; best_ch [1] = 0; for (ch = 1; ch < 4; ch++) { if (range [0] [ch] > range [0] [best_ch [0]]) best_ch [0] = ch; if (range [1] [ch] > range [1] [best_ch [1]]) best_ch [1] = ch; } *bg_ch_out = best_ch [0]; *fg_ch_out = best_ch [1]; } /* colors_out must point to an array of two elements */ void chafa_work_cell_get_contrasting_color_pair (ChafaWorkCell *wcell, ChafaColorPair *color_pair_out) { const guint8 *sorted_pixels; sorted_pixels = work_cell_get_sorted_pixels (wcell, work_cell_get_dominant_channel (wcell)); /* Choose two colors by median cut */ color_pair_out->colors [0] = wcell->pixels [sorted_pixels [CHAFA_SYMBOL_N_PIXELS / 4]].col; color_pair_out->colors [1] = wcell->pixels [sorted_pixels [(CHAFA_SYMBOL_N_PIXELS * 3) / 4]].col; } static const ChafaPixel * work_cell_get_nth_sorted_pixel (ChafaWorkCell *wcell, const ChafaSymbol *sym, gint channel, gint pen, gint n) { const guint8 *sorted_pixels; gint i, j; pen ^= 1; sorted_pixels = work_cell_get_sorted_pixels (wcell, channel); for (i = 0, j = 0; ; i++) { j += (sym->coverage [sorted_pixels [i]] ^ pen); if (j > n) return &wcell->pixels [sorted_pixels [i]]; } g_assert_not_reached (); return NULL; } void chafa_work_cell_get_median_colors_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, ChafaColorPair *color_pair_out) { gint bg_ch, fg_ch; /* This is extremely slow and makes almost no difference */ work_cell_get_dominant_channels_for_symbol (wcell, sym, &bg_ch, &fg_ch); if (bg_ch < 0) { color_pair_out->colors [0] = color_pair_out->colors [1] = work_cell_get_nth_sorted_pixel (wcell, sym, fg_ch, 1, sym->popcount / 2)->col; } else if (fg_ch < 0) { color_pair_out->colors [0] = color_pair_out->colors [1] = work_cell_get_nth_sorted_pixel (wcell, sym, bg_ch, 0, (CHAFA_SYMBOL_N_PIXELS - sym->popcount) / 2)->col; } else { color_pair_out->colors [CHAFA_COLOR_PAIR_FG] = work_cell_get_nth_sorted_pixel (wcell, sym, fg_ch, 1, sym->popcount / 2)->col; color_pair_out->colors [CHAFA_COLOR_PAIR_BG] = work_cell_get_nth_sorted_pixel (wcell, sym, bg_ch, 0, (CHAFA_SYMBOL_N_PIXELS - sym->popcount) / 2)->col; } } chafa-1.8.0/chafa/internal/chafa-work-cell.h000066400000000000000000000041671411352071600205600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __CHAFA_WORK_CELL_H__ #define __CHAFA_WORK_CELL_H__ G_BEGIN_DECLS typedef struct ChafaWorkCell ChafaWorkCell; struct ChafaWorkCell { ChafaPixel pixels [CHAFA_SYMBOL_N_PIXELS]; guint8 pixels_sorted_index [4] [CHAFA_SYMBOL_N_PIXELS]; guint8 have_pixels_sorted_by_channel [4]; gint dominant_channel; }; /* Currently unused */ typedef enum { CHAFA_PICK_CONSIDER_INVERTED = (1 << 0), CHAFA_PICK_HAVE_ALPHA = (1 << 1) } ChafaPickFlags; void chafa_work_cell_init (ChafaWorkCell *wcell, const ChafaPixel *src_image, gint src_width, gint cx, gint cy); void chafa_work_cell_get_mean_colors_for_symbol (const ChafaWorkCell *wcell, const ChafaSymbol *sym, ChafaColorPair *color_pair_out); void chafa_work_cell_get_median_colors_for_symbol (ChafaWorkCell *wcell, const ChafaSymbol *sym, ChafaColorPair *color_pair_out); void chafa_work_cell_get_contrasting_color_pair (ChafaWorkCell *wcell, ChafaColorPair *color_pair_out); void chafa_work_cell_calc_mean_color (const ChafaWorkCell *wcell, ChafaColor *color_out); guint64 chafa_work_cell_to_bitmap (const ChafaWorkCell *wcell, const ChafaColorPair *color_pair); G_END_DECLS #endif /* __CHAFA_WORK_CELL_H__ */ chafa-1.8.0/chafa/internal/smolscale/000077500000000000000000000000001411352071600174225ustar00rootroot00000000000000chafa-1.8.0/chafa/internal/smolscale/COPYING000066400000000000000000000013301411352071600204520ustar00rootroot00000000000000This software is Copyright © 2019 Hans Petter Jansson. All rights reserved. You may use and redistribute this software under the terms and conditions laid out in either the BSD 4-clause license (see COPYING.BSD-4) or the LGPL version 3 (see COPYING.LGPLv3). The practical intent is that you should be able to use the software in an LGPLv3-compatible project without giving credit (although it would be nice if you did so). However, if you're using it in a closed-source project, you must either publish it separately in an LGPLv3-licensed library and link with that, or credit the contributors in your advertising materials. If you want to use it under a different license, feel free to contact me by e-mail at . chafa-1.8.0/chafa/internal/smolscale/COPYING.BSD-4000066400000000000000000000032751411352071600212340ustar00rootroot00000000000000Copyright © 2019 Hans Petter Jansson. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 4. All advertising materials mentioning features or use of this software must display the following acknowledgement: 'This product includes software developed by Hans Petter Jansson (https://hpjansson.org/).' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. chafa-1.8.0/chafa/internal/smolscale/COPYING.LGPLv3000066400000000000000000000167441411352071600214770ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. chafa-1.8.0/chafa/internal/smolscale/Makefile.am000066400000000000000000000012241411352071600214550ustar00rootroot00000000000000noinst_LTLIBRARIES = libsmolscale.la SMOLSCALE_CFLAGS = $(LIBCHAFA_CFLAGS) SMOLSCALE_LDFLAGS = $(LIBCHAFA_LDFLAGS) if HAVE_AVX2_INTRINSICS SMOLSCALE_CFLAGS += -DSMOL_WITH_AVX2 endif libsmolscale_la_CFLAGS = $(SMOLSCALE_CFLAGS) libsmolscale_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) libsmolscale_la_LIBADD = libsmolscale_la_SOURCES = \ smolscale.c \ smolscale.h \ smolscale-private.h if HAVE_AVX2_INTRINSICS noinst_LTLIBRARIES += libsmolscale-avx2.la libsmolscale_la_LIBADD += libsmolscale-avx2.la libsmolscale_avx2_la_SOURCES = smolscale-avx2.c libsmolscale_avx2_la_CFLAGS = $(SMOLSCALE_CFLAGS) -mavx2 libsmolscale_avx2_la_LDFLAGS = $(SMOLSCALE_LDFLAGS) endif chafa-1.8.0/chafa/internal/smolscale/README000066400000000000000000000025041411352071600203030ustar00rootroot00000000000000Smolscale ========= Smolscale is a smol piece of C code for quickly scaling images to a reasonable level of quality using CPU resources only (no GPU). It operates on 4-channel data with 32 bits per pixel, i.e. packed RGBA, ARGB, BGRA, etc. It supports both premultiplied and unassociated alpha and can convert between the two. It is host byte ordering agnostic. The design goals are: * High throughput: Optimized code, within reason. Easily parallelizable. * Decent quality: No "jaggies" as produced by nearest-neighbor scaling. * Low memory overhead: Mostly on the stack. * Simplicity: A modern C toolchain as the only dependency. * Ease of use: One-shot and row-batch APIs. Usage ----- First, read COPYING. If your project meets the requirements, you should be able to copy the following files into it and add it to your build with minimal fuss: smolscale.c smolscale.h smolscale-private.h If you want AVX2 SIMD support, optionally copy this additional file and compile everything with -DSMOL_WITH_AVX2: smolscale-avx2.c Keep in mind that this file is mostly just a straight copy of the generic code for the time being. Still, you will get a performance boost by building it with -mavx2 and letting Smolscale pick the implementation at runtime. The API documentation lives in smolscale.h along with the public declarations. chafa-1.8.0/chafa/internal/smolscale/smolscale-avx2.c000066400000000000000000003445471411352071600224470ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2021 Hans Petter Jansson. See COPYING for details. */ #include /* assert */ #include /* malloc, free, alloca */ #include /* ptrdiff_t */ #include /* memset */ #include #include #include "smolscale-private.h" /* --- Linear interpolation helpers --- */ #define LERP_SIMD256_EPI32(a, b, f) \ _mm256_add_epi32 ( \ _mm256_srli_epi32 ( \ _mm256_mullo_epi32 ( \ _mm256_sub_epi32 ((a), (b)), factors), 8), (b)) #define LERP_SIMD128_EPI32(a, b, f) \ _mm_add_epi32 ( \ _mm_srli_epi32 ( \ _mm_mullo_epi32 ( \ _mm_sub_epi32 ((a), (b)), factors), 8), (b)) #define LERP_SIMD256_EPI32_AND_MASK(a, b, f, mask) \ _mm256_and_si256 (LERP_SIMD256_EPI32 ((a), (b), (f)), (mask)) #define LERP_SIMD128_EPI32_AND_MASK(a, b, f, mask) \ _mm_and_si128 (LERP_SIMD128_EPI32 ((a), (b), (f)), (mask)) /* --- Premultiplication --- */ #define INVERTED_DIV_SHIFT 21 #define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) #define INVERTED_DIV_ROUNDING_128BPP \ (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) /* This table is used to divide by an integer [1..255] using only a lookup, * multiplication and a shift. This is faster than plain division on most * architectures. * * Each entry represents the integer 2097152 (1 << 21) divided by the index * of the entry. Consequently, * * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 * * (1 << 20) is added for nearest rounding. It would've been nice to keep * this table in uint16_t, but alas, we need the extra bits for sufficient * precision. */ static const uint32_t inverted_div_table [256] = { 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, }; /* Masking and shifting out the results is left to the caller. In * and out may not overlap. */ static SMOL_INLINE void unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); } static SMOL_INLINE void unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) >> INVERTED_DIV_SHIFT); out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) >> INVERTED_DIV_SHIFT); } static SMOL_INLINE uint64_t unpremul_p_to_u_64bpp (const uint64_t in, uint8_t alpha) { uint64_t in_128bpp [2]; uint64_t out_128bpp [2]; in_128bpp [0] = (in & 0x000000ff000000ff); in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); return (out_128bpp [0] & 0x000000ff000000ff) | ((out_128bpp [1] & 0x000000ff000000ff) << 16); } static SMOL_INLINE uint64_t premul_u_to_p_64bpp (const uint64_t in, uint8_t alpha) { return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; } /* --- Packing --- */ /* It's nice to be able to shift by a negative amount */ #define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) /* This is kind of bulky (~13 x86 insns), but it's about the same as using * unions, and we don't have to worry about endianness. */ #define PACK_FROM_1234_64BPP(in, a, b, c, d) \ ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) #define PACK_FROM_1234_128BPP(in, a, b, c, d) \ ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) #define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) #define PACK_FROM_1324_64BPP(in, a, b, c, d) \ ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) /* Note: May not be needed */ #define PACK_FROM_1324_128BPP(in, a, b, c, d) \ ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) /* Pack p -> p */ static SMOL_INLINE uint32_t pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) { return in | (in >> 24); } static void pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); } } static void pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { /* FIXME: Would be faster to shift directly */ uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { /* FIXME: Would be faster to shift directly */ uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ { \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ } DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) static SMOL_INLINE uint32_t pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) { /* FIXME: Are masks needed? */ return ((in [0] >> 8) & 0xff000000) | ((in [0] << 16) & 0x00ff0000) | ((in [1] >> 24) & 0x0000ff00) | (in [1] & 0x000000ff); } static void pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); row_in += 2; } } #define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ return PACK_FROM_1234_128BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) static void pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = *row_in >> 32; *(row_out++) = *(row_in++); *(row_out++) = *(row_in++) >> 32; } } static void pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = row_in [1] >> 32; *(row_out++) = row_in [0]; *(row_out++) = row_in [0] >> 32; row_in += 2; } } /* Pack p (alpha last) -> u */ static SMOL_INLINE uint32_t pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) { uint8_t alpha = in; in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; return in | (in >> 24); } static void pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); } } static void pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ { \ uint8_t alpha = in; \ in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ } DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) #define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint64_t t [2]; \ uint8_t alpha = in [1]; \ unpremul_p_to_u_128bpp (in, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000) | alpha; \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) static void pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } /* Pack p (alpha first) -> u */ static SMOL_INLINE uint32_t pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) { uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); return in | (in >> 24); } static void pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); } } static void pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 16; *(row_out++) = p >> 8; *(row_out++) = p; } } static void pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p; *(row_out++) = p >> 8; *(row_out++) = p >> 16; } } #define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ { \ uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ } DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) #define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint64_t t [2]; \ uint8_t alpha = in [0] >> 32; \ unpremul_p_to_u_128bpp (in, t, alpha); \ t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) static void pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 16; *(row_out++) = p >> 8; *(row_out++) = p; } } static void pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p; *(row_out++) = p >> 8; *(row_out++) = p >> 16; } } /* Pack i (alpha last) to u */ static SMOL_INLINE uint32_t pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) { uint8_t alpha = (in [1] >> 8) & 0xff; uint64_t t [2]; unpremul_i_to_u_128bpp (in, t, alpha); return ((t [0] >> 8) & 0xff000000) | ((t [0] << 16) & 0x00ff0000) | ((t [1] >> 24) & 0x0000ff00) | alpha; } static void pack_8x_123a_i_to_xxxx_u_128bpp (const uint64_t * SMOL_RESTRICT *in, uint32_t * SMOL_RESTRICT *out, uint32_t * out_max, const __m256i channel_shuf) { #define ALPHA_MUL (1 << (INVERTED_DIV_SHIFT - 8)) #define ALPHA_MASK SMOL_8X1BIT (0, 1, 0, 0, 0, 1, 0, 0) const __m256i ones = _mm256_set_epi32 ( ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL, ALPHA_MUL); const __m256i alpha_clean_mask = _mm256_set_epi32 ( 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff, 0x000000ff); const __m256i rounding = _mm256_set_epi32 ( INVERTED_DIV_ROUNDING, 0, INVERTED_DIV_ROUNDING, INVERTED_DIV_ROUNDING, INVERTED_DIV_ROUNDING, 0, INVERTED_DIV_ROUNDING, INVERTED_DIV_ROUNDING); __m256i m00, m01, m02, m03, m04, m05, m06, m07, m08; const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; SMOL_ASSUME_ALIGNED (my_in, __m256i * SMOL_RESTRICT); while ((ptrdiff_t) (my_out + 1) <= (ptrdiff_t) out_max) { /* Load inputs */ m00 = _mm256_stream_load_si256 (my_in); my_in++; m01 = _mm256_stream_load_si256 (my_in); my_in++; m02 = _mm256_stream_load_si256 (my_in); my_in++; m03 = _mm256_stream_load_si256 (my_in); my_in++; /* Load alpha factors */ m04 = _mm256_slli_si256 (m00, 4); m06 = _mm256_srli_si256 (m03, 4); m05 = _mm256_blend_epi32 (m04, m01, ALPHA_MASK); m07 = _mm256_blend_epi32 (m06, m02, ALPHA_MASK); m07 = _mm256_srli_si256 (m07, 4); m04 = _mm256_blend_epi32 (m05, m07, SMOL_8X1BIT (0, 0, 1, 1, 0, 0, 1, 1)); m04 = _mm256_srli_epi32 (m04, 8); m04 = _mm256_and_si256 (m04, alpha_clean_mask); m04 = _mm256_i32gather_epi32 ((const void *) inverted_div_table, m04, 4); /* 2 pixels times 4 */ m05 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (3, 3, 3, 3)); m06 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (2, 2, 2, 2)); m07 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (1, 1, 1, 1)); m08 = _mm256_shuffle_epi32 (m04, SMOL_4X2BIT (0, 0, 0, 0)); m05 = _mm256_blend_epi32 (m05, ones, ALPHA_MASK); m06 = _mm256_blend_epi32 (m06, ones, ALPHA_MASK); m07 = _mm256_blend_epi32 (m07, ones, ALPHA_MASK); m08 = _mm256_blend_epi32 (m08, ones, ALPHA_MASK); m05 = _mm256_mullo_epi32 (m05, m00); m06 = _mm256_mullo_epi32 (m06, m01); m07 = _mm256_mullo_epi32 (m07, m02); m08 = _mm256_mullo_epi32 (m08, m03); m05 = _mm256_add_epi32 (m05, rounding); m06 = _mm256_add_epi32 (m06, rounding); m07 = _mm256_add_epi32 (m07, rounding); m08 = _mm256_add_epi32 (m08, rounding); m05 = _mm256_srli_epi32 (m05, INVERTED_DIV_SHIFT); m06 = _mm256_srli_epi32 (m06, INVERTED_DIV_SHIFT); m07 = _mm256_srli_epi32 (m07, INVERTED_DIV_SHIFT); m08 = _mm256_srli_epi32 (m08, INVERTED_DIV_SHIFT); /* Pack and store */ m00 = _mm256_packus_epi32 (m05, m06); m01 = _mm256_packus_epi32 (m07, m08); m00 = _mm256_packus_epi16 (m00, m01); m00 = _mm256_shuffle_epi8 (m00, channel_shuf); m00 = _mm256_permute4x64_epi64 (m00, SMOL_4X2BIT (3, 1, 2, 0)); m00 = _mm256_shuffle_epi32 (m00, SMOL_4X2BIT (3, 1, 2, 0)); _mm256_storeu_si256 (my_out, m00); my_out += 1; } *out = (uint32_t * SMOL_RESTRICT) my_out; *in = (const uint64_t * SMOL_RESTRICT) my_in; #undef ALPHA_MUL #undef ALPHA_MASK } /* PACK_SHUF_MM256_EPI8() * * Generates a shuffling register for packing 8bpc pixel channels in the * provided order. The order (1, 2, 3, 4) is neutral and corresponds to * * _mm256_set_epi8 (13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2, * 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2); */ #define SHUF_ORDER 0x01000302U #define SHUF_CH(n) ((char) (SHUF_ORDER >> ((4 - (n)) * 8))) #define SHUF_QUAD_CH(q, n) (4 * (q) + SHUF_CH (n)) #define SHUF_QUAD(q, a, b, c, d) \ SHUF_QUAD_CH ((q), (a)), \ SHUF_QUAD_CH ((q), (b)), \ SHUF_QUAD_CH ((q), (c)), \ SHUF_QUAD_CH ((q), (d)) #define PACK_SHUF_EPI8_LANE(a, b, c, d) \ SHUF_QUAD (3, (a), (b), (c), (d)), \ SHUF_QUAD (2, (a), (b), (c), (d)), \ SHUF_QUAD (1, (a), (b), (c), (d)), \ SHUF_QUAD (0, (a), (b), (c), (d)) #define PACK_SHUF_MM256_EPI8(a, b, c, d) _mm256_set_epi8 ( \ PACK_SHUF_EPI8_LANE ((a), (b), (c), (d)), \ PACK_SHUF_EPI8_LANE ((a), (b), (c), (d))) static void pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { #define ALPHA_MUL (1 << (INVERTED_DIV_SHIFT - 8)) #define ALPHA_MASK SMOL_8X1BIT (0, 1, 0, 0, 0, 1, 0, 0) uint32_t *row_out_max = row_out + n_pixels; const __m256i channel_shuf = PACK_SHUF_MM256_EPI8 (1, 2, 3, 4); SMOL_ASSUME_ALIGNED (row_in, const uint64_t * SMOL_RESTRICT); pack_8x_123a_i_to_xxxx_u_128bpp (&row_in, &row_out, row_out_max, channel_shuf); while (row_out != row_out_max) { *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; } } static void pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint8_t alpha = (in [1] >> 8) & 0xff; \ uint64_t t [2]; \ unpremul_i_to_u_128bpp (in, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ const __m256i channel_shuf = PACK_SHUF_MM256_EPI8 ((a), (b), (c), (d)); \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ pack_8x_123a_i_to_xxxx_u_128bpp (&row_in, &row_out, row_out_max, \ channel_shuf); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) /* Unpack p -> p */ static SMOL_INLINE uint64_t unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) { return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); } /* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); * It results in a different channel ordering, so it'd be important to match with * the right kind of re-pack. */ static void unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); } } static SMOL_INLINE uint64_t unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) { return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) | ((uint64_t) p [2] << 32) | 0xff; } static void unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); row_in += 3; } } static SMOL_INLINE void unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); } static void unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); row_out += 2; } } static SMOL_INLINE void unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, uint64_t *out) { out [0] = ((uint64_t) in [0] << 32) | in [1]; out [1] = ((uint64_t) in [2] << 32) | 0xff; } static void unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); row_in += 3; row_out += 2; } } /* Unpack u (alpha first) -> p */ static SMOL_INLINE uint64_t unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); uint8_t alpha = p >> 24; return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); } static void unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); } } static SMOL_INLINE void unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); uint8_t alpha = p >> 24; p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } static void unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u -> i (common) */ static void unpack_8x_xxxx_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT *in, uint64_t * SMOL_RESTRICT *out, uint64_t *out_max, const __m256i channel_shuf) { const __m256i zero = _mm256_setzero_si256 (); const __m256i factor_shuf = _mm256_set_epi8 ( -1, 12, -1, -1, -1, 12, -1, 12, -1, 4, -1, -1, -1, 4, -1, 4, -1, 12, -1, -1, -1, 12, -1, 12, -1, 4, -1, -1, -1, 4, -1, 4); const __m256i alpha_mul = _mm256_set_epi16 ( 0, 0x100, 0, 0, 0, 0x100, 0, 0, 0, 0x100, 0, 0, 0, 0x100, 0, 0); const __m256i alpha_add = _mm256_set_epi16 ( 0, 0x80, 0, 0, 0, 0x80, 0, 0, 0, 0x80, 0, 0, 0, 0x80, 0, 0); __m256i m0, m1, m2, m3, m4, m5, m6; __m256i fact1, fact2; const __m256i * SMOL_RESTRICT my_in = (const __m256i * SMOL_RESTRICT) *in; __m256i * SMOL_RESTRICT my_out = (__m256i * SMOL_RESTRICT) *out; SMOL_ASSUME_ALIGNED (my_out, __m256i * SMOL_RESTRICT); while ((ptrdiff_t) (my_out + 4) <= (ptrdiff_t) out_max) { m0 = _mm256_loadu_si256 (my_in); my_in++; m0 = _mm256_shuffle_epi8 (m0, channel_shuf); m0 = _mm256_permute4x64_epi64 (m0, SMOL_4X2BIT (3, 1, 2, 0)); m1 = _mm256_unpacklo_epi8 (m0, zero); m2 = _mm256_unpackhi_epi8 (m0, zero); fact1 = _mm256_shuffle_epi8 (m1, factor_shuf); fact2 = _mm256_shuffle_epi8 (m2, factor_shuf); fact1 = _mm256_or_si256 (fact1, alpha_mul); fact2 = _mm256_or_si256 (fact2, alpha_mul); m1 = _mm256_mullo_epi16 (m1, fact1); m2 = _mm256_mullo_epi16 (m2, fact2); m1 = _mm256_add_epi16 (m1, alpha_add); m2 = _mm256_add_epi16 (m2, alpha_add); m1 = _mm256_permute4x64_epi64 (m1, SMOL_4X2BIT (3, 1, 2, 0)); m2 = _mm256_permute4x64_epi64 (m2, SMOL_4X2BIT (3, 1, 2, 0)); m3 = _mm256_unpacklo_epi16 (m1, zero); m4 = _mm256_unpackhi_epi16 (m1, zero); m5 = _mm256_unpacklo_epi16 (m2, zero); m6 = _mm256_unpackhi_epi16 (m2, zero); _mm256_store_si256 ((__m256i *) my_out, m3); my_out++; _mm256_store_si256 ((__m256i *) my_out, m4); my_out++; _mm256_store_si256 ((__m256i *) my_out, m5); my_out++; _mm256_store_si256 ((__m256i *) my_out, m6); my_out++; } *out = (uint64_t * SMOL_RESTRICT) my_out; *in = (const uint32_t * SMOL_RESTRICT) my_in; } /* Unpack u (alpha first) -> i */ static SMOL_INLINE void unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint64_t alpha = p >> 24; out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | (alpha << 8) | 0x80; } static void unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; const __m256i channel_shuf = _mm256_set_epi8 ( 12,15,14,13, 8,11,10,9, 4,7,6,5, 0,3,2,1, 12,15,14,13, 8,11,10,9, 4,7,6,5, 0,3,2,1); SMOL_ASSUME_ALIGNED (row_out, uint64_t * SMOL_RESTRICT); unpack_8x_xxxx_u_to_123a_i_128bpp (&row_in, &row_out, row_out_max, channel_shuf); while (row_out != row_out_max) { unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u (alpha last) -> p */ static SMOL_INLINE uint64_t unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); } static void unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); } } static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } static void unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u (alpha last) -> i */ static SMOL_INLINE void unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint64_t alpha = p & 0xff; out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | (alpha << 8) | 0x80; } static void unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; const __m256i channel_shuf = _mm256_set_epi8 ( 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2, 13,12,15,14, 9,8,11,10, 5,4,7,6, 1,0,3,2); SMOL_ASSUME_ALIGNED (row_out, uint64_t * SMOL_RESTRICT); unpack_8x_xxxx_u_to_123a_i_128bpp (&row_in, &row_out, row_out_max, channel_shuf); while (row_out != row_out_max) { unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); row_out += 2; } } /* --- Filter helpers --- */ static SMOL_INLINE const uint32_t * inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t inrow_ofs) { return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; } static SMOL_INLINE uint32_t * outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t outrow_ofs) { return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; } static SMOL_INLINE uint64_t weight_pixel_64bpp (uint64_t p, uint16_t w) { return ((p * w) >> 8) & 0x00ff00ff00ff00ff; } /* p and out may be the same address */ static SMOL_INLINE void weight_pixel_128bpp (uint64_t *p, uint64_t *out, uint16_t w) { out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; } static SMOL_INLINE void sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT accum, uint32_t n) { const uint64_t *pp_end; const uint64_t * SMOL_RESTRICT pp = *parts_in; SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t)); for (pp_end = pp + n; pp < pp_end; pp++) { *accum += *pp; } *parts_in = pp; } static SMOL_INLINE void sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT accum, uint32_t n) { const uint64_t *pp_end; const uint64_t * SMOL_RESTRICT pp = *parts_in; SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t) * 2); for (pp_end = pp + n * 2; pp < pp_end; ) { accum [0] += *(pp++); accum [1] += *(pp++); } *parts_in = pp; } static SMOL_INLINE uint64_t scale_64bpp (uint64_t accum, uint64_t multiplier) { uint64_t a, b; /* Average the inputs */ a = ((accum & 0x0000ffff0000ffffULL) * multiplier + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; /* Return pixel */ return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); } static SMOL_INLINE uint64_t scale_128bpp_half (uint64_t accum, uint64_t multiplier) { uint64_t a, b; a = accum & 0x00000000ffffffffULL; a = (a * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; b = (accum & 0xffffffff00000000ULL) >> 32; b = (b * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; return (a & 0x000000000000ffffULL) | ((b & 0x000000000000ffffULL) << 32); } static SMOL_INLINE void scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, uint64_t multiplier, uint64_t ** SMOL_RESTRICT row_parts_out) { *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); } static void add_parts (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n) { const uint64_t *parts_in_max = parts_in + n; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in + 4 <= parts_in_max) { __m256i m0, m1; m0 = _mm256_stream_load_si256 ((const __m256i *) parts_in); parts_in += 4; m1 = _mm256_load_si256 ((__m256i *) parts_acc_out); m0 = _mm256_add_epi32 (m0, m1); _mm256_store_si256 ((__m256i *) parts_acc_out, m0); parts_acc_out += 4; } while (parts_in < parts_in_max) *(parts_acc_out++) += *(parts_in++); } /* --- Horizontal scaling --- */ #define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ static void \ interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ const uint64_t * SMOL_RESTRICT row_parts_in, \ uint64_t * SMOL_RESTRICT row_parts_out) \ { \ uint64_t p, q; \ const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ uint64_t F; \ uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ int i; \ \ SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ \ do \ { \ uint64_t accum = 0; \ \ for (i = 0; i < (1 << (n_halvings)); i++) \ { \ row_parts_in += *(ofs_x++); \ F = *(ofs_x++); \ \ p = *row_parts_in; \ q = *(row_parts_in + 1); \ \ accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ } \ *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ } \ while (row_parts_out != row_parts_out_max); \ } \ \ static void \ interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ const uint64_t * SMOL_RESTRICT row_parts_in, \ uint64_t * SMOL_RESTRICT row_parts_out) \ { \ const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ const __m128i mask128 = _mm_set_epi32 ( \ 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); \ const __m256i zero256 = _mm256_setzero_si256 (); \ int i; \ \ SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ \ while (row_parts_out != row_parts_out_max) \ { \ __m256i a0 = _mm256_setzero_si256 (); \ __m128i a1; \ \ for (i = 0; i < (1 << ((n_halvings) - 1)); i++) \ { \ __m256i m0, m1; \ __m256i factors; \ __m128i n0, n1, n2, n3, n4, n5; \ \ row_parts_in += *(ofs_x++) * 2; \ n4 = _mm_set1_epi16 (*(ofs_x++)); \ n0 = _mm_load_si128 ((__m128i *) row_parts_in); \ n1 = _mm_load_si128 ((__m128i *) row_parts_in + 1); \ \ row_parts_in += *(ofs_x++) * 2; \ n5 = _mm_set1_epi16 (*(ofs_x++)); \ n2 = _mm_load_si128 ((__m128i *) row_parts_in); \ n3 = _mm_load_si128 ((__m128i *) row_parts_in + 1); \ \ m0 = _mm256_set_m128i (n2, n0); \ m1 = _mm256_set_m128i (n3, n1); \ factors = _mm256_set_m128i (n5, n4); \ factors = _mm256_blend_epi16 (factors, zero256, 0xaa); \ \ m0 = LERP_SIMD256_EPI32 (m0, m1, factors); \ a0 = _mm256_add_epi32 (a0, m0); \ } \ \ a1 = _mm_add_epi32 (_mm256_extracti128_si256 (a0, 0), \ _mm256_extracti128_si256 (a0, 1)); \ a1 = _mm_srli_epi32 (a1, (n_halvings)); \ a1 = _mm_and_si128 (a1, mask128); \ _mm_store_si128 ((__m128i *) row_parts_out, a1); \ row_parts_out += 2; \ } \ } static void interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t p, q; const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; uint64_t F; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); do { row_parts_in += *(ofs_x++); F = *(ofs_x++); p = *row_parts_in; q = *(row_parts_in + 1); *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (row_parts_out != row_parts_out_max); } static void interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; const __m256i mask256 = _mm256_set_epi32 ( 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); const __m128i mask128 = _mm_set_epi32 ( 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); const __m256i zero = _mm256_setzero_si256 (); SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); while (row_parts_out + 4 <= row_parts_out_max) { __m256i m0, m1; __m256i factors; __m128i n0, n1, n2, n3, n4, n5; row_parts_in += *(ofs_x++) * 2; n4 = _mm_set1_epi16 (*(ofs_x++)); n0 = _mm_load_si128 ((__m128i *) row_parts_in); n1 = _mm_load_si128 ((__m128i *) row_parts_in + 1); row_parts_in += *(ofs_x++) * 2; n5 = _mm_set1_epi16 (*(ofs_x++)); n2 = _mm_load_si128 ((__m128i *) row_parts_in); n3 = _mm_load_si128 ((__m128i *) row_parts_in + 1); m0 = _mm256_set_m128i (n2, n0); m1 = _mm256_set_m128i (n3, n1); factors = _mm256_set_m128i (n5, n4); factors = _mm256_blend_epi16 (factors, zero, 0xaa); m0 = LERP_SIMD256_EPI32_AND_MASK (m0, m1, factors, mask256); _mm256_store_si256 ((__m256i *) row_parts_out, m0); row_parts_out += 4; } /* No need for a loop here; let compiler know we're doing it at most once */ if (row_parts_out != row_parts_out_max) { __m128i m0, m1; __m128i factors; uint32_t f; row_parts_in += *(ofs_x++) * 2; f = *(ofs_x++); factors = _mm_set1_epi32 ((uint32_t) f); m0 = _mm_stream_load_si128 ((__m128i *) row_parts_in); m1 = _mm_stream_load_si128 ((__m128i *) row_parts_in + 1); m0 = LERP_SIMD128_EPI32_AND_MASK (m0, m1, factors, mask128); _mm_store_si128 ((__m128i *) row_parts_out, m0); row_parts_out += 2; } } DEF_INTERP_HORIZONTAL_BILINEAR(1) DEF_INTERP_HORIZONTAL_BILINEAR(2) DEF_INTERP_HORIZONTAL_BILINEAR(3) DEF_INTERP_HORIZONTAL_BILINEAR(4) DEF_INTERP_HORIZONTAL_BILINEAR(5) DEF_INTERP_HORIZONTAL_BILINEAR(6) static void interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t *row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint64_t * SMOL_RESTRICT pp; const uint16_t *ofs_x = scale_ctx->offsets_x; uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; uint64_t accum = 0; uint64_t p, q, r, s; uint32_t n; uint64_t F; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); pp = row_parts_in; p = weight_pixel_64bpp (*(pp++), 256); n = *(ofs_x++); while (row_parts_out != row_parts_out_max) { sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); F = *(ofs_x++); n = *(ofs_x++); r = *(pp++); s = r * F; q = (s >> 8) & 0x00ff00ff00ff00ffULL; accum += p + q; /* (255 * r) - (F * r) */ p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); accum = 0; } /* Final box optionally features the rightmost fractional pixel */ sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); q = 0; F = *(ofs_x); if (F > 0) q = weight_pixel_64bpp (*(pp), F); accum += p + q; *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); } static void interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t *row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint64_t * SMOL_RESTRICT pp; const uint16_t *ofs_x = scale_ctx->offsets_x; uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; uint64_t accum [2] = { 0, 0 }; uint64_t p [2], q [2], r [2], s [2]; uint32_t n; uint64_t F; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); pp = row_parts_in; p [0] = *(pp++); p [1] = *(pp++); weight_pixel_128bpp (p, p, 256); n = *(ofs_x++); while (row_parts_out != row_parts_out_max) { sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); F = *(ofs_x++); n = *(ofs_x++); r [0] = *(pp++); r [1] = *(pp++); s [0] = r [0] * F; s [1] = r [1] * F; q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; accum [0] += p [0] + q [0]; accum [1] += p [1] + q [1]; p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; scale_and_store_128bpp (accum, scale_ctx->span_mul_x, (uint64_t ** SMOL_RESTRICT) &row_parts_out); accum [0] = 0; accum [1] = 0; } /* Final box optionally features the rightmost fractional pixel */ sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); q [0] = 0; q [1] = 0; F = *(ofs_x); if (F > 0) { q [0] = *(pp++); q [1] = *(pp++); weight_pixel_128bpp (q, q, F); } accum [0] += p [0] + q [0]; accum [1] += p [1] + q [1]; scale_and_store_128bpp (accum, scale_ctx->span_mul_x, (uint64_t ** SMOL_RESTRICT) &row_parts_out); } static void interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; uint64_t part; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); part = *row_parts_in; while (row_parts_out != row_parts_out_max) *(row_parts_out++) = part; } static void interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); while (row_parts_out != row_parts_out_max) { *(row_parts_out++) = row_parts_in [0]; *(row_parts_out++) = row_parts_in [1]; } } static void interp_horizontal_copy_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * sizeof (uint64_t)); } static void interp_horizontal_copy_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * 2 * sizeof (uint64_t)); } static void scale_horizontal (const SmolScaleCtx *scale_ctx, const uint32_t *row_in, uint64_t *row_parts_out) { uint64_t * SMOL_RESTRICT unpacked_in; /* FIXME: Allocate less for 64bpp */ unpacked_in = smol_alloca_aligned (scale_ctx->width_in * sizeof (uint64_t) * 2); scale_ctx->unpack_row_func (row_in, unpacked_in, scale_ctx->width_in); scale_ctx->hfilter_func (scale_ctx, unpacked_in, row_parts_out); } /* --- Vertical scaling --- */ static void update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index) { uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; if (new_in_ofs == vertical_ctx->in_ofs) return; if (new_in_ofs == vertical_ctx->in_ofs + 1) { uint64_t *t = vertical_ctx->parts_row [0]; vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; vertical_ctx->parts_row [1] = t; scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), vertical_ctx->parts_row [1]); } else { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs), vertical_ctx->parts_row [0]); scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), vertical_ctx->parts_row [1]); } vertical_ctx->in_ofs = new_in_ofs; } static void interp_vertical_bilinear_store_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT parts_out, uint32_t width) { uint64_t *parts_out_last = parts_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (parts_out != parts_out_last); } static void interp_vertical_bilinear_add_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_out_last = accum_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (accum_out != accum_out_last); } static void interp_vertical_bilinear_store_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT parts_out, uint32_t width) { uint64_t *parts_out_last = parts_out + width; const __m256i mask = _mm256_set_epi32 ( 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); __m256i F256; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); F256 = _mm256_set1_epi32 ((uint32_t) F); while (parts_out + 8 <= parts_out_last) { __m256i m0, m1, m2, m3; m0 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); top_row_parts_in += 4; m2 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); top_row_parts_in += 4; m1 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); bottom_row_parts_in += 4; m3 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); bottom_row_parts_in += 4; m0 = _mm256_sub_epi32 (m0, m1); m2 = _mm256_sub_epi32 (m2, m3); m0 = _mm256_mullo_epi32 (m0, F256); m2 = _mm256_mullo_epi32 (m2, F256); m0 = _mm256_srli_epi32 (m0, 8); m2 = _mm256_srli_epi32 (m2, 8); m0 = _mm256_add_epi32 (m0, m1); m2 = _mm256_add_epi32 (m2, m3); m0 = _mm256_and_si256 (m0, mask); m2 = _mm256_and_si256 (m2, mask); _mm256_store_si256 ((__m256i *) parts_out, m0); parts_out += 4; _mm256_store_si256 ((__m256i *) parts_out, m2); parts_out += 4; } while (parts_out != parts_out_last) { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } } static void interp_vertical_bilinear_add_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_out_last = accum_out + width; const __m256i mask = _mm256_set_epi32 ( 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff, 0x00ffffff); __m256i F256; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); F256 = _mm256_set1_epi32 ((uint32_t) F); while (accum_out + 8 <= accum_out_last) { __m256i m0, m1, m2, m3, o0, o1; m0 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); top_row_parts_in += 4; m2 = _mm256_load_si256 ((const __m256i *) top_row_parts_in); top_row_parts_in += 4; m1 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); bottom_row_parts_in += 4; m3 = _mm256_load_si256 ((const __m256i *) bottom_row_parts_in); bottom_row_parts_in += 4; o0 = _mm256_load_si256 ((const __m256i *) accum_out); o1 = _mm256_load_si256 ((const __m256i *) accum_out + 4); m0 = _mm256_sub_epi32 (m0, m1); m2 = _mm256_sub_epi32 (m2, m3); m0 = _mm256_mullo_epi32 (m0, F256); m2 = _mm256_mullo_epi32 (m2, F256); m0 = _mm256_srli_epi32 (m0, 8); m2 = _mm256_srli_epi32 (m2, 8); m0 = _mm256_add_epi32 (m0, m1); m2 = _mm256_add_epi32 (m2, m3); m0 = _mm256_and_si256 (m0, mask); m2 = _mm256_and_si256 (m2, mask); o0 = _mm256_add_epi32 (o0, m0); o1 = _mm256_add_epi32 (o1, m2); _mm256_store_si256 ((__m256i *) accum_out, o0); accum_out += 4; _mm256_store_si256 ((__m256i *) accum_out, o1); accum_out += 4; } while (accum_out != accum_out_last) { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } } #define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ static void \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_row_parts_in, \ const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_row_parts_in++); \ q = *(bottom_row_parts_in++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } \ \ static void \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_row_parts_in, \ const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_row_parts_in++); \ q = *(bottom_row_parts_in++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } #define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ static void \ scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ SmolVerticalCtx *vertical_ctx, \ uint32_t outrow_index, \ uint32_t *row_out) \ { \ uint32_t bilin_index = outrow_index << (n_halvings); \ unsigned int i; \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ bilin_index++; \ } \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ \ scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ } \ \ static void \ scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ SmolVerticalCtx *vertical_ctx, \ uint32_t outrow_index, \ uint32_t *row_out) \ { \ uint32_t bilin_index = outrow_index << (n_halvings); \ unsigned int i; \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ bilin_index++; \ } \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ \ scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ } static void scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } static void scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) static void scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t bilin_index = outrow_index << 1; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); bilin_index++; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } static void scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t bilin_index = outrow_index << 1; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); bilin_index++; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) DEF_SCALE_OUTROW_BILINEAR(2) DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) DEF_SCALE_OUTROW_BILINEAR(3) DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) DEF_SCALE_OUTROW_BILINEAR(4) DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) DEF_SCALE_OUTROW_BILINEAR(5) DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) DEF_SCALE_OUTROW_BILINEAR(6) static void finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT parts_out, uint32_t n) { uint64_t *parts_out_max = parts_out + n; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); while (parts_out != parts_out_max) { *(parts_out++) = scale_64bpp (*(accums++), multiplier); } } static void weight_edge_row_64bpp (uint64_t *row, uint16_t w, uint32_t n) { uint64_t *row_max = row + n; SMOL_ASSUME_ALIGNED (row, uint64_t *); while (row != row_max) { *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; row++; } } static void scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, uint64_t * SMOL_RESTRICT last_row, uint64_t * SMOL_RESTRICT accum, uint16_t w2, uint32_t n) { const uint64_t *first_row_max = first_row + n; SMOL_ASSUME_ALIGNED (first_row, const uint64_t *); SMOL_ASSUME_ALIGNED (last_row, uint64_t *); SMOL_ASSUME_ALIGNED (accum, uint64_t *); while (first_row != first_row_max) { uint64_t r, s, p, q; p = *(first_row++); r = *(last_row); s = r * w2; q = (s >> 8) & 0x00ff00ff00ff00ffULL; /* (255 * r) - (F * r) */ *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; *(accum++) = p + q; } } static void update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t ofs_y, uint32_t ofs_y_max, uint16_t w1, uint16_t w2) { /* Old in_ofs is the previous max */ if (ofs_y == vertical_ctx->in_ofs) { uint64_t *t = vertical_ctx->parts_row [0]; vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; vertical_ctx->parts_row [1] = t; } else { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); } /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in * that case. */ if (w2 || ofs_y_max < scale_ctx->height_in) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y_max), vertical_ctx->parts_row [1]); } else { memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); } vertical_ctx->in_ofs = ofs_y_max; } static void scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t ofs_y, ofs_y_max; uint16_t w1, w2; /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ ofs_y = scale_ctx->offsets_y [outrow_index * 2]; ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; /* Scale the first and last rows, weight them and store in accumulator */ w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], w2, scale_ctx->width_out); ofs_y++; /* Add up whole rows */ while (ofs_y < ofs_y_max) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); add_parts (vertical_ctx->parts_row [0], vertical_ctx->parts_row [2], scale_ctx->width_out); ofs_y++; } finalize_vertical_64bpp (vertical_ctx->parts_row [2], scale_ctx->span_mul_y, vertical_ctx->parts_row [0], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT parts_out, uint32_t n) { uint64_t *parts_out_max = parts_out + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); while (parts_out != parts_out_max) { *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); } } static void weight_row_128bpp (uint64_t *row, uint16_t w, uint32_t n) { uint64_t *row_max = row + (n * 2); SMOL_ASSUME_ALIGNED (row, uint64_t *); while (row != row_max) { row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; row += 2; } } static void scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t ofs_y, ofs_y_max; uint16_t w; /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ ofs_y = scale_ctx->offsets_y [outrow_index * 2]; ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; /* Scale the first inrow and store it */ scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); weight_row_128bpp (vertical_ctx->parts_row [0], outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], scale_ctx->width_out); ofs_y++; /* Add up whole rows */ while (ofs_y < ofs_y_max) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [1]); add_parts (vertical_ctx->parts_row [1], vertical_ctx->parts_row [0], scale_ctx->width_out * 2); ofs_y++; } /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ w = scale_ctx->offsets_y [outrow_index * 2 + 1]; if (w > 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [1]); weight_row_128bpp (vertical_ctx->parts_row [1], w - 1, /* Subtract 1 to avoid overflow */ scale_ctx->width_out); add_parts (vertical_ctx->parts_row [1], vertical_ctx->parts_row [0], scale_ctx->width_out * 2); } finalize_vertical_128bpp (vertical_ctx->parts_row [0], scale_ctx->span_mul_y, vertical_ctx->parts_row [1], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); } static void scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { SMOL_UNUSED (row_index); /* Scale the row and store it */ if (vertical_ctx->in_ofs != 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, 0), vertical_ctx->parts_row [0]); vertical_ctx->in_ofs = 0; } scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { SMOL_UNUSED (row_index); /* Scale the row and store it */ if (vertical_ctx->in_ofs != 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, 0), vertical_ctx->parts_row [0]); vertical_ctx->in_ofs = 0; } scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void scale_outrow_copy (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, row_index), vertical_ctx->parts_row [0]); scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } /* --- Conversion tables --- */ static const SmolConversionTable avx2_conversions = { { { /* Conversions where accumulators must hold the sum of fewer than * 256 pixels. This can be done in 64bpp, but 128bpp may be used * e.g. for 16 bits per channel internally premultiplied data. */ /* RGBA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), }, /* BGRA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), }, /* ARGB8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), }, /* ABGR8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), }, /* RGBA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), }, /* BGRA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), }, /* ARGB8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), }, /* ABGR8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), }, /* RGB8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), }, /* BGR8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), } }, { /* Conversions where accumulators must hold the sum of up to * 65535 pixels. We need 128bpp for this. */ /* RGBA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), }, /* BGRA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), }, /* ARGB8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), }, /* ABGR8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), }, /* RGBA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), }, /* BGRA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), }, /* ARGB8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), }, /* ABGR8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), }, /* RGB8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), }, /* BGR8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), } } } }; static const SmolImplementation avx2_implementation = { { /* Horizontal filters */ { /* 64bpp */ interp_horizontal_copy_64bpp, interp_horizontal_one_64bpp, interp_horizontal_bilinear_0h_64bpp, interp_horizontal_bilinear_1h_64bpp, interp_horizontal_bilinear_2h_64bpp, interp_horizontal_bilinear_3h_64bpp, interp_horizontal_bilinear_4h_64bpp, interp_horizontal_bilinear_5h_64bpp, interp_horizontal_bilinear_6h_64bpp, interp_horizontal_boxes_64bpp }, { /* 128bpp */ interp_horizontal_copy_128bpp, interp_horizontal_one_128bpp, interp_horizontal_bilinear_0h_128bpp, interp_horizontal_bilinear_1h_128bpp, interp_horizontal_bilinear_2h_128bpp, interp_horizontal_bilinear_3h_128bpp, interp_horizontal_bilinear_4h_128bpp, interp_horizontal_bilinear_5h_128bpp, interp_horizontal_bilinear_6h_128bpp, interp_horizontal_boxes_128bpp } }, { /* Vertical filters */ { /* 64bpp */ scale_outrow_copy, scale_outrow_one_64bpp, scale_outrow_bilinear_0h_64bpp, scale_outrow_bilinear_1h_64bpp, scale_outrow_bilinear_2h_64bpp, scale_outrow_bilinear_3h_64bpp, scale_outrow_bilinear_4h_64bpp, scale_outrow_bilinear_5h_64bpp, scale_outrow_bilinear_6h_64bpp, scale_outrow_box_64bpp }, { /* 128bpp */ scale_outrow_copy, scale_outrow_one_128bpp, scale_outrow_bilinear_0h_128bpp, scale_outrow_bilinear_1h_128bpp, scale_outrow_bilinear_2h_128bpp, scale_outrow_bilinear_3h_128bpp, scale_outrow_bilinear_4h_128bpp, scale_outrow_bilinear_5h_128bpp, scale_outrow_bilinear_6h_128bpp, scale_outrow_box_128bpp } }, &avx2_conversions }; const SmolImplementation * _smol_get_avx2_implementation (void) { return &avx2_implementation; } chafa-1.8.0/chafa/internal/smolscale/smolscale-private.h000066400000000000000000000124611411352071600232310ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2021 Hans Petter Jansson. See COPYING for details. */ #include #include "smolscale.h" #ifndef _SMOLSCALE_PRIVATE_H_ #define _SMOLSCALE_PRIVATE_H_ #ifdef __cplusplus extern "C" { #endif #include "config.h" /* Enum switches must handle every value */ #ifdef __GNUC__ # pragma GCC diagnostic error "-Wswitch" #endif /* Compensate for GCC missing intrinsics */ #ifdef __GNUC__ # if __GNUC__ < 8 # define _mm256_set_m128i(h, l) \ _mm256_insertf128_si256 (_mm256_castsi128_si256 (l), (h), 1) # endif #endif #ifndef FALSE # define FALSE (0) #endif #ifndef TRUE # define TRUE (!FALSE) #endif #ifndef MIN # define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX # define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif typedef unsigned int SmolBool; #define SMOL_4X2BIT(a, b, c, d) \ (((a) << 6) | ((b) << 4) | ((c) << 2) | (d)) #define SMOL_8X1BIT(a,b,c,d,e,f,g,h) \ (((a) << 7) | ((b) << 6) | ((c) << 5) | ((d) << 4) \ | ((e) << 3) | ((f) << 2) | ((g) << 1) | ((h) << 0)) #define SMOL_UNUSED(x) (void) ((x)=(x)) #define SMOL_RESTRICT __restrict #define SMOL_INLINE __attribute__((always_inline)) inline #define SMOL_CONST __attribute__((const)) #define SMOL_PURE __attribute__((pure)) #define SMOL_SMALL_MUL 256U #define SMOL_BIG_MUL 65536U #define SMOL_BOXES_MULTIPLIER ((uint64_t) SMOL_BIG_MUL * SMOL_SMALL_MUL) #define SMOL_BILIN_MULTIPLIER ((uint64_t) SMOL_BIG_MUL * SMOL_BIG_MUL) #define SMOL_ALIGNMENT 64 #define SMOL_ASSUME_ALIGNED_TO(x, t, n) (x) = (t) __builtin_assume_aligned ((x), (n)) #define SMOL_ASSUME_ALIGNED(x, t) SMOL_ASSUME_ALIGNED_TO ((x), t, SMOL_ALIGNMENT) #define smol_alloca_aligned_to(s, a) \ ({ void *p = alloca ((s) + (a)); p = (void *) (((uintptr_t) (p) + (a)) & ~((a) - 1)); (p); }) #define smol_alloca_aligned(s) smol_alloca_aligned_to (s, SMOL_ALIGNMENT) typedef enum { SMOL_STORAGE_64BPP, SMOL_STORAGE_128BPP, SMOL_STORAGE_MAX } SmolStorageType; typedef enum { SMOL_FILTER_COPY, SMOL_FILTER_ONE, SMOL_FILTER_BILINEAR_0H, SMOL_FILTER_BILINEAR_1H, SMOL_FILTER_BILINEAR_2H, SMOL_FILTER_BILINEAR_3H, SMOL_FILTER_BILINEAR_4H, SMOL_FILTER_BILINEAR_5H, SMOL_FILTER_BILINEAR_6H, SMOL_FILTER_BOX, SMOL_FILTER_MAX } SmolFilterType; /* For reusing rows that have already undergone horizontal scaling */ typedef struct { uint32_t in_ofs; uint64_t *parts_row [3]; } SmolVerticalCtx; typedef void (SmolUnpackRowFunc) (const uint32_t *row_in, uint64_t *row_out, uint32_t n_pixels); typedef void (SmolPackRowFunc) (const uint64_t *row_in, uint32_t *row_out, uint32_t n_pixels); typedef void (SmolHFilterFunc) (const SmolScaleCtx *scale_ctx, const uint64_t *row_limbs_in, uint64_t *row_limbs_out); typedef void (SmolVFilterFunc) (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out); #define SMOL_CONV_UNDEFINED { 0, NULL, NULL } #define SMOL_CONV(un_from_order, un_from_type, un_to_order, un_to_type, pk_from_order, pk_from_type, pk_to_order, pk_to_type, storage_bits) \ { storage_bits / 8, (SmolUnpackRowFunc *) unpack_row_##un_from_order##_##un_from_type##_to_##un_to_order##_##un_to_type##_##storage_bits##bpp, \ (SmolPackRowFunc *) pack_row_##pk_from_order##_##pk_from_type##_to_##pk_to_order##_##pk_to_type##_##storage_bits##bpp } typedef struct { uint8_t n_bytes_per_pixel; SmolUnpackRowFunc *unpack_row_func; SmolPackRowFunc *pack_row_func; } SmolConversion; typedef struct { SmolConversion conversions [SMOL_STORAGE_MAX] [SMOL_PIXEL_MAX] [SMOL_PIXEL_MAX]; } SmolConversionTable; typedef struct { SmolHFilterFunc *hfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; SmolVFilterFunc *vfilter_funcs [SMOL_STORAGE_MAX] [SMOL_FILTER_MAX]; /* Can be a NULL pointer if the implementation does not override any * conversions. */ const SmolConversionTable *ctab; } SmolImplementation; struct SmolScaleCtx { /* */ const uint32_t *pixels_in; uint32_t *pixels_out; uint32_t width_in, height_in, rowstride_in; uint32_t width_out, height_out, rowstride_out; SmolPixelType pixel_type_in, pixel_type_out; SmolFilterType filter_h, filter_v; SmolStorageType storage_type; SmolUnpackRowFunc *unpack_row_func; SmolPackRowFunc *pack_row_func; SmolHFilterFunc *hfilter_func; SmolVFilterFunc *vfilter_func; /* User specified, can be NULL */ SmolPostRowFunc *post_row_func; void *user_data; /* Each offset is split in two uint16s: { pixel index, fraction }. These * are relative to the image after halvings have taken place. */ uint16_t *offsets_x, *offsets_y; uint32_t span_mul_x, span_mul_y; /* For box filter */ uint32_t width_bilin_out, height_bilin_out; unsigned int width_halvings, height_halvings; }; #ifdef SMOL_WITH_AVX2 const SmolImplementation *_smol_get_avx2_implementation (void); #endif #ifdef __cplusplus } #endif #endif chafa-1.8.0/chafa/internal/smolscale/smolscale.c000066400000000000000000003524311411352071600215600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2021 Hans Petter Jansson. See COPYING for details. */ #include /* assert */ #include /* malloc, free, alloca */ #include /* memset */ #include #include "smolscale-private.h" /* --- Premultiplication --- */ #define INVERTED_DIV_SHIFT 21 #define INVERTED_DIV_ROUNDING (1U << (INVERTED_DIV_SHIFT - 1)) #define INVERTED_DIV_ROUNDING_128BPP \ (((uint64_t) INVERTED_DIV_ROUNDING << 32) | INVERTED_DIV_ROUNDING) /* This table is used to divide by an integer [1..255] using only a lookup, * multiplication and a shift. This is faster than plain division on most * architectures. * * Each entry represents the integer 2097152 (1 << 21) divided by the index * of the entry. Consequently, * * (v / i) ~= (v * inverted_div_table [i] + (1 << 20)) >> 21 * * (1 << 20) is added for nearest rounding. It would've been nice to keep * this table in uint16_t, but alas, we need the extra bits for sufficient * precision. */ static const uint32_t inverted_div_table [256] = { 0,2097152,1048576, 699051, 524288, 419430, 349525, 299593, 262144, 233017, 209715, 190650, 174763, 161319, 149797, 139810, 131072, 123362, 116508, 110376, 104858, 99864, 95325, 91181, 87381, 83886, 80660, 77672, 74898, 72316, 69905, 67650, 65536, 63550, 61681, 59919, 58254, 56680, 55188, 53773, 52429, 51150, 49932, 48771, 47663, 46603, 45590, 44620, 43691, 42799, 41943, 41121, 40330, 39569, 38836, 38130, 37449, 36792, 36158, 35545, 34953, 34380, 33825, 33288, 32768, 32264, 31775, 31301, 30840, 30394, 29959, 29537, 29127, 28728, 28340, 27962, 27594, 27236, 26887, 26546, 26214, 25891, 25575, 25267, 24966, 24672, 24385, 24105, 23831, 23564, 23302, 23046, 22795, 22550, 22310, 22075, 21845, 21620, 21400, 21183, 20972, 20764, 20560, 20361, 20165, 19973, 19784, 19600, 19418, 19240, 19065, 18893, 18725, 18559, 18396, 18236, 18079, 17924, 17772, 17623, 17476, 17332, 17190, 17050, 16913, 16777, 16644, 16513, 16384, 16257, 16132, 16009, 15888, 15768, 15650, 15534, 15420, 15308, 15197, 15087, 14980, 14873, 14769, 14665, 14564, 14463, 14364, 14266, 14170, 14075, 13981, 13888, 13797, 13707, 13618, 13530, 13443, 13358, 13273, 13190, 13107, 13026, 12945, 12866, 12788, 12710, 12633, 12558, 12483, 12409, 12336, 12264, 12193, 12122, 12053, 11984, 11916, 11848, 11782, 11716, 11651, 11586, 11523, 11460, 11398, 11336, 11275, 11215, 11155, 11096, 11038, 10980, 10923, 10866, 10810, 10755, 10700, 10645, 10592, 10538, 10486, 10434, 10382, 10331, 10280, 10230, 10180, 10131, 10082, 10034, 9986, 9939, 9892, 9846, 9800, 9754, 9709, 9664, 9620, 9576, 9533, 9489, 9447, 9404, 9362, 9321, 9279, 9239, 9198, 9158, 9118, 9079, 9039, 9001, 8962, 8924, 8886, 8849, 8812, 8775, 8738, 8702, 8666, 8630, 8595, 8560, 8525, 8490, 8456, 8422, 8389, 8355, 8322, 8289, 8257, 8224, }; /* Masking and shifting out the results is left to the caller. In * and out may not overlap. */ static SMOL_INLINE void unpremul_i_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = ((in [0] * (uint64_t) inverted_div_table [alpha] + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); out [1] = ((in [1] * (uint64_t) inverted_div_table [alpha] + INVERTED_DIV_ROUNDING_128BPP) >> INVERTED_DIV_SHIFT); } static SMOL_INLINE void unpremul_p_to_u_128bpp (const uint64_t * SMOL_RESTRICT in, uint64_t * SMOL_RESTRICT out, uint8_t alpha) { out [0] = (((in [0] << 8) * (uint64_t) inverted_div_table [alpha]) >> INVERTED_DIV_SHIFT); out [1] = (((in [1] << 8) * (uint64_t) inverted_div_table [alpha]) >> INVERTED_DIV_SHIFT); } static SMOL_INLINE uint64_t unpremul_p_to_u_64bpp (const uint64_t in, uint8_t alpha) { uint64_t in_128bpp [2]; uint64_t out_128bpp [2]; in_128bpp [0] = (in & 0x000000ff000000ff); in_128bpp [1] = (in & 0x00ff000000ff0000) >> 16; unpremul_p_to_u_128bpp (in_128bpp, out_128bpp, alpha); return (out_128bpp [0] & 0x000000ff000000ff) | ((out_128bpp [1] & 0x000000ff000000ff) << 16); } static SMOL_INLINE uint64_t premul_u_to_p_64bpp (const uint64_t in, uint8_t alpha) { return ((in * ((uint16_t) alpha + 1)) >> 8) & 0x00ff00ff00ff00ff; } /* --- Packing --- */ /* It's nice to be able to shift by a negative amount */ #define SHIFT_S(in, s) ((s >= 0) ? (in) << (s) : (in) >> -(s)) /* This is kind of bulky (~13 x86 insns), but it's about the same as using * unions, and we don't have to worry about endianness. */ #define PACK_FROM_1234_64BPP(in, a, b, c, d) \ ((SHIFT_S ((in), ((a) - 1) * 16 + 8 - 32) & 0xff000000) \ | (SHIFT_S ((in), ((b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ | (SHIFT_S ((in), ((c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ | (SHIFT_S ((in), ((d) - 1) * 16 + 8 - 56) & 0x000000ff)) #define PACK_FROM_1234_128BPP(in, a, b, c, d) \ ((SHIFT_S ((in [((a) - 1) >> 1]), (((a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ | (SHIFT_S ((in [((b) - 1) >> 1]), (((b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ | (SHIFT_S ((in [((c) - 1) >> 1]), (((c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ | (SHIFT_S ((in [((d) - 1) >> 1]), (((d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) #define SWAP_2_AND_3(n) ((n) == 2 ? 3 : (n) == 3 ? 2 : n) #define PACK_FROM_1324_64BPP(in, a, b, c, d) \ ((SHIFT_S ((in), (SWAP_2_AND_3 (a) - 1) * 16 + 8 - 32) & 0xff000000) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (b) - 1) * 16 + 8 - 40) & 0x00ff0000) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (c) - 1) * 16 + 8 - 48) & 0x0000ff00) \ | (SHIFT_S ((in), (SWAP_2_AND_3 (d) - 1) * 16 + 8 - 56) & 0x000000ff)) /* Note: May not be needed */ #define PACK_FROM_1324_128BPP(in, a, b, c, d) \ ((SHIFT_S ((in [(SWAP_2_AND_3 (a) - 1) >> 1]), \ ((SWAP_2_AND_3 (a) - 1) & 1) * 32 + 24 - 32) & 0xff000000) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (b) - 1) >> 1]), \ ((SWAP_2_AND_3 (b) - 1) & 1) * 32 + 24 - 40) & 0x00ff0000) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (c) - 1) >> 1]), \ ((SWAP_2_AND_3 (c) - 1) & 1) * 32 + 24 - 48) & 0x0000ff00) \ | (SHIFT_S ((in [(SWAP_2_AND_3 (d) - 1) >> 1]), \ ((SWAP_2_AND_3 (d) - 1) & 1) * 32 + 24 - 56) & 0x000000ff)) /* Pack p -> p */ static SMOL_INLINE uint32_t pack_pixel_1324_p_to_1234_p_64bpp (uint64_t in) { return in | (in >> 24); } static void pack_row_1324_p_to_1234_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); } } static void pack_row_132a_p_to_123_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { /* FIXME: Would be faster to shift directly */ uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_132a_p_to_321_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { /* FIXME: Would be faster to shift directly */ uint32_t p = pack_pixel_1324_p_to_1234_p_64bpp (*(row_in++)); *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_1324_P_TO_P_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (uint64_t in) \ { \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_1324_p_to_##a##b##c##d##_p_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_1324_p_to_##a##b##c##d##_p_64bpp (*(row_in++)); \ } DEF_PACK_FROM_1324_P_TO_P_64BPP (1, 4, 3, 2) DEF_PACK_FROM_1324_P_TO_P_64BPP (2, 3, 4, 1) DEF_PACK_FROM_1324_P_TO_P_64BPP (3, 2, 1, 4) DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 1, 2, 3) DEF_PACK_FROM_1324_P_TO_P_64BPP (4, 3, 2, 1) static SMOL_INLINE uint32_t pack_pixel_1234_p_to_1234_p_128bpp (const uint64_t *in) { /* FIXME: Are masks needed? */ return ((in [0] >> 8) & 0xff000000) | ((in [0] << 16) & 0x00ff0000) | ((in [1] >> 24) & 0x0000ff00) | (in [1] & 0x000000ff); } static void pack_row_1234_p_to_1234_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_1234_p_to_1234_p_128bpp (row_in); row_in += 2; } } #define DEF_PACK_FROM_1234_P_TO_P_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ return PACK_FROM_1234_128BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_1234_p_to_##a##b##c##d##_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_1234_p_to_##a##b##c##d##_p_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_1234_P_TO_P_128BPP (1, 4, 3, 2) DEF_PACK_FROM_1234_P_TO_P_128BPP (2, 3, 4, 1) DEF_PACK_FROM_1234_P_TO_P_128BPP (3, 2, 1, 4) DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 1, 2, 3) DEF_PACK_FROM_1234_P_TO_P_128BPP (4, 3, 2, 1) static void pack_row_123a_p_to_123_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = *row_in >> 32; *(row_out++) = *(row_in++); *(row_out++) = *(row_in++) >> 32; } } static void pack_row_123a_p_to_321_p_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = row_in [1] >> 32; *(row_out++) = row_in [0]; *(row_out++) = row_in [0] >> 32; row_in += 2; } } /* Pack p (alpha last) -> u */ static SMOL_INLINE uint32_t pack_pixel_132a_p_to_1234_u_64bpp (uint64_t in) { uint8_t alpha = in; in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; return in | (in >> 24); } static void pack_row_132a_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); } } static void pack_row_132a_p_to_123_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_132a_p_to_321_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_132a_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_132A_P_TO_U_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ { \ uint8_t alpha = in; \ in = (unpremul_p_to_u_64bpp (in, alpha) & 0xffffffffffffff00) | alpha; \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_132a_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_132a_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ } DEF_PACK_FROM_132A_P_TO_U_64BPP (3, 2, 1, 4) DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 1, 2, 3) DEF_PACK_FROM_132A_P_TO_U_64BPP (4, 3, 2, 1) #define DEF_PACK_FROM_123A_P_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint64_t t [2]; \ uint8_t alpha = in [1]; \ unpremul_p_to_u_128bpp (in, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000) | alpha; \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_123a_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_123a_p_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_123A_P_TO_U_128BPP (1, 2, 3, 4) DEF_PACK_FROM_123A_P_TO_U_128BPP (3, 2, 1, 4) DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 1, 2, 3) DEF_PACK_FROM_123A_P_TO_U_128BPP (4, 3, 2, 1) static void pack_row_123a_p_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_123a_p_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } /* Pack p (alpha first) -> u */ static SMOL_INLINE uint32_t pack_pixel_a324_p_to_1234_u_64bpp (uint64_t in) { uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); return in | (in >> 24); } static void pack_row_a324_p_to_1234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); } } static void pack_row_a324_p_to_234_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p >> 16; *(row_out++) = p >> 8; *(row_out++) = p; } } static void pack_row_a324_p_to_432_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a324_p_to_1234_u_64bpp (*(row_in++)); *(row_out++) = p; *(row_out++) = p >> 8; *(row_out++) = p >> 16; } } #define DEF_PACK_FROM_A324_P_TO_U_64BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (uint64_t in) \ { \ uint8_t alpha = (in >> 48) & 0xff; /* FIXME: May not need mask */ \ in = (unpremul_p_to_u_64bpp (in, alpha) & 0x0000ffffffffffff) | ((uint64_t) alpha << 48); \ return PACK_FROM_1324_64BPP (in, a, b, c, d); \ } \ \ static void \ pack_row_a324_p_to_##a##b##c##d##_u_64bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ *(row_out++) = pack_pixel_a324_p_to_##a##b##c##d##_u_64bpp (*(row_in++)); \ } DEF_PACK_FROM_A324_P_TO_U_64BPP (1, 4, 3, 2) DEF_PACK_FROM_A324_P_TO_U_64BPP (2, 3, 4, 1) DEF_PACK_FROM_A324_P_TO_U_64BPP (4, 3, 2, 1) #define DEF_PACK_FROM_A234_P_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint64_t t [2]; \ uint8_t alpha = in [0] >> 32; \ unpremul_p_to_u_128bpp (in, t, alpha); \ t [0] = (t [0] & 0x00000000ffffffff) | ((uint64_t) alpha << 32); \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_a234_p_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_a234_p_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 2, 3, 4) DEF_PACK_FROM_A234_P_TO_U_128BPP (1, 4, 3, 2) DEF_PACK_FROM_A234_P_TO_U_128BPP (2, 3, 4, 1) DEF_PACK_FROM_A234_P_TO_U_128BPP (4, 3, 2, 1) static void pack_row_a234_p_to_234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 16; *(row_out++) = p >> 8; *(row_out++) = p; } } static void pack_row_a234_p_to_432_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_a234_p_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p; *(row_out++) = p >> 8; *(row_out++) = p >> 16; } } /* Pack i (alpha last) to u */ static SMOL_INLINE uint32_t pack_pixel_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT in) { uint8_t alpha = (in [1] >> 8) & 0xff; uint64_t t [2]; unpremul_i_to_u_128bpp (in, t, alpha); return ((t [0] >> 8) & 0xff000000) | ((t [0] << 16) & 0x00ff0000) | ((t [1] >> 24) & 0x0000ff00) | alpha; } static void pack_row_123a_i_to_1234_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint32_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint32_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { *(row_out++) = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; } } static void pack_row_123a_i_to_123_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 24; *(row_out++) = p >> 16; *(row_out++) = p >> 8; } } static void pack_row_123a_i_to_321_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, uint8_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint8_t *row_out_max = row_out + n_pixels * 3; SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); while (row_out != row_out_max) { uint32_t p = pack_pixel_123a_i_to_1234_u_128bpp (row_in); row_in += 2; *(row_out++) = p >> 8; *(row_out++) = p >> 16; *(row_out++) = p >> 24; } } #define DEF_PACK_FROM_123A_I_TO_U_128BPP(a, b, c, d) \ static SMOL_INLINE uint32_t \ pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT in) \ { \ uint8_t alpha = (in [1] >> 8) & 0xff; \ uint64_t t [2]; \ unpremul_i_to_u_128bpp (in, t, alpha); \ t [1] = (t [1] & 0xffffffff00000000ULL) | alpha; \ return PACK_FROM_1234_128BPP (t, a, b, c, d); \ } \ \ static void \ pack_row_123a_i_to_##a##b##c##d##_u_128bpp (const uint64_t * SMOL_RESTRICT row_in, \ uint32_t * SMOL_RESTRICT row_out, \ uint32_t n_pixels) \ { \ uint32_t *row_out_max = row_out + n_pixels; \ SMOL_ASSUME_ALIGNED (row_in, const uint64_t *); \ while (row_out != row_out_max) \ { \ *(row_out++) = pack_pixel_123a_i_to_##a##b##c##d##_u_128bpp (row_in); \ row_in += 2; \ } \ } DEF_PACK_FROM_123A_I_TO_U_128BPP(3, 2, 1, 4) DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 1, 2, 3) DEF_PACK_FROM_123A_I_TO_U_128BPP(4, 3, 2, 1) /* Unpack p -> p */ static SMOL_INLINE uint64_t unpack_pixel_1234_p_to_1324_p_64bpp (uint32_t p) { return (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff00ff); } /* AVX2 has a useful instruction for this: __m256i _mm256_cvtepu8_epi16 (__m128i a); * It results in a different channel ordering, so it'd be important to match with * the right kind of re-pack. */ static void unpack_row_1234_p_to_1324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_1234_p_to_1324_p_64bpp (*(row_in++)); } } static SMOL_INLINE uint64_t unpack_pixel_123_p_to_132a_p_64bpp (const uint8_t *p) { return ((uint64_t) p [0] << 48) | ((uint32_t) p [1] << 16) | ((uint64_t) p [2] << 32) | 0xff; } static void unpack_row_123_p_to_132a_p_64bpp (const uint8_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_123_p_to_132a_p_64bpp (row_in); row_in += 3; } } static SMOL_INLINE void unpack_pixel_1234_p_to_1234_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; out [0] = ((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16); out [1] = ((p64 & 0x0000ff00) << 24) | (p64 & 0x000000ff); } static void unpack_row_1234_p_to_1234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_1234_p_to_1234_p_128bpp (*(row_in++), row_out); row_out += 2; } } static SMOL_INLINE void unpack_pixel_123_p_to_123a_p_128bpp (const uint8_t *in, uint64_t *out) { out [0] = ((uint64_t) in [0] << 32) | in [1]; out [1] = ((uint64_t) in [2] << 32) | 0xff; } static void unpack_row_123_p_to_123a_p_128bpp (const uint8_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_123_p_to_123a_p_128bpp (row_in, row_out); row_in += 3; row_out += 2; } } /* Unpack u (alpha first) -> p */ static SMOL_INLINE uint64_t unpack_pixel_a234_u_to_a324_p_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); uint8_t alpha = p >> 24; return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); } static void unpack_row_a234_u_to_a324_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_a234_u_to_a324_p_64bpp (*(row_in++)); } } static SMOL_INLINE void unpack_pixel_a234_u_to_a234_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0x0000ff00) << 24) | (p & 0x00ff00ff); uint8_t alpha = p >> 24; p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha << 48); out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } static void unpack_row_a234_u_to_a234_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_a234_u_to_a234_p_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u (alpha first) -> i */ static SMOL_INLINE void unpack_pixel_a234_u_to_234a_i_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint64_t alpha = p >> 24; out [0] = (((((p64 & 0x00ff0000) << 16) | ((p64 & 0x0000ff00) >> 8)) * alpha)); out [1] = (((((p64 & 0x000000ff) << 32) * alpha))) | (alpha << 8) | 0x80; } static void unpack_row_a234_u_to_234a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_a234_u_to_234a_i_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u (alpha last) -> p */ static SMOL_INLINE uint64_t unpack_pixel_123a_u_to_132a_p_64bpp (uint32_t p) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; return premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); } static void unpack_row_123a_u_to_132a_p_64bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { *(row_out++) = unpack_pixel_123a_u_to_132a_p_64bpp (*(row_in++)); } } static SMOL_INLINE void unpack_pixel_123a_u_to_123a_p_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = (((uint64_t) p & 0xff00ff00) << 24) | (p & 0x00ff0000); uint8_t alpha = p & 0xff; p64 = premul_u_to_p_64bpp (p64, alpha) | ((uint64_t) alpha); out [0] = (p64 >> 16) & 0x000000ff000000ff; out [1] = p64 & 0x000000ff000000ff; } static void unpack_row_123a_u_to_123a_p_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_123a_u_to_123a_p_128bpp (*(row_in++), row_out); row_out += 2; } } /* Unpack u (alpha last) -> i */ static SMOL_INLINE void unpack_pixel_123a_u_to_123a_i_128bpp (uint32_t p, uint64_t *out) { uint64_t p64 = p; uint64_t alpha = p & 0xff; out [0] = (((((p64 & 0xff000000) << 8) | ((p64 & 0x00ff0000) >> 16)) * alpha)); out [1] = (((((p64 & 0x0000ff00) << 24) * alpha))) | (alpha << 8) | 0x80; } static void unpack_row_123a_u_to_123a_i_128bpp (const uint32_t * SMOL_RESTRICT row_in, uint64_t * SMOL_RESTRICT row_out, uint32_t n_pixels) { uint64_t *row_out_max = row_out + n_pixels * 2; SMOL_ASSUME_ALIGNED (row_out, uint64_t *); while (row_out != row_out_max) { unpack_pixel_123a_u_to_123a_i_128bpp (*(row_in++), row_out); row_out += 2; } } /* --- Filter helpers --- */ static SMOL_INLINE const uint32_t * inrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t inrow_ofs) { return scale_ctx->pixels_in + scale_ctx->rowstride_in * inrow_ofs; } static SMOL_INLINE uint32_t * outrow_ofs_to_pointer (const SmolScaleCtx *scale_ctx, uint32_t outrow_ofs) { return scale_ctx->pixels_out + scale_ctx->rowstride_out * outrow_ofs; } static SMOL_INLINE uint64_t weight_pixel_64bpp (uint64_t p, uint16_t w) { return ((p * w) >> 8) & 0x00ff00ff00ff00ff; } /* p and out may be the same address */ static SMOL_INLINE void weight_pixel_128bpp (uint64_t *p, uint64_t *out, uint16_t w) { out [0] = ((p [0] * w) >> 8) & 0x00ffffff00ffffffULL; out [1] = ((p [1] * w) >> 8) & 0x00ffffff00ffffffULL; } static SMOL_INLINE void sum_parts_64bpp (const uint64_t ** SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT accum, uint32_t n) { const uint64_t *pp_end; const uint64_t * SMOL_RESTRICT pp = *parts_in; SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t)); for (pp_end = pp + n; pp < pp_end; pp++) { *accum += *pp; } *parts_in = pp; } static SMOL_INLINE void sum_parts_128bpp (const uint64_t ** SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT accum, uint32_t n) { const uint64_t *pp_end; const uint64_t * SMOL_RESTRICT pp = *parts_in; SMOL_ASSUME_ALIGNED_TO (pp, const uint64_t *, sizeof (uint64_t) * 2); for (pp_end = pp + n * 2; pp < pp_end; ) { accum [0] += *(pp++); accum [1] += *(pp++); } *parts_in = pp; } static SMOL_INLINE uint64_t scale_64bpp (uint64_t accum, uint64_t multiplier) { uint64_t a, b; /* Average the inputs */ a = ((accum & 0x0000ffff0000ffffULL) * multiplier + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; b = (((accum & 0xffff0000ffff0000ULL) >> 16) * multiplier + (SMOL_BOXES_MULTIPLIER / 2) + ((SMOL_BOXES_MULTIPLIER / 2) << 32)) / SMOL_BOXES_MULTIPLIER; /* Return pixel */ return (a & 0x000000ff000000ffULL) | ((b & 0x000000ff000000ffULL) << 16); } static SMOL_INLINE uint64_t scale_128bpp_half (uint64_t accum, uint64_t multiplier) { uint64_t a, b; a = accum & 0x00000000ffffffffULL; a = (a * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; b = (accum & 0xffffffff00000000ULL) >> 32; b = (b * multiplier + SMOL_BOXES_MULTIPLIER / 2) / SMOL_BOXES_MULTIPLIER; return (a & 0x000000000000ffffULL) | ((b & 0x000000000000ffffULL) << 32); } static SMOL_INLINE void scale_and_store_128bpp (const uint64_t * SMOL_RESTRICT accum, uint64_t multiplier, uint64_t ** SMOL_RESTRICT row_parts_out) { *(*row_parts_out)++ = scale_128bpp_half (accum [0], multiplier); *(*row_parts_out)++ = scale_128bpp_half (accum [1], multiplier); } static void add_parts (const uint64_t * SMOL_RESTRICT parts_in, uint64_t * SMOL_RESTRICT parts_acc_out, uint32_t n) { const uint64_t *parts_in_max = parts_in + n; SMOL_ASSUME_ALIGNED (parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_acc_out, uint64_t *); while (parts_in < parts_in_max) *(parts_acc_out++) += *(parts_in++); } /* --- Precalculation --- */ static void pick_filter_params (uint32_t dim_in, uint32_t dim_out, uint32_t *halvings_out, uint32_t *dim_bilin_out, SmolFilterType *filter_out, SmolStorageType *storage_out) { *dim_bilin_out = dim_out; *storage_out = SMOL_STORAGE_64BPP; /* The box algorithms are only sufficiently precise when * dim_in > dim_out * 5. box_64bpp typically starts outperforming * bilinear+halving at dim_in > dim_out * 8. */ if (dim_in > dim_out * 255) { *filter_out = SMOL_FILTER_BOX; *storage_out = SMOL_STORAGE_128BPP; } else if (dim_in > dim_out * 8) { *filter_out = SMOL_FILTER_BOX; } else if (dim_in == 1) { *filter_out = SMOL_FILTER_ONE; } else if (dim_in == dim_out) { *filter_out = SMOL_FILTER_COPY; } else { uint32_t n_halvings = 0; uint32_t d = dim_out; for (;;) { d *= 2; if (d >= dim_in) break; n_halvings++; } dim_out <<= n_halvings; *dim_bilin_out = dim_out; *filter_out = SMOL_FILTER_BILINEAR_0H + n_halvings; *halvings_out = n_halvings; } } static void precalc_bilinear_array (uint16_t *array, uint32_t dim_in, uint32_t dim_out, unsigned int make_absolute_offsets) { uint64_t ofs_stepF, fracF, frac_stepF; uint16_t *pu16 = array; uint16_t last_ofs = 0; if (dim_in > dim_out) { /* Minification */ frac_stepF = ofs_stepF = (dim_in * SMOL_BILIN_MULTIPLIER) / dim_out; fracF = (frac_stepF - SMOL_BILIN_MULTIPLIER) / 2; } else { /* Magnification */ frac_stepF = ofs_stepF = ((dim_in - 1) * SMOL_BILIN_MULTIPLIER) / (dim_out > 1 ? (dim_out - 1) : 1); fracF = 0; } do { uint16_t ofs = fracF / SMOL_BILIN_MULTIPLIER; /* We sample ofs and its neighbor -- prevent out of bounds access * for the latter. */ if (ofs >= dim_in - 1) break; *(pu16++) = make_absolute_offsets ? ofs : ofs - last_ofs; *(pu16++) = SMOL_SMALL_MUL - ((fracF / (SMOL_BILIN_MULTIPLIER / SMOL_SMALL_MUL)) % SMOL_SMALL_MUL); fracF += frac_stepF; last_ofs = ofs; } while (--dim_out); /* Instead of going out of bounds, sample the final pair of pixels with a 100% * bias towards the last pixel */ while (dim_out) { *(pu16++) = make_absolute_offsets ? dim_in - 2 : (dim_in - 2) - last_ofs; *(pu16++) = 0; dim_out--; last_ofs = dim_in - 2; } } static void precalc_boxes_array (uint16_t *array, uint32_t *span_mul, uint32_t dim_in, uint32_t dim_out, unsigned int make_absolute_offsets) { uint64_t fracF, frac_stepF; uint16_t *pu16 = array; uint16_t ofs, next_ofs; uint64_t f; uint64_t stride; uint64_t a, b; frac_stepF = ((uint64_t) dim_in * SMOL_BIG_MUL) / (uint64_t) dim_out; fracF = 0; ofs = 0; stride = frac_stepF / (uint64_t) SMOL_BIG_MUL; f = (frac_stepF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; a = (SMOL_BOXES_MULTIPLIER * 255); b = ((stride * 255) + ((f * 255) / 256)); *span_mul = (a + (b / 2)) / b; do { fracF += frac_stepF; next_ofs = (uint64_t) fracF / ((uint64_t) SMOL_BIG_MUL); /* Prevent out of bounds access */ if (ofs >= dim_in - 1) break; if (next_ofs > dim_in) { next_ofs = dim_in; if (next_ofs <= ofs) break; } stride = next_ofs - ofs - 1; f = (fracF / SMOL_SMALL_MUL) % SMOL_SMALL_MUL; /* Fraction is the other way around, since left pixel of each span * comes first, and it's on the right side of the fractional sample. */ *(pu16++) = make_absolute_offsets ? ofs : stride; *(pu16++) = f; ofs = next_ofs; } while (--dim_out); /* Instead of going out of bounds, sample the final pair of pixels with a 100% * bias towards the last pixel */ while (dim_out) { *(pu16++) = make_absolute_offsets ? ofs : 0; *(pu16++) = 0; dim_out--; } *(pu16++) = make_absolute_offsets ? ofs : 0; *(pu16++) = 0; } /* --- Horizontal scaling --- */ #define DEF_INTERP_HORIZONTAL_BILINEAR(n_halvings) \ static void \ interp_horizontal_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ const uint64_t * SMOL_RESTRICT row_parts_in, \ uint64_t * SMOL_RESTRICT row_parts_out) \ { \ uint64_t p, q; \ const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ uint64_t F; \ uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; \ int i; \ \ SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ \ do \ { \ uint64_t accum = 0; \ \ for (i = 0; i < (1 << (n_halvings)); i++) \ { \ row_parts_in += *(ofs_x++); \ F = *(ofs_x++); \ \ p = *row_parts_in; \ q = *(row_parts_in + 1); \ \ accum += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ } \ *(row_parts_out++) = ((accum) >> (n_halvings)) & 0x00ff00ff00ff00ffULL; \ } \ while (row_parts_out != row_parts_out_max); \ } \ \ static void \ interp_horizontal_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ const uint64_t * SMOL_RESTRICT row_parts_in, \ uint64_t * SMOL_RESTRICT row_parts_out) \ { \ uint64_t p, q; \ const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; \ uint64_t F; \ uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; \ int i; \ \ SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); \ \ do \ { \ uint64_t accum [2] = { 0 }; \ \ for (i = 0; i < (1 << (n_halvings)); i++) \ { \ row_parts_in += *(ofs_x++) * 2; \ F = *(ofs_x++); \ \ p = row_parts_in [0]; \ q = row_parts_in [2]; \ \ accum [0] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ \ p = row_parts_in [1]; \ q = row_parts_in [3]; \ \ accum [1] += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ } \ *(row_parts_out++) = ((accum [0]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ *(row_parts_out++) = ((accum [1]) >> (n_halvings)) & 0x00ffffff00ffffffULL; \ } \ while (row_parts_out != row_parts_out_max); \ } static void interp_horizontal_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t p, q; const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; uint64_t F; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); do { row_parts_in += *(ofs_x++); F = *(ofs_x++); p = *row_parts_in; q = *(row_parts_in + 1); *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (row_parts_out != row_parts_out_max); } static void interp_horizontal_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t p, q; const uint16_t * SMOL_RESTRICT ofs_x = scale_ctx->offsets_x; uint64_t F; uint64_t * SMOL_RESTRICT row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); do { row_parts_in += *(ofs_x++) * 2; F = *(ofs_x++); p = row_parts_in [0]; q = row_parts_in [2]; *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; p = row_parts_in [1]; q = row_parts_in [3]; *(row_parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (row_parts_out != row_parts_out_max); } DEF_INTERP_HORIZONTAL_BILINEAR(1) DEF_INTERP_HORIZONTAL_BILINEAR(2) DEF_INTERP_HORIZONTAL_BILINEAR(3) DEF_INTERP_HORIZONTAL_BILINEAR(4) DEF_INTERP_HORIZONTAL_BILINEAR(5) DEF_INTERP_HORIZONTAL_BILINEAR(6) static void interp_horizontal_boxes_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t *row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint64_t * SMOL_RESTRICT pp; const uint16_t *ofs_x = scale_ctx->offsets_x; uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out - 1; uint64_t accum = 0; uint64_t p, q, r, s; uint32_t n; uint64_t F; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); pp = row_parts_in; p = weight_pixel_64bpp (*(pp++), 256); n = *(ofs_x++); while (row_parts_out != row_parts_out_max) { sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); F = *(ofs_x++); n = *(ofs_x++); r = *(pp++); s = r * F; q = (s >> 8) & 0x00ff00ff00ff00ffULL; accum += p + q; /* (255 * r) - (F * r) */ p = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); accum = 0; } /* Final box optionally features the rightmost fractional pixel */ sum_parts_64bpp ((const uint64_t ** SMOL_RESTRICT) &pp, &accum, n); q = 0; F = *(ofs_x); if (F > 0) q = weight_pixel_64bpp (*(pp), F); accum += p + q; *(row_parts_out++) = scale_64bpp (accum, scale_ctx->span_mul_x); } static void interp_horizontal_boxes_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t *row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { const uint64_t * SMOL_RESTRICT pp; const uint16_t *ofs_x = scale_ctx->offsets_x; uint64_t *row_parts_out_max = row_parts_out + (scale_ctx->width_out - /* 2 */ 1) * 2; uint64_t accum [2] = { 0, 0 }; uint64_t p [2], q [2], r [2], s [2]; uint32_t n; uint64_t F; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); pp = row_parts_in; p [0] = *(pp++); p [1] = *(pp++); weight_pixel_128bpp (p, p, 256); n = *(ofs_x++); while (row_parts_out != row_parts_out_max) { sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); F = *(ofs_x++); n = *(ofs_x++); r [0] = *(pp++); r [1] = *(pp++); s [0] = r [0] * F; s [1] = r [1] * F; q [0] = (s [0] >> 8) & 0x00ffffff00ffffff; q [1] = (s [1] >> 8) & 0x00ffffff00ffffff; accum [0] += p [0] + q [0]; accum [1] += p [1] + q [1]; p [0] = (((r [0] << 8) - r [0] - s [0]) >> 8) & 0x00ffffff00ffffff; p [1] = (((r [1] << 8) - r [1] - s [1]) >> 8) & 0x00ffffff00ffffff; scale_and_store_128bpp (accum, scale_ctx->span_mul_x, (uint64_t ** SMOL_RESTRICT) &row_parts_out); accum [0] = 0; accum [1] = 0; } /* Final box optionally features the rightmost fractional pixel */ sum_parts_128bpp ((const uint64_t ** SMOL_RESTRICT) &pp, accum, n); q [0] = 0; q [1] = 0; F = *(ofs_x); if (F > 0) { q [0] = *(pp++); q [1] = *(pp++); weight_pixel_128bpp (q, q, F); } accum [0] += p [0] + q [0]; accum [1] += p [1] + q [1]; scale_and_store_128bpp (accum, scale_ctx->span_mul_x, (uint64_t ** SMOL_RESTRICT) &row_parts_out); } static void interp_horizontal_one_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out; uint64_t part; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); part = *row_parts_in; while (row_parts_out != row_parts_out_max) *(row_parts_out++) = part; } static void interp_horizontal_one_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { uint64_t *row_parts_out_max = row_parts_out + scale_ctx->width_out * 2; SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); while (row_parts_out != row_parts_out_max) { *(row_parts_out++) = row_parts_in [0]; *(row_parts_out++) = row_parts_in [1]; } } static void interp_horizontal_copy_64bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * sizeof (uint64_t)); } static void interp_horizontal_copy_128bpp (const SmolScaleCtx *scale_ctx, const uint64_t * SMOL_RESTRICT row_parts_in, uint64_t * SMOL_RESTRICT row_parts_out) { SMOL_ASSUME_ALIGNED (row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (row_parts_out, uint64_t *); memcpy (row_parts_out, row_parts_in, scale_ctx->width_out * 2 * sizeof (uint64_t)); } static void scale_horizontal (const SmolScaleCtx *scale_ctx, const uint32_t *row_in, uint64_t *row_parts_out) { uint64_t * SMOL_RESTRICT unpacked_in; /* FIXME: Allocate less for 64bpp */ unpacked_in = smol_alloca_aligned (scale_ctx->width_in * sizeof (uint64_t) * 2); scale_ctx->unpack_row_func (row_in, unpacked_in, scale_ctx->width_in); scale_ctx->hfilter_func (scale_ctx, unpacked_in, row_parts_out); } /* --- Vertical scaling --- */ static void update_vertical_ctx_bilinear (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index) { uint32_t new_in_ofs = scale_ctx->offsets_y [outrow_index * 2]; if (new_in_ofs == vertical_ctx->in_ofs) return; if (new_in_ofs == vertical_ctx->in_ofs + 1) { uint64_t *t = vertical_ctx->parts_row [0]; vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; vertical_ctx->parts_row [1] = t; scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), vertical_ctx->parts_row [1]); } else { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs), vertical_ctx->parts_row [0]); scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, new_in_ofs + 1), vertical_ctx->parts_row [1]); } vertical_ctx->in_ofs = new_in_ofs; } static void interp_vertical_bilinear_store_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT parts_out, uint32_t width) { uint64_t *parts_out_last = parts_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (parts_out != parts_out_last); } static void interp_vertical_bilinear_add_64bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_out_last = accum_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; } while (accum_out != accum_out_last); } static void interp_vertical_bilinear_store_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT parts_out, uint32_t width) { uint64_t *parts_out_last = parts_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(parts_out++) = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (parts_out != parts_out_last); } static void interp_vertical_bilinear_add_128bpp (uint64_t F, const uint64_t * SMOL_RESTRICT top_row_parts_in, const uint64_t * SMOL_RESTRICT bottom_row_parts_in, uint64_t * SMOL_RESTRICT accum_out, uint32_t width) { uint64_t *accum_out_last = accum_out + width; SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); SMOL_ASSUME_ALIGNED (accum_out, uint64_t *); do { uint64_t p, q; p = *(top_row_parts_in++); q = *(bottom_row_parts_in++); *(accum_out++) += ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; } while (accum_out != accum_out_last); } #define DEF_INTERP_VERTICAL_BILINEAR_FINAL(n_halvings) \ static void \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_row_parts_in, \ const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_row_parts_in++); \ q = *(bottom_row_parts_in++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ff00ff00ff00ffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ff00ff00ff00ffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } \ \ static void \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (uint64_t F, \ const uint64_t * SMOL_RESTRICT top_row_parts_in, \ const uint64_t * SMOL_RESTRICT bottom_row_parts_in, \ uint64_t * SMOL_RESTRICT accum_inout, \ uint32_t width) \ { \ uint64_t *accum_inout_last = accum_inout + width; \ \ SMOL_ASSUME_ALIGNED (top_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (bottom_row_parts_in, const uint64_t *); \ SMOL_ASSUME_ALIGNED (accum_inout, uint64_t *); \ \ do \ { \ uint64_t p, q; \ \ p = *(top_row_parts_in++); \ q = *(bottom_row_parts_in++); \ \ p = ((((p - q) * F) >> 8) + q) & 0x00ffffff00ffffffULL; \ p = ((p + *accum_inout) >> n_halvings) & 0x00ffffff00ffffffULL; \ \ *(accum_inout++) = p; \ } \ while (accum_inout != accum_inout_last); \ } #define DEF_SCALE_OUTROW_BILINEAR(n_halvings) \ static void \ scale_outrow_bilinear_##n_halvings##h_64bpp (const SmolScaleCtx *scale_ctx, \ SmolVerticalCtx *vertical_ctx, \ uint32_t outrow_index, \ uint32_t *row_out) \ { \ uint32_t bilin_index = outrow_index << (n_halvings); \ unsigned int i; \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_add_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ bilin_index++; \ } \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_final_##n_halvings##h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out); \ \ scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ } \ \ static void \ scale_outrow_bilinear_##n_halvings##h_128bpp (const SmolScaleCtx *scale_ctx, \ SmolVerticalCtx *vertical_ctx, \ uint32_t outrow_index, \ uint32_t *row_out) \ { \ uint32_t bilin_index = outrow_index << (n_halvings); \ unsigned int i; \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ bilin_index++; \ \ for (i = 0; i < (1 << (n_halvings)) - 2; i++) \ { \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_add_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ bilin_index++; \ } \ \ update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); \ interp_vertical_bilinear_final_##n_halvings##h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], \ vertical_ctx->parts_row [0], \ vertical_ctx->parts_row [1], \ vertical_ctx->parts_row [2], \ scale_ctx->width_out * 2); \ \ scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); \ } static void scale_outrow_bilinear_0h_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } static void scale_outrow_bilinear_0h_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, outrow_index); interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [outrow_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } DEF_INTERP_VERTICAL_BILINEAR_FINAL(1) static void scale_outrow_bilinear_1h_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t bilin_index = outrow_index << 1; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_store_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); bilin_index++; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_final_1h_64bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } static void scale_outrow_bilinear_1h_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t bilin_index = outrow_index << 1; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_store_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); bilin_index++; update_vertical_ctx_bilinear (scale_ctx, vertical_ctx, bilin_index); interp_vertical_bilinear_final_1h_128bpp (scale_ctx->offsets_y [bilin_index * 2 + 1], vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], scale_ctx->width_out * 2); scale_ctx->pack_row_func (vertical_ctx->parts_row [2], row_out, scale_ctx->width_out); } DEF_INTERP_VERTICAL_BILINEAR_FINAL(2) DEF_SCALE_OUTROW_BILINEAR(2) DEF_INTERP_VERTICAL_BILINEAR_FINAL(3) DEF_SCALE_OUTROW_BILINEAR(3) DEF_INTERP_VERTICAL_BILINEAR_FINAL(4) DEF_SCALE_OUTROW_BILINEAR(4) DEF_INTERP_VERTICAL_BILINEAR_FINAL(5) DEF_SCALE_OUTROW_BILINEAR(5) DEF_INTERP_VERTICAL_BILINEAR_FINAL(6) DEF_SCALE_OUTROW_BILINEAR(6) static void finalize_vertical_64bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT parts_out, uint32_t n) { uint64_t *parts_out_max = parts_out + n; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); while (parts_out != parts_out_max) { *(parts_out++) = scale_64bpp (*(accums++), multiplier); } } static void weight_edge_row_64bpp (uint64_t *row, uint16_t w, uint32_t n) { uint64_t *row_max = row + n; SMOL_ASSUME_ALIGNED (row, uint64_t *); while (row != row_max) { *row = ((*row * w) >> 8) & 0x00ff00ff00ff00ffULL; row++; } } static void scale_and_weight_edge_rows_box_64bpp (const uint64_t * SMOL_RESTRICT first_row, uint64_t * SMOL_RESTRICT last_row, uint64_t * SMOL_RESTRICT accum, uint16_t w2, uint32_t n) { const uint64_t *first_row_max = first_row + n; SMOL_ASSUME_ALIGNED (first_row, const uint64_t *); SMOL_ASSUME_ALIGNED (last_row, uint64_t *); SMOL_ASSUME_ALIGNED (accum, uint64_t *); while (first_row != first_row_max) { uint64_t r, s, p, q; p = *(first_row++); r = *(last_row); s = r * w2; q = (s >> 8) & 0x00ff00ff00ff00ffULL; /* (255 * r) - (F * r) */ *(last_row++) = (((r << 8) - r - s) >> 8) & 0x00ff00ff00ff00ffULL; *(accum++) = p + q; } } static void update_vertical_ctx_box_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t ofs_y, uint32_t ofs_y_max, uint16_t w1, uint16_t w2) { /* Old in_ofs is the previous max */ if (ofs_y == vertical_ctx->in_ofs) { uint64_t *t = vertical_ctx->parts_row [0]; vertical_ctx->parts_row [0] = vertical_ctx->parts_row [1]; vertical_ctx->parts_row [1] = t; } else { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); weight_edge_row_64bpp (vertical_ctx->parts_row [0], w1, scale_ctx->width_out); } /* When w2 == 0, the final inrow may be out of bounds. Don't try to access it in * that case. */ if (w2 || ofs_y_max < scale_ctx->height_in) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y_max), vertical_ctx->parts_row [1]); } else { memset (vertical_ctx->parts_row [1], 0, scale_ctx->width_out * sizeof (uint64_t)); } vertical_ctx->in_ofs = ofs_y_max; } static void scale_outrow_box_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t ofs_y, ofs_y_max; uint16_t w1, w2; /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ ofs_y = scale_ctx->offsets_y [outrow_index * 2]; ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; /* Scale the first and last rows, weight them and store in accumulator */ w1 = (outrow_index == 0) ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1]; w2 = scale_ctx->offsets_y [outrow_index * 2 + 1]; update_vertical_ctx_box_64bpp (scale_ctx, vertical_ctx, ofs_y, ofs_y_max, w1, w2); scale_and_weight_edge_rows_box_64bpp (vertical_ctx->parts_row [0], vertical_ctx->parts_row [1], vertical_ctx->parts_row [2], w2, scale_ctx->width_out); ofs_y++; /* Add up whole rows */ while (ofs_y < ofs_y_max) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); add_parts (vertical_ctx->parts_row [0], vertical_ctx->parts_row [2], scale_ctx->width_out); ofs_y++; } finalize_vertical_64bpp (vertical_ctx->parts_row [2], scale_ctx->span_mul_y, vertical_ctx->parts_row [0], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void finalize_vertical_128bpp (const uint64_t * SMOL_RESTRICT accums, uint64_t multiplier, uint64_t * SMOL_RESTRICT parts_out, uint32_t n) { uint64_t *parts_out_max = parts_out + n * 2; SMOL_ASSUME_ALIGNED (accums, const uint64_t *); SMOL_ASSUME_ALIGNED (parts_out, uint64_t *); while (parts_out != parts_out_max) { *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); *(parts_out++) = scale_128bpp_half (*(accums++), multiplier); } } static void weight_row_128bpp (uint64_t *row, uint16_t w, uint32_t n) { uint64_t *row_max = row + (n * 2); SMOL_ASSUME_ALIGNED (row, uint64_t *); while (row != row_max) { row [0] = ((row [0] * w) >> 8) & 0x00ffffff00ffffffULL; row [1] = ((row [1] * w) >> 8) & 0x00ffffff00ffffffULL; row += 2; } } static void scale_outrow_box_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { uint32_t ofs_y, ofs_y_max; uint16_t w; /* Get the inrow range for this outrow: [ofs_y .. ofs_y_max> */ ofs_y = scale_ctx->offsets_y [outrow_index * 2]; ofs_y_max = scale_ctx->offsets_y [(outrow_index + 1) * 2]; /* Scale the first inrow and store it */ scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [0]); weight_row_128bpp (vertical_ctx->parts_row [0], outrow_index == 0 ? 256 : 255 - scale_ctx->offsets_y [outrow_index * 2 - 1], scale_ctx->width_out); ofs_y++; /* Add up whole rows */ while (ofs_y < ofs_y_max) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [1]); add_parts (vertical_ctx->parts_row [1], vertical_ctx->parts_row [0], scale_ctx->width_out * 2); ofs_y++; } /* Final row is optional; if this is the bottommost outrow it could be out of bounds */ w = scale_ctx->offsets_y [outrow_index * 2 + 1]; if (w > 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, ofs_y), vertical_ctx->parts_row [1]); weight_row_128bpp (vertical_ctx->parts_row [1], w - 1, /* Subtract 1 to avoid overflow */ scale_ctx->width_out); add_parts (vertical_ctx->parts_row [1], vertical_ctx->parts_row [0], scale_ctx->width_out * 2); } finalize_vertical_128bpp (vertical_ctx->parts_row [0], scale_ctx->span_mul_y, vertical_ctx->parts_row [1], scale_ctx->width_out); scale_ctx->pack_row_func (vertical_ctx->parts_row [1], row_out, scale_ctx->width_out); } static void scale_outrow_one_64bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { SMOL_UNUSED (row_index); /* Scale the row and store it */ if (vertical_ctx->in_ofs != 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, 0), vertical_ctx->parts_row [0]); vertical_ctx->in_ofs = 0; } scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void scale_outrow_one_128bpp (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { SMOL_UNUSED (row_index); /* Scale the row and store it */ if (vertical_ctx->in_ofs != 0) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, 0), vertical_ctx->parts_row [0]); vertical_ctx->in_ofs = 0; } scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void scale_outrow_copy (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t row_index, uint32_t *row_out) { scale_horizontal (scale_ctx, inrow_ofs_to_pointer (scale_ctx, row_index), vertical_ctx->parts_row [0]); scale_ctx->pack_row_func (vertical_ctx->parts_row [0], row_out, scale_ctx->width_out); } static void scale_outrow (const SmolScaleCtx *scale_ctx, SmolVerticalCtx *vertical_ctx, uint32_t outrow_index, uint32_t *row_out) { scale_ctx->vfilter_func (scale_ctx, vertical_ctx, outrow_index, row_out); if (scale_ctx->post_row_func) scale_ctx->post_row_func (row_out, scale_ctx->width_out, scale_ctx->user_data); } static void do_rows (const SmolScaleCtx *scale_ctx, void *outrows_dest, uint32_t row_out_index, uint32_t n_rows) { SmolVerticalCtx vertical_ctx = { 0 }; uint32_t n_parts_per_pixel = 1; uint32_t n_stored_rows = 3; uint32_t i; if (scale_ctx->storage_type == SMOL_STORAGE_128BPP) n_parts_per_pixel = 2; if (scale_ctx->filter_v == SMOL_FILTER_ONE) n_stored_rows = 1; /* Must be one less, or this test in update_vertical_ctx() will wrap around: * if (new_in_ofs == vertical_ctx->in_ofs + 1) { ... } */ vertical_ctx.in_ofs = UINT_MAX - 1; for (i = 0; i < n_stored_rows; i++) { vertical_ctx.parts_row [i] = smol_alloca_aligned (MAX (scale_ctx->width_in, scale_ctx->width_out) * n_parts_per_pixel * sizeof (uint64_t)); } for (i = row_out_index; i < row_out_index + n_rows; i++) { scale_outrow (scale_ctx, &vertical_ctx, i, outrows_dest); outrows_dest = (uint32_t *) outrows_dest + scale_ctx->rowstride_out; } } /* --- Conversion tables --- */ static const SmolConversionTable generic_conversions = { { { /* Conversions where accumulators must hold the sum of fewer than * 256 pixels. This can be done in 64bpp, but 128bpp may be used * e.g. for 16 bits per channel internally premultiplied data. */ /* RGBA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), }, /* BGRA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 3214, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 1234, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4321, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, 132a, p, 4123, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 321, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, 132a, p, 123, u, 64), }, /* ARGB8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), }, /* ABGR8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 4321, p, 64), /* BGRA8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 2341, p, 64), /* ARGB8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1432, p, 64), /* ABGR8 pre */ SMOL_CONV (1234, p, 1324, p, 1324, p, 1234, p, 64), /* RGBA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 4321, u, 64), /* BGRA8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 2341, u, 64), /* ARGB8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1432, u, 64), /* ABGR8 un */ SMOL_CONV (1234, p, 1324, p, a324, p, 1234, u, 64), /* RGB8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 432, u, 64), /* BGR8 */ SMOL_CONV (1234, p, 1324, p, a324, p, 234, u, 64), }, /* RGBA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), }, /* BGRA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (123a, u, 132a, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), }, /* ARGB8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), }, /* ABGR8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 4321, p, 64), /* BGRA8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 2341, p, 64), /* ARGB8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1432, p, 64), /* ABGR8 pre */ SMOL_CONV (a234, u, a324, p, 1324, p, 1234, p, 64), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), }, /* RGB8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), }, /* BGR8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 pre */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* RGBA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 3214, p, 64), /* BGRA8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 1234, p, 64), /* ARGB8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4321, p, 64), /* ABGR8 un */ SMOL_CONV (123, p, 132a, p, 1324, p, 4123, p, 64), /* RGB8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 321, p, 64), /* BGR8 */ SMOL_CONV (123, p, 132a, p, 132a, p, 123, p, 64), } }, { /* Conversions where accumulators must hold the sum of up to * 65535 pixels. We need 128bpp for this. */ /* RGBA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), }, /* BGRA8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, 123a, p, 4123, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 321, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, 123a, p, 123, u, 128), }, /* ARGB8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), }, /* ABGR8 pre -> */ { /* RGBA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 4321, p, 128), /* BGRA8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 2341, p, 128), /* ARGB8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1432, p, 128), /* ABGR8 pre */ SMOL_CONV (1234, p, 1234, p, 1234, p, 1234, p, 128), /* RGBA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 4321, u, 128), /* BGRA8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 2341, u, 128), /* ARGB8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1432, u, 128), /* ABGR8 un */ SMOL_CONV (1234, p, 1234, p, a234, p, 1234, u, 128), /* RGB8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 432, u, 128), /* BGR8 */ SMOL_CONV (1234, p, 1234, p, a234, p, 234, u, 128), }, /* RGBA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), }, /* BGRA8 un -> */ { /* RGBA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (123a, u, 123a, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (123a, u, 123a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (123a, u, 123a, i, 123a, i, 123, u, 128), }, /* ARGB8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), }, /* ABGR8 un -> */ { /* RGBA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 4321, p, 128), /* BGRA8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 2341, p, 128), /* ARGB8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1432, p, 128), /* ABGR8 pre */ SMOL_CONV (a234, u, a234, p, 1234, p, 1234, p, 128), /* RGBA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 3214, u, 128), /* BGRA8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 1234, u, 128), /* ARGB8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4321, u, 128), /* ABGR8 un */ SMOL_CONV (a234, u, 234a, i, 123a, i, 4123, u, 128), /* RGB8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 321, u, 128), /* BGR8 */ SMOL_CONV (a234, u, 234a, i, 123a, i, 123, u, 128), }, /* RGB8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), }, /* BGR8 -> */ { /* RGBA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 pre */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* RGBA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 3214, p, 128), /* BGRA8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 1234, p, 128), /* ARGB8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4321, p, 128), /* ABGR8 un */ SMOL_CONV (123, p, 123a, p, 1234, p, 4123, p, 128), /* RGB8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 321, p, 128), /* BGR8 */ SMOL_CONV (123, p, 123a, p, 123a, p, 123, p, 128), } } } }; static const SmolImplementation generic_implementation = { { /* Horizontal filters */ { /* 64bpp */ interp_horizontal_copy_64bpp, interp_horizontal_one_64bpp, interp_horizontal_bilinear_0h_64bpp, interp_horizontal_bilinear_1h_64bpp, interp_horizontal_bilinear_2h_64bpp, interp_horizontal_bilinear_3h_64bpp, interp_horizontal_bilinear_4h_64bpp, interp_horizontal_bilinear_5h_64bpp, interp_horizontal_bilinear_6h_64bpp, interp_horizontal_boxes_64bpp }, { /* 128bpp */ interp_horizontal_copy_128bpp, interp_horizontal_one_128bpp, interp_horizontal_bilinear_0h_128bpp, interp_horizontal_bilinear_1h_128bpp, interp_horizontal_bilinear_2h_128bpp, interp_horizontal_bilinear_3h_128bpp, interp_horizontal_bilinear_4h_128bpp, interp_horizontal_bilinear_5h_128bpp, interp_horizontal_bilinear_6h_128bpp, interp_horizontal_boxes_128bpp } }, { /* Vertical filters */ { /* 64bpp */ scale_outrow_copy, scale_outrow_one_64bpp, scale_outrow_bilinear_0h_64bpp, scale_outrow_bilinear_1h_64bpp, scale_outrow_bilinear_2h_64bpp, scale_outrow_bilinear_3h_64bpp, scale_outrow_bilinear_4h_64bpp, scale_outrow_bilinear_5h_64bpp, scale_outrow_bilinear_6h_64bpp, scale_outrow_box_64bpp }, { /* 128bpp */ scale_outrow_copy, scale_outrow_one_128bpp, scale_outrow_bilinear_0h_128bpp, scale_outrow_bilinear_1h_128bpp, scale_outrow_bilinear_2h_128bpp, scale_outrow_bilinear_3h_128bpp, scale_outrow_bilinear_4h_128bpp, scale_outrow_bilinear_5h_128bpp, scale_outrow_bilinear_6h_128bpp, scale_outrow_box_128bpp } }, &generic_conversions }; /* In the absence of a proper build system, runtime detection is more portable than compiler macros. WFM. */ static SmolBool host_is_little_endian (void) { static const union { uint8_t u8 [4]; uint32_t u32; } host_bytes = { { 0, 1, 2, 3 } }; if (host_bytes.u32 == 0x03020100UL) return TRUE; return FALSE; } /* The generic unpack/pack functions fetch and store pixels as u32. * This means the byte order will be reversed on little endian, with * consequences for the alpha channel and reordering logic. We deal * with this by using the apparent byte order internally. */ static SmolPixelType get_host_pixel_type (SmolPixelType pixel_type) { SmolPixelType host_pixel_type = SMOL_PIXEL_MAX; if (!host_is_little_endian ()) return pixel_type; /* We use a switch for this so the compiler can remind us * to keep it in sync with the SmolPixelType enum. */ switch (pixel_type) { case SMOL_PIXEL_RGBA8_PREMULTIPLIED: host_pixel_type = SMOL_PIXEL_ABGR8_PREMULTIPLIED; break; case SMOL_PIXEL_BGRA8_PREMULTIPLIED: host_pixel_type = SMOL_PIXEL_ARGB8_PREMULTIPLIED; break; case SMOL_PIXEL_ARGB8_PREMULTIPLIED: host_pixel_type = SMOL_PIXEL_BGRA8_PREMULTIPLIED; break; case SMOL_PIXEL_ABGR8_PREMULTIPLIED: host_pixel_type = SMOL_PIXEL_RGBA8_PREMULTIPLIED; break; case SMOL_PIXEL_RGBA8_UNASSOCIATED: host_pixel_type = SMOL_PIXEL_ABGR8_UNASSOCIATED; break; case SMOL_PIXEL_BGRA8_UNASSOCIATED: host_pixel_type = SMOL_PIXEL_ARGB8_UNASSOCIATED; break; case SMOL_PIXEL_ARGB8_UNASSOCIATED: host_pixel_type = SMOL_PIXEL_BGRA8_UNASSOCIATED; break; case SMOL_PIXEL_ABGR8_UNASSOCIATED: host_pixel_type = SMOL_PIXEL_RGBA8_UNASSOCIATED; break; case SMOL_PIXEL_RGB8: host_pixel_type = SMOL_PIXEL_RGB8; break; case SMOL_PIXEL_BGR8: host_pixel_type = SMOL_PIXEL_BGR8; break; case SMOL_PIXEL_MAX: host_pixel_type = SMOL_PIXEL_MAX; break; } return host_pixel_type; } #ifdef SMOL_WITH_AVX2 static SmolBool have_avx2 (void) { #ifdef HAVE_GCC_X86_FEATURE_BUILTINS __builtin_cpu_init (); if (__builtin_cpu_supports ("avx2")) return TRUE; #endif return FALSE; } #endif static void try_override_conversion (SmolScaleCtx *scale_ctx, const SmolImplementation *impl, SmolPixelType ptype_in, SmolPixelType ptype_out, uint8_t *n_bytes_per_pixel) { const SmolConversion *conv; conv = &impl->ctab->conversions [scale_ctx->storage_type] [ptype_in] [ptype_out]; if (conv->unpack_row_func && conv->pack_row_func) { *n_bytes_per_pixel = conv->n_bytes_per_pixel; scale_ctx->unpack_row_func = conv->unpack_row_func; scale_ctx->pack_row_func = conv->pack_row_func; } } static void try_override_filters (SmolScaleCtx *scale_ctx, const SmolImplementation *impl) { SmolHFilterFunc *hfilter_func; SmolVFilterFunc *vfilter_func; hfilter_func = impl->hfilter_funcs [scale_ctx->storage_type] [scale_ctx->filter_h]; vfilter_func = impl->vfilter_funcs [scale_ctx->storage_type] [scale_ctx->filter_v]; if (hfilter_func) scale_ctx->hfilter_func = hfilter_func; if (vfilter_func) scale_ctx->vfilter_func = vfilter_func; } static void get_implementations (SmolScaleCtx *scale_ctx) { const SmolConversion *conv; SmolPixelType ptype_in, ptype_out; uint8_t n_bytes_per_pixel; const SmolImplementation *avx2_impl = NULL; #ifdef SMOL_WITH_AVX2 if (have_avx2 ()) avx2_impl = _smol_get_avx2_implementation (); #endif ptype_in = get_host_pixel_type (scale_ctx->pixel_type_in); ptype_out = get_host_pixel_type (scale_ctx->pixel_type_out); /* Install generic unpack()/pack() */ conv = &generic_implementation.ctab->conversions [scale_ctx->storage_type] [ptype_in] [ptype_out]; n_bytes_per_pixel = conv->n_bytes_per_pixel; scale_ctx->unpack_row_func = conv->unpack_row_func; scale_ctx->pack_row_func = conv->pack_row_func; /* Try to override with better unpack()/pack() implementations */ if (avx2_impl) try_override_conversion (scale_ctx, avx2_impl, ptype_in, ptype_out, &n_bytes_per_pixel); /* Some conversions require extra precision. This can only ever * upgrade the storage from 64bpp to 128bpp, but we handle both * cases here for clarity. */ if (n_bytes_per_pixel == 8) scale_ctx->storage_type = SMOL_STORAGE_64BPP; else if (n_bytes_per_pixel == 16) scale_ctx->storage_type = SMOL_STORAGE_128BPP; else { assert (n_bytes_per_pixel == 8 || n_bytes_per_pixel == 16); } /* Install generic filters */ scale_ctx->hfilter_func = generic_implementation.hfilter_funcs [scale_ctx->storage_type] [scale_ctx->filter_h]; scale_ctx->vfilter_func = generic_implementation.vfilter_funcs [scale_ctx->storage_type] [scale_ctx->filter_v]; /* Try to override with better filter implementations */ if (avx2_impl) try_override_filters (scale_ctx, avx2_impl); } static void smol_scale_init (SmolScaleCtx *scale_ctx, SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out, SmolPostRowFunc post_row_func, void *user_data) { SmolStorageType storage_type [2]; scale_ctx->pixel_type_in = pixel_type_in; scale_ctx->pixels_in = pixels_in; scale_ctx->width_in = width_in; scale_ctx->height_in = height_in; scale_ctx->rowstride_in = rowstride_in / sizeof (uint32_t); scale_ctx->pixel_type_out = pixel_type_out; scale_ctx->pixels_out = pixels_out; scale_ctx->width_out = width_out; scale_ctx->height_out = height_out; scale_ctx->rowstride_out = rowstride_out / sizeof (uint32_t); scale_ctx->post_row_func = post_row_func; scale_ctx->user_data = user_data; pick_filter_params (width_in, width_out, &scale_ctx->width_halvings, &scale_ctx->width_bilin_out, &scale_ctx->filter_h, &storage_type [0]); pick_filter_params (height_in, height_out, &scale_ctx->height_halvings, &scale_ctx->height_bilin_out, &scale_ctx->filter_v, &storage_type [1]); scale_ctx->storage_type = MAX (storage_type [0], storage_type [1]); scale_ctx->offsets_x = malloc (((scale_ctx->width_bilin_out + 1) * 2 + (scale_ctx->height_bilin_out + 1) * 2) * sizeof (uint16_t)); scale_ctx->offsets_y = scale_ctx->offsets_x + (scale_ctx->width_bilin_out + 1) * 2; if (scale_ctx->filter_h == SMOL_FILTER_ONE) { } else if (scale_ctx->filter_h == SMOL_FILTER_BOX) { precalc_boxes_array (scale_ctx->offsets_x, &scale_ctx->span_mul_x, width_in, scale_ctx->width_out, FALSE); } else /* SMOL_FILTER_BILINEAR_?H */ { precalc_bilinear_array (scale_ctx->offsets_x, width_in, scale_ctx->width_bilin_out, FALSE); } if (scale_ctx->filter_v == SMOL_FILTER_ONE) { } else if (scale_ctx->filter_v == SMOL_FILTER_BOX) { precalc_boxes_array (scale_ctx->offsets_y, &scale_ctx->span_mul_y, height_in, scale_ctx->height_out, TRUE); } else /* SMOL_FILTER_BILINEAR_?H */ { precalc_bilinear_array (scale_ctx->offsets_y, height_in, scale_ctx->height_bilin_out, TRUE); } get_implementations (scale_ctx); } static void smol_scale_finalize (SmolScaleCtx *scale_ctx) { free (scale_ctx->offsets_x); } /* --- Public API --- */ SmolScaleCtx * smol_scale_new (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out) { SmolScaleCtx *scale_ctx; scale_ctx = calloc (sizeof (SmolScaleCtx), 1); smol_scale_init (scale_ctx, pixel_type_in, pixels_in, width_in, height_in, rowstride_in, pixel_type_out, pixels_out, width_out, height_out, rowstride_out, NULL, NULL); return scale_ctx; } SmolScaleCtx * smol_scale_new_full (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out, SmolPostRowFunc post_row_func, void *user_data) { SmolScaleCtx *scale_ctx; scale_ctx = calloc (sizeof (SmolScaleCtx), 1); smol_scale_init (scale_ctx, pixel_type_in, pixels_in, width_in, height_in, rowstride_in, pixel_type_out, pixels_out, width_out, height_out, rowstride_out, post_row_func, user_data); return scale_ctx; } void smol_scale_destroy (SmolScaleCtx *scale_ctx) { smol_scale_finalize (scale_ctx); free (scale_ctx); } void smol_scale_simple (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out) { SmolScaleCtx scale_ctx; smol_scale_init (&scale_ctx, pixel_type_in, pixels_in, width_in, height_in, rowstride_in, pixel_type_out, pixels_out, width_out, height_out, rowstride_out, NULL, NULL); do_rows (&scale_ctx, outrow_ofs_to_pointer (&scale_ctx, 0), 0, scale_ctx.height_out); smol_scale_finalize (&scale_ctx); } void smol_scale_batch (const SmolScaleCtx *scale_ctx, uint32_t first_out_row, uint32_t n_out_rows) { do_rows (scale_ctx, outrow_ofs_to_pointer (scale_ctx, first_out_row), first_out_row, n_out_rows); } void smol_scale_batch_full (const SmolScaleCtx *scale_ctx, void *outrows_dest, uint32_t first_out_row, uint32_t n_out_rows) { do_rows (scale_ctx, outrows_dest, first_out_row, n_out_rows); } chafa-1.8.0/chafa/internal/smolscale/smolscale.h000066400000000000000000000061131411352071600215560ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright © 2019-2021 Hans Petter Jansson. See COPYING for details. */ #include #ifndef _SMOLSCALE_H_ #define _SMOLSCALE_H_ #ifdef __cplusplus extern "C" { #endif typedef enum { /* 32 bits per pixel */ SMOL_PIXEL_RGBA8_PREMULTIPLIED, SMOL_PIXEL_BGRA8_PREMULTIPLIED, SMOL_PIXEL_ARGB8_PREMULTIPLIED, SMOL_PIXEL_ABGR8_PREMULTIPLIED, SMOL_PIXEL_RGBA8_UNASSOCIATED, SMOL_PIXEL_BGRA8_UNASSOCIATED, SMOL_PIXEL_ARGB8_UNASSOCIATED, SMOL_PIXEL_ABGR8_UNASSOCIATED, /* 24 bits per pixel */ SMOL_PIXEL_RGB8, SMOL_PIXEL_BGR8, SMOL_PIXEL_MAX } SmolPixelType; typedef void (SmolPostRowFunc) (uint32_t *row_inout, int width, void *user_data); typedef struct SmolScaleCtx SmolScaleCtx; /* Simple API: Scales an entire image in one shot. You must provide pointers to * the source memory and an existing allocation to receive the output data. * This interface can only be used from a single thread. */ void smol_scale_simple (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); /* Batch API: Allows scaling a few rows at a time. Suitable for multithreading. */ SmolScaleCtx *smol_scale_new (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out); SmolScaleCtx *smol_scale_new_full (SmolPixelType pixel_type_in, const uint32_t *pixels_in, uint32_t width_in, uint32_t height_in, uint32_t rowstride_in, SmolPixelType pixel_type_out, uint32_t *pixels_out, uint32_t width_out, uint32_t height_out, uint32_t rowstride_out, SmolPostRowFunc post_row_func, void *user_data); void smol_scale_destroy (SmolScaleCtx *scale_ctx); /* It's ok to call smol_scale_batch() without locking from multiple concurrent * threads, as long as the outrows do not overlap. Make sure all workers are * finished before you call smol_scale_destroy(). */ void smol_scale_batch (const SmolScaleCtx *scale_ctx, uint32_t first_outrow, uint32_t n_outrows); /* Like smol_scale_batch(), but will write the output rows to outrows_dest * instead of relative to pixels_out address handed to smol_scale_new(). The * other parameters from init (size, rowstride, etc) will still be used. */ void smol_scale_batch_full (const SmolScaleCtx *scale_ctx, void *outrows_dest, uint32_t first_outrow, uint32_t n_outrows); #ifdef __cplusplus } #endif #endif chafa-1.8.0/configure.ac000066400000000000000000000345451411352071600150630ustar00rootroot00000000000000dnl ---------------------------- dnl Automake/autoconf input file dnl ---------------------------- dnl --- Package configuration --- m4_define([chafa_major_version], [1]) m4_define([chafa_minor_version], [8]) m4_define([chafa_micro_version], [0]) m4_define([chafa_version], [chafa_major_version.chafa_minor_version.chafa_micro_version]) AC_PREREQ([2.69]) AC_INIT([chafa],[chafa_version],[hpj@hpjansson.org]) AM_INIT_AUTOMAKE([1.9 foreign dist-xz no-dist-gzip -Wall]) AC_CONFIG_SRCDIR([chafa.pc.in]) AC_CONFIG_MACRO_DIRS([m4]) AM_CONFIG_HEADER(config.h) CHAFA_MAJOR_VERSION=chafa_major_version CHAFA_MINOR_VERSION=chafa_minor_version CHAFA_MICRO_VERSION=chafa_micro_version CHAFA_VERSION=chafa_version AC_SUBST(CHAFA_MAJOR_VERSION) AC_SUBST(CHAFA_MINOR_VERSION) AC_SUBST(CHAFA_MICRO_VERSION) AC_SUBST(CHAFA_VERSION) AC_DEFINE(CHAFA_MAJOR_VERSION, [chafa_major_version], [Chafa major version]) AC_DEFINE(CHAFA_MINOR_VERSION, [chafa_minor_version], [Chafa minor version]) AC_DEFINE(CHAFA_MICRO_VERSION, [chafa_micro_version], [Chafa micro version]) AC_DEFINE_UNQUOTED(CHAFA_VERSION, "$CHAFA_VERSION", [Package version string]) dnl --- Standard setup --- BASE_CFLAGS="-Wall -Wextra -Wmissing-prototypes" AM_SANITY_CHECK AM_MAINTAINER_MODE AC_C_CONST AC_PROG_CC AC_PROG_CC_C99 AC_PROG_CPP AC_PROG_INSTALL AM_PROG_AR LT_INIT dnl --- Required standards --- dnl POSIX.1-2008 is required to get SA_RESETHAND. We should get this by default dnl on most systems, but keep the check around just in case. dnl AC_MSG_CHECKING(for POSIX.1-2008) dnl AC_EGREP_CPP(posix_200809L_supported, dnl [#define _POSIX_C_SOURCE 200809L dnl #include dnl #ifdef _POSIX_VERSION dnl # if _POSIX_VERSION == 200809L dnl posix_200809L_supported dnl # endif dnl #endif dnl ], dnl [AC_MSG_RESULT(yes)], dnl [AC_MSG_RESULT(no) dnl AC_MSG_FAILURE([Implementation must conform to the POSIX.1-2008 standard.])] dnl ) dnl dnl AC_DEFINE([_POSIX_C_SOURCE], [200809L], [Minimum POSIX standard we need]) dnl --- Dependency check --- PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.10) AC_ARG_WITH(tools, [AS_HELP_STRING([--without-tools], [don't build command-line tools [default=on]])], , with_tools=yes) AM_CONDITIONAL([WANT_TOOLS], [test "$with_tools" = "yes"]) AS_IF([test "$with_tools" != no], [ PKG_CHECK_MODULES(MAGICKWAND, [MagickWand >= 6],, [AC_MSG_ERROR([You need ImageMagick-devel (or libmagickwand-dev on debian) to build command-line tools, or pass --without-tools to build without.])]) PKG_CHECK_MODULES(FREETYPE, [freetype2 >= 2.0.0],, [AC_MSG_ERROR([You need freetype2-devel (or libfreetype6-dev on debian) to build command-line tools, or pass --without-tools to build without.])])]) # Used by gtk-doc's fixxref. GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`" AC_SUBST(GLIB_PREFIX) dnl --- Documentation --- # gtkdocize greps for ^GTK_DOC_CHECK and parses it, so you need to have # it on its own line. m4_ifdef([GTK_DOC_CHECK], [ GTK_DOC_CHECK([1.20], [--flavour no-tmpl]) ],[ AM_CONDITIONAL([ENABLE_GTK_DOC],[false]) ]) AC_ARG_ENABLE(man, [AS_HELP_STRING([--enable-man], [generate man pages [default=auto]])],, enable_man=maybe) AS_IF([test "$enable_man" != no], [ AC_PATH_PROG([XSLTPROC], [xsltproc]) AS_IF([test -z "$XSLTPROC"], [ AS_IF([test "$enable_man" = yes], [ AC_MSG_ERROR([xsltproc is required for --enable-man]) ]) enable_man=no ]) ]) AS_IF([ test "$enable_man" != no ], [ dnl check for DocBook DTD in the local catalog JH_CHECK_XML_CATALOG([-//OASIS//DTD DocBook XML V4.1.2//EN], [DocBook XML DTD V4.1.2], [have_docbook_dtd=yes], [have_docbook_dtd=no]) AS_IF([test "$have_docbook_dtd" != yes], [ AS_IF([test "$enable_man" = yes ], [ AC_MSG_ERROR([DocBook DTD is required for --enable-man]) ]) enable_man=no ]) ]) AS_IF([test "$enable_man" != no], [ dnl check for DocBook XSL stylesheets in the local catalog JH_CHECK_XML_CATALOG([http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl], [DocBook XSL Stylesheets], [have_docbook_style=yes],[have_docbook_style=no]) AS_IF([ test "$have_docbook_style" != yes ], [ AS_IF([ test "$enable_man" = yes ], [ AC_MSG_ERROR([DocBook XSL Stylesheets are required for --enable-man]) ]) enable_man=no ]) ]) AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no) AC_MSG_CHECKING([whether to generate man pages]) AS_IF([ test "$enable_man" != no ], [ enable_man=yes AC_MSG_RESULT([yes]) ], [ AC_MSG_RESULT([no]) ]) dnl --- Specific checks --- AC_CHECK_FUNCS(mmap) dnl dnl Check for -Bsymbolic-functions linker flag used to avoid dnl intra-library PLT jumps, if available. dnl AC_ARG_ENABLE(Bsymbolic, [AS_HELP_STRING([--disable-Bsymbolic], [avoid linking with -Bsymbolic])],, [SAVED_LDFLAGS="${LDFLAGS}" SAVED_LIBS="${LIBS}" AC_MSG_CHECKING([for -Bsymbolic linker flag]) LDFLAGS=-Wl,-Bsymbolic LIBS= AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[return 0]])],[AC_MSG_RESULT(yes) enable_Bsymbolic=yes],[AC_MSG_RESULT(no) enable_Bsymbolic=no]) LDFLAGS="${SAVED_LDFLAGS}" LIBS="${SAVED_LIBS}"]) if test "x${enable_Bsymbolic}" = "xyes"; then CHAFA_BDYNAMIC_FLAGS=-Wl,-Bsymbolic fi dnl dnl Check for runtime gcc x86 instruction set detection. Used in 'chafa-features.c'. dnl AC_CACHE_CHECK([for gcc __builtin_cpu_init function], [ax_cv_gcc_check_x86_cpu_init], [AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [__builtin_cpu_init ();])], [ax_cv_gcc_check_x86_cpu_init=yes], [ax_cv_gcc_check_x86_cpu_init=no])]) AC_CACHE_CHECK([for gcc __builtin_cpu_supports function], [ax_cv_gcc_check_x86_cpu_supports], [AC_LINK_IFELSE( [AC_LANG_PROGRAM([#include ], [__builtin_cpu_supports ("mmx");])], [ax_cv_gcc_check_x86_cpu_supports=yes], [ax_cv_gcc_check_x86_cpu_supports=no])]) dnl AM_CONDITIONAL([HAVE_GCC_X86_FEATURE_BUILTINS], [test "$ac_cv_gcc_check_x86_cpu_init" = "yes" && test "$ac_cv_gcc_check_x86_cpu_supports" = "yes"]) AS_IF([test "$ax_cv_gcc_check_x86_cpu_init" = "yes" && test "$ax_cv_gcc_check_x86_cpu_supports" = "yes"], [ AC_DEFINE([HAVE_GCC_X86_FEATURE_BUILTINS], [1], [Define if gcc x86 feature builtins work.]) ]) dnl Check for working MMX intrinsics AC_MSG_CHECKING(for working MMX intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mmmx" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], [[_mm_empty ();]])], [AC_DEFINE([HAVE_MMX_INTRINSICS], [1], [Define if MMX intrinsics work.]) ac_cv_mmx_intrinsics=yes], [ac_cv_mmx_intrinsics=no]) CFLAGS="${SAVED_CFLAGS}" AC_MSG_RESULT(${ac_cv_mmx_intrinsics}) AM_CONDITIONAL([HAVE_MMX_INTRINSICS], [test "$ac_cv_mmx_intrinsics" = "yes"]) dnl Check for working SSE intrinsics AC_MSG_CHECKING(for working SSE 4.1 intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -msse4.1" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], [[__m128i t = { 0 }; int r = _mm_test_all_ones (t);]])], [AC_DEFINE([HAVE_SSE41_INTRINSICS], [1], [Define if SSE 4.1 intrinsics work.]) ac_cv_sse41_intrinsics=yes], [ac_cv_sse41_intrinsics=no]) CFLAGS="${SAVED_CFLAGS}" AC_MSG_RESULT(${ac_cv_sse41_intrinsics}) AM_CONDITIONAL([HAVE_SSE41_INTRINSICS], [test "$ac_cv_sse41_intrinsics" = "yes"]) dnl Check for working AVX2 intrinsics AC_MSG_CHECKING(for working AVX2 intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mavx2" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include ]], [[__m256i t = { 0 }; __m256i r = _mm256_abs_epi32 (t);]])], [AC_DEFINE([HAVE_AVX2_INTRINSICS], [1], [Define if AVX2 intrinsics work.]) ac_cv_avx2_intrinsics=yes], [ac_cv_avx2_intrinsics=no]) CFLAGS="${SAVED_CFLAGS}" AC_MSG_RESULT(${ac_cv_avx2_intrinsics}) AM_CONDITIONAL([HAVE_AVX2_INTRINSICS], [test "$ac_cv_avx2_intrinsics" = "yes"]) dnl Check for working 64bit popcnt intrinsics AC_MSG_CHECKING(for working 64bit popcnt intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mpopcnt" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include #include ]], [[uint64_t t = 0; t = _mm_popcnt_u64 (t);]])], [AC_DEFINE([HAVE_POPCNT64_INTRINSICS], [1], [Define if 64bit popcnt intrinsics work.]) ac_cv_popcnt64_intrinsics=yes], [ac_cv_popcnt64_intrinsics=no]) CFLAGS="${SAVED_CFLAGS}" AC_MSG_RESULT(${ac_cv_popcnt64_intrinsics}) dnl Check for working 32bit popcnt intrinsics AC_MSG_CHECKING(for working 32bit popcnt intrinsics) SAVED_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} -mpopcnt" AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[#include #include ]], [[uint32_t t = 0; t = _mm_popcnt_u32 (t);]])], [AC_DEFINE([HAVE_POPCNT32_INTRINSICS], [1], [Define if 32bit popcnt intrinsics work.]) ac_cv_popcnt32_intrinsics=yes], [ac_cv_popcnt32_intrinsics=no]) CFLAGS="${SAVED_CFLAGS}" AC_MSG_RESULT(${ac_cv_popcnt32_intrinsics}) AM_CONDITIONAL([HAVE_POPCNT_INTRINSICS], [test "$ac_cv_popcnt64_intrinsics" = "yes" -o "$ac_cv_popcnt32_intrinsics" = "yes"]) dnl dnl Check for -fvisibility=hidden to determine if we can do GNU-style dnl visibility attributes for symbol export control dnl CHAFA_VISIBILITY_CFLAGS="" case "$host" in *-*-mingw*) dnl On mingw32 we do -fvisibility=hidden and __declspec(dllexport) AC_DEFINE([_CHAFA_EXTERN], [__attribute__((visibility("default"))) __declspec(dllexport) extern], [Defines how to decorate public symbols while building]) CFLAGS="${CFLAGS} -fvisibility=hidden" ;; *) dnl On other compilers, check if we can do -fvisibility=hidden SAVED_CFLAGS="${CFLAGS}" CFLAGS="-fvisibility=hidden" AC_MSG_CHECKING([for -fvisibility=hidden compiler flag]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[return 0]])],[AC_MSG_RESULT(yes) enable_fvisibility_hidden=yes],[AC_MSG_RESULT(no) enable_fvisibility_hidden=no]) CFLAGS="${SAVED_CFLAGS}" AS_IF([test "${enable_fvisibility_hidden}" = "yes"], [ AC_DEFINE([_CHAFA_EXTERN], [__attribute__((visibility("default"))) extern], [defines how to decorate public symbols while building]) CHAFA_VISIBILITY_CFLAGS="-fvisibility=hidden" ]) ;; esac dnl dnl We're not picky about floating point behavior, and this makes e.g. dnl lrintf() a lot faster. dnl AX_CHECK_COMPILE_FLAG([-ffast-math], [BASE_CFLAGS="$BASE_CFLAGS -ffast-math"], , [-Werror]) dnl --- ImageMagick checks --- dnl ImageMagick 6 needs #include dnl ImageMagick 7 needs #include SAVED_CPPFLAGS=$CPPFLAGS CPPFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" AC_CHECK_HEADERS([wand/MagickWand.h]) AC_CHECK_HEADERS([MagickWand/MagickWand.h]) CPPFLAGS=$SAVED_CPPFLAGS AC_MSG_CHECKING(for 4-arg MagickResizeImage) SAVED_CFLAGS=$CFLAGS CFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" SAVED_LDFLAGS=$LDFLAGS LDFLAGS="$MAGICKWAND_LIBS $LDFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_WAND_MAGICKWAND_H # include #else # include #endif ]], [[MagickResizeImage (0, 0, 0, 0);]])], AC_DEFINE([HAVE_MAGICK_RESIZE_IMAGE_4], [1], [Define if MagickResizeImage takes 4 arguments.]) AC_MSG_RESULT(yes), AC_MSG_RESULT(no)) CFLAGS=$SAVED_CFLAGS LDFLAGS=$SAVED_LDFLAGS AC_MSG_CHECKING(for 5-arg MagickResizeImage) SAVED_CFLAGS=$CFLAGS CFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" SAVED_LDFLAGS=$LDFLAGS LDFLAGS="$MAGICKWAND_LIBS $LDFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_WAND_MAGICKWAND_H # include #else # include #endif ]], [[MagickResizeImage (0, 0, 0, 0, 1.0);]])], AC_DEFINE([HAVE_MAGICK_RESIZE_IMAGE_5], [1], [Define if MagickResizeImage takes 5 arguments.]) AC_MSG_RESULT(yes), AC_MSG_RESULT(no)) CFLAGS=$SAVED_CFLAGS LDFLAGS=$SAVED_LDFLAGS AC_MSG_CHECKING(for MagickAutoOrientImage) SAVED_CFLAGS=$CFLAGS CFLAGS="$CFLAGS $MAGICKWAND_CFLAGS" SAVED_LDFLAGS=$LDFLAGS LDFLAGS="$MAGICKWAND_LIBS $LDFLAGS" AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #ifdef HAVE_WAND_MAGICKWAND_H # include #else # include #endif ]], [[MagickAutoOrientImage (0);]])], AC_DEFINE([HAVE_MAGICK_AUTO_ORIENT_IMAGE], [1], [Define if we have MagickAutoOrientImage.]) AC_MSG_RESULT(yes), AC_MSG_RESULT(no)) CFLAGS=$SAVED_CFLAGS LDFLAGS=$SAVED_LDFLAGS dnl --- Set compiler flags --- LIBCHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS" LIBCHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" CHAFA_CFLAGS="$BASE_CFLAGS $CHAFA_VISIBILITY_CFLAGS" CHAFA_LDFLAGS="$CHAFA_BDYNAMIC_FLAGS" AC_SUBST(LIBCHAFA_CFLAGS) AC_SUBST(LIBCHAFA_LDFLAGS) AC_SUBST(CHAFA_CFLAGS) AC_SUBST(CHAFA_LDFLAGS) AC_ARG_ENABLE(rpath, [AS_HELP_STRING([--enable-rpath], [use rpath [default=no]])]) AM_CONDITIONAL(ENABLE_RPATH, test "$enable_rpath" == yes) dnl --- Output --- AC_CONFIG_FILES([Makefile chafa/Makefile chafa/chafaconfig.h chafa/internal/Makefile chafa/internal/smolscale/Makefile libnsgif/Makefile chafa.pc docs/Makefile docs/version.xml tests/Makefile tools/Makefile tools/chafa/Makefile tools/fontgen/Makefile]) AC_OUTPUT echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "Build command-line tool ..... $with_tools" echo >&AS_MESSAGE_FD "Build man page .............. $enable_man" echo >&AS_MESSAGE_FD "Rebuild API documentation ... ${enable_gtk_doc:-no} (--enable-gtk-doc)" echo >&AS_MESSAGE_FD "Support MMX ................. $ac_cv_mmx_intrinsics" echo >&AS_MESSAGE_FD "Support SSE 4.1 ............. $ac_cv_sse41_intrinsics" echo >&AS_MESSAGE_FD "Support AVX2 ................ $ac_cv_avx2_intrinsics" echo >&AS_MESSAGE_FD "Support popcount32 .......... $ac_cv_popcnt32_intrinsics" echo >&AS_MESSAGE_FD "Support popcount64 .......... $ac_cv_popcnt64_intrinsics" echo >&AS_MESSAGE_FD "Install prefix .............. $prefix" echo >&AS_MESSAGE_FD echo >&AS_MESSAGE_FD "You can now type \"gmake\" or \"make\" to build the project." chafa-1.8.0/docs/000077500000000000000000000000001411352071600135125ustar00rootroot00000000000000chafa-1.8.0/docs/Makefile.am000066400000000000000000000050511411352071600155470ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS = 1.6 # The name of the module. DOC_MODULE=chafa # The top-level SGML file. DOC_MAIN_SGML_FILE=chafa-docs.xml content_files = \ building.xml \ using.xml # The directory containing the source code. Relative to $(srcdir) DOC_SOURCE_DIR = $(top_srcdir)/chafa # Extra options to supply to gtkdoc-scan SCAN_OPTIONS=--deprecated-guards="G_DISABLE_DEPRECATED" --ignore-decorators="GLIB_VAR|G_GNUC_WARN_UNUSED_RESULT" # Used for dependencies HFILE_GLOB = $(top_srcdir)/chafa/chafa-*.h CFILE_GLOB = $(top_srcdir)/chafa/chafa-*.c # Ignore some private headers IGNORE_HFILES = \ chafa.h \ chafa-private.h \ chafa-term-seq-doc-in.h \ named-colors.h # Extra options to supply to gtkdoc-mkdb MKDB_OPTIONS=--output-format=xml --name-space=chafa FIXXREF_OPTIONS=--extra-dir=$(GLIB_PREFIX)/share/gtk-doc/html/glib # Images to copy into HTML directory HTML_IMAGES = # include common portion ... include $(top_srcdir)/gtk-doc.make # Other files to distribute EXTRA_DIST += \ version.xml.in \ style.css ######################################################################## man_MANS = manhtml = if ENABLE_MAN man_MANS += \ chafa.1 manhtml += chafa.html all-local: $(manhtml) XSLTPROC_FLAGS = \ --nonet \ --stringparam man.output.quietly 1 \ --stringparam funcsynopsis.style ansi \ --stringparam man.th.extra1.suppress 1 \ --stringparam man.authors.section.enabled 0 \ --stringparam man.copyright.section.enabled 0 \ --stringparam root.filename chafa \ --stringparam html.stylesheet manpage.css .xml.1: $(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< chafa.html: $(srcdir)/chafa.xml $(AM_V_GEN) $(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/html/onechunk.xsl $< dist-local-check-mans-enabled: if grep "Man generation disabled" $(man_MANS) >/dev/null; then $(RM) $(man_MANS); fi else $(man_MANS) chafa.html: echo Man generation disabled. Creating dummy $@. Configure with --enable-man to enable it. echo Man generation disabled. Remove this file, configure with --enable-man, and rebuild > $@ dist-local-check-mans-enabled: echo "*** --enable-man must be used in order to make dist" false endif CLEANFILES ?= CLEANFILES += $(man_MANS) $(manhtml) EXTRA_DIST += chafa.xml $(man_MANS) $(manhtml) manpage.css dist-hook-local: dist-local-check-mans-enabled all-local chafa-docs-clean: clean cd $(srcdir) && rm -rf xml html chafa-1.8.0/docs/building.xml000066400000000000000000000105451411352071600160360ustar00rootroot00000000000000 Compiling the Chafa package 3 Chafa Library Compiling the Chafa Package How to compile Chafa itself Building from the Git repository When building from a clean Git repository, the build files must be prepared before anything else happens. The repository includes a shell script for this: ./autogen.sh Afterwards the build can proceed like it would from a source package. Building from a source package Chafa uses a typical build system provided by autoconf, automake and libtool. You can build and install Chafa like this: ./configure make make install The standard options provided by GNU autoconf may be passed to the configure script. Please see the autoconf documentation or run ./configure --help for information about the standard options. Dependencies Chafa depends on the GLib library. If you want to build the chafa command-line tool in addition to the libchafa library, you will also need ImageMagick (specifically the MagickWand library) and its development files. Extra Configuration Options In addition to the normal options, the configure script supports these additional arguments: <systemitem>--disable-Bsymbolic</systemitem> and <systemitem>--enable-Bsymbolic</systemitem> By default, Chafa uses the -Bsymbolic-functions linker flag to avoid intra-library PLT jumps. A side-effect of this is that it is no longer possible to override internal uses of Chafa functions with LD_PRELOAD. Therefore, it may make sense to turn this feature off in some situations. The option allows you to do that. <systemitem>--disable-gtk-doc</systemitem> and <systemitem>--enable-gtk-doc</systemitem> By default the configure script will try to auto-detect whether the gtk-doc package is installed. If it is, then it will use it to extract and build the documentation for the Chafa library. These options can be used to explicitly control whether gtk-doc should be used or not. If it is not used, the distributed, pre-generated HTML files will be installed instead of building them on your machine. <systemitem>--disable-man</systemitem> and <systemitem>--enable-man</systemitem> By default the configure script will try to auto-detect whether xsltproc and the necessary Docbook stylesheets are installed. If they are, then it will use them to rebuild the included man pages from the XML sources. These options can be used to explicitly control whether man pages should be rebuilt or not. The distribution includes pre-generated man pages. chafa-1.8.0/docs/chafa-docs.xml000066400000000000000000000031241411352071600162240ustar00rootroot00000000000000 %gtkdocentities; ]> Chafa Reference Manual For Chafa version &version;. The latest version of this documentation can be found online at https://hpjansson.org/&package_name;/ref/. Overview Chafa C API API Index chafa-1.8.0/docs/chafa-logo.gif000066400000000000000000037150611411352071600162160ustar00rootroot00000000000000GIF89a6TwDq Kpo#3MyCiB_J㥴!0o,@r LXHi4L0Y}Go0,+5OMq1 4<:V)&6mNwJo>KxAyDrPFn.0O|m3Hy.'GqYFqWmL?p#*n9MA],O8LPb6pgv@G@N 3g]f;qBb1GGm)/Ҍ5KZyy'N0#P]NҨPoX9U;RvKoMj;|u{+1Mdexoqq'Qai4IDPe `b.+S(T,?&Z;EF$ =C!?kAʇLMDsg?^Kk|]Lh|#'[{@W89q.s|Kp}z:4uIw֦ݱ0Nw_7^F-ZBVnuNn9N\l{%|Sޘ_S0[o. ?V p$rMns{P>`x,_S 30ZLE|ۦiQg2_~1tN5W jL8T! NETSCAPE2.0!,6<ৠ*\ȰÇ#JHŋ3jȱǏ CJ8$R\ɲ˗0cʜI͛8sɳϟ@ }ѣH*]ʴӧPA$Xjʵׯ`JٳQ D#۷pʝK7%ڻxBECeݿ LXÈ)Xǐ#˅娌UĹϟJM麔/^}4sJAGW= JNͻԬ[vM6ȓ+G$УC.^h`  ӫ_p)v$T!s |Gyg`VwV_RSjhThNMEiRđx{"H`tcP w&ۄFM҇¡AېT|6J򁨠J""G{W袎Y蝅6ƅ#([$kB9L-&j"~y7`汸%呇w ڜ!$ {nh$\E~Ɠ (i<.I[AypI'Iy'2*'(8o@&X^AAl9l:ZHљf*TԬ2bz%ii±,Z[$[lc|Vﺑ)I'G q0K0j<1<`&20̾a#'Lo);R\qӷPW|EڱwSgϦK,}H1<&y6_X73BgtD111Z' GN5W=-Qa;:]3Uh>QP)wn9+wɂ੖K+mb{l !Pӧ'!r3 JCl<kdB}c_/k'Z׻_VE.L\6:ؑ'(A)J'B a2HE*f B0 281̡m8ܡ HT`f&:PH*ZX̢.z` H2)Y6pH:1OGюIH;L"ǔ&=Z#3)'r҉!9I&hb&?IVFѕI#)Enl$ESƲ"'a9U%LOS#1qIjjQ~M%Z2Q8iTqe68nҜ\;ng+~2:IyLhA7yO{NӚN^Z4M&zI`zꬨH7jQRt&GSQ>(Jy9R”85iNUSJ@"EySJrD}iR[S^tiIZvs1KjՋvSLUӡĪTVգRFTKM԰5`MVW~=lS k5ml[W4Ik0XFZkԹ26Mgck*ڧu_]+R嬧\)9X6+BkΐuU(XʃDM;RϽ.F*]ն]$6"U$z;@f{Ro!_Ƅ:Hnic [806oX qFL]ĝHC0gX81$vcN|A"oH&;Y )F`Cn-\ khI<X)na5αyYA.|%ɈN4 )S.pe.w9 _gC29hN6ypA: xγ>ABZɊ~_<Юt*.Lyd4j1s uOU'"Nw`X;^Wx^U~Kw{_[g/%je3{F ]im y6;mp;w@ns[ݭn=z;*pIû6@GvQ&li[ߴ8i+!7Kꓣ{*w7r|29%|hl!dC'?<&=mpKħ6ձnx+bc.]i|~nͶ[n:FxGݙnf'~9l~o%a<̆yvCQnɱX{֌sۖ>#~󡿸.ͭw!Oշ~5c=+y7{{|V6|w~~~~a7jG(}v2{H((QGM'|G|vG$HV'h؁Ugu hgo&(p)pT_xwnRw0!mu8VkP dXr7kJ؆ƄP(SXvHsz^(g`HkVfkin$0`d~_gHgx/dHMHf؉cNvjv8yKtHeXah(H1h9`n%8v8d镊dXɸy (Hgטs7h VH$uj0и;hGsn%UO(P) inYM y@ō9`2ّѸj!Ɇ#Y%َ(Kpk/ 3j "D$ p @,FJL4)h68QvZĕD_a9d58):I+m q{G@8$Iiz`薶5ƺO؛g}nD+^rҼ&9@`N;k;Li{H{!;e˾Dk[;᛿oxDJ[Eg ˡ mDҿ@f>{6 4\ÅnˆjC*lg,.̠R2\c5l7 \KpcPV|FA@؅b/ELEH,4|ñ +uDV\X9ův,ŗSyf hQmq<Rǩ~. [0 Lt<ȗ*zȈF|'\aYpUG\};hlnJƁ@i-pad~;K9ƭPKw&C@7lnDl>wM*a6X}咙z9S qi`h^xjk ,u Qf[Ȝ~En蓎>iEjkN+a)fzN |؛=[TWnDntL^t^~F!^E. Ȯ7Nfs̩fRy{鿮]>Avdrnn!h︮PgE>&0ly--#E%o'D.0o24_7Vpo=^.VtnQSUWYT4~(_5pj?n߆Z\~/EXKg͎/tq_y?mvȦ~7]auWw-af `na(КKdӗr2fcF]ߠllH6UƏ1؞0tvg$XA .dx0CMlhGDSIN)U`Ieƌd,G99uٓeQAiqQOI,]g(QZjYn(ҏ\Ŏ%[ٳeBj)#э I$J)ewL%o ҹiAE %zRN{JZ[%iԩUڵֵ^}EJ40b"NC ;(zcFɚШOr@x cC $H$(I& A>@J$OD0cQ,a'1?1!?kR4N>%#,)ʏ$J+tq:,K0[EiF>T2u|A 4PTS+V[MUBA 6<2QPd> U1LtNGDT@O}uZZl眳UWK-נ657Y݃v%]xqu-y5IɬtKѬXjqT!KeRZoP"lnYux+p \sEW7j]xߕWzWR`) 0`=f͓Veh0hJV:Tpy8$裫kkX`fa>Tי0[7N{{+1;5L:D,k<"\U*׮ @ᠩ%hPz馟rWꫭzZ&sO6l?[<+<WoǓ|P<޳s?W%tG駡.WGu__|٫=lov'x#<-ͻBMqcS9Y*X!s#L>HC:#6.oc@YiqTH YpRA$<*A}0*FƮiKaX:@`cihC:omC1Q?'DQpHtDQdlxqeh[Vx0S!XF⬱l| ػՑw\ CT !2L=E{$GI@YO  ' GPMVMC,(bD9JД8F7ʸD g9I2d^JoҜZ=ej)'ƌӟ)` ZԣZ sSsMdQ&UQ鑫b*HEU(B=)YDé lukwA怮;@J @%ZT>vXlԙ<*4Ypj;crVD߉x0"Fڛ5u]_Ҷ X54MOoN47˶^5jW]R%l;kVx:KibZQ&{Rehy~r攌ٹeѼ5͋qO\9/ɼxγ̵X%9aC'q+1HMҔքLdFN2?d(Kٜ~nU- .c`Vb;2?f1׫vMl9Fmb>v`j7Tұ'Mio;EMiP?BԤ65OP-wÛvvo:[;o^P|;p16k>J<<ÛC#ƸCIq(G=Rvkݪzq 6&f tSt oz?/Ͷ:zDH5]9ٺN^wwWyKn*'xDLo=|o 6\Dх;/! gG7\Dwk}(i8y[%'F)Ui״ cW^Ԭh.Y,7m>89;4+:/8:K:SP8p>>雾cK{3K@8CTCd67Ԅ89#{??=$ 4z#§Pľ9RA=3ZHHGuDESE4`E74 <:]4A5]CвA,?8"bK$L%\H4Ehi4jF<2OƎ GG2,:$Pu"4LCUGWG8|A9A:E~D@Ń4;od2l FL\s˷0NKKm, L04ȑ"L !\ Ŕ0 ɤMfk# ܿ ͜(MJtMEMʴMMX$?DN˥GE?@ABCDUdBGuRIMPXPKTT? URN2N3N4Ue_pZXwT9՞Ӝ^%AQVSY\8"]Dm A؏g5PT)5 7ю`;.%I/\Vp W8YPAR]*ňP_ wͬpc=T>c$ -dd9QdEGed6BddPNfOI#/%ST~UV.WV{9u\>]N_c`:a);f.= h4,jkdlgmnNdl[{pvqJZtg*eX0 BQU ҔjގY.΅eǀ.'cd|hii(i8`i4siG g2Kt6 N0ǙgfeYTlhZNGNNh;vHƄFBn޷1yd7kZ0ldkez.e|礎EH쀮iFKFս=e&بd;V^6k`폾n6lqiixmk.g6n=+x瞮.{ngVpn̶6r We&njO$"o+淺kGpn#BɞnnliVzeˆ n?pXZVZ0LG*ÃǍ?GqkHq=smHa\"ix>웃"jQ'(%r r n._H+ sHS?u2B"k$q0IXs6G*8s`s;m~s>?O?wIdZ!C#pynK_|t)rQ{}xQSGuLv3ߋ4uYo['9'i:sv=&,*tigg'F7GoHkimt opϥP'w07sToGvuYyz{o|8dj:ǐF(P2-sJb(kl_jƌ-ܷAi ^C/K? 3n1ȒCQ!̚9pn/ѭ9 &WhѰVt"lF?M nxI#\!đ=]ĨR{:R藉bTde= JQI, IQEJj֖]| ;vZvOԮyx b : `MVGf}FPh[fimZk%wtPB DWGFlDKG-Ws)&u4] N9I%w EQJ9^y祷Tdm5_}c$e_\ez!qbygFFY(]afqY %ڇ%bK$ZN(&m "\D5wc;%sAB)Uw$-RxQRɔSP#]՗^ibE IgZ;X|HJ a(,:$l!ŨЌd#"#%$dt0juI:#:T*+z*ǫ]+V{Jt5,ךv2Zզ5*wn5x嚋 +UEͥ@jJ/}q<&㽦)L_ !O2ޮY{ez,*y,c`)0''a49K'I29-r9@()FJ[C4UJͯ0XZ֗tm;%v CQS]wkzwKߗӛ\(174sf5q3g9K{Ay[aA0tHEViZYv\sիޑaf&6m%pӘƘ1Xg=UožL=$x|a>M6.Hq.~]6U:{ 'ٟE* jXuQw%\*2)YzUBpz%lPx%{kC8! m>9B~Y⇖F_GT]$%IXl y v%iɋmOAglHF8g8({ Gц9\D B~z2[J0\?)O9hB-=9EEdQ.(8ґ"Ĥ(mJ#RJ2)MuM0G'M.LK9Cٴl횅6ps]+unػ-yzVm}'tb6[&M1mvqoK6'8)s:LqM w[#[0gqm^ph\ ,jm_f.).n7uv5K|V!wuvV6o\^e 1#Ev!r~DOc ~݅˞ /݉[E+!'68%yx-T{* WaDR\ẍ́ I t% myOu|_mQ_̈́:DaEy%Tډ_APğњ=}G`f % @_$`R,`JB_Ay`OnK"DTr``1Z ͟K F yA `e jFz`.6RNVHq 1aai!__ ߛar_΁.!^)2!مq_H"&"-4b'"0F$nt܍% &6n"'V(Ά(")")!*BHbN^V l-"b"f.~"00c $㟵a3'(cG@f )2`E%<F^77! -:9!.## I;bv<# 61 >v3VU@`(fArr)$62l#"U,jjB\Έ}d($JJLKbK֍L HM2Md?N`O"OZEPBQ"!RRb8ƍSb`G~$U~UlhpI$'|cd丒edZ%%@ԥeCQvcR._6SJa-R(&Qm&@$D*V2cBdNXV&YJH e^epTf\iCTd_^dkj`BelJl&n:n>$o2f)%LpR&Xdgff2'[2NEPa6dDdEcKG`B`&5Pmfzoz{~G||g}rr~ӓ(TlbX6"̝rhu`QH% FXI@b@f'\)01E1h9g(hhhᨎbX *)!()L4)e:M"m)mO:m\S:ORZ`,%9iZJmΞm-!b_-MFk(22m '\m߂. .nBkH*n2-Jn9:ڮmM.-[)s n^r2n$䮷B.T.r'*"&*"oL~.fNMV/\ojFlE€/_𾭘l.yNh$ /s..¬$>J7SIepj7L/00 gbR DUŝI^. PEU/[D&l& @ 9chE1!_W_GdJm1VtK|gqL"r.lT i,ܱFڤ$ ( r!ϲarf%r"x1A1%[r`mhrJpr.z(#*g2.Y;r 2-3ڲe2r##200qsm s33#2D44g384ʐses$82js$$Sr<_1+&'kt(1ߘri@ۤ#4K _ C+#?:Gt;/EDF;Fs'GtHCKs)S3BjJtKG(tUŴCC4$'$ %oNRm=>Wh? 9@OJK\K[U/2#25MO;;K+W7H5>:4IZJC\Wv]K]{LcDk5EI`O`@a bY4Rc70JSueeGKfftVoXvvi @jju?Y0 mkQmKunn^s_{!pXXw[~]QDs#@t^7nSTmw^swx6yy=r2Z;wYH7׶u7K ΉP8gA[xrhrl8܅x3G8ȹGȸ.F8xT 99'97yv'츓O97ƒW9g_dw P9999ǹ9׹9繞LbA9::'\$GO:W_:go:w:::@@!,@*t *İ!H83j8q ;)dPR²ɗ9TR&- ,`ϟ@7Q<*]ʴӧPJJիXydpaC#vcٍh2H%ży1mk׊NCI LÈ֊k`2ZklZk~| dM}S^ͺk:I䱕^vnB9H&)qLP7%1Zx);zc*hn&7g)k" 괢QFʜRꨣzv)|_:hnꪈj_HiQbzrJj#ʨMF)U:ŷ"ɹsnKt";S|b{ *X.K>f gqk<.*|. ;<K"^0<[ L& q2llSl;GD-=eHw5PJ/^O;-=WAgm\-waMvgGݳD#5t[XwCl"[ N1׍8e|Gx܁F[ns⚟y)S;{j;*K..{K.~8Oz6Y0>~P (ٯ{,HA- u V1c`eWh4bE2 ]/ܢa,C"BT!/ `E=mH&ɈHD&:X⁒8CZ` H2hL6pH:x̣> IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌e AZJ▸e-y^ʒ%.s^j &$yKeLf0) g>3iT&5iLlQ1YFpS$g9vy/P8@ jP_X 5B42(ZЋbTd4["0HjAӚqN3ZKN>$Z,2Ӛ/9ݩB{UBQ?bԣ$)-T M1(Z DY]V*7 YA LZUZ[j5#b%]VkqFd0:5+f0/6򅭜a,[hL&r2L 0;cNp̯as9f/:yg=πV(3ϢQ#@2rq']<`i Nw >4C-jR q&CMخ|?8ٴNiPKDԮh)F6evvk=mjź6 mq;Րp;17ѝu0my/-L/LgԢuQs&v-i~ߚ8F;9%`@CKݗu2ZQt㖅6Y%Nu#XI\8S9dt4F7-t/Pԫ^ug$[:odDŽCqv}Hi̝Sյ^s '6/GX+3"gOy1X)4;χ~+Oo!R~fa=C\ϞxYo %/Gs3ϗ}ow#ӤI2ʬp*T|Η~7}~ Gdg0~&(oPp~}~-g}y}Gzh7|X#8!($h觀g{ L082PzigW'ocуSƀ*{Hy׷@G/Q(ypX{pe=ȅᅗbXG}-/{1hwxOzp79!;(Z#|'H9w+E %Ryg/Q/ 8y:!ˢ*xSW/Uge{ @׶fH(ŠfH/8W^ژ9| !HHRH/~+FƈyfȌጫ'Q/((بȍ"XuFюR %Hx19x h 9i yӘ!9 E%JƎ*Q/Y19҈x:9"Y=i?A FǸIpD 6Y@Ǒ%xPW4GOØ_ic!I9hhmɑp)R%8uvzx zQ(Ek!/Ih4k9 RT7Pј{y}@c 0AOɖ?疍 x {wYYكYiɘ;IYPxq5h^)aIiը݉e u PwIyɜ02RCMz% S"z E0k o#2p$Zyyl0A*23 Z@穠7ڣ :E_%"J:zdz,";V&*j%*.,̰\ ^s:OYBjU>2DZJwHZUJoդyPz'xTy+0ȥ]^1k:#qŗ npJsZww"ԧ~*!U:Wz|%9Lj so*2qZ`zujT"gF Z.:1᫿o*zjwdo*2"5&*VJXzʭ6j!aP 8 ˰ 亠z,蚮ڮWa¬ 3\G d` { 12n аkk*+`˱> g% Z ؊.[02a@`:ѳ"3XgBMKh؁ /-ǵ3Kz ;2b)[( özz6j͇סJj)ntK뵇;4C+. { p!r t=N{PԺUҺm۵;K鶻1ۻm{.z{˻={ʪ{}˲ !7@[y- KRaD.uQg1٩TG2x(g"tz0Lq;*;_ ;9 j4|\|+ L"sŭk<2Qfƺ\*<,!w y{ Lr<ML)?7XŗȥV`v=nv’gg<35ɠ>̠>@]CMEzG IKβ$-\llP$ժ`n jml=ڗ ^^ ~ xMb+^3{އމ @ } }=++cM7nj hxA lQve"˭ԀLº(5 ?tQ.Rm~0lw~_B[^7Q#!R%u6~rM jAjN.xB'E粊Lxz[N6a.Nƒ7^Z陊4!>P~un Q|n k>x =i^>{~>P=X臮h餾nv !$l>3y&sd^qyL֎O A*FXg  u]R%n (5nP|75>^~  /NPGX.ΏSO nf\XZ_\pOq>kwmo@g2ʮ vO.xB~.Nnݎ/V SU5<X'baBGM kϢKiOF; ȎN'b=7| W[[sIC$XP+ -tְC$NVE5nG!EdU$*)gd%KL1e.9ug(A % QfIct#/Q%M:qR)UիIGĎIkHiKӖhqoUV²ko[s p3XVd 㗑W\9e{ l>; 2t/ԩodݺ#e-dܳdμ[3O= %|N5FJuUFWc= m wڼ׽_S&jfW|L),28; 4' !rƴRCm*52 6V/bZϷnP3(c caFeCbG߾ o3.zkϽKLC$P,A|LpB,C 5C.[ KPL[t1FiFp}.H!HBK/OZ2)>ۯ:#2l5Wv-c`ߍ; easqa"80-bNCNo二1?.9+LgiZcfSKivUئZږfkF\v~nD~=+υ GMa,#<ӫf2iO:w9ffCdte;kKϜZ m)X,iR5ww<$lc^ڜzS!kX8 UgD;D qqE%-$OLG()6b.ubl`8nS#20 j 2ι@&Y9ȣ*qLd5 XH҈d$|GD%/mre'cJgrtW)g@&h$FZZЖl\uy8R>DF0Y̶!33͋ "Tͳ]^YI`R#gW #tt/r,c,8K%vAۜC%*0uPœPdhCLVQE݃Q9d7?R$~%IɐҺԌ_`ܙxQ DWAJHv9N{:aL!jQZFKmjFz7l/j>kjufW=ՅO$@9?,*Cd+OWԽd<ʻ2kO}`SءB(ۖZbWO%ճBCwkU%KM~uX'&լX:j1p\kKWzMoFsqWK8GXUn\K=ŕJ0ǺN]v{'[^X/h!Zִ}b_ӹd"mmb݁`.Ѓ# pP2a{ qnFM-xjzRcowr|O P2*045M{[A&L*Xrt _^*y!f @ MRXw7y8S'l}EF5nZk`CQaCm&%DwӦ6cQ lraҨ=aV&:FGl >Q"pQ C(C BL휩T-VK%ud]6Zָk`Z]ldJAY Ht&kbhd&WpSf*gkigݔi7~ޭxmwd~8.'<2 gENg"vkd\f>$!XNlD-yt1Ν%ܧ@cN't°d;zF>Ogԥ@ַdf:&7Nx7!@BÂ'>S~{>|8sr/舩s#s7?H?Ylx??У:?[k8##S;\ $${)9@"0 j5@$3˾0BV菝f(ACA7{B = "D#T8i8d!a6)l Yh1bIº0@ D^ʑ 6tC rk (AТ#ʠ=>U!ħ9C\kEdT3CΑĒDK$DDW|Nd;P\QPC+CMEEr6-ECi9ŝah:aAC[AF D$|sFDPDjtkDlId=& @o4"pDqBrĽ9^H+v\6wx;s9WGcGO6ǵǀT;"` ̼zȡHxFF? HmIk‘FgqEsq G1ǏE EI0|U܍VtE8KXY ~J,Ȳ+aT5ʨD AD Jk$BmdJd1KCAŷDǸTGR븓;KM˾˝{s$?@ ABC DŝEݍF XIM$}KLMNPR-u.1/EU S{Vu3VI67e8"U<_U 3-֔8d́ef}gh}Y5VX֏m(R*!+rms uUveUWeyXzU{U|U}YVSQlQQ@ѻQmqTb 5RkTl*&} 'UVUUIPS5`qזڷWbC׻םY V@1X@أuΥUԦeԧֶMyU+kP!-ԏ}+r۶IP艓5H4 uu6Hu\{ۆW[ H۹:Eb5Ch ͉N&]jȅZL\5ܮ)們eU }Zn a׍y]]ݥYa/ŏV⋃)|*_ x _}ܴڬ^^}5`*ߤ8ߝ0>Ze]םX؝U1U(UYe`_F_Xۂ\.^fIf`f _˥ڋڌ^mZM\o^Fq_g)a4u6ZUCY[ji Ya"b!+"ދnNb R`ncx(f\ԑT-Xbv1F'3K4NW5-67>ƥba[ccd#&d$&%DƄR b56ʥLMN'tMa[5S^cڽaWfeXY_Z"F>%_EfdaG`d ^ebfT1WN!4NWٸf gr@ڈE \v)+i6h<@"(I^&/">Qa3@b <`X(=/ uNyc9!">.A8rjjhf T&6(v΢9ig頹ji9i j&Sx6Sarjۀj6Q j=G"F&y31^n 66i&(XN]i?jAj˔ǎl֐ƈʎ 6#Ͷ.F mKm[F~k+i^smi&(=Fmm" kn9>nh"̖i͎ Yh"m>mImlΉ$jbP֊6noOXnLhk o IWФ`־ Ʀq (oV5_;syhg-Hmc` @rprچW:_sr,o91t s345cs7O8_s<#k'er&6Dor~.ot1t8֊4/pN.OsPo RPpM{m opuDFw^_f vai1>2m 8kJw̳@$SvgA@>A@ttYR:q׎rOs|[wykwxowsa!yV$`M v}7W;?Po39@wgvtv{y/y gӑxKZ?Jy[xAϸ#z!'w\x xkz>90ɰ?p9ۘHK(TH: 26ި l+81>a}_jYEhD7>hg|wqw 7_ LJȧ $˯J|}^"}ӷ0gQ}`}}u?܆}7o/'䧛忞Y#d^ jᡈ}QeFg=#F,VW8%Lg>d&:'Р-(Jji?Rju)Zr+ذbǒ-۵ ڍrK]I.^NzB,إKYFeX#H'!tC#ReFJj9z)H)Ui͎7qӧ@EzTRWMj*V‡/^ml+7.Rvݻx`܄YF{5r231OHLCL t#:lFQnPǁJa  rm5wsuYlj[]w)cwz]fy&|W~/_q ?(F%ԂE$Tr̹e rq%PyueMVVYw%b-dR1@M0|! 6y#_fOzH=n{q%z;/#^jg]iyqE'=POÞ v|g+j Sȕ.~&:p(@.xD8  /$hQˠ9(Er! Mr,,F7^04̟$Ҩbխ9;TFca *F8QRc/'\H}1-;Gٰj\#n>M-oi QюX %5<H(PuV,rE`L#IIR҆L6I]^(G)RGyU*͸r,YKݲ@C`zgЅZ0G:TXJfN,`4IMdX{[or/'~\`(L!3%zUPT.}@ r|ʩYִj l[Ci1txWvt{i_W,azXr c'VS5:=-O)ۯb֕B=Q ŮYAJTF2{jm *js-+mܞVUl6$t 6H'>c3V"_{80Mc3eWVPJP6noKVu$b8JQVUgobI+|`3x!!aQf t[VD *'NclSƘq2`B;I}|G!0*d s8v P|V,^r9^G29͎6!CGvĜd24Ky޳n\>˃>u Tb1_JG9Ҭ lJdX2eO&2DnѲum:\FVKek&xr=ecsĆX-9{yO`צ5RFn&ܶ9Z/3lݰ3r-q{1w݋}IH8hP%Snp ܞFup,H8} .cCRl~&wR6A|9Utzc?.rGuGg†y̼M:!u`<^w>ZgN/PNw=_Y%p; ;—<'W;rHD9f*' !,@*tD*|İ#>dDE ~qǏBBD(SZY˗bIJJ,WTT0@ JѣH*]ʴӧP SŒ$j5q+#8v1$'&OLsK2cm۝=˷߿*[%zVFeELn[+o]ƕ;݃yMA +2|k∋-6v yY=s6^[ȓ+'hUį'ƈoON[w'q޾f}3Uz3^Y;ʕz;@}Sqh@{X9V]9HɄg]m~ ⭵$*(^"~"㌧hNh! Υ~ id]$؟L6dR,"H5=M ېyIn`ldRR)2bc9haqwiFn&pHYtyZpdia:$梤\S>ZevR|馜*)*qrYY!ZS]RhVKm+ [,&'dRMzanʒnZTnnkR(ݫf5> &l-K4A[1$L9\pf4,s鬇sh5-R73;sA\HI6 NK-')JC{'sL twMeooKۗr݀Zyw[~)+XW<-F,im[+)+?m:Щ:^3^:O2{Ȼ[L|'|[Ww/o觯/o HL:'H Z̠7z GH\H !P'L SB0Λ*$@ A"H! :nM=XD#"JWDA1"+bQ[4](Qd,FѭimHF3@iGqcum% AJN$6IE*mqO i.㔨d%'VfRe,F䑤)QyJUR|3dIL2'evK_09K4x% yK_BsҤf,5WGeS_L7Nqnl9ϕ):+Z>I~ $y4ʨ0Y$扜z >~4- ArPP =CыsEɁ͠3GR%I'RB\$p锎 i:5q%zӒuQeTNm*T)"U .SѮzu4-bX )O?ϚV6u[ceT*b})TPȬf㰉]^WXFVNe Fn},h9u,P!+ԍ2ufB[Z^6,\Y[6ݭi{+١.7 zY!)./txܧvm]s]~ v]uһx[ަ1w^N\K\_6N'\ ["6aNFo]/"[.Ť`36XZgl gp@b3Ě 0xS,2ϐeg,W8㢈]rO➏gkt5z H$=hH--Lkz͜ހ? Pzz. otf;B&Ȭpi[7b4 ykRtMdj3tmY7y##fMLr[@ elhTHczޢv-~ xANpWV8XMy\]Bg2at= }JgҝfDTձ>cs!nNvؚAzяTsl>k=#{z"t}_'V ֢sWx?t'zd>2 [ ?Qvųd!f.=P<ޫcoF~'e=o/~`ѽ?~g?<`+BP~NV |tJ4E4c ytyz}}-nv0$X T%K7C (cuv}{9IbUw|z@%d'TC MO;b}}g|_>8cBh &x4NzЄw}}}§?x"a~cIcj(׆QhT:V[H~`Hb(L.Ȅ0脅wn8prTHPu8m6s;OkvX\FFЋp&c@XqZ8C ?NJRHtXx8QHlL6xmM LF!u٨X(F8uƘH8BǏuhsyI3VEp  i焊n!uu8mZq$ .֋9Qhƨ9U!sŒk/ُybQ,8 68"yBȐ)9C,ɔyR94)W8j:=?9A75PPHug&9–2}Vy|2uɓ&_U:TIy.N#2٘U q51\^ɗ7])4I58HKVJt2 0zcm~s  {-}QiC4GD7JTYy9ȩYI[ ))Iљ}ĝY7yJT y`۲7Q0ZyyUPJСzL)ŸUtH?4_G~4ErӦeb 2:^Ąie'N.a}i6+A/#Q,.b$$ ڤ (BEd f4E袳v24,r):zԣ1F"H1JN Pz"RJDTjXzEZz\d^%`*5:f d%9%njp2EJ,GZ$I FK !0{ } D:cZ.d5-'c@J BjD*JjMIJz ժGk26bڨe*gJg^9zsuz1wJ !`93G֊] Jt:-p享ګDJojqj1zJ CJFTzp(KLذ%Fm3?Xqfw)mJjbЩ @9PԱjm$՚d({ĤeJY;PP\Ht:@ D=C bח !K=90ˬbë7-&UfJ[(i3VZӘ L`6unxAJC"RE6sJ[7ԋE&F y@E:d&wwk5~0f-P:evXXpI!$]%dzTIƎʾa>*m<_ޒپݞ{ =QQ.cN(U FzdDf,)E@ on~>yߪ_(K*YD>k) +.2d4/~ӷ WBODiHI o.]X^D#Idpik:dJY6/tO{~=y_t{/PMJNP?r TڇT|f_`_ݛO3󭩢ϒ_c )1y/|I$S!$(Ad.С*M4n|#%D "YB(1desdͅ WYV,RT$ NIJ"UZժ"Enu%ak%HizV-H)USy_:\Vpʔgyj±zȔӈ"w\tECSڴ+ԩ 5„9Ф-&D*fђ&e̙5X9Ϟ?z-ZhRNKo:Hj 7X5֍VmޏPͧ$'ڙ͋W@CV,( 3L଺φmB0PsmDa;Hj6UrӭM$OB8) h[n?2B<| J.2.ȻJJs-(>D @HLp8|ɺ03. 4HE$qPAQosQ̒q8#2<#K(ӯB)J,Ҭ"R/糏"L+@M`Cpc~mjsjg{>ݠqUȤ׺+`+ ^LF9Zؾ8WlbQ 3LX;ewc;vOxPW>曯ЄkBa~H-NT|:y͙҇NHW?xu_;>wO 葇o|QÜ@(W `c٭t]o;߽'l,TByk=Lz#DFog T qèl>H~ tN*@>|bo85pVttd dD٢e$4RЅ!cdH.8Q>0ˇ@Ĉh J]j9Rb*`Q]$|j02Łd4c5n㘖9(<Ǟdy4 s eG[dxARd%)qғ~(?X.6l ##( Zc [g+yOOSx) _R3}&Y38[9}f!QӒ+ɿuSGҵJ`:NvƝ:K<#FzӞg:trN *P+üB ISC.B"J<D#BI%n#-&I%)4Kj*M%Kҗi>by:)O=K^@D-Q(u(L~G *C 9f0tU5_MGm@m|[}1$cKө׽n_X=OGj|ltѠ40K NU&u]hDZr} AmUT4QCk'e{/_EN7"}-qܜꔹ,?X Į{MfgSY>3$FJ֚Cff_]_X$Bs$X ֊G%석K f;t ne3[HlaD%I6(_v7f_Dcx@#Io S&˒p0H !m"!JCd]═/Ef6\L׭"sq[ K$QNhn.U/sL9w$B/ ebt]e^FDM!kխFk=뼒M?I}/d=luo" [nf(k^wW惿I (^OϙOSzѓ<{U`5ON{^!p׽y?;/.;?S>ùp9 ::()4ãīŻ@ 12??CCESCcC{CU $xEX,cC[{C tA$XA㋹DH!S`ʦ $4Jʆʅ8.\K 샎ʴ\˶|KLI(XIlIQ䋻IT:F[L|Ilg4 ? qE#iV kmleWm ҤRhLuUIS5yz{WRӽX5]-^U1SdXuuXdoeVgXώڏ Yn%sAuXw R² },-8PYuE:U5X=ڄ]XamX}}XکfؕڎG֕Z԰[2[V'mW(e[x=Uَٜ[[Y[ X\u5}Ӥ֥-ܦ=ܧU\]Vƭ+8 GCD] !a4qó=ܴ Eڈ0MX˝[Zӌ VAV|t[]T5Ei]^G}(u˖\ ݕ^#SIuU%ٛaݿ5pS]!]KXm\-^:^fk=U襉-}6ݎ^  `L3\`V[._ &k1nƅVu\;a#VQ%fPU^*޽b`|/c] ZE567cȥQ;V=< !"#$%&6d@dPPd1`b{HbJdLۡGdKde( UV~X֟@\^ V۸e`f$f5|d6K&fFg~2N3Nc]6,f gEeVo }c9]T_a^˴@qiȰ0 sڐ T%xAy٧ i.=BШA%҈xhFhĨhVT֫h  &#^N_ -R ioX&,V ~&>YjhjqjjViN>d..=k#Hk\kxkck^k뢖l Q¾nJ~ꮶj&h,lQ Si ׆km~furl6nFn_+^h f$t!Dm]Qm^`Fi>NoW^>쨮hjl^>l6oKei4(5r _rorrqG1'df^ 4q5q6Om 8W:s . h@_lA')/*?qNq`tGHIsJtt߸qtu ?uT7r^un~Ďu'o\GtkEb.//kv"(vP= O@6f/ot2Bivslvf.nmo8NLrs4t7t+؍uytzaw= ~Ms|smgPx(W=VuŽFx`Hy'߯ ٤l檎ǟEKpLYEA:1"y6{W+B!Dܛ"-R(+ CAfcٔOfQSԯ" x4GѴgp0GfM{ wKJ''7Oğ"lM|ȷyâ/׿_}"}%/ڰ1H}/X}l|}}}Aϻ}N}p}l?8 ~|x4ba+$b„2aPcE pqȐF|ed*sl%2_d󦠜:9IUBZ(RJ2U)T+Rr*TZr+ذbǒ-k6ljm-!r璩knݦȪW 3$B0b M0n,q"ŋbhVG/I6s3Тiʜ&N9{(UHmWo87*n[oUҙW0v#(rGzO0sϞE~Y:&M6UfͩbE[QF6Tzn8 uI8!wr(sC8MTu֩va߁7xEt E4YVz#GGs  DhV(xW=!j%[]X\n:D蕉' q.#yҍގ A7K-Ó*(>dRFeWV^]zojᆎy]ze"vq9'(vx(՞.fic>*diD(/I"UۥXUZ{mYfWS^}1嚛 眆8+2j6zcSZl&;`Q2+U@ԂR+-KܕEp;f߂۩V{n讋K%kKIi.):0Gyp /T\= Ec[y]y1!nΊ̒3[sK7 K-Z'9T^駤:O-zFno>;{e.UU-<Ԝǣƅq.\&\O'3_L=9Ht ՝/MS_Qgů&4" xu9Ayy&wYz 9?9 B%jp}g F ůw+l!?6u*-bNE(H D&jQLoB@ƭ7[<$"//, 36&^G9Qrd]x;1{ AXRCae!)K-*2L$^_8&+; yT(I2G!nώANJ u^`)NEkhf k[ 0} 5`01F19$3'hBfy `S8;q~td Kjy'2LpLf'?9Q6'4k> m3h7Й΍m!g.͹Ktr;UL0 O %l|:Ӥ)/*¥?)bIӭF̦^# >]EۉQRR:|*J6WZuaX-)B*XPyu8Dz֏1BeQyַ5s(ר hKMԛ=-FGab&b -n >-p d=.j 3ե"TX*Ba* Q-le;Z6rnu[7@.~^r\O%s]HW}uٳ+m-|^bb-yo^ӷ}psB׸}}~o3r]XF9I0C-1fw3{5߹ ݊[,,8ƛqK-s9RC bí{}sbt(Owk[< yȞɓE#yUogBYvV{\+3b q/>ҠZ#J,MnR{ړ]Lzb6.=PzԁϩQUI4 땎cRk޶֯7lΊ~6vJlO:4%u] NmX-mo7=n=*Lu{Hl/vl-Z;8'$<^/vuP]7!yF`r+NV얿/ ZoZ<:϶f ! ,H PÇ#JH1bÊ3jȱ#C CI"ŋB.8sfB6%DĜqBC25(ӧPSǠm*}H$ɘUylV#J]˶mҦ8&U՗qY½AqQ^rNLx`ݠpK>adʖ\!$k'PO~݆ Ey]aU 6[Q&8^)`dsY]ɥ{htrex矤'%I衐 袌VD*)=cdi`85҄*3:ϩ*X*:+ ͮR+Ʊ 첍6=Vzn駠jzꪫN`j믾 ;l`RIKms-é z>#*p.鎱.++k/$lrFl /, !4\8G,Tl-tDJ+.+'7L|p5ی-6=WA]hKt+uW] ouׄ| ah]to wݐG~Dt95Ax<tQۏK.sR/7g耐.nG+n4ڍNCgs;:M"|kѧ~TS[gwPgcҼ|jT ϫOLl;Ȏe f;Ў6j[6`z"e+>va9цlkgex]mq9FRHy~7(-oR/~7N ?`LV8pw:x=}zgqιE\#/ɡÄ+1o߹%%kAХRt[ woÓ>7[No>WW: `_9:@ILy@[:F"/lUlns;|󠏻'ۺgޝ:wX| x+$~ov!yգ(|a}IZӟ^x<%^q{/6!<3|ѡGugO{'p}GlpX~p~~~?1wz3v~gx\WhH_ {hz?~CCχ їw \8U&(x*ׂ/yW'iCa҇DxFhIKȄOX,W'>TXЗzWz֧yHx_8]cXexL/Ȇ?oVXZȅ>t{\}~H]hxzrxh6~9~ c5gmM4~pexƅ Oxr !|2Xm(z,kH,m<qHGhLjЊ)vшӘ~~؋X¨p+x~&9拶20s[G I9 *~mp7rɢ,ݨz+)Y || I) x 9)qy* Ǒ 㑊,#9n' ) g-钰e4Y7 9<)C moIp!)Kɔ%9yOS9eU)/p23Yy5I]cIx xƷhYjyl"Д6RTYy p{90\I ~iDI/FHJIl Y4voy-9['Yٚ ж?֖9qytu;ٛiy Y,S9~x'kfDnP,7p֖]ٝz:pP Iа8YZmi(r IsRJ J䈊UZYx"醢Ԣ+1 3Zjԅz;B)AVn)'ٝM- RU*W~7\_ "DJ9i٦O 0*j5jY:ڥ{ڡI/BZj"7kC9cnGHlm5y秀Ъ*^9HpJ 9B3eyȪs̨?j' #kCb0yZ~ᕫ90)ǚr͚ϪRcEٺҭ JGʮ૨"yɪ謴D%կ([)P9phbZ*J{ R(ms,GǪ k8LȳzFk!#%˯M l -;MW Y; + F_?k,KkGIKRo 32yaIT x  DńCkh+Ѻ4#h*wjfۦչ+Ce+;[Ȋ@m˴Kp=׻,MWƻ;;ۼ;[{;kQu;7KgM$;; '@+j';GT+xۼ@+BKD K+`BZ27WVPZ}{^yP )JZ` ;l[*L-,Lq.c042Kl5,7ȅ;>L@,GĹjĬ۱+j˿PL9t;s/ڂ?\ou;Z PXs~3:Pɖ<~,C%zf]*ݠd⍀**}9 >@MBsT~'H6unN1Pޫ,Mx$oW>yYe[N*].zb.qdfnhnje檲ݔs.cHLr./!>>1T:2XXply=^0^6D97֚B6_XI+Ye^pz^`wIc 7ȸ.pn݁(s2J^s^kqⴒ_V $_)Xi,/#6Le2#IXﰠ3B@n&6JlN/>Bu <>ӊ7U|`Fi`#_$p-ڕ`1߯YV78=y BD߼r3pKNP4R_TV_1X|Z]\\^_/Fc8lp?er/+w?y?X{o (_ >ovC:a s9)G6)+jІ)s4g:C@IlIH'0Ƀ5tzUP T0uAЅz/щJaUFyQSGRqJWe/mcLg*:ԦĩB6d;jTyBJ$Sp2RUtU-$A]ꐡ$+Zok-ķbsfT8,j1"`$Z'603ccaSG\l4OT,R7T 6iKkªek_{زq(,qf;1PѸD1<67nQVӺa%Xv~j45y׉ޡ<R tTBvUms^ (R +#ڍbNs]vZ"qZa^x_]v:t sqgHm"S1cx1&xe\@x屏+YJ 97p6i<$_uɹ]8g)ʲ+ml[2˺EQƕ(ͱy Jg(w3 0zVlYb i u:l5cLC.`Y>ڪJc(tl򕳼evYIlu];ErGfsN:6f6ROWAj[?XYW8.o@{)NPdY\f6ڟxń~Do{ywW;KdF.<d%1@UoV[Փ-u/}̿L-D<&}novڥ͎Ҿ_[K6qr0$ BЊkh@l +'C:T@ȡ VA 8"!@ȓŹ?x- 0HZ? Q1s-8HBJ$l9:rؿ鿠F@K4A@¡`@l@, t  4 @!p A@ D+#J[ic A<5\dB7!Bd3B$T% 'LB( )B+,$-™8@$É03L?l9@, A=)iF׸FƼyD8c D/(ƒ:BsDt́O|B#vwSLEԳ8q_%1H 8<^CU ],F=D>T?dFg h<i+tIɮ ,2ɷX0ˎJdSrsITǚ:"ŜǁɲIʡ1KS£쐤,:7_3j/pHcJJdFH8%H  yLJAItKɤ˻:KKDǙ\Ƹ”` b@Zh6t̡̺L(H8 Ek̨̩ <8H m$ ph$lD<ܔtl߄KN#.ךIK'tN؛ID\a lJLt0 D&kHbLH4d:dTaML0]PmdKpl #IP؉G|D -0Rlx}ЪҰjt RT(ED<$ŭ%mRk@|AҔ(K9˴|>\$I0415253 U5mS|S8]9 @;MYQmL}>TTB=OCO LU#QTԪHT$4HۼAQePRuPSPTP#2}U'\S;hzWYZ][UuU7US=%@tZV|1GVHUTd:X RҭdlLM֍4KEMXcU0QrhGEE@iMjԎԏԗʃ˱RDDYkTُdِt٭STYWYzYUZ Z 'nڃ{ȀIgɝ\"$NS\L3@6a?T R@M`+r߀J":u]Ψ,d)kc2\``I-x^E iAiA\(qB\'\:c\#q\c9\ʅ&}_Uέϭ]9TDݏP3`]D@I؍ŭ]]݌V)#%LtUeu^}^^i^>p_( _=4_A_ɭ m#{F܎ҽîee`n" ܵ'^sͦ ~N .m&^ 8aT-͈j܉z\6DDN b]]#%n}]])F`*V`+f`,vb0`15NNa$9ƣ:F;&<=6)BaŰaJ.CNdD^F!d~FJ$Md΀NF6eNf-0ή1&2>Rehehe^|ee=:*?>@VA&dFfvfhh_@(k>5]%f'(f).*+,.nw`"c7`wD,g?g?gh9;Hi黮iziWvߙ`\.9N^&"fȎɖF 3llllG6Pjfx"돶dfb`^vfiRmVm{&.n?nN f͆LnnK߬nfdh.oQmi9fNk(v*TFe UVo/ p O/>pRpfvppbpl^^Fljm1>OXmn޸oooqi=q 6p#G_7r r &Isi3_]_q~`vvۮk m<p=p>'n?]#.^yL1U@6٨:Q-`4aM~uDb4=q+Qclj4R9^c8K&83i9={N衣 饫YzVG: n Cl5キuuDaG3vgl4HvMo+uȷ߀ ^rLJgW~̛ϓ矇:~:ꬨS\n ;p2)Z׆gM%C8< DozO:ucoH;g#|8AKs.ѯ~&4u3@Y7 [(E<kay ^o3!PBPr>xyj0C~"!7"I,e'mtd'qPxܢ\¼h&msBFh΍0, Gwa㷹я09DBLdhy;(> K΃ˠ< =P$7Sn[ U6qc c[F/{_ìPg$"s T3;hzmū 0ENf^7\zPyN1 f}x)LէQ3z%~S6&26*cd T1tb53Mem 7WQpԡ% IRv+-mLc*ST6%>s˝ sM*T|Qȥ2YM]4!zkz`VcW7cU*KVr5Tm=[חΕ8g.wӰsI?S 65lQX6}zld-ѨN61Yj֫`-!Hjֳ:mjW~,ҫ^ }o>z[#@o[G=:(3+e C;YMbY=Ώ~ՍJe XC ڑSR+^0޷-oRbx/|tMo~n:!^rB҅pW0"hKRv7*mkxWkx,_ 6wmxcV=d&~nJV T˦JU;5Gx"- oҖM+d!H5,t.*z85}U/fˤ[!Fֶ^s:Dȡ׾6A aX;Mxݦ?S;9ejzl.a} 8Ғ>7-]iLG@Ӝ>! jnåv۩YծVrY7>ָy^ &m;e?z(ƋSZ#߁,oQIrntoUN+iwt:(X]ֿݛ7roX_pz; gx.bK<˶V3qs})ZoaNOV>jE+v7msyYLj!ʫ \F7R%Nqh]w]QqmTb}rritշ}"o37}Caw~z_C|iW |3b2T~jP4Jv|h+P^zsG:W}z̼Iw|}k}x ;OI @_I!E% =`Oh:牜&_ʽE ]͟՟:aA`"v(6*GBr*]OB_`^^)׼E蝾^С \ XAVau~_魀E C Km_!^&G!!U\ n !JE2b֝_^≕W !A!ZΝ !qG*"~^葅=!bTb.O#>"/`0R0`1 9'\ul26cd4@5"5bc6r#yc¢8""V:N;` 0!; R>W#@@A[B":i$C1VE:Z,%S:=E*`F%QdSZm:M~[T]9 O%eDJQ$e1eSeDeUZeCVVNHW.WfCXrXQYnYMZZ[YU]&%]e#e7[`Fa.eflS~dGU^eݦv*~jj'* jOji^krjh~'"eFmͮZRj6£+*1+}Ž2EkNh: l[h+p+z&踖:kBJjd*z*^&I+*,RO**+"#Zlb,lhlg"kvlX~,Xbk6N>,EB80ߙBmM鄔|EqRvfmEF-RrM^mֶv1~m~XmN6mI\nMb֪֞ᶊBⲂN FLPTF.t:|enƪadʖ\!$k'PO~݆ Ey]aU 6[Q&8^)`dsY]ɥ{htrex矤'%I衐 袌V$D¤F`6dvzçi:ǩz꥗Je*+ 믽~!찻@&, )Vjn)`ܖjӆ+EnKZF;ifz-rۭu:Ӓ몹N{G,1HJj/zK ̪R4,,00ǬP^ t]Hd7:QO-u:Y.e`^cv.|i6oÝߍ6W^Sqoq3nv|?l꬛(.;ΝkyOK7+NH.Qg\;>-o9N6Gިsd/ZoOkְje ٫ ̠Ə~׿ ܠ W0a M(=Y a8 o5ġ>1D":1bX?X.VD&"gE1'ר4>ʌ? Fw#(@:2qx $hF>ˏj iCBѐBdJ>1uճ&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0IbL2f:Ќ4IjZ̦6nz 8IrL:v~ ,@Ђ(.G85(BZTd шahE1ICJWzt0fJӚԣ@iEOzR2&MJURZHMQmT@EOU-UV-T4SdUU6TbuJZUrԡa dT@׺Uȫ^њԵգO}+ ǹ֕w^zT,ah ᲘUl^XǂೠhG+ɮulf5Y 8m`jtUe5K־6-GkDyn[z}(qh\&woSu*n2vC ^ ]|!@VU~ 7[g0FdhC+h .e|K_wT80MbʂP%_ Wp5 wG(fTP.2Vc,pP,` XʑqlQC.Lc$P~ +c}Z1GCcq<4y"18s 3=14VМf@:*4c9?U4yYt3&7hO{`pRKiӟnҨdz|S΃|0G}@};N6j_{Ot;Eww~Ϻ?Vz;g|>SG(桪̓z=i ?Yz =m{; ZtZ(0y_{*c}~9q9-ʨTP)[kVI*F@ũV9j,Z*uʪ ():j2pȫVk bF'f9Z,:4ZͪT|q"wںJzګX _UӮc{ʬ=$rɟiP&ٺ%0, (RZI {E [`+RJ6*Kq).@&[(;*˲H*ZfZ?z%\X*3~?kK0Ʊմ%{"o0J˴MZaH~ T;JH3[˵a*w|7f{iKZ|i"th'P˨{_}m5T]{_]34r+{gkv0빖oPȹxzк+H;Kp7˸+1!'Rۻ y;Wuԫ֋뉜8xdߛn[$S[Zv*+hǛ#-KcH%;5e,ۺ fQH콉k;' ,R!7|h+!,*#a%nº0.|T~{0`2 Ni&8ZPF75=2ºFiOTjPvx*aJ_k%0z*T_ikn57W0PɊp~ZՍA 2cDeOj L͐g͝ VIZuRK"v:7~Zρ|- CДlɘ ަfH0ߪZapW&=^ ƍҩs 70HW!pޒN$`Ό^!X-iױle մ.0~}lN5N_ c -Ӓ& >GI~GsY;DH~67PF' C&^(Ggil4n8:.xnB{~@.pBND.G^~B Uz˘[W]6_.Edfv]j^}.wynn^=踾>iLUyzt=5%wtvLM{ߥZh@, H<"sQ(ynH.ժVlYoj-]Fb4,uu cr Ւ7 o:yTv,)tP7s 8>hLdnt.5ntY_  A89>PT8*Y!#_|ےsdF,OW.K857O;+q(F@FosHԫn0G0\ p[]_Oacf?hj~/i vy&(*E?fYb6GA:hG{otp<W2siďu,&Kb $XР a0MLx5#!+h iƤ?RTJP1e~YM!9HRI3w\!zKI-Uc^8U@l#War ۰!iCӖgD q]5R.X:yrx_&\aĉb`اO%G4Is珔}!3.zk;̦Xqu[x$dՉ/Z!^G# %D=,@<]5f@N;ʹnđ2LTT$8!G%#{):쉹(R,+R!c.4i0>DM6)sSOpUO tзHԐCI QH!D']5.N;_i3rf]$m™q Zh iECdiy[/5DfZ u cNXז&f;;*>YKG1 !@ p3!^8^ :]G>zUZGStPC8$=S`*3RI%)A5Yʙ7$e4*Z-ev$q"d@ B3IyG%&I[.in2)'CI <Rm -m\꒗{)`S3ƼSA fNaЌ4!`hә|7ON;E9ɓRӝ0#o$D,$Pg>CO34$Kj"Ƞ%HI>(UD)jQ22إzQ4)&Cie6ә,Ka TlKZ{ p=Kyv}%.P<:-W/+mnA*4WKMg[l9T=E7=1 \UK u/ :u{˾Qv}j'~n{;%`w2]_{ eO;IjS#>7}v~&}'ӌ6O}BU|U =gʶi|=Kþ*'No.e>P+y;zKH<#:"@ @f7{#T ,5I !&`9Zh"s A ?(Qd1h.pX\x%ْͰ;ޣ@ $\ 95(8C4Z7|@@N{ C20 ܵ(A䣜K:ԡ$AAAUBBQ30B@BYBhB'(|)T*RB$ C4LC5dCzC8=C:C< >?@A1D#x[hrAĎDALMNO Q RTB&ĥTTFBPC2x#BE.;/ŋŌE _FY"F74c:hF$xF FFEk,D2Aƌ*opqrtAt$Lw"QTRGULVB ZEG0bH{s_HiCH=AF H$@$I+Ɠ<ĔLDs&ƗII17I+ɣLyz4 SDE',ʣ9łE L̤*(xH ƬƭʌC=Ʋój\ψ܌sDS*zaGGAȠKLIUK)By$@zT dĥ ǬZJXd-LL9J`B%@MQ PƉ`fg3#DDM<wM<Kca(c/q^腮xaƊkad^Id6K>L¸動P'&RN1UF -.F9_Ee}(hez;cAc !<=>gVhnaiBCDlmna!B!."#>gu[mgxy>zgXe ehhh6FhRhvhhE芾6䎦fkilmN nVoagr#t^bfQwa jхTוs }6jBp]VHufc F(4AX~i8jdA BEh8XHcdqbL.bMwV9^. ill/Vnflplm*lcplh~ihh5@m P`픖dnKmrmsme]F mo>ᲶY>^flV^&.f~moghhXfov Vkj r~inl9Vcp&0HMpzZpH1hc ?G7斆m&Hmlq^ͮomm ny>HlNX^'~džrmlc17g^΁^^hs%x֘sq=.?tt$Rk,ADE?Fgp@&ŞtrHr,lNl5O02F4gv.Y' Z[ؾsvaGe!wj")FtH6eBx.gng,R TCي}I3y·*Y<2(FKx5ryC`'@#4pX6>stUk Pxx5xΓx9-#y5Cyw aӍ'ס T#C zUz_xhxy'g x yyo-y8yP<{?]iZ{5{{cSGX|h^~zŏzƟzǯzo[7'” X<]鍇ygy-{7?p!ERyXUsXmUXԍvs|踣x%yA8$EX|,͇_e  ZE` .臃Vx![o ETQD("Fa~t1h]7cd^| y$JzeDW߉dP XVivӗ` a^e9LTaI$ Ux^'L'cu4b'hz *ZV8:)jKXR,iQOr%UijY^.'VșeH놺uo QTUgPAxɒ mHWj~;k#AS[efIځ)Yf/*Ē/5Qpp ǴpÀ.K{s<5Fz|)ȚRQ,o2_+s,kfk|>h H#YJ/qU887H0TpX~ș d^^^v.U 0:~:\E[OJ>{7sFYpd|uv+\s-.5H?=ų:uԨ9[3˽ָ7>A#u9Pr炅?~R uKdW*.wZ8^Ex| k;%y&hQQZ 4@Z=y/|d>O} 8݀r2B/0 FBgu9&.㝋(G;Xd 8Az$^Ba~s!Ƨ |5\>>< h!9"(%J@N4Vlq -v'8x1:o>KRP&yb@L&f>T%qWk.&0BY9hN50\; Gx D=)SѠ&лkBLA D&& }b GjGsyUQ%LY%(=b*yL ut5 oC)\u֛,;KOzD꠯~`KTGS*SIFUXfIZ5 Y)=k^ ѮAmmj\S R0󊲽ѯ ,`fb|ibM>Y ed˞ūdO:Ҳ|%tژCb.]{ןTxc۾6%,o[ PmThn"WPMs+ү *bՎvj֚/",ջnxK#^d>ae:h/tkԮķ/cXe_$Or*n`/(p b) ~rz2 'kE(V1G ɤfvR vqv`BFpl]+yvD,$)84dd2,Q"ٙQbpGe@6bBRjP$Z=)@. U-(F{ 55Sժ Kbь~vSa Pڲ69m 8t 0 %9X:0F@fIZҤVmT4_`Yz> ]z6ĉMcRP6mg?[0MMn<1#s;n7`xZPr+|Sx.-uSp^?](c܅ǝq{"yN#ZѪ R9ni>o<穫)o ="Dѝt/ךkdSzhS>b|`n,V궻8=NCԨ{>zE7_C *ܹ"^}բ ޢh^!/B/ 0 #2*2&f"4Fc'n'R#Ff#Drb+"+8bNЩ!";* c0PkMrk,V ,s2ÆVx(,PH XorX/ "ee.Q,nf˺}zPUQ,lJ&J"ϚT-a 16bFm ,sXrrm~mmlm:m!چf6nĭhN-nmޮ*" CD@ H.Z-mQPD~@v`RҮ...,a/{(&L:/2nrUh":N2UR*/ʊWD6D/-,p/0/p Qo&J2I/Ѧi/-s&gKK # 0 {o0 /q 0FDՀcS?3313DQp01J&n 2!# 2"'_"7"3#G C$WS%gb&wr'ۂ(r)r*򐲲+|!,4*H9YpÇV"JEhQ# YIH(S\ɲ˗0cʜI͛8sɳg@2lhюP0fDdȑ"MLҧիXjʵWA c*J/2m ԨR+Qݻx2,бd(&brlǐBK˘/ X`'VE!g^ͺ67waĤK;=1 +{,ڄo&R璆KNzlk?~6h‹oO߬}qiOj̾/X|J6}qw~ 6 VF֧vLj7!ih߇,K PŌ44nϽ@޴f8θYH&dKCUdG.)@6AQRZ~q)y Wҙ_)眭 t'_v垀U)衈D@&1-*PVj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv+k覫k!f7ȼ讬bwo#50j@-o qZŮi "B1*q< <0WT/4לʡ2<"3E5LΠnHr/K4LO"L73ԿH=5U[Ҧd=\{51۳- t=x#}7Pv1br]7x罷~uhx܋3$Cy-rbtn砇>:铛^9曻{^zͧ㮦+WD/=T\Ӽd&<9C/}W=b&}T/磿gߓg/}WW8ЈW;K :Lx#HAqx9ÿ v GHV W @DA"^H4&d#AiƋ*2I_ю-H2i /Þ AhD$qLtGh)VdI p!}\Hь? 7hb((vdP"/(0s SBH;9cFG^#;/h0Ƹ̉*FF$,L2+'`S8KiAd&&"+Fhʒ$IhK=bQ;b7Mp3ΙD ,aj `(6}>'@YȁTUc⊨PHuԈD$2B(Pºj(HEZ⏌'I:U*7z/m 5gBk: O7S5H¤dl)ꔩRժK*vj?Jͬ:A+BպDĭ3\Ӻr_7*Rk *`WJD*ѰA,MEbrr#*aR\2ےⶬGKqJ/-bwM."EtZ}CF/KTTSmb[vu`o۴-nr:ϝ.u[]moEw/m)/+΋ލpKҠWC};Wo?xb(p;^38Xo1ګ ΰ7|_8"o]z"]L^vq-L_aTo|u:{9?q "ȾHrږdGr+aSD<مU}a2{b#9 J8H _!) Ns wMH5,[.z q4% -iVFy-{Ϡ~ż>zg jIհni$Lֹvo+\=9q~[.ƲgCvn}Jg۾q/܊~un|3M7TЬlYS!6"^DJv#s闏±#0; T7)bv  4 i i"JA#7DN%#/bZ-"Òl$Q2>9I7;_= C?-3EyJ98TBљ9Y]М \S>v<N9yה-ӛEkQ1ș:iyN 33i0}PY3WY\%$›Ft)?+-#":$*hx#fw щ|K4pש#ɔP)RITY VX  jrɄ!ZXJT. )ږ+wn0z2ڣ46D8*:*<}DjFI:KM65W$.ڥ9 7yb1ʟfQij k2j))yJOH*}d%Z\_ɡa顿$iD"ɨ ,.:3j@P2S>zvڠEzE9ִ" #-ZJ :7ڬlnکp*yṧ}ԭ@᳡TV{iʮz*5ڦ| ([Jz1)#E"k >˶ldc8p6#$sV۪E&7uؖօD #>;<#!\Y@H`u<#J[5cSꀳ-w}$GO³? [C[gۅ'qMO#3*U{DWӵl+2bd[|pŘꥠm5f~p[RZ'9+>[4DQ˸WL{#7S{ +ZU\a?E acKUh{Dj Ǽ Zwyk;KKTǻQ+Iz{Y΋^0 TSۋ_{;S嫺Ǻ1K۾$g䗻Qջ8H࿈[ܸ"Pϻ Sgw#uNv嵓PN!\}$A !<\#쾫t;|X}Cķ˳4l8l[˾lC\ėKՅDIHGX˄˿5lk̯KxL3 X&];}>EiN0F~t?)~LD%'.pj.j0 2|Uyxq;< hB~[Q U泩^-7LV>X^$^$]arc.eDgi^kY+.?-;u.wο68C:@?d ;M㶞$@.vA.x~ǾɾD.W~ 횮i\S2FK^Iљ Tx>z.o4D}8_>]OΗZP1.OخIO0_t]4jqd1>._0U2? k 7a!9bt(ZoqDRNkS?#JKy"2 FM|ځt4ON}L^9-oq:/o$  D0VlP/ B0CB7F$D8Q\Ȋhdx쑤r|.$SI'Nj)tŃ1%5-:4/7D9Pp;s=S PnA[p^5r?Qwp]dX-.P|eW|h3SZ2QR(rxk +P8Q9&*cO[Ijc 8Ly6eEzZ^y'8jn qgse:ֺwE:^z|/_ X.`R8!r8RSZX?ydJ>9[Fdf+y19Q.:;sjnVܼȽ\k;I_~mLxaFa"޻o&WWp׊JLNYe s\o%9Dw>g(G7Sgkuu{d7r.k\Zv׻Mx$)L6'n[Nq7ZCzJ8i{ šqqY׾a.~. ?0 e9PāϩA~j< -nͳ=MO &xqc!j5tN(33A9-G$dDqkjD5|uK_1E*BXoE#G}LʓxK@ #VG.{y$ ¬O|+]Wrr.0921@(YaO<"'IPf!FS :TE,VKĂ+K\kn0S4Sݢ Bfnϙ݃f'Hj|4,6 >mR26<$N 3AbMOsD&>ӊ̢@{n*E:8-D'YXE5FK{*S# @Ve,KⰦ:yAL=)P)#TSuDjRD{:lPT'Zu^PxwIU`jyT!]i:Q䫹)DbH'Fhc!K⨕U*S=Y~qֳj`DKЬ4\Mj6L8-]G:[̶#\{:zYΕ5)Jk7i/w9R٦[]^W .R٦)g* ڪ-ԻUu/l|-~+F87Cܳ+݂zpIQ":.{zI ©tcE5,>$?KЪ-VOx15sLiEэGL?) ^y4qeFAyZ91N s2i>id:am1iB6Zey6`|+ hN)Ml◃Qҝ]dPp6H[ nxP/n餞iXݿ؏fjm0\*& ~pN$\cb'3~\n̮k9&vl-{ϳ JVnmM1j.ܝ6}݆7O1otw:,+tqR\&,(s|Gȉؑ|?y#r<=,Y7vu=o(ɆkͷtC]&)}o[r]=f?OiHu;Wk#VI?!rԦx/_9}!n˫ѽ_|X ]sPV7Wֿe='ho3; ݋\( _ӷ0 c # ; Q> c#ѳ9+ ?1+;H0W@2C3A/Ӌ[o;{Cr[AJ0:3?<CC {GY} G GAr4;,H`<Ɯ\@ D e2sTHHHHliFtCGD|˕<&B ,xcE9ɘ6~âtʲȤ`lʄ3}CFAtT̢+AJH8H0KLI8IIh˷t˸̭uvlHlGrB OiC( !R.րeB ,&Yd6Qzkĸe S= [DxRQ_ń=>&9ݖx8Fm4"#-$%xR_҃RRR}./0ӗ(S84m5#78 H5q;=Gw TTB]"CE+D7EF=Gu. =LINO U^R} S/T5UUVWXR/] 0[u\uqY>6`]aSZӮC<>eVCpVHheiD]TcSnoԸY%׃0׏@R%%&mWgx׊y{|}e~ جU0X_]Xq;XXX6X6XlG١}͓,Yp MԌ UٴY ׮}|ٟS V5آeBX`Qب\8\49Q%TeQkXm[%J ۓ[~ԳM[uQ4()*Uۜ[Z׽վտ55EUnaZ^}jmk}T ] q-["5ҖOMR 'GHs@ꔹx4 -[E! mk!ںx!ܴÝu]5^DZZm^W]5 -5)_VɔU }_8X_E?bB`k$`F3`5l^@f\>a-WE &_4_ {-X Z6]7& a9ci_(VK`2vdHIa1`lsfflj_lgmnnaBApgr ס G@D^ߘ/ Ji0) S2c'1+<i#&q+:fi)1(d@ю6 %FS^%8F&mxiˊ6ឦ&<1Aj[f:jnjƎ'NfSNmZ+)& FPIkJx~6&6vvF#pf1β zUiݚ쭥ѰDNNmIi"(00^~>ڦ\>?vchjɖFnvV. ͦN>+sfn힅 &! >N>a. Juߙ >ѮiJ3KU>xp7avmkn 6oao qo5wfm_Q^SJbpqA nyq͹.q{(-&[Fr WB/]8tD'' d7*?vIчfo[4h SJj~5u=o uq>m|stڗvw`_yP9eKwrjN?O%g 5GxU pGogpxR??AVyfgyvyMtyy'pUpWp&󣟈Ww9;z7y1zIzzVP`Vxks2e|*|Ͽ#rpgVTejEZk[lpvKuIo8ēO@ \Gi#MM+Iu؝w#5P֍6`#7M:$urxh DabNb茆un4pg"rw)bs/>U4fe#dVAB$FꅤB9)6&eUg!xi 1إjYj>-ʪJM"G'*nŢ0'v uUߢD:WH!)z$TjfeA\VꪪrX+ۈÕh܉vx.)K:,Z.t#RHa mK{|_˩f*Cn+o{ -{+o*翿F,y 3C Q\[31YGh[[ 9[+G[=֝kr_cl6iocwͶs< p8R]‡/:J?>9[>g`[1X{:p-;p;'~~B)v_{'Arѣ$q~#x` ّ!H*Dx R ` $B &J,R+UEd6ۦS,!c =8"hh<)RIr$H ?ɠyz0#, ObhaL^ee ?P;=H!6zAb%51U|$$;vLDpꢎ006d,rΈ4"*dr\h2cЂu =+ HHCGT"hNFƴbGH,Zn|&1ь$e)x72le1d;Jd8*vx#\BCC%^ LN<&>9IVrL}F'R%i6R348)eJ2jA-yEI<)JOfә7ƂR3M()F8:BΠ#sM G3HB#%IS*թ&f܊X/&lˆ[%H~4BJT5hQRj˧'y*`b)YY+ΒaDd#+VլgMZcֶ:Ly1WGGD}YS}G˒t1,-fLg:)t{BZ/s MgA}yD YӺۑ5k^;uNЂΤFԾblÅI\'%˜IsLv<$ y͹H>.rc w!} I' {P8iTs㫾䉾}]i^NYC }IyitpyW( -4n^H[l_uqF g!`>FDƔ _֠TQvo`((( 6  y`٠nR rֳ\-$,a >DLRZ^ .ԡڝZ J ! Gē^FB$b^% E!&("#֡}$BU% v!"zbS(b))+0S"$"(."*b0^#U1VyՁ2c)8#4I4#6#Q7[827#9:D09R:b5;c^Zb??#Afd!6,*H .XÇ3"JHň 2j C~@d(\ɲ˗0cʜI͛8sC Ũ1cG",ٳӧPJJU?jѣ#*]JٳhӪ]vj\J ݻI2l˷߿ FXÈ.̸ǐ#,H̹;m̠S^͚װMVܺ۸ލ@]Թ N%ؾ1.μ_'W#G=xËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6$D)Q`OfY\ p%Z ST~ihDf )epYUieg򙦟*z(\D*)ɥ馏BiV:䥟ni'| ȥ¬.a/䪫:++l5+z ,4{l.l/J;-2Z++.mς+ KȢmkhnɦ/(%2 G +nG <1Z oq#3P',,{B@maU%l=&}It:?ͳS=Wcͭ[{Hla`t=x|Gd1T}lݐ[`߁DRd8+nk;vvS^cy/}ᢗ>5u>9cpLߎg޻@OV.{ԼkB?S_ =|IJ^t{@b}^$0~S?s h@!Pc9NI(LPu.M;+r`7?VK\J yB:1G ڐ9|OBD Q孤8ILX 0{H%g(-v"yIh0ooCJq" _qYFq5*d6& 5RL%Vʓᕰ,h.}ᖸV ^+]ֺiKJ&*d:PRb4e)SJVҕ|,@;Q V/}eWc1 d*Lt&(G/i51Mm҇%8q0Eı32'+ O>Ilfs$@YP"VxHJO":2'Zыfth,馕4ʃPJTK+XKQN2Fs1ɧ]gEס;`uBIII*Mߊno d'k*ruJ>k_ד Ea(֭΄kUR1wU 2+rl Öv0'cO0ҵ"2ۘiֶEl`ufm  /q1FMTVEnF/m ]J7a mF+eL=^_{ϫ^tU|[nE[ҎK]@8 (ĠV 0fLvJiܦe l\@"nSdHbg%>qb911glG+Dzqy ߩyF62d&/u2L2Y?X~[$,raKCل$&m'ڏӬ63q{ H .d{[]H,hgv8ۇ`o(?:+`nS?^5bfm֣A y'*rW3 S\7ƧX]&zpt+MwzŽr: 辺;\7ן=h޸_΂'w?=Nc; H8>}'\;4`.u w@w4^$w~xsӮ {Wv[=u (ߓBojį ׹^( RodH0W34i XS1SxQ9gK63,~~h~\.16W&$%G#8h9 Ȁ#j@qzxF<. c xG"Hal%+-_/v#3X|s8Ã?@8V-Uq!sBWH_YxA5}h1AFghhMjaC_f0 Q`e!h*%x@ThzH{&eH348U1gPЋQlhgx` ‰N(gF8؅hX3苽8MeƘ*s؉},h~%̘k$LD(t2hKX^"GȈ 8H`x87%ҏTh}oHtx Z 1$p@dɎ9+8vr#hW%)+Iqu#Xy5iሓ_fؓAyCdGy3 Mz)#(Hq&y7,)a.t.r4YAiٖjXtԔq~"yɔt$Ewbga7_v9 P y NPЙ?SdqbsYH >zٚN)D \ dY7 Yb9Iɜ. )Ii&wd*roC:L2Q.[ jP": /Yqc.j2}Y;@/LAƌڇrDZJQ+ G P":Z:-g~ѡR:E[p 4+?-.*2Jj:d<:Q>Zy@7BZFgHj1-OQFS,bnY?jn],_l!͂f:NhforJ1cأ:Jkpwj()ZJZ ~\O*d:Tɪ/}ꜴjZ`zH:p fh.1۬Qx&+ʭ$JLԕ6w窖dd}C0$ 1ȄLVeJGjzʚ+;ZjزNɰ: h:ڦ/yqMرKG%+'K/)I+[T>b/E1+mbz${1 B7Z^xW",*5`UQ"z{/qˢA31NtP/TdLĊ*tX;]`u번JG%qsq$.3l۶n+p{ĊtJvkx;.ҷ<%O Si M׸D6XLwC\ dc+Dqぺxʺl7KVtp24s2;;͋@'#D)eҵ*LE+vJ$a߁k{* ^b@Hyۿ!m, ^jY7$#x@XkD J*@aܲ1Ӻ+ +E 87R~[T;Jò{qC ^ElΉĂPæ)[;YT]`,b<2d, s Ð,wk5oqLTs JuPxpu\{ͬ̀ξ2F< ̮0ϗ[1sҙKrϤ#ɤLM7- ] w'-ёIb-X c(4"_UT&* A Ӗ#c@`7}9-A;=B E%1Fϟ3v 4rMD0`CЈ гQ2=Q(Gd֎}R2Q\Cugׂ~7-؄]؇]>=J@dDmXvٙ==p}s[w]ֲ}ִm۸}mr--Tmx)7.|ͫιhFԠ| JE=ٗ݋KMOQ ]X}Z\^`Йѱ}h~#c5 tvi}-87CTj*~q&@ۍ@=lI+-nB2/p-9N֐id㶂?AYFqEnMOQnL h >X>d"ὢIΐؓh朋avp.oIuJxG]՛>^L<ڂߑۓ&o6^aD)EG[p/$!\."Zg'c>Q~nLD%0F!>^DGy>{}.~}©ag距{fneK>ܛnMMt%!+GnIDi^#{殃#%.'nN|m^a=0`֌,.*T//n. ӥاK<,1Lhd`1&&YE/046n8:N#q]{S~=g= Om3ٜgӿLc)?0pʊ*)2p Cl.2!4 /&3Z8 saXc@.#H$(pͥraJ*J, FCNjFKº.O;I1k=>+? Z*M(0[A4 +p KpCPE4D++ŹVl1QW1O@4$Tl)JRKfmK/ 1ɌL4Pڬ< OλNSO>3@O?ElQ"J4S 7SO? #+SRѾU[%Xgű%n#W" %c =cS@etvfV/4k2۰̈́|h&@9=hww]P{ŷ#U(B&`f3<1!&bĸFhcWdɖ_#|KSgxy+,%orWNTi?kC0_m)-ҳ0vQ8U]ƽe{o |pw>Wv2,(˯˧չͱ:MnKO]Ror[u PWWv;1j_`6Qm+H2?"JSj R[eyʄNemˬu_T/a"T@27Ƒ sD 9Gk^ӏ+d M39͹Bs1lgIxS@a'&UyDY %-bR5CQJt%~LaJnj G= R70)5Q\ +m^MCj")Bi€/d;) uOle-ֵ$Ud()?SFu誟ZvL)myl%f^{VlEcI(6ڇl3[:)#_ͿmFREF],KxFVe9yσ#ERCT<5(s7V\e(-_ ҵEǘ֘E&p)\fM]=671n7(Z؅vG /kC. 49IdEAX`+ZR B%pd$K]mԚBIH|r!$ɽ 99 cC.G$H"Dsh/=&2\Lc2fU_B`86Wsmp!AғT%$# X P_&pK.Bqx bX9汏d!g`5D&Ryi94drQI˖^ILf3#]\,7s@ sg {@gZ6.h0wόqcq-'듯.ei]%1*_a>2:繝|tu1(Ng3:[sk=&P`7w!=˽z{= >;J3>[S壌+{c5s5!YӾ>]>^?C96"636C6X{ :?<ӶC¢;K`&:%: k14ܽ @ < #@|c8s8+A;AJ,個H-O# 1(DC>?L !ʞ?D0Kek6:0؛ 9c?=($=o3=pC7+B-BBC1<ݳ7@8C D!c<ƅ[87D ZJh$5R;CD=ԩ>|?@D93Dr,+kF4,D?DaL?t@ D+2@J4DIZMqJG|GJ,J{D}>~KP K,HT3U4*dKYHZKx˺E_dC L$L>0L$lɚnj,'LL#LO 4P|֌z{|J.PԼ(El#{²M)Ll:tKKxsXOi猎4DNglLiNL=LmLnLoF,JL|4M &G֜GJJG<EEHTHd E@ȘcE2=$-m}dLt̛|+)39sc$Üəԏ@4_EXTFeʊ/)OɊcDqKK?pָGR" E* tD☯p8y\X ֘ X*hE,hVgJZK13*Vn fSSTcTg$Th4T؍D@emGH[K5F%MN]O] Pu Q%Ճ0@հPՖ`)qֳĨUZ}\]^-չֻV*9VPhijkl#nVoEVpqMrstMFuvMw}WЈWG,HEI׊NW#TԒ UX!STUVW5Y ZUUՏ] ` a b5)YP١`ٖ}emU֜5ܝSgb>Ur@ţAUף EeZW*WTmZI![MX]XmX}XX Xd؍=ؾ]=MY d]5M=H_̥ ]B-]i'xZ%ڤIW>wWW%ذ(ر޲=%}3hx[[!ۻؼؽ%[ -Y m-EaF_M_m_gy_Z=WMW_E } `?!`%0`V!`u}MHU^捉5_ېܑ]a~UOha1.ab- Q%L׸=%EU]ߩ ܎׭خد%ذ5X9 >cI ְ퀉7a-a>&<6 ߈a-A fi6aFG^H6eI.JVbCCZLN]WWdeMe@X0^1`` $\^ ]^~^m5a:Fa;Vaeau,YO9Jij2ifjDlfP. g: (gs&VeOPQ.RniEeUe߅``g2.^3>^4N[\]>^fhvhahbΉ=h ~gj^NFiUGNH~鰸-Tr64&t^un݃P.+SNT^UnV~WnXYZj(%8:H(^_|=fƒVr%m m~ki)麶5F_Urlt_~ P~ggf{Fj|0fبh``=m6@mehc.泖ٞmk^d6]ffpk`u Ki–ni6^IxNy&ڭgg^m̆姖勦e`oFhVho՞FYpp&nVߍ^@rB-rr#˭Ʒ0 +&,-rBH{Sq)!֜=󳃯X;(C- ȭ[XnDUwEޫr=N4ׂ./07% N7OGP_2ks|s%s˳:;x="@A'0tDXtiZ}tȡJ&L8MW)::+ uQRSWT n'o"p8uC@2dAg](<(vv(vBG-R5FYGtijOTDTv*.pi*w/Q}vuxW8!:u}/pZ! 1xAtʺ·n1W]u7'Z:5OXIv`s~ 7y0c?_;Ux(_zlwσ|)"zn08 —ß476tT{_ Y{~k{x{O xD7F؉g{H%/ Kn_疈|?r&!hy|yw)3GCP}:֧H@Rz* ,X`20'h"ƌ+.8㣓F*`d/4P!Å@I8:[aPBc-Jg!t0mj)ШRPsaѢ+WIb(k6+Q:Gi?rS,XF c;(03nlȎ'Sl2Tp)[z5D)(j/m4U^MU`n&ǒ5[6ڤCܾkxOo`#N~dsgAhUeКDa$[GtIo7q=tB8"sj؀ "Yw]vq' 5]YEW]Ԟw|bRdNJ98!M1H%IH!Jjafa-w܈$x)ǢP.u15 6ވHأ/ yWFҙ`K2IecP")^fgeҖivS)mڍi\fq%ARbu{(v*h8Px<&(w5ޣ7)6)Fr-,;.mI[1/{c/ t/O<:¦%cH?0O0*DQ %!€>{ O1e1#13p|xHA3#A M35}4<4?LQ;5UW4.!ePƮ+/b/ pU0X'|j50_qlB =L39/QKm5OkpTbBf_6 o"(Ô7~cDy# ɋ7.,KN9̖_99A:m?C:; ~ %ow)ـw xo;ފ3/5 ya9)@PP>c?1 $ )^'x" @$rSݘW$=}4A}0gp$taBZx8psg`CPt:(AZDZ]jׇ I-D'BRt!( ftA1pՋ3{%\c5F81&t=M Iap\8i.$%0kR%}=XᡛC>9(vQ_$%7FYp;FJR;;,a±g>@K=憛_b2I2MEy&ٚ꫚4`6M<|3!g(t2'YQ@ }pg>_9s (@m. F=jBХZLI)ШkDY 6͹SJEte|cꓙҔ;6S[ ?jPu9ԥHeBp*E BUbALܪUU(#-YOr Bl7]*.0knB-(j($=nX+FE"4ˠΣܬɀϒդmJGA15\ ʖamp@iwJE.\2D3Qf2G VehO@ZRe'*ݩJx@2Ml^wW__7-nch#RҀ!֓8y 〨 SP)Sy#%Ұ@Al70$^283=GpX4+5Gi/9D:]щN"3շ gƷu`ǎvRgc9Cn@sd$0Lvr~XX&)eyybC@慘ͨ?汸yfpL:F;<>Ё٠ C*P*A?h,Muӝ^2ԚMʤ.SUN`52Zi^@w- NPƲf;[p C#6I]tbn(~rmx#eV|Aי}zX އL ͡N/g⁩w o5xÎnKەF9d{:ݠ .w_95 zwf(s:ISDV7hN~^Nu/FSOhF4RհǷ{=5}-'MP>7i XS~~0AYqۡd|uʽܚ[ `![щh u VRZ^~LٞЅ` ^ɟsџ`ŵ`߲G B M j !xV-fr$x,!5!I!` ry!ā!s%VGj&'Zǹ!¡!Pbj bAab j!X1#$36c%Z%BF&K'vK~"6@(Z"+b"Fūy`@b/r0.01r -c323Rb4Jc5rbBfiJJJ K$$]ԤMzNdN$O@P%]bQl$RE^EF&XJexdUU~NVBVdd%YYUZ^[d%P$%iB%%0:@F faa Mb.fczceddvFeM%[ZJFOOi6'jD&_6_>%`l'wfmmMnIc2d‹dyqޤfn&rJ` :}iFgDNH&pgwRwˌ$`$yry&zzg0Ma{q`4'Q&eR(˥]nn|gx*JWWU' 1fn(gև^b&k1<)F铚D#诀htPPfBW)uUj@Ya[oial߾*Z6iH)r"≖vii)GUg~:i}6F6iJ&*-*4wBFNjN)jتYj뤞ۥFVҰZӛrʫK橳!kK%%lROJ/b ^/zoLo/sr8."mڶG>po^/nv#n#1oؖt2H 0HpRp/#O['.#  [S ; /qoFh_pА1ZJ D$q0[F8 MQ1[q`q|ppqzqD񦜱 ;Fw@Vo nZspOCH 1q re ɖ))K@+ȁuuUZM@2Džc(Tb/W(O@2*Ȫ*+2,,C-2226/V0?01322r3;sr4%5[ld;76ws}s1KNF9Ƴ3-;ų`l0.(S\gL\p1';&Sr0P\3-$=y4PGTX5 2$ =4EyIN?5S7PXg] cgPvg ksq7=7T[w{`Mwxx3}W޷>xk菛.yޕzoj~^ҢMxO o6+ی7}䗏\_sN=ݮ/ o>\7'~G_o p>,O{@)~ !ȿ RS2  BFЄGdUzVB^2| mX 0;t] ODQ QTD,e.,K(2qhLc6p+ mb5 I/*␈XBEtы7 h3kl#G1lu ӴG>zꏀ $IH!2\#)Gr񋒜d%xILVNPeh*5r+_X3-&rS8{9LjR'i<*|f4y.rZs-Mlb8{iN7 sdgIԶq0{>T$3yt"o UCڂFfE{yOlf+|GPT$5:v-EKa 83 JUDv7uJN9ʱ S*y@ֲhV~ҁeHuK=TֳTjGUb\EWVfEYպ RJ׺^ iTU)[gW[ mM~jS冤+t';R,v}ݛȊe>3@"*5ˣ$][Kj-Kp׿NLXchܸ7p4+̾ cx%  05K̐ cdq\7x Vxc[;,>!02OW3iNq;V9&WV=x3M1#YJvڙf6EMWnc#/9Or|ܺԧy3/v'oʛr7{JVCOѹyTK_jӝ냇!Szuuf[ŽnqVcws}Vu<7@y֢TGA` ~ֻ^ۀ0FՇ[ ~r]oQ(6?Ѽ8y.Ͼ;^׮=0f~׃~j'U_+|GuB}0Qח}ط}W?d~!~d?~7zg|WT)2w.'GSzX?ٷ@xP6x!{ڣb6xO6ORg~ Jt #H25pXrﱂU؂g(H?8H:ȃJizB~DhGIMQ1S(,H0X$6xTMc1?(g PFp8AMCQEh>L_%sG8/xZGq&NB[e @fgBzȃX_W>uh=5\aMEGHXF񡋼5[KH5(Ljg˨{XJH+vX[pJVKߘyra(xrA؎(.ˆ} ~$fH>8~1{؇U,0h\4Bً^6!)#Ԁᇒ.)0I4ɵ,):ɓY%CiEtOQxRh8?xdPZ)z\k )ic(e9xӂNjXl7g'*t)v.Kٗi&̘qiؕX`NLk ,bL5}u0ushjcS&Ҏ*:0IR$p虞2ɜ2@ {7B.&v1Пӟ1gSAA`p3_T+sjy516}1LƉ0iy?I֙\g䝢N 9$4ZI١ #Ee ٟ?X`u`S. NJ>JU*LL)ڡ0Y&aWĢ03Z4z9ڞ;ʣK^%HE~KJ>ZOU:W:Y:U[]_z& *=P0sWg 5YY`h +& *1w }Hc#DRzꟋt0㤚Rz/Tauک f(z璪z:tl\Hݙ&*㢻04 ZQy2.[{`"˥$q&ƹR k۶orq˪"dtK6{ES/~9 z;AH? ? ruӵVX +K¡ĺ0 :w"vy]+F+zƋ?Z-+>[;_[a;[fbպ+(W*a36jc0zLk+ dKkۨC<RjZz%L'+K[Nc#D,[$&r*hVf-0 ReFAvO5KP+K5Z=^ 2z&FB2"'~fVlK(9kUl&{Gk@)MkkMΟ&Lϵzpd\?,ܟ-MeждZZ]m0K7"$`&]F(-+-5/&3]~zSKu5ΚDL)H0ԂFV}S5\ Q^`Mb-I Tlrr=~Rw {m}5lL0ͣ >=R y%٤Rhrl-5fo#TMڥ)ڪN [3ۈ7ۻRĭƭ-Pʭ,ϽE$Epߓms>?@M>B=G]J( 5#1Qeڣ}m-}59i]/k> Cs]מw-y09c0~9;=?AC=ޔmԖ03~5K#1PV6=EG.ۘ@hjlSU^v}xzD'-e>g6?qCHT%ޓBgbާV}>BU.7Qi>;^ACT5KI 'y-a2vx.N.!>nmNjNSA+GP F+n0I[*sxCm Y]T֮؎VINQoq= \pN^~Lp/ v^J GKU.o[l>/#%V'YI NעbF7?zEN0~oF?HJLDU8`Pd Žľ\o_?,%hj?NUZs>l)+o-/N68?:_\q>@_H` '>Noz ؙAߢ/L@Oa/iX dm' Q  ,d¡ $.XC ѣ _D YITrK1eYSM eOA%ZQI"`ZS(STYYn媵-aeTPY*e2P]y/_0!\'Ng`XIRLXB ̙n3E,D۷Һؘ=רQm/me9p 2l0D3n1Ȓ$#sI南S`ۨiN?~U Pߟ7;k@$0@"V;b<07 $bҐ:;B*PPq=Ztq,0 jA%4; ,364N H81n<W[qED",a& &,,{S %4-R!fI {t+ 9讻0+DN$,E>]|Fd"orI%1, r"$!I8,IآK+ޥBRW_kn%-YRmAPgnEnh-} W^ąqܰ(W8EsW;20-4kZT=w4pMyH2ҳTj՘`UZU-WCUT.0Cm+6*X fkEKX)"B`ԗ$(ap3cAYenkc*ِ;qr+5(\<8HCD t rs͆"a`pH`E81C-}?|%:ʊtW$Pd Yw̠+HcjX{9 YRWt_:hj DZ|/%̦ʬ!(` k6 9- sV/@s)L.&-h*"9Iҗ](uҟuAFRԨj֯kmZ+~?kt0&-d+Us 0CD0$3T!lV,n["w]-nvݨj]C.[Xtb7BHVQNjk?1~o8ȳ"?&Gy!UN!n8 =pƹuNm[g t0F3Ct0m]n%L}K7m\߷v]d7N+~;.Su{c-i }; xޘ|.x7Oo[`眹vPP!gS=aq=cڳy:@/~ ;>H>k{C>8þ->9H-aSc- +4K8#A8K>ӊ`c;Aq?ʃ?˓?̣S?ӿr29ӫ[  ҏ(p܃/Է%71 )#@X EAȏ@;+>FkÿSArDp;tġH Iž:0#D,k G|l+ ņ*&9HE3 WX01EE5\_<` !F0F*@FP`ƘpFFƚFkFl?l@AmD\F,CGPhi;̻6LD3$.{{oDzGRRȰ i0H\| HœEEpTĎ$FI I IlIٸɷ PLIt#3JP:T KTk%˩NUQ*0U@U=P`UW{YE-ŨUA\#+V(V}Z d5Afge19ij2VoVAVt WW~5TAsFTvU w%K}LzOUP}X ؃rU\@X) X.Xp]We< ő l%mnV, Tv+rtT-ZhyWW ]ڦ E$ ՊڪUe]Ղ/ D۠؂VI$Vcۏmm֑[8 ؐ j]YGYي܌ܟmMŀ=ڮLZz5m ]ՓSMUXZx]>U"m[%[X_݊݋ ތd]$ u=]ĝ%ם5מ-_d;_Hߢ(s_N=|U@]%]`ڰ=H<>uළ`~ n ޸G`7`` 굑Uv\u\axa H,нW "ݬ "=ʸ -$eڌ]٥b 0ѳF+Fe`F=ֵsßc`p2194 56Vua(aYѠ3<~`f!f a* dCfPY;|-EFG& J`bM'6O.^CR+,~?U^ cX[i, [ \]^nhRWI}OThcdfae<g擊ᣰaxy+ DRb0q.gMg%Wd v&`N6څU]]}b[ ػzqӂ.v֋vhGohhv{y_AaP i?a@Vߒ -[aiU瘶ߙdsFbVKUڅ-`=`yUbTb]jmjfh淫΋fڮH&fVk?@\ mVn_d¶ ]gŎ۱l^nशL |M^[7d:mHFhnmxԊDmmޑ\kni~e%ltnuv9NeV6ާhZmFh^Ӷ ^Aq 7@~Wۢxr=ip~qHt+/,y6o `յH;;s;_E}( B3hS&z/f ue޾h !94$%.'(H*o+K,瀕r+K4l0U2ן#45os6s;s5Q03t.t Z]}'\rqB h!D'Rx"F+pdѠ 䔔Vie ViΜvgB-J`ɤJo0m)ԖR )Z9!)N!cv:jE4k.޼zWd_{3nPLr1VK?bkZŠԪ!Nz=p#V$}UpȤaǮL6oɳO"k(Z7n4u ԖUIkµ (ɖ=vm۷nMϥrb}6`! *d Tf@5K/iqڂAl3vA3n[Tāo/'F}dPr%-K*S NWSu]AvqםwJxKwy襷V7W5HYd5Zu~EW{q `aoy'v5 eeh>!j'^#xbm*2P/8#pwcH#8 C7$*K2ݓDTU.UVeG'}aYi9Jڞ|9!bxDhNlqiB+Ra*6$})JgjM:%Z%X^e%c {釬ӾF;-ȍUmra kФ1bob!WI-tA;ݩZ0I ڕÿW& oqt~2vTŅ 1XpTXm1 a9 U*(Q0]Is G荅__px^ }THND+@:PN[K.9=-˞V:|f/ Kߟbi[=kvI^CwhE!Ȉ3N㐓tC_J$:.N)BT|:"v"W]R7]IxS^gzҋO{ێ= |/%v[K.7@}cwO{\L77o{9 nL6uoGAXP3AuЃ:H^'B'LaZ=1,fr}A+\H?$+ tb?)06V$J,-_,Xm7mJ*gLrF6zrd8T]Ƚ?PSrxO`d# iR;8 Z s (&䊑["7 ES>ֶ2H7&O|!r*&B})L$d2 63pl_4YMHIeƿo U$@i@t"0uYgO9w3d+1Q1+s#y1I( ХqCy9P! 4ht֜@QOO xrduKUQilzSgՋOSDEK`$=k÷A՘RU\u0YV9U4[iLUdBZuum'*og+r~ժRa )6zEc2B7ՖTBі&Hn2 J sns%mjDp_[WĥC t%lRXZu]rW}w‰"#OK_&w''~U`m ֒M``6Z,[)+dv#t7.X_Ԅw(Nm\߲ua+5uu9-y=yk%<`# *E:BX½2ceD[<05ӬMKŭ}'cR:wkwY,K:+ Ir>ٹQn `@q$sxvLP{"Ra*poш-n T87c P , IRprP;F嬇RÂ}HB"v74?E$8\SY՟n+a!$3DӮDlewr;ݱfwXd*6}n.{ g“TuD\D8/cjɲA&g/w[to7[@?0o|W:ӝ Uzwh {1N=cwIoBý{AUoH{,m(wOe'/Ћ<骉|l.JǼY4Χc=E?zH9\jX\~OJSr{Ȝ4;>r T LěDi<EX% qTxn  MaH J`ɞA`M3___!%._61`9uQRahO`Tl _"]o` a ` `ň̆LJy A >95 H\@- T%n]j`bO@N^ a ^ʠٞf@!ʗ-6@-!"D(`-"2:bH=MPY%V]&R&z@''N(*&@)>Z]`#+"ܽ"|-.-_./VG00&c.`".">3#4R#X?ac%b6v7G8~8]9A݅: ;;ܹ ,_-!bkb/0`abB1nA!dLj)x:z=$5JP Xd&bzb8v}`*I]$1eP%(Q"d__6A$fa*ajE*UcV:@f`HfjPfe<'Wq\mݢwvmMIr-PuI6FO'E @,`֑l݋$e`(R! d.5"s>@ttxX'vv:Xwz'x yRyz' \{K'g}*N~bg (:߃ERA*.uAvVvv( |'h(QhܬO|¨3ͨ ^Vh`o萚t!)GHGEFMiZiaC#鋚J~~i)I ų !e)2*BJ-MِTyNz~iR*Х&jIڅ*^< Ǽɩ*ͪ&E-uGy+΄k|).kll*O<\xE6dl L\ت䪫֔ [,QH7gʈ:S` :+fldE2Ƭ ,(,>I.Mɓ>ͼj, Xndžl}luTzִIlH,ׂ ڬzKN^gVVL-njO0 2Bdʆ@Ɖnmn|m,,(-pG--E-f,VfZ FkFmR~b.n!.zʊ. ZD`n--^|6-` NHR-Z-bnfon.w.,䦭J o")}>ޚ,M-iTw]dj/CDN̚ S{hbA'FF#wUCMd@pOT) VеEc RFpnpp{u g0]0 SϊZfa,KnR0ZLaq]hqp1 ׫ fpDoD 111&QO (pq)2i0װp`$Or3%co& !N9,'׮()s/ r+S,-?s*rr/-x̚hrQ0 H <2_2b4B3;4!5ˁ5_3Irls|v|318w99K233B3;`>_?r1+G2AsB4x25;tFЩZ7J4>W4OpFwGp(3HH4I^, 6PQJ KoKo3LLϴ?it4At^4V5W3jX5YuP0{s2(ś2%w5^WW5XuY5`5ƺ[g@7ƛVduju__`6a_a/bbucr@NOj'^_fsvZOHgt6:*]oSXk5fgvlZSmw6 vd6twe[kuqw3r'7Z/]xwtwB=iD{^a6mkwhsnx7w\67QD7o{|7}K~gbq7wsI8Ё'x-x5< WK˅ysxx8vf8wx8v@!4, [A*\ȰÇ#Bŋ3jȱǏ Cb(ɓI\ɲ˗0c6T)L6sɳgH>r*ѣHWM4ҦPJtjҪVj+W^K6"ŲBϢ]˶۷pʝKݻx17߿KÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&LN(PF`TieMJTZY%Ln)e^QyI _SerI'vޙ{"w"tJe FGTjf6ʢ* Zzirک**jzj j`<$l6kij6 !Tkf¶"k6;-Vnꭈ+`颻۪֛2˃ G/JTlq! Lp" K<q{!Lr&L<c2.'\o2@s'֊=kA M8+qI/t>BN}lCcm4z5b t i]{ q !^w $8Wm1mw;XᇟAo֐C]+: &kgMEB {rciY}y:u:{׎,m if|k./]?=\c{ /od{Bo:[Pk~"@  _ 0+ .v Z0:IXB~DX?R1Tķ 7yb}KֶFD1ۯhh8C̝p\@:nhȣ[hDbUbCHh)WvF6ˍq#h;qEHW NDdF$HI #]INm$BFQrOC0M@R+c9YV $]fJ䮄)Lxb1séɬL BS4L5wMEiSU 7α6@Յv!14emZ$D'jZt' :юf @GM(JW\0mifJ}zd'k:Pc ]hC XN(F}QƄT$-PX6Li:S:} O*"T%cCPKUIUR4d=UOzUf_[W֌=PV= q5R@Wux5^jտ zHh1F®>MlP:T:ny|FK.aCy z\A&@Vvד%H[PŲ蝀z~Ҷ1k_6.w&]r78v*HRX+o3 ̻6 <^)׵%qzk1n_^mW>f@EadSɻ}rvk)Wʥɋ`!+4CKy/fj|3~˦x@#O ǫ2镮jntВVgucV@Vdv 0}3"MIwV4C5 N* GRϵΜijWְ5mkPW^t`mG FMw50Sc;תW[ܴ&whs]>n^{ xOZfm|a%nΚ^VfqMO1l9l @Q GUp橻 sʻoZE'MtVZ/Р5eT/uikwФNYBڔ9n Tٞ%8'R^y!x%h1Z-3Zx Զ@> KIa`E nYäNڤPKT)X:ZM\&()fUi0K pzUYu xgBo~tݙYOwWQZASU:zT#j]98buZWk:Rviq GFg`P":ZBln4-[tHAc3XYss{_dQN_[PWHGR.dQ4 +6<UʨFĭݪߚ8[FIv)wi:|YWt.K:?\vG_Touqp Kp [Q+6;{U{[QO3Q$uE*ۭ-i 0kv9ςʮhK;{9=y?UAJ*YsJkDL{TNTP R[G kD-+J:[۵Va;P3 )j;RlGn/+˲/+btFp:993+?C3RjrYڸIEK Km;| TV˹X҂Ʊ{+PG;q˼{`46˷K8nTDɋ2˫Rͻ9Wћ.KkMOQl啹{OK[^{`bd۾f ;Kky[{};LAY+CKxK;Kӻ5՛;j٫yۋV ,@{ `ܛKA-%[6vPR˔u{@Bܻ:fHRR[yJa l1 l8|U.l-W,Y`ePr'Sa<0f^o,¡:n[pE˶Ǵ~À.뻘3L0x\hІ1Yp AsɑXA0伦wƧ.\{)ư,1|9!{|,-L=L<|/v,Ğf|<|! <\PYvj,d:+-Χ ۪:l*$ǎe̼̅& - -.9J@3}|\HVlDSFd]!-yK^қ\£¥ʨrӝA C-EMH CwZO},Q8S Փ5X0}\.4eMg#%}}jn;ҭ<ϯ<*]n|Ƒχ́ M@؃|̇R~̌ ͻ,D+[ ,Tڦ}֩ڗ̂e<\|Ӿ!dl0wn<;Q'Dx.XR^E$2:Qc4F>[{UO{Q'Z 1QG E+US1SZ ^R<"w 1Ng)y4⬣,0Sf1q3,Vߓ6T]BN_Ŭ "*nJDG&B*e7n< /15dop,/3Vu䮁߻߃BtP.mDU.WY6^(`*b>R洽gNkiNIkm~k&B6`H/]~.u[G~.mQnXIKMO^NA~2N[ޑRžTV~Jɾ>8wLNGc؞:<>6P;uȕ_QS>e _Ο6q^EeoUNpoG!nxʮQ 5$1+0%'in),Z./*0t4/y_!?;P#?_A5CoE+o¶hj~IpȾ*PU:1~[S=Ux6-6ٮɂk_m_)oq^s*vw!8;z02@ ~NkJ#8;DoSp?G}4J}H5g+'Kj[h{.貫.P* S*j3< ,#(K/Ͷ+ 4V$8xm.hD~CZlq;$9>H$OjƖۺI *p< ( ,>4PsMbtS8kNOMA 5㐽nM{Km$M6ڶ-S UTR3u\U{_aVjJmgڵ-!a,LvYfkhV/kĶiB 7q-sLuE]u^ 4ӁMQ/89 &Ibl0옘amp*<8> ,MX |7$Nr,ƛ(crʋcH ;p.!!RauZu b + 9tO $BȂwx3Ő~z˷,XQ ҋѷZ_[l>mf{L趻F 8n \IqH(W9a6vzCzzG .tY2J x{]bGٙv v7 x› Ph< L{<Ao4ӣ宇}g{t0֕a[Է.mM_76_<7&-t 'ЀAYP'r\$ ZwY;tt ]ɀvY|Bw2 @7Hʠ%:ZD^$ekОE)mWbd&dϸo`XQxDB2Q-e.H "{dXHFrqd41M;@Q2$yc1=QIUR Lv#&ƉP$_.˝ZgQs~aO2eVf(.yǛ1YH77ΐK$;;w'LaVh1:@wHr'B%*-)jыR1 fGҐ)@_jRd4YgF7 ߹LHm)xj:Ad~", !yvj~$hW0Pyc bYYyVUk^[V)l/~4_ 0*af6bťNMdj?:plnes`6P=Q5m;Ug_m ˺93R\B q;{g$v^ldd-4B7Nʃcc6vWm{tvpݜ&RpPKʪ%0> T_5# X PP0 ^KqH\_]|A7iRXO11mҮ, --{1_577·ߝ?ّ@S\jVVo1\:Yn#(v:'bWG%zg(B9>h!Cm~bR*{9h;jKp;?$_OcBp}(xE) BS =P |I/TK _d&D~|{% !4H@TdQ@>. 5 { ۳ ܽߋ=+4Shz> )>Ӿ>c? 3A?P#cs ??7??ؿ@9#|T L@ʓ2D>  ;  ؓF Q c{A4q>>A@ P !SSB&tB*+ #.#B0 1L4>J4DL MDHPdQKG+W\XԮt\Ä$ lS9TÊȎTJĐ l8ɇ ,lI|IJIuIvEwIGG|BBB?G@lJj4HCHyTH 7Tߠi7Ct KT LH+͐ Fm Dɴ\Iǹ rKDL$J,p+9CL9pNʢ?~dE#YJ,DLLH&$JɱM ԤHT\؜MϧTܼE Ks7rs̾TL2NEN4uL<mQxQfQQ UQ9`F$TK =R9PM֬I&mU!UAܴRdOޤC .K/PӤPЙPJE P ګNy,a;LŏHHVD 'm]]Uʸ ڽS(!9VlD%P[;ȘcۻUۨ_޷ #Y %- a- @AB]@Fn/`HInb"R`?` 6 -z ^Qc=\4`T5e5=/_ Z߰i.TV?a.bcd&eVfv摀fiVbjFk~lm>^ +b L86r، ^i 6ST6Q^y^X"YZe2gؽ~vdZi`;-=d"fVBGhaabJdL h iUvԔ^if 0~i|ڥgi"Eٻ"lF[j(=>jNP]Aa>tLGejꪎë^_歆KbV=iuUnkN痖k0ҿk!k jN@F&aǦ^݄&>fl]϶ 퍶LNmiP.g&؎mRk NNV l&®NhZ`Ǧ:&dF$W^fmND._2)V(k;1UL  ^qp;я'Z[q8n4bpjl r.-9  ^["W h_Ivhyqˆq q+q;q! r!53#_$%߁&"3r),*ǥ^]r-rQss127F9W?_jp F6IpbBX.СjPe b akɌw^qU: :Hi`kp OwƖ_Y=P/!?sVau x`_Y Z_[=\_[]_^_(`abcOeofghvvvnOow(8O7vwGxw$Ѹw"wyYFWXgYZǍ[\?]'^7_/"b 0v WfrgGh i?>j tolm뙧yyDqQz2(z8xWz`zpzzozW-$zx߁gϥ߉y֏Kbyp yyny$įQ!j1w%p|w"vgwg ORYw w}g }5wuG}g}xxxx .\(B .hࡎ3&8" ]6r "Et$ VNhe 2 Ьyf :l3̠6-(RJhdɣ)U60`@ĈCkF-',)ҩF^(b". .޼z/.l0}Ppna`fA%.ps;Fǝ4Ԋ h- CȒ'A*dD1j2H %oN]`,̏Akęsgϟ@-JѥL6:PjWڕ cD-[nK˥kw5b xa9lYƀng^1;u yZlѶmUAC E#ddq)7sT=Rt/AMN)ط$y噇UWa{^|ٷFUs=P!5`j&b B&[GZ! Z`gfj'^#Fd-S3gcqr(] YHVM>d8d%{Y·X-wxag~&xX+'~ zyh6Pf-"o2F#q8cIzjމJezXreY찀>wF,~jhVڡ&v"I*B7w.tL}Lh0|4F; gm!8C p\ûzkN o"l EZ"֖å* jF2Uzl1-+Q,YǼ ZRuH{HI0qh \VƔXc⻠9!p=_Qmp/63=jrKnىjգg?cfY,S`/];S?Wus:A}' M/mW²~n@;Şǎ]0sQW,37ٮ~hZZL'N1Wb8 k|cоoevow{[2]w>$!0L.! EZyiFpaxa`y΀ O;\`*_a, R^N!"*b\YXGdOT\88U.*Z'_فT`a7vc: ,,"-\bi`r"F//Z061ZVN$g,c5' a4]5,$Ca$DDv7^ a,cc"Z. Z>@ #(I2c9CQcB2dCb#›DN$Eb7FF#G*GV:a; %tWޞs8JDK$LLM M d BUd1e5 BZ_QFdR*E+᛼bTJ_bUޢHfVP)yW]XXDY^[1?V?^@@%O%$e%``.%^4% b>TV:^`dRXIeZefnf0~0z>hi_jZ]^j lʦ|Ѧ_&fa2an$(fo:,ԥJf6IIѠEiQRI((MՊp^zN(εgٽBȧJ}gt'R gy hhX>TӖA((GZ(ah|*qhfz(Ltxhܡ(T(y hh1ͨiਖ`'߀DGHrǒEőjǪAJƑ:l)FЗg4b'v"Ջ)iiv*cvLdh*n$A} U(XlV{LTj]j1v8Dx*Z`8ZRƨ)ݪT[!P*v%N\ܜ%`j'JF~ű Jv{DĴZBYkK`kɾy+^kl묝+_녞H꼚GF@Xސ ^&%n6,YfnĊDKBvdž l_fʂR,ElF~l \-֎^lImOͥ 2.9mE>FȰq\fmVΒز+^ޅ6^*- `fD @DgkQ#zቫZ`)-™fyV.PvnC|.ņunn~E%AmFoT F*zeInZf_%nn.J"Z/bojoErT/\ڬ.o6o߽/iNiX\/Ld/*EU@_$%_`.k>pF0 Ei]p!Qm^t}p*b Պ   Hk06WqG F:`Z[5T0p@HqQq n vZ#q3q;C!iϱ1k?T m rۀ'm.C2K2+X& -W((3(m1yq1 U+3+#U,,-7sF)TbRjC-_:6K-445\Y016v}s܅s993=ì:K;;<y5<׳@kH>>P?[xr 4DkA AO@BB_ECpGGw4'tF[FOJ`1Z5IqJMnK8%4!MNwz'xPrM5TQuRR7u@GVTtUUCo5YY@!, 7H A& *\ȰÇ#J(!3jȱǏ C"ɓ(SLɲ˗0c r͙5oɳO9 tѣH -BNJJ"ԪIbʵO^} KY ~M۷pʝKݻxm ߿ LÈ+^̸ǐ#KL˘3k̹ϠCMӨS^ͺװc˞M۸sͻ Nȓ+_μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&!D)% TV) d%\$WbPajeK~dbfffje9j"`p'}9ffJhaމ虊YhǥFjW:j)jZ Ʃ=*cZ* k+7j3z+Kl_TkfŶvm{øaNީ»ƹZ{mzKZhoo/10n| O 0O|C_n#ܱ <27`2*.2os;|>;(@7,\R5Tj3$tO`hl6p}t`u䭷wc3X6im8qMww|gρ4gý88q&889G>嬷>zWغ 7D?ӛgt/~'`~[)X |؇ԽO;쇿Xm@ _̗@}b`@DyГ.ȶ a BЄ( ۇBP~fP  CP)T! X&~2 f'Fq K@,}[t"6Qj ˀ IH̼QL WG;.Qk?NQ 2!FI eʦ92Rm|dؿ=Ⰿ0pV@(E9JR:tD#+HCI>hU @jA$f$[9WVҒd3&hFVӬ&5lnT7ÉSot2NB6܁4?'y^ӞpZ5F+Hz+HGHJWҖOALI?8ŁNwztv5p֯w%(FkQp HEZғT+m)K_&δ5UvTUAPIPTI]jS UNVjJUԫAiXzV’5Qַ&U(M UpK ,X; vcS'KY45!WiѪ^ l!TR ezRݶQ1ip Uq{sr4|&^1,:-KژuWM|&*VwȰ_;Vл=kxHHl^pk J\Ve+cM|b7ma,aX63æ1j܃;H^q<Uѹ񄟼[xUy,\-^nݷ1#Yfv1e< 81\g97z"r${*;0 dB˂j>SԨB0-'՗B׽md+ vMVSv*K'T4<9POդ65S-U'Nz5b=kg֝#uiK]"u=8YVFy .ϱ:NufoS܉mnt@n xkz۽Gogv'mn{{ 55nkwO}*ouv6婁]|Fq21Oi.qh\;@s1Cя~֤MxNpo7uQSLS6 Mpo'_{ё^A= _yC\ү- -y}ʇ;OΆ|%O9G! yGԞg! zyw韇>ᡊ_ֽ=$H^/?|COB?t驙*ab;RX& ui)+[-+k/+R1K3{ha&7=>3òvCO:EOvQKU,We]k_*b;"U m+y H )[].} e5)(Kb`͑c˝ະ{f;P#x2~ۻ%a۸zg6q㉞h75<   [֫˹=, "\%$l&;+,©w4ֿV'^.pėD\G B;SrL<ܹ+JhL`Iu,a]&<=Ue,g\jRr{q|Yg6;zi? It*+ǩ${YY" ɒ(Wɖš ɂi m|;gr`̚rY),H`|ZKn:O.OQjSnUnXl6nI!^kţmp=r+t^vx DGE3~t N} ~Y\.^`~v{eg\n0R.Hj!|mGN7J{멗^TNdPRW힎?]^_α~db5*X2+4 .;>g?7J~ JL^nކbVP[.N`'"$^&~+Ah13U5*7Q>|G%߃N?otTl?FEԚ8oJopJXtR阣7tA}JzÄ So,J2.F72$nyFd</78DY=ߑ?M_ю pwJc_=ߟo|H9SǠ*a"=$``E4n|q@9tɅ)UdK+̤M9uٓOPћF.=R`QnK lJW aR8؞\*`v[%K~TΞx2l%*^Z$ɕ/wI& I ['ПBڻ#G- ܪԯUX5@[p6ܞ@: ^#lô3pB"~l3:,4F#PSm5 :- :(v b5*ȩ:9"ɹnB2Z )ȓ&jkー ?ۏ?8P , T1rB +t@>L 92 DL\-:3jM$sܱC $TuULlɼ(*r<̻#T?255#45PS-6ps8loN:,0,%O4 AC1 P H1&tK7jq{!P{^HHLUCcarrd12b^|J&ylh,RVat-XRd(.$:r&agG*":b5֘+!^p@ 1 `Kp5d6#jQkWZ‰&Cn]ao d&*@ ?nȄOaHb ["?P:= dF&9=/BY"f lYgwhɇ.3|1vzIc;/*XFdFh{Һ;o$[^}&:J\ős|~>ʻ}=]-щd:fP:u1jFѡv;Њ;h۟҄'jV ִƶ=R´my.i$|#s̷://#?ͯqCrT:/ixE,Z1d`RN:խnIfd'Ip5;upi[igy'Z !W.< `=Іq!*CiUYwp9D1щ/URW f!Q@:EAlt8NуtaDx3+">Ig2ra \zdջN@ilPxƅ֡CW3U|yD4 L!MvꁴmTk*rRqRr&~@"TU6]Nӊ4> PGf6$:/˯O[OWM+R*sՄWUMEVn!RƮe+hZb-mM6t\Kttp+$ Yc=)[b.t1{ѾO+n;˼'v7>G_Z&^2[ Խ3=<[bc ;*;/ZC.> 4;k>><3;s??<<˱{>쁰 ԫc=Yy?H@2̤ܛk (>L90(@j,;ٻd@ DAaq(A?ڀۓ<BB"l=-܊$%p. ;),:,B;@S  C \ TC6%7\>l>ù;KA_E{@A$3C?QACITJļS?%=&ŃD!E3S+T,=tWl@­E[$: L $F8F9C;CsA9hFtkLl4?`0pDDp6Z(HpRQ<=)@*$ (y(ڋE #Ц E ǭŅ|ȭ`>bß/`DzƎiFkIAϱJX˗I(CH 8LɅ.ǠBB<+9JIʌ.|Y@ʅʐJsLD>T ư, lHH̰h|KBXD*pL,Ȑ0LOM#t$sNH>? 9mYN9hNޔDDlLLБ ObtԿOtaȲ|OQ= ] ďIВ%R*+eѻƼL  L,$NbLDd:G/\O0tE'MMJ MM,ޜڥk]kjkkmn ^ NCI}l-[ɞlvlƼlZlRnbϦm6m۪ۮ(mvc].Uܵk ik~&nFnS>llnoog>hXծ]`FZVhmmWp`aFݵ~p%n hfʩ됖fpͨfkl9.quށgqF@oTo|lq}6NrXP%cih#*O +bn`kpss&s"0s@p67ggs.9;_s>~_ΆoqUtOq^v[tJRKLW`(O+/,V~U0_nޅuYZZNi\n7'q^s:G;vx&uvXw>xWmn\Ӽnl-)tmߦە\mJ\j-Zb H<ܭRqbMVn]t1WViKTy7 @57◜x،x3y'0yEyVeyguyyW{GwWV^N(9z HXzfrύzgݬz!*_{l bwk]{{|,yyy9|ğۜ߁Xy ɇ1?z|1t΅z2z$ 6}GԷd_, )hP„2!Ĉ'Rh1⌌T6rcG'"3a *Wh%L bfA /Jd& nBr#Js0G̨-ԩZYphR=({,j&6 \ rBkw-޼sw/7-l)-0h 7I`$',"̙nj*eB6m&3ԪWnӲgmDEdeVt%3o┍ۡOBy{, v#Ls zTWnkFŒ5l޶o&5/ x S9XE6dfdpK5hkzals%FQoqDar%sё4֥݊vM=EaxaEyut]vcP{% g_~z__GK9F^ 6 MHdai}x'!Hۉ}O)RF@e(8-w0)8zmTiW 1ޓeٕ֔nU\Y%^^a9[ ``;UYg&t!ډ'E~Zۦ. Jzw\Azf(3(+viK ŸiՑZѩG8YتRE{JZ \H`6`&ݲ͹\F{jzRӵ-Se(é$n3=iA [+\ C~xH* ^  NJ%v+H|eh1o|Yoȣuo$C0ϰ͊2]6 8x@ cGK4WG)1TZg \ wm]az1;b{Wel'70Bvw|;7 TN|ϏCyF*?A[~&*>@^5u:dW`:ؽ.Lnw]v,ńdk6D7Ey+sߠ-L[<,g|B7z;J;8'ϙ*jMհ6)k X@0 \,>w ^Y&`7Z~k "(v>#={t A]k8$"eXU)B I"8Hs_L(M"K8p o|Sؠ $%*0x%,t%вeLiJ.R WL`MF L#Cve2SAM ď28׹m,ʑQc@  ")JV|d$'Y0\Bdc< ވdUe)Q%,j[Rt Dh/h<8X2f>-flbM.}.Ǣ9N?p;AC*'>'I};B'4P$DBSPhEoyQ]fԁG p̐a,IQRf,]Lwk4M;t<OPydkP$쎊T@RLE!&aԨgˬ2I݋DZueYֵԭqk4x+pNr3`uZX&]]ȾNH>l6R8`:HtlaR^Skak@aκؕe ٗt7/AkՃbռ1@}ֻolGV4~1\4"UphC'PY^xŬ?I6^gYTׁv41[5X,йλmɌ훝|J[ zoB</}n% ](Dե2e+k8220cmU̜P4tlnj&9svg>π/]a+JtNc_/vrQ(KW{lv]tXԤxUmծcYtζ~s]޸Jt'E}r,p}ΎG ,P.δe93j!v_\S̳;y|t#C$@k޸J2߂+sӾ3@  {F?%du*imc⟶8?YϞ*yntܔ&e*.q<0ogҚߙvu;?Wt>tΤeLQ״i_q`]]viǤq7͉ۻ{}D0@D ^FqHXߝ_ (Յ `` QK  IFE&ʟ!!6!;%D&@aN!:opayn%'!-  `iz:EJM_ f ~@!)b Ɇ$2ވp%V"4J%f"U uaYbnؽ*R+t ,֢@Yňg$r@/*/0V12*#2j(3F#B4J`n5vb66r(a88&I9b b: Ơ,`Z٢ .cT# N >"1? d@#A:i$$OBB'barL$'NfN'\VuveG xg_RdxgZ f-&n*{{ogg}qLr'5( (&Mh&N.h46Bh6FwEx&z'h.fcjG)'構'Ҁh(()8 WBgb,/ f"[mT;zА45[ C܉trjP)^i *b*jf j&Ӣb* A*RH P^* $.+D\*ø&Ȫ**EbPB8j  քn0+2ƐDRkX:iFġfGZвk*ZkK+kNl VZ뾎ziD4BlR,bljlr}x,^l A&ְ>OɆF̮JlV-B*l鬴Q+k1"h ;)Z) Zr©-aj-q-ΊNJؒX:v/R*[۲,YDܖrV(fmO .n&n,. 5ò- V.fj.zֈ.^0/EnpU,"Х&֧RDL.fƆn-%nv*@R/t,njonS~/{S,l//o}>ҕTO`o~.0 GNƂDGR0\bE0wqZ2e 'pдG̢0  0O 1[psq2q8G@1HqP-pGAL 0" 2r so#q-ȱH+oN_r2 K&#!"K>g16ձ?l[2&rv2'k'T((n)W1$s1++22%-GD'߲ӌU=($7S2CD3!"KsZQ3Z3`,j3,_7W7C3֖sMsHUsZ;>*?۟?@'@{AϢA6+t<7HCC@DD_EEgGFF_GG44N;`It 촴R41ŴմMpN75 'ac0r$UW5)mSo5WITKZX_OVw5ZE|5XX5V/Y5]5F[[u\+DH5`75^u U'bw`aW5bSbOST5^P6gNʬomfsdwig dhhf6l#sjjhk piǶnl+cmmkSb6q+qo'ogp'psZ@!A, NH*\ȰÇ#JHŋ1 Ǐ 3Iɓ(S\ɲJ< 鲦͛8sɳ'C2gvIѣH*ЏKJJU4 >zׯ`lukLhӪ]!فf϶K]oƽ˷߿$ È<øǐ%HTN1Ǜ%MӨ]jݙgСINMr»CKУ_ֽ7o GγJO0֯c}{ x^}7}q'}&V~ 8`qAf9_1LHakjh;qhځ0]m8Ž<7)$D1ʈ\%> 9dD b0HP)&DRHeVހ%&\zYci'epion_Bu硈 }m™љ!t jꩥErI!k vBraզziV`챨&;*z)؊kīRK"̪*JKY;շ[覻nZkf{Ҷ6l&! p;nWprJ$qS3;pO{g ˫%ksW*>Җ^Pij=,FsKѴEOGm6JSSm5ou1~ a]Qg=R?6nk w_CV7&lތ4S]: 0m8ӈGT ܵ78KvkbyҠj襓~:z讻w-${7`Vd wk!&`(h"vGBɟC=ԫu{ W/}_~_?I/  h"0!X [. -(" G<aQڀ*BC~4 CqKE!FD XhFE8"cvEi[bF&Q1#ϨF6эoDAo8ǒmY&, y#MHD$!dCDdFr둑$P c6yN/ ( iSSBQdXJ{$mi*\꒗C'Dam<10Gaͼ3Ii M*^̧({NS|*i3N딥4'kjeDKn&_ ȨFa<%9L2gSB0Z6LD'zz6!hF9}!(IjN4Q016~C跞@ժ2Ы$-(xb T8_ HZIֶpВ5Ljb&NL%UL]SJV&`%Tg[ֳ~Kkm+[ ׹ ]W_X7 vbzU&d_[mOW! -,0Vn%B+ڻ0bثdzծ.w mrBַ㒀U.k=tS+'EwU:نwmn[Hbo@_~+"$0 [c/#|0v N{@rÈrU<bk[X*@6.`0s|{dSj"HB^G9cR'W ™2Pe# =ZV /C'J[E3|J!oLod3"YyԒibz v1o J%VA}cHlҔt1aMsj8si0y|v^kBJc.z׏fnlI {fv1lLQu~̩SU+hoYF;Zan%{F, [~Eljj9ɪ3m/\ ݝ[zwx|Qvw޴ƦbNk+'=X@)j[t_kRQ@>6PqK#~r pO}VUe=҃GN ԣ>T1ne.gJ9v{v;hӗSOx>'g:K_\>W]>fn[&`=\_QVl{T^мAU`6(~| Wf|XV}yxys1zBPzj(e2*'~1@_X~gBw~g~g*7e7PƢx<1v|t!Qyh} _*TguwmP8&Pw&S |G&+'GׂB}2owagx=i^% h}}燤GVyNvOPHhZx{\~^`{>{gW4nPX!rt؃?((aSh!j8!8ADc𥈋ȈE=\dD57҅Vka{‚p'+в{(6uh.!zrkQ XQH)\3WSrs[֌#hs,VlGbq8Ge(X(} 8(Ȏ4(B_x%[Dh~WSFbH% eH| iy0qwXy y!)$iA":Q(Z+ɒ-RH㏒lٖH{2;ј GP1suwFIH "ȃx$4QYNWU? nҕ`ibI"hwҖn9py&rfxzy}Vvݘ;Xvh%_ј阑9"RYm&`)"T$xoٌqI9sCj/}@)@rS6~wx\S&>1?tl:$TBFs >u&C7Rg5t޶'E_} Y0$zU–u"A>bz|򡞜B9=3|񙉫BmrgZџy:ZE%2"Gtgաj)-韡hKʤN"RTM2:Zzx\*l^%`J%b\dQfzi:&ji(zt !+S-*/mٔ͡@B &D*FJ$ݳOwji*uVSZ_[p [g ;֪19ZR=ث9ZʣyHfZz%ѬMJB t(Z*J׊Xz*ۊuv _Z5,۲뺦ڦ ztjvjC>92{e#{P[:FdⱾ}!+H_e~VQm-벒îJ7*7{k)в:G+ : V5*8܃Fг` [d{,; k˲/˦!5Jw)y|+[;HJ˰Nϛj 48=Y˱Z*[繠 bUk, y;;oKu5\+vy?Gʺ1{c뽇ƒ,㻝+ [Jh0ۮ2s;u9˻ Է ZFKIJѻ A `(;W{FuC<^x!|.g}}"V }xubɛA<0щC:4= }qE#d9a$nuN8ˮbDWP6'jf@N͓2B$ë$Dfq"ݓO5me_PV L]k %*my "aт)-HMjB_-]Җv[ү-,yDX2&d7]&9 I; =?2"dyԪf#W`eYM!u[,_ ֢,ք.RքyE=ein=sfu_yJ+.7 Ӄjb^/ =Nւ EI~MSjƃƽ}^`.K@氢 "݊- ސ-].~٣b8/NڦXM,pnF0^M ѠT"NNnf&ҨR$xg>H^}s‘w*~,N/>ߚ-~,NmnQ^q,rMP6?T3v(IN$X}`~=f^h^D  OnÞ~|46֎퐤X.H.J^L~n7_9;?=_?AR$ESGmIKM/OQSU(^+~5-ntco͎X ~b!$ˌ_*zx=~隮5hb%>E&? 57F/m'ŽzN.bf>NkomJ,> Ġ ,dh%rXE GGDԣGr<%HidΜPf9}yO %Zt( IO,eS`LՈՅ +ן?SPYKȐa[29s[WbBy<<{!(k_ &,/`c'[HR9IٲZ/ѹkЄ!t'U3a3};z L}Fb'^qɏ d5oU X<(1b[3Ve1H&]%3iڬ)'ϞaoM6R'B%8tҊ 4@B 6Zr."Kq0~ L2,{M8+ )L5Rc͵*jN4PnI&(\cJ*հ+`/O) :<;xPA VdA Ρ @gO B04p@ьк,L#[J4NcqPC} Ȝ`;/9gѶ!_OV0@^UCq28a>hƨ*T6fT eE[qu[pRJ*dS-9uN22M5bM8Nw=$P(MP) C%[T $774L70BDSwL.jWm3nmu^S`XcmCemg#vZl6pw\(5WAt]Kw3^Ů^ŌE?<4A`? 6)f#QH-Nck9MEaRO}NUVt4X_jtU_ تzOh'΃Zմm ysgj^\=@v.^y2'2N[L3H{6nf@o'b5^b0 S'S`eZu˻y}gx3JhBB%i.i[<4aNw4|W=x\3F®5y;YՀQ[ħ@qY[(Uo[,>c)lĸO Q$2ʹLꟷ=f9cd.Ku t`!4(L`$p;Q\T ւxZi<2y. _#1P ؾ%6щ?bP5EY1 C.,s4<92QNhDVn46GhA`c*}n`9Ai$gRH~Ah{dpmZφ3#6|L=ABE#eƎ>1YdLKDn-1˧n'JTd"̃gCౚz&A9mv3N S(tW"sNFF/_q;qOsQ'9}&ld?ЀAc%(AjPSƠcb0*J)BG/J/7B:̑AJWAvIL9Xܴ97S5B݁9N;LS/Uj7N^E` (YJгncjU"[9':r%]m`Qu{db6)%a L],RXAV=eٮi,QeT~?/i#)w*⣪=]K}O')1$XNeĴ ]%\J]ȝ E^sϥguիRWMa%V iTx %lNI]NcT{A €Xǰ$׀1QC U`@x-*Et0? 0RM:U T1<21phDxj8qcnVv rf"vJINxBZ:P֯W*Zg^Ue-]N0uf&EozZxU+ jG/vg 3N{Asd7?qU -D'ZNߩTT-/*f7sӈ:J ׳;"7Sc=>#0|HX8 @3Ìû ( C  ?{ 3?C?;9b!C'8#<999P9 0=?9R= 6i$& : @ ̊@B$A|3AA ;## !#1D?*B?#0\01 2D3mKZ=6T7C\CC :tCu=|7?DĴ3`A^ EFtG@90D;; L"(Du 8 8M$ c*?z<H G2# CyBֈJ h&AW4ŨFa qd)5|;`H+ɮ:|7, E4l\CԈndD>23w?| D#DyԀ| t2#f2*mFlLth׌͵JTű%MB9KjQK)Γ!Mt4NKH9<-=UKf,Q- + à=؜)LD1$(AQ帡(a HZ  8la 7a+[M(Š2yf؇X YY@)9ٔ]ZWHYّٛY5 Y;=\]EUڥZ܂%@:H5 \Ȱ ۢ9[ۥ ?W[3M0]Z\4/a\p\Yܯ ڡ ]]E%:B=YM]YōU]F[PDb[pDMf  [(8^Em٪4^*:-ه\-F_U_eYߡaf_5%ZM~ݥޅ߅[] 6 V޽e޾u޿eYݦM=mMa.ߞ\ ?c!3 ])ݪ bԵZ1$]?Vމ'.ۜ)*~+[b%bQXɑ͒,t9@bx9oJK&,M6g*Fg+Vgvw.xyz`F}).nF^\7VfhUqhhh0h ^FANi^ie)iEiSh(JA$hi7dNj j9أ>ޤNTUܨ~=ګ>NhDYhhh fDZ0B Egӈ-k"ֽE :ȏdolp&΂6Ğ.j.wlxv͞zl{l|&c/׀je7nmFtyk5>SVkF޵⾥kf"f]1qnvqznnw>戀f/qC`R;fovog4\(:H#ȖضA6pۮw㊴֍Nk1np pq)FOwA[{qΒqooUf2V~jj#G܂%l&햐mڈ_*h+_p7iii 'ipphnp`sii.l9o_lsBdžq>?s,iˋf@[IJYm h m<So?JlW 8FNӫ 6w|a/ ~(خM ¨)Q&P>J9Vڃ !4t(7P11&S -J1 KGAKS׬1z0ۄ ?xdf'4~H(1P~ڷtNmi5vaT+A \kk]xU{W~ vaQb>,F@cVek(xYh6⅔ƚklAoY$qes(AҍTu- Ir]QG!5yW]=%g~j5 _Vn NXa%bdyM(∤vᜭu6آmMnq܍mG ,IGrzLPڔi%!mnN e`i #Br:[1ؠzៀ:h*Y1:IE[ƨЌaZ~\bPj"jxO%T-YmGja^Ķɰ6v>VMg~zٚm .(lr@p.XCȮš U"|tWj , [+s,1^1%B#WV2'ۢ߆.f8sp=% =4 ҒK=MN]{./WZ7p{a+1 2ڨvn8xŒ£yJE\A&^[㑋ty.~ыbx4n:?#? gPx1G %_8DL@8.C 2, *m 0щRZ%/{0a1?tL8&3 t h@Լ&HCmv 4L8[bBG+,4R%'voB@ jЃ~/P3a&ڄZtm39ѐ#5 yH~5$Ti#YBNSW9jҨӊԧ*['j!hŦ.XP5^՘jE/5{cYS ȳ6!%5IV-(WB5|-&j1Dܩ={zʟֵPKT2,uTbVUhg%Yx+jUDjBc*BqEܭ$R0mXm HM 03ls >b-vp|װ F7r5&1a kIs1KlH}Q,nI߷W$.Wy3T C8j0+`A75x3lYiBrq-dž>c4;_8'J^r(~*7e-sA P|dرhƥofUo|gMN+D-Qs@%`qޱìb$Qg : ml&/]{FC7Li\[vs?zW.6coXuo~N p3'^4( 7nO*}ch!W"I.;YVan c2yony7 xDab+ x&')k ҙ t2h v=f(2|E9!y~F1K21{ ]H8>^N='"}-;СIx2p>WM^ ^M)/LAB!_=U_ Gy )GI$LGp-%D^_aU1B1`8 EM"9>QQ_@ rD F) @^^n 1!ҀJ!V^d!nQKDaF`ݡ-)E _ݠ b !&"3Ưa!aC(:aBSƑuW(|#8b))Q*:S"K-vD,b_ Z :ZSliN)n@&LJf)#zrihZ IMM|*`diS] iuFr&d\2GIk]*eZP~Pʩ:*Z ǭj*J * +J+!FXc0kt*@kٴEǵQrP*7::+"js-櫳+BֿD:Qv}+F4>lFN,UPtЂ~V 4ka.li*AQlz+-ܖ+B "О !lҒbHTdI-8]i-}p-z΂-j٢Mm~.32.m-խ 8[ Rh.Ԝ͚f.X @lG-Oj^O-RidWnӒy..nb~.80` voj鮲"  `&f+f͙̦nlmþnf‹0H/۪M/'[oDrr8^ olך¥00 )/5A0DHR0JX/֯oi0J p /0  /ݖ ' V+]0sG͞ I qŸ31[u1W?WhP+iԜ%o*V+I]"/2{qiHƱ% ձqkؠq3 !{e"/"%2,wD&'{'{[r2rr 2r+`,%rA-W-sh貞r3s223 +s1;3\.SW!\)5q*wd7=89w9=4;;6[7=7ٲ?Ľ:3QFo;vBBC;I+DmL4`T^eFktGGHEI4PiJKӆKL3LMMSoNtOOU$ut \QSsRRYSsAVOuTUfuNsuQ;ݜ aԴMG5Hг1õbӣ\4]] ^^_5`SSVuN[/hIPhmd(R6lh϶hIvv .6llӶp_mnnoOovp7ttqqDr/7M8F7w7rPi^7mww727w>vτv/.z7P7ƾ{7[LrL2twC}?3b.s4_+G c83.u|x=?8Ǹ8׸8縎889 9'/!,NH*\ȰÇ#JHŋ ǏB1ɓ(S\ɲ˗0cؑ$#mɳϟ@ JF7qbCJӧPJhJʵׯ`>U)ShӪ]%YY϶KݺoIz߿ioÈ+8^#Kתe2kʭgʠCzfӨ5k6װ;Ȟ=+KsDZ5֮a MXdKAzثWƽwe'O7ϝK=C'_^z2^+]}% `ހcʇvVFhˆ$2bb8|y_؟$x")w-Nɘ4DY72Jp)2cdeF9R$wPGht%Zf%GbFdlQiena6i'x|Zż)͠Ii(wV(JJި{cnSfRIìJͭ8+<ñȲl>Z*l"MAkjkJLnl~ۃ0+`4[f+/c+1~wcŖ\t 0v(Wg|ƽzq ܰgq+l)15۫ax;',&BGm ,|4qJg=M BSmHkmd\62m4Xm_ wo=4L@67GxVwW{t7^ x9Ns wE/8㒷 ǞPn{/QE$c% %w27 cx{4HAlDFFˑ @,V: 9>(s$Q^ok$*ͥJJrfG]+e**-VgGm[' 6]SuXw$,j>Ȯv7RZٷv=Rf׻v ,2;]-t[+ݦVvM|[75Z/{ . =g@nrx\`ـKa }d{Z>Xq(*dO0i9F_.f+[Z<#/jn-{㣲:k";XMUl%&7(vWLXyuXq[~9-pas4yu#hyK۝d= vqhҸ!VC˕3F}^%F(z>t4}/%+.V]ݒ,jtI]j`YioK&gX.{svzH}%J8LH`p¸bDY-[؅،d8QqȓWH <8xB8{IKx~MO8,Ep1ҏh~)bHj>h/ָ((I LٔiP9?V2VyX˧"$ ܠw ,ْ]%0Db7&X)uvRj~? ) Ithăݨ8HٔN O)\rXɑh؋gi.ɖ喙= %;9*ȗiZ2DXqɘq)ILiiHY$iY#iuhek!_kB5QC" %[yi%5 *%#V  Ca$KRGQ]Ziw%#Z9pjl`gMs  *=!pdbC;!S&y%Ty'9"5fMџZ`J)bBY ڠ|IBGjb"s$ 2&)Zb+;- |/(F*3j%58 /X{YIrIfHzJj/ |NYtܕwfVsXz#Z\ڥ9aZbcp(Z$ƦBŢ yuwʜy-{2ǣ`w^%DZfs*F៾0+qRY$RZ Tʜڬkڥj16jOYqݱЦ@=R6y\~ z63-M&*EZV>r$W#RG:SZUc-ںGZZhzj*ڪn:8#Jkr:ʧRkbR;k0{F "92 ;ر{ߺz$5G#:bʮs2u3fVR|=;mRgM?:D+i@ \Q^A˪Gӵdkb:f |j+)C3sB|<v%x z ˷Ļ1kJi˹ۥ~D`o ۺ'As{x;)7{"ϋkAQsnx2ҽu늱5"D]ԱG˾0s{g+s\1ܷ ,  w{}9acqװ{-] YB¾b ӱ{p M-\b;"e357d9 ;,п @7nF:ڛ8{<ž(RWFSlL@<+c ppwU4q\Ksulw<ݱ}lbȸû{Mu{\`~ ̼ !+LA,E1:UʦFaL%߻i,MFc*o,ǥKk)˺4\\bɬ<<̐}#0-Ya%bՐcBկ&}*m) $#Z5uH@L}И+Ř}"F= HEJ0ڥm-9Y[]۴vֹ]-ÍT$}w6T|R ݀ $Qc12C)؎m:[=/|A&>T!'1S}=]դ~ep8][⦜eJ+M-v$қf./.4 .ڭJOo@=1wҐ,79@߫߭] `c"O.QTn VNs7dbާ^5۩`ߒg-}"1$~!s*.wN$㌠s D':G,1=?^.N+M+H鬻䝑 -^qM.0a‚-] .ִ,nDQcحn!Þ\Kbξ.4Cxg툞>A>]pi2S2A`ﷂB 25>R\| Udr.9z3vnqɇtO<{dD}"]4QfO$7@&}M.O(4I[@f",,D/FH5n9Bxga^o`x7Q&v3k?^톎Bv߮|ۗ.n8f@uǦ;"803e᝿-i.3'Mϥ?j!bAoDNoDlS`7 - PCfhE|mǏD!yd?TzcIˆdsW3͋XܶOn؄5yQIUctS!VOAnW`EPYv,zk֭ 8䊣[]9s_n J1a/nTc$O.lʠg5lbgС?OiUgݘuQ(3 wot{_2"hLms ^ƓP!CRI3#>骟ϤbS1$>XKPUڲ *8p*L35x:SJ5RQT,V[EUXI4F$7K-cW^{`udXb0X`Yf)fR >ҰbSnhp Sr;AU Op$`^zɱ"Ɉu\3_ix` Ӣ0FQx"qi+~];2x3Tbn1RV`uOVxfu4+Jlx Q{Cu1k5*E'qWVX}Kge6ikGZ$Ym-v;\q.].6Fo=Ʒ}Tu,ٸwPIad녚OXtBc{sQEqYe?dY9D}5hFh^UYy%[:ת}ع1k=; ~^յ0_[oʗXUMs\ɤa>ԹeLt#D:O s]`7lԮf6;P`g[7<]EW;@0=m{ (I|H_֗}w/!:׿ATahsLCG:tI)2  =`#Z Ku@x,ԤqN3 ïm= dٱD&:?YbX\E0b΀!̘4:04L%Aes]wǿq")ȥBD`H52kփt%ÇkڄxMzBDR\*JWJlre)..ve.@6F|ol0ELj|=dT~ 48-R֫7,lfjɤMlti1qSnKiOIrgDO)Os?92dU%j2Ǭ1m\8ADEQ^(ը5zס=xlF-Ts YZ2Mə"iOyS#C9?nl?}pfj2]*X"֦uu;Ҫְv6#2*WfxG;"KHAX v7bNΔ=dOPYbg*?BIhYUPZ&YL-j{LMsMrW&MttgvzG7MIiy?YcIP'=cO7A][ۥed(Wm-}+ EPqx܋&wF4 -&_b ݎ.w'c񾄼81esc@ng_r N"~ȥ%)WYtWLX:Vgp, p65!V׭ֺx^ghόc YBǫ1==uLQL_##IT"UCGvz eYA6!b9=Ò ~p`v%elE)6KZZZe$&LqR|a8 ˍJVOoI@ !. " bԹ8Ne(l)XOE>`zs;uKEBXRsl+ p,w"+%qhU%}0"N|Ei6^fٳ)sR= N?$:;U>r<'.VN~oC^bA*:ϋ&}JP=RV%W== s;8ήK+c[Y9;>(< ?4{ʣ3KИΨ?K?? ῴ?}{zyc@k 9>>i {<<<$"rg&8e(Ve`i.WS-εe26]^_Z7f8Wccjn vf?f^uۿ(YwitKb"b=1WgPzf{.| .e..Z/cpe`0e6`abcN ngƏ@iRinfd]iwitauN)SwF0jx-~~~vsꇘd>Z]h\n3Nc^c`~cbhh^>V^n[>HCfl.l=FNFxvygR>ˎTNenj\mymҶ Nj2v݇FVc hh9ݶ)PFdN!jΕ ~ώ&ׂҾj=N1fhkضo:emf Pa~>nENnV ppx6ej* Цqqqq0ow` WWk Ə #mmC%m'rrDnYO' ./d0-1g2n3F xyTV (|kf؃. @slt ΝNѐEYp7^at瀝T L)<@@wu]c^q&{~ w)$@o튁x ouu]^W vbޭCvPvօpv_hvtv nww&H1wKwYhwx@yz {?|g wnwTwh*'_?ox_?wg^ǁ `? ayc/yOegwyɐv+<v9yz z#1@z/Gyz]hrxvYa {{){?{M\xgpofW3@yVyi`yh|h? _vmr?s'߆|'W՗y֏?_}Aghٝj[lf@x#WAzÐq!WEiđG o۵Sk$|?%7T.wUVWXdWqf# `kE,>g6jhd?Rׇ5gm%8ij4޶6Fi; #',sM$9$%NWUiPDeԣt ߔ9Wyft2,gH;-v{;a^v(f!.J)f):ooaCBJGB$7ѻҫFiS`e W)&Wߙx$&-R:-P|劈nsj m3CCksHJd6C!\ŷ^+'rT_ZUzuU2fz=1XrviPیt;bwe Vs<, NF1YL$ mN.uy6d裋^҄:QZ&k*ڕoCv׻ōd#^_my|{w/p:ǽ{% j'51*6 ɐm&$ Td!FH! E':$- ` bp1)[1&%5n|r<4Mx)֚6ϐ7 A AFEREv9i:sIR=Q!&3*G0 #E0:dC C4"%ybHE+bbF 5Ac7ƶSj!tdH@$3$"HF62MyN$ @J撚|'iizs-t()oOTq+HPYhF3GBu+^qY%DE%hǎ6,Izq`IGn+ UdWî}.<IǂdOa0E*g; z */݈N-k7Vg^/E^n[A.p+0Xn$wzB*dF,wYpg12& i*Y01^i~r<8V wib~G_CE &) ¢=k"fexG\O UoQEż81heXǵ1p}< !84tIƒ>3Qn'3]eDx2d,/owwatfxěXsFZr%!., ȏxQDua `HEYH_'AJ !nBZ#چ#J$$%Ne&n`'v`{!( )r *" F^XYѢ jj>/ 0D!Wb\(!J2264<#}D#%.f fn#(! [N9:r_+ ;,H-c==F bM0 1"x^QC:dOxS6!IdYZ5HU+-}j` XeIdcI>\_JPd?de aA^TQQ&eIS:SN#HRe8V6VWvW%X%lhZc$fh%,Bb-&S6cB&Jd]DhFf frgZjhFiR:ef\&]MlamfOxp>D' %FeM%[T%e>'DVzcW*gvfZgfvJޥ_Ux'mdofMYJ{b_gnn &Nog32~~HCWe`eBb 6hJ](bhvjqhmx(ozڑ( 変0 >gfŌ.f~*Dh񨁶i(.N5.x&L,i2iB & N W _U'l*қRjw.s<2!SI+zGXF:Ȫ*+EuZjh*n*ޘfŨߩ&EĪQ ȫj#7J*,Y+ƫn IJcVkO@kNĵjRֶv |k7[kF+I~>fR֫v Mӿ~l9 )ai:ڝDkT,ƻn++JȎllb+ʪ춢F<, ˜vҺF^l,f~ l"Cf뗱DԞzggT-Ʀj-rmlކز` ާMfڮml^:,x,H-NUn Pn:i.vкf.nO4C F Z-X.̚R +r.芮g..겮Z\nvR.>Rf~//o'41Ȳ 4o=E.^nA&o//o3//aZBUooiooo0.>֑*`09Em9]*w02 *;LC*KP?_ْrÒd_6,y:1^#1ZH,14;q7\kqs1. WHﴱ'Uq1\%'*q3^!2%~, Sq$۱q_+q&O&2C*^2.rqqJ82.0p,o,C-T.r2J20eL5crk2 t7+2S3ry4c4c\3;.nXqk!7{s7N82&zZ]q;ƾ<'=׳=S>ssǝosz 4Ag4"6+B7C33DG4s t@Fr4YJBBHOCDcE?44QLMMtN8OWiPsQWuJ'’Y^ XuU5V.,FW#^ Y5uklu^5_s]^^ct_6LF'ZߵHr. dbpc c kdO6ggUtesB6tij,6䌶fi06kf6nl/m vnvvmp'7q6r+7tGtO7!,NH*\ȰÇ#JHŋhȱǍCIɓ(S\ɲ0cI͛8sI0ώ< Jѣ*EӧPJԫXjʲO`ÊK֫LhӪ]ӣpȝ둭ݻxf7\u LxÈ+w "K.L 2kFX1c!KӨWjybC;Mh۸s#\q /Ax}(_sG^qǗ3<ӫ']{ݽ^^Ͽ|%T`$ ~鷚FtzThafx* 9$o^!X hCQ+0PÐ2Zaf$L6钎)Xď@8d E\v*H%V VlF4lCccYe|op)e/ZR碋)ty &IDtzA ꨤ驨 A!kfjvJĩkj:k&d ѹZlAkvk#:!KƹR{fJ.ϒ;螫.jl7 {ok^b{,G|ݦjpg0 J,rnW^Yqkp #lZCגdWm]vtݬf?K~j (-jS~BQlg۹̭TwޢѰb"Vʍ,e7.ҥg{jwP-Cݵ %鞈^./콎{=\su}alP;]w U ˱jy0ALRape:op;\ž#.1s6*[XβY[Ƥ1Nظ8NȈV:Im|a8#۷5+_9XY/'HUfLfe9l^%@7! ] 9wt~ffz^2':SC ]eD'.=Y 4(]cL}?-> Ԡ5=K=xn2$'f;b$akӵx=__@v={~FViuٚޫ2Wkc ڮ& !bByjߦ{Bk浽-ku\>f/ipOfͤ_k(ptܫ#K^ ^9[wpx9!dx.%F 3=/Ͻ;# ޅ ']>QtS7C# vǞyv!X{>1v.:=~]WㅧCx1~*57&T^옗yi:̹Pf{[!m]@.}G7d>|qzO-RO(~XR"`8 ؅]"%syz'GA4PUHA}&e}#}&x}!)@~Dg~RP~~~"zդ HvA X#8#*13vM-x9 HL;x$}'}Fr~NAaڡc8=Hw?8 0BFxJHH]!SX sZ;\(F{{Hex&Ætvx?|xV>HM(Ba(ey1&6gZXC8MBN46P 712z8AЎ( 1$@xv*R6&hB 3*kb]Wv8 (!Ո3x ਆH8 3騎N#(vh$29=u) / )h1A Y2i HHܘ*A(x~b}+Fj('َk8`/y+2/WVPd3)+Óm(F9HCД2TiW1ȕkA0hg–pi'8)*tYZx92EIhsTy91I895əY晠9*85XY(ri{)}}6i PR #9Xi|I˙63ϙd9?yHfQ3ٚ=ky*y.I95YّW)}ɕ62* JrIڹٝ|:Y`=ء2$z'ȩ/21*35:7j Z =ڠIBh FjH:})k#U B 2$R!ZZ7?1"$1 'ZhYldyk'bB5 $q*ܙ R1ZWC#ysgG6ʫb䁨bU>#*|8-by&&ӂ5Sr):a0J* @2i0SS$!J:9%ٺ׭bѩYf*ʮ3c ZGR!$2Ұ %: *;e/> >JI '{5*4*# 0+sj45/[}4@!B #D "F HJ1!1lD(BkZ릟:^`He gK2[n0گ̊s.uKw3y[%{!#*Jj+) ˸: ;4([c>`rh3l27{ܫ2s-;x #T}9*7[:[{!;&!ظFZy+4eJ˹ѫӫ2UЃ袽˽:{.ۢ t(˺櫷 q[{. ! ak**31P!p\]x}*ԍ)*+N-}|.A(6MW5ݻȯM!`M-#J~.L~N. ~-BY,[2]._.½Ӥ>!#,kPp秒{}0NB(r3~.M(锞H=.OQ.aNd`ꤋ ސ!dn洞l뒱뽞*n׭n/1|N~ .vWXS5pn 靎., AVN`nNhj~l+]1+ 2R/W5z)>.?4'* "#Gx%Ľ+^qD۞i/HJnF)I?5̧mQJbbYoݎa?$#nf3Oj?m o?qtӤy.0_ v!] |7Pyyȱm~6JOR*!X3]'!>e0$x" `ؐP$N$Sbkl4ёK$)d KTiM9? qc0A2-eQCR'dZ,](H-0߫!:oN%s)UEvf͊7} WMʴl uz cկ Ϊq†}o޽Zȯ ; 1Bẖ"̲2ӌβm:p Z;1Z"JAjMrh|78򨸐+$Pb9&㱧j`R;EXjsɟ>Xajogp?^)d/FGJYrg6f.CXs7'qFu3_mZS]lw5Nw\Im|Ax{(Ά-O msf0QoM淋!c'0s >ɹe@sԿd0$!RiPS`;.&`aH %`m@$bU0Ay {O 7)fqiL=L}3"9ΉY4E-/I[-vыZ#1 gHF15oKؼ:0z)[Be/ 1R0;W}*9]Rd&EeI(V"B=  12Bz5_d /IjЦWL+Ae2_~1rf w{> ::m"YȜ/wQͩtKbPXJxNKe?4{ޓ te X _:BoP61t'W~pVJ4`0GхdfWY4|aXϚ)pXv3f7MMNS^ Іj>˝JRTB5_MuUTk[U"\e3[ffVUn f[F܍/# *8A _|T 1aچrecHv빝dltCl>^p*MZ# b"{"$!2TjWm&'0p=\enwP 5u-#XX`SWʋ.}CQ0G^mizZ4MeW_MA3-oQV%d3Lzx)EJ 'uU?#1 OwFcƙq>wB~/j^3c\l[P9 \$Yf0{ ߂9ƌ?@d%Mrf+@u;c3Al?VƳ!q$lg[J#pw2&<3r32?7 hp\)ot=]XQpȊPUPIAJ @8>+{)7e#!/ B@KˋI ;Ƴ@;S:@`+Y).)1~ȉ@  Aړ#t"F9AۉJ;%dBLsB̘+4D*|>ǫ>2ɣ 1İ0C@C`QCaaëCACpDC< = 1@#B C=ED"*GI6$K&DMjDPBQB+,pnjx7.UlEW#Ş`#]tÛE`4VՋAX=k/ـFi\FqTF?TĆGC"᫹;I$ZOLxZ")4 {}@YtS8] =Lls0XӚRJ+J쑑; m X x>`SCpR/6{r>Z3؈7}ӿp8H 5%4eQ'՗ eP,=%$F&ݶ'(uƠҫ,-9/L0e 16l+-:AL#67ӼSS' QQ#8ҁ8QS@ Է6%DME5FGuHm}ZP -!L]#CTlt ( ) TrAU RUR^W=WȊ02'3;3^e6S j)deeVpV?ViTjB=Qtn%! WW,W=E"Ԯ*% wIW1WRRyYUYlWK X11Uih 4iXx_XΠ X"1Lk>V@VjնkV'd٠ѤyY%W{YH׹4ԅiP݃Qu)E-U e.-/uZէچh<\չ؈ ։֊-֋%[%Y Tp`nk^QW Lq =`ޚe^Y]&aFaVa Oqv^]_(4 &m6ߴ!jZ=`E\&v1()vB+6, be`\263_S 6&]x89^]@FA.B.CNdOW8bx`J&\)6\*LMޤ.`0~Z܂* 4Nei7vngXY#cˋ=&>Vdea.@$Vf IfnfL`M&fObFfQV2qrF Mt~ungv~ggֵ;6\eebYD&cf=΋nh~REj``PQa^r6gIcu^OesӰxp*sf' 򙋛8< Ҷ{[ UbY8!0LQVeJQ1ݽ82AEAW ༁-e"%Rnmm~jX/1)4UEm-z')v뻞b4WζYT.>.3tY8Nɦ웰lllϦBX&Ynm~&fm7ޟސ䖈敽JmڿnlfVnPlƶ B oo6DPފ~x(9boئm#p4p>Gk o _3]qq.qo lM솀͎ qo?' r!ce֏Z&wknsrp(prpss.n$nXJY8gus0?t0)CplwEwt%t.I(LMtO/0EuV3OцPs[?qYOZuL0mjr%t)媢X…Q,RDi umJr>%`zK$%iڴU? qȇ& ҄ɦ(oj1m(4WHҲ/>heeʿ$7:2iĞ1Nʃ %jRLZU)YmUװbu061n$,n_{'?R`%'@c!Re5gU%ZAƑZj vaA1dSqv!XW)_m$F xc9ȢYE(zU)bJdTѱK6CERPǀ}&PCe!o.uteiIҕU{ X0hpr[!YQi:蔅F!*(`(H.n'!܈9>A4defTKѤVd$%H]]~V9i&Ne)]ۦo*ugP{  (az*e"WD֢/rJ6*I*ejK&ꑬ*NΚUJ\V+,him*S9lAgr .)U8鹖Zi f j񻛿jp`閰+XGl_ol]_쪫n Lz~ i-2%2q7*.F4ƻY=x-8HH`ݿL{纰Խ>lthf]s5t,4kSܶo=;ʜ-`΀!υy+xHG^Q?}[ O9’y袓u:ڭ6%!{hb{=N<ƧtǸH&?&_EF0Y鐸RH#xN$0\NEPZ [$ O)N6ύLW^9nj,i8[·AW҈ DD21j_Be638ϔ$%=mܦD6r?(8CT fT:mwSgYni\ ^diIT Yă& udCa*FŤE7i6NݨGA*ҭ ͚uҪ֜+[zk 2GbXJji+ @LX1}l"+F[آlلuhCYώ mh ۝P}h;Z^ \- ժueuYֵƫpݒ\KTuWy5QIZ,}a 5*6l_YZPfcԂV=-{9R omqnk- lqj.4sb醁֍ib]q%;^ʖJEoQ^cX|Yw/_/d 0p,> ['G tIע$w)ڊxĒ-1RNbv-~o)B_V9~vQ jb$oNIygm;Ÿo={/؟q32ܓz} [SIV+8o=}qڝU j ɈAj3D  SEoh p P lЗٍU_`HVu[& M Π ` Kl!!ΡEaL!TJLzAz!"& ak Lҡ%!awa a"6bi{EfM%Ƣ,NT'f"hx N)Z$,b "iiEG͢4Nld-E..a//FV0j1D8 c(#3ڄ34~5#=Va [|i6'F:8 c9 ("ؠ"2 a(գD=Ja>f>P?c7H@02AB@:.CƈGEvIF Q?faGhVvW e{%eZ$[&eMbMj6ҥf٥UePIel%K&a&h`I @&d&llGFXxɀnfm)D~Zhg,fQf뜦{&kbkk i)Dnn& w~gF 'a(g0gs~sBtRX1Dvj'w&x~: C0 ,箬JglΦL&Mvn5~F~Nf ~ d2F'Ҁ^'՞h~RiZ腦GĆJ{&huv'F( j([rW*ݎ.ih(A(NftE$2h hT\)d)p'V)@|%iz2RK0 B薾)ʩ&iJU)$j*b.jDz_Zf6GmV}&**fj+Vj +~*l&f+F+Ap@Lji D.!븮++&aA4++n+v~++zAkl" @6V!,NH*\ȰÇ#JHŋhQ㠏 IRd(\˗0cʜI͛8v)dȒ@Q\2ѣH*]ʴ)M}~ThEjʵׯ` B(uՒVb ˶۷p ݻoZ Lat5ū7o_VL;kf1_1CKL23oرТעM킪Wsѷ 6n+?ŭ=xlسky͝?}zuo_Ͼƿb<}W﾿0;nρΣ4;FDC`#Ҭ,`3?@q\J]}1t3`b6.h;6sM 8mWv|#b >8 42`l@NݓSJߗgy6wnRm8cd: :#sw׮n8v8;{NsY#Tog_/|<~7K1WC=C:`~3x@zIe~(啯y#C3x, 8͐p(L!WX )ǿov[;AJ#< BL]ZҲ (@! Y]K6H$@R@2)OxHt6v{sh*$&KJĒDqU"Eq_O 42MR7rNs`(rϨ?qQ;lOwN1w|OAX}*2w/yBb89=W[<=$a'}4H7@ǞN=o/n'_%8iO|H~ G2g!|ܲ_g<qGz}zwwwd%Ё~w{&0<gV"V| u||4|Hi7;// eyjz W(8wt t~]7%h}W'҂.G@284xN6rRsGm1 QԕiF} fw$ڷ}'(w)Ѕ{{*lhdK4E.b!}kAga0yq@qlՋR/X/ٕMXOrXdF#^&[~"0Ԉxgؘe0 n8šH6Aws}Њ"cXXȋ8a`ĨF'ؐI8\h$XW٨%ܸ08Xt"Dӎ9 qmX$6)yJ 化hiX0ݨj"9$iєXĒ2}h(#\'C FÂJL WNePɐ`2TYHuXّ&H%(lRjƎ?bjɊlY]08=Ht)vyaxًybtY4, riu5h٘Wᰕqv`IVə($3,Vx490g(P k $٤˨~v@z)y=5 Z iӉXD $ؙ +wÝ0ԄY)eؠArw֞Rw'{7,RF؟ p0bi=xՐ)4cC z*z)ʝ á> )a@cP\.c0*2:YBpY{i!jpՕBDj/GФ}X0ZzF\ʧ05`6Qiih}j$FצBr$tj4d! *~(J{J*_aT?SZ() '( bZ~~&[ 7'"Ipb3])ndÑJ'ê|C=&ڬY0vBDөdڭ k*,*/qdyіMNƮFJ##@"+Nѯ>t T*VJyH,]* k w$V;,Z"Zd!;SH'+ji0* M8$ [Ґy&hFP ke2 *zLR;teX&qdwd0Y۹)l7)xکו)@ oB\rn4; uC+x3t{v{(}[r;I" ]EY[;Yy (: {"")к& #9@h{K7KMX rkE~ IEkw6٫(Ľ[he5:pUk9[t˺%뫱k_k: k-{Ksu;$K'd SňѡO'hU[{h-$*s1 nka([=l?Zkn4\ L ,mL,N_v;sz|;HXLk c\Mea$X\lnI…)5a<P ;\p A\;ȏ7N<$)TVɛŢm/BlARe<˵l ǹL/|Dž p%<;|:k̈ l@ׄ!D}byԓeLMN <̉BMb՜ ђGa={J.%Mpn] )+]u}_d":9Ӄƅ09>ƿ p%ٓ-8N}ٲ5لZ=\]LJ=:/Broɷ-ףLlÅRթĭ-˽U5 Aҝ.mgUYڽݖm4U-W[JbF-! #?m!ңl]ۧi H6% >N4Q'M~׭ "$&ȟ=JI$o+4m}8:_>,ȘM'!}6lcHrku̼WDO{e kk $`}(67K1KQt3ADZ!i=Dkuyҩ+U Zs .< $zQ"X!-B1Wb l!6!@V뉭z؞F.r0n ^?S,+^HK@T[du؄nM$ R0-,6Pyڐݱ︉^90s ~n4>^:_}tJΎ~ڗ5/ ឧE*<.DDOzFH?L7hUT?O%m6xac2e?g ž'SAp& #$_Vg(*O`:/@5b_݌/=$>wg('wbX9Z?k # W W`*=`yM8p ذa5!)R4QI&qd9pd"YMv9ϋ3 0IzdSg)MeT@=8lURWĎ}Y#f4ɭǭPݺf?dO.aX Lï߲gߍxgğ n\xUfk7s 2ETugwh`…LFǹZ9R$QlI|qך6q乓v=jlRNJj(V[9x>X,N6j.+̺+U_/oppm5W{TW} ;7_rx,]lyαs㝛K8aE (njӼC;{._zS;/`U3<m F M<1OeoͶ {GPt5e3f%QGgi~XJ@m4)ŀSfjOֺrC ^zKzһ`r¿a>1 7v{^ %=2ņ[]~p3dpG'=.vQ<,LO, dhJRR">X'<>Kx4./^=F h! ,Jf ̙A7ͧP+b@Wu/x6@2ZrkO6,la*W8ѱP.P$)-* 7]ؙ3l'%)bQTq~+a)XRCwK09OziL SiAg3 MHӧ80EMM śLj8I΃A%S0Ipxh(QS+Z_ISMw2̜4Y*wTǬ&R=X NTv'VUpգ^}gޤnh\rІdWՌf6 (a;^5)8P*Yl.}[z6%P`lhCvK̬3e FT_t a]ުY\'%ApauqF`̐эAыqq:q\IIJdo2#{ dJ}_dBRh0\ބ L0{d flעkmn\H9pί$q{L>㥟*n+[C ыVtk zOئyw4XWK&juPa-kw=uӐ]Gg-aA*4CbhcL {Ap&pxnĹZۢ=7.7R5 W`ZbKXPEzA}&@[8 j;(W;M Q;sn37Lm8<yBno,$ꔹ:Yuu9}/Bv.|#iM{ƣ*[ mD7yd»!φ">yTgA L{=CRG=뀬)%4==Y+#==ˁ+ࣆKa3ۢK8 qr#룶>Th>?c򓡠pS?ss ۴hO3=|77k=T c8˲={~d19B+#8Hlx>R$![‹ě9:Ahܼ !̡"l:[$Կ˷k՛{+8+\@,2󽶳12$>5@Wa7L8D9nS;$Ytԋ,0#UAdzSUeUxrj`WuW͌R6X(XEX:U`X U؟ئeXʀXATϒ=t$VV'u=WKWvٜveVPxM!ekEפץX5V֠ڃu^OhxnX<[ѼX4\|tV\!OmԔHev-WTۿe>Yu #^zm{] |EZ6PZ/UķZ}TS9=G-ԝXdS]Ӝ]BVC|[-؜]YIuYJYԾYMepޟU\^x\\^˥IZ7rL_\_ZZ _(ۡݍMێ-T!V"E#ERPݗ[(ۚ \ v ^݄aO_'&@pšh8c PL_p bm[ؼS& 昇M&H3IrY;^ A+!) ںAjY' KM+T 6)`X](r!Xc6vc:KccT A.9tKdhGH>CYdLivON P/QR&x.(bdete]塰[6\ee_`vS#f91f@feV!hfyfZa kl&U5@dPgw=BgPg`dWyfOnPֺQ VKWvXfY6Q[nhe剆ኖ 6-79h~>ݗڂ9iIim!n/opfdgddu0yz{&1eN>jjj$ ڢ-]_:.Dz6fk<iʒ n ^mnn~oNU†Aîdľǎl^>Uɧ:Xv*Xh~WAU5bٮmh뷖ff~@ln&n6nFni-dggigr]h.^hn~q1Qזmomoo=f>C?kp>.E^޾n`C lg@ f^qSjVﭲ%qa~6neVk f ̈&r6v[]rpp~iS%*+H`G8O1nXIAX 7@oo62:;qk ?htAmcrpp$n%H?Evrrs tٰj-. >uN3WuYOqZu87mH]ߚ^X)i8x_9{Bo6AB3k;cnr`pHs6L-=w0OGwQR70wal4wXuuYh#^x4#L2x?x_&omW˵6gtwtxxJ(7OyO_ POwXhG'wy@ hs7G9'z7Ozg_fox3$tlvdopwq`\y-oW2= zy _q? bx/Hreor"K Ng%Ơ7v} iW|& SkXs>ۊ_cBg 1!&hqƌ6r#ȐGvb$*l12Ь&Μ!dp B-<4T9)SZ 3=fd,ٞ +CiK2bizYsk7n.ШQ/ R'-77ËGysW\h^v۸7#oʗPDM]RGZ~ +6Yi׎iܺw{/q ƃ8b=]mE]PfKqgMFZB9jikM&mtPpDJ6Qq/(s@5TQG%5ch]Uxוyg$H9eZB'>$]WF7lX5V ngQXl6e)f)brG.#qJ ^HH]TugI.BPF9U^֡q`b$VyfbiYdnyi‚Jcz򩡟ѠgVnd5N~m-VtSVDJĄ ظ(꒥&Jc]v݅ |yL3-0&YY+fkȲ96r8R(-x&K؜ !ukS=$0B;4obX}{|KVXqR|`y 1ǘqȫ9 Ӻ\R~Ms74-8@;rI4.J9PCߋoWc/=Ⱥ Ff٫}]4~g{\0nZf8Hy3ΐ,jZ{͑.:~/ժ[u k}ӪaMm y f*DMˬ7 2%>3 SD\07}_~{%!lV]tWlC':Pyw9˂,!*fQ" Ere:(4 $Ʌ/ gHِu\JvL]T"Qc`4OQ")IZo \kq #81R|gL A6q!UwCmiU?CU׆osc"H(>Rd%GLk8QH4&8S q l'r2*гA|>9})@j)F3ώ jxh[2>fy -ˠ=h d0(JMAXȦPɎ4+MEKZRpf o3'5 RpK乤zڳLgb>P/)si"fE1:b H7S+ul`*Sv8kxS2\BjQYsoij} O̓U߱OWi&Y(G(n\M!׹*v5׽5`9SvtM)b˽RWR gd'ʲ`4 TRWm8J{ښMXSVvIh1$V*toW"(=KK6(nJzڽRE%jvJY<֥s*x٩zHRЫ^"]-|;mv#m~ۺ_Yi>*J$a3tB2Y! S9AdS]v1;bx&6'JZ֬ oZ_8>o :G{d9ݫtWIc̙a˂czlA2g:* Ѽ8^|V&kͱmdWd5 da[F W* ~a:eJS['(ےi.sy9>wQ|jT`5XӤλtFIU8}xOlƳS ߴkF^&*_y˓2J7@@e)6?mL?zJ^!ogNCcosOCgXtuk]\{$Q<Ɏi^k8KZeZnLqDzDaLF;Ԡ ~I`l!`%IxDq`6GFa@ @nv`|`5 `C   G`Ġ  IV*]!`H!V F֕b0qaRbH!4EO !:FL naС3a ` " +!b%b-i:er\3!`q~@'J'~F(f(@)Ɣ,t7;I"9b]xeG$.cu"NI##j0e1#?F2pA>c4Fp(aGd#ldx7 A9c9GDx}$H: N= =b>>#:S@!AdAt$$,FC)r#EʂETNYd`M@DJEQ%M"e$Fn$SST%ɣIFUZFVGqeWK\,EYYNNgeH%EN&K^^e_.!`O fUZ!V"#b.95&1ejH&>T&BX-VNxeQ&iG^"izdj$kVgkR/l#m.m"n g&pzpgrfE%AUi.etvtRufvE!fw^wfc'oD&Y>czz猼GD.'}Dss~5' ¦vheSmfxZ˂L"NM$vNh^q*ghnhڧsf~舖J<)>chTР* Dx|)PMh6Y4б-fBiᔢZ)ah {~i).!ΙG֚zћ.jƩj. (G)*sK%1*i^ƤRjȘo)~NYR>3>jkj}KjҌ.ǯnnkTZ%ߜ5k=kJR+*I +<&ɣB*C#关f~-Nؤ>l&IZ*l ~njlB,jĊ>^,nClvlJɦ,NF>EHȀ̖+͞+⬟GG.,lnmu Vњa~'2-ڠ͂r$~r-j-NJ٦-ڮ-m~ےDn֭VDT bzFXE~.zZ溛B]k趮VDZMm.jz nԮE4J./V |Vf*2/f/|.F@&:o 6Br&dOz.GTw@/,B)˄LWp.60<=oDw[- k;06. 0 װ 00011'/17?1Gg\@!,LH*\ȰÇ#JHŋȱ C&I2ɓ(S\ɲ˗0cNH3@H$Gɳϟ@ 5ISѣ*]ӧ=J%CX!i:ׯ`ÊYQh=*5jUXn:ݻx-k6-ӵNۺ}K&ܹ*^̸|2E2`:{.lqǠC 6ɕ)_μ׆mM퓦M0k¯aMsZΜՂ[uسkKΡӫ'U}G>w(`$ˁNGaݷ߃FV 8`"`} '$W.pء(h_4x\N#Ţε㏧Ȑ&idH& c.4/P6̔TiXc;ۏ@ IE&$N>uSS$Y)g^[vc`)C2BffdMB鋔TZ9碌zUxghj(n*觠hd^:k曡*+KzZz矪fl& ;کl,{NVګNVbm v^(kB-Z`kz+'{nʮ;s[㒫/xD,q׺+p{p 7Ʉ,K1Z o,Ǻ;1#"Lr-&*k6vTDHϢ6D-Q`bd5ceM`sdQm6h%"EK3 PSXku)}6c `im6q4ݍPx߹ ^DŽxoҸ.)9x|9*m~MX9{}wЧ{?&t(Ĭ_m뱟4F5?ѧUO}b$} e ЧR}#Z7i/j{? *zp!v: ~R+Q:$#LFMlK^0>od| ȁZ]=.qae%*`3)r$)!I8pr[ԟOĉQ0G!axƞmF4Gn dlg rGk t\H5$}ӄxg(ƫq2EP|˓&_ Ixʜη9]|Zgzν~#Q]KUjf{n[|o85@ B c]h5HJ@w{r) o[X!RAH| V9'/)H&+Hx|2HT#yZ'JC'VH({fxI!8Wx {dqGxc.Ȁx҉Fxi4kh$85u;=%5YUrʈX~J(Hǘь 6(m1dfX}(8X8Xjdž(7y8 C}U`/`S2?g$L'9t"oP!(=>%9,,FN(H1$7\ٕ^ɕbb9G oX b`'ir)c9 Yr9 C9PpBA;a?SK9c y95ŗ~IFi'Oi'Q)TycVI[A+bad{ci%k/m(g9t)阶)~C!>BxeiT `\YhKé&Z6yRy6X ɖGp&&)uٜw){id1yb.Eғ˹٘xٟ)X9#%IN5I.bi驞P節 /FQU+bi&Gw8a+>ǩ&SÜ9Xʟ<>ʁ QZ:R:ff!#J._''z),J.j|25J(74 N2T4A_B`bU j2Q ֥VJ[]%qI"e,gJ&i{-q*us*u&w {ڧ:z+#:SJ&J]TH6UZW\ !`q$Q4$hzH: 5VoiYtz2aRJUs72B: PŚЙIҬJjXZך֭1{RFB_)9oJ )BQaʧR* v"CJNe $ x{zs8[nܪTdy Sk ";$+f(tJ:A.0H5 @  ꕕr5ypޕX㴘*|[q{ $[{I5xӪɮ1j,[ k۶$p+MKK+x{z'| ~v1c ia.4Fz'۩4USX;Z;5+%K'+뮑pGԸBj3˯+ᯭ[!;yە{;1{+~{"‹,ī_NH.+ϛV={z+5KN{ca4Am+h.Đ"x ~W Os?m9@%K&t`xU-]p  9@^ 4$JZ}YB*eu437jO[aoN&h493\7 -[,um+\ C=EmHYLawokVUR,RőuŸŏ9`"}2ƳZj7hl4mo!tTadzi-&Ǣ ÑVȐsϒȽl, N2Sɝvq'?Ő3ţ_ksʪŬLa zSpl8o Laud<ƌ[|`[ǫ={),CBm>m@4BnHF|5`8B,l]f,iW$3KƮyN(FiZZ N)-%~ẂhA4Qу1 H 9+8ռ-\VxGLHIKs/,DWӥ|v;]E?AQ,Eƛnw i ̿}mT}VЭ^`muBX|֍ ҏ,ґhr҇1Q+:tm68}MGqA V WضbmٱٻЩE! ]XZ]qt'( Ϭ}z,ÂaZۍ "q}ҔӞAw$Q!.ݱYS)y-)%-sIbطݔԍRw筗גv#*e]{rl5{}YGa#4n*\Ur45:;Rtϝz=!٤-9(.*~'Vު*6߉ vӁ㰍@!߁~RJuLpN>PRn5^TC>"7,:$'qQ!#%'O s ,-/}>>Rz^䂮㭽N &$<kcY"rm/]FR35 X܃nc,>g^ݱ~j"^ٷ۹ޚ뿾'6-^a>^An߳,n^m9͗yט>N '.^e>ˍͭC4oeX΋N&5qA+m y~7Ά9/O^Wbi3mEW.NCrCEoG IKW{Ja+[H2 { 4o.#7-R ~W|׫Ei_.C]9MJ56{H.b[:V\l5] 9T"b3HdUfK. 0(iQVMBL,k[:nYpwqz[ܯlOc S~+).D:isH#k\D$^5ԸV&f+2޸c_ d͵d^9n(VY/Y̬D93{變vsz]zozjx"WϪK?Hny^;3{sIų;XWѺ6p -U^W[UDtD#)d e_DJS'.ZYME}1;:.j z Hx]٠7A;ܖG9InÞcu^hS%|5B*YG_`d론T? P;,hLZ+AduKZSpAY)< Z"/8{j~n\^%<㣊("J4'FJb$$,jN ,B0<#c޵5he'MwvPf>P d,ޔ.ː"=Hr1B~ ($ttӈGtI&}2O|(:SD7SfTn8D]'X-BӨ5>50ug)jw,1OG.od[YlS"MG7WB:չMoTB'dPMP$_.px5R//#ea[Ii#XMJ M3RT$V` hRXc&3ٕKFe-+dC 603R-RhW`-Nrӝ( Em_Lj;^U$|N/aۈ_%vAlÐ7rc$i#f7 +R$:MD q^ѥ]6E rYR=.da) S< ڌ$MimTۤvIMkM_Ȗ{ۘr,v?@pY/[B7}vT⚂'wOl/( &/0^~ak-!lc),fD!VEq2$.)V-s!NWV!UHlb2ɝr;ʘ/ٌUlݓw"dbw$E> ƷPw*ZYr0g)#3q4Bfѫl's|]q@tr]ttR7CFG]b5Qե2bi!u5jŘ#|\z%Na;is>>a4bt:W,os [mR+WˍG:y&rEJ~62ّFV(?|_2t^-}魎p& <0 [ڳK"0{#g=#{>71Snk9p'1w AAA +!L\)B;HBcBs‰{ 1B$MBJ0\(3C4Lïr6A.~B8 cAi@A\ B C,F@D:bElľxDEI JT KL8[8ۣ'6OPD,,E-TU@1ܔ EXl4< LZ$|/^CaChcd\39M{F*K:i<ȋF3T16˿q?M8NTǕaGw8Zyz G2K5\>d9[;}<.]䲄dȜ3b$ Wy dQCߓF!U\ < B 0j3/E9#H`H|8M;߻ 7 (@"$\ʰ+ʩJ;JYLt?N9iCxK֚˺ɻK\ +4-Č@l̦{LNĝթvzbδ8M!uȪʼYJ=KMMJLTKgdˈp/:d N˛SuIϬhXx۔NN/P\ ωcMO! d>\/Լ=JMy+dPpP4O̅ 5 V+PοDT 4ĴUNNB֠`#̃(TCуQJXOήL\hҏNS͵t̀P S/50вPP}R5N6qr.Բ9M:};ӞL8OQްF1C.QCd̽-RI}MJOKu L%KMR:*] ĕ,--Φ * S4SDoeQzӔS\u^-<}aN-@ULVe h-)Mc:b#FУ%KU{mH`3arsRRRSmW u+U%VuU{=Y|5TL [奀]QuEmHlE..9JbU2eؼX YmnMS%,Dmٗ}5Sm12݀3M %ZףMkռu|{Ԕ) bc ڈp۱mѥۨe "Եeۣ{}Fے%lлM][v/wYEW­WÝ]]>(Z7m\SSNuZZU@̓]̮[%[10ݣeeށHYݱݺR"ټ- Eտ5x=^Z]^^8=eQEF^V#>a_J yzF{>|^vn.Zbf,Ƴf bxf6J 쎛X6֖UEIhQn(ibgii}Nl"ZAJʎ"lE|,ބe6Vޒ]8mhH6Eym&7>i~j9\tnggl&PfnqbfI2& ͖pdfAFC`VfEӈHX 8;.oopk8iB>gNQ$~Tp,gΞ) GT+sohߏm .ѹD>"plDKOC-'j(P0Fp -2rTp 3O4pnh7K9:XMssΫs BDϹoxYS=K~rROOP'n rN5&1Ǥs1o2v6nnW\^OqF爵6Gm =ljVB4mQmifZjwwV's\xZp|ws}*ȭ Yinokvv ~stܼZ=0 +p O:'1!1wny_ynyڎzFcyfx&x6xFxB6_pylxI著w7ɮ{tg'/ W gQ{4F?VԖ{IG;_Gv")zh/|9|"oz#8A>rշ|/*fΞO^& {rj#asU[, Bb *k0ZCȪhqƌ6r4c",!G"EjGF2g:N[:w)pŋ1:*jԦ2gJw1RP *֬ZrZ+خbǒ-kV*Ubm[G檋^Nz>7V 6eĊ 0nL1䞾,樜P3gҲY;wB*mZ?%B6!NTVY)72d&~e2:ykN?olMΘT԰gǓ/oشk۲mFusͻ_Y|b5dIFee#Y5-j k)mQnGpq- s7thu w^T~7$E{qW|Շ^W՟P=!eƠA(!aס F$m$vo)´J-r1*5'TS)FZz)\!LeMxAy_TZX[r`XQed*8&tnB"Icgm7U"+ ! Zf0ƈ(>P9I d{G,BMZQNi*`~*Zr `1,yri6& d,xgJ͊o"Y)jbW5i2Y꺵I}RGU/RRױ k &rҜP)bWRIvqs8$ߚHq 3u o<=Cs$1  5 Op5m"m1p^3bSKRfSmn-g7.r;nA /Nm1FZ[l\~εDenA&%>|{c{6Oj_RY ;(Ԋ*)?f6!ffΔ6&NsӾVZTg2Cg,iO3*g?iv\TƅjE+֬`VYj TݫJҿ0LUEӚ6X,csBdle7.5Ml:+Ϛ iT9bx/|miK֬Ygھ-o\ߞ)]i.Zƴfs qJw%ԅuqΚ(TJ} ST@5UGUFv |+_8җd+̶W(,X(2 փkSNVpu+Y av3Y>QTCbB=-Θ5. q\s!z,E.QX:,/ !re 7 U%FaUF@t4(4g1ԹVwƳYA =wtn\r ޯSNN+#N TiS[ҟt}ziLky1=xӌbV/է6dd=ZWU?+[vKA8Zx6{jWخ4qmMb w 2RNUjά{57X _)8a ^pJVhKŰv5.Sw1ǿ,Y\ѻ"xɛWU!8;sCanweNsa;WtñzЅnlu8"q28_l@$YuPNfd7Fю}]Cn;Mo7wo.{ >th329yTzf4_nΣV 5A۰kz8^xO}M3`5AG"F+{ D1Ak8ɩ_>STfWGL \`T-9\+_j{.`FH QVTb`%h`p ax Ÿ  " w&N"!zB&\am!2uM|aQh"b.Raa\J^j`:E (R@GE"p,b5bG=N8$%jN !v"}Y2)z(Mb+V4^Ea-&-^XU*!&x]`0 !("c,<)rQ-#͇5co"-]GQ-6`#E.Sjz:<$"eFAe$N!eA*%j($<0FMe(Z\VVv$WjWbX*hJBK%C\LRMΥdP]n]d^b^B_&``az]-e26e&:EC6c%dRd&S8*ݦ3Gu.zqVHDɬ.ifʦtΣ ݦnoozprp"!qzsV'x,'W5rasΏN'|ʣyJuRvvv |'gyƍTPdS{{'gg~v LgS(99s"snh(ǃ:~Z(AgqhWthx|"((XM(!nc=i(E֨R X({Vi)7 i%'BamHDA&jœBfT@rifҗ@)&b2%Wjb6iv֩~iܞ6>i.^**/h)(jiۣ&Kifҋb*Nr*tx**iSEjYZjjlv**"D'**Fk!_m-сnZxB Udjkz˔kFWe++F怽j,r laSFĖ¢_ n,:lu,ɒlREÖ,N@ʶ,l֬͆,Î,M -&.m!,BH*\ȰÇ#JHŋ1QǏ =BI#nj(S\ɲ˗0cʜI3ɍ!sJ"Yf͟@ JѣoHHJJի,2T`ÊKJ[E:j۷p"E+[x˗!ݭv}L6yǐc&ֹd˘3ky=BCӨn^ͺ5ܮVb ZtҦQVRaˎM۳۸1 Nh^bo^yӫ_ozuس{x=twK~uw~_sF(j 7̆.XL4 ~Mh≆Uxq!8b(h\*·a.a H7iXԉ=c”TdIґ\vUKhPHe@bH^Q`$vd:SZ![49uus'z%l)"餂Ηǥz虉:h*jCR:rZ饚h ɨ@î +,#k,$qmlB{뀹ʫf{춌(,9A:{ZC& nϖk/RjnF2okN/3[s ڰG|lcalri ɶ,^,."{..~×!2'{Wk; |G }SIX.zTǺ`u Z?CFqA$^HGr~W {BCx"C[! ה)qy fJpJ/CwA4hD"Q Y"D`3h=Mz9M5(1>c4B͸ s!$ 88VHcEІ!ihVHzFxb+Vǒfl$(+,FK_ aHa~7I.j{]׾I1kcȢuj[3;׾@2V 'vz;%Nn+[iVp5\\q=;W)m%ՉSWJ/nmĬ\u5sx?>iכ ǷHV2vD,=QՔLSx`e?u99_N^2`ݑo| :5r`>~teֈpS5'r9!]^)Nl3 gs jRAjL!&_z.^\3ƻn#_ }qi9')nKmI[ն6mߞqr5n,c&cv[&$I<}?wNkںqoz(>TYZM7㓠I[zЇnu0Iyu)]U(eXNu]N&Gw&*nZhV*3//Ni>Jas$H Ѕ^t 7&K7`!D'Ztug=N퓼;LdR槄vG}l{ɝ4tw}w1}|(x ~_o+~1v1C}c] z=bٛ?4xj>kMٵKgg|Ã|?P R,Kej'Muh}gZw2wz 7p ~~Ǒs yPpG7Gc&'3gjW}(q1v'5v'z'~ȁzzws%uA5wR+.kgEb:t09MzRZ:TZIN($=^rփ: :y)ԢkjqJ$C:%mJ0:SꬱUz SZNcĭ筞JusJzR׮oq s*z4 zZ'= Jf)xR 9$ A;~,j}?#{N%Z'٢˲겱 416+sI:ڳz%%tAVYҀUK UJj4ۊ݊ lבmzLZuz$jJzC:qn;CNIKJR(w+$ķB2"m"BaZ*UB[:[3q1V%:l B{ef [ὲQK&=-7결Nj˼~R+KY;uaJ;o*hf,&پK,]A8V`$T . B<"@aL'Q[Llٛ!ʽA+r@| .%|jl5C-\/<!sK;d7ùۿ}PƧ0DL,F=UR$EL ^0fr\`|R] g\廲ܲƬ;;tv4x=,ȄlB\<ȡȉ˸; lŚFQOTR̢cl,ډ-2#pzU[ U炚;ʦA%U{1t6 ]Ђ̂'6J̰giB; ɂ!at;$ 0$]D9A$AΤ|,{Nrl3Tv%.աyڮ--]BC%<+`S;ͺZ2SqbW'Y\N=`騅fRTmLINnnp>r=tnx~->@>A ߊ^[nCL.gE1c<[n M;nݤ^Z1gi ۱^z mM-g28~>ہNŞAǞEҞ . N Y?Wᶑމ9oqns-`5d޴,o..;t= O:V n^5k68gR[}ynoO_H9})m"$~ 0tOg n/ ?6royS{˰` !>?LKQ S_WY:/`G3Xplpo 8w#O꤁5_Zx/AЯ̂ #XA.dCzXqE4nƍD $-TdKdQ&FtLϞ=$ZQ&@SMgYUWf-"[6Y@S`VVeqv]$ /0 &at{V^$O[k5)* mTSE!]ziԖfkKmܖ-c&0D!n 8΋"?Tsb-^vٵg;U;9s&Ƨ>]{71W%Lh/*Sb&ki@ebke96t5lh: P " v̋E4 gM45D7a|;H8&$nSG۫Ӳ骣 ûb4=s=#& 2@h ,ͨTpA, B )BNIPEiE1EVlQ՜F&GdCRG"S54$I^{2J䦤r+JtL1#Qe1pTdMdN䤳;ӳ>S=,C!ElGSU+ 3-j;0IԞEUUazՔY#yӵW`7bI:YeYphTK[nN;Sr;7tyvfQ IGMsT` `3I}+r҇wX+[UɎ^0y{8yxc0^9li Els3gv9\5Zqo- jHŐFkĬ6q`` fqq:Κ7[uni+Crp w9fgNXqSSxsO]v;s}*,t SZczvhl3T;wm0+ѭx;^Ȓ7%yMATA9)J6rMۓ{qUB4tN}`3##QHiJSԱf5A&~A'HB/ Vd#V :Q*52Ւ& DXT`8l! E5K҅+0ԓ GRO.aG< ;q"Q HD'KQtX+rbeE/aDI;3S@7±8 ކU1 Ch8?3/ DI`d~ٷDhS%CEYTF'HċrIQ9A]RxŋtFwf!^9dIΨ4 mU2%GԌ˓'6G Z.fBKM0lO 3r^4 K+> U蓔1)ݕJ tAF*KfE,Cu Q^EQ5n]vN 1x{\iG@.˖Sޒ>CN!>SJQW24RRqNXW Kk[vaiIDJV dZVua32׽dYx^־ZdL 9S6ScFv,Q.niGd(T՘ *ʂj\hbU+|E,/XVabfJ ҋd9NYX#U< 93_w0}xGdrq[ pC|6~/6F tvCE9ysoYO]/6u{=;ee_9 ;,:›>6 "-;Ћ0'2c+NU8/>cS+;,Ly!.T /\ 0T 12|m܎/B 78ԍE[+ ?wx\?냩RSST[UӅVKD+B;¨rD%8&́'D >đ E(E8EHEX l95\bZE?:dUpA FbzCcҳ:h:i:j:kTEdD$to=frEKL8˨v+\<͓GpG{ì09׉>0= zdCKE0909|^E*aHcȣ-Dң:B !EFLB2HpHIܽ(BvDw>xԓ|ܟ:{L34,.Hs$|D7ܫ]\Ȭ4:y#Ư4ưDƧTFHHSFkԍ˓<\BaIJuĚ|´̏ēlL+xL4ʘ;C4cY/`ϩ#5G&ƆY4ۋ$;Y Jfxdx*% ΊH<:7G@ z%]=W :5eM[!q^׀ Y_0eF܉%%Z3ᚳ-= ]֕U!5"߉mԊMR<[n D[PORPҹM@ɵ`/@^`\[aFUe^b6ᥬb5Xb=]]bj1klEI'& Y*%FWfb/ fch^>c@[IcV6c9~\c6acaaB~X X!X5یTk%fbI.`(],W+`,` u e11@eU`e+pe}hcZc5c]c=@`dV 6fC6R"^=%MG^HTjblvIm OG8hest u~]SwgsACȃ熐i:սM c`M29@d +'ċh i"V &r UCxk.ƈĈ.2]qg} Mz6-_ii&ˤj;Kꩰj`jkd!k0k̆Hv#븖Pk(V7iv$VD^bxʼn콘l ~FݩmѦ>jE]ջPn6mwQk ޞn:Nܠ;*N՗릖0jBދv.<~ @ ono &o܆qk_#?1l 5 G\ pGv-Xpʆ쬐j<9׋?ӎYQ֦׶o;GmqrG!0Hr%Cu.r>a,rj.l/ Ppq4q( ڈsxs՞qGV6@&t`rEF7GHGa"wtXig un;oTUos8 [k\]>q?7s@_t6tGK_vHπrFxklmߎnobQqr5l8qvWw=I,z?ot 7x.x0JYnrgiox2x/xxxs@&u0u.y56luxqy7wh?~p W+pkg"7sI|wՊ ܠxvz /눥X /}ɗ|qwo}:گ} o_Pu}"`" =2\C/|?|z}2qʟ˿ ̯Ϳxwvϯ з"/}g_g}G>+ఄ !0l!'6hƌ7i#",Ɓl2L#2Ь&0:wneIB*jҤh)ΨRN`*լ8r*+ذbǒ[*Z]m-\MrJ.>zV,*۶bĊwĉ&S1r7z3Њ*mz-?UI B : s3bDȐ%M<P/cάIs*c>&<ѤH2{ԴS,깞UKvUЯ?ngx|W 6`хQ : 8 n5]OqLL/|@$aQp!nr 1&H5s p$Q)A VDgBw\1UXyzYj%]Gއ7_}Auş WX`t *B?ZunST#Jh5A 6%'ډ yMg0 I6C$2ʛ+Fj٧Z*##vnr>dZLv}q껯`U֭dyfꦰN;9 2˧I@Jġrjc} @K枻)~c/Z6I\/A ppSWC|ͲO[Lj§(kfTrC'?rK+Tn4b3WkgЁ j?=fʴN# ^iMWGpq}a]nLhF;#7j8+ý1;wU{sMy/ ?ρ`d&HA^C`,8 Gva IxBQp8i!^Hʰra'vP ?g1ĩ|C'MƉO̡E"Њb 'g1j4D7jPtG= ](/!}.|c"9ɽTRsO1@EXЃ&c2At"sȾt@(#)9IjNPtR)b0=LaI>9鮀z0!j1*أJb2."+fP,\%6?X7k4h4$0.fT}-*\Ħ]ж&h 96:l"*Co Z}.D'tu,d%Yժf)TT|vYMh)TZӢvmmX򧶶nܝxI22Xp=lujje;WO NzѴ׽o$X־m%syp98`75Cs_xU[u6ڍ,ah'G@[tĊ)ee"f#XHJsƛ>4|n?o<qC 9*E_ɑ84&iʛ2]..UЌ܈179IOl56:okBg\ٙǑC˞!}$4 D'De HG̏e˜&3>}PWw͘a8q`p),-imu׶14#h;Îִ=߸h5 Y525TvTӄDfio7 x׋^VJ|G\귿1p@ w6Sɶ! nl{í%km3/2V$0r9.צq"=Jӳth4nrzu|=a'{ak[qŏ̘_]t\{y羳>_N]hCψ?~@?R6},Sɼa6ο}+җܧOUsUgLy={Z=u{=doǬk&<\LUm -Xu\-rEǙJԈL ON <\XXqP[ޗM0 Π2FJ\h`Op |r NJ Ѡ^19 DjImNt"`ݔ XݍEJ*Pa b!hv!}ao^ FO>IT &nH HP (f`"aڍE$v* '"/v' ")!**""+."5E#J"ŰF-ݶb/v70na)r1cFJHWD4rP$;#~>[uFc:";PT5Fcr ?^E^?Zx A$2A2cB:-JCc5…D>z}b$MvF@v֧ Hqd"$66$]tddLZdM6.$db}q$#kcNEJQE=R_Le&:Zb!T8RkXeath%\pK#N|eRf LBZf/~ #~JO$b-xtaffJ%m,&ccdzNH&{lj&u*ffcc&dddn&BFBDȧNS"o.'soegiqtifj6'wv'|DJŝL꧁hS}x'}Z!N(/Iu߀RJ/)ZƆ"_~(ghnqh(hXLte`ꨐ f) $?)6)YFVMZ^inh~zhhgrgQ@!,BH*\ȰÇ#JHŋQǏBL@䊓(?\˗0cʜI͛8mrRȒ$Q\I4ѣH*]ʴӆ;7hP+}ʵׯ`Ê5TV`ͪUطpʝKlԳ>Ӯm붮߿ ^ZO{z̸ǐIu˘-Y珑CMdSҰc˞Gո/Yh .n6s@丗]W~{j__Ͼ=BmO_}/~(b(|w~H  QF8}a!)f``4(ڈ+X߉)X@".08H&*ǓPEAViErx\v9 RRiXnh^fW`z$RJcgb[H)?:$xYeuVzz2*f }Zja?i~ *')U'+=*@D+Vk@ k쪭Ͳ> R{ϼͽ޼ȿ,0<#O: s." 1eqTfL;o?ڃoo5:"p 70LL1Lv*U 5BZ\$#i SZ# %}rKLjҍ6s9kCARѕY,IZ>2;w)^0ma0I:eD2ݶdd3wcCJsդ-7Mmq3$d# {vtZ:پ6(gZIz2 A>!s[BB@k$h'9s RQ'k運f4 @ *AԞ'喔 : 0QbӠ$^ZFm6S;oA@L3|:͠ u{D͆Q#I kqSKj q,7j΄^ ;Nصf5ZVYC-*2E*D4kS*KRuV,azXsPj"2Y:Yg맗ͬ\yJaVJ-i:տ/+jK 1$BR[ +mz۶Xqs!brWpXWlV]ZsMw dk/^r&iQTݐT^U@ud-Z0J{ `by@"3 hS<9/HTG, K갇bKdAQbî,qO49v`}Br̴$+ɘk(?eH#TfhƮLbϚx*492ߌ l<}g=yG V"9kÏtl'J[a3Ȝbu2:jʧ[wjb:d htz :-^cF-˖N3]fgw:Rv gSsWp{bmuݿr7`Myw|}KG*?iVt}v~"s/u.{u^hyENis8`iv˳QOYAJaY!8ƫhxwovlm)".2/f h6yB,T6t4BkhCkqT"QE) xVdp+m&XXeH& j 8> n&&cYr?1ݡB<%+Bb9成x((q+щ8ByP ZI)aS"؝9liYqQ>H Rj*bsڔƘ9ɠ)! Eyکf&jJxe\*yRrQCx* ȣyƟwIcGʹg)K  *WI T1W<_`:b*ddvj)К٦prJtz"v"yz'ʧ&?:6ZF*DEب>j%өS's2JB#0ȪeaxZyvҫ|nߦD隄jȊƬꤓ9Yj :/ߚd2$fêz(*-tH:Qt<nG:* ˬh$0DKg*Yi,Z[ĭ "(iJin)k+-k/13۔jLjFyHZFE ڴA٘("yzdw'xZȸ2Bwk$aZQq!pgm2)[ds荐yYaCkf[= 0Mb̋ A0 @c2R*ay긷Kd"+wGfjk"4agԹBۉ"'Ӻ뺱iKpػ$ jFF`ļk{<J ˽B1+ ۰Ȫ{ [ c%XQQveAa\f 6ܔl Vl0'|H ,k: K*+/c9ÀkWj9|n?w>¹ {Okk"S c }Y[A[dWƊtb"<qL$j²qyLvEǛ0 wwCG"aȜ*[)\ɗ:9܋\Σkʞ{ư<³l©,pcz\d¼L "Μ||R O͍ͬ n\| /& iҖ|*"\DžkC)c"m';} m! Q>)m4<1p'!-$=n6D9휊b@,2MϞl\$  Ԭ*-Ʉ;ȇ̷Bؚؗل;e!+BՏh՘ݸ[Md:Hkv2ڢmMƵ큷˹]kӑؚhLkmύЉm!Jݖacp ;ޘi1ũ06- M ]ch=!M0Mj6 ,yXZDv&2HԵy $ng-ޔʌm0N3]794㲭-m中)#= n gXZ\. EMnc#>l>]}(j-~y2.4ދ-K (>~@BN>GInr>)Vgfޟ@^٠jǵQ g>)ywjzI rOߞ{0i\6{lv2GEX4bj}@.dݩ neJv2OEձ܋O멠c$"+Z?͗8OXx/b g gEHOvJ߾L N} / QW)YZD?dfg_difBZw O b~ +/1ﷇ??&8O:?AAOژF9#,ӓF@ %ZtI Sy>Z(QlaY S6.5\84xP7]y~_$+Vk\#%$rT5?~|*s4݄QCy>+UъNVfM L7W?Q8h&fs,EWvaG*GReyA>{4pRLBE=Z7,:+"g.K0#vzk3$ 4ی A4LO\{-6ٔörۭ ~ +Dk9{"4Ž@NS c>J?/@!<DP<+O@!Bî #=E4RRVkm W[-Ci̭oɱ"bV"}<:%9:&')b1Xbe"*ת6LoK|K9s"2Mܪ+7"+Nڸs>t^zB~M9FsctR+ !LKoYLDTL=5Uh5WUY[sn$`C Xemʶ4=Vn\S5b\PN:}]mzET6Eߙ&RBX>#axN[56m1㍁B=+z=e^.vfk^fusvg-#XG/JJhK~҂"mBt,ôi`eMXF4qJ|K,SDt CdC33X۠f5$XATMy:8S t=g$hD#:Kz7iό@V(O!I2SR`h;ϋW:GEqdTnpCQ rHphI +Оt(IFMң|b&z4V593la&+Ԥj:љv6T\GyOotC j]n Ѐ 4TJW٪^UGRnM+J;+"֞'mmaz5E-H5ْ41b[JMj^T6"]ih/\n]W}VD(lΊִl͆[ WlUu+8 rӯ8bҰ5bBl5*" C_ [Zw([QZpn)Ile6GAJM[nEuQnX2cnsܧ60Uv׻p{L7UZ2M L `5BNgߙ|$p|S'K!UA/ cD9l@&1![oXŰd\g 1u 2׹nt>GN`8A]M.y n2/wկ_3O4$=a7+4FMx8i۱gP[(C b61bSF`/m7KFTeN 5vfmiߤx`5Mxam- @cxR$NM_8m[ 0bX81yr؎z7Zk}ϟX--˫-iJSqLy=OGg̯] ~U{ {4lua̻ttA 'nM^nnlewn}W5h|9'wOEng7jݭWk~=h@͊'}˶Bԓcp=Բ>؛:=ߓ- >K;^KS q>06{rLH=C?#K?77s?s?t?ݢ#CS<@2<=:T`rAd d5 ݻ@ =.5DD;<L,kAA3%䬝ã991E|2BCF{s)BF,@S=$0 ͢@)RcCT?,>ŘCbǸ<<CChCLDwаG4[-C{?䊿Oļ?RS TB-tEECFEEWE@[sCӵ94F@yV>dgEۻAjDCl,)mFDYpD"EٟMu95e$-P`bZuZ`%ZZY@ѯذرJVVP[ڕŢMccXW/[-&u[ݕW<%SSeƵWȕ\,]_MSefZ=U݉%]?6]ݳuݿ]kۇڭU]U@u }u P~m|ܤ݉4 __]NZb XN}V-ێ] U-m+_߇]]UMmIay]Uv$}]g^^ \E8ީ_S=sLZn֊ua VTU׭rc>Vba]r ħ[*`;`nHNPu`' )~. +U, ^fMc#͆34Z6Ʈeu96ۏ_>~d@aAfY(:)D-ENJ~f[bEu"xISۛKXad-(gVuPT|#ou]6j Ff`(gت=u^Vok V~O4EQ&% h=fVBhifH,k&_/f?>cYj6G/m/q>qr\qim^kqwhړݾ~Oe,ʟ>)DNOg=eɲlQ`@p-I9mM0qWKfM4'i5WjDV@sNkk<=Lj>_?g@׫AsX$>E FGhGttt_KG,kVM?tÇQ/R' SVcN^Y_Zg[ϵs^ Idzv|2QiSf摯A g}J[|e¿%m˚Sc*;%͂mJ q -1m]:ȧ 3B⛩3\pJrN3ǰt06Oi꫖qLj{ES(?˵-ڶ56稁3>eJtKOuS7a/(źMb=g6SVo;Cn l=tp^2s[x]%.u?^عݎ6 瞃79>Tu[_pIi =#^g#Pk'=zނq qa$'O0kLE0Fr\3=;A?/l@tw%$ x&+\J-. Mkֈ0\# ͥCx;bh֐I<Pn9KH0I<Y'.0=bkǫd[A%BRFŝ;BZQc4Xj8K_ 8Hv"e2S,~*%~PTxjmrG$2MҖ}L 2u{$2ygj#+YKV8(BW ֹ`p, HdD3Ir ֌$ͰqgTE8 F:%v#M# YB$[%RaP^^ai(j@ѐ% G;Ql<Ԧ\YA n}*י>^:/\B'S K #A ZwJVE) brvy,h/eT0uiJ>Z6}-b+[ ί5 -m!lY}rV$L[Vε u]׽G`0L B;ȥO,;dT,VYю:iRZmlk[/VTh- \׸p}{Fj[Nk+nwE_;^UonثȎ5P0[5מ!,kT 0 l`BNnaJط0,h$ bGAw$6񉭛b쮘we/U أ>2;dHVr4C&`,;[m 1Ԓ]ޙMU77\8:i5]z1.sze3 d iW3m=Ҥti H1FLV(TfR۸FWWVuKh-g[ֻ5%3\؂3}^@ǐ6񳣭0T^Kv46nیw]e`KIp`ib ס^ ^\   P"F*G!`>a.L*^!]i p]xᮉ!iG)Qa`Ҟߴa;aa` Mbr%"V0"2Qki]ȪՐ#l#7.+|#8jF ] "\8GX8#tPh1iBY-i16j> ?D@ $>$B6B9C3f EV$M#Tc\cdd# }$HHd#)zds:RK.LL: M.Mޤd$#dePjR(eCBISNIPX3`eh#d;dbebe%KX$AYN>es,!Op P0&naZb%i,f4ENeF֊q `f8~eIJMrQ!jZjknk&J֔ܦ0 Rnfob.pFUv$ -'WfBt6uJjEKZhqLfMgشk.y zVz{2eD:|cbEMs: X'gihhzivs0}f )iDžnUn(IۼHd,'(Ҩ(ؼs( hhhhs)Viix$-i)pU)q .v6cʍi2ӎnDޏ]B(jix)*MiDj| jj4VCj$Ê6@:I)))*x*HVn6\ pjщ]Үv7)ZMHd˦]rގk6@lQlA\fzlkv+4~k v~)>R+b"+*2+ꫨ+UPm,H^4%DpzbQ,v-7ۼ-Ž-.7FZLάl'ʑϢϒ-6 --!m05-eGؐmJDV0QzTR.N}Xbnjm[&ϒdm.n.3n6.-tn.E-mn ogy*/60BnN*Fr,6xFno+ٔnDTDEo̯@կGv,B-ߎbw/)0E0w_V j{0  _ .S/- ~0f0lp 71pd.i1yAq *0qnBW^hebrt1M|Ċ/w1 ix&.,!ttMVԎ"2%W2A r%"2'+2#/#[(2arir2"{2BG(2,rq**2+++,/36!߲F2/A 31!25_s}8s6@-Ks+c8Lhn(E3В;9&:{:w;o;5dzR@q383A33?;??@@sA?4,tC&C,4=CF3W9ՎnH4II4JJ4KK4LǴL4M״M4NN4OO4PPP@!,BH*\ȰÇ#JHŋhȢ CIH7nȲ˗0cʜI͛1UjD1H@yйѣH*]ʴӂD 4(OOjʵׯIX]˶۷ Ţ$k$Zp˷/M%yWÈ+F `D"KCD.̹gY Aɕ)_VװcO =4ӑSƬQcN|9+}0[N:[ċ7|9sDGmKg{C[3;D?^Z4@^ R`af߆ןS( n(:`Vxav(}"0`=*B(awb!0HLzf#8ޏT2蠐-"MvaOf۔U*8h"."pVSGfhyyd)\GRHG$_TyC&VjR1@o^j3e¦G,)ʦnrꭸ^*`{F[jߡ %jꨬõ&,E+m&rt » [&/6,,ZSZ1<ГFW#j; "Dy_7 ~? hAO" 4=PxJa3kx ztS$_w+&3HDy Wh8;H 7O|P(%PK@2Іӡ~ ʀE<h#/61:"h1a[,80 /eWp91"Jt!Pq}i8H[ R,4ICؐTˡ#stP,):~$?J14f&/2u4+|ĥPviI_'G; y(zꊧ:יL%-sftDV\c6Mқ8Y;Y';yEuϑ3%MEN>o~z䟘\H ^@\TB)*Tz"ʣ3$"Mz )HBRܘZ*t[2MKdCQg5Mp@?)DTUR*S_ΘsH@׺:(O ɟvw$jJJPukuj[ŲD^S-\J׻^5aVA:XְUR+TA5V }Wd 4YZX=QkΒllhJɴDmjZ~>,8DRFEȮv:5I8@OB  = Oj7Oa" rޤ;TW]{9~WHB^/{^W*]}$wL>Su3FOtCTaNźaP|ĭu HlbWmaPx5ɍqW`F'&[SD[ ˫ `n{\OXgFsռqל0HyL O" ͈*wʉ^xGCz,4ɜN38%Q*Af8jsO<_r:iu kYJk9u-Z̘>3I١רmg1GJ8fFQ^J%s<q7ĸJv]F^qK3  { _Z4%p^B+s Bo5sW߃TC""o<"׾r2 hs H:ͬOp?<˧z(eLG u|ˮui]\sϯ;{?A&-7_nT=;t73M_9]7y^xkt&rQ3;ԬJ\W_fNؐ'H=Jzӧ^/@.uMV'|o/g'ΌB,'зҷ7ؗnIb}߷N7~W~w~g~P{~ц&-x:N$_cg&gC61NUC5yrp+:D1N1$Fb$ ;R"9]d o/;$0 V0ŖxNi;$W$fmA&G(#*hy!9C+f89ȃ:ACXuc:B"r_> "F:d 3ŅFj5'G5`:׆%#'I)_+hhb9Çr=BcAȈsKX 96=vRs̱؅B6?Sw6>P)vyȆᆲ)C// H+ *AáB>QV!/XjSH0 E^/x阊븊혆v("Gł/x I9yX&Ei%jw-hrxӑ'0hd討_Ǝ>-2I4Yx;i=? ACYƔTO3h8Xi("Y$ &)Cf.p~BE9Y9jȋ1!9`ze;hA$_Ju} .FĆz(5j`3Z)'*]3'ixyv"U9yȚ<9;~Ya3+}އ"InGN9j *ɘ+'Yyי;8ps/)(9IJ#ڟT).YrʹҜ"I`)+zʡ d"$ʞ&|y1,Z.z)I%`+;fz?EȠ6Xg'peace|O*S4Yv:trZ/$`ZpbG/Zyjzl:g cr: ژA~Z|z~~:a⩨Ũ%\Z1P0j≦:E8䉪ڪUy@.2 FzTzA [ɯ9`k8>BVjj<> 8ZrI;j$;SW{Y[C k# ad>!a6i(k˶h= PKzw;>(K0{}3{57k=? A븠u&k ;s+>{{! ck:5k={&%ͣ[E;00/F{AE*JuU ׫µIT4QS{[5ٕ<9u& yK2Û.i \ l +˄D"ø"|&ZɀB꛰k!29B;k\;?|&ElG964:4ŽCT%q}<;><ȅ<<6Zc;I,>;Mg ,[΋ORW?۶[$Em˒A(bZy"˶UL# y aΑxl[!/3ʬ} μ)ɉ$[|LWA6a,΁@l!D@Nh%L<>$G@\"e+̌okB_| ͏<͑x -$Ҝ=U--=cwJ] }@=m$/#UCxMx&U5#zN^ s9q٨`D(?#L N:Q 2jMsTw&9@x^y>P,Kqr(O2~p3"/b]^"A0F>HNPJNLM:0x#V.5X[$n"Ε"ےm<5p`kmoRq>sN!wjN|^~.QPn~y^9#9C~gG~gI3Kyf0n/^L#N _.a~ c0kۂj4hR쭍vxzc.tPZ=0B1{x"~~+d6^d!D_\^`>_?3S+;7n3~N ;6&(b^Fv2`4w9yw"C_D""JLoPVXZ߇,=0aOqClT]Nr_w9:50ӥT7ȅo t]#OC7! om>a{C*/Qp$nbu X0Ar C%NXѢ5GE0Qd )#dK 1ed 9 Y/ QFɑ` SQ դka5nȱVe;ۜ9~@(8*TIФ SNP˗c/ʳZ%Lve+V\|sD?邌T3 OFM6 @0ǷԲ!u!C/gqlƲMdJ4# v)svqq _gBE:riӭRϷUO~ z8:`;୷Ka8+0 3dCI8$22ی3-H3 5Vc$^cK6h6Ѝ7#E"k9&:CI.&,<~ .2ض**M# j(D@R*j/B3@ /pQ;l*+@3LO(KE QQ R!ZsVTuOK+W`L b2Uv-%2*$LL 6sW^{E\t>'{0eQA^5PRJ+,S͔^A:R6U'jGYkW#"Wׄ Vϑ5JeͰgmVZjLlEJSMn%[¥SUHt[vߥ+PWI_ 6)`M-:8O QKԇՉf+V2c! :dd!86dݲe^33:4Ygy܏& u dOi{˒ 1ߊN*`UGrTsNN Jf;"ZXf_>i(32/J`}5sXH!`/Gn^>k@TXo"@mo-9fb27AIG5?!$H,`a "0@GOA<pkR:4{U=o"^ D3BЧ> w ! wjAD)Ix -Hd%pL ƒ,|aFbH&ԘrG"`wc^yj")79HoҰDlNX6֧j;-βijG-KR3ًp8 TĨ !ݻj Rj>Ɩde"+mwR;!g$FHYu<:*'cJޖ tn%5^dDA% pca/fw^t/}۷oU9_Z7/.7@|+rR 7vpq7aX)fҊb4dRR \8ǔe{0d,{֭#Wcf[Z( @ʼ2k [,rBs6_fYmqWv-qZmbZ)R^L0ъGZ˥z{| dR$ԢVU_S[VYMa5(az#5, vsl I&Rm&@Ү1 `:C"m&/i'4ԣVhrEy5X5m[ _}Ú6&ǛL;dX"vet8̎Ԇ166(l'tIr۝Z_2ynoͯ5gZWsfzs;x98Gl :1qې##1zS"^Z#ҽB#H|B˗ʑ eDbK"-d_L(}m )V)SG~F{f4A!qTLT*h_*Y@1 ,@z*? ?'?ٛ=S=>=>#>_2A>9X> 3;Ө>/о> ?#0?:A[?hx?K= 8ԛ4@zI@[@sأ=ʲ=b$9|`@T1TAk>s?t5 >#ASa@ !"|=#$,%L2&|'|(̿ۿ,B,,;-B 8@/CC#3CCPC l |t۽9 :;C>> ?LB@4A [ KEFHDL<& )%1S\4PUP@`@YT)ZL[=4E^7(8aTcDTF>lFM|hiāj< T`SRA%0UD VFG+T0eV5 TOUJU$USET-UVWdȴ`֜ SVMSшC`uuӑ0V edu;W=Q>hei%Tklu VXTpUgLstBLXQuWT|R{TU`Ut,UYWV[ 5YՂ-ף͉bU9SeFeSX?׃M֏僐]T+ L]9Vm"x٘ҙE$-YE+̝מ۪R h\}& Kʭ܎T,UH`%%@Z0 pԗ]u Q8:-"H-#ِ\-t!h^(ME ];P5|5'J[] $~ ,QBm'a\m\eʵ\ku\;\-Z=-ݢ8]3Mխ׭)]^l]]+ޮ_ގ]l!x^^7MECak^^OU ]Rݫ}U_2_ʽ,ܥUE%]e `G` `]]k] ^\(FLuޕ^5a= &6T^R_.1T#" 3eZmd$Ve c bc (= a F9]:.;N<6T=>c1adkUa `a~O6fH^MNX}KLdV&\#$F% T.hpeXfOY.9Zbe">`^f5 b~ 6 ;<=a?.^ xdfm>NdoE΁F&raJNYg)!YgPFe|V}i獎!d0F&6nhE攘]I hNf``&ga1c @5iCDYeg,5gii骖֨i e3"2v$~.vf{m`5Ne_5p) j7ݱhfhζџi"V^B^ẾkkF!-^אg]lklX&.l+Vk,l.Ķb]v~nb]cnfԟF n>Qnaf%,Jn˦wn  Zl i7탶_&訮e}&ӖԞN`5mmcFnmg wi^p.pntWp?(WqJv~&߶~od&]V^"[6Fنc/drdpFitJr*+/l>kEVn.s '&2swF&ds89WeVjnjؽsoPrBVFm'VJt:1@kN?ikn Ɏʾ[.sicnuun\q_v.v8\A7BguEڦvHOklitQppRvqpP_6wGrSTO'r҆w0w*wwvbfvTQ_1\ <L _ 6tt H6S0 mv?GDїG#~:^_pPi~m|mB _7\GzPzfzt=*Y_ŎU%z.{Aвωr{SWŻ?Ѽ{o{+ 't7&!o"w/oi|'n`Зzz4zzz .}sn? X` s0l!D'hQƌJ6rH#,"z$.*WN̘0fh@vРB`JQ2mz)ԨOPJ*֬Zr *+ئ)WD aCj2ikI'r̨kޮ@MY.02J/aZܨ&杚U,.~P:/d)nM~QCχ Zl?"ňI"  0R<:TI\ҀILoKŴӓ XR Q6!MRJsJb] .sG %f^JW<&2Y0&4I.r֬Md]h'i qVbkB$:+Es%I6e--Ip/eSFIࡅT\e0\꒗d)aq3i0/ҜfyM is^/q::sSs'd" zړ ̤&z2MA)bЃ&iдviѕ`%MGч4"IgRh4*56[:n2MkjSt=,PzSGl1 ZdHt3T*STF5S=UՊUXR-l>ְZeYCֵf4!eNw+KWt 3` 2wm$c*OIR25*j>5CkDYAQjiqpڈvb7QKӅ]+o Qys+,༶tq3v+ls#3dB b w`5jUzU04Dq0^hkFQu+ng_pp R"[ n0la Syw/Knɇ1#P3T5lCM QVƌq;_n<#lx8XU4l,jTeZeG;d.^ɈTmdތ8wΕ7m=YH л9tkR$  -wsҔ0D.ik4]HɩlfU&&aY/5X>$C=lCFbMf;nBmFZ6ƭoKױfM~Ӫ]jxZŢ-tU7߭%7q5ՠ1Q^p=`XE;(6!fx79n.7!5~[\yF" Y֪zST}YM:`NcK:~;P-m5PP\3bO7dc#j΃5w}흪>:S\#MQ:?~ґ<哯a 'Y?FP(΢{żf_T*@*lr St:"Rˇo?['ظ vW rA)zdA( aAUh)HH__JdNğTd$]k >Ni6!T \ n z` ZxB@ _eMAe r ߂ɟ bΕ @ a` a"AZhLp`Rvo|( * >JFJha(bo*z"+ [aJ". MhkX!q$%HZkx}N(b)B)**ݡ,bb.#FdcFcD1% 4c<#ЀM#5^)6f6ry΢cs9Ndx@:ח@;b("#2# &bd"FDx)@f$(XdFAj6# tD2QRRj\NSl4H#q$Jb{KILL$N~NEOB>(!QYR.%^ES6U:eT^kPeXUIc>dLԤ@ eSeBe?- b^&Qf%d \i`a^aVbbvec~cddd_Ũe?dF%6%&'8i>'&/fSHe`G*ba #mBb2cn*HYRo^fppnfq¥gʥ%٥sBgiJj"Ju&fmĦH&2ޣWXR' yf~4bf2gb|&|f}f'Rɟ=J =(Hl, T !^IJEf("@)wkh(p(h(ՍIZȎVHFEf% 1S:d@Jf͌h)W>E)*VjThik N N6"> "*T(*–Y' **ʪjrzi)SPj!"鰞@ıgNҋiV:ݐ+Qīf+ѷ+tkw 빦U Q *`kޫ*l vl^ S&6jkk H*,2l:lBlĮ2Zlbl!ilv,EjLwGl-Jt²)F,vP,r,,">ЎjѶ[mҮ,^S̮BbkPXlUv-N Tت-\>2l FVk^jmr+b@]"1OwA.yx..nS..u}n.┮T0&b z ^/6WRHAd"@Fz/M"K*Ew-/pWR寖oRo7;G,?psE7n`dv_|W.0 / sp Gƨ.˯-/R!,6H*\ȰÇ#JHŋ1BȱFI CI2ɓ(S\ɲ˗0cL&"Iɳϟ@ JM8AYӧPJhǤʵׯ`êVhӪ])ُI϶Kݻfܥx LÈ\ǐ-"K_3kܸ&eľBCӨr^ͺXϟ]4۸@a5;tm۸ yI>bký;s?>y/lξ{ө#]{'|˭ހtW}ׇ~Wy XFv / J8f⊄mxˋ0~a.4Hˍ8xn(j2zX:6c>HTJ5G&Y̒46٤&`JeUiPWx$Ir:yxƔf|kޢp('(ڙ硈4Y}e8_@&jm((HJޘbj}(nAj+ɮt3+Lb$$,#6ɳО"Tkfڭܖ/r+ ;l*lBZ{mnC~KȪ; k 2,˫/ <0 3l/$׾c+0t) %\[ʴ<3l:7d㎻k촳dԚt ]dutL4{Zo5^+vcsgGmq2svތַ@n5}_ߝwKh"vڑ=p#n+nJD~Jᳰ:I~Մ"#ˌp,wuz0O6 V-3ϐIA/s}F^)|A_3?/~ _D^RVuծ </ FlHt;*~*GVZ= nMNъ:A"Ji$2_ܬoNt)M1Ԥ4lXmuunŝg3n8X3^NABUhfĝV)'P:?3K gڴ@S-ee͚֏Xym&oN%|fY?l{3/mSxErܚlW܏vMkq Çtf8una_؇Bt7l{+{oH;?Pq|1p 860޽5a#Qɸ<ƓQL&;dyͅ9mq5EBިΘZP P:`${ِe=\B0bf;$7YK3|RXzaǀ<0px)Qgk?ѽw]]cIɴxP[y[=|>XH_8w>^ ~b]`|,Yá|d||}o2}aU}:r}w` yb}o5W7~5?,Ӣ~F'^Gwg0"7=8x#'0;43NK.59I2$~p{1"* &-8/{W.ZHH7(x ;=(=Zy/"y H&,I}ܳWqCVUNi~&~^`&+Udx`7Uw#,'4Xxm9hxw}y(}C.E%,8@yxrRX8(GKz{2sG"nՉR2GHv|2:d%!oHTZ22ʲCswhse7 ؓh3#<3f1xkX'( }Bp@C~eQژz2ި8AdoVb8"A;nJh VÏ㏛Viѐ),W UвjhёhS1$9o 'i.-)8/Ik1YHv֏ȓ&Di݁I n1MYO9QSk M"ɕ "a) Vcg1B(Aoyqsd(Ș pI9SjiD9JqX5ZIP!g`$Hi ș|?,H(Yvpr)Rj$;J06jSss4Z"!uor~5aQUN 3y:F$R`k-V`~:JB~ӣ@1H)ANXJJ#LM RT,VAX0ԥ=`b:WU9 hj:.lڤnz=p:-r >t\kqF*=zz$| W&&[2Z54= B uAzʩ3gY[^B2'9:QryҫJu3:nWg[Ϻ%* <ڪcRԭʄ&*O$ꩃj#W ` dzfj_Ө?t @d|!(K°ڒv0t<1;#J2$ڙDSU겢 ىp4@E@aeg4@;BK}3Phqzsհ".Hz]`b+SfJ\׉=l˲ *I9wBy˪{ʳ@;Pզ˫뫋 jḿuE~=f[kۨV +!A@,0mQ-~73@Իڪɀ#Cj ViXKM{;xS n+ .3@ZCzkE.{۽RʫȿvI >x{zW `= kܼ{Ss! TFLk!#^k;*<:.<k2|=4s9,SZ n*C EtG WIlf[NjWy3ś< =:g'<).JWqO׆[,z :|:puFC\U,J,=XĔRɚ X ܹT[{ʱ$#R2XBx#,W7|l0zDu /MR`*_ӣ]ڤ]Dݚ41j!׵:nDת1t]~vYxm0zK| ؃K ؓ60[rٙـ#7ڦMɌh:mڳ=hc۸Mk-[=oM2qsw]4=ܚMّuIݣb}<ݭ mes%@Wh}ޛ߂(ߥM(=Y#ߛߥQ}=Vub!ss<΁ōu#(۔ .Z2"M%"~"% B ݩ`ޟ q 4>:[*J>@@^ߏ@䰽]KM M/2{أZX0@4"3^`܀e~ZWkS=q~s^}Э QC9b66KQ ڹ0uΈR>TWW/ 9!NVb>y Q9ҝ>Wعof煃n>̾ahҮӁ>V@!q,B>! /gpnauWg,1-ޤ.03 KfO/W?Ѿq~dDžs~3=N=#bV4 RsE0P2_ 4,%ln<?WA? C_8R#4"3fG~3`Q?TtCr3^?,qY7\=cnui=5m oO ?wy}Д)=O^)ɰo9$FnN  T `ذC$NUѢ l0!E YITa 2dfx,9s*TOA$x!T"Eb)>QZ(sXFGY*UiȒaŪ"[VJRbۖGq[TWy_:"\xaq?+˱zN\e̙5g+o&=q"G')։OW\ܭ^>Ua HԨA~3tдDibDQ֯am2ʖ̓Zn:)j#%.[6>Ix=+Wk2 hk˭íM:KE5J4D,3[BBўKm[ &dX'K5^nP~7y:M8+Tne?Y a:Ĺyd~K݈6xya[~k긪j8.qunB{;nmɐ- 5#}pD8oo#orTncsÁ_#Le1UlvYϜ3-C€ oxyZ[G(/@aָ!bٲ׮{l#\Ɨ |Hrо~O|9$7翧b`I'tN(S 3>E I$: 43Pa;!t'eVx.Gn"C J}E-qˇLk-V@M҇ٛtґ :I(d=tՓSeUinuD**غd5+Umu,\921wB^ ``7جV5$S8.6 vt 2O0;BCmdiT Uuka3q?P!V,>O* =q\)nuFʂ^W)a54-7@zM6=ӊo TYW9$<;T#vm{U:Xj')xğ[bN]XWw錓h%&:~ }H CF&$P9]9duF/Sp\2UPT0=}V% /0q9,9wޙPxg칮}˟7h-54xI,Bv2WB!i&\r~ jw~1mQTTx[Fpik0Z̺D qt.n %s]>t cBw5פmwMbJFSN7/;IKo*Ju $ 5Miֳnpk8weBHnrb g.TTʐoOvV_Ȑ~T\S;H$cđIFGBa+J87 4duĔ&@,y<7g^r TmQ#|{ڳk IETa]\ @ZӾRX>1Հ|`k{9y7x@~ay3bbKy#=[ ֋?=ٛ૔˽ؽ) >8D>)5>;>{c!>J>"?= K{ ;+ ??8?мA輓<@+@Yp[aE @@  @D8U0>+Aa>p>D3>#5$A{ B#1:$$%l<° (C)\*_B5B0D@1̌+C<4ýܣtyCC͡C;y 3CAà ABR7|KXs{ ӻJ ?i;I,8"B\ ME' ʻCmFGTHIBD[ǤiG2|G$['{G%X<l1d|3|\d̽\l81|;cC qyc+Ftzi F(IUn໠ShJӯXHZ $Im|˸d,ѓqrDIuX̙$̚4'TLbKŸKGLܴZ[Lt$ ̅Ѵl6;=ZͭT״،+ nUS?=@]OhO}Oh 8R,F8U< ?u CR`8Sc\QN&IMX+z#)% VPKxyYN@Bi)hDxICûT &1TJHZy (& YْU ɕeT|L]CT'U -. tUm e 9UaڹpZZ|cZ ]+)[8I0 Ju5MC[۫ ܛŐ=Y1 @u]U%]͝u25(ԥեm -= md-]]]^B=^īY yy^^͜yAy`8_εύ'M^%m߮ϰCy e}}*^^\`͎ .uŭFޫ}Ye,K ](jZaa'.b=UOީ)|&^ཀ))./\%,°,r0/*㤥4>5N6)62x=.cѽ <,=.>na}"dbbj[D^`('8'H^lQ`J^`L ZY d+Z Ùeى0DZW^+QcPv[\]~Q]D<^ZM_F;e։侂ܽ 洺]jfg&f3unonKdX!ta3hV+YwgĕgZ{FGGb*a ~ Fhe]>Xe厝a߸]hF˩ i>d`1@Vi%m~i'F>K.^>Ɲ6+`Å^S`5j&& -a>h[-]߉>&"h1'n^2iV$n {ib ƻivb- >졮g0gNɖjFeў.hZK߷6Fiڮ%n.o톸tE<b ^/0}v-ij3^ߦ552.N$;JmojoGo<.ppNbÎupTVN  nlEjq.f kZnh_/ihP"Y[b)URU*p d2Crs#'jP@;5n@y+4^3YrXp 3iGƍx쨏] Ʈ;8s>| [CDE? F=G/ t MNZttuYu"G1uYʫSuV?WgXSuڍf Pۃ rBc?<=>o(h[?9t I?YhtwnIJ/KLbtu7v,u"{w,ctuuuƄYxhyx؎_gՍ_igjgkl罓/Wvqrw)y-&yyc^hW?~xxzd# CzzfW|ixjykyl/ym{{ww/wM?8L87|@|}Wg|s|~v;@ ||&bOv_v5x&%Kv7 7,nڧ۷{s)X  2pa=&Rc񢕌sq#Ȇ"(餦Vj!/fάdf}:vy2RV c4RJ2mtiRF5jU.Rذb>m:VeײmVܸbҍfzK5q/`DN֭Ċi1㙊kX2a7 Т}xktlg&hHwXyr2B?SjZK+LoԹxgB*0xirgϺ=6eVU`)/ bIf%: &|{lƹls>x#Ӟ}U\t޲-dNR(m\}X=Fœs6+l{/ *V$ ȯ+ ;llMر&,V-ӞT-Vf-\rh){nnˮ3oһK2/v/ p հnA,qV+DzqS?*!-$/kFE2PYr>83d,.-j>?>41ӽBڲVXO5ZTup4CVF`pӉ;ge-/4∀m|k|1}C} G#{/~:)9U?"9^E NkF)귈$/_T"ۤ4}]A3Cp)SaҘ5D&! Y=QM a0TH"=nϛ'HCrȜ@g=@0Ghn9e;<._C#+>oQV;C3BҳhCKڱaU-X[ n*hۚmxp [>Yn܁Dwӝ*On+wW2/ʼn^鵱UVnq#H&QjA>FkYGѡdd7B$gDeT\Qx`ZRpMs+4y'EEXK$W^$nؐt@mv8B^5R=~=ěWBp; op<-x[bqC:=Oh(yQ2~Us̜!54sH7>Oб7pEt.*Mw"`Qh:Oo.zwE#fOJwj;`uѷb;QϿ+i9K {<6q3'yMn$#u^=Fį IhWÅwL9{ D Mpk:=U;Xѹ*Wc|U=E_MuTRi_x]KB<_DUAhO\sh"XEPğ ŇQwݠ`I ؙrKE*)0j" Sɓ )RXY,&w_U_ za~tHaN0 22Z-*4<,DaxMTa5<:&`a ƵU J `"a "[ v١aa2ᢑ̙ލ5FƣDbMb]f"uqG'~b !)")"6'l#7T8b,qa^!l$ahU9 11V$cz.3! J(RX_6.dxpc73DDO8¢,B-6.j;<=>Q)=6H>>r?zT?daLTdL~CZ86DN~TE 9fd:aG2GDHZH\II@JRJ.OKLLMMf NJL^O%Vc1HՍ|"WXEI&%;_xyWDԜ%dNRpe ԥ]^^ _j_r`*`a&bbfV4cA#edZE`f ifpfggOhh>_d`Z`GaaGlJUcmw2$oZozp"pt:2^ffOtkBf,&V`gmwgO'gz>{r|6N }ha~^'mr~(:[^!(&(_,h"VAuPjZΦVdvR \mhB~ 9ug Bm~R0'ni )jhqf_1rBhƘG}Sk*RnU%)z)ĘiK>`h)@zEҜiY!L)榟Ѡ:-Z{&*L' N)gSťf]mxjj.<DZMrʩJ6V<Q)wR*Zj&FkOjz*/R2h믬*1+곮+6p-ż۱M=QXGzh&egwZb 22c,^,V&kk6^JPlX,ƮjUplÆ,QhҾRM,Zʱ5K΢J.-Qm mS΂zztM0 ӆr†R]iVZnIn|-VLyӕ:Iڮ-i"잽Fŭbݺ!l.Yqz QtO".您F+N.nFInXt|. nnPnΞݪJkzH\^^.Z.^b6>/S ZVjB/(Vo~/2oTLJloz//{ /.fo//32 0mo'/NNW_0go0w00 0 0 0 ǰ 0 װ 00011'i/17?1GO1W_1go1w111{H17DZ'111 2!!'"p@!,6H*\ȰÇ#JHŋȱFBEIR\2˗0cʜI͛8s3H!O Ų*H*]ʴӧPГFYFʵׯ`Rڱȫ%][T۷pʝ;,GAђ\V+ݿ ǂ@嫶-ǐ#K,p23̹f>2FZ%ӨS3מcG4_Lͻ_ 홶u^μCУ NKtνw‹.}CcC?!Vϋ'Z@$` 6[F„bd2v(̇ &I$h"B*~9D(!^v!x<"0(-4h28XPF݁"Ep$I.d:J)昻Qiߕ.yfx٤0ai睐Iɦm)'Oit]uEɣ~ hTj8։覜z(t(N.9RZI\>IjX{S1ꭥNk#4l2F+ k&Pe>-Ӷ覫Z+~~j͎Kn5ʿlb&$ +o߹1ee*qj0lp$3 qGNLg0Kֱ<#K0l1=4l Χ3>KBEXӅJ3t<3u_T5KWg[[l_7؍\*\)7I<C07LooMx睘|W߀mo8㆔n4EQnHHs9##D~_68Nn%#!mwz#/K/ ?yS}YaχpTH>z$F]~=u $6A ]thfk&1\0 %h?r#< a/6=YJJb zW0 atHhBa,|! %P M=,S#1'YADM#X pV-|a gH0#eLyF5ErD9u DE>Zs9[XE ŐaYDGBFMbK61,>qt Cq&#C +=GzF.lgYZܤy*0icn$L3c\HBhlQS'YR$IPiL3iІΈDI2:%$\]')Err1Ѕ6D%jdTR|NFqtyT?9Ғ=mJt1L#Zӛ"/hpg}4`{ j?E*̀tF6r!"KaUNU /^Uv̰tpHbGYV$dӚԥ68p\iQWNh:R8'-zSPbE $,eJ˺U? f;V%x$H( هZiV"1!"(+TӬiLBۨ7Z;An%l`o4;mOܳyIaS#t[F֑~Zs [8f ҿL {- v뼊B#zm4$mkZ_涆$=[9u2.bZ;pxWT/^+rCjj .0vmj;q/#͙djt5o շlΤ|+E~aΜa^Nl.TAxw;Eq EѵqKO O`rϡ𨩮v^}Yߺ߻npDӜadѮvI'"eЧŦ} 􏫆'EOV [\xhַc{ [V"Ϲ#vOh~<>Vޅ{QzY,\N^o|ew|3ljEFlLT}aq5z d\EjkKH^J8$([ &x(h7US0G]s P7[A06 6hc|aGPHhj7^ } 1@R4eH (D4$0) f-h///1XB38x$:<(T?XA"C(b:IpwjLN88 H/ TX'X ZF\F^(I8ATG)m8.o qhshu(#wyO{}!K`=L0xWX}O Q 5`Q+f}Љn4d(շ8W.0zC N9Xx80=x( 8"E苿l"XSu،t\lH7KCR^ܖѲM;zXg!8H8 .9ItQoƐIJ9 h -iz4ȑ Îv莴(H4p&yѕRؒw%Q/b%NA*D y$L4DY1v@3Y}Y cmBu06*iqg\d*-I aQceɈPxД[ $npٓrfY=+x{9WP)9pLOG8y5)Yr ٙ2ĀТ%ؚ@F9_e1M)E[™BwʹL5Ŝ✙5 عqAt]Fq)#Y$Hb+)7>"yBBDуYYwɞ: M D!3i :<zuHz! #  /k6(#J&(*Ԣ\30i@Vq";ʣ"nBaid׉jKJ?MRWڡ<ӥ^$JaOi+Bh9komjo>ɔ?_ܦa[Q Eb :'BfW>gcq"J5լ%w!K 6q,ϐZ=Э{OPʗL#XP|65 F3=_0j#rd㵫S-#CA H!Jr35QҪb0.2Z sFǴN P$kaZk3q6aa˱2T!#l{oO3Cu ".Bh~$=?+]ӯ ø0I[ ۰i 2+'0zk@LsN$(k_U#Tk|7۷;`q 6]B˯D 'b_vM{ RWѽ[F !JS> \!oۺkmeUm;0{ ,jw{@sk$Gl'؋#EAZ'C_30h'B#)Bs #[ %;:m NJī@6ø-=ܘvwL{<$UPoU<8yoEC![Lkh$ j|>'7%$jHy }왑%F5WLMĎx\V|t,Aʸ0ily %\=rpa4haĿ,čI<rJ; k<ͽ#Es'V,u 0"8Ku"tϑ1a$D9K ;K{9xͼ3H S.ҝ\[]\!=[f`ʱ,nl00-22m#46U H=m#?mCAݻLkË#LK 8M-5Q}kތ<Z]A`eI"c |')ֵ| m0 %q s+u>!`̘tI[!~λWzLs叐傭 ]_' fWb),;k^Ba{#}1!>&pt˼K`ZK4nȗ꿰o+[.QL=!-!`*+n#ޭ/.JdnޓҎn1X|O-kW.^3oS߬n]~71i~m%n' `&_246(߯_jE>J̌00_248?a[vβDo렷,"u^$5) T m0tO,qkmmyeLַAl>3(2/fK0EzS_$\2Q_E[jvnrWk%(f ONΨ|c7\a¬"XV ObFM"04n G!E$YIT,ANͤ<9uRHA\!ÄI[ Ә2j.YyN +M&Ď%+VάZ c[冉jj,y#>3 /ު c`$O&Z`һ4 =\\*mX)ݶ-ǎ% 3£#6jlТGd9,- vM|Jѣ~ 5jy9jMͳmp㢮{WUL0ĦZOP뼩ۋ/"@4,hA#P BڰG@MD|cg ?` qMm*$8z0HRKiHӊl4*$tɭ|;khJ򨪴LK/ S̱3Ts׆ 090M\pOO@-י ̳EKT]Q P't1m]S@QM- UE1`5P!TvȐe3n[:M+L.Vit8yf$/2Ng֛gzh6Z.ˡ㻩T\2XdkCㄓ[6hrSbQ;6p[UB`anfiF 0@Nep7q W9n2;pjb[oΛqnЃn&`^d#䖑}vyc4Ed)Yne,Ojfr֙~6hgZhmCv#ܵ& F_{%SD&kHriDs CWёt#AKTwA,*#C,6;n"t:w;H Re&;Kc36:K83镅Þ@i\ZYƐjYTؾp3_os-֮B! x@3]Fp$Eҩb"`K>ǣ e|X{cG†h˗,@̅AD#S"}=m{1i|\W>b;cx7͍TH:V_C O11$!-x nB 2H6D&%;ͭ@'rKxU҄e7Z2IӥGEYQ|X$/"cLX?4Ⱥk"t\v["(ADF<)K.l>Ot3H2T",k<`2+@D7QUSLFrYd [G'60&e&Ji5HnR}r nL" " 5La`Ҹ'n-QjcuU\oF,mo'"M@[ 'R4sh]3jE cO:\*g4"T4˜J=s14W0gr4kفb=jhw7Z_KFVk{OlJ[MI-Yz1NpB,K,r EYX;鮢~ vG:X*/Kgޗ,.Yo{o^%L df:NVAT&8[ $V » m\P=#o91w˱(1unnJ1R|ѥD<'WI}/ (Wʇ MyeOE˦2l`0#s )CօR<v3(g9/vkh&N\jWAs쫡kFEX~mfx|tEĬh;6j͎zK@pĀ9Q_Sdlj\LnA}=`_X~!vgU>`\h[E%& X[Uq3E4ЍuՏLf_w]}gG1׺̱Me[1pY5\gb_ٳ;>0=jb%qaw9in9PySwk@_Km*~b(6dRXcCnya#aQ(:t/0'ED g,m5A @uAR$P_KZƊq~s,}scj^#ݤ`Dc-;gD JeEEM`S܅':xBFuR0੼Y 1> c(+=Ü>>>;>a88 $Cs1+b1??D35?\$ɼΣt@HB D d!أ @ącALØ&ZA烄AAbAA >*BCxZ¹{Bx?)$*LB?*}ENDO P@Tl-U |Hĕ,W8WoIpTdAxeR {MЍW5QWՂ}MZuUhXy) P]#[،Ehh`6$m mSɚE֕!Q5QKܤ❋aޟuޜ^#e뵎^Z^Ř}LpIߵY}__H_,J=ߢ _:Y NY`~ 2 Fі! ;]Mfa)a8KJXaBh^}% uE.!_b(.b`_Cp\%&vd_bUZ5'F*12n` 56x`:Nf<< =5@dBK3d@da>a^HƜI.Jbp)5X0NFb[)e ,-f ec\2^ fh[c$d`efEaF.V^taJK $bgONOPLTg+neBzdׂ h]@h\^5T``ubHchAKvf&a6a  LgDgIgLn=M>OV{VߵAeQ}]jv,G..Z>[Nhì5択V`'cDZH I5ݦG@kDd^fpOvVVnfgzg]yn ~bvax2j>ŨXCҾ&{[36Xd бn&- ވ6innn.nVgnwFlxo(.ހnzV }5$n}ooSou΄ ph,O$^Mឋ 7Anppkkq rn߅^KosqC&Ħlmuoπo g˜o#$ kvr)*^m,?./ f2IOsiHٞ8sJlo ?Oi@gAqCKtUY(zsvIwJ%7׆'tٹm^ypRr ?uHU?WfvsZu>n]')^n|l)cqHveqVgOiߚjvlmnN7Ou/2t7TPvs O~w3w5whs]~wu]0<=߅?WoBgc xExԸ/"xJވ fԀDq:K P ~A _  xHlw:YI˿R8w 1Lr;=9cE ˎ]a5VE{}Z5YCWp ܏ѿ6#H/Ƈ7˷G|'Џo-L_h}x8_(hp„ 48!Ĉ'Rh"Ɖ q*SZl5$ʔ'=z寘2\fΜ:q)LР @Ҥ^T(ԩ*gEz*֬^^)]b-l."jTYcn+ؚh(leGwj/d*>cr̚r3hFGktԊV꺧e;ŎWDM7࿍"RdISUu͛wT(ѡF*e;H)^k+`+) mڵm+7kݎZ YfXa-& %J j{hvZjHkt:fm" "1fDbǍTr!$(t:Uu|wH-uS ֧y9^XcuZlE"}s~zY`FC` :9D(ThVita"8ۉˤQn$#2J#K6&cJM 0)duEy*I wM7kw%ZYw[QBv%f$ ) =x`ie]|rk@8bt@x)m;o~r8RҎz5 u7m}{ܹʠ2Q[(#)]:SI>!N f-p3ĉ&%NDx'<8Q[Oֳ)z"@RƁ P"*Y1t%.-˸PuODA3LF:+@ӁijJZ9hJMp~S9YPvTDi6N`?Mn 1(BPED)ZZ^4|h*<΄A#aIO()-KczL8MNӞ^@ -ڧFS\j4VRl*zV;P^kljSU)DʫUTFّh>s{+|:S%u`'ӟ"6W%z"%1Q(^VlBAmuۆ6 UoaدFՁÛptL+Nͅ3nXNB;sREqsZǷ31qh5(o2R7%BjSdhHqqQ_=DfU0y_2aO O_I _]QG(``Q 18WD y&&aBt^DMBa | ` ` " ڟ, vJq^P!X֡5!HJ`UWIq! |!Ҡ: ڞ֜aa꼡Ha%a2)obr!!"*"#0b!I!bGq'"( *63B*z+ ^vFY-"7/ D01nt#2nG˷<#=c4&4O5V!ٛ9M!/X5G :X|#$#c%@0FI1c2T?r?ES1$ĤA-.$BNOROR$ @AZFF;n(%S% Nٔ F5Yb%1i%Wd"B!4ce/& Ud%e[“Q"]&]h^F_bTvTEU&aL&W*dX2c6&ddr_TZ^%Qrf\~\6D]nh6g&E]u 8cHYgcV Ht)zxoT9{gp@'HQqq'"yK)cS@U'XG§*| }:}> "~$Α&5(bg~ ނ(6wGurMC'goMJRh~>wHh"vrv)^D}(_钦酬bi_hĝ!m)V*=iDyܠO2^Ƿ%b8jکP)bh\p)()h0'Bi*f)HR**jVમ~^YfbBL^R"-k=n*uj7}'^kb붲H)BkǸ @*ީkS "ȫtk4~kEk*v, W%ԼfkEL,ʂ,ȬY.m o-;,.1^D)by8^ֆ؊֮yt-Zض۶_CxDJ ݚ]gӔ-nm,mn.N*iKBT& ~gZzHd.,mu.w.^n.HpnznNz//&./6>/FN/V^/fn/v~//!,4H*\ȰÇ#JHŋ3. Ə CIɓ(S\9G0cʜI͛0]rg@ JH5SPJJU: PKiK6, O]˶۷unAfɖ )ܿ \2\uDX̸z>~7˘3klxnbƍ'LFtY7^ͺuN^li:]ͻwìN|vڑMY4У1ċFW9ΥO~-ױg`;Ya.Oߟzۉfh|%巟~_{&GwAvF8a 8Y,@R#Ęh9h8HȎ<㏤ i$H&L"R10d6ޘ#{ SMh{6:&qa5_i8yQmI'8[wu7]M3Y%kyqv=z꒳.J|axD|+H+Fi%ܓTǟ2|$[3>Ԉaocw:䋕}HtF{۟-򽱅vWXt_|RH Hb0g >ZA0#NNBqib*84" /y9, A#q=Rd'BQJbV@:fxІ>CQL}uH.$1}̀@|$ {N\dh(ǺюsɣG?Φ`!Eu%\@#H2$.FavXGY$(#Hsi)CəBB 'X+3ԠF`K?5M\|gՅL]rיf}[IKXxg$*}j=*[Z9XWȬfQ^WկrҪB0 zM@fF-/Yf,] L*/3iY F2a،uGٲmf]8G4L8y!yEt:FȲ4Y!ɤgZ2#B_>U-uVnb$@6HqO\!ץuCCd852G0BeCx1W>reit9f&|2i^3+;`6Rz tth@YьK!]#,k˹t5]INDERsު[jJZJ`#y6 ;+}iMd:2bb%ZQjmΣ5l:&1A/Y݉E;Ʀ4 /3;de"4F{{{np!^s7댳{vt \lIɶi}w/,Ӡ_na{T3g5gZsaq-#wAGhPfx{o*O|֫ ۚ+ٰs<>{#NwI]^D'񋏷qKJ>˔7^vf5sÝr;ǃw ;.{^}]{چ_s1j=o>_t!:%Q2LrN'rPGrRe}~-Y 'y4mM}$اDz zY!6eS#eP6hY3HruB؀p_s{'Z|xov8yAF|wPEg='qLgz/X41h386xaw<ow x1#CH,WGg؄RxLg2y RBdtk#U%I35h= ((I5+l%Pi6oc6,b 0& HDwR0iщy删($U?AȊ(6!GFg݉ٚy䩡ɡ@6q;rТ/*`16YwjYf-7(+(Bfٙ;yI9vupcﱡ[ڡT!z8XJ5Zjڥ1s39Xhh:mzci'q*(#vjx-a!} UpAxګ* S(ɞZɨBo)9,Z᳟ԟ3dfzraT#Fp }rZVɪT;Q_ !Kꧏaz ZQzÊ'Ŋ6ʛ*^ *4.f!mH(¥ l꒦Vc>aff !6'% Q=(l+(5p8;SH}гH&)Yef(`6R3<"42C R"H[#1tbf!( g9S(* W:0F48:<[Vw84rWJ#L;8OQ;de#Y2굼a!d VzVk+&$[& (;_\pJ)rI{k}{ Գ}E{n+'{5ҸkK Tk$[ CZ YF'kxtֺ]afJD+۲w j,ӻ+'%d{%kK3KM23R+;kѽKGvᛘk#d2]ŤN)K(!j+6[2 P[K]a(2,h+͚ ?ai{#+<{~{›cXɿ 6'8lY:| 2 m1f<|"7y_1;", }-aU%JCD_qsY![+=hK&Sܱ#gI #nE602]4$~(ᅤc9⭴t!@D2s˒IZ>~6@0ENbj iԭbXWʨ%]N_acK'Vjh~ᓕ>nHp.%nÂv.&x瀾9H`;6+#3Fn_L放ߌ_f^^ 1#Bîz> _n㞃q_#.Q3骱=*!d#NNnY~LH-2/l}1ND^68ht4o @aL >9P:6.$x Ս,cbh.U8tI("EqCsu<74i>^뱀1/AO;_-~ /YOR?X.2+_,DGtP_Q(/3I#z= R@ ,d"M8ŋ4nq09D$TFU`IS, ͛tF9OѢ[FSIM9iQN: kIBBͳYeT6fd`-[#. Laĉ/f|Xc;%3l{5KcGE;$W~x:`kT;ʳ-] rmd|1-3HBCPWc$֛|< ~uǔIfܜ;{th񣑔.mUZ V,Yri-"b-Л*I: L0[1 3pl6㬸M47ZtѵB/믤ـ P7Mx  ኌJ  !(6bm;׼C >3<)|J(*CJ)B01jÊF"7P0AB DqB+lCFu#{13Dr;KIpbє}3G+rH#C1Ib9').)\RAҤ-;//ôi2;=cLMS?:*鿱TU-g\r0PDQx] H'Kw̍?mF SiK7{dGWqXuZPI瞔΀ҷa .)Ye]/ЄOZܼ*9Nn(7==P\r5WP58D}pW^E_7lL5dk#Ȱ[ ˶6Q{Ȳ٨[|Q[Fdh^%ygM&9-).pܓ ߝj̛ס M GV1m:H<[.R;\k'(y%Gzlr+i {_H/*6T{Xm;܆Uۥ6qoO+pkwSDCׄoȿr`n{R:'+.0s cJӭLu` 2dPt`mwMdw*#~3 x+S\MnyKxF1 p \ gX $? 0s9(0 a(đLa+u. vlO1BjK Yx6 GCinj:0aSYJ4BM>N#~Q]E} +Ν;S{cꨑ; zing?" S0T }E8IFm4%xSܤ'HrT)OD Dk%K̲9ZIr]c0͈ 4&0eJ)F;iN|J6QMxSx 7:NS£g=C:ķ|giJcIwdіbE]hFvK&ǑV&JIM:w%K}MΔY5y<%/)TxΓL* #1FX9uepQ@|kA[>mr.[({&۾>[s+ Qk ??z??k ۤ뿪?dd ;@k;}=k31 t <3@+C#;> ?. cA qY?Ȟ̢A#cAسh@":$=%ܽʺB*Ĭq4H>S" .l[#닄|[CB yC.:;\\?ȎA$Ċc= DB?=D*IdJtBCӧ¡/TC(34YlCZ?iEPː ^,_|aTb,@LFet w%g|JHaý%\BFH IDgE L P7ûB0dxGE3*;#:G CFbԸ*rH+uȉH:Ȍ!;Ho’DTBSIaIӈ@uU*yEGI} \C:G<|H\J$ /JlHDDJJJJʌHD,ː$I G,øtɦĘIɎq<||2 ,9J!ix̎L,DBHJ.& M4DŽY0h͹z͹LGJ܌GM L LN,=m$ <4  lNįKQK׹,KDMTͶdͷtM3ؤ90ϻIDTCEC#N]<|EmkڎN!Dl-G- HI6԰T?ԶQU0STRd˻p!Xi[}\^P6I)cUdEeݐqu iEj]ϸVlVnopm;WH_epWlWWV{ URWD@UTU(VEW}yXy _y`$!Rb(U`X6F``vq ` ` .N 6Y >}ub!v3n Tap({dNfd!>aK.$Vb&v3Sߨ!DbyR~nc.J%\ϭ4Vco67n89cDQ#?@ABƇC^D&]^IfJFQRNdnde#R>e&nbmeSbXIere\b]evImfc%F0cfd^fFilxfmcչ'9fa@ q^]dNާv~xyMmgy>emWFh*VIyeenOH!hdN nf6^ihiw"nd &Bsiv?iKg.eZV%N⧆^-6jx儶j/je`k6N8&if~Rk ݽvia)56lj^l}m ll>e^__ӶԾd>֖c`vk^ic]m.^T+nv>nJ.쥆.Nl6UlDj\m06FoU`c>Ֆ9o}kok¨ndoFgppNi"kd$OpqՌ_{+OMSy2^^^ȉpX =^-s]Osxl(HgmDg!f&gry!r#/6X}r*r$-Wr02WCF.56g7^9: ;?@/cd-fxxAj~ub^r'w& xStj6P/Q?Mw|vvw'xwxNsu|~w<ׂm/t(>Bof=tz 6bx+ x y'RnON_Ooy*wQRWwS^owps6zDzv@_Gv/*z˿h?ߋ{{ { 1A@Q{g{vpqgr7lg_uWXW{/|[?|XI|^gzLwΎ||zˏ~/r|x}.5_ӯ _}vwyԷTww3z.c4*h0„JkQbƌ=qDGy"5|hDVL%2gҬi&Μ:w'PlB-hFTJ)OԨݦRhZpsآFa,jӰm-\:r.eT UFDkfXnے* 2/HQ>yr^>6; .0!ԪK4Xt{Mhբ9z)IMQfKʗ3o9O+Fn"J*E[r61ٲgͪ]ۖ-ܸsb޾̮aǘ}%Y$=3CiYghjk6Dvnup%YV=ƭ\r8#5:'udhW;Nqw߁'^VwX 6_чoxՐ~Tx]vA `<ۅek6d9bJ#V"FH*ĢJ:8g>ԏAF`dPF)%UZ]~嗗 FXV (bdΙU nr,axg,Zb­XR:.d)Š(y:dFN*eZIǺT_*ٌi9pnl-g =aMg, Jh'f)Op1.檼2/2<\, s&=LAo04}I+MW4%Qkp9hy{n S25r:ۭg *˱0<3r :shMtGҘc^0C D$ELۏE;A+E-rH*b"1hLcsKvq <1$U 0"I:d*i]2R8"'I'ωObpCKT^$!yR,Ӹ]5e4 w3&BeϜG4HFV3$G@KћlFN~L&iEUS$ B*`>nԕ(> {{8(5h48*&]LθqVn?rT"x6s`KO>*(6̼-s˨1Zoull[R}+G\*H,JU伡kDƆ5 Q dXK '*h'~.yWGtbڸ6kV%+-n&m>ӭHZC6XKf5nkBݗa.vl5['v&oDZ׮}oC.rJ;IHqnq\ 8 ~+C֍q8ƍq{#:ԡSU|}KMr<׻>dk~sp:?v›l'Cի,_&;޻rntG~I!kUW@hm|q:1Sv]Rmٻ'=6D % -4=hR\a`Ch`q`y Ƈ* f f ʓ F ` ҝ6 J  AJ`J 9!ȟ\`#vNxQra #vJ|Ba8 AL2ҁb]bNC &"-#bnEb>a%:~i"''FC(^i"d!aZ*` r U,΢paҝ"b5"/uVi:ݔ">|>#bsQ@A r9JI:;#DݝL`>: ?? H-.{4$GZc$I$BcV##QD<ԋ$dM~M:U#PJPQQeRRE=dTT O^[aVnZtA|QepYf$Zr$帴[ùJ%;%I%ģXZn`\U&gbb>b:Ic:fͣnHfDeukffN av&mFgB[hh&F&XkkƦfm.gYt"ofF'_"qq'Q%y0x树$ J'XIu^O`pj'krgw"'mxg9ggg޼']1jVq}ڈr'T(l(r#HFPhDžI^v(7$ƨR[(h޴z((s…Q3i(( >OѣX )i2iA)vMH)P)X arzB)" Bn>)KؑjP3d)V j*&b>O:jF*V*]dWx~{Yꢺ>:}!,*H*\ȰÇ#JHŋhQわ ;ɓ%1\ɲ˗0cʜIfƎ8A)6 JѣH̩sϞ?O&JիX:\ڱӧQjKٳhri&ʝK]k7 ݿ cS}^̸8Ie}k̹ȓ%[ӨS;ƺuhѣ M[>U[+[~[6ٷoY? 67vï=r̫,3L1LYwk4T|r+r#5tJcJMVC.Wq}-n -c7 gM+5wJs'~Z݌Oh= v?ƸpC.”ckN4;{쎍>7^\cun4EvV̿d;k 8W.<~A^e#S5觟=4xKom~rڷyB7>6Iط>y! `+% $1Aw(Laž)1-P f02=ȡiSrAMb7!a OBN쓈rҏZ⥈I_c$1Haf$C(֑(tIOZ!#vMqӛDX8YLr ̜FDgeD|~ @eTvى CuXsE/jOD{@M%=IѬrg 'ZQbԦqtҧL8%q]BqT&(L)*ә>!ERG?#9щĪ0 t=_[XgTH6t]-(SԚ)]Ed׻#)WIpQ dJ\q@jύ4*w൰lA'-v[]eDS:8x3bQ% K]G,Rp@ђ6-Tk#ֲʵ}lHbؖ=-ow[G$ܐ)g&}Iӵ.vr7U)o5@ yہ}/+ҷ.a_7L7V9K$0Jeglmҫ#妉);x%Ml4׋;fpA2YTS{&KfL[qDqQYewى_s ^Ctq18SQh2aw4fgAf\\CײSh=Z͑V\i=>˜9}g[jZA5`VWpˇ`,fFg|W?r)%ea꼆حP>lRσ4=jcVf^ܭX%so#5g Ypl^|QߌⷿǪ_1π^pEO]D(KffwJ{$,G G'||\|M|Z&W}H }ۧ}}rW~X~ֲ~L&<'C%dGg0&(7B`ᗀ찀CX@ x`}A؁}'|#8~(,*^,9.0H52HB4X|wA:3xTHmB(|,Fs豄 ф(oQxT3Y[]]_x~a(oZe8g P3Ԇ &r;X.VCyC$,Ӈ!Aa7VfU8Pp3CJF72@+CJ84n({A{Sk=*([~x|G8(V\"S $'x21ɨC0Gh1G#aštXH9 W  aؑ9͇玓M8}A1:³}蒌Pa)65H 9qo"&.%zxg Y%I.9܄Ԏ&(+*:~7LC59 <KgGY>y@YpxE9VG%Iy.K9h2HT98y$|]9H)9XcIVc󀖂kٖr#BS E({jЗJ?8AT)R鄍_7DJ"V_taْ3 )5IPd碚i׈0i#MYiu gQx7Wɩ1 ^$uW-%ө#B92ndp2)yiy#ٞabKuHfah{36.y~z#D 1չil頥 9JS?98ZGDCm#$|Wz@,^x|u"j%j)(JSSH=Br |TDP=@J̵:Vb*[-2}JJ3uIB1Eg(B5)-B yNMS.Qj"S*Vy\*^v`:/VdS '8:Ѧbs*vzAx̕zw2j땨¨1:2*BB*zCZ(:5~GɫFOj0 !J+YJ[[]{_wajcZw«&y&rȚ1{v}33ZjغAkEa=[V9-6"2)b,ʯ꯴ *Ҕl6 :F wR۬ۧ30Y06TpᐮѰQ98 xl%> @[z2Eۯs/CPKh:Q p:\0kl g˴ud}n q{⺲ڲ{::9;Pʸ2GI;[j%^ṕ9$K`kbۺKJ%+tkdz˷~[ǫq;4\Bz3K0[bދ,qܠ4D=3\K*׳<պ,(Yg=nCF=ǻwy K =qJĤd()7 HJ;ˆs0#,n[5'K{¬ӹ¿…,02 R :8::ly, .*}+XM,K(nl5S\ {&)c.e*g\9&|(? FǴk2zTuǛklGp٘֩{23+vuw EeⱀⅢ≹1.95ntUp [*>LK]K>PM̍ ͌LVru:RH]^rta^'Ua"4CS0%tv-nM J9",Sg%x\(>}naTtN-m'03 ^bԉ:)td~amN 넥Lz4(z&+^n5<2>B8D^sߖnbm.x^u"bg^,v綾oN7/n %P N/}22uy6S/xdICܞUt[J4mrҰH9%^^n/au}~f5 ^(-$p^J_([N+oI/MP[x^oC{5`]qj-@:2q, dRy{]}ȁ?b> @o(Z2UQ6zNoMTO[V?(K_ \ݴ uBDi6lm&P`#cC9$NQbm#5VIv)ͭ1ͤyMT9u'1AK(IbF67cMsUYmmєv ";Yiծe۶-Sشͥ;0yoZ@W/6qІ$l"O|ndo!]4驩ccrʼnȘmmtWo+3Nq߉͑"VWݺcK|#F!I*YlSSn7q̉ysPDU)MJ+2-#m-,3,+[pDn8͊[EN զjmGMGjK6 .Ԇ6 NPD.9Ę,aq_)H' x[Ʉާa>8c8x̥E&MF3:bqy/ F:nzűڛKr6AlȮΨ3x<~{P^a o¿9ܠYƍ/r*g9-)xÏh:b;wRbffG˝vǻn*>pP=|><`ַ} Ep 3c*q* 9j_S1 FőA䍃Z@B)Nػߵ#x.dx 2mNáfz 6MG4[Ӷ9ݤ* >#ഈ?.ϋStBe<8e,ΌOkZé81u G<e0(BB)L"'0t5֭BoһhItxB D)OR#|< ۘȍAnb+JE~ejyVo/A$ -D}(S?<3_M JP벹mr33!8Sqg+\3y$=vT>ɠOEs{((>1|NIB%ЍN4(,J[nqy@GKDžUk; J]t *LQW;'˩NyS d GgoІS*Zɳf] TKpbJ ԔjCAS#E'++Mt_ؼ8%aWRWel p^@iM3Yo, 5Y,i*& VkyCzUVLUV){;V3QZ%c\;q*cuhkK*ThWO)-BL7Xzӻ^ך6}60_W?>_6 jc#MgnS"W8շdBP{ O\ةύG-b.ksv4Xwqi@ rK!oS_,M~~l[*DXTIryUd&\[Oٟ]u)+[o0}; Q&v6 gxYZׄH.w~8;y']i@vcqzdOs,b:dBԞL1`WKSCXU2cFlְp, SEC6avi]Ɯt @M.1^QEo$}Jݒ4eTͻ &uM)rưܝm둹g= 7q2#t4ogsb*b:FtI\-⦖d9|g63X TR B(!|gYɈP[8>IV7.+2pPm%hdKk0 &D <]!|)*iNaa_rovS\$hvY$Dxj4Q ==O½X;+ȱ6 >"0> %>v0.s#sc{A.Q?k?hy#0AXAFhԕ̌BͲAjAL \ !d"QBbB' Q?, ț9>DTjFČG|+: 3(?i!!żk{BHE)CVtE+?*BH5t@+7CT/ , CCDBLFN@Ċq!DryrL#Ʉ,$O< DTG. OQt"dŢ K<ܬHּHb@d4efK{K\ ݝ % P<QNN (AdQLD Ptz!u Q~Il O8R4%&=E]K+mK|KҚˑ˒ =H6eE7SS!L<mPA="!\TrQiE݈!=)5RHM8TYrR5K? ҍRR4N EN YU2mUOy ND 'X[]s՟;`L(bcd]BGBgETVj͑tT$[׶rMy)NMt;CSPTPKל)SWN~UǘU%9Ӛ FiA6[, V(߈˅r}[V@LjY|O|i G]p5(H`BQ:P7T3hZ%;LF)ѼG}Icp;ڭZZZګZZP =[M۵哶-[ m =HЩ-*؞ܹ \]w@)Uܓ`\Dž5ɭ\)p1u -%T ]p0]Bee]z]؝٭]ڽ]݂)_;M -^8^nH޼Qm^x^޸˺ΥP[ \x_Auĕ\ f?d__}=%]NNVv`Z   TZv%[ aU&E5a6_ 5_%+(l➽}5* bҤ6.n]0cr.#$N4@ ފ)fa`9F:Cއ<ԲB?G[vB䮰amE^"dMKPVd+(R.SUeb=`XV`Yf&[\]^_af8.f9Nenfzf?Vaeaued_m _1 !fgLvGd$Q(]Qf)6P;]V..F0f`:J~*sV-`.^`VfmhWZŢޓ^^AD~oHInܜ䒂g#jsZE<[َgI1pc6&- ki)<[!x듈kdf޻&$N iS~n^.F7쩥le{%I..am~m=f涶~fdfއViz_e%䎧Nlwigȶnnn> i6ICP aoM6FP-?h:I덦o[kk/stuFL _p^ gO YwCFoqX pqae .im7f٦n:z.ri@j.pەf(`rr ~g0l(8iMf)ks~s;unoqq^mjsg$`#$%/HI'P,LpIFgJVgK&b t u.1Ou˩>pZs 49buu w~W^$(£XB7 9q#{c )MhD %"Bytd WZsa!&3p>D&2QɄTb*?Jy򕰴fIZޒ%/KSS$f9юlkir$&!ilc7GqRI9Y$()!S8DT'R%e,2բw, vFPF 6BT6ܲLp`TG*M @dTRHo6RKCjsԦ)ϡ<*RԦuP*ZU#jt(p:jcYGlݕ[ r&JIL1_qXt4,; T5*D݇QwHvpj.h@s.31ֲWh-6n(mK4m𷿰E'=iM77}'q;On ʍ_>X~:֟Nu'^phwn#KE`Ƣ&j^fUE%.~qjeTi|ۭ׶{$K_7=p;`y)+a6.a+x.E<Ų{iMV#/zeӹJ7V|v nȦ@r: |%qEsrOe"ύngeZ0k6̱8ӬmoNA1:Ӻ:w_J>w MhC#)CB> w\8]>mv? jQ8.3j5ͯN-gպaF:@-K=ay>_dG;YܑN, K_9n̦mݱ""pw펙Y]s|v7U露<}@ 8SdNC.ʘĭR7\!/H^<^~k̭ZVF4-YmL@ݢvŹ1)Io E/)dLxCwBl#ڝY6P88a //;:M!eٻ]~ qR>K[ɻSw&TxOH7ryK&3RD'/`j};^?Z#j}mM/|]n}/X^9_094L_]rxN} Hŝ͝8_K)`:Y :`AUݯa YLy_.QܘFD B_ }  YEG>_u Q`M$atr,a{X!KD!]F f!m!m˕7ᕡՠݠFȡ'af_"!b"@XJm ɠ X>&X!Fa_bb"N,".#."/.@b0H=# cʤ3Lc=ULP#PY`c3i5(^;.7aՉ^8&@9c 6!:Ja;@ţl#@= 1ZW2U#I΃@AʄA5&$)bCFdMT ]dFFcGGCnKUC`c$`JNVK>?E4IUEcFфQl!ӀO>dt"+N%R&S.%KG>%ETZ͆](nVneit KeYEZv[e%ͥO"]*^G8_"G:F!&b"AYڣcFd>N%?IffTly@ghچh i6S]]j:k¢k9fS'm6pzo0|'g̈́x\U|'z*'DT8(F$hO HD!L(uKzy g{(E'|g:S.(#hʇr>F ʇjRHٌ(h&(*(^hÍV(,K5 T葶DגBH)Q:nvbJ~D)FLL蚾)V*o zr'NUw )'f@ i$*bJt~▤ Ʀ:~zfjj~@꠪[jcTI8ά*jԮ)QYZtiħVB)jGRQ7G&*v^Hj+nhJ2fVw2Qi-@ڃtǢvɖ,C&Dv ,&>j:,D+|+UQYl-e~n~)t\ɚ,nD**r,Ƭ,vmSElV^&Bk=-B*MLuXҀ,z +V%jI-#r->ȭmLmul[6؞rX=-mZ.斘rLxn򭌌n͞zn ,/"S$sM3?B3=;@tAS%;/EE =G4a6Et-gt=4GG174K3ECaߦ+qK4(܈NMDEPoOOLtP5SnQw s< ,uS_JQbRӴUc5XVG5RRtXZ5[R@!5,*H*\ȰÇ#JHŋ"ȱǏ9fIɓ(S\ɲK!7~͛8sɳχ1ѣH*]ʴdС6JիXG>*5ׯ`ÊufױhӪ]Vpuȝ lۻx굩_qBݽ+^_;.L0˘3㾐#KLӚS^mϡ% =isͻKל?=mӹu^μyУN=kO;ӫ_]:ױkݻiGA|1}Ep~gFW (fx *`x( HhiQha "(h#V*"w2DTHF dDy^Ti8Qh\*d}.)dQ:0eX&JZr.&(eyfk柀FDaq9 wPJi~j5h(>Ğ&@)ivih.V. Zn0TD+-Vµ "+.X;l> Vk-mE[ӸbU.k\B ЭkqU.`a+&,2(| (rNQ03 v8,p/lQ37!(D{> u-.84$ǠTY{:yئMvfCmBk&flѭxJvzm~d*5xO7.8Մ[DFמӅ]{5ǀyrA:dK6a`.!{߿3,?d|S'{6 qF2!-ԯ䯿K'e@@:pCM}@?O'\AC@8+HA fpH]Y>¼/~UZp? 0Z! [xQ sPa} qqC,($aM|T(%PV 9R÷tQ_yHBҌg[?hh%d QH4HQ !8 @L"7>"2rM'VҒd6ِWl(7TJ T)"jbwjJX&ҙxf(C4L QǤC2i4f:44'Y\ڌ79MpvQgJP\\';aF>3TyOK^@?N`@-hT.`3 OS]!6/ztsӞt{z1X ˇFԴLm:'wS A yԣƐJJ5s'T'*ʹ*?qO^iX]0֢u gŢ2I"QRPi^+[χ_$cNdm$rw6eN,?82s\3?1jF0f_)s|g;y9{2ՠ,@ׇ(N¡h,%6JS(ti Re jH>oR=g'ʯ>Pem6Dsُ \~sh5x5@Ll {ذ-v\$dkKVl mJ7^U^WZ۱uH@q0QM[a߻-F_o~;Þ of;gZkn;hprK| ũn&vtqO)#/իޓcX8 xe`m<3 J{<σC;;4t35f=uź϶>в[7{n@DWp{;w]| woT]yQ`kW2G8͹:>g{[DǸџχ_:NPlT21lvL<;f!S> ~!cdZȿס+0բq\U^RtgROUd.yb! `Z\ׇ}7/ޗ '~G~b~v~M~7 w( XqȀ X((7@J`т \!8Y#8%X.'X),.d02hrU;8=胪A(p H-hL؀O#2"SS.؅4Q"Nb*1y8Kfq(Q3(;h*0_D8PGp\}0U$U(_7) /G) 8OJa!芜!4~63$Sx$HrRRhy"H>ڈvō;8EC}tLHu(.X8s؏}PR0W0 Vs!7| JD,ȑju$ZrQtꕒyҒ`1h*~5#DP=Y2?y!CY\ ]ҍp7`]HBZ×=)#Dee΢fÖnp鏍Tƙ'Za!9B(>۩0(T)꩓x)?zٌ>y~ CG;`![Yxsr̙7KqҙùҠa!Jki1Iz?tY\!&9kѢ[0J2j4*'S:Z:B#,ؒr<ZV&yu.Z=""Q ̲DK+r$G~:*w$M4U. 8489yԢzj($ʪc8Su ⫺51 $OZJu*#ۚ3 0Qfs*~ڮr22RFï证 Aa=а!ˬ;^+"!3#cENò/K/*z Lkj*<;0>_r|'D{FG"L7 "@Y 5z]kZ_"akcBe' ڗ-~o 1+JjO[xҭz,}Z WrZE[c*-Ç,,,?Z;W (4%{۶.q[.s+u;w[۳'%cC+1C ۼ:[+߫SAw"Պ&dbd9(8ö*B4%6;pj*[>{[I|L+ےS1;*,;~z6ǥ[¬9 ['e{.}1(306L2JRA+b@'B\FܿͷĶJp$TauX_Z[\\z0a|'L~To"",&$L Ǥ§wy { /}8,,Qȳ;ldE쳿;~7Ľdw&ʡL2؛l®Lu^:Cf0y"YlaA(>"EP-1`'&9.^0r1,R>t ۭiP3*RAiqHH&QS~5aZ~6"cԂ[n>bdfCO&Qiݼ#2X$cwN{n(h!!AIq.B2n D6F~!L^&P6-ēFv吗@72n?=F*^B!JafsC~̍B&.~L}L~'㖎!{YNRnT2@칾Jn>΢ o'0.~ َ!4_x:<*^.4]XM/TVoXi~k.@a}Q,bwQ4z~ "On1CC֡$ݭ%*-"rEn?1PNI"_UeP3*̟bf PA,0F#LG `GD$)ItT$ʕ% h&,t>ǧKi Q.e4S.< .@aʤHY%i4Ŋ <ʐA. yC 6$73l!-QY/l#J32m _+{kرeϦ{muo=h˲Px 5Ze^shtAAʔi$Lɀ#+p""8Ⅶ!cpȹ>EM+ CHk뭸 0S0SJȶl2㉷<M*̈́ӤRjqGumvͷ0;)Ϫn:[̂:򌻒| uTK3bU5znO'ap›4p?iw|Ʒnd2ʸd w (7?r{L1r! _-o4o>@5iF8Ƚer\I.Wms%1B)kh:vDRN+ݟĖ)x“V`$Oy6@7I+Vl{}w|CYW_C;cc^h-~;D @_)΁&B9X#k:At!RHj{XB#qk t!aDQ KD=YE/;˰DƷo"K7or4 xGSS!(" 1XH >s\$ȎN$d%/BnymjHIt@JnH,ec=ZrUԥB@}N!pϙ_4~0fcnNq} *H j T'wQ%sU\4hGbt%MFꏥ-4Y9p5mM)v)9jN1X>,u4qSUhɨfE$?gɲR[!|Jш }eC:KJh\yYֵ5L^0ҍ`xP(;2"_Q\N#[*"3+pٶi2,h*ZzV}2~EFh! \c'yHؗ`zq5zby0DA"R-z@&c0E'kJh$I{䀷Jy>72{݋!m8RY’`xȁa,X+ AaL" Cn::HRb{dpxqhbL8 q1F$22dϫd&ɡyo3eVA5}b` MQHy6 K  -C$ъYhFz2MӔRU˗Q0NT .eYʐ̲~oͼXw׽&s<h n0l0ȸ]m&~8lhh,^?֘n7ơJ}{Dd%+u‘CkfRv=>Ӽ5wɎ\:X9yX|m~\ ܙ捾9r>i޸99cO׎u#MY 傿]R^Zz[F`@ͦ*.KL{B|_\xx݊_׸>S&?;? T??C  @0@ג'qPt R 7vqـP5PtPdP=%!q-e2QDQ+Tр\GAsLQ\-ȫyQO8H }!M4$`BQ`R'u#RsSҹ,E0- 2u38aӒ78-:KQB>J=GCO֤Ϭ FxT#O%&%LՋ()mFUU-5.Eս\VbXU5U__MC z*`;`L[`u`$`9` `a^a6 a5a޴dtᶝZNPƂ%[5O_:"M$V>Y&T-إbzebb`+^<ޢ!u:@P]ac7.. ߖa@&BBjd9ydUX'}#;CvINP]-6RKURVF\W6cYVZ [6gc%c;f3&}mPd )d>`fgޜk7Hj&Bkv ffNj^qr-.`wFxon|΋1 >H"[? \5c;M.fv>hNd!CF>bHdi qbH=v^6>Gf6{it mb) XZVFjHIeƧj8똶]?V鋮^CKÉ..5q689V('>oHq67H@q r($;rOrvk38p|N9r._/onx6$}2>5a7G9:o_s?h#tf! /D?O"gtNxt5p~)'*op x& ./O01ǧ GO!%xuʎs[\o]iuv@h~0c/ CeZvvEy ~ /xxxd﨏}hNp*Aрo,7Z@ [vMIe\ꪫ\`XPdzЙilJH:aq0~%tGIb4ܤdir)Jnם!9]}ʪޮlI&0~嗇 K`f&,>~yaoIk&l CނK"n&+:\QވrTZ] ]S*InbŚ\ 5ILq;&A^l "}rVHgvn2k/ṑ˛@~0yNH{GeAvF]xI.0k}\e^~_an{ǖwu6Y+e+3H5ofKH5=Ig{SڧW>R+} F56lk|VEdMF`k)RBHE*b-51 fɃP`U:%>ɖTr%`YDYrh"ũ]9Ģ 0SC26τf9TӚ6Ȯ "Ehȡ\gxډIbs =ϡ@<?ED|eBqPašDgF3nX;J"!)I>tG7SET@MyS"T{(HJb\F:M5(BKC2YbZQv6fmLJV=uQ`[&j nsZ>|^ u` \SP,cZ*.-i_jV g?;6e *HOڃ|mke;$ͭn+v p[\S\.'2 1Nn@_Խ@U v̪r'(Y䝩P.6P.&}ٕ[: 0y3tX̺!:aTXjCs 7qDeQ[bEƊInL>_t>.84%7Q#0*IJB"tSeHfYJ`3y,8{G/z(w;[y\?w̩)mhDZHft m?I3{W4DeXn3Ս 5gKݒ͡L1V W֨I3 ױu vX4 Yb7L{Nߜ8ttVeg̻sǩ}8ЇxCb\g.t4<8Zĵ[.}]ADx7"g^ oA "›T~#[˫~ToL+=y^ 5_ UE|JMEYKh߲p_A@U qަd_5ܟ|__d 5Z^"M$$%-P(FdFLdEZ@U6!+J+,~U#-:m%WW*`XdYe{%e1]M`6ARԍYOzP=MF7*%cajD^iVUdd@WV&, Me l>&ext1&]ijހjkkfbewf7Rdn EҟSfDgmYɢcbedgr6X:Xf&tgdtu$vvfw~ng^lez7 R&9BGgHdg 4'( h(LLJVAJY@c slX͹%>)D⑷^KMCle0^diƇsr&h0(hȐ:Gt(0i=)JiRiY)҄:c^|iGEiN[DiWHQ,i5&*T*$Y6VZ*)iFz*iШi驦**d'mVviZj**+&݄sQLk k*Acj BRoX+`h+Z8Ay,hߙO+Rk+k+ΫkkV^+fʒe@#ޠ`svĂQ DX|Ni5H(ƪVGΟjZF*FuB@̾bkQtPQz m~(,TjӪй\-d.v }N΅٢-ڱ---RmnmtOR&Y,톭F.{Mɡ֭mnmnFt, 6n>뺭z.n.*OjmnR2Boƭ:QoZof/FZ/| {lv̪}c^pՍp7l$p-p[cc s0|y pApp0[0yE (G 07[|n0Cm Ap1>Hu0SX-ӓE||1 #1q4q pmα,1#Oj|$Qpf !+s\Td#2>r$$@%M&&h''ۣ(H|vW**'K++;rҲ-'Z.(/ﱲzrfrYXX2w?2)f 5L30W1%66%2{MU UʝKݻxb;l.@0n˸ǐ#˗6.pHM) {64۸sfQINuױ#̮sۻKN; '>88][ܻ9ϟ4{ir _vl]h%g} 6 ӄ8 Li f 0x4؞6VDIɑH&xƒ*'PW=裐CI$I޷MhN7%7Z ;j%x%(dJN知@~'ig56'| 褔6&(Gg҉ç6ڨ=:fTUt]ZeWک*j5}?m:+p+sjJ?DC\VkmN7xŶg>&+6쮽^eݶmֺ*)!m*<** e_Qvқ0 7o ! l̟ľQ,ƟqéF42&@Weaatr$4-dPGMTScҴzY&6Y֔m6"h-l@ MVGǖt&KӴ8OG 5U_mu[k5 ؐocivotwGJ3tN81ls^CE>eWndyAr{.M$:|8ꩯz'h_' <:zߤ^Fϵ j6'%}^#mbmo3{'8/Wb?/V_;2n H6b Haa<Jmu &X)oҞ*@aHx>-Xȼ0 RX:cHQ/`t5RBnJL׾=R@+aS Ů #6xFkt!ԠH* 8ߛ^u;#a@@z $ D2H*iGBw^K'{Ԣ&#~,GC.}\%#]H:l -/?qeRR6AvK :vFazEm)OO󟈚@BN 700':pӝ< O3 '+~@ SO 4 UhCЈqH.Zs)Z;xiJYZ2iMozМ9OTeEݨ8:Q#-)TN:,Lu0*V4S”`v5ehRQ U*HeWN TזrFKZ =[!jedJٸ>դQE=9 t\-iGkZCkSmZ 1VElxX#q0;oz7OaOu^7UdH'U\ 繎݇tJv^qA4 >F0y&n]{ B曲 辯.z [S`Pp,Kxp0<nkuˁ&;$'!1LLzmqs:blX0Wdp[u1nuݤu7I%Z^́}'wY5Ǒ&v7<۱vkmo/%'Ƈqt;Y>;ݜy+SZ9%Mʗ &2w˲wFL8TxN)4h^:i*#GpjaFZ3׋﯃}櫪ynn#| (r_(ϤHwP~$ɯ{. cyUIή|ts|/~x#>Gx Qԫ>8CN SVWidi 3ȿ5faׇ}Mg z}~'~r~s'2gvW]WS*e'8a8\ | Haz~6YWq~ЁwyW>g8Zf'S*0ւhԶ_w}5h9؀7xW)8f*Rw#B}7xB `Fx8&b'tBLHW(RXR)Ȃh?ҵ,3`8:<'–vp~}h4oXq4sXwz`20XKv'g8(؄_Xbd{'{G)`j8Hv`8%7B!sXtrXXeh0h2Q˘#w21dDHI) 2R'u{3%Yk(^Gf)af&9zy291yv]bȃwMCy`k iy->,t 2PyC v"i6)vw!Gaf֒/w>@:>{ h&6) YvrlviN#ɐR=QY@ɕɑb[T5tdqpn49֕b7azy|~%)* IvƑzD Y9ixV%`aᡖ>VId9_>i.$p9Y$( 8ESY )[^yiR)G 'hɢ9\>k1҅|U*虞}AFIH ;J!9y֔I 0j*X5jgOڙ˱) *ʝ4I6IyjJ=}]t5%#c'ڇ)+ʢpM $4)u8 Ra$uF. D:<2 z&rKʤ3ɚZJ šW=+֥"(_5aje`gZHY;/I$sz5沣Zq~?OA?Qq:awJd@ R'uIx9Zr$`㩁^E*EՑߵĄDZڦ.yt*!Ig!jZ: ڬGԊ׊|Vڡ I努"cb1 ٮ)+99^Ruj撣zOٯ~J* A Zʊ̺|ҚFNz虱#3B23&cg6(Z*jpxUٟ/{"ud8{ww Pv{u[>[;  qYIGG˴ؚ+*CAb[KU$۪ackf*ꀹ¶t}ZzsKz??Cۧw++kLjkz= 5{+}5\Bf"g,73JKyB8czl0`I4bK<BG^}5 G0(J5@i 2Fxk 6R1j4Wvc"-i Zt½ߛ4;Jk4`)EA;bv^:A ܨ|7 < } <|\"a*UK"QGl(,|/ 14[满޵PÙ%{7[MCkF8K?MYkN248@. L3B'\k6)Lio5)dX&ػ/=_aceLgli kӹ|!q#=0m K=̊M8ƩI@afͫaY07iۃy>| []{荐J迍PӥகW>nJއM!9N^ѵnRw]w'y~ N'}2dQqhBF.ӎؐ>^qcߒn'مN ٝ)X h#xm=˂Kd6 o GhA; uTZ?`.:qgR`*QOʗHT[Zwabv/x00Z3yO,./0?2/4O}Y? AACo Ei)IK.L'KnT,VXZ/]_Nacdöj76 rm(/yO{}߇Olȧ*LAAKr -:FkQ+ث$XA v`pC~XE4n$c!E$V)UΥKo1&*72KhִYtQ. )Q+VdW*Y%nJq<;ŊkTٲԮ)s[5=xW_=jY`lRf^{{l4gM9ͦM:dž#$PxvZk[=1Xqqeso? ^\q6tUb/nHyL\ٽ%ж4D%Z\O?Z(ңK:mUjWWb kXzk 梫,R p*2 1vͱ *~ 343@3oL#V 57`Mj[17xSK߂83Hcȹ纒4Dڮ *<" kp(lQ=º')JYJbA.}Rќ8(KBY,w?*Y0g3#=F%XA]Z婝s<~+O"cxBH!C9oi Hnګ$6)Uo,.1%2v2yb*WٙVU,7u 6`c"q@][f1вi$=i?L{ԮV4Ma+[6KCn[SITPbA.c]j^X"V"κ.Ʈ~]"v7Ut@:޼iU/`^W%}}i/xYThV.mІv ~J# GVl5G)joE\fM1W>ދ[xNvkA&w[-Rd<ٽrJl89Y6ҖSg#6^|5< ֺ°wzg+B@X qY\Zu4W鎕eFMOl aXR?MVu;X(c}gMؚ^Fv h&GvnD­p[,:g) >÷H%q7LXvSnx8^wu}]q3z伇{} _d~wխFɫ^߂c񴉺5Ǣv}] Űcȥp渜Mʉ|,1,O_Nz7h薴y8\zњ:_~=]O!vL=T{,H!5֏u͈ alq2Ɔ!5x+pGm8; nK ʓ o27O7<;+-gZ@@>ЋÎЋ#>K; 36S-*c6gc뫭kSˋ4n+4?rKst˱9 :;8σɂ(ۿo?SR=#d@ +/;d8 @6lÊ+A2HZA|p8ߘA6ƫ9ù1!4"?3% (, )3B.L//0 ;123DCS6tC擻9LÛCfØCDA BDPQR-)S5'HR-}Uuݬ޼U 5UHwX_RXѺLּLQKvS@@QQl=mSFuTHT%JTfmJWM]WjWdx%Y5,R)|R$I dUP6dR!HDKd݈l֭!)~ߠ=McUϦ`;bϕݎ;W3&ٜٝ-ʞ%˟MUУͫ̓-صZZx)ZSS _T*TlUַ]WJU g?][m O u¥Å]ܙi\|m5Uթ5ʪu%НҝO,]Fׅ]]G-۝r- m^-z,Y-}h`~%TۄUDڔ \E4 x[еS/XZyIh_z߰# DTssۑ]LfQ_ ^^7 ,>`-XpZUT \K kHkhf58a]kk~i-lՅpz^j L>wֈɦlggh<.NmykÀhD* ږR7mmInv:vTj^UǮ }.>ohhjvN֎h8vnطg]MGAXr&?_iqFlrn 'tp ȶ(n.?vqӆ6kmo`mk!d>Lr6`&/'%c5)*0fH\'q #%R/uf m^f"|A!нLs{ފ[^.DȯCGttMF_Mt͘tJOK'LMwNO?3 R'SOuXuiuWXOY{u3u^+aX 47NHvDgv#wg'hi]vnvJn,V5OO%PQug2Guw1wvvama|bx#V؆n,#'qu95#J*J)ww$5IO͓DATZpZ_`(&es&d &fUx!hq FvI 9y+ Jh):)zR]]Enz=vj[*~E/ i+ :q~0l88p쳷(Ծhqڸ!mc)7)UX9n@ͨ%EF=X]r K+b#+V%, ?p.K3(.V㵊#=X l*sw, 4P;3 *L%E7)UA;oeWGxp_|Vh77ݹΒx7߉]NV3D]Px>_FWV}ۗpS'NsNzZ61^1 7Ȼ{x6.ߔ*O;8]s\T5Vr"\=s)\i}s'?ڔMp[o vbr8*xj L3T%2Jyb,cjPH%^W'F H$ U$۟R9QpX},g:x<HG?kq>Kh@"R,x,bq;g1A"5B,cIбRXXؐ%@YA'5r^$D'Vd56IO](I9>S%T*Y>W1lhYzQ".{H`sR9"}tBj0IySx22P {4 E!j!Ҏt9"aRQ=D4܄}5F&)NEq>]eAc8*Rɐ0TT*U0K7'VCϥK!> M8iCѥH(0*͍ ȣ-THGJR!(UiJY3|Aj*Mt$Dh"߇&Veh-2G:eHB/&fx%oJ@ccv%'!}rrj(@)rf8dhtzHu\>F]>]n'kvk~gS>%WԦ` Z=fCxW{'p*pJЧ'BJ~2~stA[zwubHh'w~wleyb(Up(fyh{g|WJL(FNJ.E6NZ([GhT(&&:¦_&`^hme(Ƈ"hKh)4(T F&H * FrD&9f*rdEr~t*j]jC꬐l jɢ6*<*$J"EVҥf*nhtIj**ѪV!F*1DF>鱖%HN @k H+- rU][rN:r_ST4˲˄LF$BꪶV\,v fB ,fɪ’H,l4lfF-†b~nJn{x./jUBjG6Gvey)1"0=/&҄Idg(jGj1:/z/Tz/ίWf2.EF0/('/o,0d/s0:PWyh=p0'0 sC S p "k gQ App7p ' 11Sf+21(U# jL^1/l,sd.{q_7C1 \csR@&# +q!g![%*23r>2'C{gq|q%SU"+"o#w2+4Z)342-.m*ײ._-q.20 n032 1/34)2\4WNY6)663v398r93&;3<dz<ϟ3=׳=3>>3??3@@4AA4B'B/4C7C?4DGDO4EWE_4FgFo4GwG4HH4II4JJ4KK4LǴL4M״M4NN4OO4PP5QQ5R'R/5S7<vSs2T'T Uk@!#, (*\ȰÇ#JHbB-,jȱǏ CIƌ&S\%˗0cʜI&D6sĩϟ@)(HF*]ʔ%ҦP>JըS6ͪׯ1*vٳh)bLkt-۷pʝKݻx˷_"L;+^XǐcL9˘-̹ϠCMӨS^ͺװA.|0;"Ar߱ Nȓ+/={pm۸{o˳kνʛ#x"=ҫ_zf˟O~p+¿?=!g&`xzނf.x@ƈ$F߄V8F0**hm#%z*xaDi䑣h㍌#dxŏ"XfeJ.Nre>Ri[u$N)%x^| L.Pixީ6(Aäadivjħ ojj] ''ޣJe^@iZ馝r:ĚzjjnNګzvkl"꫰\kܴKVCzüv:ΦƮ«[~Fɒlqlţ \c"000vW1, dRz{!k<,.k3Q8ws?6{.\[4wls:OmAVuí~tؿ|3R Wj*p:4 =}K RWkg$⊋ 5e?vNۈ欻8㌷a!n{F;ģ`/o&^Gm[gw>ߞ;O|C?=cW{}w7|Kz>lS?O1z!|m@kV (Y◀p~͹b?ؽŽ1%4S.P2 k\hL=  afDڎgLl\ŒIqUEnA")e[q#)SxƑ+Kв&xrKOrw4$"YcM2Lg>3H:N ,_(WRsSd.ƕtd9sNt3Lq Okӗj0O}ӛb8/5N( <(BJI1 H 0N}0@tÓaHS =dTR QPEx@ժU@SIZ&& XK_SSʹ7^ ֝9?HPR5RiTQA5RUUr*VTRMqz?<%O6&2vk^Ծy,Uꪬb bRTgMdjUpl`<Кx*iӚЍjy2ͮ&Qڶf,Zm[vٌ&) g%VK7UmuW m ^܊Wilof{v˾~;mw |7v 6K>N:BY 07D "zd[ߚ(P@*C816{+>㸾:jc7Ȅr ~r<*S;r0zݰKf3ז\v|(nLg/nR{L>πz'CkLtrUF8K&o/!Jv&W>V/@;:s>X]RW㥬<\O ӋOV<.Z# 8\K-gYYo pw[>/8G3 '~w: ne6JovY jβvN׎ NJ1.UW&&} }цq *7SĜOAE$ɫ9lWOzz>}jvyx?'cd ߏ_>}Ntt7)}}+}'~?C~Qg~S~_~gxL4!$I7sͧyg_9pX}cR } ({L\x8x>FgHKHx$:q{2woyK8ׂwj~dhEOW~[p~UaBuDxFRȁᄦ^"WjwjZz\^xH}3XexwhwjȆ(j5"SoWJxrn:zHc9⇗ S;.%g7ۇad'KH+lK2ljxovxu Bah#$HoHpa0Nj¸7Xikmn%ȉsɸ h؍ߘH%h_H/xzH}Xh 6e{;{!13#z-t'' K4+qrea.E%GnJYFLN )6SwhS\<䗋'+d[yepmb3Y /Bc+# BiYB)O #EUZP`Y I$pY v.šB' zdBٛ%$z9) GZyS3d zЩBd* zYB:,)kﳠ-"@ 9!s@›ߘ+cIB.ҟ{6>3#:SK-.P96&ǣT*ԙAJ+$5H<͡NZѡrBf*ݲ7v1#q49&\+_ EKeV7h3]WGyo-JtVHJlT%|*LQΨTI'zoJ,:jc($:MFvPJ"*q. z?٩eZ6dC[5ˬ" dP{Jc7N<o "ZZ鮳z ʲ,];TRpÒJhHA9ģGZ 3VW{+ J `S^U=&JH3IC7Z{3 2?ywGF?'@c0&Tž) K-{-\ۿTlc7Q\y L+E3|H, ,AC/&t( ~*%ĽHEZT\fsXlZ\^ BD=hΐX4sC- 2Tϧ<Ϫ)\}l_3 ˼r,vx|70+)ѰG΃\c|ÍÏÑÓ,.!b539m_< ],_Lalǃklm3oq*sVXK, 6T[Gg3ec8ټ|6?s)=t]wy}<ÒBSXWWC_CmE,2R) =!L`xM 3k}ёi=%ֶ=בQҸRK-cz=S܂ӄ-U܉ ݋- LMN ՚]*~@EP"gf'kMmmq}A@0ߜs,.*5ةW N=_ݪ2ݰRݪxݙ* UW|dk 3S6 \7/<{q֐E3Jw~D\2RnU=Д]Ж}ИЗ`b^M)jNnnӟ=V\ĸ;WQĂoWFN_H^p>BNCh图Y=]gR<ѳ^4~69~'+@~oɾT>_n>jTQ^-PpٙN nm^>x6(CWwUqR'pzO+z|p9)v䡯p?9Oɕn ad {tMO?BK*ȸ,@,LPA2ss8p1;c• aD쌰8VTaC#0bmuK-Z}$r r!!(Cn%k'*(Z҉.ô(uDS*d$pNԿ< .?*PPB8*UBG!BI+SMI,`= TF%cuHUYQ5Z37r]`W(֢̆Ucl˰r-ZmpbM75]nX ^"^MT~!xJ;4D nafŇ#׉V5U^ 7YQc\u]H&:-ag^yI3] fd~6˚yvټ Z- sUWDvv>/A담~ pٰ`N*S5!nRb(nF1wy |bIcYh;(r<čo4GtLM Ab$$PrS5 mh` `6(4CAܖ<y[V3WTbA{ߔgj|Jrƾ1IJ.ҹُ~SֿwEuj h*)pj`SxIPl+[4‰0cjܘ#@8% a p/5 7hpӒA#[P<@LCD#ZSr4gO:YY"/"wb9c6 jPE #^B8F@8:@OxHz| f=A2dGBR@0MprK8LyNr@LF2Sj%IE ]@J"5usf/38L 9G`(jJs,A ,zQ(`/We$' i Nyl\Z%nAx j#8TP;KdI('0)APRw"9Qf#T)HӨ k] [J4Pn[ PXwqQ}.w+r tzWU{mC xkJzW5+W@lh}߹xNMgd" `3BX 2׉axPf假"($ތ:Wwxv0滄oX$$e/d+Y@1)nv?i2v{?8; 킿v ž[̬N4Ub yt>⚨8, hx-tu#+l^Z/O1ʢm`S'Y^[e:*ȁz\<4foN9+uDҵBŷB݃:-1\B*<56G+ޱ`ьNwj&A'JScrs/4D_TT%`Gyo;jD7mbSoYx(nq`WDžZʫF,{-w `>6G7ٝ0> ] :>"N([:Z𯇝2c_NKsv!\<_6q}~q'Oul}|w#;ӅiZO.p<"ɗ63Q]MO|x}"~{O~˽s >һ'S < >t9"NS`+?У:Ciz?qԣ??ÿdӳڻ.L#9(C#/K;{  < T>@<99u :,@IBA!?~csAa-TA<?ի  C P;SBdBsޛ;@`-p+L)C>L,/;23>4 5,78|[(@(.Fbt@dHflFxFh<i=vҔ`z‘Ԛ SA7DEEF}HmI  'WM9MNRuflF %)0cUyd|M}]/F \] RXa E|QV5c?'pU! 2E^P_irs֌ˍ|%Y0IFGw䑘=c0KqHC)*bP&-.f/v0ш2QNXch7c :e% ?c@d 1@dZdFV G~Hں}tK\d OKweR6eA!bXetle`|ldei]^^5667cc.Gdˊ@i>dff6f:NDfCh~[pqIKXtVuv>w眑g㣈J.+57h(h1>vلցn&.5;N <\=hhdo.8dfFyT `iҝdimNin6'ImmmZ]RƙF lDVgŶeވ6Ω`vah9cdVN &f֋نږmo^S6K.&V,DnP`tW[\kl˞̆h6j>@^ ?lށmdf|T qnn&6SFpOlv6GlZ6V_fj.fhz.TYopqEoksr~&r:Icppp/Ş&1+php.l/wjj.T4W󱖟B^f;ks[p,pAGgI{z2t YtVctvtthޘ0K01/9q1`-. VmoYힸuw?oF@7^_g3v%(cdd-epf~ܚ.vvRWspGmq_urnq=w[wfw{'`V"~Ǫvv-KL=~O/oQ7xguqsut׳syrrGb۸iʩtc0{Na3Z* de!rπ3+܉XG\'qj)D{ͧ䲟T?'PԊ7j{w' Fŧ% ǯKGɟ*ݰ/|ǙGߍ'}uOX} 0{Hb1ܯ ݷ}}8~K)n)hP„2𡒈5P"/7R#"G,i$ʔ*Wl%29Ь9&N:wVx'.B&(jeJhTRQaUŐ0ZFuN b(kڴFNuD5azs/31dw'S8РA2|5nĞ5_:HWnK1kyN5emK:m*E[Ufl+nc2fV-<{t&˷nODCx1o!"ro]l : 'GM݆n p 7\'vY[EtbUuhuw߁xey顷{'hE_ehh4F .R]zeK9Um>imRW]% ygrWϱxŒhc%@y`A7$| MG8e5XmڪM:قfpp*%Uty'g ZtZh[ڨ5ף=){ZY hæ: jNizA*:T ڛim&Kq+ kKl֢[!(^ۣr;8| KfnƋ/A)ۘZixî ذJ<1B},u/>#J]C&s|0(3O+%:˳> 7&HJ _1q,7;~\lqCp.!l78|w>e.v%^0*@NՓWŀz1ъ=m2*~:zˮN֎z.Խ߻LNa?CN $x70t#H R019r0m`B8Sz%^qstZ{s,->ăY2!qP } 0/x"@0~VZ?,B b P"iEA RpD yA um !iJhB6e, q%94sH.?A, #&QiD)f#U"(lcزf 5n|c3GԱew(b ~%Zزi>ɴ]Pko_IjoY gTQT68~}vps-o rEc$d"o&+>9 .~i8:'=`n1=oݪ:@|h8C`@fzSRF:0(z3X ]w_\*5Wps\:H"=JGӭw6ꈙ~d=[:p@}8=Ӿv(J;7~6}pwP>rFd|xG_/ԥN[m^,;$O`Vzo">=Ke?߾r#\z!> _K[&_|8UeI iIB}^Q(QTѝơ LBTS?!_Ev-A]IYe_=ER` F``|`T4>a*N!(`𵠈 % 6Q^FY`!)1 uz!] H!X!^ Z<   bձ!!=ŒbRHit"!b"u V"vyd#6EGtxL" & :(N) **+>+¢6,-f-V"O_"/"ncb&!3nS4J#pb^f6tc,9RP#Xx̰wt; <2 E2c3BBiЀI4F5Z#@b$L>AF-&8"?=$DRL$U$!F&F!G!3ޣ3IId?Rc5KZLnL*͕,B 1NODE:PZ"cR*'S$)oLIG*@r%aFeWvj^%-,m2@N,,֞iy,`l_mJX-ޭũ-߲n6-TV-yF-֭..V:. #-_Vv.mcnjFn"E.Z>nP֋Te.Gr*ѣH=M4ҦPJ tjҪVj+W^K!ŲBϢ]˶۷pʝKݻx)_sLx+>¸ǐHl'e2k̹ϠCMӨS^ͺװc70`…V1cȑ'K|9ȓ+_μUk6w7p‡7ӫ_}cϾ{̾(q'_v~ (Vh!tVw }-ؠ~]($X (ڡ"~ 䙨<+bx/˜݌4v7ϒLPF)唣)-Y@H MXdiV^eft#`.ϝxx| `Bov9'u~6:pZ@hp*g(z*ꨥ@eȡji5j+kZ;Ȱ d>&,ƢВ*ꪬnj!kV3k -64@fmj]z++ F/ mWپ-Hr <# F,q3j r"p' ˰lmHL+Ҭ~ <'|C7H3PMm9_\i`KcNڄ?v}ocx#t5 C~.%^?s4g~9{Q!𦣾꫋,̻ZG}ί 37ohF/]gd;v;F3_֔>q8/4s_m~&H(V`"6":xpb\FhPkH f0LE?A8w;GP 5A}]! MXB0-t! ՙ6a}ȁqABH$=JTDDttF*VQdWblp3^4u#y$"QM`+[ æٰHIK1Đ.(G&щx(ȏb L yHͤL>ycj6\%slҗ"GbT沘U ۑ}hFz2K7oZR@gTq&BωtV]NLʈO 葏76ISH_OzP AZ΄.}" (F}QԧP@ԢQ#M*kJTF-K[P01(l:&F;aOSD=j_0HNM)TWzPVYV x5a9Kرf *ZӺͶ:V4o$JU*U̫hJ5:$latز&viPDt\+[곜W~aKwV#+A\ҋ**Q vq 2*2h$jFԽYqS5K_]⾽cWr0Ͻ`tK]f n8K0W%=tuobRҾ~E_ZEe`+ X &+"꫅ CXowb\~,1G\ Xm;~8pl,A葦/}zȹEKnG.l+C 2\0'N%s;\eݧLFƋ EhC P΄g*>͑nrRFH)Y}~0ݝȧd 3U fخNs+-k X5n\v1c< 2ubݨfvAz9P3d`]s,\?Yי}VYusl"iY fãnt _MmKx{׽K"_ŋ#AN{;Zk.9&p 뜁e:C91u缩8.NF|r|tOOH,xHy3:>| j dك{NREqC FO|_ۅMUǹaB}>JǓDQ}X_׋ ioVJ?Fp$6Gib,"WzWJߧ@%z z{]2{~h~Gw| G8(4r%vZ±+Ѐ"B86g~~.(Z XT"8C{W^=-j24hsYx䑭ٗ#I%iQ).}E?IMdY)Ņ'C}(M ҟ?aP1OPJ{ť+Z{%dR(E~SGGڡ*;U8Ee5%0@4ɞ/CY'y'9 XD j 5z|ʑZ$j#Ƿ^!*#ZD!E%-./>1z43J6e:`У)IIDZsJʤfOJ0Q |S*DXG\ |_ Z":D%5hjTdҦF39d1XCBhAVC: bzҨ" WEXy ^0rP*2zkU&:,IxDZ7JLQFڟ:Z#yPjJe ʭJx+*%:媙qJ Zz<>0uBZ f2kVZ ]jdfJ.|;9᱔DJ GzD}YгB*.jJ_5{8 r;<۳\Ol ]з[E"K[iڴPQ:V 몵W; Ga[~UہZ` B{S1+CʶX 4t[>"Wyh}۩bjۊ: ,øxkuZhwz\k{ba5Uuk;_mo{KPp.kfB~K›+&:.L˼;!EcKC0׋KE]۽_;W b )кx;H[{?+PG[p#  D<)P)Lۛ^.M'/K +ƒE)*L 𣏕.<K`: <<>\@j%rĀKAMĞ"K̽|Y,[,],d|fZҾlnq\ XiL*S+t@t)C/;Y)|.eG.Ѱ{𬳑~D_sR,\HEe ]oG0S$ /$8FD_f"|p,oC>ηh~I.,Adiܼ͗޼,L%}+ j 3U 2GԍK=p|ڜQW] _-% Me}PD7M nM"r- }]A) SuM]=./} M ݍ,- 1 ϙ: oC>~P׫ح>دnݱ4jn׃ޝӻ> wG2.4UǞ G&`DFGP L]ێޮ >>,^NnQ1%(ܾA۰nMS;{i.bn+~MT-$FH8?;Gs_?yA3'Y]CE~䍾hR$/f9`yO{%i0>_uDooV1ڥ_bOnԾ?OHiԨy#3 -&ͪA UdܹD9vQ:9ٵjţ2yaԋc{nǾu^oI)cg PI##f`B 3T:5 ⌺kGŧrqVJ`0 ҳ@2 ɵfsp4,@rKx-p +|4L4T NӃ Գ)'`OiK>?$P HA2%p< 3 jC?\rĵJ4 Es4sqFdDƠlF~tL R"C;ҩ$l'[RMd-r<0[[p1js7aN:SOv&]rޱsu4I)=*0tN!p CPCJpI# Y}UXg#Ff*״n$(3x`-T2e gj/ s1*3\lrDwjޭ%^CgBT_E߀m&|+Tn%ؔM1FUODcX[ők-֓uۖ9!=3fdo1fmy|j.aV\5ܩ묺֚kyCMEmb>P~y ش?QMTSxտ?ypZ'\OyW{m|G`!'VrmVv9*Ahl6}n]fu\S:Nj׮+ Tջ|_Cvw$oyk)Hw^~yc{8@8Ad +rE͔, 5ߴ4]K<يPWPX@$BS@μCKk:;l3|xAFz ~Jl!Jwj\W{V5AYہVdl1 Ҹaq3x|{_ DWlM "nOk%IjҨ='5UOhtҺڭʲ-#5uŭZOZvM\H74ZMi]0 t+X4c՞ ]tzwz&Lg9V 5-}Q{_)\f և= >w O50rs.? OLBYnI2wC sБŨ41τF9 `JOGgG>f@ L$‰88y&Y3u3*UhFJ,k)Nqst d΃ ъ֡d2t6.YZnND'd4yYOfO*[yYX2n\.lFl& 9и q >ytdBНL EFQ}$lPESipZԠVAjSڗƪozmkIl*_46-ea&5渵ז mg0z&M#;rw^w6"ẸLzEͥwPmz '.1/<:%qWIƸᣢx| LO]c<& O˱vf9悔im:ϔ>z}t:$]8~ p[෕q|:N愇 ' ~vXg3xs ]5^g92}z<ӎyy? /16wsȋ[-ohk^+>sۍs>>>³>û5k㶜> ߚ<@՘)+L0 C"*>AL7 w3)Va!+=°-#Đk y­؊qB4 ,2)$B9VlE/¾Y $Cx wz 4 v!tCTA39TH: C=> ?#@ AB:L4BR08 a‘T 0ڧ%80R-Ѡ8QHѕ@- RQ5}&],Qv RR(R:ҼP%%4'}RRȰzR|@-]3R 01 25S_B83YS7]SDQ:pӼgS-S @T(CţDmE=F$u " :Je KE&TkRϔҠnOUS T/0u1Y Z[\5^9f4љ@Q=5VBVlPVTOhVF hi5 GlmֵVsL%\)-*M3R,%ǣwxyz{-|}E XU#̓3 qL=6X2͍Es?՛ $TVDWu݊UD:3ZUYoYY Zp)Z}%FQӄ-ڹ5ݲZ F 3(R HrZ&U,ØhxYzٻ,[];%܏1] V!mmXܹ Bfό xQ%F( Օɩ%EL8#נݬݹEx-YzoYSN+…ͯez^(@ރ#%ު^ފ3RՍ gE_=]jP]bʕ_hT%a܅ m];;N^n՚` f;਼t`q\6Z&&\j]La]a:[M[a_Իta܍_}o-b=5IbYbiWQ^(~XbŅ7q`\" ڨ^3\ 2΍^cE]"~ G:.6;V m M>!d(j`DVYSF GvHdܓb:Kb Md  S.TƍUE6[7HṥaOqe@djcFfNfe&fg֌hfojk,d d0.e2>tv4FV~婈ez|a^gʍm0FafLu[!hoEhjnެj[jeʲ@ ꭎ꨾e}ȽkޔV, 8RS(lÆ0džn"&@vj&0kvӯ <)kFkVk1b.qk׈kkbk뽞k>3l0nkf_ޅl^lFm^ kԮ6knN؆n ۶v$kk&36@lmT~>nnD:nj֍Jm8(Nk~ZYo޾ﰆbXqoopDnHqQl'6>kpoimQlg??NIGZq?sqjbpj.r2jԖsh~r/1g*k,r./nq@It2qeDsN/ 5#67_ 8h9pލ:2 mO@bT۶mPRǰwV{8`nG W{azG{m|(|>H|:z`8ǯ3ȯɗy|2y}堵@Z&T̓B*Js;7~(BJr.̇k@&\$e*;h&a` 1l(!&R4`"3q#r"`$ʔ*Wl%̘2gD9f: 9'P$B(j Jp R:j֬QrkO -ljhŞ=ѭ[vwV+lx31 M!D'µqcG C,4ԪW|s_A l4RMޜJjլZn ʶeV۷klޣ| ,xyQÇ#f'S|CimsТGY$7 8 kP'OնnƛoRWjH\qAVN\sϥ]@utݕQEwxՐWy砗z {7_}بe_h UZYSh%uL:x2%^耆iTqxyI]}40 Q8O>H5zR䑚m2$x%jR[j%~_ndBUag9lHr\w~g@~!z㢍(PIJif՗qʟ$i6;j)=1)DAjZK+ɕ+d+ = 'X8  51!9Ib %o;.-<++}jkefΪoO`)0|pe-XGlS|qIq[M"\r$6isq˽R39&&3@O ^Qg4w)+tpQ5u4U18\K`#ec{6nvsIuӺ>3-pX*I4O'm @O+Q[Gy#-:)ۨz:nþ~ܲLͶ$n ?bKFR^8KjaX9ms3=lIDցouc 徻i$o[tw4D y XG,(T6i=M|!4JȺr˖`@nfHi%+( 8#xdž9OVAԣ 8dTB0O}b$b{9&3IL:$0'q8%*ѡUMkkb yVHƃqhƽEqm&鱙#G@#!ȉ@ґ$)YAb]KD+e` l{)ZCl;Rbuq*Xu-1ּAzۦ'f 0]6#iclC!+kaNx_!aǮFpTQ$7%>r-⳹~teTw *p_!Wu%,+dT_Nm+FS-u]~NӁ*sHڪGlglzX!s 9$ A/;h2XyťU9;jybH*NUMV MmsO:d)ZģfӝurѠ!y?v..A p$'/4VRR[38#qZZömoܤmr[^́x[L~Ox1Ƃ ׏$ '7ާo32ܶK`*_9Z$rOՀ |,8=n/ѓN'9-u!65/w?^(y6n'o|t9NuÛ_< }bǯQȮǟ]yh*/q󾺚8RgIHWe\f_ov>~h״n[]wc-oqzOxgmG偟 K硟c_y]ݳ4ߋAĝ RZQ_=E QJi vp D`A\ a` z8x`< "QX_z 7*FjyW1!J!EKCb.\^y!ܔܵ.]H _\: ")LH*C(",6H,AtȒ/"J0#ZQ\)bg*⍸",,ȳԢmܢKb,b//#0#2c)#\0*bD4,56Γ6 7v78#?Z[]A>;>FQDLE>fL?rW,QdH6BR#-R-C.DbCKnKL.hդMJQd$NeIN@E &QDu&lzf}e@AI_eYf[j%k%\fƦqvr.rHD:q^'%r*gs$JtJXP'v'ivvgG|'txg|bz28Ex|}&8q~gee(nv~*efh=hzC ](pjGnLR爦XƊ(K(`ΨR׃ύhh&)M) i*)Ɠbh~iZz,!)))U)y֩)橞))**&.*6>*FN*V^*fn*v~*****ƪ*֪*檮**++&.+6>+FL+o^PAE'v \+l|z+fSy봢 Mkys䫾+^V+F l[,lžJ]2l[8lgBNlU,o4,& ,Ǧ[F,ȆLFlɢ*lʪlF˶,^Hά֬!,"*H*\ȰÇ#JHE2ȱE ňBɓR\Ҙ˗bʔE憛8/6ij@u JѣH*ǎC,y$[j} Sf̚6q\*O?ɪ]˶۷d>E"ŐR*K[,cɚ6-ǐ#KTܺw[*ଂ6|Œ̺׮-?,U/ժ?&7iN/V ȇʆj澹i'νwF]ybC5{gɟ~NA 5( \ByezŨ{7}%PM *e%y G՗}q!Z! x'1 `,N"H#Cޘluy˕XV\v9%&Xd\0:i!6J9xUbtzi睯٠)a_ g]r^IgxF*i[@b-b)ǧZ"ꨘj*#Ȫ2ʫr 2(u]+l;NT饙^i:* #klAkI$*,/ڢzmYRmb#rJ&VR碛"0o/\obsk‰ZW>gR,|,/&$&E{ (;ά(3`̌3YzG'?ynra\`Uf5E#10bCѨ6wMoHIZ||R4I0Cs)D)љqĩV%Irԟ7NF] RmT̩dzITSR8ԪV (/iGv*kmR ̵u])zW^T5_5fӫ=oһ/׽M|nBo~#Pd=72 m a Ѳzݛ g M2"U F7NSR:˒Ü+D<"WM(nԧx\aleYeWbWbRhZdؑ*_aF3Hrdp:x8thGJ؊KQ8釈V#%T{t{$@5X!%Ds碌)$Ha=xcܧ%!/#mCxE_)(ݓYEx(nqMP35[$9f4 ^  ""$CiEk'i%䑟X#yE%IH*,i.71)0Cf,R9y2;F=9!%K59p@?st|&)#HW) YQBt@h1x{у/Qhh !1$ki2G0ǐq$AICiJz |)#~%T3%)r RVJQ{fY^z'`=Y$uY%wY)rG" q2(혘$I&ęƩ%ْ)ϩ>I^iO!)@P`扔{B"r‰U1cUl*ֳ:`+4ZLjx1@/1OsW{ J$cy#H4\@ 0\:TBNF)Q "Qb4JWjjsÈCXb'jĢ.*b0Z62j~!8u<> ?*.Acآ.JzפvT`Sz UETY]JHbIX!W"&A^a<3 *IzE/G7ʤsjRzE #Zڥ^ `)bz wʪl*J`2;īr%ç:ĺ9 =̪ *Sj0QjE:!:ZzeOgz2i٦Z)j*5Zq8O*sg=dMM۱@\JЮ*۪,113+57k 9 <6 !@/Tlƴ(Pfa*J`{ϔ&z{2Buj:k=7`l;4i*e bGJX^kAkn=lZoq;Fӧk+6-ۅ+$eC) 1dʽJʳ +<;7⍯sLjf"f=xtL|< \W8Q*͜k: 0x"}JY,OslG& 0RmXS{᪶ѕ1WQhV3(,&y9 Q*1ET]R㙮ӂqקQ2wЛJ)~+H$f0t+} -} /m42MӍb2%O,["2ح0Sؐ E= G Imq;d Oy(TV]?#Y[ `(bM›h֏p}E1@hD*ݓy'vT/y9;M0c+،=ٕ}@1Ԝ= P١:U]Wmգ՝m_aܰ2igж 6 ܚtw=3mGay8M7r`c]ݎݒF ܍m=[=ڪ=Xިުէ >6@߬\ۈlI@lF| # =}1Ꮽ @'J])nޢޤ=mY5-'.&.xߏAQGJ.bLi;mSUY~9|#]NQS=Ob>NehKݛMZ ,u~S4>}%>=@.BzVQ >}n=)@g U^{ n@`ENf~sLn^Եrn02ע<ƌH>îΞtɾX!싞~ DnQ:⭥}@.J? x}JI%Y].OQ.B&2u'?l n?O^-nxzC?o;?$,N良㋏ ?M/oz_ZxMa,$okomo}V4tP `/0a4CF$΢H""lܨ#/DZ!) [,UÆ!ɓuB!3-dA&\iQ2J|dsQ5rZUX!mիJ$$KFg1Bb6[q"Ej]"<4X)Yt^Z"ҘN$OV4e̐ Vlٞ}D"mizobŋe ۻL)RL oD`=4Y Q!x1cU@n8J,]T.S5Yrt((iTY=/ϟj0/a6OqdA\5dM>&^K 0[4Rs0z,40%M[mFkqGRl!jm7T@eE$#j &!-9`˩,1,JJ/)rj<8%.#>@#xُ?{*LAJ& BG1Ar3FctNK1{jEIq5i‘ypqdɒ:Dm8b"c!]=|/| %ITs]M|&$9dNtVE3:ש; |BS<[+߅r A/vP]&Ԏ eCPQώ%R2-LұYΔhC̚On,]ȀKƔ*4&o H7ӭu/;7~xg|8ɶ~~dpe jyT|G^] {mv}%lێrƁ4\DnT^D swæ<7x0|Tpy|A Së}{m$Ǹ9nAgoKLˌ-νtny9L99ù3SO+>>[<3'7۾%39úz9=8;d ;A% Kj=;=$@ 9u#*4Pɛī@S@ ! <@32\#A1iZA3<6;$>AÌ$m۶E?o1)B߳1ñ+;lL;:*:+BB 0A[ؖm%*u8A5D+78\@9q2<ث?>,=5P6BĉC;KDI[DtDDdB-Z‹Bz# Ÿ+D8AQ>R-SD0Qb:qE\:Zl TE<[DF;b4 @F+DCDķ=hlę">4l|BD'Dq$KB/ǥKGudǒ!VLyE{<ԻEA4t6 LA\(&E;FDTBF}HK|@m@%HƏ ősL{Qjr^q94Lc鶬# `˩t9 \ it0L( @ @03'`64q<0l SR܊$ĺĬf w̧X Lf70LB[ż L $̔~Zt4uNShPʹ^PkJL+L_̌LLL-#Q2Q0DѮpBjلY[ֻפhڧ\Z=5E\W^mǽׯ}FɜJ̥ĴT}C5݇%ZOTe]U[r[=tZZ>MN8?A][%^E\X8mށB'd8`rJ0K5{QrI q ɘ䋵 T '' 'j Fx"SḦ́_8 ``(`;0K\`l`x෈`#^\9 ^ > )a'Lau̚f]a4&a^&"16!f $+ fbc D b`I a V FSa1~${3NcNN56.7Ƅ8=c (=6>?@\`Z(d'bbK1rd6HIJ})cNfd߱aca=@%:ZW.b>>b?%;[nb!^N_^dZ,6-.dK^fLvaMfi>hPR*T@ygZV&S9<@bu%nA\~ D.1Ga ,2VDafGpMFhXLkRyhV(gUe'nGD52x6Df raP 8؍"Ǭ 5 6蟎kd(@Qj@KTHDIrjjxބfk 1Cjk^ll}ٗNvIƖg~ɖ ˦̞މюz`Sk@qm&duZŞnZ 얈ﱶlIoAGqd붆n.X꾛nYjTu*oÎ ~VL~~>qpp57Y`6Fp&`vp  ϗpۖ ߓ//oNgqo춦qkG W% `0-I087Djz6g1;:UA-H<N5 YLt.6Ls^hs!M9ϒ:-eӫyK?c].J0n}V?H#eV񵄹RtoQyj&2ل5 Jz:` װo}q5~&H򷿒Y"x! xB& Ɓt3N$N7zFǸޙ"QW9a1IIh#Ө "Ls$ĂUNW#h$$(^D%Ěڇ(p<_(C1vh4c*Mn҇LH%Hǃ1G䣱@ Za,O1e")Ie9)L5z_' FiR^A*J'Rq"yBEY-sI^<&N,yF0#&"'>3BjA *Е5B*}h+$<$mB|΍r7z.Г0=E~ӟh@ jЂ&TKc] QjJTh0ѡ57, H'OΧAiJOj0LkЅ:uEDM"24B-*Z"iSMT,*`t͔ZjӰdIJԪ* }+SKjHW`x =aX0e[*ϸNv*F0O굳}MzV5mjڙho GJ0Ot+Oc!1(" ܆Q0q\w]tE]`kQ.2Az$o{KګuW|R_WC w;"b*/q ld6 >q'ߛ08竮0L+ /$-*E ģqHR/R1ΆABP^0)muٹLg1nݔn.9nwl';77o@[/7x q.| qOCP1t[5&g5r|.C>sxΏ3=?'P>tF7ys]ݼ8;ӮvK::]Ée'v=rG`];7.~7|&kzto /ys̖;VѮ˧}S/)?s/Sֿ>s>/??ӯ?/ӿ??  &. 6> FN V^ fn v~  o $O Z Ҡ, ` !Z$]MǭZa l^R-dZ~at!!>ʼnXAZV>[ab f T ".5B"e4QY#TEpbyb!bv )V*šQ Pa[%++^'Ƣ͢$u,.!A/, u;bx`2*X@!,*H*\ȰÇ#JHň 2jQ Lj@(\9K#0c 暛88@ tѣHtP/JJիXjuqcǏ!G$2J._Ƅ9@M\չ'ѻB6]ʳ)Ԩ[ L]3rQ"nj%[YieKܹ;{ŋWoREpװcN`qǑ'Olrf͛۾腤IFZ_УK.mŎ}Y L8ɇ.?P]߀>*7.0sʹ73pyW.O}Ȩ+/*η*MxpkO0g^WLҧbrGx-ve?A tBBG7ʛN r$ @aM}3x "8 ~iW? !JzW*d CƐ\`8*Ї`'ա8"D&*ZGx̥̋O 4F!jL"GFBr `bq|c@gP!˸!tp [cDDvJ^|&797OԤ,!3_V]y&D(B\Mq6٘;8ǹ6$jL N yI j(1̳3/"HiM[ !oK.o@<)W9$_@v«k-vS-tsғZ7]OH0r $FV;. vsv6/ W㾛"֛ J<9r,~ [ Q=d?;sϻ=gp܁jǥa$d:#շ=gq#n g|! L4xzȇB8f-wl؈ɗy iBx}(Xn.CCؘj#sG*@tAbAv+V+q9o'mvNEu966wPFXjR u33NyuWh1GOC2@٨ ፀ!x6-؎"x6؀ DYIyD  ɐ-%GY9Aw$ɑ_./Y!!y#h(5*y, 3i(5q@;z=@ɏBNEG )'@Tfj\Е.p9d9g/&+іhXp)?Pg56Y'oh5}>)>C$iV Gɏ R0Vjّ,e@-i)RaȞ$؛9Д O9Yy˙_nAQ!Yɍu).9GY#+92y)#XI3ym:ڜ\.N+G5&0jpن ()Y D):+3Nя!R0TprAeRʹ̩)Z2zA)C 5E*Ijn9D1ʡ#G`"Fҥԛ1&"fqŦn*pJ `ɣЩx){ʧ7#F0j4*ʆӨ 'zʩgK sT٦qZsuI9x}Z'2 ǫi@Izzo9ʀSzͺzzmz3b:d*ڭ %pZ::%1: Ymn#c>1)oк ;?J+JjZJ\`5㪪ڪz$k}rݒ+۲R%sZ6;"ȱ H|1JzLđ\lكŜ|  bLfhj<۽NDŽD˝kvLKCg˿;ȅ|Ċ<\R" 6$%EǶT߼a, L"lLB”"ϺQVx;SҔ++C,ЇLлv`z?EAd0Q&*RaFSh"$%2%MJ l1I8|@ z3g- =-?-Ĉ}P{`S"Y řR;MYqqIՄ5Lؕd, p]ILtmvoQ: <ݺ PIq:du8 LurٕcՉ[y Հ!-n$΍ڸҧQۻȷ8u}݀q1زXݲ* &PLocK!I L#/@XceŨ]}u֯γ@0!z=|/9 LLm@.6ؚ"ݍHeA}M;N)- +}A|]9;=;?~ C.>M@eLNV5 %MMAc~6έ e8g @.%0۰,篁 W3c2yxdxB06IC j4zc. PD.`F6C5TPdf}6hrOxm@72(".g%mɕ;5Qv&#.M8P9""n/o~`CWw\?Į޷߷Q.Ծ3e&3~= 9LYjN9cydw*#B l ^_4@K0 0 y"P$_yyPA>/2.v9oYB\^H_'J|NL Bjg^Yi[x9c@ehBg>T׽E3TM Lr<]z<uK0L-%ea6y9E*,fH 4 tQA.dpaL(15ؑ"E!E!Y2IrK Gd\RӦ92Ae 㐒[aiAQLUQU EѵN kY ZV9[:qK[ys4o[ L0,fcȑ%Pٲ̙rL! D9s/NUn;wJR/ p}%Qc/@9 ʔpٶL7OGPA'] ;:⪀[etE5ز56H[$92F-/N[ 0 ̉Sl2 +в2,9>;@Ѯ;4!V3Qz c-%ʭ-8 "Њ8;-뮃q2R^bKR-#-[ۊ>>" :k-P@ATAH\p̄0 52 +p: 4=EDJS#܄3N椳֗knՓO>&M@+C54CGAc^z%U-}m apq>ƃl4RMEUXuuHX;2YVsUJK~r"/LubYfcڣv-[SSܿI5/V@^z`j75}1WS:P QOA6gcmpmVEm &qb: 4Ә<Y`Id(ܩXVg_"lrcgMM{jM whƋ>tȔ^zѦ:/j7` &v;殛e')7޵^#\X 3ťj||*k3P睅O;[ ]_L+v4Яk_Qz76/x`InGYNE/wB'8_qXɎ25 rd9Isvq ~ß&ò$QrT DP$ Sd3%ebEf cd@nF8sc ; b@0j$ @sƚmFB#HDYA!,9IIO<P&@ %M@\%_RA4-* QF=FD&:RDXE+.؛21.h!F1#)'e{vSSTxFu=mjZ֫ l2V -Dw;j5%Lﺗ&F @nru27΍"az#vSmeٗ w 2oDOTUH||W e(nVTi_j%j^9&.^C 582ISĕ.IpŴ !pM Fd7M!.w+6j!ВPSe@XǺӠ2tY[s3 #2fBs4mfe7s ;\3L?"ihS"z"6sIJZ̺=ZKJ&Z5jKZֳq]QdT{Gi^˚,ώ@}C 2st07ow#ފg</GHgyHY>![>n~ B׃z>>;8S0-c A k꩞XeZ&yA\&;!h~! % #qғZA)i4G:%IcSɢاM+5,t!AYÐ7xw9䨽X6jx:C> A Ax鐕 $T9thDGtDIl+! BB3'9B$ܧ(2`B9pB(T)*S-BB5 )"@EQ`ÉrC8|C>C AtBʢKL<+t\I}? O7!QQ}˘˯|TTGPXҿ'm(ROPQUN T]35SWX4Y+78- S<H=E_`Ea 4Vd=:eMflTVo$ߜGLF SRPRs^DumwmUWׅ758O~UQeQuuτ A%T xعEVjkT&ɎɏԐTT+,-$U2lteTuٗxYPyzL ?h=\u @7A5( (7\Ϩ\y 0ʫb'νDΝ1=3ې\"EtQdܣ18{E}\\X]5; %)ӝԅ՝i]Sy ڵ]޽Yݽ 8ܒ[%=ިH^MX\,i\}\c^ ^ފ߂բ͎ͦ )ʉ]1__[E&F_6.^%` ` 0@`Pؚ;N  5 6ސ*] q>) _ݦa56aa _U @^b" #$3eb /F*~K# V@. 12n35侚Ќce,v=N^>n~#^%^Df_``Ӏ-nIdIdbAVYPhWR~&_)G*^ޡ5ȴtDh+0޴|d 12-GѶ))} ?$`Иg&k~klfSnoΥpne!>s>HZgjS w~7CMz {&#|\ 2hȩh誀hv la9hh-g- P`g8wVi]fi<秺g ai-i:i'& j8j~nF &g aif%^;TNig6kxBkR ~nn&ekiz4>[,ujhs~Ukxv6fюҶ>6k^h~mmpnl.l>lVljjV%ilv鳆vf,rkvNmj67okQUG z,b5X(q+yy]ڴt]V1(# r+fTq1@7ܧ:\OpQFy  NWX!q/qP%g/M[q"GiGo-RA$ r!P"w;rNrgrsrOr)_c*O+?, 7(pM#s13Os+\siq!z8s:oǠ'>_A?!"?:BgCD%EoFo~Ht-t./'01P?q,i"11xssq?t'K sVȃ(Zׁ[\]^_ߏ`vb_cYθpe.t0thti3Gjwklm?no7poq͗0UBw PZo:{UʝywÍ|ʘp~rgv-zHvԙvkvm6n oSX&qrxUys A78CrTroyw|VOWK?5/yyQz sxlש׀rQzgzStx޷i+GB\ 0L˰' .{[+yJovqO W'yGz8?%_ux: wǏYȷhfITH*g{t_v'xg{/W׷}};KzXw 01c𠅄 0l!'hƌp$HȐ3Ft`䉔*#lI%2gbi:w'РB-jg ~i2eZઘ[nؠ`b k.(4AӸ_Э fռCM:`0ĐgxȒP\2fšh`F{x„ ~0Ŋ5rdP:ܺIؚ^a~ 8 %Qi)"-& 2H\܈r=J:)AIh*d|QƝ:\&9rI'j:k$KaFzz`p6YTU8{& *U&*\.h ZBAld e@;f@T <0PfiF)wԒ:f/{<5l[$ .lVQkfj˭#. /}84QK=u0+y*az6@lǸ<2o!,-El65Kq` 4-DѱKۗPS}9W#,)0|-ʪg6c\2rӝ+1]߀'8<8!Y]Ӛ[Vc s_6cUvMg ;۳ 7\7KwY0LsJӊft-y q=H^ !<3kTڦG=QB7PtC]TG6֥ub_ۄ#ɯ*(?w pDД6yH`$X9 2`u a Ą> mcr@d6ڱve;I7rHL xHP3E_ԋH@QTvEqa]]ΌÝ3C.԰ mOj `,GAK/%Zn4f#7EU1%-//Jc|_'y~kt)SJr T,k?&C/ 3VĬ1d{M,j\&&Y_ Bnћ/ۛ)rcG&႖dnwƳ'!h 곦Agz"lN:@J 0e{W_޷I~ -F* |&X.F{\FA1a lw%b8'N1O1S1͌Ok2qgcCB̀+Lvl)SyEVǫ=NLIggIY ooiKywd/9gz2M*sˊ>Ӗh@Z'4fKgzӜf wx snh."'X|N]] XwAelkE-ZQ]m#NLv0FN.+,)Ӻn2etJr7ƭ؝fi[*0ہ>x_ ko7.%α['8Уqv|_Pw9}3?>23\:).*fzD KϠk>fK*N?LUgu Hf8/sG:ݼ^#_״j75#aKvqEWWxkN021y.璿}(/ck׺7޻anl[=XO= x+9j|KU σP}}kMx?ew$$?ٴ@<, = K% 5 RF߂__qeqš2 @`FߊTg\`dz`Ɂ` i`j >Y fL : V` o Dڟq A(B0᭡@Ba؞ޡ`!|i!IpCxa=O$aF!%!D Hb bbba-6" >b! ʡ !]-^!9j',if)qI$b%"6L.biq "Yc n !&cH$܄5#EcdL#5!ta6cl#"8€8 cf!::;v;£#,>Y=s>f$t?ʋ{@9F:" $K6$L$DFDXERbiNGJ ہ!AFmd,)DJ KMK$Ldxݤ5Nns L PPdAQrR&d=OSKTNJM#N W4 Z'֘tT4rT"=^OJ`$_^&f`}F h&$,&ccQ b&l:fgghfh战x&ZAP&&rSf}&FhopšprqZq]r~"-'sfstVtuX1fcž fkvv}bxggzXDc'kF}}.(~ֈ~R~fggoSXO$2(bN~gICJ'XCrdr(#%hxhAVf vkҨNJrKWڋ|ni$jt*Տ:m̕^#j)ixlizI^gbv*&N`["va IN*%*-j֣ՇQ^fl*JuꧦM``Ƙ* ΪM4d*Ҫjd&kRO k *+> M턳vVV+.D.Ŷzk"+HK^D&.J.+k ,6>,FN,V^,fn,v~,ȆȎ,ɖɞ,ʦʮ,˶˾,Ƭ,֬,,,--&.-6>-FN-V^-6Zk^kzL#[m~--m1َ-?6->mmmd&-yť h&Ⴈ▗2n:+x۸D~Dj.rxF@nDrl j.nf sM@nv@nƜ2nļnfd0}~nnZ/ .IDfcox5nmoM/6//_/K -/pT0fp0_";p @ẻ /\0o:xo%0!,*H*\ȰÇ#JHEhhG( CIɓR^Z0cʄ٦Mrij@= Jtѣ=*ȴӧPJJUbƍIrI*Yl97ܩhPpEnջx˷/XnWN)K4Yhk\۟q3[k]CMz*`C.|qXƍC/y_5h?AZNBuP3! 'p苡4Pq:Xx "B(a] 瘢*ְWdC0D<F5z ]HFzg!&IJZrȤ&yNzj83 cjH*նey%#=ґ4$ 'IJZd&9Nr0ᙤ2a9Rded)XrMuF{վv}0*za2wit[F~oˑ@YGx .Q^6lЉǤr{o{8= >:zľ=$voE=Z{?s~>zAIw}W}׷LVWx}]'~^g{{P~M~t!{3a7Ny{E$Afޅ[AJD-#P0Bp(e\#"J%\U2Z JFfdA~ .P``!3G"0 .}rmNgk)т18N3X589!C`KXwP(*RHV b]PaH cOfihb)l8np(02HeAnxLzȇ]%GbJQ8`W8(s^!`(/oH:EkQ;f(0vhJuP!$A~X (h\hd?qxngbaRRsX7I!$=htŽW(*>$Ш>Ҙi$yan  ٍ+8(Hai‘*R!hGW,. x:IZ< #tx ؒLHsYY4Uspڴ10鏚X?&$凓f;ljn鐭rx47!1X&Z͢|~![iXI t&/ '@cg!m9 YBAuɚ䚋"J8iK#%YWY'H) f!hgfG*˺۴k$ʹD[;K "bVPp˔(ڶ1J5rWZ׫NEFKH{ KxhkKkJ۵!K^ֹ&(k銶k>պ+VpKS;{[+ K+} WiU+*KdVpidp(5~*+&[5RꛩS$ Z1q.H :}Ϫbv* \ ̵lq,@j|&,$; ˪~%,3' R) +-^N6üYw ˿nfQ<ܬ)&ĝIk,GLIܼKMO,RT +bd|& (Ʒtۯk2 s $&edmI}lk.~½c-=ۋ5]IΝ,&J 12>m]P)1߉a K-~>߇]-.3&~^ZRsпB. mz-*g-3*^,r܆}?ݰ-046B5@NsB^ID.FnH&bQ6 (獰!>1>^.`~b'/Nw~j!i`S:Np*D^3yn{^}>KX!N-TNV厮]_^.cbeN Bk +V0bz=^0&8"JAJTS]f^grlڔ@!ڢ]^-#"D$PDN0>±@5{#3 *nnI^3&6n,~Wt`,^]Փ`Z3r(fb _tv4l15_I9_<PBb)~ Ma2&0qcQW/sX"P?_"* ґ1%>a..r}_MXdtO AEa}LzT` I-ѐCW$N\TѢA4n䨑G ESHT`2KdδRD+䉤eK8A!Z4H7,ejȩQҡeG?(8 F"[6YԮ6ۤqΥ[]oׯMP&,Xa I%41#ȑM"5;~ ܊1bQUoeݺ> ̦-j'< SC'Jm℥5`&iYF;sfP2&"gT凡_Zx9_ ÐC(=޻ 32c30Ҵ2 AVsß`lيNt+~sJ8JNs:/#E(?zϑ ۓR䛯>S+p@ CP,B {,*m%9:@se#DUE\ ƣq4cxq: *":ҹ$I>\sϳ2>,Ԓ/c50Њ2 ;3Syr,"J:-C<4>E@I,Tڍ 4I+$i3} ;NӽB-IFRM)\5VYou]m\p~VXb+BA2T=|hjvlnnivbq\1ʭNS"5.${W6y^v_@yEMʀa}؈miY,6EkWĶE_d4FGM68IuRr|!d]AY sY}Zhĉ6zPq1&@*̄{|b'YH3~6h$'8ܭJ6T3R,ݟ<$ gzg|{z\ˤ`μ7F'=;ROKz9v܊ w[7ͦxG1/IϓJ6Q0)^=6{3gG}`H>ɯ'[Ć63?pR1 swzyXUy-]j<FŃ BB)dc^޲B&+XΧ̰"!CCE4qHDg+wD$!XbsW'fN l`+!Yv5 `e+e{7F?md͑0̜(VIYi ZЂDgAG$TbxIH%!d ?iEo NT6 4#.u^.Ηca\h9b&ĘĒ2GULj(Zbͳ` #CH"q:wNIxU$YO{zQ*iWF@giKo .yģ$jUO-aY%)CW:Ѵn|T)ßggd`(ͺfRE' dL9b_Jd:Uڢp&3EiC %>XDV?5pT˩OBT9ժR *J󟰾RqD:c+ ŕs\]yW3 {L_Q:f4*lEؠ,.)fh}gY Њm+N$+^˒زdm-, ^Z҆5k[ۘx.J"{)v{aYq[њKz;Zo}o[O+4Z0bk)}aG`GpuѨF GK4x`B_kZUƻqompq<Cfn{d^PIYrqeWp*KeՎ5?ÊTfڞ9HZ̛C7v(joBճ`?3Ѕt8DkoîKz}i˶:[Aq;}AyY{5ifm]ٓ>9O ʐ\a $G Z"BʓX8Aߺfxy|gc [Y8<%K@ 42, Aj& A(A:HYAa t| B:!4*$%,@GB@Y€B+-.Cxt64AA&CS;t)B18$L% &EWt DE@\ByDDhRAaAKN(3l4\CokC>xCCSC# >t Ģ*+3DXDH\t0A,/<0edFMtFN OP$Q$E|F<n VtE`lXrs|NjSGdG^tG€LJG.IJC>D3GGHȂ4#lLH II\%2,Á@,J ܞ@Nj#lJ{tJ87y.BXJhdDI,CL8˃۶ɒ J؈SK I I Jx4JJHL\dJJ\̥lĊ|ˮKhl$Cʭjj:;6c(c [y=(&d56d aFZJad5dKBL[(O~P>Q.e:ec^% v]YEd,G._`!bcMm^n9&aK1#vU^mvbn, )pD&r^9ԝ `vn}gSI-IcKS*-FuV Ua?dUiT̗PWxtG(O]:ɒ,{-֔7=HFEY6CN@zFiI 2ijnD6gEꤎ)d¬6"N\jԔ .Jf@2k@QkidiN̝&.̣abN>Klji knil:lllO휌k+kҮkԾxՖl3kk lcmܦ9V޶llnߗfnq>.nіk6mV%V >No ׏jƖ ǦeF fPnn%pJm(Nmnk/!pNj _ގp$lQNf'gv.>nVqo{Wq4O !oHݧƱ{/B@?qz5'_oPOeUEr( 0lh!PX"Ɔ7*#Ȑ"G,i$ʔ*WlrÇ2a&vuB(jҤ2(QԨRj#Gnb ذHƒ*`A5:cƵ_ҭk.޼z)M+7qYG"-RN5k6+׮LÊ%;"9@oV1/زgӮr̚ssQOA*.ʸ㥐%C\ٲU\rУKM)֮.o< 7?N$ rN ztZMYu!Zw x7!ZnowOw~G8t[QWuւ9 ^#=!` a‰}%Rt):P-jTu(v56tc9#aiyq&~UP&[y'#}̙ʡ* R(u5&Z:[E4&q9(uiWg8h衻)h:(\+*eiV|Z9jժ}j>jk+zЯh&&Xp+*>zm$~{/.zZ.+nuf 0*\z/[ {nsfp)7p+kv1-ijȌ_8{* ;`ᩌI+-&̨P=3VgЈ8amK}vM tԜMMՓ`s[=6kG 6ۦ6՜U[=7&uC0 d,8ĔKxq3s~zMk:-qnsFWwٱN{T 0=nROm2{cC7HE[~s=J׷'!_pU^ `?:$p~j &< X?uP8B6!Hֲ&e f-҃%21LA؆I6PH&6+jBE+тX}D )n|c2Q~g Z(S~1ֱ޻#=tc iIph&3MrRZhEQђȤ&9IOJġ)QIURZ|%.HBa14.%_4䥎 K\&4=3<1krxk)}Sr':WN՝3|'<)yҳ'>}'@*Ё=(BЅ2}(D#*щR(F3эr(HC*ґ&=)JSҕ.})Lc*әҴ6)Nsӝ> !ԡUOԢ2H*:T:JTZUvSYVzMf5b&Y1<+1¤ut[Kb+]I=+`:]V&16 ~R, n0=(SY٢%bI͛fj锣P,zf]u8u馜ie]~7l]z)({v(PJ@ d)qPz*ޕu] za`ت*qTk-ފivmÖjꗩ,9+6֋-fnnIaӺ6[ܳ[ټF|o]og\V[ љW|tarz/%2{]0Ǽ4ӷh$&DQHߠLrO4DTWM$2 *'],33ی; @+JE(2P7:i׻=x0b$f̈ oscBp-' n.sǔwb\ BH)?K$ȾFrdHZvǒy&AMv{A` XFBiPVm)Xdp@$nzSx)!m= LgU4&ʃ> G J@@)i(@AYkZ fqͅd㴐9ŘNusgs'z>)T%( :*trCӃH1&8Q"ΌfD(:? Ґ jIQj|4t`ӂe ]hO%U ?5JPE0>uG908#jIJU4VQԦb=CʪX(km[ɩPd]Z$ғL>kP*jŠ:cY'e- Bu:pfɤhI)xiYX(p 4M 7j[sr(] 7 r+(ͅntK^wuvֵ݈P^MoR7* e◴.jkSC,X5j9%|Y mp6/yؾ5-Wf;m1HCPr@*آ~CFwTY=6`^p0?hcY\LU֢ hK[@>34,(KYHU2`ew`sTigV3n74Dnbd@L?BPrpevqt W:YtKe,N{/i7^2ʄ8M'Q^6~sْW,z;Ҧvmq>]n; xOo1xJقs9I˕x&^q,^8kk 9G.PVr:0烽[89JɄ\ikgMp9JWlgc^N_ EzN]։Ӯv=&no$~0 ^= /gDn?ܔ]Nyvli_BsISs] {~>' _'*(Ïwg}|s^mg~/> /z'}}tSw{7k2" _PR{z`r7hohcs’eW!G"47{It9mke~xmhQun4G2!7k|6VzP.0M28tFHq8~׀mуx~O'RbIЄNg$zWXeӷJЅ"@td}f}ilnh("HD gG9Iqv~VLcT(NfȈz13X({(~3[pʸXehu.uvmWvFXgeWH4GzQы:hhq hVbxȌA僖Ar8vxKx(z|؊8Ps\%Q-؎^`bH7h9HQ"6c3u%\2*g1z<d 8%isv%DY Y$Ge.Pop!Vys!26=qG.xk!3{"Pz\J7'_`x+-)m/1y%5Y/7ē.7EIGITKGMY*P4QWiby[?]G_Yua "Czd]Jǖny'pB1Vwyҗئ/?YoyQy%R)ETYu2CvU^2`TI`7~Ú k.HRfzr+ƛK> B>"Ju&H9J LPNٜ C"ӹy㩝ɝy)-AI6y1 |ewW)Bٓ1[ L3FIiP՜o~Su9ƕZIQ3eyxgЖ& (r*.@Y7 Q*ǙN)PCʠtHJzL_N SQ IiJZʥ$$9+7mlvƓ:jfa8o\q Tc,6zg}嘥dZXZ\a.Y}Vw١*i' r&T+ac#DKKzBBs8wU*- ZTṬ] qZ$R ٭x98:yͩʮ1JCZE Hzjʅ4iPy;"OZ+Jʭd$ `Z$+&{V0@Iʫԩw ]xg>[J"EѰ#KPMee3ԑ#T۱mC2Zڵ#a7.IzBx01 4˯tjǁ*z~K 0HKJ Lˢ٪PK1V[X{`_$۹cU{{74ʶ9PsKgwj|CkgQuYsC`WGHpqA\Cr[G@IKNYj0T M.?n u,iB\G_auvl$˴VB>vˡ&!]i0xp"+47"Ӽ _& tvgw@BeK|YŦ&m,o@LZ|.@SWPLkj}<=ǽ\$ Ey]c};/4]}!m\Ҏ|(o*-,<.7LjAӝlJV,0 VqBMFr"ZTQd]*=.WFfR׆ T"$N&U(^gZ}ޥ.^402L9R;N B>D2F^.$ & .w+` U%W~Y[~]aQceogS^LQޟh~35~7yN٩b@>]PqEQ>}>Q/D Tg⫾4sNu>߷^ߛsPb?*VB~c죁IKQ-Nn|tI7#~f\jrcP~6<D4r~\S!vkN(p5xsUy D2E^o]_aˇnGNua|@IK2><@6#z߫:0ofe66c2"2QO\Ԇ>f[qǞro=7@XK^`_bod&fvhj/l1_w4zm|%>dIO;_+y'(Oo5ܸ?kg_^ XA.,XPC}$NtQѢS4ұ!wQ )'4`% 11Z8ţGV\O*f QRiL4fřLPf:Y5* a.\xiծM [qΥy Ø1vPaĆ.òDr"x4sСUDZԩOk2]/„du+4-^̸fɕah.L1.9y (Ѣ%K+M)L3WpUeĊja~gѲ?]0(U0("Dl &6B+KM5PmȘ1 vso M7K&nRn9LR1^:ӎ([xʅ֫< #sBb7 \+ `1,l P61BmD5JtDR3C3l?8j4N;3;e *ЃKCRIHzW[ccSOAJTH UUXmHS)V U'u'9w<X1ɬS^6MgeZ:uv< p+\ P3F݅Wyux|_ $4bkB#]5\xbZ+.W5>cDTF&9>JYeݘm?aVfg= ):h6ZѤ]ĦwŨؗ~u뇶.G W*[ab֛nήV=36Fye5oSVo3 u˥|s\e fUa}=(b\}(^W31^^@YE.Ac0bÀ){~(= &^k]J:wUMtb_LDŽlc NmݔyLn 8<.0 `EYP@,'ͪz ,phBa I4Ks-W`-d}i_pGԭjHkom3bx&c!;T8)NxU|&-@f2A)MT/uRalyK\%D4s` ґ#{H)gw[ni8Bf5ypMl9$O6sK>qt f0Sp&KI` : 3qH'Yr7D˪@ BCD'`?;`uRde\)&X4@.M^! &ebVxH9fv+ܦYMjb3&;M[P4gZuG;zyֳ''H!n J"t uC)ֹXRFQN+IO˔җx b˘Ǭi|nt4=eO*kjs$SU RpբY79g:vV3j|S@a5XJ2 \B~e?*"xh|BP` {2`j,|Kg ETjvf)NѲuigZ4jkcfP"Br[ .qkvS)hLYҝn`R $MLJV%51^zVU'UrU`pm \`6Kfd0ح+*4PƬia]zxaتXR'&c!8 孈K긽xx/nh܀zP=-]S)@tZ*\ж$E'C9.R4hebYͅǪ0jE+a]3T%b3ݼ8o^iɜKqo\zkh!@ӮXpmlt 6)EM B=5nx:+m5vc-YK2֭s<vTj`>k.!}4mg8#πpOyܟS%C2,XDHC)Z6<7=97:l; <=D%>$Ĉ2DDa?@ AB]}׷@a,dF ֘ȵ~̀#*²3S2),lɺ²w>9 ݂SLFٿ<$Џ(TH4xcנ+WWW ~7(ء8J؊X؆Mˈ@cSXXّْؔ=֖Ɨ]2YYY,4ZW_0=i#؍3R.^ic\pܦ\m^%B lc`9ʤ{qOͭIdJd%-aO3`+SޚeS.FTY7[8\9n:NI;I!n"v1f'>+c^Xevfnhbcfn6opr>gLgY^g=n[N\^]dzg`9zgh=p6/F_Hh)^(~hhgߵWXih\N%^&i2kf&|.(V ƁZC f+ Rjn;hV>hŭ>?i#檾&敖J>+h[.8>hk~q{M) LQ^F5ĎXNFLmVmĂjУ&ٟ!ȊapH.wx!ȭZx nn?n v*&Of^Vj>^@ٞmVVD#:).Av>6n¯BΖnm6mC;V`׮ئmoooom 6n徏v ߛ n n 8 ">^={o w~/_ir~(Ǹ)o*WoOrVq?cFfL345opGqq sFrwΉ( O),i;tHtE4F$-_oHolJttM_4&5? Q7 RSG8n"gp#wpWsXYZua0odtf{i tmmT+2Hru8wVpcps+tYG?x0?v~;e_xxiPpSgnr ? wyry..a_tדבopI_V6/x79/u w7zw Ԁ oȋ2_tҮqwP{l{m{ K ||/8H|oc|p|NjWOYi7Gx'p0r<pXw}q7y$(Q:H~)hB [0T)P fpN1"@($Æ.*p ̘Gf af˖'5zC*A]Z)a:樤T Pnkla(!dzNMhW1l/# QgVnӪGe1[}u{L!+0rA%uB)Kr4<6^{-176,7: 鲒6[%EAG'1݀ARQ*֙k5i].2iݶ tmwݷH;3S PB'/ݴ`O3ԗO9g] 襟v˫ݺ.vP޷/{E;84_tCS^VU5_ޝ5M{'Qzu̇>/wX&,@x +eT5( 6.t\)NQq΀<9GܜF /HҤ)2J ƒ[[rP`hNНfʧeFkIT|/dTYUh^UVU=%Y'ZQ5iUZVU \I5rl^׳V]ia&v,'c/UEG+{YfV '+\y hh j6]-t ڞuFImo[L+j mBSfnf-Zލny[OƉ7 .qhai\RkY1_Jֿ 0`>TjHC}УXeT; 8c(lc_ڶ@39Ln~&Oǵg3;=?ʉ2Get˦KBfV',7yZrsyGO[ok6 d_^5ʃrݪ7dUa59؏k!yɁ}'E_޲ou~zӮz<Ǻ{(o_V;7Q[ {~DaΕ=wrÛ\t׀!@0 ~է[ >:%=aТY/oiI?C?(`үBN%`7l% _L`) FrL)RS4f @-R 2 ЏhR ` JxGS D "!f.a4"9"$N"I"%^btY"&nai"'~My"(b7")"*b"+","-֢-".."//"00#11#2&2.#363>#4F4N#5V5^#6f6n#7v7~#88#99#::#;;#<ƣ<#=֣=#>z,#?^i9@FA N@ A$B~ .$CrC>$AFdL$E*$>EF]Fj$pdGNdE$ $ԕF*@ Dՙ!CL@MM$@ȌLdLGK @e] %MQ1%9SIQJ%U RJk4V[TrWU V_YJbW"IOeP\Xfe\R\ҥ]Z%t_V`נ[dF&,e^ dfRYEiŤ!eb6Y)ccSdJdVeZfnhg"BfiIj*jRkBdllf&g%[fٱ$s~n}fppAq'f&ru8UfmRgsZ'jEc&cgwNl&x'oy$zjbj'kk|& gvUgggz"@d'm'z|l"(v((nBxt:(XfbHhPXha^\r)Ŕ话(( ۨ(ZV'~~ˍ((z|WA)$) ffY5Gh[Li8 ҕ^VZf'X ~)ցN$&iœZV6 䗂@*Bh@!,*H*\ȰÇ#JHŋ ;hQm S'@9RZ鎜KblDM{8sy@ ѣYʔiʧ9LIkXʕׯgٳhӪ]˶mC8fmH,$OT%L1i6ma{>}ҥKQJ6r*UUj+Xd݊MӥerdɓOWjp7LsƏ& y2eR1g|sgžسk'mw;/9֭3hPu^)eq+ǼsсVvhv7wfRl}fzg{@XQEVe9䧟r9Q,"Z kzAXQXb|W_ ~%]PF @ 8^LN)& 8#|fqy)8t։Ýx &৔*(jTwx_uNbov暒yri'wIaLgeZ)^e[6_idyץii&pv8**}FZqytJi!y,ɊӍu*jيk7~Ze.kCozcnKX݂Kp銸nT,vl䬳<~QC7DmH'I4tp ԱTmrt51`=d M {3ڃ|sc:=|'N?T[]5Fn͵]-6clmn]3u³>mBݷ,HMCx~kxyBOvۘ/:`^4κz~{5뾻亥Q :ޘKOI=~}#ʙ4ֹ*{7'/8ѿ.4Lז&pD>dzɤy`,H?(:x;~lg"CQ! [7-! oipa dF c%.х1dh8F6̡8:]!H(>0# 5Vs# ǫ.<Q&U<ȅpRs.wDJ㕰lX$%y#6JDpȝ,:љN "N PPtB*D&Q)PF5QhT=s\$X{S̑S5 Vp9.JTH՚RT~C FzUöp ʴge,N-eZTIRTZc'kKZlUIKmʙ0,{ST]\?P׻l{ej_k׺gaEXV=l/-4-8xjw-G rA}\w#Ta~qjw J1D8t] xl &ooK-,: U.ia(H.".Se'"Y{\qw˚cPM1+a:MvgCʷM6͓9eنπ,i` CH`Pf W}pw}sxڧWp|~H7~ b~N~{:~W'rb:wrw'yvvlVwBz&}tw WO7x&4(hx87|6bL4|w'f=g-8}&8sfcIWk!֙>s4,Ez֕JJ ڨ)O)!'QZw\ڥӡ "*$J&Z?sZ'ݳ-/1J3Z+峧ѧFÛ [CK{YR,ڨ:*jxzک}wi6I j8qJ'u wjJ>#r~JCH35aEZ!dP,Mmȍʭ܏БГ =Pݛ=>I IHM0K4 L+FH64Kaxxd9 8T,!9?% 'lMΒ4! ׮V23X OnQS.*U^;9%뜎+nKQ}fꩼ굳n!M~BNJ%N>ӎծ"bqjX/n~֪.M>|u*>.P7p{~?n셎쇮}z튳^ K)gNi -%H4rd/G4gC9</Oo>^~>P8UR^CP|y.%Da:$'Y.{mobPpBhoE*,^T _i#؟`-OL_}J |/R=BY|sFB^!//a#A{ cC$NSb;mG}!V$7)9dKw1eYM9o~iOA#ZtQ.SQNZUYbkʔĎS,*i W[n̥nnyR_9z#\XexmaÇ\e5 VgȄ%wQzi 4ȳC'RXQ3FzIdI(Y|R̚4Q{E"=Uٵouo_q=cߞn]ݷ>sr1ƈ1@Fi,;bB KYR[>zƚM" 1/S($}O+θIN9T :1)nH"Eb<&QI/{/O2:B@$ MhA" MCM 9tOrud 5TI⊫q ,RL:$#&cL(ٛ,/ S1 *%KSMB 5\;_s=@Ut$CTGIL5M vYY zUgNRU +]1XvVbr=d9`AX#5VvkHcP giPDWmoVGqsҺs{.F2v%YWU|M~ 7-9"ؙ5qoX/]-%+d=Fd,JyhyjЗc`f6 ٣|W\gqSn&^8mz}_Dkkxn>l퍅u[5;I<ŧ^opOHqȻJrˏ<͗Bҩ1uWo}a}3u^]L(~W'4č!$+'eq˼,қaa/^wBK 0g͉sW4qC+jޘ6ok_?ȠLctCO 87խ"Cv7jYp+A6x:G(pRCaBą!dІ8G'QDV bÈHgmj#L 8ŷ/"bBt<͋=#wA2hTFqug,Lf2CtEG?98K=uR3$T`hCP*( >ʁe*epGd.#L39(iV׼M6nzsग8 CNI_։3/|uJ$} !U[u+U]t Xя-ʜ3C6BPJUMhHLN@Nyӟ'BQ:Caj7 ~% Ah*XM3mCGM֐f&\ZWjƥ+_eJS88͉N 덞#A;㙧y"u e1Vp.hS)נ%RKVֵ]HE[ٶ,>*´.qqb\&wmPs .?+RV,ϸ#^ {.yQri{e޸t}So77lN {\"s ݣJ7@lS̊Ba(sX 0ų"[>zx7Vy,R|C}p_ aiY!i3U@:yFe뻧@cA^&01@9Svcea)[D݄9H[V}|fy23 gGYt'BY2s=5;> cꫵЀ˳ Ӿ>Y3??(?cx?CZ ˿~Adr k?D/nH >t,d @ ,)@ ?N $k]BlA7D-?Ŀп=A B!C0pp'I+K>' MD+h4LC66|CSĔ$CCKr#? ıCd D|EFGdHI./tMDD&ʈPB`C9DHD(F1G@H Id̐DflpPÂFLҌkp Iȝ xJ1K(T?rk<,̅:V?qCZJtĴJsk@ C KpIG <'`Kp˷KrDD>˿vQLJMLL:D oCǤȜūJLlLMDK|>XK:kiM,،lF !M,NƬG ̄H匊4:)MdP`ͷtMıLϽDCt?|OJۃJJJ4IKHP8)>*¿NXO M u#PO`OdH]ʠCm @@.4Kn—j|y+4X4XP ы.|`I`SD0PK7ȏ|⋤Ց1= KT4|RÜRRHS-.ݲ/rG2݊346m7EƐSS]SSSӹ@A B=THTYTh|I-JTKTMT(-P-LQϒ~R-ҘmzUUߨ[uS8e ^_m9`52a5STLVXxhV@TRaTpsV nVTvWqUr5 tuRvuWwW$WJA=3}4MW5eS}89͋: XSbceUd%A'BuSgh+-URBXo5 [-ن5%&'~`)bM[,eHc 7z@]&_a8`9a&?c e[('FdUd޳md_H^ddXaݽ:mR&X_`U.` nezeeT[1C^e*-]_I6aJFaKVaaa*gf\dqeYn*R78&:f-I`gXrse\V ]MdxN8!(i)8Hiih1$ci.i-iIgb} U_@5yPic1HGy8jL~铖Ni&& Nf٭iii-bաƁd^L^ _NX >꫎~օRsk| k>^koh|x[>k>&jll(hĶS~clǾ> lKl6kNfލ.. c Ԯkk/vmΆ^jbmT>m^lnfVnfnvni^mYhm~ml\Vݤ%$⭊Jop nl[hxnmo+fVݾk}mjxpp p râNs !1r+GfqvqojqY!c( O!r8 C 7X'7(r+r,'&>Zop8 Xs ]89:Wi4 _X[]S ܸwzЇX)w~[o0ޛϾ'a"yye=z'O7ߒ7jӨ-ǜs&t5cpe]e}SxgyGZ{x")h}نnGpH9h=* ABWEBH;VJ-iGLmYT#bzyyڊa9&cHۋo8`:#? Ey}]!RSvY%(Bz&Zi_&Vqʉu*w'鹧tH*y>9hfF%TVv\:Шi+ Ɋ4,1T-Fp-̥! :-vJ\ =^0/e{ ѱX(,&J[ b{z .I妫.p.;/%|rkbcN*B0$U{3'1W,ݸ{e{ ռ4Qۇ+ܲ/lA 09G̳JTCP#t#;-5uG2۸,u=`-hc1Gϔ=q3Mݕ[/W5[_ÅO|8Ѝ8Ks>)j}uࣇ]:sQwj>=Og;ֽ{8K+x+7囟3j?{V??^ϻ ?TB>}SfD#8e/2L a hF }8|I.7= |,NX%d!c Ĕjؓ `a{XτB$";/$.fLq5'?бy#Qגp# HKg8$"H32I#F>Q#&3Iя EHG+d$Y1IJVRs$,1NT c]Dfe6Vam<GZҖ({)if&1iLdRwd-hR59ljh7L\R'>Ξ9p8ix2$c>Чs'?'@JGo恙c>Ѝ޳x(;#*QZT}(GS:M4"(EO6&KҢD3%F޴) ӂ4Mhԩ2a*Vup*X'4KMUJMbZuKW ֯1dTJ9ulm[ WΕuk^N} Kvl>V"E^іSZlgYmV fMlk)7[6-n+[/epW+`b2FwM-rݳnI.quK]݊=5/|^~Ux:2/,>03~0#, S03 s0C,&>1S.~1c,Ӹ61s>1,!F>2%3N~2Rʿٍ2L*[YX.lV"^ל6k9V][-/ӣ hI 4m?芨^ j:ӥo>['RsiɺꬭT  >skڨϰQc;[p]T1u6qjW{h>mSnw/@n(X[؞=vu]k[V) ;xr|aN#[ًr+Ћ'޸;~폇\ %q7:An/L˫se➷͛%ܪT鹖._rCߍrj'=fKOmw΢Y|R:hh0t=t 9w9jt~hs\:~{hMv=o"$F$N"%V%^"&f&n"'v!,(*H*\ȰÇ#JHł2j<U CIS(S:Z9KZ0c IS͛r³gȐBC(IC*]Tӧ΢JEFuի^ʵׯ`Ê}qcǏ?WiRJ,])̛8u3PEeJxSRREvֱ#KL2ò9 VڶoQt$w.]ZvUw/_+ fL;MjeYYN 1+9mСGtuլ[/ڃ2O~yҫ_c˟_Q9s?Wn& vm [lfhWy橷oG߅f}gAљ~nGZixj؝P-Ƞ#aoj<%fmHkybdh8$7U!f X 営=JȌh# l яgG JԤP$eTv*X%k} bi椔N Cdx'UNҧ** z)e1:Z#iVj<^mzҵg蜪ڪ:(Z#V ꡦEV‚dF9屙HB(JjJmz' X (k( 3k w, LĔl2(|,r/\8<7qb ЮU'Dر ȡ|r(r9,\sLs?KBV-- 5-,5oY]2Yk}K^K7l'> tlm4tWnx{|71+#\>+nvn 2.݉k/`mᮘb&vVw1Q/|~<6?Solc{ HR>ߝ}~+ ͐Td4$+I+J0w]6fӟ(A *Lg"T/ E2ТJEkQmv3(8ARꁤ&0)-P w&%Qui{ aMЀiӎԡRF=4l64mGUj^XO1ϱ´2=k`[֖9}+\{.+P5dHjzѿjR 'a ]UWYV1%)%g$Բ8N7b6͐HPVM-kZAlT-mKju6X)*([Vb .\*W%1E%Cf֤}-ɮv벦hxK3^qV] Ty>ѽ [|%[\Nm]n~;Ljƿpw8Y`i ; gxUn,#> b˷=qrJ\ 78*kƯqwǯݥLd[JXA'$pQY/or6. |3ɼZqѐFw;%6SY-IQvC9s6⧖R/W#0׺,<=QA:hc%,KW͹4;<&ϦZS_VTʬg\kܺ5gk2׏di`:0/ᬥOvkO)v[_#_u`8vr٘kf!XфX5t+%1V~Yq3IIsYKeҿ\p"8n/wxě9q=(7\5w<)$S3ӠRV]c?/Bsy?yAz5) Eҕmc&s|BO7?Xַ#ٍ겟=iO9;yhw=Bߍx'}lx5gJ+OOF|L0O$Kt-|?]snxތ@׻셢a~0~ۥ՗BKziF3[ηy`b W}vz:}Y{7~AQ~~wt7xe|~`{7|w|" 2}Ps "vw=usd*_wg3w~Gԁ\C'G%xg&,Di1y*rӇvsV0s8};81szLB(5Vb~ WMgp!Rc xa#UHWxЂ`(vbHvdhfivks5wwF2HчLhxe1L5\@% Xu؂'2HhHz "G9u83t8ЊK8dH:苍 鱅\h}Xj׌npK?@'txvT!i[#MF; *HБ8HDd}8؏jl9)1zT % SH;y(Ȏ $X^y,y w7HJ<~qzGjR`ODSRvn Z Am*5#5/C|nvb$sF&AZG%Йisb2Q IuW@ EjpZVIJ%KMm1PʢCd :"JbȀ**Zzi﹗V1u:Lv9 f*Av@ KRd+GL۴3n*C`$9 iX?c0/bꚲֲB}@4;Y3Xv8N:Q<۳YB.*J{ M\36TkTaZv]U" b3d f k۶/Z5T { ag6{۷+@k,kf3˴O{3QK;~;g2`{3;+8H;2۱;7{`۳G)d.пkǛu(;cѫK^ܸ+ѽQ 1S9p껷 0i84]VA +| |y#eʵ̹_G1R$uL Q pXlH\[<ͣ:&TՁc w,5ٜ^`b=XL&]4fɮoALa71|W$9HM~⡑Y J`N|7!9^#TL~6B.%D^|H3%-U~U^WZ~a 塱|f~3lnn^prnx^z\ҩޡ2rt^>tH뒾> N^ .:Yƚ{/ا^hNjm.o~(N*N80n?ΚNȆn-&tɮ;n?>\8 "@~ꬾt>>XSsﻞ$=N":G>.F_EΫ  R}MSnb\$oAK^ >\K.=C~>^ea*g|n=S e;AnCU>!%wnd _nnB2}д ^b;$0$=lDNEs /Tي2\3VDI{~fzEdvK`ABH6OFY8ʕ,]):yNhѣIbqիX{mkرwʵZ7{8Sl1L*26쳦BMNK5  h6rShzCR܈Zl19.ъ砋Fnǟ3*)sE^Ll".qk.C0B C30dP3 \|2-52lmeXD'& E9*E= fFpQG3ڮ!1)RTIrR(l6+ҮLؒK/ S2 | -ÌP #O=dQЄHTЏMTEAr[SDR)MKNӠ4q]N)uwR"e_%!R5=( ~Y[UL1Tl*΄,Mal8笳;/lg6jI[Ɩmwo%UM$]uuweGīדI }IwbUG 6XXΒaapA2&MN I> uOZv[ U1pfIq֙gL}:hM-w馟V('͊( >x>i5֮q8l{ycB C>og7\qe|Fѽ1/tgwOK E6lR݁0;cɢ4=PT* 6yl U1B42ýx&:$'5zU:>‚!bňG|]XYkڝ1TqnWlVv2_c4A2ƌid%J\X:F0%,sTT+(D/+ZH [hA$S@%.$m`# lBf29˄ElpNnNs:@;"#_RF'nqh']9& fHI J&T$%LpFZ1 /} `S<\1/MyB9jf7 Nsl4':עu^xeyӞ4~f@VPFU$ad'0=u,KGXh)e=kZKE[\WZז^ ʚB!N{jV"\lc'Ⱥfu*f5{4r֡lhCړVC oy9BLs&ThE[+ҤjWꕷb:#T95a}u퀠-~me] Ӭe=CQKnu^-/[^wz?׮mMs{[obኩas6y0"̋ SwpԲ5鮈EcҚ5(NFЛ ěތ ] v'N׹$|>sA$YE, +GQXV;G߭[Cfapɝ9f7ù ՜\Z"T Z$D}MhIF!6hA,pe7$ih2 0w39pĞf E JRΨcj+>g-Zy_Bŵxmc+ZOfvEh'|$0^𖸗'Fn27[ lozc?woBl"\ giwc&%h|]nEǃj$Z,oS<_6GyG [n5Zq/=1޾)P0 nr7tcx\=pG-z6lB c==> 0>@S;>>N>> ?k1@3+Gc+ۿ0==ݳ3T@1`@JpY@(@A:ܾӵC?f8ĮA#)%7|S0#$ld^ >>(>8>Z@ & <C$C3D4dC4;8Ԭ9?=CC;=L"@!0D$vʍ ǝĢzJ ˨K,˳ĉEtI]J ׳I"d+F$ˠl9|ƣƤƥtJLLSǜDŬTE47LL_ D-?zJŹ쉄KNDefg}&DjkVmVn0qr]s]t]v=xyMWWI*}~2 mU XQ,cMemօxHX؋؍VV# NYEI8}U[DUUV}Y{ZٜMYZAMȽ0څ}؈Տ&XjTX RײUR[v-[w+xE[yU۵Wu[ סէUns ּѽMأeؤإX#\ZŘUEYG\jYe{E^+x[ҵ\=M݀]݁m݂}ݢݾŦUZ`]]޴}Y-^ْEt)T`Z2(ϥ^X6m^^FY֭=A EZeVT/ $XR5`_%IMU_>Q`1&/^Z^\u`^ _  nZ`a+*vۚa~ޢn&G[" `& (` bnbyU./1NNE6nn$/Ɂc(b_] '6k_BCDVLFn㠯d3a4aBMNO%`ccPX'`Xe=r.Zez%^^C_\0HInJ&4^8evPmyf [YS_%c&g-gs6dtWuY.ns Dʞ(NiNMdnDУ(i)gC#iD2.jNDZF;~Diic `xi΁N 6Bv>Ѡ`*j^Vek4j'^:iNi^~6lifhk|k뽨k|&OXjlV۪MF~l&k2iӠ&眞l^m.Վ6/h&lؾ8ꕎ˚ǎ nn&n>AjkbIcsQΈjnikmm>Ιkjѐ ѮDooJ%o(o&i,>pmdKkk)9!'  jp"7?O!O~j.~tMOldYqfqqoŅ"oo%6oGlaq*wl+:rq.r01?2Y,rJXtvsHJCn9nʎ/s o/qa6i3uAϛA/C E%F_ GHu8iLsMOut:dS:u(uPGGZuZ􍀔YoZuNr\'ss)<vPvb/ct A&o12vXv5v7oKlzBS\Y 8fP 3_ל%DX$Q&Uv(_HxX#N}/sxvx GPxbxpxdxxɈ CU2ywU02zyWyhHxY܇w7 WqxU_߈zGVx1xyyyyy {dfrz{7rwK_yw(3yo%qzybx|G|ˆOXezzqZ?'?|O|_|٧‹|ܧ-}}_8{' dyѳ/͵oH\e` 0l(!D'hq"ƌ7r#Ȑ"G,i$ʎxp2ЬNv>'dBBFK20TTR(֬ʶrk(JZm,Gj_mer!m0* 1ŋ).l0'WDzP̚4o:iyC3(!(}QofJ:)7*&p5g$vޙ{9=u-hUq;xqJx;q|c2 "1,# Gr򏽑dcQ>$3Nҍ`(@R*]6<,GiJӠRĥb.vrWjn@Kwyϋ^{W!{w}s׽bow{N9$0 ̇ S8P`X-V28ί?؋A߆7Ӹ6:[Xr _Z,Cx1m%]."?II2sd&;$PnTZ%YNy_C󷨼DйG>35?pޕl+9:zDG79W#-h:߹2ng#niIO4d U}FD j=zVt"L=cTDլ^zo]<4Ak`o xl;36rmk\٧bP=`"mn{y>GMnfOI7 vsd]o0 pYSAv[L񍟐V󻟕gʄ&?9ц_)>q_9;έZ"9erFoc)G69un{?:ЇݢJz(dtEkP =~nֹj.R9=^97Bw/9xױkk<<_HCu} Ƈqȗ_^೨cyw+o~FԁmRG/4MEߩ ඼_R:X@!,(*H*\ȰÇ#JHb3Xc ÈCrɓRe%bnI3͛r³g@AD责H(eĴӧP2GUNXjʵׯ`fѣ"ÐsʔP_6Yf͛6ϳEzTP?Ju^la#KLec1ntӱ kۚ| \vgͫ/O &JҦs3 9ΟGM@\2Sص9T{aĹv{.~ܬP6>7&Ww]^TL5z&WVRD(G%Z!RSه}l m xR10(zr֑mesOEׇ`՟l֢R8XfYHᎢ- G"P`v8))>U͝xJLY埀`fyvValicl`r$TxҔf*@+jjɩE6ȫYBZ&ngaI7z0ށ&쌦`k[(`+Joï;l5[޲춛^zҺZ#Қv[vJާ&d 4pG|ggl1 !Wl)( d]#:B'.8\<\y- $tL7PGTW=G 1;zqs!,'2 l3|9s?oFM3-XmX7\bl`- g}k1}Mt}>RlK|rI.8Qn;/x;5#_sv˟7Om驯R.{~sc?:?q*QJVɕx6)KX5[.VI `0d/"Hq@zړDe5UE$f7_M3sL`9/tJpd?ρ(ͧyOXe6 @IEs OшNE׸K!ѣdH lȔ9J4,MUE9l` +JֱX\ֶNp+1Jrxͫ\"h`@¦.Hbbi᱗ԇd'{XV]ATQFUjTU ǰĵ4ydfWSVgMZaV0r\ꍼu_*ūb˔ Zd%[fVlj=8%dqvi-X_ [ve[q ֮+p۫8.r}Js,d8]Ӳ*]ӖwzlԾkK"qn@;^-kl8[w/1xnWpnMs룺./w/aQNc]6q{ZǕ4_ X=1š2Hd|PrG~Yeqlfj. 7stg%kV'I4hbVZЃ&4 pEUu^/eB#[Llx5No#5e숶@jخbmU,5 a ¨^jW\k¡\zkGq.lAN6lgC[Ѯ}mm'vjq[5Ӎ}i4[o7;_d _[V1pA<o+mw[^`%;n9FwNR*?h7ioF9W zpMOҗnqi`\]<~'Y]E9~I_z1 xo\ǭsO뢼Mfzynx}Mo5jɀ~sw}Ξٵя.x( ocҫUPhyvp ؀Ȁ4(Re>UkE#st0N 4 uͧG(HXF Zx8 9qܐ9$u`(H+Ȃc. 7R#t9;3=>W4GGQI4KXM|0PQ(TXWDY؅HXcMeuh9Ȇ$q(-HvtEsgև~( q sgR8QH y*A8eXVc_"ȊqxVxizȇ}X~(mÈȈVΗ+(XHȀ׈w>8ݸA^5؆bdN,}$C9hZ"CKz!^Up yɍ9SmhmV#qVs7,B.0)x.!ʘc*ؓjԀAiC)PdhlJYي#HAWVXIãٖ_B ys'/mtH5rZyX;:gYy胗M_99,ęy yj#X҉YJYY)j'V8Cpr$YZIc#ԛkZ{)ɚ(39B)ɠԙW)mڏ4z1yjÖ`CFcAW C17t9' vz ćp@j)<РJ׉B Ңu9PZ\2^ $ED'-jɦQ5Z[sZ uzvx֧%G:IWK*NJi݉IWzL'-:Mj Zlp&# ,!(h ШJ*wsWD &گPkvkWrqj-FI%^zƚѬъJZԊ*ڭ Zv誮ʮ;tz گtH[9 [ H? g{DJuzıi*D%kl۪Ȳ-T84k4=XSBKFh;IfӴb4UKjW{U2ƺ<-kEG" 4B[(a&G˶f^E'EcPc&}XPa(K-_t#/hPk4S;Łu؛ ^нK9;s'6k$;yP}˺뺹;o澤 {{xˉ4lXK_ G( \ ;srOҰ;՛8כ Y۫ k6okЪX˿pi"DB[ޚ8 +`4rT@2\ [ I^L5RNb&RY> [ ]_ ac^f nFjf;pr>9v"Fz&.6(.3*޲!nNnnNN.[n_G雾589m&1Psf;봎~lNnq.6^t촐R90_-q^j.4ξ[~Jlf %*oOX5tp<xn쥰^iG 6N8!$En_aGS%{@u-.Nf{:VS3!^8X5>"fub#"cu]we:*{M$:-"NAs1E_?JXK9"?/OnҎԮn?2C1/'Ov+ P.dC%NtC5G D|S$#)d%K1#T99a6F1c:r?IatS)S#Y;v+ a %Y A0 ȭRqi *nз/RKIfkַpKIc,pM61e4o&3!DW#]nJ*G5,Xe]MmJbbMkk#̰{+L2,83L;MZs fͶp#hFы;.19瞓&t'"7{9Fڳ +ˏ%9(P.N;p l;{™$ڐF+L@15WdE`QFkSP{ R,DR:%cR;'')0+$Rq $ULP5kMklO9- P =4QE qGIg^z^LWԠPq]yU WmW_("^V sc]Lg炖M7@9sm3=߬OЈMEXm$^z|߄ڷBG蠣.@Fcle#vf 藦{ H~=*7I'1;B p SMQBOK\1OLwYs)"DQOuaNHծmw_H؆Gdy+X}sw9h+G?׿0$" ;+nSA |ڈ%Svi%#LL /kd#zLN{X8 ~ O(b!xDNc (JQcd%=pILZ1hlWp"'FlD+]yFårllCF/}پ*أ5ld3 iHhJ ^Hx*xʒUIKbV,QgVI9,etr|b)>52n-sK] &Yc"$d3Mj\bGvl9ε dcX%`aFKrL8Tqܧ]٘x4F&;/g}hDЂ.I\DndpG?zU$uIWR-%j7s5MSTi&%TxG-LR߲ԟTE!^U"U>Q$kG?ꁴu,)cNVHtuidNΔ|5_XҒaXKdq fYN\EZI%a|\&w^kNRR Nua$`] EhSe-yAqJNx+έ}Mui/kc86 TKw 6r+ D+%FF 3DZlrZAqewЇ@5&T4L화)O#c XsjG<|ZJFp87}l,$[J9U8:2& 򟭭@Z0EюCtCZltd OO*RrOmilՑʫc}WzR:`ie[[yv!7TfB-wK 52Q/MFѼBj:ηo8^p|3\Ufمyx q+ "w ɓw5^T[^;Nj9o&9QI?t'Ď:Uw[XinL`;av$:i˛>7+{@ca芷? Wc=ë=;P \X=B(ӵ3+@>X@>7819t>>8 Լ=l@?ňDD'4D(D)TksDڛD۫D[AL, iN 3P$QTCTtӺX=Z,GŮBCDl%dN AbDCHPLԷMhxELl4E:4/U>tpq4Gttu'(|GNG`lGHB̃HtEJtO-lӼG1O@OTO}ctO]ˬ˝˻ϼϹ l<uLЦDLp@ ʰt  Q\Oٌ v?@fAv0(FbG^~d-^ጵaf+f+d;efcf4g4hfc!"Ve>>b?Nb@^& Z&1gEu[ wÌazY1XceicH}+32j^8ev儆eYNdtd]v^Df>LcFH^~sZ~i>eNHm4f.dإdh̺xݺkvNK^ijꉸFek6v;X^k]jjƌ떚kkܣh^Z&ijǭj@Ujf$6WNYvh^vءn mVvx~6ly,$smjvl6m~n^'Frk2k~іnXno]mhmnn ٦ZF9f쮎yoffiZNnYoڕn}o p?y;%0+\54 #<[&Ȋ^yE@_X)'1x<r/.gg7 r!rS(rDP7'%(ϩ)rIrsH?.sJ32H5ZCm7^E =Gd#9t&tl(t8t)r7gqHIt`KO.MNO];iRSrTsduWO&׋'(u[G\uh&`_wv*cdwefgps}<rk?#@j uYFstdvxpL OPw:RwfꛬֶuذEǒ`,ڴjײm-ܸrҭk.^ [o40Ty41A'_d̚sNCdТ~ӸԪv:*,iv6 y^/>am9ҧS2 xMbNOۖLe˚7wѥMVk@eӶwnp'q&rT : q]`fbMŘOsz魗Y{}_hט}F~ͶQړ 9$Eքnd݅e(ކAfeYY|(x -:bQG78 Ցq9'%apؓ $PZ%*i8(9N5Y&)zgxzޠd)`> iG:Zfl֖fP)Yv*,jgwa|jN )j٬2حzY W){u|QN R>$ĸ>Jk;[){1Z2ll &< pMZmcܖ 2A/+2c׮Z)DQX}5Mn#TQ3d̩e='Jd*r˅>-DTv5,#m^O6cمެJ ǝt Vg =W~zuuK-Ud66Us;;8{; I'AQC ѦU8s=>C6{ϬnC؎;;OP&P -TS=az9o|3jU#wzo~ƑBc(C5u]7A}_Gp%Ԣ@ (Ԑ|DKAA >'qy&H@"(*v9\_wmbwg/jLݘ 92vpEj 1frwLWȴr/_𲗢L")KJEtcg4PM^<6yv,rf򜙬9*u4e-|\LS/_Xn$`; 6 1>Yb85;RL6G9z,d)(P2X9Nޒ%'SpQ>r-s^2,1f>3Ӭ5n~3,9ӹv3=~3-AІ>4E3ю~4#-ISҖ43MsӞ49\dԦ5EBe>5U]eVեn[ Ow!a[ظuwk^m(6qldWn6]iS[&%hs6ݴB.4qGݱ~7u6"/+|[i^wT|C-C qL58/>e[D߸^Eq$wO?,oN9r+P9KQF⢳0Ngzm-lu9{s\_я.eO?{ԧ[}NX_yg' <^iW{8q*Vh s0w(x}xĻCր7>MY|x=c(w_^/Ry=;ž e;]zqj#<г.t 1~J2o *FeR韞./H>d|#7?_8D[Eğz__`[b2AH ~·>] ۽`u _ ^ F r" NWndpF\D ΔRl !LagHJG^Fdl!wxFI![U!,Ŭ!Ɲ!aBj!4"[!bZ""B#>̡M"%vZbbb Z "MZ""N#b{@bH+6G%^f-!..[bZ0c1#g (2E33"ܢ b5/6O7zcc8E9z!ah"'z"8A!XWh cDN$>*\c^?Bc:J:Rc;"A:Oa#xCCvADND~M֤iEBFj]p$-d4n4vb5TI$U"zWSꪬDh$i+yylYV@KvRUU(Vk-J&Z뢷6zhV:,B*~w>[sy* I넒nzlS| ?y &LnV밺FpV` Pelvj+fG٩%!5}m[pqt`c8daH]tݷ` pvq, !@c ~s4iKD=5Z\{5GMgˍlpߤp9+ٍ7{xnJtOA!mk-a_x9Iuyrm-|u_?P$5_^sd>z߫wcOZdz%N|kLtu)O}uj^j?nOd3;8-| _0큿7 Oy4X:2# %E+ (!!|D4l`G bPD^7$ω9!(NlU ǀ>^8gFt"|;#IbpQx&БImHA},FFFψmBgɰvѲu axN.w# ;'Ail*u S]-/\z1/{)IVu:Yv̤0}BLc^\&ۚYI-.v B:mr#gnƹK_Z (.ȓ=oI,2 ']O4% A{d4o eh9zӗ_P@ԢTHRýW@ժE-VJzu` ZlQZJB ҏ~@׺L°2)4/yɇ y5OHSMcJ^ϗ@jPO@ʾಘURw3z}jT*Z57X%VծUd531u+\]#/+WCEY{yRM]b2o}'uw1I|\ 7Eȏ!(Y/Lv2,e }²<{y`jzLfXLh.eۣ6bб@ؙ)xn>/Y t e)SAOEoїNMbo+AA\iKә6=P6q5IAh /@Xq*W/w l ['Ćtg$mZ-O&whS'Ѷ6Qj;\?v{m H }+F7)n kȷx=/s\p4\ C\>oorc|Ǘt׺TFU/eYw%7[[< z̢7f'4Pԫ> [Эesu`NM;CqKwQxWھcONԥ%?~2ɱ)w!aO^|g͎~(|)> -ыzZu`t~or&:ɗS|۟?)m 0X(* ^$ ؀eзbcv[Ah.4FRNqjLQW5[a.kVEd!Y/(a5hM% @W*U'8ߤӀx 6nVu؁9Y4JPu(HK8,:1V3hCP87KV@YB*D(UFIXG&R8UZ  +]s_X)(dXgA "8pP"2PwyjЇ 􇁈hTMCX='x\DŽMHOh6TX(щZ&e㱊VӊthHW)،wXAȋc#HȈXyIX:2#2f.HvWȉYw,8!"6jl(Jn؎)S*Ղ#8ՃPŨ7| pӐ98RMpKX7y&X!E%'G) e&h135y9;y=3҈^EiH)” &TiVħ[Y%򑶕3`\t6DF؆#Е:xq :hk 7 y~yŘz$yi&fQ2eGkp)E~aq&>!XCy?A*yE ћIHsI|ʹuМɒ,fәؙ?8qu/AI癇Ia>LB&zG%ɐbb.ER )ƙez&X3)) j Hznl:c)Cmi7! ]#*r^'G[Ѣ/13#5Z9NONAڣ> 5fRdr7ҹ 8kPWZiY[_]&_9#J7zϕ"lڦpZr:Iw*!yZɧ>姀ꟄUZ%#ʨqIФOj#~ZWz6icz*e]AQ?R*cH:F#UzHl; }Y6u!: wzn"2>#T#b.hknX5!F&Q&{54d,˓{::A`l캮*& Jx`Sto ۰E# 7%kR$ѱto!##;%{&-k/kx44kGH<_dPr]9J;L^aKe Yǵ)10R#ȱ41)otq+tvk.|;R9 )!=#?[CE۸Gl+:; {ە{]AU1q9egۺikm!qdIJ;]{+4Ko+dǛ{!K"""cK;5_oXvup 1qk*t{[<$[r;knܘql K>kP(Kk,&l7! #%"'lQ %/1q3\799xU:!Xw;\E,KJ|YĈ8nHDYţܼGRz+@~BDF~IVKM>$OxeQzY^V b>dN$\j+n-/^)13^5q}uA..ޚN>S*dAmj~q;tG9 e>W^ɫ׭~&+t@4J]eùn%^5QvKPNTM7V|v^zlRA~=5nMN^PdowL>N^T x'Akn.nN6$"#?)J~{("H]1?M'7ENS '*[AKOP/_Gq_q!o\{,"ȶm2)%.V-_/>n&+GHC: %R"G9IV>/y!!&/b_n1Pկsx^O#>tGCDP.dCLE)5zG-DLR)Ud˖KdtQ& 9 lsOxdRhΜ.%ԩPx>GUHq^:a6ERȑJԮі[q傠[]i_mct!\ĉJ8QcF$O.Rҥ&4o,.gDF"K>ڵCƍ/bԘGъ4yro̜x0v04ЏFjŬtRfǚU+ ^';6yfͮe- św޾5n bRk12*΂4?F# R`5 _Ӱ*Ml̓tۭ~N鸞[9禀(HnMn,2No<3p 4@#T,B `5 1xbRݖ`ccTy>?0dtS[Nb9kfftZbn5*pw\sMY]]xKEzS_+ X` 6a+&F;%x7n1Cc7dJ>dmT8gmg pCZɲha6mtו&Wy`k{^w<6{>o/w3/o _y^6<ū~! r)7r1?: 輂mxZ](tRϚ\k.blommqÂG&nxn+As,q?\*7m|P)ҷ}Uy鬆:~sǿ.wY Mn  W`vA()`uh݊L6.~*tzBD~a;Nl#vm{[8nGl09щQdJp_ %d'walfX %ٌc8~6keD+R xĜ7(ٍ\A^'DHMld3[SQ@ӔdoV%3yr0$'j*VǺa^G_pO|vk|?_(VpPc&1ˤ3-hn y%m¤ \8U6N ;ݹaΓuc'ڠŗChBЕ<(z̋Z4{ۨ4eu))Fq~2,Z:YN5kekky0Qfu U(:4}!zѩ~>Zs ڰmQA:a6ikukg ۚyZn毁M`a64EqNﺞ+p`^)ߦjV0Z D=e&TY q i+k\VB3΂pcHNrqc 3CZ@|d$/D.X,&CC"& WxD>*W4LWÒdDܤX(܊383qsc3XZ豏c!'mSE^@ ` Mʓ`{1XC$e;XJMì1̒9zL$n.̱>->=0;?4 ۳==N ! A 3[4%  ӊ \B*4A3ATABtΰL,3l j @ @*@+DFa?B=/ĽC'{BCPC#6TTĉBCsC4CKC ļ>dBsBBAı]^@aa7(Fc{]( n'S,Z!𘉰{7ɗ+8Gt|qĜcYG|}G[E\\F^_`π*=IelxF$FaB-HlmnopܖuԀrttL,)Ini{zGˆG3G5GG|G^_` $ ETHHVH:-Ʋ \\ԀH#Uʮʰ|s$XamI˵lKxKˈ˖Kӻˉ˽4H̪L ̥lʧd̊t̋DȴJD o pqY]DV*BY֙|Ҩ# ([] H8' +RI0|IB)ě 8\-&\yZU1ښZnZ[e5۲E۴5QY[ۻ8&[[ۿ>…E-x\%\m\\]Mԭխ[Q۾ݞ]ޭ'M^0^uU^Z^ Ϡ^\V5ݻ`[p[@_(=M_%]]M߅X_Z@ - ` 0`2IA/` ^\ݛ_*1FVց\\6U6F+n`}` !%ص[# $H&F4%T*+- ac{G)qʎ_ݥ LCKd[sb̈{A.B\J/FGۂx @csd O^QP>fQn*e&5_Yfmy0qWfX^YZgfY 83@P.`fe$'AkmfoNpg=DE%pdxey绰g  MVNf^ghfd@h4ٗfnnhU.gEdEaawfyVv1fcMiXi:/i馽՟fנbZN^en.} ~8hh&噞k&0kerڴ˂ Y,m6&/1Cۼ ^m p[Pi[02Yq")V4bynl U^mٶ&6AXm&> 쪞m;&n"n(MfnN鞍n0hퟘo&=$.h8oH[p0nlo`nwnoo/omonEpcTpMQqo^YVqqpgo ocC?f'(>^/IXipqqfs.ǐo6Or^mrsJXmF=C8sY'>ARft Gs5tq%n`_uMMtվ ?p&0@rQu`/?0W$Y>G'r6\7^u_vvC=W>fgWhi_ jp0pl34/5w_qs?wQ3/wo} x}}}[gB}}7E~_)Pp::WSN{w~= ;GAzģm|;޼E/7h}?|oN~Zw>y A>ͯ ?c|_T@.\9g`0qܠQ/"< Münw<(DB78"%:N|"D$F-j"w-PBb8#.o"M0T`X"(Gx;XF%61P\c+α4HGBqI8@J Q 06r$ )KbZd2~ҏ$)M)K"rarK2𲗭L+Y3{l-u^hZa;&AnKeꀙ=0ӡ1$*R iԄ&r\*F*UB,S r5~Z*XհUsc-+ZӪֵn}+\*׹ҵv+^׽~+`+=,b2},d#+R,f3rlZ v-iuݫk_m]o۶jPgwKԎvTt ~K qo\2 ! rc*x{=P+Kv@ݮw3MzK^7/}O`5-߻]E0}A`7/+\ c$?<X$2*/zK>? !5fʍLx19d"xs&P~@|"Ә0XVl9S&s,/YN,e1YHrEuYy׻@1І6ԔDᒎaMs X,ˆs4ѐt)-Kә;=PuZJ S+ uX5Wk uXh[g:כ541le{d0 mH+ִ,Ýq+Wun[V;Ɇ3;L7j}#ߍcD3T *8po2 6I}kpqLF1> f'H ! O#gf𨦜-wUO1?hyֳ :h&z}!Vwσ즋juY:̸u^]d/{BNجn w}_;ַ󽓡4;"˦M?|J4>WCEy3lȼ?q>wW]?|R]쮇ZiG{D 'SR2T m,fh!r!*D:` E!!Rmԇ#Bb$N"V"]Ad)}J"\&"#6*:"" -{U )j[ ˸"FHc0 8[11"+*c2#->cxRmX#`6? 7^<_}8L9c2a;b4#c=# ()?9@BAA:D@!,:*H*\ȰÇ#JHbl3ZȪc ɈFrq(\Y[04@f8O-@zѣ8*ƴ)PW4AjXغբׯ`ÊK׌9zQ$&'U˗1g֤3N>JQJ6u iUb3k̹3E7ZȊmۑ%O{%[27_ &\ĊSknd)[칹У;M4iUMuױgmvnݻ  7[e,uk]jr&UkՀ^z{S|U (Vi xM\s=F^o!qr$)䐜8Z!"w.[x2F IdM=FeDi晟 *ɢ,9d J9e]:vaJ6̡i衈ةfwx3҈W^g\.'qjF^Lzwלtdxf馝64"7*+lAkl>&kβ̞nD+m=V{ϵBkrøvb:;%ަlcAᅰ,0&,>무VK-nmzn[KҼދۿ 0#p <> H' q4 PGS[7Xϣu\wmτ-6<]6*hly7/S xc߀N:0c5KDeF3A#<9G@J/QC=Usu[wЈ=g6msmwzw]~#7\i>[[i9Kyӡ>u{]$Pz׼8fS<4;nI*Y|4;SζRxLh6Zִk".|-aԋ6O7=ņVQ!h#5ؽ2#F, t/|k' AA [k!xx+ԞI(& NW=hWStX6Fv(S~ 58pE |=&'Xn`oAX0(`[ uj9wgeg?B?N}=]ʲmJ[$xmlP!QDR&~tr=c[oEޡst}}o|#ZIhii{ wG,rW|nrwuw\C3k{k '2P%m[dަ](vkyTHF`⬞qY#7]JZmdҠV]"eօJE@]vۭvo{q~=x7IA[mg䏧 [gw;y]x)z觜'QMջ]'p'*c>{o~Խ-|?|yUGr7VP=QЀ8}Wyv}dOf}3P7~zlw~X~Ws]6w7{wp{Sf6qG|f| SGf3 C] Qy8ycR8?pX}ϲuL 7~~rWw-;""\x567x~"WC"C P Mxq7X Y ȁNӅ"HezziUkh6m(gtW7F'|zȇT8ACٳuAP8ȘŰȌj2x/0Xcd|g`RU:M3.`P2I6bT~P8qXCHPQG!  Mޢ2:48 xrQ64hi8…8D;k'T: 4>`瘎6Z684xȏ4sC~hc 9ً ~y *4|D&(0*ɒ =pZ$4Y6y1I:i<(aT@YnDYqHIJ) I) %Q@BFFB;q8badfXP?FͶNی82 WKkg-E'$k-F  +QƋw4Ssc[nݻ K~`諾 GK~˿UYk\LK캘{ޒk{=ls ‘$BGi*,{+<F+b4\{k:5++[ġ LƢ9m mĚ &Q [ |<|X = bL,!n{" XqT ~܋܌ Q]ZpDrݧݞމ4ޅi=Ϝ UZ2p-8:~<OκBNRqG^m-s [aǩn=^=}gnO*D,]"{ךP{m|~䀎>NH^ >;TNh-N$%ehnj-TYZ벮>"\N˒H@T0pGU-(6)7onq;]R?f:MQ <.-LFbǪ[UP&/! BqTV`@AZ&D61dQpN E~:ڲaqRUl :_R?[SrڒҰpQ!$o%_B.Q-?SZ 3t8:J>/,@_5Z nNEO?/TUO X 4rInhoj (suo]~O9tBDDPFI/KOMoyS/e:ON  ɬy:N_iR3 -dXCl%X1EZ4n8GDdȐI8TRK1eVYM9u)'̍ :B ,-pBZj‘y:WaņYԮ9r yqbT.UШ}Kx!\x.TBx-U+#Ȍw9l朗*1С[W3<aɅ!JhcZ24؍P\rKm2gds> Q-I2u:W/Vam߾c@vƕ7]{7^; ĨZ,l.ۍ< M4J;ʹt[!\{ 6n(z(;8\I9FκJj,pt#ǢϾO?%+90<".,37 ApuRUu_~w `,a/6_6b6 m6Ov㘪? tDl1"y;7B^Җ_Nw? u^.矍P|qT_Pޜդ;r֦q{aV2z'Y {C:8eMmàt*jۤ&v7ec'he.pYsd!W>ОMX Ioj Sm4`XboJvZa,7ؘ q˴$6m.+R7ec۞򥻚.[V|żz^0;kb,rq 7`oQD\>830XA khp[ZvjK|_F1ϾV3-Cl[7&=ֆ3 )(#"ɗ9;amre6!+ 0yŐi[Ox\鯜'UgTH:ҁ.7,~N6j5c yrTy@s3\Xec.i)+~t#m萶bϼwӌ4kpPg&=gqg'-C=0 x'G˽(|N&[uaC |AEB 1g:x0| >J; M"P(9'&RFuc9,w-a.sIO7Ϲ3sC:Ήn !Jpcur԰_~F \_%e'=N82z[seQqV#[Z!uN]ʖձuγ7y8҇"Gz2ik=4{ל` ?곾S>@KXk˃p?=8.?ģ[36s:K<{׉@ܚ> ѻ3TB?S A9Ai9+A;Azӻ?;%{A#RcsBr@?;$|;KsB(L=[fכ(> ۽Ѹ [lC|C9d:C( >@ Z#cʐ\!]E^%Fqȱ󭹓D/D0L>1p gkl\fGjq@sϋEu$vx$yh`GH=~\JTKdƖ Ȃ3DiTC,> @lEm$,*yIqBWLGRH sIx,ɓG3-~4ADSIF4I4>7$Q t2JCCr4B6CDǎTG̸t 4y{dIDd|À|FNɅʆ|HfJʽUK|Et TL5\s&_T6 t92.x:hH"+th6P |N3>"Ni\7{0JIJGOdk8Od\D8xODڜ-M~MiXD(,L0dN#΄?3N|K\O,A\OlOiĎ!,,#.+dB lEĦJ!XPj݌ć$ $5-F 81O=QC]QkэOUN݉ h"}U ȰP(P)5pWP,R R6AIS2 034USQpr&d8 9E3R"5R=ER K S'P)uC"D-,ՄE}"7ɜ2uK{!QOQAPQFRg=ՙ9RH%mU(EBCR\U- ^%7`%Fa0p*>Y^iț#Vְ3mnVp}@A]Z[]\E#_W}W6W}U~%5̀YԂSK7%$4VeqTrX~hZEZ W=5.}.׫ZYPhYe{٪ZٙYY҅"TENW>CZ uڦZ5hڬ׋׮Mٯ~eu(\%صնFx3ZԚ-أՒ<[\ژ%\ EPYeYuYɅYʭ\\5\eظuعϴ[|RiDXtUۿ]}UZݤe -(=CH^j܄\ Zҝ^ӽ۷H^~]D_ňµ]-1yJ| l]$WB.@Xa#PƋ2Jpx,7a2T֏K QpA b= >ӫ; Nn9fv̐aVL &F9(b7.0bHXb#hbybbC*b.0·1&c3FP!~ ctc75>FbnQ@ =dY`EBQGHVIv4cMfcNeQ&3T+hpY ^*TFG:)a` Kvunvdhevg:N |FCi^h%hY6[+q$e-ct u&fvnggdgcg~|Vfg@ P@`fW֔Ei{e⇖^j0h蒆v惃fOgx8b_Z6Vkiijwvkrhlg>TkF[j4V&gu0瘘lNڮێ@`VώfTnVnk RO0 D{qثmz \ 9ȁyR"X?  ,dp8 !!rpp%Ǒ g,qbLGwR+kqqq9q ύ!'"/#w%sJir x(ו/+,r{*s@ 4WYw s2s9wjsO<)*/-%pz>PgWt57J7tpOtbgP u)r+WeSr uؐuFA3HIu7t`wab/w8v;Hv ufG@G[h#i j/ ku2l\G]6O L8q7w tW `wSoYڢUVtkw[7mI?_"7xaOxreRLD"x"RƟx|ot}twtw7gNohzlXyo=v1yv?Mxx1yl/7gNzwzxy}4lYg_pdwu_@m#{7{7z_InLJbˇ'z |w(~?|O7o֏|/w<~0>T&mBQ]&A%Y_mh~uj*h„C2aQ ƌ7b #ǐ"G,i$ʔ*Wl%L f9bZŠІ:s&4JTT=Rٓ֬rb4X0B BŕAƬk.޼z83AMs7u3E&]jӨTZU+׭_왬@g c4[n%w/زgӮo:J(RǏF:Ge˗ e9tϟADiө;׶Ǔ/onkdʏݚt(lbP, ڱ..ʻݒ{/=[^K30<\W0ă뮼Voo& X|p)2Wg{1a&c07, =.3Ӽ7;=l2D[}5FW5Q6e]5i_5_}Lmvh7 7H18a~8sw\7]vߑKnnv{̸Ǐo39Uߗהs8r菛~;/>氞ats<<&oQ7==zOO }w>}-O4&Z6>o~?t|`#otC= Z D4 -}"|Hh&1 [NaR(¨!'װBذ~0V|pO"XD^X\E.~b\(G#j\c0*Bp~TY=rG/ aE>,$ i7'$&3Mr$(C)Q<%*SU|%,c)YҲ%.s]򲗾%0)a<&2e2|&4)iRּ&6mr&8)q<':)nwFԒ:EvV0g(IϿsA>O@SPO ]B5)64!2QxF@ ґ^(G;ȏt"-EO{tuNsҗTPZMm*t0 OZDNpԤlL `ÞꆛpuGX*VҡfZ G^FY}jWof=kZѺ@Fo%j\G0׺eͫ^־9 ` KV" {#c0pYCiaPYYlf/z2 khG[ZҞ,xX;6-ie=-ZkZ՗d;[ֆ5qی}.t!R6ֽ.y+9w^K΋zRnOޫZal0Ax03 S[~_&w0 +X% v0,a Opal8J `8%/=qtFpDK2t,e$ev6e51ZsXFfj J7dPAyfSq_ti9\Ƌi f,> l259K޲a"Ey؞W?%6cCgՐfG31C89ݦ9]OdF+o% ;> k-zĆQvNJjdgXbiN{aՎ AfQ|Ll؏3F䩟>γm߱[omswxn~7.7^p9݆VxYC<+]CxVI>etP[&3o1n|2 ̭Yrsq*i-^sgzǏ}]ʳ̍J7In\_8ssޥ-q,rV#5B/֐ck^9R79lП-Oϛ솏NG7#|~p}wLj^~#~9?s nIay6t3O;9䣔ZWnkeKOy6ww6m۷qݧWF^nZr jjj*+Պhk؄+~W>kǺv"+r(>*`7-$ZjjTlA[Lt?qNzBFGoouo@Kpl 3찶=r+qȨ#sL #͐%{2ʴ"[o2o+:#sϿ t ;34ۊ֭ 7|UCqs-7@)7 TnO7w.砫:耤. B;G>n6J}#.c:$O67fxc xՄn8?x%昧{~ 'Wѵ*C5{]ut;w WE'y:X^PnelȰMl%,8ZޒK񡋀8?2P!H'>TlQԬkІv<7EFR jv(g9ϩt 1g4LJ d;?tk@NwJR %RR@5,E+K3hɄ$= uCRNN4Zck\Qj)B*R& hMIQ>oKy Oɔa4͝Mӝt&>Ps5FeR!T"ԩZhT0UjӪVUb%+HGu H)RV ׷@)gtk~iOGӓM6 2d++ljYϚHӢ x$u8^G-_q݊P֍,mjxe.;͇YUZ\]ς55&6ab%jߊ(W=VbD}/`y+Xvb;Os6p_E2E W.G. 3.C<ĚAb*1}j_W)py,?L#g2 XqjHv3XA*RiuD ^<hA{Ԙbȴ&VBӠQ@R:lz W=!-=fMkqy ȵ=^Al]w;)"sEJa[+eSk \IO׾45iu5ˋS:lfuXֵͭu^O6nc Y$(hwO{6эmi{4G[ˤOqu#ݙG=ohz%7PoU߹>>k[s6?lW-X;35ʖL44oy4˭s[[Ix\%97{~TD7z{ t:JcC\4V?n,N8oծt@+f\$++q s1ݕ="}|߷ x ~7S[߸= o{^6=!Xm ֿGۋ{ H>n&'`Cr|c`RчJa;iyz˦4ܧv7iXa23$XW@G1`j At"Vpwxb?WlpWuwufb=SGЄN؀awEPVHf&} 4ԁz6%H';WiH/0DG7t/<: BgS||DuƄN؄%VRh}dW}Y82wUybg@|a{Ƈs(Ax]8ȇ~X8WAycyQ;U iwrc$$XFq bfXns&SteXuX%@ XIXyO(Z(O4af+1G8h8a(@|1͖B&rIcRИIa0I/?g"KHg-"e;fs3s#~rk=КjB4p~d֐s0p wr[EkQs^ 阍IiřWцq)AvLI*7r#隭 !YaɛIIly'I*IXʙy8ҩi ڹq&љ2p y'H1i "Mgo j.* T jJY]a~/5G$>&g¢,ki7"2z4bx8*~IK'<Ч~:JjGfKjj &g{Aбk t"+@j(*j< ljg6|(Iqa艳HRⳭzwKG+ZI hK(MJ/QS˒:XW;$P6D7VXŹp l۶Vt v+xKpz˷yk*qdlPKո='a!X˵w AG =q(9kQߓgQvճs+f˃H s膼ż漖T!KKk˽gOo曆ˠ 5:š8q-(п8>+K3l6c;Hl H!j\̤!ll8NlX>Nap-lqsKtI=.>1>YaDa㻭?߀-@,2ZF~䌐.*\gJ[}w[.l]+_a3KdYOmUo>j!;=mzo7Bu>(n%F{0>藝0{ ! ps-~lb=篬?nAEN^IZ.ʎa+t(%N.ܾ ,浝 eϪ,Ϻt~ضT뀱^o'ac3q̞~!Fd}'D0NnAp1:Fi%[‡7w n/f r?3?AQ%?{Hm$`  -a9H$JXE޼uحGDW$,TwsezWSM&4YϖAԨQ~I,I+QڣZժ{+n+7a%Y"iղc۶[Wbz/]_~ Pث6aEcX:CzksС1f$JipT3T@*dxY\fHd}QcnGdr%K07לwSk=;ѣ[γԩsfݪU`d˞5vm[p_oG_ 1"2l-36DmުIm֘fC -FA$̈́ H2ɸZ)&s\+qG { G>Xsm =<DM$FE6S1sTt̵mfԊzӾ@QR*O-rU@Xeu MlU9 baNc5DV٘uYݢӷV$ZVQniƛm2ra6RteWW޹^PgRw,xj0[oLk txM뒸#kc?F~Di nΰ=efg=g!x뙷:OFOitߨSz0~5kHst#9ޘmewI.YSF.)Ѿ[v7'y/]+rwO`|///s6'sflVt ^}_/ w#N6-m&,n5 pJg]Π?y`hr1Zպ7|+_`Ú6ܷ5uVIw:ͤGf翜 w; e"<L&46>#pK\,B:h Q(* 0V`80zi{aeŨ+2q~Hꐰ.k0DR@$ᨈ4Ns ʄ,j6ypLC`eG0F D(lY!&ॺθKu T6x$fN"S.4RVͩ 򇉼JuGBR\!Kf'MJd*7i,eIwKL92f1dƣ93uxMSs$iIM?&,B&8+#N*TygʔZr uU<+ n@ڍv٥BJU  %+ՋUm;ZHe[W%8ڭ8_0^q k.M(IXD%v} 74D7h{՚␮c^ՋrCcƸMp%NJIn ,hQdBغvrI]NyV>di1dryM'v/*f2gz?fҼf65ƽswA^n'Q.e gѦ.˧t<$ Jy.*N9|'e2jF65sX dy֧k2 {cNj»>3>><?<"?:2(A4Lk?#k S;4A%;A Y",Bcq %ȝ<%Z@B+3X/ @@C8@,-)"LAL?qA 4TD8$9DA B7\#<'t@4&%(\(BijB+$F EQB0 C3Q5 HEG\:CUALCY A|f1@D7\hDYzH|B9@KT%LdMBNOEQ ER0< \Vl1 XE9:4XZd\T=܋ tL_ `$ 2 F#쿼cDd|īOD{iƳFk>Fmn 2AJ7YuʓŰS`]T;{TL_#} FLD4LlF=KMKˋ$M۬L,L4 M%ˆ345h8ޤS+ӋSQQ$T_5TDUE QHIJ*T,TO#\%Ui1ղAӺPUt9UQcQ!SUǔ>@AUR,Wɩ0>sԃHeG\BM-?$UR5lEmUտ`pqSz[*\e]To x%T`D{WW&W]ց%VC %S} nXoXWUYU=WMWV?UAՒ5]YuYY&Y ؚ=XHiU֟eݒ VJYXuuZw >G,@ڜbYNPqxܘ[J@\Š(NM jYk-Ջ-]܋ӭ[[C הUZvA]}Z|e\p\Z})[\MY"C 5]U]d]um׊ ]]EuR\ȕE^[Rg5[}]ۆ ^ޏ^ݽM=ZT"Ue_Ir_$ G}\%^5^)_Pa= _ %5M`S]-_8` ~D $ S W%> MxpaU9a>`^sܨ٩ >9]ߥ_%W&C˥U*b,i9&.ן\{u瀖!kR.34+dNOdPpR >b|\ ; E$@eZO:3Te^@{د>daޏDEhdGVH I;J K=NdQfO&e^2e#FeU:VWWzeZe[> \;]eya)f8feRevfgփhij.kdmfno.Vg+g8/0Xgv~[ލ\yz6d|>iUg~~dҁ.8K懎ƝqXeiWthngxg=0T|!Fi FΩef旎IfJ)3iQi[:eiFUuFjbPj#AAk/2u>jikj׹ie>Fh/h@ZhwixQ8Vk XI*.X c >'!VQ"Q#(~ɖAhimk̉6^~mؖcKd%3nnirNnh>6*%o4odA.hh@kmўnogHbpm^p6.o#9ITppq^jW!V!_M&qlq9C Wo gVVlӨ.>N)?*O+',-qsDq346?$sb~n:/]#{jc 9<-"fpK}$d%'<4PAT?da(vcWmte-PgQ'Z5u"Eu]uXiuGzY_Zߍ[ϻ\I^u9c`va7cGXewjR'STnv2#vnGZ%[q*srmuHvowwOxwzjg}o~ o 'dovwXr^ ׏g 2hij?m?yvw?Aru?wOw/wUyXiT$U?zRWWߓǁuRy(x {vzww7z{MOd{\zz7/(|z_|L|ܾɿʇz{MEq7} ߞ>|AkIwܷ{?{ ;8O~N{)17CB*ߍ_~z/Ѯ },h „ 2lp P#d6㳐yF4adL*Wir#L2VfҤgf:qI'P-B4`@RJ hRB:(֬+rͪ!ذbǒ-k,ڴ !JAŌ9z)i(Wt2F5oԹ'ϧ?i 6ULMԅV!zʵkjG.m45L'w/N$yn=쌅vR/oxn sdݗF}w[~yx3^7鍗W9OYg8ݟM;j,}?S;Zggק7f4 !i}'@*ЁT!8(BЅMBRm0-J(FG&=5RRx)LUR4HORZ.Kyj .$NM*TA)QG&uLKKU֑:*V┫]iR;V45kڑ(v5[mp\Z׻5- F +a kX@mlJrVaXˎ(,g7Ȇ6 %mQM٤Vemh_V|CpkT⑕~0caWORMo Nr\箧35)uӻP^<.w]Ƅ/es֤͋Wm:ErULܗy;a`f2TC\ߕ4YkQ p?bx51VPbVŦJp3lw(2Q3t!.N=$&>>Vbw(̌A%߷=(Ҭڒ@S@dk+2{/ "󰘬P TkgΧ3"" }3 9!64؜h&E6U;o9&]4 E-7ùw=5_j@ZҮ,-kk8Ap}ԎZ65\evd)+d? ]İL4 j 1a`/vۘ$˷MXqg277nPw aɷÍq #P2pCܮG8~j`$.zRƫq5w(CoA3$0WL-8-kXx+?'y}:Wb x cnнI(w[rxz'Mܳ_W+[9k]K9"/yI^4y;7֡s^~;CE!}%-`u_G[]73O<x;<?=G_}ch=u;Ws|,xO?/Woq__m1dH__e5ZL`^n!M_*_͟uYlHUǬ xPwL< ! v9$!%,4FY^pς}v^D "`ar^R>aBY^fz!aA>v<a6aQ"!:a{%"H#>b$NZ"!2&v"ˡ)!*N :@+!$$!%b -ReEYl0ciU+*:3Ƣ4<5&^c66Y7zw#s 8`*Y#2!:2:a= ad4FʑfKkDY)7pEn K*{Y[U":jܦǑ"g* 70Ezf'cwbLɠDˠoMl0| **inn\ r)9t&: B rb=!>Y=W!5fφ77!Φ_~6g78D.9(W~UFZQtnP{!œ>⡲_w]f =vgڰcq-wYzmb+=Ky_nIG¹%::yξBL{#vֻ ?|y Yyk'>N8ֻ@{~tOV1~6~%Pҳ=G7[ #@z֛ (@X:i.OW&}$| Â?, qEm oQ9>, !I zEt`,BuT IH(o׊B[t 7% 65R{q%ґwt\>FL$!ٹZF,B2'"A*9JL"0fz1 FIJu4"rS"YYҲ|uiEd&0RkFP0b4)kݬ|(Qs;DBMmrrf, ЀȨF)ZA\"_N,ƳY=HLK'DPf>,% 9.LC#MZ4+hF9&RtL 4!Q!=4XMqSF3:}#O}S$4ԈC-DzԤ*VrjC(GPU&263i(iWUNn4iYiRsl[F$"!j+jQnԜMe:Β򤿼*;*QbUU,@'{VZ֠eO@϶!+H´KEm^URKE2KFێ-h)II!(,"[pĞ x E)uz݋D>;xV фҋ szR  Ͽ|5Ldy {^EfN{uD_(13`on\sFnS^\qՆC#ҿ"vTKWKo3\b41oؾ,El wȺ) \$+{L6O$Ur܊-CRqC4bb)&oY,OWx3:<ӘF8vRϵ`AzfܒECXJᨐzܸ1k;S{i] X٪psg+n^]_pæD|Tf6}?@;o1ilN(]fmp@L$7ktߙs~q9]Nz"Ocَx6p0sZ$.8mA0BFq\ԭ78P5G.tడ8P"iۡSGmXfϚZgtD1n{Q_M~@8lֻuTxf#<7ǷWގ t}n3OSn:c,񏇼$oK5v`)m}F>`gWv~ڲ}˜4qg&.xՃYvQ0\||}7}kz7}6oWd8~zWq9@uwi&Xwgq7wxD[\hG((hxy ȀΕG}vzzX~uz ~"X{$p7wւI _2B466hgI:|<(9}B8}DYF'JoLXzNv~C0y'$T}FC8@X"HTQe#\l2VlF= xeVK'6=G#0L!`yDd61!:('%#>X|b7 vw0%5XTx]5XXS;sH(cM HЋ;7BQ|pN1ҌSh}=hr(ݸH8N2;P?Ȏ4xM8x8/B ¸A$  Ii8WsC!W# %97'LCȒ.Yz0Ɋ8qU ~ȏ!gyy 8hm8@P)*T0V ;Yu$ҕ)щ%jٗIy It<ɘ@G,ǹ=2 %SB$}|$)y6xan֖#6xa']vxz |79y I>SXU!: JYC6zuԠ]11Pyf<^ءol *p։$J&(PCY&2jJD4VBDDF H *O9<)M5̚(`ѥUGyYzֺ|ئ#Nʪ C{j觅 e:<mnȊĬZ "AGکڢy󢤊lZKzo: Y/Ջ֭}53P9(SAנt] V wDM }& W5}؎ }#<šF#Ғmpub&i\m}=(\ }=Mn \| ۀ+ۀ]qUC]]ܼp ˝]|;dfhp٤39*m9j- FG-ߢA&c,M]QՔmqq{!Q☡4E@ PR &*zK V5 F{$JgS9~Ah[Jbѣ7aOuQ"im$9 yvw `肎j7  SN uTGB#DnzͣO䉞U^,WY[n2;`*bn6dfhN j]o!C>jw yޫ{y}.N7yѝ&NGIR魰X>Ġۣ  y䕴7B N~>ʮnT=V=>~9iޣ!ZA7t>/x~|$~>0V.'hB$C M4ܾ$|4Saw^z~-*/O13fp}9;/f@o N~S_~a>^~~EZp >lLiqhsuoNjra+@)X < =1WO){D)-/HovNo?ǒ/7qr;D?Ý ZN"8tTVb-!($b8i4H( SI29ra)1+'iaB!C.yŧJ9vTRL|iLZթnի*a%+YXծeհ#*YW P(y;K/<Iaľ/f٘-ȑyN^T%.ʬiB#S2eb$=Ш1MȰÈ+^$ǐrZ^R"LWkJ'>zE:EԷkr5 )XC =yΥkʼP Xpچ^1[2xÌ͊@{D4J+bB O+*5nr͡U %"̏Sh&TIҜ @UbLfssD7C Nqϔ0*9uF,9R;rr%>KL江^Ʋ|B} $d61TQx.4n!HsX9rJT 0LXb3RN :ŃT HzkbӨdR/p2uD9?:*\p#NZruj]*V )|r G:sWW(և=ET26Oa[-l ;Sz|>c:љN5gڸVVFQ K0 c +PG1vP¢O#1jԴۢJyLq߲mov-)1 X&G ƞEEODX֙nR]]}n`{/ۨ]wn{P>}MʥZ`<0u,\;p)la( p<|!1J|ŦxP+&M]lA7)lc2=f⏁,XRFőK%/BMv2T )[@ɫD;~w,r]K46?ҋ;~Y9g:+^ULH#fD t-'z^JG/"tx3^K8jlU^łZ0m~ss, s^ݣ>ɴ'lh]+> PMA[4Bjڑ $>5Hh%P3b~u')X"c-Y.tҭ;%*yjٙ9r9<Ʒ!-~[qG^I<2RN8@vqM,r>nB3t=RV=9ʇ./1_dN<v`]Cwkt)UL:UBҚ^]cݺ S-,7ؕof?b7N_Q{^;mKޮ8x 1qxdAq &NvbΧ:ջ>;=^ ?;?J3Mj?z#k J۷˛C;̫AC=@B"„1B(@BPBk'dB*BB)ɋ-,)3h01-*K4<5dCZpCCC::+xC@+AB4U@+Qa(tDEH&pR0CLE0 1 2\;Pe @5l0TLEUVWCXk@"ܷ#4D^d·s_\FFjEIDjMN F~EkSdmF"QZ4Gl[Ev%l&8>=iA!ը+@:xS8u, iv ?%SB5TZQP{T]THR+33N [S^ UQ:X0U|IULXUhJxUZ-C]J%"1%S(cEVe Ug}RŞ:Vk=lVVVBmvMr-X>4שkquvUxUyViEUSLSl]'?U8oBXQ]ٳDX4SXLeVacXzSӌUՍeՎUWYXUW XW}Z,rY0EKLBE +ϼ("YgX{Yj5,h9~Y]Z@Y#!X%W/]͗`UיuZTU#=yXfaMX۶%ٷUH]s-t2[}Wa ==\u-ۈyzS[VM@I͕TMX]k[x,Ѯ%"25yc/\ƭ\@>(Pܦ]_ݘ ތ(Vo*^E^-my^uM^@R^u[X_]ݼܪ%%}_q{.|_ޓߔRR 08$ f/e f)j#m )[۱ P<ި!Lra&^fV`9"( `b b˨$Nr%J@aP'nahaPb:a1%cI0@c5>dX78Z(Fb+,+U[=1>?k\{HD.XbNhb+cabHdb e,-΁.f܊a db#eS.f@eU"V9XnJnӉeQ=>b_X1 Q"1fq d~c'vd9~g.`եMӈ+b< L!qrfs^fHbX^^jg}_eyF-]zf|^pgVd&6UF/fVg+qÅg+yAhfEnd)iV5il{hP?戉U鰠iqg~6\~>ꕖf~Rꀦjn6hN^hjNj|njh6f# <VɘnA=l"oԄW9TRѹ*Wkl(8vƘƳʦֹalچ0 Ƴ΄^m!ko|m0m^KmUQUk5vllovnЖ] mÈn ntlZ,kΔ6Ξncn쉅UooNp6" p kp_:~R p pp9 q'DoOo_pFp?pq$NJ>!WqNr-?er 'or r1'7r6%&'W 2r?/F6osޘ[VhOB>_t&s[ A'BsBtPtFtpt)I߻5tJtM?{3 OgQP"uSGuYw HGPJ_DYu^u6uQ!Xu`_v.at\7jWf[v6\Evk#vEkwqwrXITtWLbvwx;tGuo?{wwl}~^FN-xe1!?Ĕxxxyy/y?yOy_yoyyyyyyyyyyzz/z?zOz_zozzzzzzzzzz{{o/>{_n_{n{l{{i~{qB|Wch|C=ɟ|D|w|_|Ƈ<|]|/Xї|)}GO}r]}/ ]ڷ]]C~7~I~~?~I~RoU// w~Gl}igp?,h „ "𡕈Q"7c0"dh$ʔ*Wl%̘2g\Ĉ-b#Ƞ"ÐP(ҤJ2miQRQc5=/rk#b)k6ڴ Ӧ--ܸo٢j.޼ziJEuU1Xn0d`-]'u2̚xˆ*˸1]l)ky3زg\ϬG{-} .^95ʗg 7Z]80ngn[a-PD}IMuS[uWӕ5U\w5a2"6̩rxhxNÍsO\x뽵x/7cLrYޚ\8JkۏG.myp;l9Og}gE^#v`œq|ܯP}k}k's8:"|HW;jv Ԋ RppS2YE|{X!Y [A-`{7: Ґ)a%rH# ]!K\]&8"X01 ,FZO$5$"qEk(FP9C0C,"xD%6щH+*0ZW^'Gxq>`Y21kHC!DWшrcG=j㉰خ 9"SF6,DI\$ pғV % G)H9;T%2YJ4򑫈$+K.A J/#REP ,D&R>ȐaL&:QLGF+f80H\ ɦY?z-XS3U:N6ҍgHO{~Ā9H6'@Í=iAWӡ $:QvDCBF5ѮtT i@XSFJRZ>Ԗ2f.i ^49)'zSK"("zԤ }g-SQSWMU]WKV,=K!:GZST͉\Q5yGZTԯ΄fZVհp*4X2|ldIJT$ !,:*tH*\0ÇHHQjȱ#‡ CiP䆓(\K(0cΙI͛róg@QHYʔ&!P騪UZXׯF2Keh豭۷ IHWFpU8ߒ!Q\˘0μs꣞>J4짤J.U<'TVfʵW?-{6ڋ{cˎ2b]waޝׯM @D+flcy$O 5f7+ y誣I.vcbBL-0N6s?`Wq%s8]tQWeq7w߅Gx䕗zǞ{gFrgECa&g# F"9buriay|ۋX@+xWYیx9?ד+pIwGbbKaxOzxޔ*`:ЖeŢz0*pgyN>v,]/zo?J?G ^CBB*\ZxPs m?r狧D!]r L,2OEqX " ^aO:gD HC6u#x\Q厷Șᕰl8HI_KBF6R|C#EIŎ2# 85Q Ќ)IMQpu%fʿgT  =@̦6P=eGE !XjF%Qcl9R3'EOJ;T|\L{$SOpMq+)ZjʡnL'1Q{Ւozu*T E vegdH:ˤrB)%U4Rli])3K/r)T꣼|IbJbº:#Uv,"Y1rb\+h(ZҺ\WFO lZGe-bS 2l,/?ڼNE,r-,+|{閰)iݺv2Pm>:/n Q2uo$\FaU|ůb^va]X1QVzIUkڷ2Xw#ڽƶP+"l˫7M ަ5*[=2]@X:nYA2#(5xPfJXxv{aL&FqE]]ؾa^ )a!'Y9a%8Lޕ9LSs2\./gs[f33DS3f8;FΡLgx2YMĂAéJ4H<:4*K&ӇG;i6zŖ凰LsYqfˤ9;u9b}v.2, _v I:NS]G'Ѷ!q/k%o{ܔ*CU3u~Vs<}7om9w~)a35?8jgEwq w!IK|t>Y|WX<,>uHH|z\xpj} G xMR6?@Ҷ%eqTݰW^֝P!S-1VXr=4ݗowϻr =Ds+L'^,*qz*߱.OS+xGR9{9iIo9Nw:k}~>^i"%N0G>:p?;'uq|7Yj\\ ^(2rpɱ}}'~w"h~'iV$6CbCbLٔ ~CC,3?z6(A Z="~Yq dYP2_5'Mi+Co&*a^.Y 02E467R '=|@YBuKQs#:kiOz@csc[i+^)`I-j7eIgiY*mICogq),2u|yi>{9}闞i.3yQF)HB2 Pyc14T"'Vry&)XYh\`䚰>n9ye5:99V|ʩ/I) ;90z샙i,9gy_`mr驞YBݦM4Psْ8D#wd7 >'*0Dz+ kI+۹RrJ"A2ANm'LYO0d2zOyɟ9"VJ@y&ZZ]ʕY*>c*zYaɚi +ʦnr 9Z=Dj4v !{XC-}ZKEX<1  zx\#/zQ+Z+q#bJQXpVv哢lt骺[c ::ʫJt:eɠꠐb>/ꋦHHѭ657)1yyj1ڪr걲*8zd{گQ+ϙ** D#CXJjφ]RzZe*jX,/) 4k :K*ǮrAq,E\GL1Ic߉N cأ,U`\Ӂd\ƗpƗ@ln^)|ȔavPȩrksd5Q|V>pfɝ!ʤcʌʩl8h|Mh&((<ܠ5!죾îǀ"8 !<DV̷Ͱ$=&O\͆q͙͂͛]95ʡ> "])˿ir|\"- E:<7Ĉ]e-ѯDJɖ=\ѵmќ!#,'7=)+m8s6 1-o*lԵt|vzL7܆L4A4=Aqmˬ 0 |ѕl8f>T!r!g%=Τ>ce1g iέveXB.a xzn  !y`NЋ<1R>]<Ֆ-% ՟աMe(>MZ {0WrrۥϽϿזMCoB heM1-ӭّL͕ї 6գ S<g=g]4"5_= :={. >VnȶXe~.:TNڍ!>7SLՅ, . |Ek\}]Զ;.=>w עbO*Us}ͭoN[U9~K1|@B}M'|4rR ŗ~C=#UU$x~+dg/ANϊ߷~.x<-}>z Ȯ찄B"\#s !!S~f#ۗV0fE80$:/*10!1E}>bybΡ:m BVF\AhZʔPPͰWb 5{ w*y'=/X{e"G[(ы%5"[P2E46LO<'>o@26aφpQU&[A媨c]W_Dh?jOno a΃P[2~/$M=b ?b WoO^(`pch-i=kOmh&(>[V 0 ,dC%2 PV4&ǏD "$*TFbIKW1e*YM>t\g͚t!:SIY2b(SLQe:՗/'mkarUY$ybH&ʥU3th s|,jRJTV:s-^$sLQpEYqSv_ VzbDUbZkؐd6hmܹqC_ 0bfu)S/qܩO2=JRPƤ*V\~+6,Yh{e;˹tW#8>c+ɼ˶#>1En(R*ż :Po+s\*&-/+t @I1Wl2#$rBzӤZi~M}Zըju5a޺k^}رKcS1tVdhIjT[?o߭t!YqAhBSmV \@%OFx׈{od;ߖS» fb8%pg=K\+z͘8+t3$t5}by~zVfP5#^%$&AD:E-C+%x yeU:H>W\*&7bB+,#'AXc|)H4Д&aEK$=K0FHaV) ƈC%PJ AEy]`%a4w|}lcG\0>u챏) Cȕdd#-I0N$^2NDb(ERF1Ĕ*,p#bReKGQ"WLe'̄3MMde#MIޓf3) qx9DRpq%ੳ-Ʋh>ϸO]O|069ѠˣX2ӡS48E.{$6ͨ`!iWqFRPTiΗF]hOOs.dF QPc.F|g65jEzQjtͪ7 NfVQ` IYWNt+Eµ&WBپ?PP"& 5%XNrsD'+Y2ײlt 钓2')Y4E%jgښzJ[rMuonY԰5 ьh_I~5rGX kKP^ٚ˼{pZT!&n[wjbZ`,\׸'èr˂sEºRci1 ֻ\oWR4E#AQL1W8-C}sfΘŵh>sggS~#L1yϸh8@> +=?#?3:áь{B#+ۛ' ,S/~ỹ,<ϻA"A3AA8ID9[;лĴ<) \P3KB[ɛ@'| ( ))B’9L;,,9?5D'`C7 >B˿:d ! [A$B܊CDDE:Fp.DJdD34̵5ܽ뽽CT #@VpŢEZY\HtL*E*{p Fa*I,2k?KƕӐdgԿhifEF: %o4p+qd)GXtTG`Gp)xԑy$zGbBc ÎǦGȉ s2Op$8 A[8Ȋql \ d(2I>[@đ$4ɓ”{d9;! d~ds S 1 l9hlCit*QH lm$ʰ0J^Բ&E`^"r `Rm=z IVLa -aDa'^a.%>`Jza&& &+ Tn b8E?[0#F &~b9v(~2bAx+auP]&ZJJ_[_DcSc6Fx59d͵&Cccb`%%cE GNH6bI8d\dB530xTz (3 Y`I elV'_F`na^;fH)e fFNpfV6hviSSd$1lF2nf1&M+gsFgXUT.>wn熘 {a| }ggX "sբ0g@YgcfX6~a0hhLfw 材瑖N3i泅 Cz酈PJjfce6_hpiQ"% F>j:JjXfGiSvQvHJ!]X1IR28+3$!I'H:0$flf@&r{ٖmǻހV֝~>Rv.&nlɦlsm6ѦNt2CV؆'rmw&lMcmq퐃`l0rڞɞnVmkHno oKnNo^oڶ ʞog&m6m6,^w:>o6pFp~l~pp5 <å>myvxMy(rOw~?how{wxyz {Gw}~xhqw0m(IxSuxW0y΍gQy/ vԗEyyx6yw7@Pzf`|zǍzgw zUz{GLE<'/o{L{Nc8|eX|S 7|?|g|Xvd1|Wc4b{_x_Чҟxp}}}vv}Q~v> ?w'Wxܗ~~~~Ol'x7J7w^a ` „ 2l!Ĉ'Rh"ƌ7r0ȐFa򤏔*mlI@2gҬi&Μ:;Ir$B[- ΤJ2mĞ (V[G_B+ذbH @uիYn%8-ܸrfTez/.l0Ċ3n1Ȓ'Sl2̚7s3ТG.m4ԪWn5زgӮm6ܺw7‡/n8ʗ3o9ҧSn:ڷs;Ǔ/o<׳oeЯ_>|Rۗ~=G}`H P! 6ރD߄UxaOj(!aHR!1b"5(aݽ#3Za<˜#x;#5 7I&UZII'^M.$Q&y%PdiҖter`'&e^yfiqei}yy٦T{觜t*ɠ)(6j\Hhj蔒 h[^)H*©fi2on 阩VIiꛬ';˪[^YK"Fnಌ5+F Vk-fvKX)[.% F/C$+ ;l*upio pT0' Kg G"0S*Ȳ.UX5ی3*:,@$h2 )i=1ő^9ec iKmK>|uq7޶Vu_}Vmisn wr[x߭7|7X&^dm'k'θ0Vg2tK>9x9^i^n艗~:թ;o뇼Nf9;6^8+鯠|~|t2L)dTv>Ԡk<>H{;?׹U~WW(%~ ߙ?/E;^/[ @Apyku2/FY0f3J6#&^>\HRLDS0NdJ*!_P.) ¥2 o8Ї= _BbR((Nv"MD XCQf<#1DQG(9D/Gn.HYm0RD.c)J޹Hr`O%Mrb,$M?ΰ9,*WHWe$mD\bRH'IarX1OT!rf Gi6fG S1cw9Nc3tg&ύ3즰Om?PI/{HC*5!Ԛ HCJbk áE9Όb3Rd%UIQK-Sd) kOb46N%S=i%]U ŤBj8+Zc>Jx+\jVWOUTRuִ2uNe$׸v.t]׽^>QQay6i Z*8mR[x-+b+[SrݞH&M) j fYO|YK֥V־-j{[*+\Wf5>75i ;IҖ/y˫7e/}.K#R)!hfN7o2{D/[Z|pC+YسvK RW>X;3h&S28BdEjӫh4(`Y$hnZ۷ 4鳻x}x߿X ;r)rd(UK4 ̹Sϟ] 4(IJ]]Xn,XӦ͞MsϷpE7^+_0%ቆ#x˔̳<2K˗[ffgϾC&j)jլ{V>,޾V\E\q _5 `=yWTgc=w#yx]yePV{uUyZlYه_~H(!X H&PMx^PFyƔT6Ơr!(gbzi#c#0I}}h[;磀A7$ ^H2YXMF)%SZ}i%@#(y'^8Ɣ2)6ֆ~uv'pyҵgq}. :ZfHkdZ$ǰja#$x8g%)|[ܺ*=듺zGېH{,>ӶR]KVp""n0+n{`q_ ng+/+ǿ ,P,6emnWjWGƯqL77ҏ#T(7}JVd522όoa7Ρ,#pqhAwD1pH/߀/m 4PG}'JF!Zo͵0kcVm6 kq=h.{ݸ>G'^JG8U 9xS^\K9؛+K3衋^*\mۺ:ܯ%h;;>2Ft=IdPȁ:Pf s ÑD%7hz0 a"@-}@ W&Դ.8 gC}vȡ 0@ bP@@2d&O Gp,`2Ё S\7ܠ=pF ӷBȅw! hCP1{D!tc(p(LtELQ"ϱrd^T (keD?F5n|9Q/\J.G>De4HB2C,bY<2x*iK`'ݠO*u%xJ2p' ԸW2 p4b-G#%eIc>$ÜO1qLdRFd\)&FsӤfyLj2 h7uRT*9tq|O '>O~ӟPh Ŷ&3dBPI5 E+꿋f]\G R0ԈIYRW3-%<Sʹ6ũNq A@MPI8TJI!E>-N@‚HbM)h[;wQ*aP%=Uak*FKZsXHb5㵰 .09vM>_ӳB_4v%,,Ŏc!Nɦnl2K͂4,B{LҖִUj[+#m\oꖷIrХtDnv3[HִmH/V;ǽ;_69zjFD=_+ XEPb{UL&OJ gYsbįAq>T\o;DR[Bw rq\Fmπ#Ɉœ ?LU j`t?}]6ijG^Ӡ3LB6ӑjviy:rIhg⹰z $@zz6i D;PK [.ߥ͛tn˞E ux1C ̲HpV2uJp3JC*Rʷe?ݾ]~1̔HÅ0G⌊[\%[g+78 |/8hQS|(%5+Xw2.7 doHL7o]St/Xfj_$|iZ tOICq v!/`pI%n.)_UX.0K2ns6_:!wo.~+}:..u?\ +qkrhxX>w1~\hJ'B' 2kmo/XsqhZtHvHg;;ԇ%DuR| }šh~cjTQdbXp(yPQ呆/ln8r06DD(:KKq 8Z؀DbՈ(MьUIШ҈@yC;D7P2/ŵD׊tgu $?UP]̴1x䏯rs%ؐJ% L,y mC2j3X1W"&yB&):(9*OY.me%NPܲsPXA$v_BD+FiS8JɔMHO=4]VWYV_ 35hub wim-o ryLtNv)-x:zɗW/Z7xr7(4pV>T\9-J;T']ڕ7,&3ZT27aS#>I 2tq\Db@biB yb6@ *IH>brGW֞)3&YCP`ty6ə#͙Wщ3;y)_Y@IDY9^1ĞX .Q) 4=Q D *_  ye *\WPsPj6j!#ZOujQ]-Dӝn4RB; =j CZGpiLb9ZK@ : C j*ѥ^zO`z:81gJwisЦ`#" mC9Wh*{3tP7*-Z`ؚ癞 )j냩٤:ZujXZꪏ,1I^`hj SfjZw*Uì䬓Z8J㕭ʣ: c: U詤~ ЩFZZ aj5ZʡR#4[  ÊD巗ۧ024 %Bdfغ8z&yKzꤜ u P@jjYJ[[ >+S@+JZWCQ:J#jD5"C)Z?{:KD+9˵m%Fp"$;yLHNӓXCeکx:bI#;f%#f:jz;eQ.kpt 35е6b˺uKOW;}лp"k9t4B$=le[q{JIljpy17V۬Zھ_ժK+HD$#[HKDJ;,A` C6<븂+&L,(})P\( 2|j(ѺCDELGEI)CL<N ž`0 l_u\4J kŃ+J ,¿0*%r>tlqy%{&DF̿HJrZKlUkɠ\pBa BM:DʹJٖTrK=]=_2Y֟vHKָۼw-Qy={+}ɭ܄8܈݊- םݳԘQS}-]Ppڤ']]8,V![zM3:ӭ|PΝ=G=M}u*g㥊 :._2bC.)cIKMxP=Uη::}eceg^m -x;=[aD1f;qWب؍#ś5l; .⣞&n-.NF >v>8.:~^ǎ.XNCp}0YS6C ܮh> mڧ/.|K>!pm낾.~7~nnoNQԮ^.!>ηeXڛ u^a(B*,o?@K/ooKoO,NWYV/\tyEsoz|oK8PР4 a0C+LWq }mDRrk­ g`4dYP5t)XR\32 д)QBPժ6YlS'a%YNN`ӦڧlN`]Q[_ &-[6PchnOSzm箻 /6鷭g{5_>ۺcW 3c̱" */@HKMF.T5^Mj$ܼͷނAK3j覓κÎ OO?jBH$Ϯ!+?'z? p 4@LP&k#l^‹0p479dMen7PLQ|la9xqCyۧ.R\+Zatj/_Nٖ8n44Q6+}\>nOsnvЮ]C] Uֵn6\؄.ݮl{N[X?})uLt"@)́YOFHDnsG{=µ#+$K Ͱ`cqM]I5"҈CdhFlQLVJ^qY̤(Mb,( ʓҔ-3aT72F,HC}et)@r0D*k<2LALwިbyIj^D̠6=Vwû5F[߿lGeSjzSW崧U}FC*lT~AuR5UwjU ugW_U5kE;Jm}k\ڇuxk^mw$r ˜f-Eel[@F%ohjSTig\i xZt<5Sg%0fնn*VUN .jM.{:} ð0A,]}}ly]s0p&8s}0"< CA|0,6a~!q4J|]xb8弘f{c y^/D gCM K𪁟(f qAW 7[^0%vM]5{ͱcdjyuqO\=q}{zЭk݋$ѹu4og./m\n8j٭v{rF-YA-2Ph:Zq17|V%6;Yѷ]v Q(CZB8f4m7D!427|u/vw}gxD <_-I|'Kf{(S^gƫspou#ro.K'[ʙr{}/gM1ok><+Ư~>u}aw7F;␖ԻA>X}X#ǹg]N#~2PNqon;9}ou7U&: Y' tqD]ꐗ<̐ufsc}Q,3_=m^wqs@E/p]zKkk铸ְ,!%3;s3=U:+?;:3S?+=6v#K;?gqă,Dḍ>@4C5 L7?{3|1A䩜r{5=;!(Õ@M)6L:8d:{ 0O!A$1E"8HD=DJ<`LDg@q!SHgFi«jڢFxǓL8OrIIIWIɞ\c|sǀ?)J 8ʚJJb\cleR('2KdI9T+]G'H/eԟTyLNMS Ua$ReJLTSSSWMPȟ?@PoI]^Ez_%?aUOP=(d5be-f]gOV8Q5UV4[nmU-RXLYWZUA4u)P{RG )8d]+3\"2BX6YZZ]r ◠8)9*:.bTcUfq?.哫(6dP]F Gn`abLZfmeQfRfs fm>fnv,[.\]Ygigxgxd2FfMa{gΘh>i6jh=ZfX@NhZ^h[\vs>h_fg^x>f3f`ma u).>&EiiFiej0^b-bvP hk1>ӈl;l† 2kA^֐؞P `pzJymѮ았>mz:Xm6^9(fl@lGYlf dž <ܨ솙?Cm6mԮSm^m׆m>ٶmmmnl@. ɾ0n mn5>ܐmo; KlVosenA 﨔oo~/q6^Fsk2O.k! o 7 7q#)n7خ?Fq[nin&r8r4Gn%wӘ(O)+Xl w-  !/sNsDץ$sӐsq(\>q@t iNtTסEoG8o99tMs?@8 6T/vY%w&wHIJt[/W=+ qoPoQr3b7vqdws&n }uuiW )[Vz*^Q&ʩ *qDfoY2,y8*: Kv۳jbr@m]{ٶ+o~ ~b{.[.ogjh/d//%H;NE 1$q{[d<2Jw1 &21;e93=3A =4E}4I+4M;4QK=5U[}5Yk5]{5a=6e}6i6m6q=7u}7y7}7 >8~8+8;x~29 X~9_ՒS>gι՞.: ^镧T>nQ~k1<#yPG< o'4;/K?_ 囟sZPo?ioǿE~пOc~W R  Ԍf.ۣ`/h nP`fB(.s&t '.B\͐m J,l C"I5&>@!]dX&]rYCFqʘ3%;D.EpE ģǸ/ dIACrP F:E~2 JCQV>y"H*4DPO)O?Dsn%0K$N,eZϖ4d.]˜R$1)ҠT_)N)) #7I@QO'=Çlj-܌' )ps8gԉvAxJy4^O3A):d|(J J?Rͧ4Q,P9(A˙l 1iP6-r8*R#ԥ.)+g8-N2&qJ͇Ԥ.UMEcӠֵT6IUqhU4[}BЯuKEXZVP\+[ֵur* CJά6 Cׇ| ,h0iEl2<{\#+Pֲlf mh;eMVZ:փ6-9Y۞!-_y[7d-+qb\Ԫ֡e `;%Tvn~!^7 @o[~E+ [^^]&d)^L+%/}s-嗻` "v灟^y0Qߔjw_5qB,O P!3Fz Y).Ͷ9ezKi$+YukkYAr|$/Y)M~rS4eeZyԲA/+6/ٱ{c(ȋ1r&;b\gbyVF?slh@ ͋vgO_N}2U-]N=PtKr֌5iǯP(o` |?Уo,=w=`/[$=td6QHȏ.≑8Џ}bW%v/X] @IQ__qi^#t^ ` Dۭw#D`M_՟>A` ")`B>%_Y_m`u hXA%ѭu2! FN!rǍhlaZN: aa!ցv{ PRHfbQaz\$J!tl\C|& N` ]"` V $$BO%_ax,G-v-~bb/ޞ #C#*Fb22b3!&FsL#YG*W@!,*H*\ȰC"JF"iܸG; CIɐnTK bʔ(q͛4rܙӍϟA ݸ#F+HZʴӧJCjXhJڻۭghjZVTrpK׬jx˗i! GÈ ǐ#E܈j1$HK\Ri2gR@ռ 3J.} V^+Xoȓ-V-۵ŕKwny/`6bO_!)ڦ͹ϡC&^rfklifnHoDU G\]Nr-ZAtpUgr]wKF`wOyX8{e3fb)d~DRJ (klD).`啴 )\v *F_|`bhW"w݇τ4t٦7בHΊy"x(=繑&HA)~+)NŦJ9%i\y% ]z8{[v޺|' JcZ첓Q֣*iƬB& zCB*_꘰jf]ptޙ' h&f#zl)AJd#-MTl1z-K.Aj»+s,=7F3_fT60i#mX4c1 璬dkl8dmTB#Yw83-fDkit[kҚ94`kuYZ2eLkU0*se9ύ,ިMcZI/NT'.T;1䩖86?`u}vTigލ,"s3#;볓T{#(Ӭ8T񰖼y(28´ Z0{m7Ƚ>OqNg*$co$IpwwGƉq)C #X= Z9X}|[L(&O` BVcWg1+r-&H#>Ꮐ ^IBR L&HAJ㒘&7y;T:&O򔄹R':ZśE-+ vK.*ˋ Ȼ2VR}( +#Cx({#HuH"HG*tW%-Kn(C9JLrVV%-bK2e/}(`O`$f1YxLd*3hB3!L Mmrg!ʼnHrSrB'$'FӝF<9Og>䰒N,ꔂbӥB9w0xX+Ƌ$Z@&eUa0\1mn)U!YMIy`iΛCPHb:P&KY}څ'@ʪZn▥!aJժ窻*WUu>R Pj[똚M3s!zeג viX:J([YӨD?* mTG;Uv?_jZ׾d-jg[Ḅ|;4uqR2B}n8[Rn: ]r 5 f5KY}F%UWIņj[0RnFvݭ{ QU0^is8ɹq@*)N S7>2޽,x3+͖W-TYL,8j<# _90pK#'ؤKd(K9Uۑe {-|0 v9gqYF3NŢKBbA'cw94gWg|||| cׇ}ڗ}B`GU~~^)2wsnh|qw6sS!{ȏh#膸X4r I%9iqQv'#I,')+ P-/9m"@a7nē\X}UY|og ]uP5.5Fh_r Kqq0_.D0 >fy!x1ؖ8!ЛOv't1;F搜ՓoiBSķy!iLgf0 YcU.H p!ّYmg>i.U;yIJ钼N2)H蝶Uiي>4YY2'ٹٔiWiٙ7Pq)R pПQ  9 *.'7٠trrȩC)t(C\iaƘyqڴiТIy)+! ;ZidiINtbJ  `+RG: 嗽Cڪei#ʝ%ɐY_yRc0{ڧp5:Oڣj:*(0UQ˚) JU ,(ZJG`zb-nh ʦ ɫ*T "yJ3ڞ5:.a٬?nN7#R”)Jzຩ٩㊫jx3G?=EW"S.;U005R7+S:{mKI@K6B:u!G碴KN2NQ&rϐ[^1eKg~i[fk4Mr !t+v{Փ$ѷ[>P *6'8ָ0SqF۹s"K$bӆ[!kh[;X$2|!·귀4/?cK%ѻ"85;˽N3 P+E&[Kۺ v xLaԅ+ekk>"EɑSP!grB 4%|mse5r@|mEGg"Д'R TVi`-@Slh]pSșlה9۱ע?؞.6]?Aʌ-TeԸ<٘&SS1qМ]1]d6 ׳+&pbX<| BZ):Xx~-mЍ,+6Jp1P6=ޙ&v@Aە߹M,a3>:਌ȍʭ!lJ~ȡ\׵U%[A8:N !:2GU^\. r>萾HX$+5^5y=ZUXcmiI%p%SNq7q}]:]~&Z#n5tt'~̉sn nЉ H ~.>KQ^>iI >VRd{땔3J.GH1#+M>n֮a~ڎ4p 5|^=:.H)Rr@j>bNcԖ Nɺ.Jn%@`+n5#SӾ&P$r+K-O/2Od+jB~F&HD!@e /OojNrpgs$#@Bl՚)]ٮ{1LԚ䞶9&.Ӗ&o&K_Q+:p0 Y-TpCoޤMf¢ xmDGD P)dKͤ9͋OlA#ZdȑI.eSNMtS*AlЫhaF$Y?c6[>uT.8y7] ߇R̹a/qNoNe"lPwE;v\mʬ.Æ^-U{q\hDf xB!J$|1Frtjɔ*Y| 345)GS:C {哼njV\~m(vlYtk<@DJ)|Bp39vpg*C!m:̈TTQYs-6\6po&+HXQrrz騳,ܲK/ SױZ3;Js[lTMpÕ94S@w@@ tPB1ADЯF-H 'EK? 4N;4QW,uTSHUsFpdEGoiU)yHLR9Gu:جf h6j6[tMۣzhh0rs ,vڿ:zwiEI]I g0l7 ԈQbV-^e͑slcC8C29T٢cۤY 2׼fiF[gбFh<餗viꍬЬŇ})\l&=?[m4mEU߇{\+pUW4d'%O6藧\ͻ}Bg҅2ou% { v թk$%xS^bu[v=ŪQz=™p',=|k㾘/s:[JqX BLAѥ., &t@c aͣk$F>moKh9g d!q@L#H c#/PXdn) ճa-k$ox'ZIŨf0"ئ2dhD7$#*N2m{cXH."f$!ّE*|d$'Yɠ\R :%De;Q)&)"(ˈp&^\.Kŝq%0G1|G2LfYMD&%7MP\ & BN虯I9Ag;IJwd<]X"ڍy2q@Hb"ÄP19B?ΌD{ʈ&6GBIi8K:ΕlR+HKe*&Ui+J{>m7ܦGjex*[U>F -zѮjt`XC Nld(ZRRqkkUWy^IK꬇``m(ԍfBƞ9Tp̬Ѱ"%iL:Iʵ[Zצw>&a]9O8%Fŷ$lG K܋ 1yyujǕ@ʼT2Y>j"4kbgA,-3EDd^W+ {[n%S}sߠ) hÚ@!K d&xmso1fu9zT&+ ⴖ+=qjәbXOq1 4`E5~׍4D-A'!=8d0X}yM)o)"|zv24(|j7tbu}PyiT& =VfdNr8QMuJO[f3yFuU cV;e>}cnCeϷβG1!zN &P#dyEJQՁy͇_]#8pS$G3?`vA{)vi{F;C[> <Ó>cNjӾ>b?|+?Sz>o:{3?++ 0@$2p@ < ܿɸ Ԃ @JBT:AS:a:\= CĿ;ˉAz#¸;@p`BsBC3+>|l.>0D'í;2 c?-A::?GBS;C+A)ESEVT› > BGy"4EC‡ɵ4G~$ 2G,j gg FkFP4no|AG6#G9lsń vl[x]DBLBC~|LH \HsH<H F\쐎dÏL7hO?(TGM ?-:@ AmB= m@āEmB[Hmҋx(})*]+ʼn,mPQ%R=uHUU5@yUX?Y5U,!@$] U^)̥rQc=Td%eL#:H] &}ij̈́klm: U/W1]A%†(YevA8Qѹmzō{Y ~BUCmDa+Ut#uUԆTLMN OX0tؖY=YLj|זW(xه\p̳M5#Y,Z f=HsXLjTkTlTmTB׍ERZZ& @Y}8 ӵY|٧Yۄ[[[uTgWhUXI-\8}>Lל |I ܤh [\T ]0S`ӵՕ֥۝J=VZE["fEgV5\E\=ޛXйM=ۚމ}ҭ] rۃ(_-VDUVeT\%\Ju_..U`5c`އ<|Qݪ`]m\ ^-ڃm~XhIad֙nܱ֟aLJ[^ >!"#v$%ځ[_ޕV5E] Obx `\^U"EVMdrccc:`ZF>?`5?&db]dFvk cc5)cdνaO++` X x#\Y̲CBg_ 됩%OlMB H͐<}]\Ghh 6YfN3xhni&&޻ꔖ{闐}iƩpf9h@jnk(JΈh.o*s+V08C{V dcnj#ʖΆlq NL$pr1p@RpW>pl^ ?/4QnL0faqpummmnV1r;LrXr2dž#>p./ R0s sD3Go4Wo5go6won7o9o:;o= >+?tt/cGDOymp3p7Gr8pOorud!^)u8rVWg!DZhGH!"#W$%NvwYh2 3\5 БыgնP/q]Hx+/hwxohY,xqmxw,:{\|0pwpw< o g Wֲxk˩(z y15GRHywzwɃw߅>OzmzGw=Gww0W҂ߋoGgx熡8l{Wm?*gŸ8 c۰?o_ؖ%77 oay^}}yyso{O\/.9V%Do& PjՈ$蠸rkĈhƌ6rqȐG,i$ʔ*Wl%̘2g Μv'RBhJkԨ@bjU֬rkbÑUgMj*dŋ1z-H.l00m'[3t)SMJekjV\~*vlٳiײ} w`[!'VoG /n8/7A1lHl(f7s0sUhǏZuۍwޙUy^)w4~燃/ݥM͡6C}fq?+lz)rv; >/ _w?"#=L C _%?e8ȞK6HhA\pX!Ⱦ~v^|&(!i⦚ּmN״6)NtS8өu|'<)yҳ'>}'@*Ё=(BЅ2}(D#*щR(F3эr(HC*ґ&=)JSҕ.})Lc*әҴ6)Nsӝ>)P*ԡF=*Rԥ2NM TFumS(խ^ըf%+ ϬnUb*XV7+HZֶ5]ϊ׼s\}_S֬%: Vuf3[BvD_+kdVMgi9-lI[ZlMjZ: .j3+حk}kY ׶ qZ*sݸԕmi/T6da-wy]fV,/$ant *80b`]BLͱ}y˿ 0 \x Vp^47_ ְ À8"v?WXfsxƆEnc̗b=N-!4FrܲnOXbS@`e-G"^mr Jv@Aw4Q3\Іvr%15TyS]׿\ ذ֯DG;˴)kkvطCs*cWőL/'Cwa+{x8}y׻lwA͕~WZ<]xG\šx}FǙ>K8)zOp7ws[qcc޸p'9 \y:7:҉ssȼ'4ͣoKWVțsp*| ]_>}+7_;v`}{قw=1c/;h4uW_^8:?k,!ΊVMJ!}XD*Z[ C6F¡6!N!V!jU ~ᒄb!D"."#$:$%V%U&B56b4 m0= #1ڃ1>eY3>*"|5Vc,1f6nVݢob9/.D0 #16&##0Z3>3Fa5ZV66vc^c72U;A:6&=C>Aɣ3*\$FFn=RcUHC]? .Z.d*;262CB;J$QX$F^FrdG~dH1dF4d&9B&L:$l1DRNdOjdG~PPH!%F(41%"9%:cLJTV ţm&FnfoCug& pK%g-s>gtBtbduunvn't'1|g6g>g;y9nK{'p pgg~~g&PguEd'hv'2ha9(!zbz{{jhhphx~h~E'b ꉁe(yFhzN(ofh|n('(CFNxV<]٨('$ZhJ癆i> ~i<ᩌn΍J6xaf|꬘颖2)B)AȪ\>eު>$= ZjΦniӧndꨊi r6ɪuj{4i[AI*lƯZΰi~x]hòjʳgǴj:hIꭊ+=eh5k{kfE:Cd,jnjElSU_5RPೠJ >_iuieZCo1r%AJR5(Ҁ)8WrD;!l:ӞJ c!E6 eIvt3Rj|6Y6EiMk#emʪѳv"Ibғ;pW"1iM72>;*TǺDjvm݁XeIxٹ*lzVa|[ѳl֑qR x1%[@EW(%jugT @Y)ȇc*//Sm{_/3yۻvhV`q.y& ^ǪnЃpAJ36b|,46/DG3T̓9Tj% 8ǹLƒg*7-<ȵQ|ٙd@ Hw},1CɦOfpeXvJ4/]a-_81jk-xMUq9Ŭ6_-XZֵVѭ]=vm"bC@vše7 좓GNh\Myx> Oܼ:WTtx#^w{6ukH;s( -pŇC!ЖɱhLhAqqw;[F K>Owfc+_9 Nt3䥹j{t~8:>.ƥO_\g:wR_{/g OQwI~`%I=Hs)<2NL}#UHr-ͦCf ?FHqf&Y ؀W1<&xȥR2&V~H "x $XL#c vfdOI7w3}~!~{ b~~f~x~_JWf xUb`b 8; l%Bq8q  ~"8%H')(+j-hT/h1H3y5}}L~<>*~Ge7I8KMwt'X$XfY!ȅ_ h-x!+۰ҁo!8t( (!*h3,H .W7}gHV!?H xoK8xClЉ0Ux%aȅ(HPqq򶇐Շ˘}8Ԩ׈iYq((W,ْPpԎ"%+ъpb#d E?PFS!Je%P &  IZ)dctXw@%$W'9+ْ,T8VXXhu;w> x(C9;FyHL97N O)hĨr1ȌG$%Gfa7^A`sry)w@kȎ~Wx^PƙӀ(>(XZi!0YsNM yq@1QY!ix@wlHI6`]"™!)YUْkɗ4y?-ĜWh!Ytr&;8,FQƇ\ٕ =ȞrR"&@y`́&yIʒ*x6y Zș+HJ HU"qߩቇHa2آHRWA|:ʣi@YB!&"zyZK:i~ÙS U Ҝ_21yewk,ڌvcNe|Z*~3ys %X`WʨQ 2WZ4 *Q"*A3cUҫ0Zoj:1Ax7 )=JiIU7YSz;yک* Kɷ~$Ҽoۋk{Ep%չgvq[ kk;!pVɻ*(X}{+:\*q.C۾",ih%%(w!EN~`-f#>$Q"4;8PnRBB<-PK\`7̖kī^+gs6Y})b7d~雘MK:ʨ.XQ.0R6hS먝ڹNjX+TƞmC'Qz7)rg. ~)Tk1^F{v﬽&wî~t>OCoN휥np^by<*Z-8<>o@oϥԌ ?}ft]Xpod.e+l皨W-1 bͺG:0m~3jo2Җ/ȜΜҤOO ThPF&U|Qk|֭]J[{hބmܹuﶃou{,m K 2( 3X >!n*m\A#vè6p DL\DcQDn%嘋ɹg:: n;@bjŨ +LPo=sG- K,A̮V#?N)JjТ:#p A)Cr6찺[DQ@Yt+4U1GyF!N$<'DFӒ0#3TM7Ss9;0AA?#Ԍ,PLkCQ!̀A&Rt-E`]v3=nS:unzuԟw_"9$d5'uVppӛ2ܯjc 4p;Y> Oj=&B nedDG%9FvEIS8ezկ}5#SI]RJyX̯3S4_X3:uٷPi)veѰP[`fIMtg_c p31`kSм77CdžKtԟ|OZr2,fbvl8n La9~ `>< " i@}/`h,ixA1ӳ iхⓈ_fhCqXaL| s 4<2!$+LFΗ'+r$%*SŲh el^,ɣZR2.yqݰҎyP$tsLAl4(k%c*Œd@dsRL. %;oRK',4<哖?U=:&u$Pzԃ.9DRH":G OFВKRy4$<_'잳)Oq1C%heT6J] SABUSfyUGj&p*(-+KѪӜf|k\qϺ`mkYqi-`mKC6Mf!ZQ^T8ŪdMRҲ̬XOjΪ0 m[FP9M[ֺtP:{BGB]E:֪,#'kVdyٲu.1nͧ]^־U_[[8]]f3̧FSI=;#FֿhS3nrLg] >8!>,XX.6uDiq/E>U+b7ƐݨqU~9scA'i_iZqª%fR-|H "4Z-w_$"QRED &'L}j<1@իF/`(>h*DH G<մ`{1nk̆6EF$[{ 4l&Ї>9}mCAt@E7ю4(]i^д8O' df KMmOzժn5``:.s].K=l(إAl<6kc'ڮ}hpI.=iKj͑xsg]Gq}߯^ ZW#xE4hw!v#:Y9 +:3~V7g;~қ!N}QVo巐;N_@߱K_tK; 8; þЛ6;;?ܨ ,At5W\J AKE?7H:a Ԓ;5>1Avp}ђ[9ƀ \c`:̃(,{:"!B"@&K"8\/ 04Ad A<tѥİ,4:\!dC8B$%|< 'l<`$S@+B,‰Bn5$ L2 3 ?$@A,4B}IDEaYG\H $DLDcDOTPLQ|R4BEU6ԇVWCrEZ?= ><:$; Ca\E}@DcܒETFl|Ʒ*L@xHFGvpQ2<p-qrH`cWÓ3M!ٍ$LDL5TṫL_LlAˌ$L$:9MmJiXׄM/MtF6߄8C 8lPo äNβLHMdҜ<TMˌOL,m}5]PtP!m|tPP -8LQZQOѩQM($u+N9NR8P$N%Ή.CPQR+ ,-SsƄQM28ASݐ7SP95ڼS % e ( )T,Q9T-BTTdsTƄԐToаK45N UgQ#$%E&UUdU0 QDQX `]'C@ 06tOXbcu7G[O}V hME,UjSkSuSV)5stՇvwWkWWWIvQ~4mցّ(ʰӄ=ձHU]AnM o%ApX:UC-,XoXmmѳpM={= |5KtٯٜYYNY klY̡ p5:Uq qռڲPW 6yXzLm%=?\كmLSS&S' UEeR[[[A­m]\uh5eeD[D+RiEصUضuͷm݉%[E] eZ]ZZx5ܒEܓU]U˥Y-[PNčTA]ē3_W9`JsB b+ ]"dL&AxsL F8"$ ႘ìڼEBA`~ 4nE^`X= 6A _  n LLr^b0a@T `]U`9(`~fĪؐ~!.">#$~%t'(b*S,fL-b|aa23N\clcI8&9;^TFe+.c>dfenfgNhfijgMWeW6G>h6ho=pe&3bh^6 \ Nadc#q#rgg he^mVLiA 8hjt&6qpať^j&Aƽ|}fi>aV♶Ii0bj_adv&NVꦮ꽛ꪶTviX3鴦f`~ݹk&m hkdjlv)VF~l`.j6vbff|ϦbWi0†8&k6kNkavuކߞG S3F\HOXo&V簞^I@):4Wckb^ܤP"o&bNnoK}o︜osoofV?pOGvЀԯooo ?Oq$GCoq o} qrkG ؀$?sɈ?~rrgjqwVM"(-d4/tQ5oג q~ kJ %Q)SOu'6VI<:Z7'[6\?$uvob?fT]UO\vfw g7'hgZ[\vl(_'Pv}p?Xc6s7FtOXwOj Dj/Aqvkmw`w~7dr?UV_ou{+BAw|zvvjxxWvyq0xAx;yKypHxyoz# mPƦ@F@2$" $rqq{c"le o쫟@)az{2ېzɪo{{|{{ѩ{ŧ!OkW6% |iE 7I8yd|Zh{|ϏgO?}z'#}ب}ҏy¢XO(Ғo/~7G~}}mr~3w_~'ǢW/ ׇ} (h`A .hPĈd&Re⧌p#",i${*Whr@2gҬi&Μ:w'РB-j(Ҥ8a DTBFHqE36ذ(Q49J.[2 -ܸrҭk.ܶOFU8jUmWf׮a-K,ڴֲe2̚7s9Т'.4jvWC,=gӮm6ϢG&:jխϾm8ʗl R2ڷsԹ On׳oOyy no>7~ 8 B'|{T :H{N`AZx!EЄf!!ҶA*(")8׊18O-S4#=#A 9$Ey$I*$M:$QJ9%UZy%Yj%]z%a9&ey&i&m&q9'uy'y'}' :(z(*(:(J:)Zz)j)z)z_ ֨T꩖*5j4+:L*i66 찊b,6(J պz>-ms.Y  Ȣs{/+nλ xo| 09H<1y4X|1y0*rH=6L0;5U[*kfis_-c\ut6i}.+}6pǍ4՝xv{o߁=8svx⊓ɸ-_}j^9a,챃'榟.&?z낿;^kYD{<8|;KO{=k?vmN-5+C/}[ۣ0$Ƶ-%}[_{ ~)W?z_=,g = C@@0`wq&%Bհ9!DЂ0$8 P'7|WЇ66"BЈLC&/$CX@ъq"9+r`KC D$`DGBKhB#ZH0StQݨ8>Kt\+G=R}HҐ1 "HF #$CIlXu6=^(R\LeV-|cC9%/Ps c1yT*$ϺY%6;OrSL7;NSSŇ5B[صʇ:jYں^^ P[mo}֨Ujqs\$W*saA7f9O>+~qݭv \n c`Lyzƶ%{# (}݉׿k^,I\+Rp&_./4tq9DLb{i3V\SR~MeYsq~%x?["c No]`Cn~-Y2{XD&fNV2e,c9KΞoL.a.}f_d-8QK8cy|~uh9ϭp]:Gn>J&LMׅBB}Q8u5RVձ5j}k[z}a5P6lO? ,5M견՞sn{޹wnup7My r6mmlk[տ5ugz0Lx8u#'λlDž8cq}gYW;Pf좋r.i> vCz`?#vJO8v "`oGgV/y {/~ߦw)IW>뙏k\PW8}18@lc?]۩ ŔAIQE%yu^qUߩz_} < @ ڈ|rML(kM}^Yt4R+i^D+$Ya; N$5ҡ5^cArc6$-D..*"\䳽`Gz H5 PPa|Jdu/i`5aM"M,"9FWD eꔤU %]Nq[`ː FB%2IeaU VV~WA&7.d,V˹\Y^E7v#3df[l[[\\en兡^r __`f`faaQb:b%82E&M&ETf^fn&ufc[f%EivTj#l̼11LT:T֦U&Vjb~%co>pvqNqr&3pZ2 HQ&Đ&t\Zq d'Fn'w6Xf'3Vm./'b'oo:& ph+}&~z&LNgFQ(q hJK4U_g`Zhyb'inrhY7'b[|~rP! ,*H*\h0Ç#>L@hG+ CIɓRò%0͛8܉OsJT(Hv,IsJmI*XhU`먬ٳe =ɩIpȝ˨!xy^ I(R L뀱uǐ#H9"M> ) %dHNMTI0̘3+J^pޞlfiRs:jwvX(_ɶ5ln^= X0aÈ3fMyɕ 3gA=:o_ʤyk>6nFFǀBUqp@Wr̙4[n$U^CQEXiXAߌ4(z{A%[}Wfd %hM&XԂ [Mha\sFt]8uJR_(rwui睍H?񸔏'dERcI.=ɨBQX%z saՑhr(jAzJgO~I6d5jS.e> VZ)WVإũɕhȉC8NJߥz1>빇yȺV 'IE}횚4ȿI$PR::l!Iu†QY{džBeօnKjVsP,("0nod}/- Y!@!KCCQ-|IW.;YEScLto bnj|HXp&.u-j/ ksLAs]NmtKL)sHMXb֘oе_͉c_1e7rnMaܰCƞw㭷}wT;8{8&)`X%O0]9\eε^{ؠj.W)6N;އ~3P2߀3<o2YKG@R $=,Y^Ű9큮{sV*ʼn|SYG]v`޵OwυCf*IpbsNvAytj <6kU*$ZٌŨ2hL'I't[Hȣ1Ǟ$3Q͘BPsKF $'F6LC6I= aSC!**D^R"Hnd#b/)J4>p dx?" Eȑ%Z IJ>2%i*!eQH)=2O+_Xd̪#o\rn\/uK D#:Y-fJș f4IMX3G YBFy'Hs;'NxBDDPTT%?b1YR-A`Ѓ- MCP3fz#RL͝Gln]7RGK. :cj'FU+Ab,a$hS29LrZƫ" (W3zGX}*r_&7oB;"z~$I/6S Iz=IE7e-q\1&F߃қZr<:7cֻ~MOڛl.4w{IjRӒ4O|as$ah|$i#~ZI[ne+%9Hl$z{m'K'UOd&|'y,W{=zz'{%x{{7| |7|w|`|`|ϧ7w}}Ww=}~4~!W~_B#Dwt XJh GNZ TXp煦b{h8!8|/"؁E|]|m(oHqH FY`F/5gcG(gHT؃마ChI 8XXʰ zPw xhUH!C' АyҐjHdWpؑ"i $Y8*9 ,ْf2 4Y([<ٓ|7 PFВJL NPThTIHǐǠy؎AyFiԉ gzFY!Yz芯8 yYًx.g )!Y&Y{()ɔ/Y295IA "=ɓ?I ELKO+Q W ZЕ똙`icy5hqPǖ!C)?{v }I)Y}Pi '{Ky AizQQљ ʚN#hР:UYɕɛJ8y xaPY˩ʊ :i ֗Yl蝅 j} 驞ɞɔ)[Y s蟏: Vzz XȜHgh"$Z!y+j-/*1 k(4jN)8j٣{)*9G晟)AIUzV#^ڠ`W9Jx⹦ئ1v*, {}Jpڝ/^P7J٨DFZjcjکڔ*Cz(긪77*zRqhQ]9ڞwy" ȚɬJЪ{z8ɭ9Q韘zj皮ʚ;j:]YwhjjZInjca9Y .]0ٰǪ)6*Y;:ɨ {J $&;([ay61xГf"lq{%8[}иy7!HR  yL, P!yO [  ^'JIgixZ  v!ÓD!`āñ}оÍ +C`[_̺T| 9HƨjY$7 l\ "+tev~] ~܄=T9 ͓TTtu[B ~"ZY'.)~+\ n/Θ1^<079n)߸<}> MO ܣHm>LZ!U=ޟ]ޡ}ޣޥlo{߲8wy>5]1}΁~ >.@,=P||ŀ ƍ]MCdJVnXZ \^%9` CT4Tn };ʜc![I H7W?mK՗wݗsJ$O =mQ4&.hf9bHD\!E+@E@V?$ޖmbTbLї!JA)+-/_@?yhZ2JQܓlZEÎ Q"U/Wo Yq\5^_`τ2yq0=/prtv?e@g(DAfSCooEcQZsD%/YDTj6t~ݯ'n yG^/RT@!XA.LE15رGyD4Qd UT`K1YZYSFB9u.\OA$Z%I.eSQJYU73 uW [٢VY q;]Gؑ_ pXaL1$ŖTOt aBFHqEZr v$I(kvKVѹg>m{qɕKkV]pYDًoϜ3r[^.4aGdkobWsp0jC6ܰC4$D8\ŪT$E.sB;=G HރϰĔo,(°ԒKռ0ǜ?BSU}rNE<s<>eVTB:$ Pt=FatH雔&E4* M5ԛ<UUX^"Y(\͗Nx.OLXXMeKjY;QG%Zl`2[:t\=7.ZTc#6aw^k`}s_W"8f~ YmamH(xTm=4py"9"Ew#SOm Y%筗{u{D&/KI黲\"V+h36!|6*gʨ‹ C"M<[O.s(w(VӨ>R“W~y\njoo< >ܢZ\/y\'˭xNs18,u@/BtIt UֹTC;Dx1&x+\eP;1Þ]7{_GO2S*O~V:DLufݎ@a« ./QF鍰z~ Lm- cH;IC8$C#~"MJd'0 bqE&fqīKD1R:+cFΘ΁mQ("&bPAF1{t_永 "AXD/!h$ B1Rd%1IXZt \ݳ^7ekݸv?ۼw_)v ױ(>nZe2sȨ E2n^7FL*f(C̘ݢ%(j\2h DȌ,[vȧHGz:t#,}i,]ғPZ Lb0Zys\_bfYcXpf82hZ\${E@1)cLE/0Q#]hҙ6iZԣ@O\U;y<3;&͡u\(s-@-DA(~6}FmFp[#nP3Pn7O}` Yl(sFGWC9A2tb-7?:ImoZrpkɧA.rs)'=W;uݙ\Nv97agIxp/5Mӣh:%ni;g^Ngg9]v]nr湫')qw[KN?" ,WduOyyM{f mv=>'6|8Ŀ'5+UQ3 X# K@ӛ{#3S+,+6#C:?8(ؿ?I 7;pR@ Σ>=h K ? 9 CA#4dAk?GAXY98LA9t l@{@53*%T&,? 86* BVBBxDH0| +3C4>A7DPACRL:!ľC C?Cjå#DCTܱp?EL$HGĸsAK,CLAMDPFQ|RS$껾;WTBXD"?ZCEEX^ FFb4F3G{KLghijCY،?4 &a2`QD ?MA:NHNp"ɹXښ7p`yIm MMԒ8,H\LɄt7tHHH:ɐ| lI,zɀɕոIɶԶH(JL4PH2XJ?xHD5D -KJqJIaiLɱtIɻIT˾`Gq΄ˢKԙK 7zʨ| L{ 5ID,LTI%sDK0MLLpFҌ[9͞KK#rMAιMP͏͓,< $F4qΥO8ΤlLNM>L(MlLDLL2K=3N4 4 T O]d=2Ol%$Hy Wʏϭ|d &1A7ې PS}(QT;ϠZQS {k"?%O0OQ\I46" #]KbL&7R= )MMXMD- P/ EђM NN"M#D:5 ;]=զSRAEB LT?F-OMD!]*T8]NblQQ%UgE SE# ԓXUh՚;ՓհլdPGUEOJЕT@ce %}WP<}VgEU+mMk lmVUTnՎ\=OLW Ո 7Oxy;e{QԠ簣JX+/T05)*lXGjY( %<5.}kٙeٜYS u ;!Z0ZMZ].hګxZHYZ:΀YZZI [e Eۡ%7a,}[[2ۨ 0 0Y\U6M! -e\qȝ]q\>[=|JP5]]- ճum ǥ] 2]]Bݫ\E^ ]G %^U]eݖP[Ð_C;D߽eH]C1^y-ܱ=35E` K`9_VM((=V&U e`n.c<f*?8aH3`]maU]vԕ]%` a.a5*#Fb?Pbeu.^^{=W4h⟝.b  MMcN^b􅨒P;<-cNn3^51BCD.a5fucH*I Jc@LM  YhDC#3b.S8fd^Aqfnfixfhifِ h yTٸf\gePEb3dNuYfffhijFkflNNbfgN rF sDtfHu&vnw>fe֑yz{|g gfʂ.i]> ^iVxiތa.6avh@^jHhj~璞 @f閆阞i_`f2NꌦFXjjjkj0hij扱8uQkf.ꍞkhjHiknS(m>ۉ5h~V~6고llkmmmFm>N֞fvfhkgLjm.nFƋniz nnooo.o>Ho^on`8pnoocPo7p?pwnpߖpFYpmgo7ڸUr!rdFnwS0^'r(w_r*q+!"G#wq%&(/er4*W r"?f#o$6(Os=_,77sSs s.=_t,gs-U CDglEgtN56sI7mJoK7LWMtVO HrA7n$[KOnu^WWXYmr*u\Gu6)ugh .vfGv[egg1uPoiila_`ttGwowFsvlgv7vxyzo{7vu}ov_xyhG_=rfx xOx/yO/yoOyOoyWyyﯙyy砛z/z?zOz_zozzzzzzzzzz{{/{?{O{_{o{{{{{{{{{{||/|?|O|_w G|qgWf||Їp'-w|Xm1 >}}b=#?#}OZbgX~nm\lĊ~Y-P'1~0_|m|5*$ „ 2l!Ĉ'Rh"ƌ7r#Ȑ,$*WBi2̘2aiΜ 'РB-jВ&Q\e˙Rsؼ΂Hr+ذb#*%),Z*F*jͫXyk.޼zv-+mZlHd0\2r1Ȓ|VmN8ɢG.m:Ԫ- ZYD r )_f`-^[9'v~_U O&H!e(1bH{1N#`0R"A*bJ!܌QJ)Z䞆:] %B9ѓJ¤ "9%m:VWfyy'Bi$Hfzj&19xG$ jJTA驖XL.w 6z`JEZh!k F瓛z+H)\4je>()O+\J;y0] d`D{"].lUnԺB&kUBl߂۟+! l.7D'ko;C~ < –U%0j+R\.Ɔll &<v!PDtly8tQKAlȚ]{!=6Z|6A2tX4lJ_(OC-uY]slIŵ]de6ډK6F7}-Mlv;wqߎxoXe822ў}HҒ~; m.pήזM ~<{ѺېMw۞Bܻۿ[N#?x3뱓2y`J?u5k|!O>cyވާɏ'k߶=>c A u\옖10!3Dp G.- p2lv@ڍPz%D S7z>!`fX%:a@(A?<t;F$!1bP|L<^PaHE++Z EF#ehF4.j<8q$jvGTcNL>x2bƘ'(ހْ2Af"$ٸf8 cȳ2|d$Dɳa; 'KY}U&~r2泸zqtli\6o23 0x󞍜. Mh1L6NZLC:*k:ZNbfmhǤ\j4YHêj`bIq}):jZ]1]C 6#icI"uK@KD=kg;_|;YnC[ܹwN]v̖˼-{/ߟַMFp'x ̃ `"<^eL::>תQz]ŋy;?-3<8۹roԢƓ @U=x7Bʗ0 9M3>j_nK'y^/y2yt~b4ǤX?U?O&je22ٟE9s/?~P|tG>C|0_{Gåq4VLl^A~ϐ`^[湟_r~, ޹Et q `N `!  `  !!a!.( !>8!N!,*HX@E#zHŋt2j# ;v C$(4ґ˗b4@f8&dPσ qRB4ȣA<´S5k,TGXhׯŠ@ٳd [۷ʵJcV>k쥿3EXFDY\ǐ!|wΆ˘j̹O#J8"FtZE:$ʔ+[t4qsNLlHOmuJ+sްhUv[r{7{)QwLXL"O>Ps(@=$"f!vxG9IXdlVs[o hPP7 UG=hrO1t I7&Ywv8rwn'jUfzŞ{|Dw_~ןh;1ځt%R)&(FڄPT(ln[ |Wp'֕G+!ʹd88&]wc( ^I֑-D!$cAwQY%^YxQbbtwI8bfP)'OV''&{ğ&'T*c[é}$! Hbv[+^2A MhfJȩNwa @ jG8VѥA~0):UF JՉ6&dv+s"!t4bP:X(*jtGRa:J:##[{@ӚԦ<,-|T QU Rq0թMBժZ5jJb^k AB֔5UַvwQ4u|"ZK| v5#[-Ų3},d%SV Cm_A20:$hC+SU-jUZ֫Tb5ޖI:{QpMCU* ioDJrĤAiYK]E 1vpwhHޟ(,z׽!jRLW@-Rbh-l"82 N8`Dk[FWu,ŎK;bc'֩W"qTLcXGtW?P+"Q~:d>2Q&͔sPe\ًenIṅsb\f$iŚS˾y0JjBg7&Mͷf 8.T,JEӤ+_4[nn;dcp+ArtY5xS0 g\$\Wg>d !2L=e3N~v*)S ^`Xؑђd.sWrt[E충uA4u;˪$HwD[/ack p6z~L'4BrmAZ[YJ~r<,{%\wDEH9"y+5f}=ϾVAt(2rO=Uul5] ]2nwn {˟UǼ3OsRf7CI[|st P<|h'ZqIaw*ΨQP*5drDݝ{in6*Mg> |5x|xy&OwVQ w_euS}~wuQ7xX~~~!Wn'qWJg1QQo Ȁx H=CGeeE1P2 \؅&h45SGbaGWC;Kp(MaCpd/+oP%1I|xqKzR8JG`O )Q4Ұ/a1iAX#/A>CSUM?mE}х^x6DatHL54цbqx/4DQGq.%|؇СNN874Hu1H4( > OAD2fqHXVȅ_!G waƈr txvx&}H H<8tXF\ o4X4hȉ8S6YxXH8H?XR) ِIcjYV"uhјV%ȕߘdXx瘎JXHgHNa߄eBÏx oPlXi Uot I#YA_p` yіt8ppԈ2)*46ȓȘM|MOOQiS1eً镭 1U2y'5@`ʼn/y|9=9B(yGx|S~RZ90/4* YYF,_I(`\Mqdj LK0nɒܸy)J5i"qi!CYe%Nh!"x4Vբ)x 46J eH@ښ3YuܖMKP=4ؠFٗ _akcꎊϗ:jHm:p:vX5*z,yy{ڧ퉣 [i2:})yWzv*!कZzHĹ Yb]`:bNwZ#j őY Zz j?Z#Gf$: lYL:+h6Z-ɠ㪗 \^^Юdya1Qy|ʯ:Zױ{( JHKI ۭ ѬbQzHJڱy*&6){-;/+nJ-*@ՂfK-@йl Ra;M]1<~8ӳ+ l-*}MQk\ y}{м΀쿣ϥ\M(=ː@1M×g1[դ- ]֪ ]aۥ1 |5mܢ <}\ Fܷ̫ ~(Ԗ-V}#mLm0d-zi ,pM3V۷ ܗߔvɽs-ňܒ nc^ف֊=yj{n M[- (~*,~;=- 4.QiQz-@.! 6]8]pHNK^MNm{~nqGDt^c`b+{i>k=+nCt~ wyd9n߂ 6}ڋn>~Ŧ<"SU DHwcG^ ڲ^=uWYFnWă̎AηʱKn؞ΧrN?zmCv Y /DZPr]Q4dŊKxTx6vJ+*R~HpZ)yS j -*ʹQC*2#l+8ZJ7~r 7|n9{.*KDcj򂬳H܂K.K=QKF " M(.hя2Ѩ5>aL̃ 6A\ :gkVS%|ijDdQFyq++yILcF1<r.!H"+*SdR-9;R)$T3#.r4?$M0"V[^E"X1$L4Lv6݄6Ni;;I[m![j֑E Fsewt݅W^F̺W=Շ옣WU8\a.xa'.b^6cErc EvOT;Д+U1.FӥyR_o^O3h:T]h(NX2u%k˪R!evi cf,'j<#r}9EW+hNpN{pP}LW:L|:>!e]#ҹYWo{pl۶]K}}n&7JUfYl@[;M(࿲0@U aAI6OC&\(ˁa:B<3EA{x X@y2 bD(b{*B\4a,KKJ xy)遰ʈ @AXPCCQF02 s8$, 1C 4!GpÅ0M#:>K(y(FCEhQ,Kag#myˡ8pl-RTc-a$8cB|dZn8^lI '-0Lq=F>' . *UVĕ]Ĩ2>gܳF(bI 2̟R+d4CEVS ,B6#I0Gf&d3N%3D:?:SfQH6L":SBnKv4H9f71۝Cw.WĢyW9W|R{{Ֆo_{~DKE[f6)\9,H]@Xwv -| yè0C6y'GAЋ'KKӟS]̎o-:hБaKqgXq\bw|%]s `4) /FoA锷|ӡo^I: A/x_?}-.[]eeO{%Hws#K[<## UX@>i_Ⱦ۾0>?C?#a?Yk+=4ɻݫ @+@k lɣ@ ͻ: ":N ԣ8ĸ BAS?HC?X:A\?CL<`s \@d"^.~@V_1 N>b6v䋀ǐddbiVihiCxi#隶p)I֏ߠj.j8jENknN= (>Vk5KkZbA;#/Njk"f> kkzH>l(ll.bl~k 뛫Ok̖ ltm6m-8n 5ؖVР۾ΊZ$lmnF>nW#G:tێnE^n&nnnvoN0>^oFNOSV  EI~p$ p.1=FXpwpqgpҪH Dq q0qOl ?oq" 0Zx1?dpF"8HrJq&O'Kl)B*o(r5׊H>xs8WE=k~TA`s>_s?GB77sFs>sG׼@tB_j23&+ Ї2g/0ώzt"']GLb-s\u?b-rqC-]-ف r~b@88ۖ d$Qז5 Ma>z[_Eꩿ[Vx;@Z^Dug\1<.Ҳ:N$Nv ޻(+_:>r/6=/_?GDw!9%q3w}š'A~ß׿ ^`<tP x::~|XSA)AK|e!LB!##%zE"?AøZv""#$ї"䈀ȕx1hE,VE <. P: CzNA4cШE.$|c~9#)Gv~L_ )G Fd$*HB %-9LZD#=ɛ?r &)S/YeZ %>[~#oK_ &1)N s?ʴLY&Py@]F&!pDn)&?i0]jP@&2QQXbHCR=4g> t` 3~j9p_ '%!F5SuԣFHKqRғ LBІ(M&\7B*Q}tԣ&U&MS9.H=L 46iZt$adYϚVգm}+*Zk^Z#X VU5gbXǂn]8%*Ѳ¬Ucՙj\Vd@ӰtiS{~J*S+rDP2d6U栴Q펨*ka {$W=HZFDյ.˲ EtI+^ָFb{(LPk7GKUĻ^nxq8^\ >/`U5*la7ÕEa痾!.FxEpY,U[hӮkƍ2!1~ h5(& L*ʣr⶜X+aˌ3WjN;fpRrA Ԥ&:kII7y<4V2Ԥ`D`o00^lu iSֶuB=edӞv -S9ә87nxMQu~}!ZG N&v^Fߐ"vaNAlg[--@jvNd.>N'n@V3A|<ΐky } _=0 8uDjc8gǏ0?%?v/9s:&}] ?QOϙſ7B0#t]%FNv=nhk¸ x]뚻ɄG};|#'ԋ0L-o7a-?{vyDEAÉS? ݓdzDC^ #‧}o P_쒼r'_% oP_^ܐm_)]MB́ %-^}`ZUX]Q ax AHrIF }at&\z`H_( ` Na  Ya$-!H9`Pa" Qa !!_]LJo\ -)FIV R t n`%&!:v:"R v$!bbPv"|b~(a)&)!EbuDyF ]-N!"1b#bAA # "Nm0#9#Bc4bUc!^d#"uр#u#,F9b-#)^U<U^-^>D@!,,"*H(\+#Hŋ2jñc mI#(SY٠0-lA͛rS炟@{Y:bĄ>HأՋhZW`K֪UhӮ$smpȝ ݹs`jĈ/ a1( L L"7 ԁ<2ks B&@i'SZׯ2tؐи#G!G4y2%ʵ-5f8{J߹'QMJa`~Ww\vlپiծd8^˗m 6aaA_cEdMTvemg6ZiZu &∱ɖmDopGqDZ] TdA)ŔSPGY桧z{cZk-}u_|/V zVQӄVÅah` ~X: ȡ4ڰb-c2g6ވ#r;s? 9< UjQIvdxSY*%T>xhenշ_uG&%X Nhxfg;䖻(4>)oVz)ix8Љ*;/H)O9j_*% 7ÏgľdlAtqh,V\ʹ\'jm+ͭkr4n.FЮ:齠/OsxMZ 1k`k1_╱crq (*trh6l@G 3<曻`HdZ+-@>M]uGIPWUN}8ɏ~󳟨BHLs=0XsAdp bB1&lB U/! uCJ<uDN eXB&!Ks5Lg$%II^ $ؾr O"~2d\IWf(L]mY@\&L%;^-&I<8Lb呚mNY(r2gL': vjGOO2ߏ -+E=ŋ`̥#*Q]QsOF3j "S63IRN*ei SCz4b↾)3OKX ~S>l TO:Ȭfmc NҚDV;zV*!Ob;nw[6(p!h TÀAytCDd'Ȯfj~B/^dG0P!lgRE4L KEU~UK֧ Bv,\.kn0}mhE;ZӞ:Mj%P~ /mm߲֧(>p;\.J.aے幑t b]j73w3^΅&4r |9ߩ{o~ZbaLX&>0e6n<"q Lz^wRMG%R,#VdG˫ $%< |36ᘇ{$DX< ~ Dϱw\]v:o p?QƃhŲS|+`LL{~)w~iO8|3ht&7ᒜ;&9-k&"@|;A- z^UmՃ3 {`'{bbgefC_{0|o'|E|}b|rʗwgGa:UmW$X'קJهxQbo}}-~5~8zWygiitFMgOQS7Uzw kpG?4{Sq!EX2q%0ׁhg~}W&bطx/[58?ޒ:(ytFtwWGaauGuZhdk xaalrȁ;'I4zX')H+8s"y:% @`ˆ8x~~~iyQE(Hv #Gwt_؊b8kW8d' `1Hwc;f0) DzxCˈ+}8Xxبуt(Hxoo'z{gJ(]e{dhElnlrq萲<%99;ՇG P`q*.)+ >X6 z8Iz:ىD1rAEL,"8].uapg"BˈyqH,2jd=1pC&mPAֆFPڦ$|m4۔5)!B01 d20t b"ב 0"/ATCl@'IyT82Mmt?R8 tK1UOqـ!}+zAƟ}]l}hoM۲޴Z rۑJ܁!/DܠdLY-έmbEխ!׍Dyt3j H}lCm=!m}= 9P--=0ߥ2l4ܵ #<>n^>Dn"mPf8c'.)>+..-C36^ރbo:㖑e?CE>J}hp)lhRnT^V>BX~ms 6b>?Msio8X1U‡b)P痣IM49kܖq-FP-CZ c^:D+1܈QܽuѬ~n1wn+U7Ю(~~햛ZN{mq-fNQ]~/>^t~> ^"~$&[ X <?@xO "Wi'?͸cA nʢgĴ 7C+R0CaG&mWvOnWZ_O"n0d/8moS_,O!\`c….zo9|~"-_qɋQL+˄?dѣ^h Q+ ,$Xp` ~a($8E9!l<$iQ)UD2A1e2M'\sOA+ %QI YSQ*Q Y:aPy-٢qi̥'a/ &|I+;`lLeZ$  *$i'T.eرakmSEU4X8B4J8F'0<-f7wG#˗eάiӱNC|JtQJ7}SNfK,* ʃ8Ë்l#@s7Đ`ˌ+4F+4nZ5ZmG>3`?qRZ˨ B|Bt60A,T={Qfэn<7eԶҩ$0r7r]I`2tԈ#-CҮ'~}<1+74yRM7+O8MNsӪ?-@d-g`Q Y+ḵO5EFe`XUXc_j\wM$ adPY's 66TkvfGY1QD<z?If|4ci' ѐjɀMZ zO?%4zRA0喛P'3^00 }boFq mkrvvWu:ڮzas߹I@9%jmB:mσgƂZ袱;i^iZÎ[6[q ֖ Cn>n Bo^ZÛDzr$<#c?G?Ye6NKV: _qfG;.wz;@xS+-yVZ$)z~=y wO>kaX).~`寁K,H@懀<Ah m|;mx ֮=y0[G*/ i7ǔ|~C_G>ng=]2hD$RN[dGE uTTEER"q'Fݑw!A+]A5jmD6uD.#c$ňHg,IfRCevEPƨyςY۝26c,f\4FU K9dU Ӆ8@mhԡRxHu~7'mRk ٖ'AbRrs['dCIAeg+spT=]GzSwV5B[E! O2$+GzXβq|&vnQ<9?Ԭf9p>SKgٵ-U?F. %Cer) jJZ1t  j2J Jj NlnI\tM ^byh66LuHB-(6 xxfRcDA.K6M1qv=>2hMGNiyM%;F[n-ݛu=&ykii}P7}d^[8x|b4c<(w?~v$y%rr3d;0 j ^-|84 s+M3oK=t(OCԥNY}} z.IqҤ]_{ۑZk=se=]%|;n1|/zKލқ[Bx<<~:+f<@BVJ~,~c/m.`u[ h3製{*:$>:;@KR?`=cÜq,#:3Hs>3<@>>ѳ ,#A.$DPAcA|[;XA@໋@B٪ ෥7 \9@`½q8aPICq1,B.`Bk{ً?3JZCArcèpxCZC: ;J4 = >Zã ~K8sR,E<(T GH)I\,Bk OO\h[PLA[AhAU?<=T"UVW4XYBZ<:;<$ō@$F4F@dTeDfLgh iDDDF4Pt ;[SLE*AwlèzCG܍Z[H$H0ȥCSȎhHtD ȋGBHcƐIT;p9q4I,E?;Cœw<\KAz < @ɝIE?,`Ƅlȇ!8LLtʠxĴIJ?ƴLJ,IQ?% @ZV V#5V$EV%e}I= +  WMH5m%I׶Z8wxW_U|@BCED]ԊRVefm,]hXxXkؐXoVo -Q5W6E׵TWBaWWx4pYI;/@dتIV(&=XIاZ Z؈ݕUZdڌՍQSI%ZڗZZwZ1[M,zQUldۣJTFYf۟+/MT躗[NS25S VM *Eה͖{[)Wݖj%]R5XmݟQ,ٍ,ڭN1%U ݘ0:ͫ-YB9#3Z[Z\޳ްȠ@*Ř /^ ץeMZB_U\5e\_ ^OM^t [ђ-٢^́.9!Ɛ *_:&f2 at(Wm"q=#\_>aa`C !"v#$߽rb΁(F?Z+Ž{Y/~Io$Kd䳲1sƃ 1a-C`6W~e9# 0i B.d^L Dn+E8pdFd䥹KMNO>PQ.RST`exeXe-]e^~;dHd\|;x[pf3fKfN~*OPfd|wn_m TBqfrsvetNgeeŁgV= {hiV^I&ph hj6bׄ^imi`6.KlVg#agh葎yz&f˅veghj-fev^Xbjpj7k&*>NilifFlfn6jUvuhk>YWv%bFc>l@m K]~8 ZQk 0q͎kN.U ,NDmn n%֎S خ6&ݶd.bW^FjnJjfX0.pզֶ׆WF3m6΍6K.~l %v 03pߙ&NϏ1ܾppv+>n'9/n  q0H0Xhp qr /k b Gr%7&7'l(oG2q`*)(tCzqf0tsVpfpvp6lsqΑ8"#%&'*0CgqprrHu0ŜEu` j.9eB[8hvggB; sBm5vog@+#vǸ;^ONTs6co,I]rg7j*kemvov`ArowTw??v7wxoyfv ~x/5wWxtxOϏ.9x8yjyX1y$@?x./ _ywwz$fx v0 Wz?'/Kʩ{zǸzzyw7~GWyu{a7g/=]{7ć_i\rĉzʟ%R*͇Fo} "}@?ӿ sg[/ ؏̤}'{h߇]V_*ⷋG~Aڑ?Oع~?5j(h„2\X$'vhƌ(6r#" ,$ l@2Ь)&N:w'РB-j(ҤJ2UZfR_PZdZ!pj+Xa-;p؂ 6dBĉ-^̈cǏEj䚔V)M9 l1Ȓ'SqSRk@֮\M[Y5i5E-Dsv{/ { X0a1+ɸʗ3ogF1:v2d@=ǓgH91Nzx{ d>xp~:B>z+:>;~;;; ?<<+<;>髿>>?????(< 2| #( R 3 r jz SXw#\N08- W(7]MH?]I%2=4q0NtxMU^" g 1jqF0gd,c5qn#S2s AqyD?$uȿ%Rl$)BJrfB)J98%*J2xf9b)zRne(KiJTҕ/a)XҲh%.uIJ^ҕt0YLc~8ϑY_>sќe y\?d&7J|4d8Fr鼊/iwdNRԳB蓟Mx(|)ϛ)BAЄ¡e(; QHt5 zЍMKC&DK hTRԥ)iDJSͩEѕnԧp*X5'aT-p^eiK} vtr.20q\πU7ZILYGM8,bA^l[1W:te]W5 , B],cXD.ހ{g*أV15-j;yk\2WU;wfY6 N KӞ6f,־Ve.fܶmn[ ]!xEO}r\2<׶muufo %2';#6},%R-p5#a6MV>|w$6J,d48aX2ql|c<x!Y7EN1%OTYF|x3=A&+|dxdvef),3vW0o5<) AS߼hFv*'$[zan2rpL ԕtF upA&$aS$e3D20 `2#Jsk;FW`{/16]e7M k_k6e=Zoָ붨{mzɛ^oQGpo7J`7m͒Y menS/7]뜛V7"s$V,~f[rFGnn-Lx;swWzopݠ^ ^|?`prlwB „Sa){^$_);?O>3Dwi#`Ñ.VїG=iTOxӎ%?ɓ򵯼0ˣnݿ[}=>z7Qx ?X 49u^5q]!D ˑUm_5=^~~_LM@ IEt x} z獠  6 > ~`TJ08נ|!JkȌ!Baa~h ԡ^ Lk W]W5)Zab,iapx!~aa!aV]R a["- 6\!R!"#6b $Ž$r%V%6D&!''~"(Ɓ("~С)*+6+6D,,֢-"2#R//VE0!oaZh&c2B22c3bD4"5*v6Z!b!"Sģ/W!#U%#&&<6# #>NcUZ\#Cd#ClucA"8"BCޑCvDDr0fX#Fj2rGF#H™H&I.I¢@"ALJahTNKRCn!GrHG$<&<њ_F[bQ"t% R*J~SVITRe2URV"$brGxeHjxԸFIZZjR[% ev]e n^zc"L`JeURaaŽb.fte}ƄetMfGU]&d^l#u%?Zhjb]N]fSAfvflVml&&c^crddeo捘Z[[g&Zh:gii$Ϡ_Fub'hLFLGwbw.BxgyXPzH{{F||"'L~2'i"^dt(ujQ_KЂGFm>fnFn'gņjE~h}hgh6sg:`J dMPq$|)b)F|<MUJ|UIiWP6yF%~a)6 [ _/} 4j:X4K`adju*F| "闊)隺 țRIΩ֩62)* j$l,G4* <*褤bjj*p~Iꖌj&V*꫞"XjS i9_) k,DƲ :jAXV2K^ǥ^G.ӷkK>Ź.GҺ2j)jȭipk^^ l 0į^†®RFNkJ쵦@>Ҷjlr췂**=ɺRʺ2ܻQΫNl/]6-$lI -Nʅ^JkPkFRn,it(""גDخ؊Aˢ-ʫkm-Lƭ{̭+,[$*-j+r+x6n@.N;iWQ iۢSg./ k%N"j୙*~L T\"Xvjl3EiQa[ړ+]+=g_0>j6;j@n֎+/g* p$0-p[{/Q_\0wVJ}00 _S SmWЯ ojY$LfT ۰j&r6[a*A_(_[pEWcXEC/v, fɬAv0 0o.݀ ()p* Uw!@"*lpp[#XzFj#n(#( )ׁ)UC@qsr(ٲ^;60.7[r0n131#(3/33$G3~L]&s D@!',"*H(\8!6"JC"jG8 CZIRɓ=R,eIbIӄM7nsϟ @菣H(]TP .\xJ h:א!+K hBX˶ep%I2ݻfi6O-GaX|mɘ1 2Ls@1oRpN;"J 5BE76,w\eI {OO5*G U[zjNimk:ܽ]#b9CYvY1_g ᄢ@cMl`ۈ$[Aěopur-A'MUwOmGT5%`m'o6zߔfͷɥW97ؑ} tdYf, Vpm!b"[d0".âR^~y0dե⭉!ߑUV-P1YR`x%x_Y~'f7`gҚ Vxa}H,&hZ>giY¶類~+*BqVN`e"}[◟t خ El s29-Zh됊X8K #n:mydjo.ney]"L-0hx`˴jl,i(-]2$ܚV4-' )J7s; ,s<?OI!VwT^I4}vyH|K*ЄX d+]b{i-[QlFp,g~I̹K~.Nrd=Αw⁄/>OUw\o7ȹ!HJ?[@5V^&Y(.Yr'ݕMtPv«xA0[&՜UOkO{י-|I(g}a~ Dgy.C`7i-2\+ M6f! ơH-<1Hj7ʓ۾䌰\}Px40{܃|R % !g+=sE4@25Jcb@(@(bR`rl_@y5F2+7>}\.{z*=ЏaEtHj7 Y``OSPQTVTumVU0|3pꖳ:)GX+nAV]:>m6+ؿҊBbXȆ$0BhֱgE;ڦ́jW?mvh[[-oߚeYrZz5Uk"&Vl.]~஺{;^leVzkRe,/@ڈxq|t q Pnk<|ny(.*X,uuYfb׋y9;e3Cw=xL O/ [ǯ;P||yy< `jȘJuHmvD}3=%=0`'&p4YГ>y_54rU!wBBP4"BRTGVN哢A'O3!iؕ=R$V3q'&Vu[Jёs90XfJђP#4i&<= ^%CYVŘ3є"SYnqEZYo B!d)>fIhYr)tϕX3^Qy饘XTw䒢9)/9p3>Ś zPRTVVYIɜ 0egiɛ?7iQG1QByaoQۉɜ z"Y㈞}Ǟ,䞞r@5yZ79IU)y*9@ "ɠ:M™n\zm99i*j,5Uʝ]Py9t;]$&BFz4pKMjOQaUjdJe Zz*I:SUqeJ J'YyyZӗAf*I鑈:zZIzɤ餟 SZ(WɪL`:bZ fJh:!ĩɡY"$&(z'{Z}%)C@Jc髉'$7:MA}VYYБYJjUHQ!Ⱡ Xi*(ZzKk;"VTptg*Vdֲq"*LaE5k8ˮ <rGI{ac*;Tjګl;J{٬hTF Dj,l~XsKU4Qqz,6K}˩ bTnٯ+M+P;U˫ , Ú\^̚[ ?{Q;Ckat[vÛ;!Ǜ71ˉyGSb-]ҪK뽓 Ki WAﻹ +[NnK~(;!雲+3S!:+5 ,[Z G@V3[[ {d긽%zik{m{qKhMQS,!U9 ;LH^Z{˥+Kj [{šzG|~P ,XBp^ȴb zgԗ("L$D!aV5avʌB?nTYBb!x!sI|#Lp^3qU y,KqT*,M%}#"+u&)ղ4AfdMh%aTP͎˯4\ᬋ@΂a9 >2a0FP| lUJ5]ZVВA7tRcy4<=-0vPru=,a.͡9(ӁBZ`X |PzgsJt2 RxLiS-_b /dmfh=vMV3)׌ʼֻѢ|ב!M؅U] (ت:<mOW댿ԢOUWF:] m }e}]MCZ)ͫwyA^ }ԭ}݉u.f1J=ԛm],MVڤڦH>n~kĝ]6JN,+-#mM]ל79--N=]?2.tN]}A[}>E]pY5U~t] ].$_n!.UfNQ%"#|Wa:=Ew1>Ds,2r&>=^MrЌy>P۷m^N>ƭ\I#᳔=o=~\P#N".[q޹n.ydž9ž}]Y>N֎MO.!=Žd(* cN!U~M^ ;X~ 4 O/^m>n?u՞䴽Q>#%= M-(/135?8<@/BOD_K?46S0C^Ξ^] H ھi.0xO?IfU\u&gB"7GִŸ?W}eAƞz;^ߍN. lnOoo[!B!XP`.$!L(5nG)RPr)Uay%!dYM#"3OAZ,I9*5tNS>L=0UV5lëC+a [ L0 Ik E\K/qbQ;v&,Q}00Q2Vjl93֢%=T},˗fSB^o9 /[/d谡N#'zn'HNqH2N)3(v/]`0a=*ʹ,:0|P9B:%cE#l&ڬjtNB-8aͯ7HSr;)t,(8.!:rK%o;O<ȋ<л8t3(&O P?-b"H0h0 pº,PCB DDL!EXEnQOH#|j5ʳW sL2M5-`_.3*-qR%Q @tL35S=qcƆNE)U{qE]*|HdV7[oU]{}bc1bŋԴc/kV^EBEϽ0 %w-UI%{#׿}KTO[uօe%Έz6^>FVdƆdh6ejcڗ@yi͹ѝ3*vNᎶP݌TSqGX%8Hfa=k?J%56m6/ّY n;%뾺m9o[57. y4qwɐSr~Iw[  NzuG?؉F >Hv =j?$Gxģl6-o)3~= Mzc_2R6 &0 |1?hg?Ofh e*~ Gf* "A\`ԅ ..޻4*!K}jCXbհV1lS,_,5mv87"oH'/  fb]49//bK`F߀j Hku -  ' h>z5n^Aǻ3lw w+Gf5 A߻% %u?m^7xc>ɼ<+õ; ?& cQc{ڃ3,L?=?s4;;#;0,@;w@/\TL,XAԴp2|9H I8R7$"C9tc4*1<  7h #B0B)񏞳 8DN4%;6a/ER*qCTT 234$-6 TCC2Oú? ,ŠAԃB4DD*EF<G/H IJ<K N$^|UR,㨫rd+sE:ŗQCu6|#]t^;]CE@<ƫHe<ĠJYD:ij,-mDHFpDONJa+8Q4Er$IvCC3GB "|C]Ǻǧ`a,ȧ8ȋJH D0 hyhHتHlHmȢȺÊG^QI^YǕlŖ|EŘGZGU\IE<ƥ F4ƬLJflHg|HhF|=*FK H)Kuˠ"K8KLsǗ<4˙K\<}IʺKK$]̣TFdqȇ#`qǀ̃Lnˤ̌XNLiMx$Mylzl<6(P8l)K"-R#2LR| = U()P+О Q#Ѡ4QMӹAԠ3m4(TOeatS8Q R%=ʹ>-)\5Άx9TQTѬpTلT1]Q25!L"x O U9 :RdVmǵ|G?A[5I*+,-}.T,JeQd5SeES}VhPKj k59Q Rnևy c)X7L?؋! I[u)dP3/r& ZBؚJ@X12)X Y,Y9YEuZY*Y=UmZ6#uW@ږQ`ZԃI=N0YC٫֟Z2Y͊`ڳm\pAuk[ tZB % -ʫۆS5\E\U\Ǎ]ܢE>=d-2=ܩ۪MYѵ u %=\PYa\ٽޡ]ۋ]R]] ]-u]YjY}…ޫ ^*KR֬ mό*h`H#X_m_Z_Ef=YZ, ]&^5^8p \9zރu ~`"&ݖAANn<v` aV#">9$2R 'V ( טc!^b%dB 5c4^7QcI6"֢(dQ=.޾p'B&d%2A_F~e/q6%ncd d9d;d<e ~_A>eHU^]X~7 `y#K#U֚B4I pF䫞)P1sfh~+i7@k=fogstux^hy>Pf| n6o&>!)h;N`hS<烸gf=Dht>u&iٕg牎ivB~_%iBjNKn~F꫐~gyjnkj6j⇔B>No/o>n*VoG6^p D p bqoܲtpJ~q,Kqqqq w!'r'7[#Hr'rd*_r(N-/Vrrds+r,7s875ss?s@tAtB/tC?tDOtE_tFotGtHtItJtKtLtMtNtOtPuQuR/uS?uTOuU_uVouWuXuYuZu[u\u]u^u_u`vavb/vc?vdOve_vfovgvhvivjvkvlvmvn'rw*v'vwqrr s?wOXwvwqwxwww{p|}W~WwYw{}4xwF8awx/oxXso\4,oݗǔcӏG`GO 4zGaayXApzV1 ({'-z_z3zzw_{ @z'?{TtC'q9|{ſ^Ư{C\p9w5:O>Gj+θÏˎaP9晧ʪ{}4znCαЁ+h`& [y/8P/ar8cVvCGAh"1I r0Z U/@3_wF4^Vk@ M"CB)#qyH)D-DT!IhH,!a#H `%@Jj$JQ.LGVQ\Y桖 N)N0d/`3 4&!:ɔ8*s>h&iTdmқ8IqL:[NSH'2h{-Rԣ 'zn ](a `J&Ɗ\ =AiO|ࣨH@R۽BI@Yt ceF8]g.IxA(Qj%u3aQ*SuJUzrO\d$DO3Ze\[G55OJ 0W,B,aEbرgjG!J,SZ򉮚 ^3PqMhRZӢ6 Y-bɪX26cmV閷G]7KUq?=NH 8-q$&J< ^z M`HO2Uxpb0 3̐)Hpj, @c{[֭L}Br8 +/40K`6X(B2a _TN~2ː#!"@Rs1}ccXH6Bf'xȒ),M~27eȅV1z|-wR_|a<X5Iռ6qvʟ,d; z&}E:9 !cAS 月=ЮA岊ݧ00Kz̕˥gr\ӞEMBRZq0j NKHWN4OJ\&_H6W|B;Yz';\*wݏ9& mh楙5f1`ra p晱Mmyă.兔>[t7غ:|[ iĭFڮ{fH^*59tBg;=lqsHSa5x=? gT@=gy끮g yT*MqۚvԤ}ߛ>2'r |\I6Jќ-PZtp\TImG!T^eF_$J___@ Om *ρ $(RU ש at0iq 1a(ׁުZ"Aq-D W LtF!Z((MRR]`aOA.!4! IQ@a I!!V!^ihahqa^Rx Zafa!P !! &aH!Ab;Y$RbX" aj"1"b'H()F ** bApⴙJ..Fi "!# #`(O# 3R#\`VMv7v'}ѐx'Vf9tzRn&|^ o} ~~($ % OFE䨎"'RBd(%$Vۨx`nF '{.{!An)6}fh~&(_O狮%*hti?fVޏfE )QLKxgkzݛ&)–F4FLT\)]pӖrL ))eWhiO)Tԩiu:) jj`A.*H:ړN)Bf*tc>rb@!, [@*\ȰÇ#Bŋ3jȱǏ Cn(ɓI\ɲ˗0cBT)L6sɳI>r*ѣH]M4ҦPJtjҪVj+W^K"ŲBϢ]˶۷pʝKݻx"7߿P̧:XxǐWL927C)⢴ %J g>/b˖=ځsC17A|q|(_sÆHzeس_̠{k=Oҫ_ ӧ o- ;8ā|af L!!p@| @;( FpVz1P N 0~!c4ߍ8;"_ Ft|$cmYv(؃C`jOfkKm`[`h Bƭ#1\sD1up99@Iyg{Zg~wrxT.hZAw!X %*qz)1X#<ꫯoWni$bH&'~bYQiX:ږyI[[plcI'wJ٧&(zh-iBx_~㥙`dZpkWk} 2~+2 K[G{b׬JKiZ/p+cY碫nuEb+j / oRji5\ೣ8njƯȱ (vg+"j+'F\@8D.9Ϋ$R|eϬelCֵF#4'lF?]'V=>ΠX|?J߈po37samq۬&N >r'92K9KZ18Opz]ԦJ4nk^'nvW˚wŵNwm9T*u0m KPNMpի.={SQV8jW &0A>I&;%A~BaKK](!P\ .LyaVvIȣ& 31ʃPȄJwV D ! {72H#b8$qLt3ZVBbEU\#((yT#qX&7±0r|dG<1 A΀`ȆHOm%Rυ^&Quְo>6JҔ(CTJAh`"d9&rgBK `|0b" T&3{ q Yլ=2 P7iHQx!Hֹ3g)Ysgy މ&Go"yb7+"#;F8q!E, DŽVƃY乎K~` g*r 2G\s 󵳍!C@ѹ4nr3hzx^ Sr޺wןYxnp gqScyށN%Kkĺ)ZeP\rLrWrŇo] __cd|ƼƦ&nĥR,tvCx zV|\"yB?Z,Kk|\jQSPL(<"|QT1pZ%Ceʥ˞!%ε\ę-wNy {DaX;ȲZ\{ ȴ; ۶ ,,KL|l̼,bƥՕ¡Ѷ͸ u<SˬJvd!KK]$= `+Ѱ̲{h ɷkVtkţ.02fm@(ӂ)˩ JN /NSU}Y:E}Jl!#D%='rmTR׀3M"Z ؂-M+ϼ о,luْbِ ,Po!cifE7]2+0a!tv?3N'*~4-7,:8n!"RohZ0o-{K 2Q2q]Uͦ1zA%s/ ('(L>Tݿc m=gޣXF+߸]ߝ4K 82rEC;2Xaዄ+C޴;^K!/;Gz)+>P-> ٝ mm22p; c]ON扴 tUlWKY( ~seo+j3SZt^w8׭/135._@i"𡈫1~Fnƥ&M[PV5͎X}<\C^`.b"i^;x%xNx~マ,w"ZԞ׎~qQ^NN^^wT~VڣEZcN]n_ac~2ާ/Fj׌  dދ2yonIdgGP0/ NnB*3Tz1.W.w{e.*0h2[~6ㅮY_n#0oKO$Z(J*Og4TnIEj")ꉮޗ=?/>">fL#n/Oo?]|]aoe oLM>k/Rދe6PР V`XCL3lёGqQdÆBT`eELM9o.sOA ZQI-SNu"Z5*Yni IY3f40q厡yrw'O~ %Zx#$OR%p"lyB(b4Tw Zt LhmtuHаB(G@6̌gf&r$hIv ͚~q);SqԪTkgU׍b5l6`|nK3. $Kl1 (6;5Si6T+CDm6-x".! ?,Jn9,Q0ɻI(|1ȯLLS5meM M;!s=1HCDO ] DEiQ&G[|qא(U;S;AI 2%RcS TrUb(a+ؾzkAw  /r]3fd,Zv c6)BLP8(y zhJ(BP Q5bYj`jYJj]v4+.M[socm-( ^^㏑8=`p 7pW|q x#-I_mc<֑KN唣Z#[viYoә'袏3SczӛEJj.ۆ?,7!ݒx>a?4VnѶcgدF|۷"vr5syMA?Y%/xZwnv3V4 Zv:gE!Sk@ѡyGx^Ƣ7U6jcb=!u ȧ?$}|a?b:6b+ %)l#? !($fuxeFX (R! p=2< $/d!DeUl~ J?$nML ,yItNRtcʼn`vZ|m!iT7E oSK7r4GI/CC6*dVHq3Idj4$&IN>U]Emv]U;Εe,+AfB?s]FY`roߛ!Af**x2DJr Ǝh$fg\7-s.c@rQw234eF§DT5fH\CbOKe_ǷvJW`.ndR* Xh.r>lhd2 5U,=KS֙sE4M;ӂ8Gz.} ߧҙC]:fK'εֹUs@vfh'M]Ggx;NaޑM}נxx 6 _tIW"?y췩}sd=[zQ6ҋ6̳C2ߚTU9X.y;9:۸+6Ճs=sp$@sԏl>Cd@6@' >P>s?_AA2 | ҨMq6 dᾕs3@VB>AФ勷Q#h<'\DY* +,- ' !)[|4HZ6̋7$947:;?C>,\E@T _EBMd єJyH10hNΘ 2=)3qs ܂6C탓А8O#& |OHµϞ,Ŀ9M >3Z̑ԙ;|t,판Nt#OD\`OxO P-~aa- aaa v, !"<`$.KSb`ӱ+ b@..V/~ n b]xdH~J7c%f= ]Oӊ)@>eBCD#fdtdH~dIJ7㟊GNO;<>RVTNfdT6Fe3e[Ie/d^_vb`bbƀB Qt > d~狮~Hh}蜨͢h `i)i@i<^&~Zj|~}MZKhN^+j 覶f>pj }K$ܫiDhkkkNj@kV`e6DFflb.̩\ KyQS=? nm(Dl~!ҮVq>B8mVlx n.NnfnD 7<~mnVN8nNoooVoj5-D~n7-pJ wnpm pVKoq qp'IoqOh qxǾq~q%<V(r!;r+/Pe% x() +6_Dr.r7XG8*MOX{s>W8'9:s< =>O@s!;BCWtKaFomG/tOLMWO/uuu&1uV\V~u[mZ_[uu^u`vvb?vdOve_vfovgvhvivjvkvlvmvnvovpwqwr/ws?wtOwu_wvowwwxwywzw{w|w}w~wwxx/x?xOx_xoxxxxxxxxxxyy/y?y Lg_yq""].& 嗇纚''G"o~zqyw5zO{z?[g{s>{!0G{l{ {G h/9y|Gn'|{ ˿||{Ȃ,[3v (|s_wL6`٧}|WǷG'ߟ'5Ly}~ ?呑~ՠް~~kBK~ߔ}?}O!>|,  Jh(!'Rh"ƌ7r#Ȑ"G,i$ʔ)̘f}D›l+OzR$y?H}s;@Co|3[yO}x\ ttA$nX0x,1@ ;\'gCCt[>056j'` E΀D``D̐x!q>u"H2$4,8"'5&6W0c˄19K{F1F D5v#> J3 lS%F"z u 3tq ɳE.ΐ$eV8Cќm?%E iEIS)Yl%KM򲗣6PnŐve.ke6Cq6%2MjdyqGUӗgA:AYLQPn<@s!(d9g2jy&E?{s|Z.}iF7дj ę)II&)MF^r07x*TAuR0K*V{)6,)Jzұ--WfK_Ҙ>m5uNs,)Py`RHR]Ԧ@VK2̫J֓uLZ8fxj{W_Vejas$Ze{Ț nlY0ULgBs m]G0Py׿ޤAl3[v =cWH!X?-p+KNkEn[ָnuKD9MHLyϛ!o\ 4},׳7G`^20`7@LƪWsoA2#7ʗ A9kb T, w`x=ث歏Mo˞ B ;lY9I/sk'R\7fF˘rg,31)ю1Zf>iVC،ᱼ9+q֑;xm2t?sʂ. - _Xh1Ӗx3)\Tj:ːWdβSrsOT^nu rX7Yrm0ѐ)H)l|zy3iAD٣~6|>ovd7'zK]_u{g|?%,_A ܌$[l=TATkQ'R멝ܴIPM ޕW Ŭ fj@1I0 6.OMpDl! ] Nܩ`@. ^m  n!``a!k$$a:ANaߌpa E۝!a!* 0#uAE]$V 33f%FcCNCDDzCfE(Z`dIRFaH#,:c- K=O>Le?.a#%dLOF%]za4J&EQ 5`Z22e:S`Tsa<&d` ;^ )#/‰/6zY$MMJZN-K\*e]]e[__d&aNa"$bf5&^r&'dFdnAejent&c|&?fiFi2z\"$.kf%PPV#DD֦] ƀj ǛFhĜvEfŝ)Bi)ȻSU4*f*%V6Prj~Fi*B]JΪF~.MiڎT!DFCjpjjM kL+|(2(H"*.D6BjZBjϸA~"fGv꺆~RA+h0Fꫬ++^͚,p 2M8,Lti+b,F+XHfNy,l,7lZkfG+ y޲@"͆AljkĬ&AlQ0o8- kX RlZ,lj졊mǂ(ڊ"~-v-̂mߖ ޫ A:NmҺi>mNTn^]d.h@咈j~~-I+MDPHRήwڑ~@:}@n/GooNA$6jOj(afpB@~/Z AמhZHȨSE$Mknk}0|#]zo)f>ëኀl^BchX\kpp C6+nQI @!@e^i]ﯹ:lbR/@f1lq ;C/ b&&ź0E0GV 31rF!O)q1uF$$p%%/p&7,'_'2fpl*+ӫqȲ0ڲ0RRs11#" x3,U !5n?q )&+&3Z1cqL0*.33):S;*rr q p >S./.@A_A_B1752' ,r~Vt%atYTK]fsJ138t,H!, $@*\ȰÇ#Dŋ3jȱǏ Cn(ɓI\ɲ˗0cBT)L6sɳI>r*ѣH]M4ҦPJtjҪVj+W^K"ŲBϢ]˶۷pʝKݻx ˷_2sla0;Zxcy"Kèe3k1ϸBMi>2iҤaCsûwN\(_ޤH8q]l²eƌW<޽_י_gwƵkרsrm c&&3K%Jbr(V,8iG#FF͊-b%(4_}؂V-dw&ijelf"z[q`\rsE7t?]vq'#A:2gqIzy%է}~z >c!hX e$hߐ+R6j8Wcam9ds1i֚kTZmZƥ!YܘǕi^@7ښlu)^wfgzjeZ Xc郋EلraXzĉ(*8kHKd:lg꫓Fv,nZɬAkVbk xxW.(}W/9WNq^uJ#|u6 ٪JLjCq ZPZWC.{ p2L`KRٜn{w\ E.}N'/~Zu{֛v=a:}00ڼ˨dz?1Ǘխi%=@* N.&ɸ޹`&J_.tDi%舖Q':mZm|dY&;O%:- U×CX0Y s"#팁&FHyoP/p+z^=E+5izS 49 tFtꧨ(u⟤Xw.S\Sh@Q *az$ػTZ }PnD KxGDŽ,4)䛰Z npZ:"C fd4CP:[d!|QqHf:3EUj/G,^W;4&Pl t` Fq/<A1pP~1=nBěi[JՋa"F:$)9f:|&9iOrGL"otJj2~P+/RjC\p@3 ʨ3.\c1vL$s9h3>恞5l"qIL 18)9˹sYt; ?SyJi|oE?OA2k+˄.Ԗ;1r\ ȄnL:B<)=G R3 ND6uQNuSs:JTp8ԢnX]*4qAul*īJh/ZeYy)΢][ ~4̜<镓|%_lݜaktp 6|'&zFhI5KI~֟UeՂu8`V.4kmos[Um6'Xm!(^6fYQFVxDwcDRN5W-VkpםwH9x5~Hb0@LjscDtӱ1J!P)zɔLwGXLji(;e W8.&(SQ(E5V{ ؅e8ZRkhhȏ5' Nuw(y(wDZ ˸`Rg!Y>@Xee #iV%)'Y'b,鍙05Q7"F@#BDFyHYxJLWO %aPĨ+ycx3gjjPYqKsik_)-YI-l=TiqR{`1Wi\fc"5<ɝp<p=,"wL3Rs^k!Ҟ,B pgW9.`rc zu1's0 ZU&AiΩI$)Y1fM9c|'yP $%!UdajW$Wc# |b5lVr רgNI71X *k"$[L˹Y-:/: 1z0l6ZQ8ڗ У1@J$Bʞឦx#r*E fT(V!+Zsg z1!tD6m|bpPZȘEȹkd:NF 9~aŻ&+ű1xW+,,Yʧ,%Y[q,Ƕø;zÀNRJĵkΜOJl,LTVӓ6{Ua)25Jp38J={a9XԅA7V̒ A2'6h_$+-s˃/2]rӺ<- ԄFT0EeLN͊P-HpX%W]|\ ^͌`mb;d bf%hֳ߲Ң`u-\M{>i|:~-ԀvӅт|m`7Ԑ OM RUVM wL0a3#>]&T۵ۉy}Eέӝ1؄]&-iԌ ݦ-ԝRmݝSڣ6L >h۹1ۼUz ܱN^ܱx>I\Gm=H~^Vݞ N-*xQR'r&*Ҳ 6i7Ό9;>B أRa89`K~}U.Z~@mެ(!@^br8 6ےS*:~9`1K7̝{J$HŁՔޠ#>֭q-c@Dr[ny׿aD>?HJN_qhġ앞3 ,$!^&lɩrIh߼}%뺎x 羬^.BX!N"YFCi,!O3!@~nƍfT)-׵Q2_4_^6F W" ?^RqIQheRT_^/$Cҷ`2qΑVw& XN^g |jNo5+>Y/ [!.^-٘o՞mQoo积ҟoݮŔ>+u$!h!L  P ,dC9$Nx"4б & UIH  EbdbɦG!A{>0X<ɨN8;Qʤ*(Q=Y*: WZaȬY+/aӖH~JJ_\iPÇ /&@cȑ%O. յ4o6L"]Ǎ L/b JS#8u,$* TۛJ T(Hsd:ԩU^=ukWt[Aw.]x5Wj?p0i3ΌLD4[HZC06m2Hx3 %†si.ɩ,)n )JoC#/rI(//)3 K0OB$B.)C0nIYq6kJN7c\< FdnxDG<(*D(%k)CuȪ,4K1# DLMtC< D40$>WtP)ƑfF &KtǺb>'IUwPMr,dIy'ge$Q U_ a'E^wRr6$hj6[Gѩ J-tr>v[v?wŘ y7{Gz5h6+u"qk߂HđK,V\dCsىX=w,xcB;N2#ۑÅdNԐgY3yqrzyhw`7a3dYAN[lɮS?[CTť>{w["m9gv|w/iEةj7akA~ U?|^o(ٿK|Up<8iqHzֳHMzc 8C{0EE*Tw1`u]ō~jat?SHU?#@xD$&qy;ڍF԰W 8TBu.#hqF4CkL ׇ1B:h0wzaٵÿ0+\DD&r t^=zŸxsZ41-EAhLQ:bxG#Qe|c-UBX2@&|9HQ> f13)EBF+@3D9S Ra"b@2ӗmLЌΤ4 ${],yrj$򑏙 M!*he\cK> %zlS,&19)eԄI4X!X!vS1=Z80΁3DNxoL=},K&|%MhSPr'0q"LTԢAfF7*Mvb )6 *MQiRS4kG9N)]|jρD[r* Lujc)˨6UjD:Ѭj*yD -5*L)N_~t}]CӼRL&_[SBtm@k4P2ֱ T UBZ͞PPB+ZҚդ@LjXV-:ÊJnrHyPAedXDANK` 1Wͅs%;Tvukv;kxIjUԢ]iq[ [r\msm_z&>jL`x ^p2)c ]&j+ū pC:8x Yk]JhDƕmZMY1og,ͱ/gaddDBS2e,QSBy5jc(r'K9bcfk.W3fy0m}g{}W'%ЉNfbٲ+XzW^i ./ZfƝ9ff JqsO\zϼsA _6-&;|w2|4jsڦĶDRj$&3=r봭TM)o;";M\7)v4m zё qJ|`zM>4'A #uPF3!PMncW哪Р \2!j.ݛ <:6}mK#<nt/xyJtSG]ܪڮ̬oq:A9vgs[￟\; R]Elq~Bq|].| 0*ӄ&_[ڶ]! i"d &[E &DzB<zb>-=}O`$\[>c4Èrs=>껾c*ӧ!?þۿ#pX?D(Ƚ+E;,I@ G@R@S?4k+A'+?P8A t $>FB,D…@*A,$A1.DXDbAChDCO5*AA>: ;܌&bK);̔B-.f}h0B3c7nW4D5^?17cWcvccBN@^B^N]AFcF?vdPdIdtdLdDdOeK&eT>CSTneW~eXeYeZe[e\e]e^e_e`fafb.fc>fdNfe^ffnfg~fhfifjfkflfmfnfofpgqgr.gs>gtNgu^gvngw~gxgygzg{g|g}g~gghh.h>hNh^hnhϊhhhhhhhhiih1@@fni~iiii蓖^ijj.ji0j~jjFPfjjjj&)SiDkNk^khk:(k>붮kkvj7Qkl.lbk+C6l~llNl7n`9ʮ3[o_lkZl -EVam_pm[Tmm,nZ"5n@Vna>pn螔p$-n>֎f6Efvon ok;S2Q\^XoH>q2?M@apppp+ppw[pէqơ?=(7zp _qEm[ q@qD5 Yw8h#$%&&T'OYn)*+ rrs42OMBsVsepC19sHrq[r mmss)s2t 'C7ϱcS=σ/o sGtHt2qtkt&CnPI JrKw(v7ۖwgn?3Sw{7rSwbwgm?S6㘑pZVA7.F;# 2xgxy(yYNyЅ ){`bo& W w GvO$/v>z'zqn,~zԪ /v=zyoyazVvcW{aggbLuGzwx ''|R2|gjO`/zk{3||۟zDG|W|$*k{oI_o|}}>H0O$9G}:@A?ܾc~wzڏ|42hD#q1V* „ 2l!Ĉ'Rl"?7"Ȑ"G,IQ/*hY #2k:wР*z1J1m)ԨOaR%*֝;b볰b4h,ڴ&QTF;JkW-޼z%LGhְQĊ} ʹ^\ۤMJ]Т^ժt`ņ%+`g6s\}ٽK7p}W t2̚+N̸ǐwS`rf+JШM@+֮ao:mCҽT=F"U鱠# ~}tv7 !uWLu8"%h)\b8#$|9RFdE_8(Ua : iNX!f!(dH%c"3H$f<IYyf$F%F1$\I)UJu%Y! ~)bbiژ&y)czuIg}i~B%F2i7%+>eT]NZG;u.9 "uzԴݖO ⪕jT(J:ijlbԠ~WV_vTڹu̬u {m(Q% +wU}y(9Ak%Uu٥`]m}t{9 2s>L2fNVg xK-@mN' W)턁R/}b_GA~_G@ \ F\N. nB{ԉBo 6m ͷ1%])тX!^`# <%Mx#,*":\K_a㤽)f~=$y"i0! Tv,MPj&3ɀMrRlbL%CIxxתhE葄{e(1I+F6"̍r%GF||LHK__3HG>R رsLbryL*)RÔQ:ә-4,8KrX5*:%0(ajQt<&ٿe֠LO46MFVӚO䤴 #nvb%%ǩR3TTJo3g-\%e?p-J EAAcC|aҨ鮺i9mKRM41$ŅIOujd`ORZTą>+On3GyS5 _ ?gaGT^cF9XpAc"9!ֱ,`f_@0uaSu9R!ˤ9QU2#:Js*[g9' ΐw,9J3&αC]&io\VW8dx>ɇy(W]NU}BF^/XlJ;z:8MmNH ؆8/qwJ̇&=Y1~_x0,JO<Ҭfio zDdXf'6E&F4KFgY/ ` 4x1 Mo]4gJ_vގSʭyҖ%fb8akFѿrJNCq,C}>s$&e +b%mtbQP$,v! x!SXlz P9L po]tP3 E^س /Ko}QBr3{}ā"qGQ\'eϘ{f~HWg"81vsg\9zƍ]lQ[% ҙq3W9xyx"S]9vG~<*Ļ)]́)Y+ )v=c#hӅ|Cp3q4oW}Ho;,CrEyKݶM;垟&  -_9۰[4}qD^Ĝd)UE3Lў=SXd!l]tI}]D`Nԟ t 1]yeya^%_jyMhu`q\^աLݖ|FE+ݠ` `]ߙ^S 5`=YY`a``mXAK -v օ֍VDTPpT|M"faba! vV|!"#؛Pa5&V`]ⱉ'*E('q)a~a ,mɠEPF.b"0 1"N0s3c4Nc5`#h^7&8&E99nR:6c ƣa,>?24Vu@A: c#1$.$!.WDN.K4!55^dOd#%'aa&~&#$( $`)D*IQdd<E.>OP%QT 2N8$c]DerVu]GvmI߁w׃dyz;(LD!|!~O鷟{]`^5܁ceтi5x! ;aYS9@-85vji5*==LE)UuYnY_aae6˃k zfRdLw.\2:ڽ *ъh1h#F:)p駅$j"( @ꡬ7 [+j뎰Fa;z 2k@BkWVK]i؂)ĶbN8k 3{w`2ʥ;(;{ \v#,,{5җ}J_JqȚqaɉc̫@,57\ Ak]aG#ҩ0mo{t'XW'u5`;-gǕv/vp(wlt)#wm @/&c#vlɪgY B=XFK1= ]Zhg-KĈ+X~JT ,m|(v -qwRT8 3eo^HStOG2IrɄӤ=~_t8kLS( 0;͎T %x+|Ukt *Q g72oSXY,oۛl"X0p2 YzH-2B@ JЃNc JnEY ]Gifr'xIO ~& 97mJobݴ^"ũ=!{=$C^r24jZ&&qu1NYZp*X&VUgMD9Xы 1HD91T̫OZR~T5vMA6 SȜp褕:oNAHg[:TőQ"T>0i$QŤb:9`.T+ēȫ9#F4ѫ qHdQl22 ue J9}QFh񘺣Z @2OukzW{L9B8ɚ,Le OV-Ґ.r~-/}>`-aNMj _i"' Œ°a?g-+INC6e+\2)dpx\*\[2v=Z?˹uC'z~ zgzPqo[?S#KzPIs)2F{+v}Aqm~s(w<ˏH.᛻D@ٹzs)~yhLקY~*OIpWw{k{Mv,GfwgAlS!rʧwwr*g`6D%.70[.)T&y@tErtgWd r;6O7ە$prC_!ta~PVk@kDwrgtwyǗ|$g~w X"}.Vq} Ă-/X135hy旅qă5WGt.E#N8p4fY[\(^h `|(l'Çlxk|xm-7wbcw%*w҂%hN'iX\~M%.XSzLȉo P8RH1#Ba2օl|X|ulw7ryWr{|p F` Wpɇ%%n486Uv_2ȍMJFBUщȀpy ؊dXeiK #!Fiqi؇ؑDBwD փ>AN'H6eȓYBIZ GIalMaHn؋ )u8bYxUGǑl n9@'C&; vYixid{yD8&3y57If혘>@٘'ĀkFyhDG%4Lf^{R>9PZP(0bt.!T ^B :A=d%Vp5] S"RG:o/4W?04}z%Ty m @OPє6iɟ)`>=AUuڧЪHl*$ktߡu10)gh2eI3{ Į;ˤ=+ q4:OK R2T˦WCJX*ڵjNJQuS¡}%[e"J\rJ1kD41)%X]>z:yJ q{gzi: \ӫ*+'jK2ۡbeSYۨt!9 ^;z+)ͫ+kDž߻a䋫 {۰r[˺g+NU 1GX(zFQ&< =k3ħo eM|k{ >{ӽK VJ%\{S ^W˾z\M^4ۗ8 *FĎ2*cFH @JLZ , |(1W YЦȸb* /f>kr ٓ]4Tٖٚ)(M_P!R=]V;Z`ͲQ۷>ݓj-i׽=ĭ/= ɝ˭a =]`pPc+řޑMa٭`] ߥ-]}+^&p%n^mMm -½ma;Jq=a#!n &&.*m3.5MAGC>,E^GRJNmO/*UnWz&|!_=] "Ώws&ޥo.GCW N4YdQx葓襱Dh L. ܑ`>(kc3M1$n[4޶n4/C0\NȞ`xK$( A "C%6LH*3)!#4Qr\)̥K^&Τ `aL~9!95P,I-eԴgQM5VϝnՈѤHC:"Kl\Ԯ-֖#q嚥KlZgyw#/%hHOvd$\+ƾ`4 ;)#wVVZOUk\kد̦]{vl܊ 6!Vx[:J/afNX__EJӨW5vV] ?klٳ)E+k[2k?/rl C,1LM-Ȧ̽_*3.@,&4PS-6ZcEx+(Eߪ!/r<ĸU$c֫J_lh(J_ ,@r0zBLx챨Vt3NS䌼#AK ePB 1Ut0e*), J4:`SNkI:H QrTi$TT#QG}r9!#K"1,+ybK.$+3%IU6lΑS4=?DGPB47E}Zp!RҮ( RLg!NEPou!&w4 G2HZA_&u _rbb4pfYa}h6ktF[n=vŽAR]uu]*<<.~&`qU/ aXU8!]öAeEHM8Ue=]!fyQFcxtn{5%hPEuzr+g-5DŽ#Yxd:?J7={첿:;mͷ5SN<>>~l?k܄t_H^z_r?j+=HmK]uօx9SS2.dnWݔno;Zg%p{P( n91ܻw˩K.(FPj|&BЩфhވ8fiucR2*~d }18$-XE{#"iIƥKFդIpҋ e77ngQo^$=|Tߏ- Imml+,] Z 6ݚBqAp U~pg1?U/>[atMvG\⇜X#eGb8/ JhiܠGo* q~.hȁϑ :4A( ܹ2I*Ŷ=m{ f˝V&L@yɻAAVukֺlYqhc|0El3aN)2,hJ!5ww9OEƽRicTȻMݒNFU^˯BɈG,xВ@?lhFI6ږ!-nMu,ZW==rs;2!߅ܭ@w:?tRpʀ/:_1RɹH:~ې|A^)cW |y[=yNEo=F {|ɞ?n } _5|巈ϋdž| cڍ<˻>ͣ3!>{7.+S{ @@\=+(?(8sSJ-; .Q8c$:9<뿕s.|TS5cu@@F$?RC?a;8ٻ;s< UR:UMUUmUbWMXA+[ZU[5\]=Q*R`bVCA,E1nװVKͤpr CWRQeWBծdW9sSz]@I[VD9׭ O_.VyeXuc-crLX$ςaNa^ana~aaaaaaaaa b!b".b#>b$Nb%^b&nb'~b(b)b*b+b,b-b.b/b0c1c2.c3>c4Nc5^c6nc7~c8c9c:c;cc?c@dAdB.dC>dDNd%FFEEFdFG~`IdJfQ>cMVNފO9e)):e=BOV~!WX>YT֊עm_eU a.bcXVefghi?^zmfILfpf؅l&rfstuvwxNy&օh)vgmfsdtgI !gfFsRFVUhapbfɈ^hI<(`E5i@鑦=[`&J陮irijίi鑞ipAiTiFjVsdjrjGє2ii.ip £fkv#2ky?Qk'3 F3HFE $񕴶&2klH()yjIlFYLMƶ6ݐlʖ&̞͖&؍ю&ӖԶY`mp[1{mʉmEm(Am0mqE ޞ N/֞d]"{ Wn>)( j%3nQ^:nօ힊vf++^Le(ao@roINYoo% p'p2FHVnSpӡpd o nod-oF25p ?g_q.(o !dBAo_&'r_ y)( ǾrIrd-*H4/Lfts%_&9/ ( ;?e_z]w|_ЦE#w_<=HvIw-wysW u_whdxzw|w}@~~UJZd ZqOwvw6w?0@y)ޗg?ׄqzUyoЏyGz/n/Z$sz7zqyW)aOzz/{ OT îb(LOtOxg7ΰϡw֭odD`_6|"WͰwD!z# y˛`ρ`ߪO1 ܏ 6 }Rp4PdX5:_]鷍ˠB)h„2aPh p#a"G*i2ʔ&V$e2ѬĈ v'РB-j(ҤJ70]iRFը֬7挨+ذbǒ5*ଘj>mr_Tf@$TpV^hqՌ=8ɒ)U\Iץ̙5igϲG' lRm#1Zz.m6g]-\q/7o| x+†(ccǏE<9Oe˗3̙vW;so/ԫW [6]~woi[ wqv!^5\` 6u%v1w}^xđWy藓z W*-b8#2r#hs#|9-EY I 8ePQ( $YjP\O@()ei)Iϸ3qiygy).}#:`z&* 8F裏X(qhiMG{QZye޸#~BYd,zk0^0vdkfi`l&r-wg|lܡ2(J,k#^딢J*lFL J#Yz ?9k ɲ ?쳯Jk'٧.:fm,v Ʒȸ{f.2쟼/[f-t:ηNjY&0 [冿p\sè0"bD̕zZ&'BQtR$.+bx#q}b,2<%JbUG e4Z }RE y1#'Ij!#0YMre cfWjhRvE _ʩqN>iOr/W̮]r1EL$4j6fM~sGYs*[T9egqޔGR}S'/{IXܜswiC!jIŨ65 oƣQV*^ s2i2o!M!Whɪ]IMj쥜cFQMfD#aXF)6jc#T#i5BL`o> Qʈq`fdלd5jȾ ֏;kbꖵ)nu\!Wa5K^ʾ2O`0}<] dBQ-RerYevj>0pQ UkU Y2\3;BI\>W6ѝ@lx+كR6#efflQo+x&wp_oi'IIm֔%`Z߻&Wk 1Har%1xM,^W+aL\x,)K_pڷ~e[622VZ.'*Е!tS\Npݗ>ϦK靋zL.޶AqKz(xU򲻹2O<;3yop=*!sw}wN ]o#;6Pp@蕅wR删K>A %+틛]:ح"v:<.f_u#PՇDu{a?sx\;}n{=߰ i@ыŎ9EMUq,mm[yu&_x^ڥ_\\>^%!`)`1 PDF NGnŊHj^e^  ۍ\\``5MAE+ jP <\䉱_.t %^&fNE H =EX H⇜ |$L̼H|E* I*n|GH$%%^G I!~!b]"J9O2\3>%f&jb''S"`)bxbŢ,J -- ../"00JU1j$pc9FcL#}T\#dtc82$R3c::H; (~ )^<#K#cmpxP?cb"Ub/| d* 4t c$$%c0X$`lub~pcxpH$4i^Ĥ̤b$Nw D¡ $%@@j|RS: C6|Oʅ1eE2xFGz&0 Z&ZI[^h>*@]^eG%N~`&aVp &&%TxdR /6B>eFǕl&tf\|fh%WiӾXzXY*YnGZ#Hll^bmf?y"H*weNf ff\ g)0g.8T@'HE&[LuV&#fnjpw'؈hND Led{>{ƧpK0\+\*FEDG geD g\09@(ɐP8YS`Oɝer2vf F LFJ kkg8%ef. j)$逶Lx) 'Cqr2f.liWDARgBBwj )ƴ 楜FŊSˋBkf[(j1Jj,PdͥzJ~&fN'T'e+j'~hwh1է%g{|ii)iװ Ik"+.+}6km=E|N g)G i()Ktkgc|ʦ,U>bp)ƦjjBev輢)T&FRҩ))͞ nK訳*~frL̺ Jnlv~l~$ɎNLʪ,¦%V(^Z'b,-~vN3h*mzdHOZn F,甭--j+e`܊Dݢ-mDdnhT"(n2.mX!,H PÇ#JH1bÊ3jȱ#C CIċX!ʅ_&̈́&n,r倉:yPG&Ej0ɧPJϙdN2 +ٌIʶ۷s:=(Wʯ]Woܬ0ҝ Ä75@"Λ1QÕ)kvyR8ϴ 5j2—}鷥jWn];vݹ>Μ8{~밧 N֥{Y嚅N]s7>\rƤ?v(HɽwWYR}'asƕe8!jgmymhbTw)miy)8]ơT#q"6VxHzb=҅E)SmS⇞r}7v[]\7BgޔeV]'ߗ%iUI'~)6:TLzh6hIDŤTji馛駎*jZ˩*|jˬ>c+ïɰĪS#l6,D+,Vk~iv& Pk.覛ʺ>:c/ lV$/ 7+F܃K}dLƆtQ,)^,0G*sNþZΌ쳱y ,%e@jw`g5NGK&yʄwyϳ8=>Tz_˞@M.k[uV`K>`YGp3p&ЀC`@0b4"8:2PЂ L=B!!esf'3f6İC_J X#^OۃF c%|Icmo]c62Q^׫#H@ ,#@ulQom[PyaZ-oW>MMYдU 9Fw-dqW椯~w^ESͮһާwR\|Y8_Wxv"O?TU.5-j>Ma Cm`C>7aVxjeѵ/ *yG Wx(>]b+D Ǡqc3^z A*Mk,Ko;_(%%aeNa9aZ-ĸ\'\L s d \aYm]nugNW峋McbZNj_xx@~CnxX.sMHyGsxȅ`d2-Zaǁ灦邇E`(hRxM-?7W1/;X~(XxXCWpy3+ [*tl!gHF275d=AJ$q)iF3%kϔЕ2&/xl=(TWU_85~f23.mG(3w2Fnc7BO85)4)B839ғ(9}2MWo 8JinO=4[TmV3`vb9qgY IRa)*i+P!YQ7ɍc),H*Imiow٠5 CH=TP,K١R)$J܉Y*+ڢE7ȏgtvpƚrx=."i肃yؑOxYzY]>h7ڒd+2mz ) ṙ94Niٚ=*[ِJYgiuwJ "I5{7JbI i{#IR*q3,)@  oUDd @ZL 3D9*Z/ͨ,G0-]McH4响&{Zq fcrz2 cN(OSP/=d7C-׳?AG+ ԤQZCگ9"@`!Ӳ K@T5+D*KJգ:R!#DUr()*,۲/˰1e315S$U?&0SrI{L1P &VZ\K^` 3P|p06 wm 9o2$V@Myx )[|\_/;3¸+/ 9Jkҹ#+D{{}q⺰+h]ö+2XqK3U$«ě`ۼЫ)/K*֛>;k,/۽Nj=@z l 﫺0KP$s;  3뻾 x;Y[{  c>|/e,!UH뽘7M T*j*W O̾۲48*fÌKkk/;k+/G(Y+ XEWlhE\ K,B_Cch|ƚ{oq tLѷu27- ČZ~ḆC]R Lǃ‘\4ѷ?d3 3<,ˌvʍU˷#O Qè%NB̻`9̐̈ͧ*,:}PD6͓?y0 ;欩 {SPm8s=FCpan̹칺/=WZN*\ ^aN/g~4f |K!sG=|~10 ~8-.҆3,’X1N~.k.`2jWꦾ @= _ >= x4Uzp6]nn.3)+r.@sn-n1*A,.x0z^0Pr^d@3NRՔS_~\b?jNngٳjmfrtOaan}_.o!,?^^)0a`?/_&HOWV`% "\!$%BPE5n1VD!YIzTD2g1eڥpM9oٳA S%M-ԴS/Ȕ U{m=5WQjKH%@-_ɀ Q7DT5ѷ_p!%S8aĉ㥦M X*=4o.L*EoUq0j:kbpmѰg֬=A&1DH4*Vⵔ/H`+9||Oٵ[R([wT޼T2T=P~?i_&ÌyՈ2"4: k+zM0 j+?*.0{l2PhӬDm+K0-hn8喳H$Qr谳ܮ O<+obZ D.e)h @Tz08POFN԰PE oX,H15O<1vi̍[rI$.UV<&zR"Rk͈J+Oò-O V$j>4qM ?N;$=t-@4 /DCX4FEf'TK M;mFSVCP5ub/2VWmWa]Y V\cl^q/{XC:8)̬Rb:m=7EڋTگƺvO> W/C5,\RI{^K7{M޷~k[ħ>FX;jŌ8ىO^/x؇uAbnMs٢{hesm@'О+hO;u~x^~:öUV ȄkH&u+RK݆cŠ3η.~3֧0UP۶Y ŐD!ǫd0Oӥig uj(a:SU/kI` TDBl\4 (Q$b xx9LŊv %`1B%c2ߐK?Mtq z>CywiAw;ߑWqDAG H{<ÏÌG{¼6>>K8 ??(?9? Qx=r^?g;*۽z) !@l;?r@@@=@@F=([!   \1dAx?˿AÌط\D pF,܌-Yt?293"67$E*j3CW QTE*2STFVW,GEE]kä5D(SEE F c<,G6e,l|FMBNCOCP1m|nFoTp q|AsH@G tvwxyEC DD| }GGI$yQ2LSaxFƅƉHpTō8,GrI Ex y4zǽGaGbG9F8ԈIIHɣ|:_K%H^P_H;˧4ǩC|5|>w,|JFK,KG0ѳILCIK%KKg˽SK"LVi,2$$6m"Qx1ظ َI?>kZ;O,O\F͹M$N$D|WNKNװξAίNȈO_M$ , 41ċOCOdQPPb P0Е6 /PIPP -sO/OƐϿOOK9N=XQt΀Q%->Qν|П4R6СйOOOC"C,鴑RՈ-=S5mSI]6$UR`FpT(/!>?M -- .%4P0QUMI"Ӵ$P"L5F̾7HP8Vd=C %*>iVrD]xU:C4UoPfdP􁪉z5cEcUVmxijN1ӨGVpԯUsV<[hWwWW2Wذ{WlݻnX́W9%ק1WjB׆RdeX} Vzk>\Ց +X=Y:-WUZkCwxšuל}ٞXYZ%@%&MXRcZv1w}Y h{٬ZZZU6$SgU"b Ļ EѽsArZ[̵}OK!ݏաčŵţ\~e\\\5U\\0}}}ԅ܎9ׅJb]ގlx]]O]LE%~;d^^{mN+ @JD^__ۻ_}^}_"_f_="I߻u~`=9._kVqH ` _Վ&Ra5@aP;6YMF.^a'F~`͌4b,Y. n&'v(b5Vx8cb\c;hcy8〢cAc?c@&dFnDNd[^Gd;dȐdO&dMdUO.L~:=TSneUـueZ~eTX~ARe^.`X9^fe`ebN5\Ffe~fhfifjfkflfmfnfofpgqgr.gs>gtNgu^gvngw~gxgygzg{g|g}g~gghh.h>hNh^hnh~hhhhhhhhhii.i>iNi^ini~iiiiiiiiijj.j>jNj^j.R17s۷槆56 sǺڝC1qݭVFɯ.=봾 oo1oz<?c\WpfpԲp>(  pFp qq/q'p G45ض/P`?pިOkYF[ <-3rBr#P% w!r_TFr+r?xrr/?q'YsIm23rb*0osg"u "5@c70}0>?'@).BoNE׮F׮G׮HIJϡKL/Mt`t:qaR;T!5guN"N'SOP/Q/\uDѧ`oaX'4(vnGne'f]g^?_V_k۷ovopߟqr jijoqNw(wv [{ߎ6CZ\a#al?'Gwo"89xwwYxx.yxx}VHNx[hk_Wyn7'fоu'K}z'0'gz(pzW''ρ_G;g4G@0g_h6lK6rSfap};>ҠL}/?. /`vçzM|j#xt|GFbdɯػ||G|c3}a},|2! 4od7ˎ]|a~ 'm/W}7b~$d~|\_a*+H` 70l!'hq"ƌ6Bc"Ð,i$L*WTI%LY2gŋ,'РB-jь|*թcUR y*Hr+XJozy,Tj#5m "TC2U#TUK|dpęIMa'Sl'ߞM ˓hzl:'=d˲7BߜW_C/ѳ%n|O NӼLoX HUl0ؓBW|RCu+CdǾ}mo|vf|!Oic< f)@.%y`>'AP,  n[`¤{VE! WMD3}gDVIn1Fe @6&3OH#+~PH{Y=n"ˆ5Jx eXCya!+G9~fX;C%-LLw":O$eJFT2,,aitA##Ȋ-0HL?L&ݢgzg%iM+27MpL;e%9s%%;y xS5 %3patYA'֨& m]CPEԛF8*RƋ0* 2E#IDJR\s{^z nSe%L7b ar*J3ԴOj>-j(5n|j$ճz$ j;k[&lgOZzPoef\*t &W FZ9q ,1Y*RћZszd  # gYU;ǡ]thw9Z8a\3-| FP*7/#8xnuiLC* `3# nUIrJ#0.u b3Ow[VE`#6n{]̗U/~'_F`eqޙ!`~:3iFaYV8\$~}nb=VX4)VqX\9{= ;| Kl5L%QN>jĕYɖQ//WЦMkf4Imk6n]9eݾ?{s|iu65 xbz"-Ig1&̥te0s;fĤ^)/];YŬ޻uyӮM\X?tf;`Ll%4%f&h}jav@]MvM]b9wMy7=߿70Ov—p=Ƒ#F@-Ѝ;DbS D%79aPNdNNeNֆss|hny./j@{a47w:8݈$X]iC]K`_P>rWޑu팉aC|wsGDl!0xjB^G=>#:oχd~pzX9aLbIP=lO-/y\މ] ^)N<_ГaR]%@}U  FG_ꁅN__b۹_ ڽ [- $4@ I1L TJM^iy\r _#)Eh g!aD ّD ! ՞`DD!%2.J Q`U!]9q!aˈV րMGUP^!᜵q5Q '!`"*"ь6Ea$N$nDàqYX~_Ѽ (B@M(lKHMJEh)Y<#|M=VM4 TDێA-$BC6d@FI?E^BDYa!IH"<IdM"T\qe6P7~###n#T:U;; Z#=ž=O>^>H??I@@^AA*B6$C>dHP$]$F&DGHJHcI$JKZļ#j$19T$LPL%QA=!RZ>?S>JTN|YUb-hVrV@da|TXCF*Y^R \eHH%!e_Xca%3eH#YՀ869G&DfHfP j(&Q6f_XS:gLS3|'xeNf/'zzgfi"ijjBBkbEtee[mndoo ggM"'n)9GvΊ;<b2揨Q Ov*e@wxxdyzg/h%j%k2kJf~lAmI\֥{$ 0M eq!3=3$sDžGԆA :R&-r'M~xTgyy9VD}#~h}$~~<I)F]7p3i*b)k(KKtv*Z4Q i#iz3)֩gvyFiu6 j%I$jl.*[Ҧ<:*L(i+XD]dz6(ޔ"~X(ZxOꂲ..Y*)*jj򪯶@ҩf)/D+|TDaiejFFLU+]+j+p+l+r&NFi%٤`++k GؿS DvŠ®(AJ,g'IDnlvUgf[fy|QƬ,2klY VLYa0 l8c^d)(!ZS`m־Z%y,Kegɚmd :*ܪk͞q&g~FM̛߆]EnU!>~R:m(.lLVnOd&E~kZklm訮xHj^'lŮ̮L-3j^ஊnPo$/-:h:n&F&rVڅ@JC&bk.X̔ 0  0 װ 0Q0011'<7?qu01O1W1RHgk1ow111 R@!,H PÇ#JH1bÊ3jȱ#C CIdŋ?\r%—QT(3cͅܜ(s@˟@]9&јG&-TA&JJդϠX~hXi^>Y֠M~%Q-ɰUʝU'ԃvʵ(û/g޿wzx1ӋNxɍ3'\ϐ)#e i r˺k;ǴV1̏o͛m٪ \[5ފ7vŸ/t.(q_k! i[7帡;4֕swf;%`{݁J]ODl]לz7rUAH݇|^],Ƶl ^LߙUIWމYg~:Bcv"xde5Tچ9bgNffiz`nyiX&|P橧wcaxgj(xjhFV¥@d:æ*BjéS*z« Gjk檪k ư8ıl>D+h`d-u쳭*¹r)z{j )P*! o0(4ܰ !q"Wlgql^ 2:0nB,<0g*3a3" EJ3=ӋO-է{5 d6jmp=7G}yF,cNp3E5.[H69Y\˜ֹy tݾJAԩkMvȝ61 | x27kÀ'=wIYNe=ehG@244 |Q_ȇ?1Ac_؀͏~BoE/Q{`"ȟz`۵9vpX EFА*\fW;P -2<,3!*f7ٱH=&:UN|b*^cg.x񌢪w?X، @Ǽ;8.? $!5AQq\8HLdN70L&Ls!7,eƕJO ",'$Jo?eЄD {TӀEa|k֎PbS34ڠm|6ɿmvSo{'ќשCr#:%tBç0uu'%FУE@"C{U=a M􈣴W=ph g]Z$fJR S}xdEOTTz*z g"сKej ,QOū1eVCЫ\:ցC()wJ6' "'oLk#5ƵJح\]JRvl2^Ug74-jZPj]0lVr1-n)ep7zOKjo2WetEP~vIf VfKK*·.}#;YUm _XX;%0 < Db D:/C 63=nBZ5/┾vd}k8Zwlш밎"yfJ^o{}dEUf,10V2 5Hs:6uε3=W%无k~u]g,c \[:KyɁv2 >M'/6o[噕-%_q+Qk57o WRsu=~x6h)t&a1Z߾qKiHL M>w{l5z=]xeT8O]+᪅x%fWxM Kv FO25-s3/A^szw}۠ԴÂQfw-[5..8~z >L'Z4{na18UH-ԉjd~]@γ%Ⱦஅ:^ 3RλofUW 5dsO >Ws-x7g4:2TFL+4pXa})7 Gz`zz7i4{73VI{{xC|`|$bgv||v_g<}]޷6'WnP~,Ђ!~Sp`?8"rt* y }3A+5MyGK`S)+6C1Wg5h.98.ă>h5(Db&ᧄyN}}3c'@~b~ʓ~xcWuG{c<}XHRv(: %HH8(48D^"8"szkt2F2C<{87؉Cg}:)OQHUȎH)~)'dfiXk؆͘}prTHwsبCōS" 938o稈0Âx{(D-(:h5-79|9}5 3Ӑ}v}㒕㗅x뗑ѸĘ̑.xЈvKL~8Xtq'z(<ٓ(*8 , *yF*J%M)xi]WVÛ ]*_:8'jVrؚԦIjs!@`녝wyʢ|)RA JL:@՚7*Q ,j(0iX8iYk9j`ڪbd*Q:RkSn *jt:v*Jz3KH z֚}RTI0;L :!5+▋ j:[ :JдO^S;1?йk9f0Nr)FU)&>3mW.e[ @:e)cD[qB`8  4OeJ<ݵ;ū\ڻNЁ@@$73S蛾 3k K [h*۴f,XuKc빝[;kΫź%ẗW ū û[K{뗼[{/λ43K} 9b+Cmf咾껾;њ *+u`D:`0c x +@$a*N w ¿Kg¤k,k)C4vPrÆ:ہ=<@`B*D0| JijOQlS,pUZWVeuƈ50˴C-j˺\L)q!`X<cڼͼK\Ԣȟ؅]ʥ\N0L̘(GGҿ5,8?Z,˴<˶.˹j<,4,M3,*< ͋% FVlQ,X<cע"8D0WM7<C8o}·ذؑMԖfټ cf >`ڙ;\6p۠ۻ(R(k*M.=.m-ȭv=.݅m=ڣb=3>Ni0k=n>60}*MnOC62]Aɼ3 bNf.h(CNL۴qnT.2^eu9{}=?ߗ߆^ZnHs lz-1M4,F@E-G]p~Kp.:g2fy{^߻^5MV>麭)+}2P؄.ؙ^^B}Nmލ >N^믑)7+,r.-F2]MI))]lA ?G'8T,#89΢6jGM0/bjF_E +S0,.L@3s).J]+ote5܁n=A/DI/YK?MOmKpVXZ]/_"hCc1Hxnp >:1?wz|/o0jo2Ϙy3lb0 & !JPG $XA,dC%ʠXE)4.ã DPd)ҥ1QDqMK<'hIy.ҔPL>ZUUnMW;P)ѕiT3nF@…T&N! D ;`XE%/W'ܹgВQT3&CUf07r#߄K٣ euUE  ڸBB4'4ȹH|Qd鲥im'g¶ǦBF ֬[qY,YԳ@{-2nK/ 0(0Ď1M4.L30"9 @Cc40lMx #NF\\Ρ`qAt($K`H˻^hм(${/+ 0JϣM@ zS T1A$K /<,1 ]Cɢ2'8$SdE";=Fhmv߂#l #LrI@J즬J,`.d3|>&t3:p|+Z2B2iBD+lQR$3/51SMU|]-ԙF-N5T\u| u sS&uL3izIv&;ϡ0+TVZꬖ–Ok x*tC30tMQvgE_izjy9Mxk@QTI`Q*![9@p톁x8baoR2'66 ؏_YdH"P뽕MjY8`reG6{(ЉZܒ5s3Tv1jy(kqwGU`Մ]|IWӪo:\/pWy)(rk~3y BC)<CFsu=9Q f5KNpul+ !/_[ˀnpv7`@+Fx Pg Rr&MnJ8K2$: tEKTgUM^_bHT`j/BPl+ S.b;) dBEc-Y! D3|J q=R ,yILZRDl0щu!uHS- _, 8Fەq!gD㾾֯+x=r3*:ԣeDGQ鄂! v)Xd ix2B6,S|It`3%nN)ETkK+ay"YvkGF]_"5rgIBE÷j`eM/=hu33@IM pަVj3mh4;&u_K7+}3ASS; `#xj|@hF96]fMkC-2}JEpQjfԬGR&-)Jr&/jBfJ<)T g LOAQT%(u?Ei?{jVZLͱt/kS;y-t6g-b+.y:qYWzJ{`6E2i'ڑI9K{N8;OvׁB›H|l;!uvU\$?嵬x0 C_q;E@W ETWy: {~^\}=nCS#<Ͱ2k:K6: n? ?hIA6{/w9ػ;㮜ۻȿA9阎J "",B.;,& ШK)A'h)1-¸1C",??EB,F 1DLD\DF$!|DHā#Ŀ$&K,Lh*+3E-B/h1+m)E\A,O<40E1%2EDSʼQS 'DB=CYEFGNIMJ5KTMZӯQRS-M}U UZʥ(]%ʇ`xk0*!! `FA#W١WɈ8؃XXW=XRXU[td0tX\hxWsקzM{|]}]~u )X-X8` =׈噉0Fӥ$]ٮ u3Y@z]YhYxYY"0X]m؟ 1"U2JaZLڤZT,)8P~Y-\1۝E۞Y Zy[+[U ]܂[,ڗ[=Ume%#\\?\ͽ\\3\ߵAsڑx]HY]Xp =][]UڇC _AE uBޔ5 ʹZu]U Y]]=]_#)86K_li_) ߟ_ߜ=[==X_X^^a]#Y]$. ` Y]_6CQa~ {:b+aa aȠ)Vm0U` ݙ a%V&N 'b;-)c+?cb/3 12^ n4N56~U:98cN֝<=bc@ak!"dFfDENCFGMfJNK>^daέc>XFbk"8LBe/__;7`|yya-c6deg^ghvE klyg}g!r>t&-vnfw-i|dz"78Hh~hd(gs7h( ǀ%^hghh|s=΁g"Bh^iv靛i諜e(m'iyX頮6j|ቃSNffkN;FH7V6mհjf[sk>Ct΁^Ӻvermkf"˾&lOVƦVl>'ljxp2ѾFm^Zfm׮`&n5OnƪihZdnV ">oƗo IoNjӦo~goo?>poޞ訚\}p N+ p ץ /mp2qh qkW _qq r!r"/r#?r$Or%_r&or'r(r)r*r+r,r-r.r/r0s1s2/s3?s4Os5_s6os7s8s9s:s;ss?s@tAtB/tC?tDOtE_tFotGtHtItJtKtLtMtNtOtPuQuR/uS?uTOuU_?=VrW7Yu.uu[r]^u+_v*vX'v^?vTvdƒaov(waviwxIkWrvpv,=nrpwqw rgXs? cy!w|\+`xmyzY|[Jqx{/!wwfOkxˆ ~Gx748qݒw?_܎~¨!HwĂfWfzpz ڨƩzz?ïİ7E${7WoU{) {F{Y {fjeiZR/𞝐nѰop~|\h͗ Oz|z}ɿ,7 '̏oN}}ٯ)}}X{){gax7/ ?G~Ҹߙ/'׀!"`1xO}O;yi,,hA$ hpĈ &RaƌA Y%KG$34QJ"b[ډSwR8BcYyBJUo>S"'*Y)}# jY-(~b*JªEqBM  *a ;))*,.Ѷ*֬Z햵uSJհĺ~b.[@[,v^m*DU{RȮ.;oB{\jL'-LH"%&5l~G,qWlS˖aϏ1g"\FF!A4l8;3  qc3vH%go# x:Md4L1U\2s5]כ3ͱ[\ H>9~y㊭畉9K:8:.i15y;SzG\x9/ ;AfC^kmyԘ?^Vꪻz#6;}{K;]/y%zaO$#`T9xtWi$}[`羰sw?U@kJc /g@ ( \ uP3 ćAyCؾ'|SP fXCD kjM;ZGY+](V})pх_\h@2PXpD sX6WC|$$8G$ˣgVQq~l ɤ RJx%, 0q])#1֨%%%.䁺'X803H 9QuA)WDE?$,aIqFhn&ߌ_"l9v%Gdf Miz( ĝfJrӜD9@~䝼gQK{޳ѧ19n}e4C7MT3} m RU`"EiQ+dJURT ;iHZO|#LOXf0% JޔY9EֵTG@ j9)s#GMjʨ~+TfXɔJvՕ_iAizP q`SZתֶNpPRWUy'SV%,F {ؚ$֟[ hc ٰJvewٝAml< bT\F]'$dSڳ .%78t)<}v؜T5v.qȫ%/x7z!&OBE(_%<N)L!C6Z4Ƈر(7`Y-Aw ݦwy%^+5}{+ߏr?]w_N.\q 쁌СV^0E`/z'/, op86/sAT81BjbRYc yȫl$ee)SU2< y`Ǭap2}O7r0[O  qT(Ы4|$_TIѴ_rHAޟ-$\N-Л I__eD)_w<9^Q Pٓ=KBp9U2`8`@I iqyD`MGW_ ~Ƞ \em%AF:&*GHYΤQY!ፇ_ ^%A ~ 0͍LX+PRd@^흣AWkŔbY!@Y/؛0]fd =9936c9ygTYTSJqq|#8؋e U"a<'."))ND*Z*T+j+:ΟajbbpbBb^##o1&A226#3>E4A5f5]6 6ryc8c ##W㝀"D&<2@==S>#da#@:ZbM""BBz :$D~INPEJEbFG5ZcaBhq7JO99.t:AB;ɣDX++#Qz,@]eSD%T2dP gA2F2A#uGdIv# %KKL !qe%V_``#a%r&@&|^m&Nf|s>'t:'erjf~p υ|F&XzX!Y&YZZ[$\pvEln MOrFdw`i +U"Z#ך(]*F)|k6Ÿ>Dƹfv(xkfH辦jFkJ)ȯ ,nf1w Dxٲ@zdz.6aTlyT-֒V*BBj&~zF x(i&W款Nar+ l *mZbþb.N-Y,Jd,hkrkF~WN^EZfۺX+y+k\-sIe`wto޵"T\Dj jdvP6PGC SH_P]ݨ{lH"/@LX iVHsR5}*u5xpXFf{.FI!/F"n.`6ogfe 11BA$I' wKgXGk1p(9pijbOT~tp5^pҒxq qڣp5qr QVT2ٚ@#0;ON1_rz&x 0Xq `qW)*1r{Hp--#D../r/E0;8#223ËjN+/p\P3"`8p@2/JrAPryL4:G1'30>(gqD3W=s ?@??ś tgtͱ1ױC3D ^L*^tdQ4G;P4BqCl#$J#JDw59ĴLG:wS|rOo1GAb*G0+2?I*~T6E]'DXO2p3W[D5qViYg4ZKUv~1## 17?7G28^I%_Ltv(6O;H4Q4dd#5e/eOOi6AoEx|U6U|BcCq1L6ȶZ_-mT+zgHI{F] $?E^[1r,4s[etta3Wu+06P;PtQQ'7䔷z_[{C{W7Ӟ0V!DllxӀ[cdootpp!K/q$wP4t_Ndz|x)/v 5ww3xA,a#"wgC|eCwWK q5U xKwp773V8G)r:`sNs8GxxH`sP?psaw8P۝[h3ݟ'/gӷzȡ+QwG$B "7cǵoky,g4wʖKxx9`t?'kt#u:4Qmyl:y[@ {V3K`_д﷑a`jpz;yPwX:;8^ cG`C;S; : ELl5?}K0=O=W=g#]w3؏}=ٟ=ۉ٧=۷ǽϽ}c==ߣ=ND@!,*_ZsI訢Y3qǏ^BI$(Ũ\c0+ɜVCh#J<ϟyN*1<&*hӧPqXʵVNeCyFK5Tb3 ;ɽUϻ2)?KLHNu![l,k# I+ޞSmq5kΜYuĔ>Tn+ ,\sNVԋcGnI(8U0aj.Ļ"x4ҥʣoTլXvHgMmH]NO}W`i\=[c>imLp"ـXOvjٲLbko则pm!#=\+E7]K>$X;6xRR0zQU|xu\}_d%ӃgE"GU/Ē9mZ۟g#TbK'+blIHm4en:*DigD YHB'Ғ9Vgk蚹mYۗ`>%,diWe/ z'^y*}`ꈡ" ؆vh]"F:Gnp銕hl1~:ckjciPf`IV\S[,{W:=KV_`S)`I趂a8B*Z{iT/(!ݘ| q=-HS\+Le0\]NYTpYbfƮ|def xέ$&h\H)ILf)\8MB{bfqG0fӾ$L'=i!v21^ )I/Z@K4{BI+>t0DX^ԙP6*Ȏ栐tY,I8uS}di/ْL]Db:92 7癔@]P7%URHR6U@O06*EzLdAډ THmSY`t6k 6.ȭUJL.obNj2lka}) K]dVȮnalpCMΐw#ll{T1ƖMnk,оj=-jUKP׳Β-mkܚb}o#w `% jvu.$]N׺ͮ1]J.AMLb^') {;\ m}w2C嬍[_%XtR+ n삟C8c wAr#ghĎ4 s]}q_blT!9Sr<׽> gDZ%dư! 4ls\R9ngKI @ʑmM˗ƞK ];IʟUHZ(sz&.mѨϥ >3n yэmadžq;_8tMqzSֶ[w0p3yihtY j>n4{u]}~s{=p:X؉!v48Gm&8;y#gL7TV9aZZde9ϻ\xsq͍vN !m<11pY<) &Pg6Ԑ.m[HfJxNIr!~g>{oҞnG%"w3w$OfS<ߒuNx8>g=?)iyЇBP!=9fGyȿW߹+[GylWeSTn{{UV|× Wj|v|y7~{B}}F}'gco}}ytW\ 7^w/x]D!/Wu$gl$6!7iaW% vC|J1wWwXqw(W$k'EIgx٧}Ƃ.~08utwy7*pz~@X|BxqF>IK~O87%'Wqhx؅w+!ǁu~'};Æ ExX((w7gwHBgyGZMyЃ8h7t$ 8Wt2Xq{ ȀvI;`|f8>1؉l7}y8U0чvȃ<hIոp/Z+kw|=3PF%;6\z$ʟY1Y79K;A CZ;s +(j[T&;{O>R:e=б4: ";d$_6jln5o[s?jo)JJ}+`NlIZ[ ' x ;)Dxİ浞7),kKG97[sj ë *K#!Q:7\; AhQxZ ̹%Nbd; Y5[[)Kvv)( u# /B<ė"a&tfaPnGlĖ kQ+ L/XpXy5l,e pxzy;zv|v~ 6&YR,|H sX`ĕ2D"PLoqNrnsXX\|^a:gvY60 `Ayw̳{|o|R[lI <0 |",ElĐERƧZ˭?X< <|`X5xGzhΩ,˴,LlǺˣ˘ȈHa\̦'?"E̿XlZ͍ɠGŋ`JJQΒ|ΫmLWkϗǾ\Ϸ<|XlsʓMК6M۟Xq`x{۞#=\/ȝĴX̸$ԭ3}፸Y 3 =}Q!}ܣ̀>z{~\ ޼ .Y⅒7I=m ފCm;W%VS%%ݚ!eXgz~Ɠ:gSkX-~.O.ޫ> -YNQ!)SU=302m~pArn;t.N֙-݃.'QtJ>-/QnP R~=!#>[n]~_>eߐߋra6^os9v{ Y*N^#t9AG>益 qrǺ6H1ћ$zѼO}0nqy3Aez>lN :-&"k7_GEb!PnonVY!+Iqؖ秐/ oy_h{!#GmI&(_~_)/]$57_^X A1Z>a+K_ M O20lrX"oIr.+_4a0m\/t ?Y7-G0_SQmNMϦ8,z6AY?!OOo~Uڦ%Oa2\ǿ ˿ 5۶oέX0C#DZ dm1u<{*qI6 [=^uvP\(%^Z)O4\ Y.eԋ3+wD4NKV5d+$n-V{.~ .*9; 0*;[tEKoN71=֋h;TlXip 4T7]sAs6 +BY2dqBD<ΟDQ`O@ϒyIs*JfRJČd" [ƹ{% @C$4Qe20L3LstS'/z5NvX:d5 u@VA =TݍNJ-%2S#TRDCuUR*CS5[X37Xtja}UX;JVY&FgUvc[JU&GbQo!w\rTsT]OY"}5RJz YUKd-L10q5'PWv⤎7Yh;{lBLPڍqN1ɰ/F/epӎKKW/vX{4+K3iABbtZ{+a* Zlݪ&v2U'+mvn.۰? G\#l)BjTcWGPvs`ÚQc#@&R*DHE^Q .QT>TELhFN, ɀwu|$^|7 㭰y{(m}70pU³Zoώf!7)~r2hQ5m~sº./S}"O8'zYg!B?y|dG]|üjCz׽:%u~a{.\sΗWЭu?}/#}9Gr^񋿎B~v79-V/#i=3'=/=_^zo=>}}{_'~|'_g~|G_ӧ~}g_~}_'~_g~__ @@,@<@L@\@l@|@@ @ @ @ @ @@@ AA,A<|JxOdM02KCH C\\<, ykŹ:Q,Z*HQQ DKX}}YQ QQQ K)4<$|B'}-Hаÿ-(ݢ/ӱS=#R)<&M '1(S)EءSSS^R>}0@-=]9,h7](=HҲTXhU]mM?TT94QuCSMՌ1V]UxUUBL,S<[]^%`ÈBQ|BSK_XVkV00gԯUH[\mRa,3E0=tߍHߏh_yߤ0#&NEbb-b5,-bN߈(_b0c@N!v߿ 8Yc%^ &c=^)^4R2$18IT dEKFVG@Pcdc GccRVa *N fej-V"qZ u1n23.ntdfxc"QɍOnP~QN(V-j>Nelde(dXpN笽e v~g xv bNc&cd|}vff^X~zEU僞nenf_4&ڐՒ1x yDˑ^j ΍v&&'ci~&1ifZn޸XFY^CB-M &6H@nlQ iꧾʨVn fVꀆ0 kJf,i-ioN?Xk>>$|kSk1jt.jkhkŽ vVleh~7J080"-I mYGhF؉ABg̛XFv#}x^ HS"Zqne@(\wl~n %n1>@Q`ץo;nTLE .m牰68S1Pox!oD$oF%̂*fH 'p0p@p|d.Y]oj0ż.ʜ1&SdrѢ6s.ܒ]s-mjWoj 5ʞnd{5S‡We0or˖3y@ٲmsodZbw6)Xv%=YWa F3Kq!ei!tI7umh&Zn[lA(ao(q)\saJB/"ڂ@՝m(xv( z酶-G`$g~_x` 6ݶYdF"$vbMY0BPgw֨~8#h܏@)s LIPzޔU>re穷߁)f}eyniɦCveIv62a{臃Xh11-*"9 )hi;.n)CZ-֙jIJeUiª5&ekjV,-sDlrIيJMJgujޞnˆRU)B7jfJr)۔oNkC{ژ0 `Y^ Rq ,2fvV(\ڢ-rln覛|8s>G7ۛ ZP^24{P9U늵_29 b>1{lx6߅Ϧͱ bf~ɡqWw9h|U5w:^Mϋx۾\wxIKu¬~[ťQFs;X0[a1esY!㭆yV3 N}x \ =DnbJT6>p6!c*뻐s!r0c:hNL8&JÀ`A@#WA ll~Գ1gv(NFhKځ^p5H ohf= "1|tAJ8{W*Z12IRJ<E`KÝ$e!Ւ]r1Fk!dNjHFiJa.9rH$Ry#!IRaᒩ"x%-dyK_ I{])sr[$+wF w%Bn1F%($̉2g)#]>8&͓MB6n ǣcZ>{Nq(]i 9&EFɑd ~:#}2k7wW D[W$͙u<3;4 4` ;k]Lb#y~ 0[e?VKCj]7ByYIGlxN|9?4Fs?/hH'Uq'a_^.`$uM%DjQuE~HP_UXp59G*\ UPԽߧf_h_EEv" Z, f&! vfFx`B9U`aIljt/ʄ)Р Z*S]^DSL(F$Hj !1(!2!"2#&BK"at!_ Z@n U!~ "Nzn,F!_&!bZl $bLb[-![D9!Jb nEV[$˭ g-T-Y_`T}# !1n% I4""vB|b5>"c%Z<~8&jd.pY(b!6E;j;F=^!G, #*\ȰÇ#JHbB-,jȱǏ CIIO\2%˗0cʜI&I6sĩϟ@)(HF*]ʴ(ҦP>Jիbeuׯ`v t,ٳhL̜;^UUkM'>"bA6}}c^ ΂;g:z8mn 5 %E37N1uW8|z>KSO՞ Yۧfۆ`ˌqh<1g-a\6&7g|<u99%x6'w#^H2]~w{Gϻ!{UN5W:WPi_˪gz-1}{$?1iU!;P1Ö.J"aNgu'|Çww^31||~sJ2'>xx1 t&Ebt݇t7/tu7u DVwX'^ 7u>_l-`LH*gPX{Cxgf  a+'B|SW#&x8dx#'d@D}aok&~5yPyϢ~~TgT=/3@ELG8"9L؄!QwSHVxe #^`H$b}w|&|jȆ7g9}"i$H}*},xHևw 0a4w;hpgkpH9h3N9V 2Ch X} x[]wXx||h)r#nև}4ׂwo~K7~MW~w~ۖ~~~՘u68OzJ;xX؎ЊX%mަڒ؏hIsxrx8h")JgyLyNj^IG!9Hcx(I&+I-8XZ\Ȋ^xmCicY"HtXvx 9 yWi YI_iɈfYh)jYl閗q9uy/9|1uNڡ$)PX{h$Fi(";2؉2uНL8pU:n-'`(ӕr9Q\~:"aY8CE9^u"vKÜ ٠udZlɖuY!iL,С+ 50R)p/ !"@bYK$$%\鹞'yYh"ٟ jJF !J6Qj¡g *;FNf*,jc.ʜ0*A6'8ؠ߹~IC #EGXIz1KڤQx vW5_azcJeʜgj*ln Jr:ԡ,Y)]{iG^^Pr٨<> @Z5BDj Y=r? E$YVzN: ZcD]A:ʚ|~+-:/:ٺyݺ},v>*SZ1r]2'xܒ >V>86$.Ys?Q0-'h)->FnN~ܳ 瀎.x%Ғ ~1.3OYP /ԎT Jm`b[o=.':$/vh6#K,\3;/M?YACb׾zFaW/Ѣ#Yb /%o}+i?U2onQczWzOV!B`OJ_LߝNmbHX%\l>>1NN90@8 ?TFO Jn_! QР M,dC$NQ"/lH#!EF )S`K;~(Rtг'A%ZQI.eڔ(*sD8F+ , iu[q']E}oar$ر oTqlNAC!4PtKenu^8|.:u_LoӦ '.aޜsK΁8qGowŏ'_PHU[z+]<Ҫe[p.OH 6b[ḽ([72+>k(*T[mb Bm7N;J9 s褫.5sH$Tr%cϽ8o+ ,;k?纭@L!4 +$ 2$J 9ĄDdDK.`5_Qp};19v\G 2!9(*&CuTRrr +/˲0L#4 6k{s8N|tO` FA =ԵD][6H8S,SMORu]$Om/U0dJOKVUW^TW=V9/\!CʖլY\jWv!J6YSGC`f5GL32f^Z^(]uV"~?m`3 M}Ɔ#8;5C={kidvdL>9eWf?57gMyYpWc>ţQ&-Hzr}aWq+ηsG8f#귿B/SZY]p@.wz|8҇ }sHg&v@ 0ccH@1@ ' x6e3 (BMc^/LBckxI cî~(ו vJсxݰŖŌ\]\)_C4yVM!'("nbJ$I{ae~OIsRW#%A r"+TR L: ;@O)2q%,yTM_rLP*Ke*Q)JSe8zUf5(EB:vͤnҰ2l|'MzS TM;BO3S (\AQTP IQe UzX" {t1CКֵJ 4**Wծxk6!;&V5bYJaITن\6 fuϞO<)PA&-@ki{]fwCn[te4B4u[o Zy}POKTuF v߬v^v+ɒ=/ҫ&jȭ5Mn[(_!_X#G| ^p`&3f/K^Ε/P!)> #qUM/* X02(370q' w=>r,$nL>!e)Aӕ1vd(淑̃&l53gmp܃9yoXc w}u У&5SmB ln2<:V+ G1Z\&40sh.uS,ժfuW\jz5ĝ1i2JҺ?k |lt>D''?,ծa7 o;g9* ~p8ymKr&Eb[ x˛|Kx ?JYpF39,~˛j8@&?ysR(.u'G]Js>u?w]0S%Jҕ"ESg{}fy<;sbSO ÛKx~\A{ uxлOT{3Cp=}m/OU=|~iDQg~}?yfύt;_2t/}PO})_COO~;Ao}{? 훿?S?:@ @1M;l@= { )\A4ïC?C@ DADB,DC\Iɖ,<ǛlɠѐIJ+<ʕ, t J JIکɲɭ=TI K(KS@MABB0<5ň6}SxڈTQԉTMNOP-1BRMTS]TT]U= V Qs Xe*Yg}VLiVhp^_5 E%FU9J$Y֥zVvmhViV\VPmU@3no WjrTs$tEuMvM1xWx׷U& XW(W=FX2)W9TTXVxщw"FBBXpVڠ ]tْ%AʕhYÛXݕqXY WڡMآEYǚeAtY0LSeٜ•j6M /1NZچUYUZuY=[.I-ēm۷٩[2[oI֠@ɽ[[ Y[~,[C܉U`\-tMȕ n\]v\j%5ܙK}dID:AB ҔBС0I&TGQ)m߼,H :E]tc^u}^}! ^+^ XG _NHME_5Q_h_}__ߟ_U7]Vu ޽ޅ_fU] f  f_ ``Ƞ+kʰ9aHޛp Έdb<aaa`.t aX Pbab'B טbb,e<_N0~-]1'CPc:8T9c9Vaccc,@ָArԟC^D7cdpd6d7#$ EL~b xNn O+vd.;o<8T1 .mKX"W[H.A0fo e!n^cHbbc&de6uf8zh~9 56fufofp q;BAgQС*Q7&yz KgEf1]gkXƌ$уFh\>/`se Ώh9feeeӟp. wp& n)|N=^ϩhiYqj:1_88 vԐf82Fԩꐼj|0jwrvs.f瞶wx~7 ڙk8k}k{k~H-hƌ?bgpiYw;I}D}d]Kdaxlrj%^~2nK fn/3mH&K;H{Ynrۀ G"n7q]ވF*8L~~Έ`63qG p@n Vq|nnL&   W ppplTmdq4q}/G7#G>O _!o~pZ$_rp/ٟpq,)-/:qHq1'2?3gN671pAPp"#?npr( )Gt*F+O׶Gg̵֠tfvqZ^NoOPQJTUGVWוXW%&BCvӉl_o  vK?v2Ov#cnv~Ysvq: \n+)!oI2o?!lPnfo[GXuws;wځmx.G/o~oiM9xsS"񅷏?/W4u7z?wJw yQyxwqpyd72yOyvkO!Xz{kiSب&zvewp-7"S`㷏F{i/j6\U>/xbҵvw]|}I(>IeVS!Kf7sd֛`-{#=2i6՚SfV)`F8E4ޕ:yk4UWg;`yS\KF[v2t3sLuK%~%M~^ ;eږԺU? uGͿJ$v,eٍy82r?Ar!5%}g7/dJn!3~<%Ͼ&3fK2b@@S.~/\CYIޙlR]_YW Ķ1_ JC DtGj vɟve~:Bd&i] 21D] D[[\E]uuĄՄD4BR6XGVk-&H|0\.Yw@EGOLGMM"dOn[PQF%eafSFTRXU@Vn[t]EY:YcZ][hf\^d]BG^^քLe&qfk*@\GbccdNM&eZq`$txa hOeiiFj&k[kdTŒLeUmvWj'p'Zu,4'\J\']tuevff2'bjbyAzڨde~{\ eflt?gpO~Ufg hh&@R:`h 'ph'rD[ G)>'%Fgڨ([QgZbgLhpgzhu(P(chx'gg'gWdNVijp_4P!Y(夺&¦zT nWv"1il %y(r% ꦦFe_B*d~ZO`bj`ua *rff^髪f4تjhF`x kg!]F)>q~h p`k(tt*hʨШ^žgo>+lP@0"/h0  ҇rE Nm~ꎱ@a}fn}P 7P*-zர_,z14jGm Õ)[Ið0pI@j"(%2#O,$[WT]m&Wq'o(Klp*22 w βrJ!_, H/L\ȰÇ#J0!3jȱǏ CI2Œ(S<˗0cʜI ˚8_ɳϟ@VJ΢H*]fӧBJUWJʵש[ +ٳ3-v۷pʝKݻxR'wAApÈOYl"GvA˗ټyM ^]p1bˆ@s[$kxe˾iK {u1l&p -cnD3g :ҏX:1c;rT?sxbS8v}gEO4 F`FX|s$H|`:B[0Jh@< i@ _! vA6@e.`&y}Xkt斛p#\r5E7uYvAНw7^yz#$WA-؁`B>!Vxj H&%hL:֢/5*q#;cY{v] hNJQٟ lj[:%}Ymb@g&|knsxnw~^FǞ&Zܢ:JHibb馝6)"0*yj3Ȫg,JVf׫klaI.sBkVzVBby A\榙n6rNWu' yz$bcn()]21@Tlťmzj9T묵6/@5y3XU3@=Zfk)ʹNf0Wg-wvm/w`'ewai+6m7;3H:bu0G t} s`#8ʑy7Ay̨\OJGh\YҰ&pND ]t(wꥧ{)lW5u!yY^c0ITmb^LeA|K_ַ eDŽ~2 :Ű@+z6@HN!:'6*_$3MMqoS}Pxb# MhFDc rYITF> i$4vC9Rb$LIA4,H2)S"D'BG`_9*E+r4bTa3 MsȪNL#HGx̓!DH=j=sIi}Hd_HGb?$(K&dԤ5WȀ2TNINg_(Bsho&y|`"h07։fEjfw|&C86-ê@:/?tB!5xd$FO{S$?pAUj~\hC5*S"T~up=],99+"Ϙ* 7L9*q;F8(崏)8J4*ROR|*T~ZWM-.TvuL+eFֲfɺZ˰un[ F>;k'Ѝ.J֥LL4d}CN3ElܥNNX>5&R՟VM~WpyC Bzόf]kZ uBpK\)rtv=!^W񵤬t"iB'fvc/ѻ|DndISV@ ;;ӽoh U@=m~{U&|kaVoseckn遘@\劗#/9]r'"hoG;=qj 4=? H,gO>-؛,~ddP4D'OR:^!sLLFgZc!tT[p˔3vUFhi6qF_ ;!۳y=Z+"4w BD+:ь H9haMS'C-QsJ5YTjkkeye>v,B9mW[JwQgz]n;rJΨvKta\N{ߜwioJԨn ^5+^8Yk*N;fx:qф\9l-+G:.סsntCө}~n%zϴ~zm0u?7{Q}'t6''fG`IY!~tpR7p~#Up~^q`G@>Hze 0=@(Hm73!gN10THRpX(v/w"Oҁ80dsDTHtZt?pxj!Adz4KVH}j`HD(beO7W*~6dF&j18X(8烤HYe!Dh둄J҄OQi8VW/Ӆ&K!chOprutJvHX{ȇ}HXXX;ЎȆR{'2؉艡8hCA?uAe%xxx+˜(&ch̘oӸpDٸz؍(X"v-8xx8xXXòeHh##BҐP#R!!s†% V9~Vq/8X#)=َ8'A'SbGuQp_iX@•蕺#苿8H}^g㨖xȖ(#ijqsIuiwy|)(hi /IiH)cٔYVIXiiikd ( Yvٌo t'vxzY嗄6]))<)?)h9ُi-p59P,D񉄲xq!kS9:73;:&(XRFBJ q)7R:65LsQrCzGmKb*1'(SA_EVrc*Z`AS!Ss~VY9x*J?tG?8%BL68J4:4<ڣ.@tTubS鱤LNjPuSJf+8ѥZצz&ceZPkp67Gszxcʊz|.B(gEL:ZL3;1AW!/!@b:Jڤ>p*U.Wh##*^z\Oz Q(*BGCI" + ˰kۓk!ȺZ[(vziObg'k)+;:\Ш/zJjV7;;t5RjtbJOjk:O+JWCzQQa|eʬVk;qX鶶q{p+tKv[*S~Kr{1ZKv&2&A,+%8Kʽ U.ak+]k+8";*X7,ۿ,Aa>īki`;o%^\56!JJ«>Bx6+Kk{:lT>~ü\9TijXw"fŗZ\] bL:`^:k Ɣ*u>s)̹*ITp/\k1635̾1ȫí Ȟq[&C iɣxz]`[13ʩlʮ<[8¸dretLSyŌ< "j ͉,ȣaYͥQNƜQ{/83%|¦՝.2٭8Am\-띘̇!f#}n=mt܎1uVܿuZB ^ .k2RڍVQ )!Bm̄Ⳮ2${1j35~^vE''?NA>0=P ~I='Mi.~:Ur$Y/7^yLB:_#ido۔㶳&0xpB74_ӍQ^vn7T>j:N-aWޓ;鞾"n:hNnpn,NAލCڷJj{Na쒈썾^qО> = i*m")~C?2^ Z>S31C!sn9DHl~qD_'"p-+>%N `׎TO"/$宒4+2X}˝ұ AHC_DAn; /HTVXS?Xӊ&olлT6 }X92ߎ4uyy?^Nb2u#o_(qx {b_1 φi)R'&?K_o{?Nl %JtQ&,ezS!QڠZUnyWZĎRi25- eXݧyj xtC`lacȏa ̙GXgС?k ]:mZLpe'VF;nޜdJ:'^/.0a:{@ԪTfݪ6ذcŚ={toe.{A]XGP 1"l`!3FN4-(4R`ZC.eںM#vMCi%RTqE[d `F#jd r j4BP6 x=kǁLK0SL`4l!F)sN+s<EZsaPB03AmsL#ePD xtSN;SPCuTRK5TSPLHS渦=i=dz ׂp$D"H"@+]R7ՅfmOz]>]OG9jS5<QF#`<-iz"A+M`2 v{V!UD8>|fYX&}΁d("Ɔ7 )ʇc|Q`c$,1H)łD#A$x]hOnZ(<mm|(9ұv q=e/C%#2=bdH<-$XI,^R'=FҌ**Q4+cIGZbĖ7K_S @C*<&"fęW43)dZs =9,8ɩ8:Z$i<ٸ=|_ T,L2( BIͳ `(J^ԜO8:iQJzx"ՍBHfUE@Lz keZV>cI9*A+\?WT+$$VXelcDG*XEX42MYr ]}bWJ{Հh^a5>V"kTaPfZɳekIv}k] ۿ vm4\[V%~;Y72'ܚ0}nt됼ZWvQE[,%p{3kU/d^ tqm*b4R\k"Tޏoi`XVp |‘p[),Z ç0uaтXĦ"1bLN'Y%uN?3~Lzye^W"8G|f4ɉzr )Sk2\8p 3lSfYЃ>ޚ753W;zЇMT?ktehô<<=rT2tE)S}Z%iOZץQDmRgԨkUꅴڝ3 )ݚY_],w}ml;iBv?QfH:"5/Vj3M֖׻f xQJKfxxu{FkKo*|.6+8p9@Y4qy|"y̓HCQX~-3yэ~8J#BӝΠ(<>~tg}_IE&:SOLk]k7ռ7cwq#i{l#7= sEu-5Oxg^<"/qfG0y|C?;}m?z=bך'> w>g~'ӧ>Ig_~}_'~_g~__ @@,@<@L@\@l@|@@ @ @ @ @ @@@ AA,ADH}DžlHiȂǪHȪH3ȏTȅ4CIbɍtI[aHtI ɆɠJ#J 1Jl HD |JIB4dn,Kֻxցq{YV-@ PW}X_ ?0XWJW Y~E/(|C 1XU؂`XxXMEVk؋X;XY(r!Y U] EW5u؈_HxWm>M 5E =Wu٨-XڭY*5 [٢吣ݔh۷[vmةۼ EȭMR?=•K5[E[EQi\|ܱ\[˵٢CkU+&+G﫢#ha hE_]M5NQCБ(Y]^x^#41ޥ@^X^h^y1UOH.HS^ݥm)[ *ވ8_uXMEEH_ ^&26`5:Te%G >-``T} 0E`ka0TPt > Օ\uC MZbRa(9)*v+v - 12>EU#aH☂c`9vQ;N-bd   6 &e8㭭W01dCQcYpSv n ~BCcXFeJe1ee^F }bN܆3f xef@eiVejfeCveDfev.eI*gs6tuNvN5_Lf苭Ѭ:+@MUn8hXN[Np^qnhIiK^ac3i=Nnf3HBgkhl^閾ؗPh6vV&`iNN9֣N`fhjgijcHdXo+d(>kHk:]Ke̎,`Q/2HHCkkT1b (mݦ59S,Yfu6ʎǪllm E6m@9UmamNtلD _aۦHm z f0En]֒ͶnNnn(oM$4o`֒FmޮߦϲrnS{hN5Gʎg+r1 >4H|md2-(8q#H[q ? e-:rH]%ɔpr(wF4rtr-rr-N;ssLsX螜 9s ysss؂nX tBp+/Do//pB1 tXPQRjGq5X auşuu^4fZ FG b'cgWvfs_ hvlmr1oP-e=wtOTIf1JwǖGw| } jFxGc/xN__&-ax!px^knыn :ڟGvVWw?@7'CoV_|(`z'Ҟcz7j_HQ9݊UzsGHuzz_P 0oGvJAb Nk"|"s\G̀x|( X({<||6_~{*Wx/{o`릎-7GۧyTy-/SG"~X5~,X hPĈ>&R` F㏐", $ʔ*Wl%L+fҤ`󦑜:7*HР-J 0i Rj!Z;r +b (klٱjײ-i2J5o`Rz7ZC0b>'bjVRlBYh" khAzl5gM$*eBv/w/nx\+Թb?F4OVNm]׺%\rwko`fϮxEQ7wXYfmBk@mA@w 6lTo )x|Q\N4 YTv9"Ux}zhwdr.gW^I`_2V`5Ta&ق 6`QE`kv!(Do}Bp ""s0w^x#O9Xw?B WBYYH$r9d|%J`jVe<*\v(a8f ebhj*Yo)Vٝyg ,)ew*:9>"^G{^4)F+ٚJ*:U/e00A kR* uK&,Ƃ:Z5-u.t-J&)W{gr v[δ.= +LKڎt]P>"?矇.zYm\(%r3isNW)_ p0ӌ85XKDH`3`HE+.>Xtȶy1be3qkJ8J)hʙnT8Ґ"ȍ$$9o#h(FJ sY\2MV2 c,3XKHR(rz$I03b+}d9>) P4s8M0Ri6A!` &9'N: <`֙1%U6QMVёTHSC  F%i2PRP8VUU/p)P2HMj,nekt*.jW;׽"~ժV ꬧I+YLկ3x`%bn/S=LؾP4iMq Vͧ#jŨH0iPT/kիY+WS^4Pzoo}n\4uv%`+(pcgY e3"+,hE;ܵtL?jZV hTXv[Um qh" eR nԭu \ꕾ` ܤmlz듹z|3@_HI//[Z{Nz lW@X0`Ę`)kKaTXn%q/-,} c,4Iy'pcBV5dk wDFDžd^ jI HTVzdY3|n% f)frDl扛 g8(>uW<v}7A5džf{ ߩя>N5靮-:H;tVu۩zyYm\\Pqoo\`׼} l,MXFph|ڈf:Ǽdd&ǭ˨I WOU[߶: o@ 7\wkN󰹫焇 _rN"1L|q6yYҮ} nスӝ vMN(P,72_ "/y]xvozO$RoSճNV`b/;IYz`D$</|O}0g_4_=ȞS_;.ѽ{w=T" x!$~/?yGW{M}qXeEU^ O,5@9_ U^I^ʉy]^ДU_u˹_~T_Z _D ] 㩏e `*5FL :X8 l  }`a6A uڭ`]_ T bދ *Şa`QF6X5TF_  l!OJ @!HV Z! a!!,! */v M.RΆ!"It\"Va6"fdJ^Z􉀲h [^eJaśTZh.)aRbԟꎦ(UFrA;:j@z褮b*ꨧfp*wjx:ARg`*gX^bڧni~++F%.+.MYT+ V(րMnVknnjo hlDXFr>*sFs+JRm~*vkj%瑖g'Z!î:{F,Nk^lq^džXfZmډ,a؞Ni{)Ct lƅnHHFχlӹ2^ڙPm#-RJZgX(+ ,v fƪj4Il¾nڹF~igv6k^_wY|&*d,D2+]nd.q 躥 DrMJ77+q8JEtsՂն/0*70-_=ْ`s2v)J!V,*HY(\ᛇ"J@"mh$ñ㋏'BRI(S"KOb)т *I @~J4ѣ wXӚn\A*XZS5ڰZЏQE!Apx SLN*ѮU{+}ES7u[ՃQLftcj!(ec jmY_]l׺.qȓ\!D<1fؑ#ڐ'FRP91tQfN}6ЊhѣF6~TWoе|dӍ՛YZmsxu_PX V%b.͇Me J&ZikjI6_hOݶSnPۀpnt$w)Cot Uu 4ғ`vS~ _| T~IaiD Wf{ׅa('d%+8iK&Zcl@inU$az3L\M9Kt&N祩zlf1ժ}u'W^7@([n B&!!bU鉗eʙgJxZ䎦,E:5)Gkk+f%[xϮ^L[c GT'e-|f⾫֠ ;2&ZHXKW{RD_)0 j 6(=@¬Y1Ī$ \a̫Ƨrܥĺ@$ky=+K.ǭ8ۧ:.q wxU4~(KY*b^7  ml1H%Vܣj{D&3lQ%UV՜粑s#:U4 "P{;VaZŎF!k;N' 4 IwWoWQ^l}L0g2b/{)g |bEgKD9h3Z0$:1C_CP3A:)FpkNF2񋭉M%aI(pAPr@Җ -Zxf/Z3 'gqUA!Ç/Pr_xhKZf4ˑ+/ev2B`ŷ@1:PUf<#-'h5&i0o# A3GO1yx\anQCp{CalGRԓW)D1~`:UFb YA (]hK2DB*l{33c2g‰D¨AdMDj7Nٌ" bY)(MQd'8.'QDeԨh)|Fg@:T'jB"X|hfѕxDfX>]H70&2<)#"T:E2>"4+\M<^?)UJݳcVTY.MuS)"URWjVQQiZRB($b>1 nwDk*@ ejj*.|BX>.@ GUQ Gt/ Ip@ ͯtC jq;tjo~Ye&L^חȰ7a@ ^ъ@z]tV+ms[ݢ jR\[R.sKv]@}o Pث*Uo}#|<`#'Y03o^29g" `-v1Za%5nYc8nq.7s|.OfwL^Z|$:3c&s\jwF@Seolg2{hQltVG=L2ٻk/](j2f'SlS6w bz6݉%>)yV{ sG::my. tyܱ ꠃ'@D'`*\Z!0{HkT؉P+*i/16!zў7=*W?ZhA j =FdEJLڛ5"cZY*Xֳj٥z>蓘E1P9p(v-ojdd35ZT/+&.1xQ>b"ʛt!' [B9v:{W+oFZT` 9J\z0DYGnpqZe'šV z`a $Ϻ*@ZDڪj);ZkVA {FczJ{ ,:4Z6z8گKG\ "͊qaC+ ,Aejɱڃ^ڮ~H,"UHU&#ZzR@a!a>;QPDuw NQ;J@aX~[+#R`j;bzd۶x r{&tkb{۳}@Bk%JK  A';8戄 CYglkn_ Ԫyڐ|C+ME[IKMFЋ\O*ѹ!:I<)Ai˲k;qھwyk[+b+n " ͻ%U^jS8 %it"~ALu H$!F`|oK;tOKzOdk"; ]Ll*Um8;zqH)?^ _,K1a6ƪ,\ֻr\ykQt`p˸L!2YmE<"!k[̧e.v'9lH𹬱qkzQmeU2pMVҬ_2l̑L1<MI a,C#!"N]6yWKUC.̠K~f-O>嘻sA^޹%2'~*)n]+^-6/61^i3g㠄ЌvPke [Lƹފ.彥Uփ[fU`{>= J25V\^rN>|=$D@X^녮^ҌŮ@Ȟʾ&_Za~cegi.kNm?XNQXqwemeD~J0nNnˎ  UBmahjNl#q!9@IajU^/2469_c;֧IwP!<5Yznaۙ&]F-ڢ4!9#,SeᷚQNP ~|m"k`o|6?qYxO rs9x{}][_A?_&/oFA,e?|?$°U*@j`;d//? P ,tPC7$N,Pbel(Dž! YG)Ur`rKeΤM9uOA%ZQI.`'QLMRY!lW^3fhY&Ԯ֭q{̥[y읪ƪU \a'/x` 2tDvb KiԦ6UkرeϦt6>[<v MVqҝ<^T} \0ċ7Hp&C6tb2ʑw(#G,"n[6 4@ITح7߂-BN9r9ڒ.ꪳ:Z¿ ̻@3Oԣ=O>BۏDM@TrI&4vۭ*r.+C64C? H4DߒP-h{/?> i<6'UtQFm[A)+2 ̬<3ܫ7YNOsF`ЎV?1PI'COCQb5I(I+85SO7+QC$5QUQUVa|U[qQW@WU c^ؒ*f-NxjΖ2vxYS;p]lu\<=wtm5#^y}Wy_SVZveg8Pz9a]`h~8҈/PzvZUbxjeݘGk ;dw7^y]ymu(c$ZoC:.2 6uinjkl G6}t%ϖ P}uUwO kTͨV8ƋPޠN!?azyUH~{r>=vݪvLqAwxEG޾}>4&e" Ce|~_WOCP# !7nU'PQ2I\2AdJB ;RҔZBeU #V& Id9IZֲ(e5eV &!]p9@f~A&&uMxS)6R|泛B)bFA5'+Gl-)OFl ~O}8?9΁4Pmƒt$R*y3=l#TTJ,W{xS&X4e n7=MzU격4}*ТSRժY%kYhɦ_5BX{6UCkS$_<s<,y]f< ϯ% nzˣ'O|{Z;S |sDݰ|+v=lx?qw|]ۖM7}fR/>K~'#M~@9k k[!Q?s  +8HDP>#ظ@ @@ Dދd"Ax@ l ">:#BJ&BR,  AxV@>BI%+<'`4LC{;8[k,B#0.-/ 0=& 2d'4TCk89tCÄ&CDĊ(D4P EDEFtDԯ l#DDLM, N,OdPEr )(83HEh!4XWXĀYZE]EPER$FcDFPFg|edJmšlqx§  !4 t-G  HPXCcDzrV1u,v>{xU+\},ǀȁd,FKT,TxtHɳ||H ~I LE,ՈGLHԚII*ɜdII;l>,.~$J (ctḪJH:j șJ!JH˵, xL4F*THl C-TBH ඟ4BüTL4(ȌTLZ1MܬKU\ ĬhߣH(NќCt\d?LxN\M\LhLǔ̸ NM,\IM> ⬁56iXlΫԁy,S NOcK,IIlTFǜp4̈}P2P@P}XFЬЭPKPK)JN|Nu{,b K/06@cLٙxdHnf㬘==fF܅=v]kdAdBV8dHd|Yv5dƘ0d7d-KdeeS T7W89XeYfZ=T%"{ 'e(:6/g&M6d ^Y;y߬ZPE#P36QCgxy.z֒{|}C[Γ31hcD^hnƓhn Yaii1ƒ>+xiF>V癮Thl ja&NXj$hr~jy ~iUj0:ij7zi Y,B/PaA&7yN> ll"lx4Ć Ɔ"ǶhIQ!lll>n Rbp>mVVjfj׮|mա"Vގn4<hM Ⱦ:j떝ԶHOk & '~4(n-. FQ9p0KzB*Zp񙯀jO`׵;ň3jq& )(odq7JqWop2r1QrP3A'o咰Ő>rQ/oL12OY3G)LnF͈ss;'Xs`$AǝB'CwD/E'FV012ǔ u&"/>Jz=VoyuĉuuB  1CL vv(vdWefthtitjIkiM| xv7'o wq_s_t>_hwt\w!Fzr{/FW wjגv{4xę>Ks_աzxxנ*m_xNܑW"u[G\WzcE}d7Qqȩ8_rΠ<ӜINO"6d YAvH0zt=a7a6"'ra8B ^$g\(;GK5Z {0| 7lBS| `|x4g%**jMρΗ? }!}IM$Ao? }=Y0`p.@h!D'&(CƏ?p#Ȑ"G,idI *W*h2 ̘fLhf:oaԈJlR Z* ZRuذjjjmrJr"kϞzZw҈s{ĊԙÒo 6sY kFVb)OWh([b n L!'zQƎ(3o$\.]ҬWo$JѤKFPjD^<{VڶlFK׮ 8b)%Db &F1PUPXxeeg!vZy QhkG\{v[nj11gFqsA 9$a0]K4 NA@ Q!6Uf^W'}e^~'\LIbc "%yZ'^ab b"a5tb8Q/B7яDzjJ!Pv**D>pm:p%6 GT  ;[;p'+ mJҚlF+ഀUи ް?խNVn JFA }.p6hktpYgT0צa¯6py?E,UAeYwqƎ E&08ጭlӌ3uf(36c~;nk.D;ѳ&]MO*C cQX]bXo6ĂICۻ]`klݑ-#7.8~8B=J'y}{yoyD/yONTcțK` kndSÜ6wukM@ &Oncм=/"Kr2\pӞb&|sedc\tFa}A_BJ&JSʩ@&(e2T P0Q>ӎ=Cfp4Y^E2$:N/N A7ȅ@ʫ81 %)jUj!g+?Z!=tO|g S4 3uhbJ^-h Nӊbt`w5a ;<:= q=lxQrT0; _ԇ L.QHC>ݖ&sAzRKԥw1EO?Ӿee _ĝݝ<%W˹Km[ݜ-eI0s @̍\d_|CAD0T]MaH&?= ^EjTRX_ [_%_uܴ19_A$@ U `@`` aeH4#`Ev!` !af%Z"B0I>a홅^ea qA@H`aYa`iE_z&Kr ! ` "I!"BM۸8#*iW%U'"Q)*ڋiaբ".W!=t #1"cHE^1#jFU} $I:)YѪX Dnaz~#`L>XЅPrdG2.}e]66"SnTtH~چ$7 cDxqx/^STYj @XPdE^EbeGvHHHdJB ȹi.EB`R$?zNZOZ!Q% eR)ZBKMe.Z%VhEVbVAWXBXD%Ze[ [G\NJ&% ]RI^ ^_~`aF&b*fH0fcc@K€W]Lef*fN҉g^Oh%&jjNNAk]OUf[h%@m`n>nEYvY eS:p$g-4gb=%XL'uV`*Fva b:-fgyT}MfM&tf@}f}ffRfp>%F6UΧeV&(m.4(ƪ))D"ț)qjg:'B(*uE>+fD+$挶:FMXTdmp hg8e+[^ l&~%XꙎJilDp xJ ]*lG ` }@LrzlanΨƞR>VTnfΠl. ^\vlH,Ԭfi&ϖiЊ2+ZҦl ¦ԮUe֚`6kצwd>\A:jQB*I N2ܲ,:ZU͚**Ϋ+ᢧ&R@ 2c$ٮPil.}t!Ѳ:Hh G!GF..y2jَffHVnn\ FYҬT+欗r:hFN6 ,fo Oc|eq.dNl`(vd/ʮ>F+I'v-pF0lChk~p'pol0~CPm0Ͱ Gs-/~2@Fn _H[Q1 oOmv1vj\1l1D/"+..2//20031132'2/3373?34G4O35W5_36g6o37w73883993::3;;3<dz< 3=׳=3>I!,*H@= (4C"Jx "㇏QB@d(SK10cyě8#pȠOrHCHC(xgͧfJQU9.h5+"`Y'KVYK[/zͮ7Jp\zIjt9qvnLaK^#jצ./ڢaDG4m*LM v[Ed\r-O$ŔWTPi2hPsU5YgvVVs դLYRi1m aq?r@ŋrݭލߑ~smdxKMxd5CG5U9WVYX:GT) d'YvWA(Z_$xųZݘ=+B]NYnC6нM'|w_W1 }Y0 sA3*1Kºu PS;ݮ1 $#tcd182 P#Bo}1k3 ?` k2gCOK!Ωe;D(&1 JHŲ89 Pc[&80!d4#Yym}넶c=ĉ{TAFҳ!{C,s{#I!1L_ZFQM} Nh+殃 t)a@}\YO+TK6>=-jZֺf2&"jty[ n U.F\UNUc,n_&5y#΄]TAȾBL0엿 $)p{Ji -yJx[TȪ0v7g]l^0L9 *+]|03fi{c/<6| d!G&Jǖ{3[n *lX's<:.w^,>b |$VJF+kn9U?2%OҲrLe+0 eR(̳;nͺjtqzv.zy^n)}=jWt|vֲEMns- v `\25\Yw~hњ1ib)=Mvt)Ǖx5sp*XzukӋ\*=^e6MnTIu7c^[s6MG}`=ZV+8n$]Ko:+'I=:yu=\ArgzӝmjzlG1=nkX^ZdNR_3sk}U7orx%G J̮./Rh~=Jz^2ٽpWeܦcڎSbx-˩V>@@TO^woUXw< |GxVx^xFwHG}Jg}اڷ F`}VGy7zPci'oYz#z%'A>Sg{.Xt7vf23tv5 ȀG/6wVTX\RtflB8y ׁ8[Pf(O"j%v'y)(L+zzYzjG+akpar'h=x@ka'x@JHӑ먎N&CV&ouX-_#r lti( Ȉ%7DBXXqUAinT VX'ɂeI)HI8bˆMb"3)Y+)mqטyI{i9uJ#TF Yc MiOiQYqڹsЕy乑ʁ驞7iO,Y.yw)ٛ8BzQ-%ʙJ) 2pI>ġa)."Y%:2 h7D9ْuǗ7*d.ڣ990\# *K PZS面~743IDa*2cZ#sl0I=x9uj<wʧ~ )&IjNEʨ߹XAʩa ,Jtɛ3'zn1ʜA ZziUڂ' " R7ґg}KZ<ךy٢/24ʪ6 J9kjrgM,+Z"$ir0OïL7,ש=p)iʴmj3xTVJZk \+^ˮYqcg #i L*\N3p;5q{l&`Xב]$JttW6`QjuwUa:j5ayKA/[C#T\V"bNFc<㘧]!JE;XaԻ0R+kkK΋Kq݋$[kUv¾?,l)[< [XLgK;gd›ċ E \ҫwAv|&} ,#VhW 7n¸x<>@lb!{ϋ,ŚGů{Oa*YG񗦡lmmd^5iN:z]z->>@*LAgE$Gn ">MOn'Qn4S^cXwv#cn_h.`=~"=.uz^iPYȮN"ď>N|WqOEţK^9p)>ZzdW~>g.~op+ -IZ??c-B]wLob^픑u\Bx/7 u8_ҔA`PZzfhy$DbLIKv1/TV|+KޞcYAjroporABvΒQ?SuUW_ _Ѯr;_nM9#04+~_\'wa?qPPOί1*!IFA .4H@%NE5nG!E$1I9T1e4Q&9u9A!ZQ.Ӕ*Q?L QjY͚ue+V%Y Ԯ+֥L!CyubI&\ذā#7e,g6m)&e4oe\*&=ڵTncjQ;5[ ߶8V|qy֛~ p.q;|Q׆\));(+q*T%RUVLԭ栫QkY2&6jZնUYϚV>ye^=׫ծ+a7^r@%ClZOޕwdw)YPV[r=+f[qd:Zҵm jYZyv* Ҷ,n[޾ַ .=3Kyuaa^-t:yQyt ڡI&׽ޭx[*ZǼAYzO^W~/}c=V@/;7!{+#!Uc`=xrms gX?o=<;8` bw*6,41apLLcLjw3E C,dt=L#92/d&t{*[X>df4`\\IƚI778q^(;ϗ1󔉢51.HpiLg4HA hA+SZ0sh"@: 10JO$t5 PNӒC;[wBY:&. k[:׻@yh`,Ϥ]EV@cv>CE򐙁ZFٓJV0cmmv = N[ Aw_7m{3';$5 c| Fˍq7&xK8080poa)͍s+-=wϋt E' ]+.]s;wpw+A{oB^gՋ'gֻR2|py tp}^i|ճs}g{^?Pl>{x, 飾l@˾Ӿ۱?ؓK&X?{??>{)>?? @$<@I@l@|@@ < ?@+k??ˊ?sK?|4@@?P1AB 6 \=#; SdBS?Ç C24 B?,-/r!C3<4LCd6 !9DBԾ?C(B4D@ĘP-G$@04DKTCMD T2!|B"׫CS8EKE?\EV=W7XaɫEF\|]Ę K_Fx\ sz.>G}\! qSHsAx'6A#4A2--HܻH\TFxsĶudvFrG p{>}GǶ9 H(;H \kȇGHL?F䞲SPdŕTyǘIǚd~d>\ID>J(J<$?lJnyJJHȐLőLGJ蟋[-` pɳ[T˚lKxKK; ˾ LH” TdLtL!| [\α,|L\K$ 4MI JlpIL>KLx)ĊMl?|LhLTGd䤷dx$˖4Bд/I#INO=ԁģMXOlxϪl5ѫLg9114TNxt4d_! DeE$M N %D8B,U<\̾#E(4m] P$Q Q3l=1R Q&TA]R{Rz~R Q.mLo ӭK`Q qI]͜P7%|>9 :U@;Ӊ ҜS-?ZU\U'UN( )?*'RH.jTLx5"+S:SISP ϳqS_L Q0XM&QV MM$םըδ[%TuՑUVz}WEy ?e<'V6QVS}XX؆عXWX H=KetCeDuEF׷WbT-YCb7M' "[ \p =I&B.QG$=`7\jF5D \\ ZtAm]u D\i 6T|<=ĺݍ͊#[[]ܖ(\8L.CƽD܆i5A\\%]3D\\-_] ]]# ^3^(^9^H^u^^@]\ޮL텠ɭ\=\d#_J_e߳t_圂%Xػ__].<.nV} =d`p`` Fĵƕ̃^4f'4B+TSta}W{3Uz a _Ր'%fubÅI=5>b^O'Az@^D3n L^~escPc_9RcM!V@?@} CNbF`Gbߓ`K/0>1&Q^m |ccF^[c\&b} @S 5d!fM?{ g`hd#uy݆4ƈ.@n "g^-XN*hG>V>Wc6=]g#aiwvͅ阖^P`~a^E&Q)j j 5AdMF>n([QeRST‘net;Fa7Jykgʫikag6f/U,^<.y޿Ua;?RljV;nrlk6i;Ni1m3k.ֶϹi.Eݶ^t&6æŦ,Òy\njot60oD]6vn⺎ໞm}mI+O¾ VN3H[vpLn p lI/iX>iYqqY9 6oHFHB|ʓY>P˙$i=\ȫ$z>*sKq8#׍USCHө-ONόϫ' )X 8ҮsC0xTs}ɿ7s _;g<׾s@24tDE7FttI7J_tHtnRSTU_Wo X7YY@Z[\IunuE8`:bco}O>Jszvhi?j=k?l FiGmH/ow ߿L?MNO_wQS\|Ozȷnj9ga?y &}!2C}Pcp}\K  B hĈ&RDb"Ba6rc",ir>|Pe.|bsN:L$)B#]#bD&J6i )T**֬Zr ذ@va jVhvܸr.\bjZ/.rbĊ>%ȒR`(̚p3.4걨Q q5칲Ua [-w9xuk"%?~S^J6y*~W?XٕdZkV[U U^Hu!Cab%xcI{]fugViq1H!FQodqw!Z!_E!a\IU^ wEx5t^zGVFG_}Mܗ~9gU%g9dZ&#]9D|h!r}bdH{'"+Ș0(#xZ>C9H:$9$tRPWqD04ٓOgf|?ΧTN1gtjz+# c5h1gƾ6`2F*^kF9p0 ;LjsATU~\jI5UVͪC~6NJ{VD]HCZ$FTskD"V5z} rT%%aۈXT15c6JUe̎7s&*BUZѪloHۺ6#%kYw-U7bkGK׸@n poFL.I[nyMPS=:]+O˩bV jg}#DOĠdF6 4Jk] [^r#+XN ։aaJp0\lïB\+9Qlҹ}qbOsŇ-a m ̡ŜES2iNE24i'+KSeSժv2oWֈuGk}}vg~R8Rk_Rq杸 Z jGגw^ŝ߸U9if9v{έRVzC\9tk3tT v~[q 쬣WڌfC.m &7+U.&~GMugZ;_AU%ek/}$#>wk-pǾ5)6-& 1!⏈s_>jf? ў\ƇMzW!, ս)H@ q^FP܂(ă@ Bb `X߈ P!_ D]эM_WT_|iu a)MA+F|I5|şٟFH`_ F۔1`jFZpP\ B| @ l r86Q``y !b2@IRkHjQ@A)Hᾁaa*zzG-!Z`^1҆E """^"b # R"XhHp"x (Tcb*n1n0L..k-B_-*^D0c@]W(`3>cfV`5^dc͢vc | #`"c\0biś$£< =cH>&@!S6%@ Zl+EB_CCd\"J~a$12$G"*#^3:#FcLJ!KdE$7 NNd:#$e V #&R`-eS^S&@R! "!IXV"WbW"U%LpHTYVYcGGzH!I%]J!66Ve_B_R``5OBbccccd 'e^&S$ XghVZ.WJjk@a<#Z†hFRel$!hDEUU.7"`F_^ǁ# Z %2@&V&^gdvVf5v'|9K~'O'zFʅX&/&§|#}*~Rfș ΁(yv`\F,hq:(<Nse#򇈎(wBB\RhyGzij"E# !Ў pHXr45&8i詵^Pi`Z)`pLhbb(cjcr(d&u vVng)*i@izTn%zdi%Xި{Vj|怡F~. |1\:*BjdiPj^ZjaD *Ejiܨf(id*ⵡ*~&V>鮚(&K{N}:*IG.iH6iD骎b:*(qd>h+fXmi et~l]+q+Wvi)xki2 iEVd'>,F<+\Rkdl(ʁǞ y f^)r!,"*`GXƀ#8@Esh,2e0ɓ(Sòe0 Ȝ)怙K`GZJ&LHdPJMXi`Î,hӚX6mZ/[,KwÂ`×ߴ6R<3t)^r H 7#̹Ϡt[=GY4ke܌%@A$d0Ċ3n1ȓ˲Lo̹'qDVtiNn5bV\zeltϞ|Feꖽ1_9~X`fS~-ƘrMF\QUh&O՗hk 6rCeV`p8aq'2hcc9\O>uTv1TApdyUziU{ŷU}vs՞}bd(؍8(YeBxgnȡ<ƚFEՖ"B ݋: Ì 8ݏ w5HK>)QgT%YM-&a -a ^qXyZ}!∆81ۣibpBV(j)t)וjJ*๪gZI׮ ޯcUg&9(ˬF!vmr۸[K2H+/\zJu+JU;JjV pD?qKq{`j&O }ɃP,7vm+ܮq8|4$Ω@ ѨDe4e4r6XWuU[~l %$KF!.riQe5\<-ywA}7$3Nj3~#,oeMm^S:]/DDoꪛziz`*j&mnHUwphOe*ޖ<+]'`qѱWu74m@|QUXui"ch>0adSXav@&pq "A? ς9ʒ!ϣuP=h4ar\p>/+!FD?MN!`5gYj iI," 艐oMǣqA a|D['}= F8̟$!?&pGuˁ&*ZFq )dJ py33-G\dFW4;rңQǙpI5|HgC&r&ME%wD8^sgEǙ񏁼;xyI&͠Ju IVšzJRݞUR~5xbY@u`Bc@,)6wld8쳡+ʱv= %K,wNn_x< fy (4>Mf.fޞhҭ}_#jļ WǒC).mjfyDiAyT!bj_s)楩{tsE:7St=!IW:k@=?R{[^l>)WC~A= نjM6|23E{t3Ꮠx.^".P<5dΏ蹬ms#_}I=mOq]sgEwD@|AHCɷ|ͷtty5u#q}In`diG4[mGBWnl[gT3{&,؂.Ȃ~k{6pj07^{w7!((Xw't0x 7}/,Wy}y.34HzSf~C[)/+njlw79n;؃]uwA{CE8|I'XFׄMgևYFօ}#~kqsi=$Vh=p*2]䆮xQqRa'Y=WX oa،XXu22Arr-UjqCtH@K84~y.g&{gxࡊh88rk4AP56xȋ&L`EdD .RU %ӵ [_cʹ!sK2?h[>˷=෸)"+dP4RYɫ Dma˼ D C |경}г };Tk7qAX{{;Z jJqO & !ܺ< x/$m/[lĸK9`*Y;24,6]8:l!9 =3x QHlR!!d@k*xYZ*?PƑ6)ȋ&iktÞ& t<4]גǷ{,g)z|$x?R+w,\(\̖\y  ХПЖХ:ʪl{6ζθ8Kl푾!-|%@5}ӏ;eb),3G Km;{9֥"֏E֏ehmjl}n &|os!ힵYqYa &\Yv `JlG2PȝʍmHЙ"͜=~25D]O!ږuBfhmM% ˝lŭMz֭!ݔ   !]{v>rp߳2:0K~?Ho:WCd'a0 uᗑUz&R>MX~3:n?9u |Q\W~q.!N>F*Ax|Nb^?acneY${jl>moL>N^S~yVH}Xr|T7$-Z@R5MA .<"^ =͛>^ꁊ=&lFB~C봜.N%m.~2^N>)/?!@e0׮06Mn_^o=.-M:D~^opAaپ(-f7/*8! [ nQ?؞N#?'NG-/?z^QVO@n+. BFf2MȱLOȁDN}!:1Aay*hZdc0cɑfr٬)uJi5H}/Ə%oI+DBNjЈcXƷeOA/X౯U i/¿;mY5]y*qIfiCt˩ّJ޿_?.aLŋ5n1B =$YI)UdK1CPf9u ϞZ=.3g>A"1JWaŎ%[L6e(P:q徥RM#PU!Uw!aĉ/fmZlSEwӾ~ ƈذƩUf#Hȑ%q[r̚wN;o|I0Eӧ]/g9igӦlvnH{[HƏ'7yͣ]m:j>wO'MozF0@TpA&:@ "0+B 3Zi1j֚.k:Fd꫱:뮽k(lU;jh᎛nؽo W`p rBʼnm%0q;sC|tMAUk}"zls0xm_}o"w~w}=x{'^ H^ۙwyu~zqߞ{_Ip"7zG>q}J? G|L6@Y-i>^7#$a G8+dM0* n a mhBBQ! W*IlxЄ;<@} j?+"oD2щ-IY7Њ5" vыc8Fqg(5ja4!(G:ɣ>E $B1w,hBFЉ$7q@i9Noc%mxXe&;(\<*HI1,m\r䥢#D%q0dS Q&d>\9ַ5,x&"yi64!6Mn6Lsܛ:NhJBf=osO|꥛,C8]Oj3(xE=iA,(6*jT<Ǣa8)E,m/ZLc2S.̩vӉT+AՈZ;5oզmTcOUkM1׸eUGMscm [ 0&6rUV1U㫏bVRM]aX1t}MjEx2`mk7Um7ٶrEbE{džԮ^jeZ> mk;ֳoK\Ȣ?Y-reZO%Νm [NUHnW]VH?G$@w}`j䟼BQWGp'qdwTK_e'#H`Z2>-aiYp@?&}ڰo6x4Fx豅\!_ÐՑߗ-9Y'~rPT,eŦgL5o` t'[i潽WE3b-?7-#t }h?̊@93dVnvߤRc]wᔫ+!ˡvZM5Sz|1]XU& qO{^y]c7-`@5Un):ͦM*j['vdۚu0Wĝ1^6HM1 R{fڑn-slu MG8DN^^vE] BP;4k UIA_x~WA12xuOjH.u#334|}yW޾0}/"#?IxJ>?b>R5U@˻ۡs? 1ك?? #>? i$\+3 >>>+Ҿ `"#;sD% A zکL+#ӽï(ܠ9ܱXl#B$2CB%jBxBSAk,-./Խ?"ѿ@SCAf08Cd t!"ܡC#A$C0? DEF|D$CD?ĤYCND!P I:Bw#b:CD tB!Z*\A\dTA0D2F3Ab;LUfQFRFS|*l(mFo, CӺǀ ;t2^!:l=kVLW Gl(,& x ;LYɍhyIЋɛcfɜIILA('doDHK\{jJy0JGJH IIxKʓF#9!|FaKTp+RT*k O5/0}QO3MS5efuSPQuYӤy/% {/` TDQ cEֈPVfմ)dVkOPm*|fT-W8Q2"!uSvSZ}W[!P'- WUE* c-$uԁmN@\RXauXmU5V͢ض 8lX W]mƬUU@Yz-K2 MZC'UTmTeU5XҞҠmVQ%"%Ћ=Ur%Qs=X Y;ZE|uRm˯ŋ-ͱ !))m:9M&%5V20XPٸԹ́X(EZ˥Nq܌܍mSWMW8Z:MDiyܶ\ܥ }Gsҍ } U >Ŝ-ڼ]ڝ5UQݕEQUUuW8ޓt\ڔZgZhY[})uQe^M+]͉M]5_E_}XXE,`E5ب٩_ߨAQ^d^'2^+ޯl`o }ZI/ '`-[ţ0u_]ߔ1"Xya_❉ .a2$r'\E[ fV^:_bNR._̿]1f2^- F6 7 8NL5&a~k#d^%d$^P]'e( )~RIZeҁR•#lݑ n̾ ۹뚢/COb#(0ak[!(%>&`Ɯ+/8h'k"Hh8h/΀% !Akd j.3g%fyh op81r&s-t=u67Q# w&S5iڝ-Q2Vc!\h6FPhTņv`^h ^xKli)i&#(o.—qr&stu^v qkyj ^ӣF9U ~j %jNiZv.Dk@d4ޫ~f9~덶"Ğ^ֻN^aVlƆj5Ȇ~6/VюE;2 3돖mvn%2VxmN~).n!n)jn1nm&mBoV վkvon !NF8mvikkS0sѥvp2p+p> nNqC qf~]inxá}|Koü &rR!C(AT$¡"&_z~.)rnrn>Xj.6#ɟؼ.sN~mʊ4W5oxӰ7Wnh<=oK)p!#2,D&_p{t*G"+rKLMrK umkS_Nu4mxu8Y(8@[\7.;ra?/E>vt!f Fjk?bmvNwu(R>Sopw$wΘ9eP«{ǜ|wwG!/b'cdegxehv nlO qzrs7y3Wqfqvuas+*{i[ :皟צ߈B҆7)ut)#x2ynAOMOGwTUmbp!={m{{\s~ˑ_`Ɂ'ȃ7OvbEϞc7}m||pJe N|WWtb0"YE(h„𡉈RH⅌!rp棠 F4`Šli`2#Ь1G*+$G˟a! Wt2} 'K"8I0Ċ2n8ݑ'v@˖XʜYТ .mɓ6V>] aKO,0e~s;ƛfC ɔl3s4o$aCi=)ߥNFH՛XVزѪAt2'<~fb% G`-fAA׃t6k!gkKID[m!Fr$Rd*s8wa]SnPDu7w7DWUAWŷY0 [%~$G^H?!H 6vAXEX!?~ZWNឨ9[B)mR 8]J!uLh/1k]|x3{W\)m!,"ynփܘp3BOp>)}_4?+徥@ `إ0 M:i^V4}jjT]xm<6[b]Z!s=э \C-)!R;")%o5s^#BP8GЉn]I 0Ɲ6 rzjC ;96BܣG-$ CafÑ92N?"ḧZȤ&yG5QucJ+9pztDZIZZ.(A6ZPw¬1kX1a4ǵ?Ӌ&I: ґ k)֜c f$9q}mxi8,K@QeޓӘs4Z$% #Έ*vB|/1OLK[_6mc k#}VvӀ>"f8U̎vߙ0m{%̑9|GmR=zc\0@/8z92^ɼ70t~}Fe!+gꇢz֋S"f.Q잒]Dcߩ_A%\Du[1 mXY`^yG_Xxݍ1UI5@_^O`[b^n؏D؊1 ^8A`A._:a A^$^tA&5噠bS  VHaUXmF  raw ! c VJaRrYaX`!iap%!^JR_m0^fK`o_a}!R  "zW,b",IcP%f&rjW*q)b*j`+r+Zߥ`?ܕxK_q^/a`f #!cm*!3V@4J#525c@!,"*HPZتS"2@qIix+GtBrɋ \ɲʃ\(a"X1OL@7񣕣ZϪ6gI'EWXZ㦯85Al֬~֖_pUɝ;7ݻ6mF*Kgo-ƶydzUeJJ9N6mm(Q)M%Nx1Fdj\2”y;I(ѮVZ:S ʡ"aJfQk饘jZے DA9dwO*_O?Y Z{ _5,Weu~|VI`Ӧ`Y ^ aJڙdžvه*'dF "cM?KsBY, 09hNYql"Htl3zƜX4k3΅ "%mtݕ*JK5GV ֝jb4Tmީ0{]N`o(Utc{z&w~-ij%Ua]=6d73*ّOANG{[n"j[o]=p馫hRu"z^$ v#r;w: %xCX<=/ U{_A%Nh;Y̷P]XoBYhNg͍"BW&ӺP7/RAb΍#;(8:ŎxԋxDK=D\#II d%r3: Oʉ"P?2;ʢ+[- ,#.#KN HpppYD'LPIzҌ!5gxMEI_7Ip9ϙIvt@xt=y|2EK?HKv=cA/rPW &C$3hEp7y:haf"Ô;<$]:42R3-':oJX,)^HbPdoSQX$(X6nVNhGBKnbc/"Y:B%4dYކ 2:a7(YʲMn4)'"iZ$8HT=@5 JMD1{BM]BbSFH,[,;ʳhs4ZZϴqCjWZH-lWQGI%#o}$FBYq{"͕sA̗!n#k3qKw VzbWbY`88mirZɭ}J"IҶ0ZOc,枈-CusZnźvSx'1{dY%r-"*eR˖DUQeHlY]N5fw<|jx(us<\lJuqn>?Bc;RD{#f)]0K`,j/I.Ph|T?bm1 kT1jfnqy8잯5p:4jţ@YAj+fؒ -QZd:Vʝe)^j`|칦qjʢln:q1Y@@BtDڜ #itovcʙ @*E}`xjI+z-ɪ 4z!epU8ɧ b%FHؠL:Ux "z:P4v ɭhzy.jYzZyY*): ] Wz]ɯJP:J$>x+#j ]eZ6;0qBy !ɱ걐 Y#"%k'geGzZ)5{SUZq>@CkE|G+)*Lv[R?faVeqku#~Iwqxg ?U!"к[g(!vdGU!y#Q!`˱>;kL|Ḳ;Wi]ѹ{ KO+(F.2vYEq1Ļȁxɫ˻Pu&Hg[ҥ[޻y0 A [{ ߋۺK){ )a^Zȿ4$k >o4 lx` X0Rj,\Mh: X4U;?k B]E\IܽK|M}OCD)\¶{ 2qZCh|j= ĒyX |ۛ9\{$~cQlS ȧ/ȍŒ,&|R#~㉚ɹwpUP<ʤ|!Jb| İ,.AYԹ\DSj4̍Gle̅`E L\ͬ%Wί L@ˉY`]Y|(p̞k-TeE! ]Bв\UgS\ўu'r-<_KҬϛ |=/]"mW̉sӀ,ȴ|=b?W]`}SGo  ̼'TkVmX]k [m\0/g\ip٘C֎:!q}s]Tx zm)]+҃]ą؀$)4؎<,ЩъwBYܡMXܥ\C@ ˆZSrV.#] e1ܗ-2"P iq#{s ѝ:}Q*FUm}=&MX}1á8| Xm ~7BV8㡱%$">%$suℐbp,ȧ9P1^0n㴡X?=ٝ{عJ.'Mź4QS*Ur~[]^day0xVrj.Inpst.E\ ^z^E(t!HH2\!~;!.s{&b|d^b 'j.|m+߳x>ӽ뢡~Y¾q!79(AE^ږAQgynjm>~~?ubuZG.Q!ER(x#DC4"?v"K?/_2dL? R;.DmFŽ6Q#S_T@_ Zw41e"g?Y|$ Aw8%{Y$BF͝sNF@^O)ȭ ^#W@MX_W_j)^/"ʏ׿C+Jh/Ruّ_ `wChl$XA .dC%NX"5nG!?r$I)UdK1e6,Y͚3uOA[$ZPI.eԩ@Q>ZUWnWaVZXiծj֭IqΥٺy[nپ&\aĉ/fcȑ%O\e̙5ogСE&]iԩUfkرeϦ]mܹuo'^qɕ/gsѥO^uٵowŏ'_yկg{ϧ_}p@ 4@TpAtI#BI* 3tP ;T 3p< ELEIT]EmqGFsQCU"(eJ*I''RJ*G,cL2)Ps%mM8L$+괓2D5\8MigSf{aelN-kCk~:_˖lV;4o#| 0pGW\p#IةaqoW<r%-4߼H/7tG'!VuIXaKvwfvrq'@wG=rUu7,6g~QCz3ͯ|~{9=pGK>~Y6._'xJ1|k bxbA/ ;Apx# Ѐ,,C rÑd6 Juh@ A"U:"EXETqBcT.D8;C0LX-E/v1c$ctF4e#Gw D./\`d#6DRdd%%)I5(AŇ=W*.C< G7CzP G^2.3It ( @JS PXyW#LnGB9%%˳p#䡊)Jd2ꔨl&IAJ3 Ԥ5mv g?;̇h$Hyu汝 |'E iʲ[=sK}ӟCjN^҂V.G5ԡ<%ZOҢug753$@TБ'SJU*WdX#TJ*/z՛"y"JUlZAFRh)J;Zlڛdl*#jUUUtի"XVkUiEZuV@VRT\:{]D_zSfD lakpXj͕ZVN\A2S\6cj$Y:eWIӞRm5ƾ-MVgG#-TyW•gy1vMjG.9M%uŠrœZ65/y#d^"״WsͅFmeB[/dXÚiD-HW8+CYOPmjSf\njZLlr]Bw-tI&a]0I4aFuÒ=FF *f)c0KpUPa=.ݏ!?GS4#O0ᒁ86r;Wo+NF0xC*sά4^lnKSF$U>0\j)ʭ!4eFx]~#IҲ vw1pq^E] RKԕ@ueծuZc^S4 C ŰMc{LقKE dr 9r5`g^ mny߮mDˍ(?9>{32\:Ωf25b!|f~;Ԅ)vO|M2.cD&aco-,ev׻F;Ӯ6d:DBOAOͩs#=wA>t=GS!tw|ԫ4aZWn|Qa؃nE4j9}}r՞OO6ߗ5T< pP< 7 v+ 4/(!y4}JewqB{Uz>>}@bk$lY%2sg>!9$ B461/rD13ԧ^$z])$;A. B4ѧm:$\BiKx(d*+̜,d-D/\0 C2?&] HDz)DEF3sBtnFM)NDsTtu G4 QGQy̿3|GG)À<(ȃ,F|)ATCtƈƉitƂlH%LD‘N$OBNGwbɋt$=YŚtE3XYG ԥ;_DCR-RiQTF͔BPM|KdMVG-IJ=01P!N]4O+Rէ DTedէtUENUZe>eN\UQ%UV4TDTRR8dEef-ghVzpVVZVIO5pmnWTUܵSv&rρ~fd) 4PJAWY=XdeY}+)ehZ*IVzR=OM,:m%=bեF{1 Me4~!\>PYT}:}Yշe]םE< Z]r11u)ڗbZ.ZnKگװ-[c:꩐@t[L[[[8ۼǣYdY~EܓX\m \hiZexZdZ؝ZZ۵"HݯT=:E0z]ڝS]J!W^ ^&m5A5SEMI`.)^"_I2_}E_U_%խ i]z Oz1 ݵuΙMN+Xhz`͗ u3`^I\6aU]j_Y]Iaz }XZ,ȱ\"&Ԋ!VY'~p^x^*+n,˕a5/N(2&_3&]4Na5ti_ם%9a:N_ !`C.Y^-^FŴ&&E ڬhf\\bʵdŭdscO] e5Y-݊:e8~We;e$FD6\v`hMH^fJ!fNh&r΁6i:4^a!xfve٭c;_\6]F ja6bbF&ܢ6Oz1}Z&Z%Țh4@l^->XrehdgKj>iffV鶆巶ߤ뻞N o^P.a>``6::lcJlTZlgj^nV--6jg9INNi6evi7i>egggfV.=nCL[CAnRGaplfȮEq[hFUͦ\E@o^oVgbg6~iU8mz{i Kc#lڬK$$gٷrZ]#,Mɵ^e$@ LBD[Jf~)."m*s+J?J܂ [=_Ar$ýkȉKF3L&q[8BMەT ?3)&I %!$"$#GB?W|ardpBǢ()^+_7Ժ&} ss7Ǭ%5Z6_BsS.sD<>s@t%YBF_8tPɉzt5IJOKDZLO< H*V8uYrcVWXoY:r5r#/o^[&d?bωtgsJZss/s>?klVv,Gtp=FIrsWԡp%Lg"OG$z*]w}~+_WKWRgv'cO_v(x2_ڑv=?@wmJo?pUƹyw'Vm%Qt&w.z:=$T}rW7fxzZ{IZrizǫOρgp y{{ltm甗y&9yFt([z|YSXY@ |ƒr}wyz}inuO߱*h0 'MB&⯌6#^*jӦY*g=zDf&Yf9X'G)FVJ[)fLLFt*֋KXU ǒ-1ڑ^Lm-—&Е ޼t)`Y (l/:sjt[/=DL٭;yr U2Оc^.&bTjFmܡ#7ի=e\ +ת5qE5S6*EI$ʕ,]*:UdП^[teeG6]HnF]0kX㩵B뵼9hdWvڊ"(XmrE]vݡħꊄǚ12ɸaJ.,ͨw!JhCӎ--[ݸc..>]|m^a׀w6yḲ2 4 y$z!C>А:qSICpԉJ z&F,{{Dx 1+nd^v&(FͩVFe(~T!)H@)e8$"hĘ |B2$>Qȼ~-#YaAZ(Jp5 q"JE":FU/A-rы`iX3jdck48pUB iHD#!CIc &E'M-Agv2mR a)KҖM.K)*Vf 䣚 2LgT)Xk 1kڠTj5ROt UDQFr_M}eiJ>/YVU|5aX_Ul~ [VՉ(rבP<)RƕV$fۇľѦmqCKZNVagTm4͊&l)?ev--_NԪd,"*ҡKm]֊=\Qc7 LvW*׍unq ]m"\M+`nwudeetُRѫ|fx7/|OHKkw 1Yi_R -.^0-Q8C+^ΫVԼb;Xvli=H/,q.Ԩ} y'F7r,CON1 tȋ3_0!YJVU !QθU+Be"ط/|28,haZYpB %ֳw87&d!bhH!zlogcadž,d7Q'Ӛ6`:=O(ԣu]THŪ֣̚sn]'̿pbW7\q=hj8{FG[ٶŶmԸ q!}nI{&)`܍My4d}|SL5V-6u5O ~p[C5^k<6\qZ~m<Wm^)y={O&6~Mns;zso@-jSK"FL7<,rwo0bBxOh9u@^$ryj+/O>H^BY9IGDٝ]" -J]5X<ߚ\% F.PA y_D N؎g,R gTUk-KKlhMHH ЃN`zLYj<_ ½{, IULo4a-ydR  & 9 E BE "B*>J&S 6]^ބ|Kձ̍̈́ ^n#/x!@S: Q"J M!R x"D#:#.~$b`%)D^&&v}"Z8!,)C*&*nD+n+-Ԣ/ܢG@..0>\06Bf1""!2&^~E,) ^|]6Bb77A8 8Y$$W:&;bEHɣB#>Z*N$+K,ds|oO6W}18$DnDcEE:2nd} R*%a@STTVYT%bVIAl%WD6beNY%JXe F%d! I$^V^ _2Q% fN2ZUdb#vأ>*Ӵ!XA#p,>%-FE$/<_,dC)&DvEk9YlzG!&ڌi$o6o$ppq݀]$,_%LPgOvrgRxg'sB&yy Ezz~ev!||}XTeP` >! 9^t炎Mу"[:\e N^zJM_((u&;(^gUhwJЌ2Bxhy"jSA)a1s:bo̔D2JV%`h$E˘JhRƩ"ivu)>gaarahe=F!jDf?&A"$#f*|*Ljy*ZjHª!ZLp* gpƇJbM᮪cjc bPk&Jff $M~bͶލk.ψkY몊)0i&i^j(.j+O+Mf`*iy,c k.R&`< BN}V*5iv,lepl^|qS^ǖT8ʪk˺˞Ek]dͺiEbltlkuc"棲?6Rjf|r-JmRk^ڣn 2 ؒF+=!,%*HRYЅC HE:9hÑG CJ)d(S<&0ha<8sB9gG*hȐXSfP'jрUBhQWv'YAȖǗpUȝ ^! wN(Ӊ?#ǰj !Oh^ʁBB4eRpװȞsݝb0  @sA Y0l0D/fёJ 0Ν;r@7y3'N=DN;TnI-ԡzU\X~uZAU\q%W]w _}՚obWdMfe yfhFiVZklƃo[o'[Н@)2t-Voiw'}R}4ey<PD5X|炇RQUeXUցi)o9 LNH^8X!bO} JFcdY+pbh.Xacm<8@%$pwd-xO2qP啃eiex`Xy{iƊRL5!ӤŹߜeUWx>Ӟgi~! Xyځf9ホB*)(beA], 7 `j`oP&M:ܯSRgu"tl[.+i Az鞚G39 0f"ֺ nKoXoaRp\" 'r) ;<*d`|i,r$$2Hn-}xbzg{kbKi -twtJtFԂRzb`[wor9mffBrp<qULG:B%-d!# JGVQ/ޝ&S^Zim Ҿɹ U4\VKyV׺1]*T2#υN`e;4u䭍KC;Lt\䇓TNԗf~hF%@^F fIb@\&Bhx(f!(v7TdFJnz8/CXF\kdĚ)ah|$p:Zqc& B@ӆ̇";IΒ`]'CYdww YBzK.#O)Xf+i^s|g>t=¸ɰh6z OD;w8]M yLkuA=IQ/=urR݂UWWNg呾W.}{r 4|l$۴)v{Rg9ްmw;79NE\fț` ztkН}'~_=-^F8o;[CE@Ǯ'M|6 j-%+w J_N q|B?'L8quKy?=WAżp278'g?!;ܫ)v4v!@rk$=w&n'?I #GN֨A:WS^˴qN~NծQ;\}9_|''|(|8$A|1g|3s&d|\tGtxop}tu&uFuPauuPug^:8p'm[qwh{f@~n5|1|WxdjRjWKeUD5~(X~UyWz4W?q9mkC%{tgwȄ'2n4׀ʗs+#Y}F]8cշYЉ0}'pg~ih䧂Okr(Gtxla'y|hjr:+"8}HsHȀ瀂wGZxx30aocX?hx"jySJK7XqTrvS:؋QLjQx،)'XH,(8]؍U$H X^Xpu옂(=pHqxc'E?ax ( IbHr1:F|)'2$t7j)_8) Fl E(v%$]|XSEP1+6`)`DxYS1ROXe2)JU)W Yi[9]Y<_I+_1KVqj~l`np)Aer$z}]O!B1ygY1di9du љy*0Xpx`ӚI9]IyH9EIq1~ x)# gՉ*4ى*^'5㹃 (蹢[ i09WuAm?i&RsYwY,yˉqI:@7tcTj#*jC/H|,(2@/2J*^@;`yAiÉɠKM*@ )TV*J[J)#aEg ijz' pa;s*HiʂyѧB:j ZL :R8rU`)bA2'PM \:p^tZ }iA 6=q>C|٨^ì2!CC#:#[ء6)*XizLxЮC6:d֣_  ^AK= = S)Kd`B:H#C1 <";$ˌ&(ɲ3z5j/368;|1г=H&;ũIK˜ѬP[RK'l-#]6_;[akxcK`mJoBr :m[oMpO=AѯAڷZyɴ#*S*/WHɄõB+L# (hY۪˶)o;Xqa:<{>@+4AkËūǫڴK;+LEH*hZ½`뽦 ,*y+[y4*]+[;{;Kb*L U5 C)bS0Bi;PQZ)C+Vl"[#ht` FQ?VAy) bysJ5'/v<[c0|CLH/bfIb40ɔ,cjc+ňAN`Ť]l_ =$BƇrikH1 vI(`DŽx,t|~bLr<HG͚\ ʢŧ`+|ge ˱h@,: XrtHZʟQlP{82V .(*]4C0a-Lg@֌b9]ӌ+k EnH~6WY\\a(c4C6^;ӆ $u.wPN^NمN*n^CGq)<0gnFYV8:#^>!%B?BFjJEsF=S"xX]_a@eOg(i((tsxY].#ԃ3,B젻o/= 4RxjrcسBٙʟ/qDO(O7Md֣##0?oi"(R  J`C%N(QEb̨1A!+$YI)UdK1eVDP9G0a API,eдSNP*Y=lav [Y ,†]r1]vC_&lfN<{8ѣI.ДԨSZ+׮_[,ڴjQmsūwo¹u۷K ^-ɕatPB .q: /O S@OJ tSN:ԣTCaQu?H{s=3-ҋ\tVZk%SBU-T.Uk%okX[UvY\QE=lv/`@ez6Zզ51u7[m)ELcϜw\|YEw uwu`[X\}vx "BQ_b3`aC׫Xc1[v;9Wesۙi84dW2d;PA  A0JhBNP+,] _f4 5^!pD$ސ C"j΄*@bAʼnNTNMGT-6#"&12As@x4amt S8Vqtc5!|@"qr,!D*lX YIP:A$ZpJTR2AJc JX .$< O@/}ʥ+e)8d%.u)R&1yL$3[3ChJ@5RLckּKpR4Pilj!.pJf,ՉLذsOS P@gE %dTA'6ש̅24C'JQ (GQ2NkKjғ *U%K[ T-)GjӛT;%9}^SfQV5ܔ Jej/Ԩt "UUr_iX9VՔլ"(!:G 5{קNA {XlrE\JZCl#纛r̫׿5lakl?h,J;J#esY'l ^C+Z<-jXJ%`l9[ ֶny a5iS ն$`d;qC>g {f+e yI@٢]`Kz6e)}y_WɽJB45L L'Xn[NlFD 03~sq ]J~)ˌGLb((֨5 dƠgtX7rOL%Hvd(Gyʟuh ?&1_q8dVj cLNrD'͜D8rs\<-|V)X zЅehtяKQ2io]}iM4kojz.5NE[eՍ4lnq$Jc%j#3P.wfwh$lv`i>emn{-6MrsrEiF*&ojM'Kp Á I|şq}k-/'BMG |]6AӝrU<;2&qf7.,7q!<:Zh 殤C2Rca==6Z8BPAii Y4ca&8νsn)L?BSDOz`m܏L5}s}>)1,Im}%_{[_U٘gs[~y)?:K+)>U>+6xE @ f륟k2?B?Pcs?S%[ @Vۿ۹#A0BR`A( AʨAl751Z,C1,ؠG8 'x C9,AD;<)Z-*ʃ"C<Ķ"*1h*+|? /#1C)È9ICZCkkCiz9CDK=Ԃ>\?Ē 8DD$E@{zFG$HL(J4 K/0D2C@?RCQ$E_2ETYEJ4W>/?'ZbD\]4DE*`tDDqIFRBmhLiOB[@7|À2opWŒ(+dulG"xG`(F}BǀȂTyl 묇Hr\,s*,vHIG 4ƪBƽ2lɗ NOI2,7ɼVHqş,CT:EXJaGbGtDtmJ`2J iR%ԀpĎqHqH2J"K5˻$Sy˦˧ɢO*d_\&BFCVDfbj`<|=^HۥA cKL`%_0[XSRNaۭ.S`_"C;Vcj)#ccЭ]~&@daWCD;ENf'^f(nf)^, yhIf(K&lmd %[pvq.a=&1u`vw0xcye1Ͻ^g]g&b;"d&f6fF)P#`|vfN9#R!&10i0ia-iPEVc [ h闾Ozg|gP%a+~m*(BUHPQc(尊 Ɋl:K~SdA`JȬ0 (m&CKʹ.θuD>HV^:f&n$,nn>NlN &Vfo }'fioom m&6DՎA֖;vmm\mNV 3n'p5nVvtMp/vrts%uhpwrxU23X u,dGCd7=_~*?@OAo!Q=FxqmPu$tZUx,Q{s>ܶ3v>w|o>xEk7D?/?CoBŌ̧"0{\D{PW/{(4O{S? ԥpou3pwY7E7şWz|"=vWl 'Oֱn8{v"ׇtq/OLb𠉄 0l!|&"7cFj` bƑGЬ7'РB\c(J0mz)ԨRj!E`֬„ m[׉|RQzbީO0nH#6l۶aWغW:G-] a i5&l M'vD(2íq)Zx1x 5ʖ/eάEN /x*]ڔiԨg`d :jcQYȶeK\uzk1!~`F!DcAdeVYyK&ioZX![liVE[EoXAr1ws(@H(vm'Md؄NP驷^{I_|OW}Z}V#b,.he)<zVhafzT"W"c'z9r,8܈c;@&ې<(M$RR gft]ZJbDwXNlZj;bTDuf5{n±d#i_ v6n0Ru_-/jevU+H<1"5ts2 jkk0|I6@rr_DgtGH34J6]8߅WWϠPuMg6kgmos,.7_ݛUk_~x/X;#/H>Ōr9IszT2 gBXNԹ,f^#gC20M/GcL4!p((oysUmzԳٰ=u`Q6>I|3J_7~ fD\eqkh1rH4G)  n-VtD-Ⱥ vuehp#%d Bs 48tCUzפQqPA]諗> @pɢw.q #(L=hl7Ƒ5m#PP:1A?n&B!w3*/Z! g"h ['= D<,.Mnl+(N,c@X\_a k(IYe>fV (M--dIY7!OYr~=K(UT2L`-ˌ9qa`ہYsΌ4VR "KӒ*uԄ[\)x;\9c.e|#9w cHK*4d^MǗì}3Y9L!_Ywb\-Xgg :^k Vÿ5Շk7(Ts=Kmyi;c9$6n5Žc)z#S#ӌwdQ[ywn_g=zvDFkҥ)p!t0jxXY)n49)vc;yOC7EUtk .!|;o1Y+!=cM2ѫ|R? vǧx-rMQޔ'7 ‰^d? $%ol7) dӻw3DŃ{w#y!Y4y˸oOd  eYuݞd:v` )]=EYLY@i_p߻xπ鍟}^_%^J4x F qE O BZ>ŞGڞn`rGRZ`ehpᱠع`NȠII݁젫|e r1 : -u!~>A^ad![l_  `~_H ơ a!E* ˆ !z!"LLȆ#$EUb^b!'>6v]5!*r+&!&-B!b"- 0@16@!3j3:@1E\chc"ઔ"8*D99F:*:%Z&c'ˢd>$E" -.*1$0>EdVD. ` 3fF[SDHHjI*@75hM $LJ LFM%d;`IQO#Q>rG*RRY!@Ƣ@e""BN~DWBDŸPEXvXaY:ӡeԩ%S5ep\"̅Wp8]SF^_&DLf4 djdb!T0cE(Z@)^6e*f"@T-~AiejkD lfl 22&b%H%e`%x9Zr*g03:gSFte [\dganavg-&rxP:%v)g-ڧf+ggg:Mhh@ii/gȀK !EZE2h:(jV \vr䈒hT% r_h(e(=Bf(>R&{D-eSS)4*.VA:)gLYTigHlNlaaFi Ee!&'r,J6"f@Ʈi)gBi}Li(fȄ]\+"dhe2{'|2Jj@h~+UUᓞYޔ1"薢^fIeHԪjꪇNdetFűVg$h'jpzSH+bjexbj繦hgjjx*BEk̫Vdz)j ېHċ7y+ލh>l^K2XfƸQAuQWڮYM A -^-ߎHUNGxbApIDmL(4(eN_j^e͢uMZ .m9-ȋRmXnB-Uڮڶ-խLm -.Lm pm.q I.6eNV.\Ud۲>H~hB!Rr$6m+.]SrqIf6_Ƅ^2o n"IL/wT/b@+~ڈ֠ NE //Z/5j..ϮkD.Ym-$oۭs~pELp ~m { p/ǘoU,J6L qq} q p( D&pEon1_mFFሸkf"^oau 2GF0~԰:16 ǯ!n@|6r\xFL(7+@$3$;q%C^\drx Kt~2(G0 N22[g*I++GiӲ3xOr 0/ sqE 38ˬ 8GN,F }*H>H@?gʈشIL@1A-_TR04^PD3W1UH203kG44IIk644\K2% QpGd53u]OrP#3=5ǰȢ,uSkbdTψVue5>s5xu)05Wu^tDdi \C\#*"6@뾮_3`Uc a#:/5b P3)dRR-o\657R3K-ޭ0L'm9ĉ+8fiG{sD[PrL`=7[T6v3;f@wA#`;hwW7P0[xZ70l}E~C~K󏏴 #+!y39MTO;7cCχQwyuog8xMwWxXv˷k99l !!,%* @)\C#ΚHрŋ-QǏCIdH(SZ˗ޘI"E>8sӋϟ Eh̆*Ĵ酧PsIjUX7i%µk`۩fhZ֪U4OɝKxV߿G9w.È(^l|">dd/ϠC4`#VCnR%˕n|c5:*4hn*͛*DZ2кW_ǒ5[65Dy3;yҫ='04d}˟Wi|WJ{pN SqMxrIsOtYvigwyg[Xy橷Q_|1 IEjWАL6&] ^$d=(@G!M^Qb$^0cU(n"!h w#L>a9AM%**iCQMjIwfLv |z:gb#溪oܟ|j`JơJNh.۟MgydZkB[2jCv!jy&$Κљx:yUl򧬳4Kp6X O{[[&yBӿ>YҞ.Kj {eijeŊcq_좩$: ڱp=/ΩIyY;!My2MCxjGjx1bE"f[.ۧcݺ7bMY>?{J~8/R;^ Wg19{ ą΍sC[Fҽ f #{ux;Ƿ}+r7 Rsfÿ f@!c9N=[Y8"⺻L uP,d/3/8\(A:F3H>K $IHdH"H$HΤ*$[RF>2l3FIM8H*QTF.e$9AZ:3Zֲ5C0@b+ Lf%7f:S$}l{G9ʥv\ f6G`ɏkd% YCR5 JHhd&5iJFs.<(I9JS҂*_yXbDP\jE!% ` 34fJ>i_I4rؤon*g sİg$w3Ĥ?uvCM+S)PWҠ%CP^QE/QbaX&HǪ85Sm~|cSd4T+;)I/I NʞRTFTMXMhkD9|b%heVt)U6MQ*MJu<~'`"X"p+X,},#@Dmf}Y h;).d!SE^ OHڅ!"?Fh!A_c$1AYunBv^yÞ∊t̡U;t2)dHԓ\oqQ%#C0߭]__(y'r/,No'3_{<험cE{||KQ׀|}n[xx^x}gyryW~z~~MqkDA%{7~8!|^ Hq vGGXw ߧs#X&GK{z1{3h5I҃ܶMx%LIGoxU}!~y痂dzj8O4bnx̑rX(t(qC6{$}x?J8PhnQ S}[Ԓx "8W'-g(X:r6xU*"2h],`DhFb' hVv=eRh}"q6Ee epWXzTKz4Irǖa?98x1tFE,8fhq( TfFh( .W74Y\~Lyz 9H iguQk"2qX瑑 ӑC!qWux/ 'GQ6JP:SiIKiB) YAuHܨa>rQT8VIX7%9O'IPa9շ P.63j7)U9i1Pxi2|yQ~?1 ЍO)jTxMcȘ\xd9/YKɓrt v Cy1깞eiRɛW&eFYh) ϙ+ѹі=``~qC )DYɞx'2DihFH/ .:;9zUY7~p8}HG70DzWUpdF+8q)A$q"|*cŢ.颡*gdD58&io{g]DZ8u̹%GO.((cĥ]^* `*_Ga57x=H1Cj~zJ&ᧃt(@.t]R1o&6*j"=GzQZ ZzeLLZNzJS<*Z:3r\gqsvspj]úx h-QzsaKխߚzZpz ڮ:z3DS` ̦A66o%23 JBM{GTMBL-"S]^ͥW]I\_ց4!Zf j]׮:Ry֩l|~׀؂=؄]؆}؈؊،؎ْؐ=ٔ]ٖ}ٜ٘ٚٞ٠ڢ=ڤ]ڦ}ڨڪڬڮڰ۲=۴]۶}۸ۺۼ۾=]}ȝʽ=]}؝ڽ=]}V5I;]+qx ^pޭya)?<NgHbF>>~ ktn!>5V&(@^wu4x46S8:R3۫o^ 7u\q~HJM{ ]!;.=^I[K]SbndB)i^km~ve Q_xԺoqONnNΘ<~q\_Io:Z閎G鞞ON`Ɩ >zhU벮>.zY WypGV,&ξо}Nޙ.jnKMAALޝ^~^e< sA7LCBS o&/qW /"OđEw+/1a`%j^] ZKYѺ+DOFHJGLEןH}d \\^oy0Pyp;LhMpFrKtOv]__|@jOuӅ ЕG/I_kkOqc{ .p6v/POo5LG$,tlPb9 @ $Xp/ {-dHF%XE4nDc!W$YdUdK1eΤYM9u)IR0#hO-e:SBQCNIUY$W/*d!D+Zxq#G YdJy_} &cFs!ͣҧPέzժV] r,²fJvnF+jݑY]mܹ:ѤC<$*a/9歚pLPuƴok4ҧQ^ͺ\ثd{.;1‡7>9rk:L%N+RcPAo|CqD84= !,*CjqlN{QTԎ"#$o5+EtR2NRQZѡz1e-uܠGA R!{ך*#QʯaDN9\[ћ;SWd\Wd>7{q!n+5jРFzxbr *qo8Un>qwS ,h"/y= ]ݲ=u5[:jK>$: $&Kd\:Slh"݅Jiї 1qc\s4B9B8CʠcMA_]עȓ DIc%:Q9PJoGY2Lf`h1𝱐Xc\8αtc3@I 4cD"HZ&-&1&1΁g .KS!c1VIRk2`Ė.yD<$&1c;31gvrќ&DYkv$Gf7pS" :Iuqh/y^ԞtOe^ҟE4?.=HZBUFԕye9MvN\&F;GY-s*PGZE|b3(M2W$TdLGIӚNvAL**JMH*ҧNQ%FDwB)& (M&ii Kfb`BE۪;%-\ڧ& )=:E]V|h{UVv]bUǢ)udPYV^6gf;ֶ-iGkBSKS_ֈk;XBD3Ǟm4.rQ,}Hzn~u_ח'GxFNxcK[&Ϫ#<v-b \ lf}SI*WԸMl| v7cZ36Oyl7'{XV iֲu+,~!#{|6*-%e+ٴq&Ĉ/־d_m$r۹xsqE"'4|'KTҾU[(_ np7"\$ W9M#1>ӂ8ȍnosM"g Qk6Caܛ\΁Nҳs3o~;4C.L G jD,&nG$uug{K3C˟=bL' {O=<jbg~z,'ԇ}co}_D[FؽU꽏ûbӻ[>ۿcxԈ`+kn>>>:SC˺ۺD@;CH98\@#;,9 A@`@@Gu㩧>> A;A:JAC:##dtB@ Ai{ t; ŽXCa? Wi@HkB'9P#AaBƫ>;qk.D7/t 0\&ҾK?@?Sl74C6RD;P; ?@"4L@B:T BEIlJ|uD[%DqDK$PQR|(d8GtD%REso: s?Z\cD\hd^_`Fe(BAtCD[GHgD i$j4Dl4 ,4Fk ǩ(oBtTԾvd[%xc9:?E÷{!HłcDE |HWsBfiHCm\NdоpɐU"sTG"`GVtG̞xy#XH6G HȅD#D\dT$hJ\IcJԄK :2Kd@KLVTISlI7A8JŴI˼|J'fKL$A,ɢDͣͥ t *\ <L #4G4M\K|GtIyDdlDd|βdS؈P (?7ZT[P- :q uSEO[xX,`KDJA6%Pǎ?ѢT9Q(cUT|cT7sTXIuJ]K}L]'5(:ҐR UV1-˷xW1l= .PJnUU޹%]^UAkCM_MNd(T lVzLi e1ֈ $e%5DMNORP%uvuwWx׹V#2W9WXNX2T=XKQ6SeRSb`cp֧=hGX{ِOKURLZ q-OKuv=PwYُ4Y]26X~ͼ]M< CXIؿ7Q>ڌUDjk Y֊VT(T)5W@Wk['yZ{Y 2RWY7a -%[ܦ+\"8\Mܥ 5֫E@Ü]VB%eE֬Kj [ ,=L*٪<ٔ]sվ=ߢ%Uٕ۹ݩ"u[Gx55އB^E@C%UӐX VAiܛ_U5[-[WW%W:R`]t+B-_[Yߐ_`6Z{;d;E\`m}և; nVCuZDXF__a.r4E%BFU][fabb!b`EuSb\mbg(Q}bfbbQX,XIb`@`12f>c-X q7Օcu㺵]>Vߝ"^#= P[%ޢub_b6 d+d]gNWO P RsCP`ecR9&8:&C;e[ֶH!d#danSbXc&DENF^fl"f^K,-~EX0W{h.hFiJ)8i^=Xni~byi&iX^i j8ɊXhij~jf% jHkFiinfkih k ix@>xkFVkjviJj6Vi~騶ijj6jfj~ꨖjǎk.kNk^k럞kkf Fnm~j ^~nՆ)l=v˾ii&jj>6FԾfm~mjm۾nVNovnn<8n:H>o wދn.nn2lppmqpn*xo nnijNm&jjikwp? Orpqnpˮi6.q ?q&^oҞqpq>k^FFr%p& 'Wl(꟞rq,q-'6@qs:1/345ootrrF#ABC_lOqקM'Gu6u/uS?TGk&k7buV~oXvf GF[]nh_ǹ`r~b?c.t>MwtVt%k?xxpno#ojpw.&ju'j<y_4vpw wj`'jaibic/oJ_ vvDwioM?wj7uw&oPV3yyydoneꗟp'7sxns 7='isMOwjy?v[c|oŋoQȘtTז0cҬyfΝ 8)?"ajT-[q3ҭ{Ľx1E?܍8dLėOtr%+ϼ9<iQk-FzyN䡫f:Xkk}׮ƛp*5o?S <"~ ɟs#m0K"PP#a10ycRh`1sX1!g2 %2x+>1G8DTHŀ08!Ez#QD$M%-Jts(bE,j1" a'F2ƌgt&@fd ÉDDE&4`DFw;.RcD8?tU.#GQ$5bI`2"'I!)pa4R FdVJq9T< -Sh:f%ҨFa 䒹1Lf"H4yk)+wZ&zl#A`$;3IytCIJ}"'Jھ5V0㌝s*T=d' BxNt"t)DɐshG9D$)NUrR:FX`)$)Ɇbd)&DiN&!:U=5ϟd*6Yh! ,"*H@e(DC#j⛋F2jxG C.IIRJ-["PCKr60\hH(d~Hy! =yVZA=xAFe];[kcȝ[ݹsu_ I ֣g !+X̔;7z(Chތ) I.8IiPbXZӰc˞mp&Ն[7tH\S^0璝YbvAQC6 X 6غgs]xcoë%zVM$`X)PcEe8lfyYS*܇ (∥uoap'q%\t767M餛vuxAEyWB5VYG}ovU XAa!؆"@Ȑd^yDx橧4hn&蛋F("ڇ9ԨL'򈓏xSPz=-ٞAYGe[V҅eYZ¬-t9 I˜ `BEQh!HIgvК'*f%o1MaCaR٩I{M$YQꪬ~\j򅫮+jfnbag촞资zŶz'ۧhiF *pZ^Ǡj5/.8 ߩ6kWZLk}+ +xfŎ 0EWk-v[mO 2r05wtG#rEmԑ8+Y 5tSڇJ4N#L 2lÔp,Gq`ލ+RԢڅ]pD;Oz?]씻7}[۳?/޸ -y Nl.G`:7]ٻ>("hO;GëT`mpEܻ3k{zX>|f9QftB6pu{􇭵rػ: x-S`P@ .*|]L z^fY\wAnDzSʧ +}S_ #ӭkŖZ< XN~zY @7sEBIP>TQsQ@ <*,1#AIwՔJ-Kӟ̔7ũS1t;A6YVT;ejS9Ԩ"`U#<_ʐ*^!h ӚP+xQε :r.-T |F"kӜRV3>P3v6GR 5ZȴjWˇֺv(~CD[VoKtYYPLm[=m\bƹlI;VnEhj<eŋYzż+REC_>ظ~,wUn۵+h `s^j.bAX hW#&F1*{b&Vr1v;iuފA* #%0Z V>Rf|Q,]RtÂk]bf1df6 IOaLg;ͱ.c+it (G .\&-'\t,݁:?1X^S QFS~`-8{Gεm}ִY|Wߤv4iLSZ–2zr4CG' Udd$3k"jETrm3ȹ\H\ 'ks{Vd7F4yx'g@S˴"#@rr|h9g_^ʜ5{Jpsn|?]E-,}wMo8,teşn 䄳dtO|xqi6iQږuwyuWn'vT'wwWgo'wwD_q0ay@hfb6X1hW@(HFrN"l)P)ܥFwTTOb8Wb(z7_\nh\5wr-o/g091479<xxAhW0!NKMqShWbՅ4"qPaHxw!OU1wz|8XS:X(XuqphhXYZX\8Z^(hXHX#{hXb$(=CA8PQ0VLU،71WxVh%2x#UȎ%W.0hQx_TȌx VMT'P::,(9|$xH҇H&Xy b$) hI(&(H(&3ىH#7<@BIv- H86QSf,WIY[Y]y _NoPe)6i I$pɓr)#AYswYh5xjCӗ~ x/jZRFp\(&)'>)A/܅o9wm>ɜtIYxך~hɑ(ȕe8)@h'ؙn9ui!•INO) jDbyr,2+5pj1 UisgyoU8)1hڝFn& JSJ 'e١i7E B<ɢ)ʟ&:^*2r4E爣ܥ;ڣ?ZGz*#: %A;i#Y.l,QШ*B%0jz2a!$':)ꨎN\"'핆*6^ 6zJAN`>ѩ2:j:!ڒD tEŠS©䩟:*zM%B:ٺKگ2DN:c!*gEcQuغO+8   P %CԺ'zRxoױ<:˫#K约')+-0ޑ@*q,:[=۵cSj:R>zCG:Ip0T24k6>~6`;c[Fek(E4}иkr1˰gT{*[۷bۨd+{b⸏[F+#oIp;I%'UWK\+«';$Tº⺍ (@`;q;; >$F蛾FKK؛˹)R[F盾軾K{ڨ ,ѡITDEȒ,Qې2TtH%jZ\?!,E@%,1p"gu(,™!q69"?,")TDL#-|H1B҅=s٢;]8NjOiACO*a'YK_ugwf>>#2K!W"NoQk_]Rpr?,?._IKl{Oaj,5n?qOoĔ!@m\^=a Ʃ//_p:0|CprO^ zÖ2_3147oǟ/?7/.Q #,dC%vXؑG'D$)I)O`rK1e¤P3͎9uOA%ZtM.eS?A:UYlՠa7a2dYe{-ƌ\Q׮y^o aY ,ȑ+VZerf3U@ׯbǒpmZl۾v;߿'`q :v|?Owuٵv4WUe5رe?][sRuy ቏#Oƞ.: 4PTpN+RЃm=h=[a> | J4S8Mt.oqǜh7(J7F/RW}[m c's] sߘ w2bxT0I-3ɺmqi R /M֓\' w_>u7oEs_?dMp y f֡MKqЃh# gUɠ(FуHGBJtC"|`?2@P!dF 2p1(bMVKLaE)z!]-p~_a\?6p\ CpqY;%u9x"'2ScC*[Q@ 3P,i%dK/JQ{$)LAQeqMlr W$3GGE %$u)Icd&b@ 2 n &JlfOfZd_.wYFyd#&Kjɤ=9=ꓔќfckJAcE 5C'Q ;i.S"ACpZ ?Ǎ$I[-jW: U.g*HG@td@ WPZ6ǩO\TϯDi"jvvEX*Kᔕ %'Zϩ֣椭yGS+ *ʯ Tj`3XRbX>6eJVEvq>8C ZZܦV - B "/;@(I""c旙j`3$! YB l`*uEmH̐+DXpESK\@,=*0ҽuKb]` ڽw+NW$oyz޹XkmITȗo~KJ ʿ<X ^0]"@Xah8Vs bm$VݫƩ.wgqv8%@֍SM/Q2Te+cY[2/| h?p>Cl^Gb9fIAdg KMOķC} F;Kv\iK3ӴBN:J}TSoͼE=:fQw;8-z\hǸ0 BcU[2CT^0lkwL2̨Nsƭ$ߘ讠](w:޸M0|Dcs'p3\29/ܾ3 ); "DNr}^5u7. q3s|/=O$( 噴wd$N(Kd͉fN&_@ViZ kmGQAX h_&A]w([ |?+{gH|'}[.0|k0y &}zZx2}_PSKo̙}큾3v_+=?'`x8S<#飵ü<# T>7A=di;fb=qZ*jA;»{0 0??+<;<3\<,Ǔ2\@s.xI?L=[@kVgKDUڹ{7VJVW:N5O$+tŀu]@qTNWݠWWWrW2;}T4## XطiXZ1XhX2G֛T(T%Y0YW-W +}٘Y!xY0ŀVW=Yudy𖣍MqեaUXcSΪڑZT*hGp, XfXu e :[M׺m׻]ս[ UW­UÝ %mZu˽\;ܹ\] ]kێ-[m=[nM HڰHYٝ1RS]wżxyzYڵZI*tO22$Ls4GZ)8 .*3}3=>t2(aֳMj !,p* } Bqa}\yـыѸ$Nb /(54$NVQ0Zi'"$Z]m{ n N. `ka>Ƙ)VVnIF2.f ΊI ֚!N"b$^b hH#-(6bL .F.A6A f 6 6.<ֈZaiaG@AfB&CNd\XbX0KdMfd(tbRbSTUd/cL#㙤Zv45.6yc_c`cac&ec>Hf>^f?nfhff aln|#xpq Lr'Ng.^gQbiwgәgRݑVVWf12ge5e6e7e8,:,d!>f܊?ȁffXihVu&UvR,-iPgVbWcXjeFjVjfj.>ꆦjKj>Q6怳Vޏmno&pd&gQ0s.ѽST.4lFlNGI|l-^6lI"%0IcK;7=:m[hֆ+(0k qWbR"~bjop) Kô.j0Xn{gX^웜nqv `+2 f*o28o=No^oC*mLoNonٞi _oppp$*>p i jPl#L?jen9^ߊޟVfG& qX to+o8roppK&)0riRpU,i/0 A gq W% H h{FfI2G|sss0;<>o<0.q&d _m!t"#%3L%&'(w)?*+$\UCiu3iXsY/sZ [g\]W^gȝ_O9onHv fgji;vDdEFontRtJ' frQ'O (t)uwu+/uSzO|w@u uuxq,x:xHxYnxxxcxWvxxUv摗lNI/JLgkuyvy/yxoprVrW}23O @]j8 54խ/OX*h-Fw!Azq/鰪`yʯHخ֨TӅ J%s;/e•G*dԉ% ?W ѣtOk=ُ"R׸}]QIɷ|̿6_Wgǯ*la- !0lHa 'R(4c"9,$*cǘ5fN&N:sB&>LbT"CTRWP=c咬 YkLb= aVZ"X*ܸ J.Y e(lX!-S31`/7l6C@mТG4jQ>)ć1gwY +w=|yiH2ʗ_ @xqC#VƘ Pd\'Üm'Μ;{*(AIPS!@ VVpuW`u$y]jhs%Pw^}E`VXb 1c{q\Hqg#pjD{3n[p7qfs*=EBAwQFW^m)z-M{6LJN:dE}50 EU[R(SUuIY-W *XeS!]vEV|Xb+.֢eVhf=#=ZڏFC&lHz[nM^F ܔTVy%d4_^VbIPxo/Ig*~7Ij%:6R!Pha[!A_\X꫓Hc6ڊ)kڰkmK*l>k@=TrZePgTua'&Q]iwn "&޻5wd/~߾L୊( BڕD_M`LZ (W~"6l 댲L:r,ZwҶ=\fCd\QCdu5t PK'EPuHW 0|LFߧZjm>h~`[歍 FZ.@(fw.1ljo9mv9&wc+8$$o6}8 A9et*Y(0nuغ`wЮ*P. ]W Smdx0(@,y5yfK{pf| 㛦yCw0& ]82*ÀǣebG@Ё *ZC4t0\g KߥPs ah1wq}g6QoWƽ1|w_faH}jĠ?V%A#mh A?%4d[F BGe2|fG9X]ڎ9H9A5+D$:bEʰa#o˒l&{C 6ARL Qyl,r$PA-3C9[}/aLfΘg:33X$|fEMpNMwLW9yddHlgxI}n2'19(j8ʡf,+jYftV:ˏ&!+iSғ2Ё*]&K[ǗRD)kAm:7qGBqZ<'Q_dJe꽢*d[|O ̓E(ǩS"4c}QCωDT U+.[ҋL]2 }.c3"t26ef\pwJ]N8H f_rъOfiN B|ŅuI`(Y<5rGU%0O j(vRa'q/и qG?>dnv.la[6΍nЙ`.w7ey5{Kv~wJ̒'zHOf*| popuc7%>qͰ␴ hXvcx:cdئVsk}Yx-[7$xMfh W9Ω %\]go߹bjs &]`r0=a<[7gox8!x npQ=f}UկLŁ|!UZ?򑋦'OyYeY1E+:o|gq|t>[)L/ BKT?UؤK)e=9Oj~U]Yv> &ׄ'y_,gօff̻|/om7c.<ޗàNK?}iDL]Uݢ]]q])qeڇ y%%_A1ߍ՝E_] T]}_\0zA-O)̕ט_Iz\mPǓ陸^ҙB^^EwPZ[j ܧi`™5:_ IOL Z 1Г  NA|] zWHYe^%A,!r4cZ[Gy\ pgtc π\(>He@5!ů=`LupheLI4ZD$R JȈOYYM}EUMIȜo rΈ"q)HUI`hDl(zZ8(h\ V)((i:-䨎t  ))9EƓErrfl)> 阖)) j(-h)h$D(*SȨ(j2*~9jD.) )NXZ'~~(*F*;M*j*jj֪ilb⧊R)aZrh赦ƀrkeRjZ긒W$i,ir@,IR)*"ΤY:-*+ÆکJLQ^d,~l6lkt,*Ͳn"f+䩣 ђ)$nY> l)&=.lF,6 T,׶׆-c-ȚlBJjV\*֭`폴ޒjΚ* nоjᆩol2.jd rݸDa_gza-m@HDyF5 1nrZG@N ʭ¢b~oGc}GJ rT"2nfT@/*N V/]/ůluEAxR/B瑱kntBol /ovi/ /7Y&pr3 '˝7okh0' 'p \ wDZF2.Fzlc-mƥ "q(1@@W7@L//DO#ES@%_AOAcD cڲm"0N#1B+M%pR0Ip'J((23>*cpWK~NX_-v 2r-L0Es _2_Ӷ23;3GL116'6+D;13n^1(NyE0;{Ӟgl@!,"*HXȥC."J@qE3ZǏ =~d\ɲJ0c:Brg@9v Q*H2YNx XjPaW ŠٳٳZfmĶmpȝKԌˣ7/DX1(^,#K,2˘3 ,9@٤ӨS <5JX"^B7܍bΧ] oS&͚7<͉8Рw= zF#Wfի_mݶ0.7`…@s1GIFe5FkDPWTo fÕ@q0Dlis?ftiQuL9wz~Ķ2hv;Zw% 桤D PFYZJǿIfg 毰9,va*lAK>Ԣa- v{E:ii{y+/` 3PPTEV UT}`gli_ zUiO&f)oT xcT"f55ozV[[Ju]tG[dQnjSW-'Y'G|dcv6+"m-UV9u?qwz]dxU֋G0?9bsOyAcFb裟.R݄լO:; 6 3Bp!ms< xkA68/p+Rᐤ$)*Sp|t9:O} : W!ꧺ oX[@5H|KtG夂1nXNw͹`̴2zt]GB2P}(d /' jxCqW<값5<kC]il$"L$y7,R Sfao{^S( DX3/k<=8ʑ,ݔ:=K@6 3! HEpwls#@ѭH-Z0[Dx)IhJ0kcDJ~<%z/6!>8ųP~(X'+8BE ei-\[kw€TIrb@Ӛf8ivSFZME@Ԣ*H5P 83#TTܳ&ܧtv,IAjЄ* "u( # R+FQqtr H R2]TTZ0uLRS..:N}J¥dE%*R4˂VNT5ҞW}HV OS/լ MV"&(O2эtti k_W)O! {g=i,Mo YHvAijv PxݙЎlofPCdymcՁ%d-kΊֆv+ҵV8.ʤ\ϭfJ5RC1%c]~7 g?̢7]gDU8.}miߥ蓵[umWa , bC`~lU0l *fl; {icB`H?v]O @l`[F1ZF":䩥9EB+q/9&!`Twĭ&Pz@/6lwcoeQ׺Y}7`S'ǽ{_~舯0=|8|VxEy+@6ԗ֧uuv}}!'zGz7;zz zz0{wwWw;*(8@Gp Zg5\*e7ALw(@\^!{ijVcj{W+u&gs6 dxEGwL=x#@Y3DhFtH8p5tLNXqPhpRTXq# ]^=bh%dBfcӡkȆy_u60{ȇȃ3ჁBxh4v-҄FO# >cȐ(䓦h@Cip)OńH2*M RIxil=LwX-%7ك3i @"B2yZJy/q9:4uIwQFКٗvV9*=!W@e ɘ㘐1Faƙxs91Ii:Ysٚ26QR>E l;))!șN b3Ypy ԝy)X<d՞?a]3_Ig0ٜo)?@o8+ H٠rj-G| ~ Țƅ3RL8NȷɼJ6_\{aɛƗkp]CrM׀=v}Zה#[،M7s^`]#idؘo=D٤mo}`zC ĥFy^~ >^~ ">$^&~(*,.02>4^6~8:<>@B>D^F~HJLNJ(2 _OQ.A墝WNY0_a.dV~ Z}np.r>f^v`bX|UUU*"R軘==[t3苮荾8+@N Mt^"K˞ꢎ7^3īN22Zs-R/0,Bi01޵W(ա$Ǝ싣cώ.HA;s’욳 .qsa9rAb.^~>^( O}4/׊Sq&/dJn ///eJnY? J8')+BD_7r8?1:yOoQߎSRK._~# *iJ;|&t>ss@s/ uN}}//OaINBeCyʑ___Q7rQFf> B {/_U_U'!ՁHB=_O_OïoIQp]=Ϳ=ϯϙ|P $aLXq"5nG!E$YI)Udٲ@'1eRYM1t$'A? !:I_,etiQQ$YklaDRĎYiծe[0eƬi͝<} *߿I:%UI^͊kWD+,\̙5o٭ܹJD7o|r X0R_d6 #ܹgѣ;~\#ɕ-[sق\tQ^ͺױgˮvnܼ{m]7ōGF _9.&.52<So7ڳ>>c.@CqD07.R+` Xb%|*+r`G sG ;"(PrI&t(4aJ*J, .wSH2OXL4ITsM63 UEaFo"+x) 0$$ZI'r*\4-K:L4lsSN;eb_{sO@BY8TDͲ)&.,LWtYf- TQIe;gF>]}Xg[ȕEy5W,#vRb[3dwy-Q,NTűU? c`VVN+b/%3Yex6m_i<OUS afXK`%RKŘcc_+RNf>RhYfkvb٥ï%S?MV{或S :dVv娥匮V!k*vaf{9읿$>m#|wdkf`z=o'|ׇy 0XrkwNzl]EMuU? ؉Ҷ}zIhMUi6wσ#2EcTwQ)uX-?s; c5okbjy:~Iň@# B MXAPK>5F@;C}H)J96 bH Kdf/|H C,fq [{h(!q';g w8"Pw#D:ZcgAmwncZ7B=_Nvo~|ڞ/~WMzGLfOtxoZaa'Q3ut]f \>ԛKL]xf'\i=^_l_nZ72Y;IX> 9Z5=іW{sLJ?y@}'>'(9?!?_;9?92YXKѐ?3ӺJ??o%@#0@T@U#|!(\ įm@ 5A(A A:1xB@ecA4 6  54 [SC’&|()*3@@gY8'¹Þ!C;C|2Y |C%CCs*>$\ #D[@%l@Wq'LEtFlDIlJD@LD2DDJA5S>TTEV=W_`tB},GH䵁TZ(4AƒxG0B"$tFXJىH(a5F@ PD?o@pU8U\lRy@mKԪ L 9JBz=_΢T$ JtOlJ8ִ"ׄMOlUDɴPJI1hR'=P1lQtо@ Œ MEL Pl$R,%tdêQ܀O@̓DI,#M$%O&'}ҸDPWR R R0MLXLI]mKӋ[Mx8\ ;y5>ܴA-TQ\[ [ uNQYuA  ڤ@YS9:5;}Y܍q2M@ QN68cͬ݋UӍVԍTՍ]hYougPR݋%]Y4{=\WW:}Q;Q<睁qZ` 60eF= *,Z\\ -_:`$]߷_ eP_[v=ݚ^EÝWU]2j6  `kީ*Ҏ~X%a5a^-MVT٥]Y YX ' L\˵40mI 3!)"Jjۉ) }U.VW.XE#/#e;f_5`%aFb6fwމd^fhfYz&fflfklnonpqBzd;tfuvӀgMCd" |=ghWaeWe Z[~\N]h.%egƈah΋ fV#g hNijk^k1 ofLZd^id&;Idhl),1"ej(jj^vh:h#ȫ6ւ>ն߀kk2 k'k&ߓS@ތ`n&l6ldn6韦΢.I{^+Z*g.m^ >hzJu0#f"rmloCkRkh.U=fmn.ↀblFiLJnr^snl0xlvfh3րVj#vvomjjFohhN hii.i ֌&p*֭.hn2wqCy갻`yH('>+˝ H!N]ʝmp"'נ-!o(yp,8-#=͌.//Fю/2o35o#9o+{^=l|@qƦBOCE Fwt!yTFot%rNk4<QO R6u(A+Q`uouuΈu(dZ[u8WsȎ`2"7*BvVvgG/iDEF mn _w VRA3ړt2-A~ ('k艈rmjؒV6jf{¢6 sm7xw|8ǶZ8x##+UU2[՜o9J פݦ$-NM#`'5QvVp'1)kI36.{p1m wQ io{cQd#.2I,Cqqz %X}1lFࠊۂ7D ;JStӿn Ks0]ӟAIPʝxge24H 1c4\N0D, È̐6CBu#j8=w"ā,.SEDߐ8%V50kBE+ /%Q#t-3"ISY7.sZVDb@3м%Q ^*]A( Ck[~ )(1^T(Ru XqyL?RSܴAB!iX촧ls*p Q]NI3 )b2g(xURBDn~իaPBjơ5DJQ]5,r]5`y,s2t4 [xd;foK,ȉs>lggZJ[jZJ/L moDXmťesl-.^k/KLkHlWu`KnK#zDzomb\82|$%ɽ6x2 )grPX-K^V[̕[n|J)mF(nv3ܛmY3B2Le84h$S iHHftЖHSB7$GwB~Q V j`ԓ^)к^f>ׂ"l$B5862\e,n=f5g^M9] r.b`g] {NS_z"NGӓfL-M d)>r[izԥȩSH:8 k'!5k]ׂmt";I^6 hhKYӦ0o߾Js;n]ήNM%n= Jmz>~.U"\Bp4 />5nXw㴾81DIplU\0yina9祢w~lS͖R\t88~Vě$R7կBv-.Ӿ%q뎨]"M!)o2< [X$[byc^ yp[ʽJ[Mӝ^U^q^^߈idá@U~]uP|WD_J\YYuPdd _ε=Ey]MaH)Խ^@^@.D jZNpV` ɝ ` bHp`_ɵ`  ]T`P T%&XE^D)DQG]{Y)ɛ I"b"1FEm [1Z$~!Q! !uaY$D !&n0#F$ %^"&&''((Id"++ ,b٢"u"10*0 1%c44~!!atQiuc:}#! #I"%#;b:;F<<>B"c"s>>?fA+*+ޓ dd!d2!SI0CE00E"9U,5F`G~W$u5IjJZJK6KL$$$:N^OnuA|bDžb I %U(%BbS#TT"Z@--Q.WX"YBYJ$ZRE%Fa\>c]]2^%_V C*bL8BbMMccH;_ddcee#).%1S"DjAkjWBB2$mRAY6Yan¥Ml\H!aqEre_sBgHgmRabNb:Ndwwaxx eye==F>v&l~f??re@@f~'gWg/2G2&΀Q gqDp(gNĆb^$"-mcm% ZDUFP1ns@0 jjdN,lȆ$c"~A*J2dti~ܝ3Q[XE* .ٲBivb#))eܩ@͞N&AĠ>YIڢ=*d&ٵR^*QdmWu@j/*wLa!^jE )ߩCعi%kZ$28Y驨)zfl t뷆FNb*YO֫j+Z>P"-GrFlNVl)΄V zll쟢ʲl ](j0",,V fR5mD<Nb U2&f/Z-ڄ.( ڦڲ-Rrj݂݊kޒ;ڬkβ:,pkծL/kAHv/$}dn:mt)~ .-OXfEǵmܮ..//rদ" 6&..n[Po.mvZ.*n}o L}Vb 떭0ک|ݝRhTBH]ռ*D/x&ncK(WnקVpۧjE1S/jo0~coⰖu . pk hq'";)Eꏌ B qFvE#&O* ]#\xqq=񩤱+S O{ k가6^ﱔ0 i7k!1,r4OE$njT_r=emrt'ǎa`1KhWqqT2砲r&+-E?.+s// 3Wal2!3!;AsXHnP X3a$7@f 8KqTP199r::ns<1K=:'Ҫ|4?/7A1BsӰ:P=tDwl3ah"Gp0rW#_s$YH#.FsJI2uv]+M_tNNeOSPˇP2.˱.;@#uA7AuB3vt:Q2[DcQV_tWOWotXwX5YMZZ[5uKKu;rMv`2Ntaa5?+?;TH@!?,*HX!ᔇ7HXj2٨cB* D ,/0cʘIM r)Vp+Hm(]ziIiHeCUիAV05WK*Aւٳhz۶0ʅCnxqݛx9L0ȸǐHˇiFڀhMMQP)F`tc^@I(4m/n[N8Qvalham&$Ӊ)/cX3܍E7]u>~ IdxG"pƦB)e|T"feXf%y) j -Vdv`jQZ)\Iz rMi][Zfjョɠjo*ۆK |"F2;(SN-*?j+$E~ ny㒛ie`~e@/8jPh!ip+Д02"xXS̜h#`(f%"_p,n Uiww:nhҡP}+F:o6j@:g}G= IL':gQw+ zJˋtĨn@B2*~o_k:1=x#G( rD4H$2Lfi МDpGQC4 Bn '4ǹ]RzvBxxSg4H7N$It9v[!U@ъ.fюCXTHLf>35hMa @7)pu*DzF~~( zPA mCш*`E98tԣDZ_URh)Uyk$ڜ)7i4ki O}jgZzn,S'T$4IRU jիltVՈ5c=`FGsVr%S[p%uCh^*5|)͹tJfEbI,A&[˖'Ag9ю%bXźmmo;Wեwi^pW=O+-FE* On,T1n}w ޑ %z7Vĭn9[ҷ}o`i\2 bK5pSTES}0#-/jZ qlMVjآ[<[NaqS&HtzepR , l N06 =)lV6pS)7>2e.5w|1<3;<>̈́,gDw@{6>c!!yCtMl6S5m ]f5-Iw5DRqsHX9&>o$׺&"͟&.a{l$n6`8kښ=mn{;+lwWga 5|w޻^`kG>{+NH*̦P|iԮjV @nݱ8#q[$?Mg[,_>T:yrabAσqO|ܬ6:`}gs9.5(L 6̃etw{g`P@&EI=dd\JN4P=_?ڣ_|bS<0^,|z_q _I}}ٷP}ҵa׀OV~w~k,'g^-X$l8}}'y@gׁ -!0D'.%xZ (V8x4x{8~:x=yADXoyj0رL*PgZT8lWVq%]#BaX.C"Q4"8憘1#3&wzW#~C.舏4hX=PHhE!lDxX&hH|Iˆ! #!ih#9(FDhx{|XӁ|WihظzCk(xj{Tv(AFDiXؐCH)4IxXev؊ީk%mɜ湐zٞjuiIFP!3&I<{zYr_I%ڂIY2%'99<+ʢ /2Zo͔_##zP &)+zA*wFڥMeKt$# QjSi䨥[:^Zm%@E5`Nz wFm? (Jvڨ$l\ڟ-%z/Щ ZҹDw ?zztڜ I[bʩکZ R1x)i(j  4#QA:w[1ڀ:^|wNJz:'l5u몖ڭ:SJ{uJu暯 KDZM[oQDj!"T$[l–wz:%;Ov QDp i,44;kea*@;={"? +D;#e9K۴!AbH#7z5[c۶2ht[p{Vruɷ;[{۸;[{۹;[{ۺ;[{ۻ;[{țʻۼ;[{؛ڻ۽km15 nFkm%˾bxB+4KeRCſ3 }u'p\aG"Wzkp\|̫ 2,' ¨:$&mu:[P68)"1>Y8#w2\4l5:[?lUP2\yH8M1YQ,"DLŞi7Z] -a,ƁIIlL^n|pXZLjlVitZꏞ(ZNNc$w~݆n LxN~gNȎʾu.{T]~+p(9d.>^~+J">^|[0TO< /??~^ zw^vjnxgy[g0Dsc8<ȊY`s*ݨ3a 7989 #D?FH|# KY[2XvzQϺ[:`bd5fikmp/tvx_z/l" /P"_L|knoA. X#DmL4}\^?ohj!?m&k=6șgx<)_ߥ%  P .dC%NXQ)42q !SrI<)mdy-1eYSMygA?QIz`S YU4Y_$W aZ$[Yi- qu 4xP]y2EcA$iJ+”M7ݺy煠A*=ztgMBJ5UY,J+ cΦ]"2ʝ[m #2%Ċ6v@dɹ+_ tsFAAbպaϧP8cιcAd!<5QO-.tYN;[BMRRŕQUWͯ*VZk\u Z_ia}"$8DL=ZiЮ͖nU4y'ͭRs `6M="ww7UV]w~_Vb^5ح{$.Պإ4ە9frw+d[S9Z6ebwjڃ.Zvڰq$jr+^Wq.~;gqS# ./ XgZpG2ckykod|^E# Z6-/c(>Vk)"پ4ZK:S-= j.:{^SuqK8Ԡfϥu3p pnܓQ+lb1Yc#X F׾_f7?/d piW#A3`'wpM/ԼFvZ }Kқo^!0h9>oH=noq^]@ENr+*A[`[5y_t{M`|㗬Gy+.L^ݨƭ\ }N(ZO u bp{ܛ=h?w7n;.۽ Fϻr<栙]x>ĸuLZu[қX=;˽;;h#2)4ckJC> 4n30+6--!;7Z>y :X Y3?C8[X#ӣb::\kdI ұݓD\B~ 2pk0q>S37vq+>A["33;czyaA <,6AAd $B)B#KkYBj{ϒ(6Ҵ 6,@@r/T0C[2c C6;cqCxQ:<=C@DB4bA#2FG,H>I +J$ +L,t0 2;5/qEr$\ ` %[,H7[ͣGqЦ&,+I3I,̈́LpǪGDҤN{DtQ;xAp`OM{BH$Mk,L64 KlTN4LI4<=P 5D.Ȝ(ce==-Ȏ%ʏ TT͙֨hY}YјYڪY4ٙMYjYv}סuKIz҇]U2=W 5XZZ_KTkQ ů\[CM֒MYE]Y[HMT6]lWUWY XΥ-=]* ݯ ;mH]XC[S ] Vy[ݵE]m OSeu^ޠ\B0^W_-XܐTXS :2ɄRi]LUODЌ[P{]f^ &='ÕRM`>aI_=:5N`aɬmb;SRǻ< vB.-ٓ8\刣\0\K>bdbsU:+NGΠ;#:I JMD8+V+Bz+Lv  ڨ;cc B&CތDV8.QHdC޸d\M.N^OPnQf sՏSNYYheWeYeee3ee) f;!%3@PfgOtfhijkf!xg-q.g8gEVgvg yFg%b}~ތn^Ώn~Qh;NՈ.5Nr(#iN nYhfj kl:bF iC^s;tNgZVPj`4 jꦸVj6jV>W~~شƹn_e'ekOe^~i؋^8sE%< dvgrv젆ltf R nlBdmd1~崣R7V>v톎-m[\*]m)ncn nfkl8骋nM쫨pn j Y[aodNd$XoN6oio Ve/떋m7em&6F(Tp:mXqp GiVi Wl w\^znBf$ :s Q) qqoj>A52ThM]m^h;$0/5mi,/-'nQJ1phn4/G5+[u7 8'd9:s,n?g6o@q t`"BCDo&'(u hpyzTWu HnW X45W[u]yՙ_;'wo+ozegG:hCq @t"SkWlwt&HOӖww wXARwbwpwx7ygzw%S|Oiwi3 YZ/x0bW8 ixxx[xsJQe@Og_YyGvvO"KL7Mt*mcn)} {/z 8VWp/xsugzFq'-xZx"/+}O{c{?'ɐyF)mvxoƗ5{ y)8 $ʗ?~7S^Xr$5lQɓ^ xŵ3ic?O!t?[+4rEoy*a.=&N?Gd׮wW;yIo|J'˯g76oSPs?~m7 7>+˕v9K69U=MqL-y \Wr4-h9OP$7U]<w˗E~Zu5@p^PWMU=דǽ p" _LZU8Y|z!v2_OrA d9)KY \E`x4@,E&61`Ԯ3AvUv];K0&ԧ'T[ Ba.`Ѱ7av=.UX+$LKYS*(XSYpi\[F[o3"\lt]5?~ a Y/Q"=rRO8':FI԰IΓ%Dh}i_TĚD13\,e+,^1&1ӘQщHX` 8;Mx!gBy3yl{#%I3Z',y=xo=^=I|3FZwF61шXIp_Bg"DM]8Q- d4cQHL+3Hlk]3$MQΛ9ՙǵDd HB+Z|VM}ќNը:a |Yb끫!LWU*R?! Ѵmlk:̸qht]{8N4)Rst)6aMmT ʼnSu#_PVx#jX0[rֳEhG[ZPV/jZ-VmOPruo}\撸آ/{Qιύ.cX׎~fJ?=lyiױTc r;Edh-#4yu?Ҥ;JW%ZG-|oVs]qx“rR )W%.Ȫl`0沗Sf1$KY\HL:%?7 Ѓ.qh,{|"IW:A7N#JSꢜx6Ѫk.:ֲ/gkal q."'m*_FY޲\m_m[EcX_npFi3@GeЄ hF ֈSp<4L< j4wB{qd>\z${GoȄsQΜ5_Em|:΀xo![7)^ͼ^:ٷ} a_Sv-+m !Ѽ=X.b{Q +(Anoƾ;C/?{gl}oYߺy]Y-\5ڵ! 9݅9ۏi_QYANQYa^\^癛_߻џi߶oɞ\\j^FI QY F$.u Z`S HO][Z h [Mw숚KDE!@ a (!NERa Y"ubhH8@bKHRbZ@`&rbyFR*2`+~i@"gآ>Y"/abO C~1㭔 .# RBL "e n"zc tcU\N_;J((N!V#c"qL ԢhƩA:)$2=0Fd1"()EE3Z`@aaGVG^cHf6V$^p".L##zI$$K$$ɤ ZedcO=#ýb?RdSbTڝTީX&ijq xeXe=F%ӵ5J56Hea]N@^>^A_e:$ չc EG^Icv]de,&VSb$g"g۸Yhh櫠jkZk lFemfn%"rov#p gq!Kai|`N`a6atNO6PztxZx€R2%`]`zF.'UZ{g|'Dr}oeX"VX!@kLgɈЀ:\FVdiL KXI) Pi])b(HSlTs쇞K|ViC Tf%\R)q>j2i%X}4^yvjS)u)U0qi˒Dhh)z)٫~0*V隢AFě@siK)jC*e**R<*O+NT))gP뮎@Ffin)U**@^&+)i"ğJkVP+CX+`ki+p+rxFꤪj]̡,ˊҺ @ i꼚l+Tkɗ)B,6bÆĦL:@=Qr,y,ȦɊɶڮ, )j,ɫ+++ DVЂiњ)h2-:JJNTrx-f!*mܦUٞ-.-Z--Elꫬj*j&J.+6bj.~Z芮릕,~/̞ꐤjv@ζ-.envBUA(19Ɲހ#/LT)jletFئؚ@ho醯kklln.*obojHɦiwG` qen/ڮ?vp~l/ ..-0pA/=g smz 1銬-rJüki*moo0DZ2)pZ9 q w 3q2- i!r(T12;2VR^pq {q%%r2z2(?ñ5jܱ;0e@p+1ڡ-b0!ײ:-E-U$rK"gO#o R000+ n1:3Bs4p^3DG) *c*1++399S+:;:t@! ,*H@l[ȰBb#HŋR2jdDZ @d„ R\%0ћ͛rɡ'=~LGtѣ[*ӛ7AI!MXi WF`҄ih][pjKݻc=÷/US%CpÈ $EJL˘3k<$B2XE;hI*Wd 3CPmt՟<I4QJۛTPTi+W]Ê\ڴkպ}.]+_ .L8c69(y hED֚k[JV:njCu:IT85\tWee];e#uY7w_{|`=W}EXViXVf ; $:6k^ r MēO#EǥbؓtHc7r;FǢd`VZH*|]pYjjugn_2 gv҅ xmn8'IY܉(&r.Ngk9.J'JZPٔkM:b?뮨*5 |*+cJR놾 \Ǟre5 ݳhݴ7V[ֵjkN2Ɂd;j8\eBG/T !4º_QXg'`)0D[oXg8`gSݔݜ" (ډvT6ɐr634΄FL=EMkbM?AIMML6 HuQ`#6ews{iw ia nz {6G/}g@O8J/]ɚs{#:u뤾Ҵ_N;[ooCǼ9zӋ`}qVN=*U&u24t#P5 ~z㦵(4JP C#bAhEsQ=m dzpe^zLtv7ԧ@5PRT3`u }e[:PX=BZ z X*2zEG=V-]cjo5P)]Ys$BݛL [S -LRBUT́Ymd;lW#v)4q=7z;ۅԶx_7Svo:XUCbXdZ\6fYr+x[)ռzIxm6 ߅FRTF8 [z%2 ֬+/΋^[9(Vq|`΅=1pŐܸjr | )G٢O RX #ڮ{}} 1sa|4XR{,E} qy%٧}Cy}~7~z;zW)~~kGa{|!k (.E8ؗ}WkGy X~x~H) K "2$ht>,u/P3 jb8;>H`~G]u҄)C|W|Z؇cDžG^hCc(e~gi(k({YokDŽ {t v-xX|zH|臢os2X5rb(zf((;z82xVu>Q<VMR8(H،phৈ#ȋE-x>hNXx$׉<ŌXWVwؘ(8~#2AH)h(ØŸ7Ǩ Gؑꅏ妏8Ƀq㑐H}($1 P,s$IDD&$Yz&) NU^w2KRKHa=铈By-Ef؏xKPR QT3Z)]5d<R>r9H\X_ w+l)r DZ .\xr՘)Hni Рwٙr)CW$"iy Yq)7 |I͙)uթ)zىٛݩ{Ig)ٞysIUNɟ/{yz :ѷ֠P R ⹡ҡ"J<:s&zP(:{*ʢy($9!5<7&=ZՔ@G蠓J6H|JL RNZPEHTjpoX ZBڥEŃ`z?O|Ѥ,*lsZvOI\t{*}Jj7y:ZV֨󨌥J^1PW3WlpvR<7-a^7O 7~j0zJj.ʫN>j 3G$pJԂYNݺj$MC:dzꪩ푘(Zqz;ZH~Hj4-E9.I>g5$;˱ӮX(0Z3(G*k-KD@;vU5k'SoP+}`G˴䲌`+vS g߂ʵOpoc{>7K9¶n^q6 QwyVaCE\۷$kb+k˴+v7;VP˷D|׹[K[k@+Kۇk#Żw/˼ۼӛcqثox+sh۸۾V`;[+˾<\| <\| "<$\&|(*,.02<4\6|8:<>@Bカsū+x?w"3=6}8 :?ӝA=yC}!E=ы2MOQMSYc"ՠʫ]_a-ALmg}$k{?[-yuwmh_}>(;M:n}!'񺋝ky ٚ!GAٕ}1؜]o !ڪKԥ}eN}hN!:EB񳶍;MF ׽}-5]pI Ԟ}}2Q0]ݖ/ܬ0ݛ5}͍ ݟޏޛ ~$M<.[}m-l.ВhTiP5&5r ᕲ]S!MMLޝLD-+0.BՔDZDjk 9a&2fG\4֧'愰'"cmecXNZ..H`&`舞~^紎xOkD^®M~~.^8Wnn0֞!>߾0諾.^AN1n鱦9um^~~AI/OҎO /^iO?o ,x.jnlN?Bѫcd5o_4Oo>=owQ<`/bȃ+f:hOP/T_VV+E{]!'_#_6lnߺ7ޣB[_#p\ɕ\Ms˴Ȓ:gsSJR?:k ?b젚|{v6XqETb@S9Šk":  42; I=, I(WܨbNoRJyFcQsn1q2R-r/] SŽ;l93dLNL-3SNSC=3JCRPOeeQTtt@H R ;U+l'b݈,AUNpOlQE,u@U puVYm$@4+c]Xc@eYhGs Zֶm6\p]Gqh#VpV6 MƉ^7\fy͌ANYe|ȻsV\{qZWZ:QɥjzQ&f[{û;oCƂfoa~e po^w~Mn.27|A=kKtSS}fׇ@vvvб~ܹ=mCG &. wG@߽BG ja 0%"$_ OB. BC:(zwF|d?g>(vlҚzFom8g*te%<9OzÞv$~&7e4YBmWIrá͜DZ-7k N\)CZzYÜikjSFqV<(;V)s=E͜b(9TQb{*GJVW--y9DV)-`W;{I W1\PbD&]N{XжxCWræڸkCצg+`*Q(bjG1Y-d'KY:fY|~eE@Qځv,UjZ$6lk+V_F…aX^D7tl<YʪTmlu<~7aɰECðpAg5#L|bsXDA]) 0qco;qw\ C&rJw$'y8(a؂ AS6@\,g9[{09)[Lp愌 n1Y9Cf53XJN|b&.$Z ¤_5<"hSs-c69d Cyjmt<LRZf^lٵxřgyJ%\Bk129PHtw $ΜCyp>Ы!t#ˊR_ʼnck]ɦ)fW/ӾvT Yr?w~k}3XVA2³F8S l\^"ֻygڢgJlzaKgsnDq9=oGo3>@Py88Hk9x҉8[KZz@ >Is1cS37!w8|;::/ D@:˻:˺S# 3!@ L  9k4AI+A; k;ýA%| >~ B#dt>蓾 (>*<+BzQLQjP Eˬ3Ć4D5l64CP7<ÑE>4/($|e\(QE~"SG &ID-LM$NyEQd6Rd?4dA5tA6lWXCZEEYEEaDBB5c%@U3fETPD5jňQ*V${WrԴ tTN-R*ubPvs\zTu|M }5ZCGКݼ`p9XfSSlؒxؔ!;K8xQS;!+ uX}U٨Zo0Z@GUY$cYipٍ*+י֛5Yw-ҠuPףM1-S1 8huhb_>FAޭ.hvPNifF2Yq&g4@g&`qgN V{}g%jjgꬮ6ֆ&vƞff̾LHk-QiO>7[;l~Mn$b!kvgbgVjvjϞh)&m;HhgYhkyh֎CMvmQmU%f^m,!0nfanusnVFxF֣^~ Io^onf+8^m955b7o9p0FPpaopGU / 4 _Npd'G86qFV͆vqN oooծoyσ T :koGyA 'Q)O+׈ q mȹ s23IWs/j6np8'o97o@[m=onhA=B hvi/!/p}ANrfFޜ?1eٖwrA4Q*G^+_ ,VRSVT'Urfu1n𔈴4}xP\Of9`qtfilv5":lwkHI%owwrwsGtd QRwz'/F1߄}7~2Mul]O. ?`mx>7m?Gm_g5hxX#_v9J/e_w` www?y7C|1/zX?zI-['xSot#?r^oЫw{Boɿ0{h@:lmGng/w9O^gxwpwD'OgYxɧ|ʾ˯8G;zcGO7{w ؿv'y/{S(hP „ 2v'ƒww7>(#Dp8F(iʔVL217YN)6'PbBʔ (RJFhtԨ!Y-XR7"<Z+캴 -\grrH+Xvc`mNl2&MFq#̚q 3[.mװƮhزgӮmm7/kgҥW39tSw)V;x1F+o|kC}+nb}3 1 x' i1q!ӖPVy }3"gxl٨ 4#{aiEA i]Sq$fM$Qڶ`Ypǟq!2܏QWig_eiF&xf4ĞG}8!WG8z !xyUX"jJ"xip3*.'2X#zJw|:k(DJJ9,QyoYG;}za9DF`&v&Nvbz埁8耒v(V) 8z{ZuQn);bj[šˬz+dX+:Śl2f0_J ՖV-y3 \`J8ߟC*/ZJ::z4ҔBFJQR $j&A5(:] 16dYW#v*Jd"IlE.c]|1%C#Kpp<@Gg2JL>Ǐd2kp%44 IFc* (iKB|$b$䓰3E4Q*JѴrVtN,wZ֒l2&N~an2Q+0&\q 4hSs!5 yH%(7Y5I:C9GEDuTiyb>ϭ3n *Uk5Iu-PPBñK@4'#E/Qfyh@j_-PZp\ӡLԛBE$3DLTa }I6~Wϲ{&>KAQ+C1 % ~ mX"V. ol @1 f>BnU{  (o^+a#j#~ ,bVĨ81ba1qDcǽqX{c ?DCMdX,Ɏrr,e*[Y;ey/c-,l)9-sQL?99ܗρǠQh:!DZVtbL_Fӎt_*&]vjߤCn|4@,uo-a׼굘ɬaSI~pv }iIJvCwۋnt!IS eF܍xK:w= 7п.Jp$A8gs3Yqx)|4{@OF*)g_*̆Yrpv+0Yϵ?5NjT3^'ľz{#&ྚ^,;Rw.qg[7s]9wm>Oϣbn'岺;ޱӝ|o‡?G|X;^鎒(o%cQ'\jG='^ZKJ#9˹uINb^^kp) }՜}q a:QeL)EoL^};ߓ_隻گEX`Gߞ ^!OIZ~~" -`5`=E`M`FVCھH.yCfH m ۵_0k ٠84 a%a.,bKJam^jv!d|a^fzJJʑҀ&!ՠ Jȟb"٥8Z|VΊz&Z'nE((•b)"!K+J,΢"" b/"0R0!11z!*"28:@Lc}X%^c6"!7~c88H9WZ_*;<*Y N =>b>? [@>@A&$J FcdH`Z"]deLld7v76GAHHJ#䭣J"Y<<$FMM^HNa?:POV)KwGifU$ b 6CE1"YPi;\duF CP1D\P̈́Kf@9(EB shȸ0G,a"ne^e`f` a!&(&cdd e"ebffn&\tQ|fhBh&fffckljl:mmrIo' ZEqBqJrr&`HB(¤ܨ(eEL &"s:BtFuuCvbvZiwbwj釶Cb܋c)頱&q{ill"HmfթnVXgɐbz!ƒBVRZ'b^bhn(v" 6b^(ʧ'L*BPsh jb\cnD!*"*k2:kH@땾CNb atꪈizzj{+hh7pZvrkrȽd Ȏ,%\zՁ˞-lά7B,,"bjmBd(-V}@m`,PXzB :~ئ껢-Bj#ۺ j*,iI6mfkԺDŽJ5FnL.zHh~hNb$KߠnݪϾCM.m>lD H 5og&./ ~R/:fLloΆ~oRioJ/&n[ܯ/*o2hp+ !,"*H@ZȐÇ"JDFhȫGU &9rɓsRDIJe0)ɜyfrXϟ7JJe˚HO1ZTDhPJԤ5#izkL`ÆK#ƟhӪ%ö[ʅDIxW\0a%jÈ+^̸I `CyR4{1FC ,$J)r&M8uԌLg+eU)ӥ/Ej߭\~ lYjoܺvM5[XW- ӫG/B˗1,6 U$дIԩY%k2!ՕlifgHopPg[ir97}҅uY]w*Y'y(42֞{|w_~'8jh`lѶ`P aQUE}}%Z_ndA)rbp X˝xF߉t&B&$Ġc衪m袏4y"餪u/䵢F!#a[eZahE披`rɔxީ'g|kZ衆&> [JZiZf~UDj 湬N֫4늷{V>4iZ/d);(Z,>aZ\r!Sa} TE.F鎸nnꇦkk4Iо+X,8l++BA}D]FƭtTǚ}LUn\[^ih:lk0$s6mz8#3<3BFtRF#4 ? qSV5kNIEǰhRZf1mx1"卬Z@LVL+HDIkH&Z≻`ѐxZ4*㦐DHck:n(iNoL.tȣ>LA`HBSoDW<Q_2HD0[D(FMQ+f 6ޯ#D#^HGqOGz4.wɞ+&R`( R1}$09.n*aJ )1GlgX#&\ f|-=#Hdl25`2r,fU0rLdYc3!Ehv5GFS70Ї9ѩξܲs HmS$$IIw $0if"H8iAi ŦC!?Q`r"G #` k&:UӲ椑gNhhAt0B*g>Wse`O7RWE 5HdJtfs0~TJ e75 k/TYPmR薷do+9=nrϺ$hAAڷ.^WTmxGӼ])ەZ|rd'dh5hg ^췿n/7lҰbK8.$w+a VЀanbfuQ8 xڅ+Ac!xf93w^6Ȝ2L7ɮXrGW9/2Xjx̟/M̈&dBλ?W J[78^`M~zكNeuK:t)mJ^ G RL'jTض-ylk9us{k`Zظ&vf;mO+[nvZLMTD{`u-]}l{kk]S>7Q*z#A6g;7 {o߄ o#@?*slBB yIʚV]r ߻ o*FO8҃m|`|K~7/oo|>rt)OCY {f=:sDbr.?`ꏿz|r;AySu{}ї=7=].~GRͧwwh7~XЀ|)f1p]f6|r+ BF]&hs}dX-0x"!#8c%x@c)Cx/hY43xhgӁ;h=e?Zh[C҂|JL؄Ox776UWk[57B+1z KhfXh8f'3" $6rSEaV&a18)M g(iXk(sSq꧈XHhXbI{X-}qR`87x+p8OHX0HxNF-}'uYx&!N5U #SEn!G>a.0}]ܸH`Zi@}BNCFn}]9lp.9N`†ѭf}.>~ۃ^^ ַoN^WNzEf?A0QIn.w nܱY.uUd.N7O~rxޥlZn((;5$3a5VæJ$'\Mr6d@H(LE|bƦzQ>o|k.38s^ 9>C$/ssD]{Yt q!_eX Mu/3 %?')kv>5 79gQV<>>!NNtaGN6Uq+-]r_?ac_gF k5-puo5MOQK@;)_zD`K*XrxkX@=p@~KY[c*8:r'/ Ŋ CA~-\(I%NXElq !/$yI2)UbKdPMtٓO*U ZQBe:)#Q'M5VgEmĎ٪jZ Wf[]y_) vaĴX ƒ reR$ɑ'Q %%G3idmS'>$MmIU.-ܴ,ԩ^%xkׯaNJ쥴kkvkܹo9w)>8⪍ B<1jF3HСEߪme}djmbi6H-b':n+xغͺED>SL10Zt=1<SZ=LE 3r! 4L0[+@ mA$/B8 /pgiĪDtMsN_dOFzst sH"Q3I%Iբdmت-$ p1[L-6DBcu"C嬳EOLOGuYD,>Fs4HWԵDҬ2ݴ0.4TLQNF.W+vVlz륨zO~U2ne`$`eFfaGTs^|73v*8v(\ O:}HE+V"p.& de8F:#S#a/r\ڨTH;"r,_/h8!xq[iPQ%-{.we*S'=I)RX)Y#mt-`K͉-c h,GeB[0GqscyL (WO{+AoMmC°$NFm9CNW3)OQBH3H4%?PreEBP9AeJv(hG_M0q@+NT_mWaꋩ)HpSfA0 ޳DRBuRU`u`Z WUu9AF;:T[!XMzH_ ]c*Ι27:˙ְL,, $>V,=f4vCչ7ma&ʊڳohh_[fmGmsq7QD'`)XV;lqx|IdMp`nK9h⹌~WHYV׺$RZn{$A0;!W^nlzl0K/,,;RB&x{(X[=ۛb pz+yXc5ۻM"f͝@uFk0F4D ׼]\ot9)``4PbV4t9 ce0<=0,PWO: uZ8j\X,f ռfŵ&oD6gwFl]t 4LhдHGC#ܛt)}ef5jɩS:U[{^TךXHuZlV94vVFhgN}tX (g;v GC:J2i)^e FQz0#gqr;p0,vf'mlT f6Hɓqm

/KxP4Ӽ<< 6bb%(;:; Ji{ػ, K?]]8==]s@;>"R&0` $;:<,@0 \5\PhC7<„!r<''B+,.\6Ct1,C9C'+XC6t6dD9do#pôCBö=+Gܻ@A?92ėAD(ELA4?<*I߁k LMN„!RaH4FĢ!lDTHcĵ|D% 2 :L"!?S 2D 8K"I~i=xS"@1M˘Pnګb`'2i: Ɛ"DWzXʐ LIϹLT<CT\l ڬ<Iߜ(NN0CNSi:  GTL_1NN O,,$ԗ<\Mtj*zͪk, PPMM|+ZPN!NDXJNв Г ѐ"Q0Q^@QP̔R6e{QQbQH`JϓOdOMS8~#%5&'=mRP9,P]ҨRtm< 1ӺNeSpASm:)@>ۍUҼA <@ A5B5TDUԡcT%}HK(#)-NMEN ]0P Q$R=4SPTeUQ`UtՎkմUgUCUo]֛b/mdR,٤M$MF5 `eK`a^~VbĐ`#6+d bX -cJ=cIT.:}a7V89㔪)z w`c>qAXg~fAa嚔lj+I[ihcNfhvhNP譺ii^2J gmq&1)gU[sVdyyi^i[F i:hj5%jـ+NRS~Fc]P*lj Zj/*(N5uv4w+x晹!k1YVM1vG>6Ʀ>Ȯ攢l+lmlYd-6dHmO[5Vţ9m~UlaQ)nIjvE~..hȸ2SVx>#H쳉 J8Q6V .R0mA픞50 Lp|kٖiچo-o6 iov^/p/5Loa צ!  7Fp#O_U"q0q ~|áLqc{RSqȔ4[5#W$τ%'wՆ)}rGrQ*/`ި3QLds^ph8-C:<ߧW=g˯?tBrC 5FJ&Gϐ1tL*,O rCnӢ(IuVkF6a'@7?FmxhiW'7kWlǗcinoX$f²Z)5;ruVqv_Xv>@hAǍ}~Nf/lt߀nAUOx x,y}T6^Ybcdg5ytttztoO/GA/ Eexxzmzꐮg{,e{/w(>>tOQ͘)Zr8X&~a}tX*)" {<51BYqvG$䨞1۞H3օ&ͨ/+OGgǘGW싨2e*(଄ Q1lH!\'Nhƌ7ʂF+$J1*c%2_ )0ΜvP:`Mej)TRd֬Zk?bz)kڴֲm*\I[ҭk.޼wY/G.<+AUj*Ȓ%ڰ!5q.ذТܵ1e#Nh"Q?y3UbXƢRLIsgPD 6t饦Fjܼ`Klٴjݒ{2g }߾ЯxbP_dq q :(hH8aiq!މ#zZEy$,9m%ݲ"-1ڒ p19c%SPbc IB'uOUu^zZxgyy%Y_G~Qd^I & Bڄtjv$"BCyh -Ԓ3Hɍ}raozأG̴ݮPS/@<^_f.<R*!hA Z%t fd)ѩBMF :JȎNh-M?R !IzHh(rk"H2% xt'B!$j9m9PUS*֨>=UyQgU4ѱj<+8*N*ii%^ә}u=yֱݎPڧHmc! PNR,tYJ43hGG;mfZuRU+:κڕz'_I:` %bu_UPune`MZ%CdwնuzaTD)M+HbҷA~C! M-TS 8POs!.%ǰ82eekpa+į91aRD.Qg ͱ\{xAN0|+#c(h9*Z&[LQʔ3c咐r]Geb.qSj l^8b=ҏsg=Z_|EV;NXy#e*:Wnyb.w_]3'ȥqEmM[z>^kU}`!ʬfl79^7%4idDfܞM)kW6ėȤ+Rixħr28۷^l,yJgoS0,+.#KL˘3k&hB"j2{6ґ*D˗6Z5շC *U!Uj+XmŖ(7Z?=ڽ]w4{ț6.GFyaÇuPqH"dJԦmŤL[<\D QF!`{  StXQWW`}^v%ytz;G{f|L6_B`iځJ%ۂ9x—`fĘdV…!PTlj%&6sWb Ř݌5^@|MF*餔g֡VHFrq& f, 'p e:%qz ⒫w.\n9s'@Uw+\6B1,\`ֳ@--à]&on;22\EMmɸ{5Vwlt$cJw˴)=jL&!vG]7Zo`!6~|v`i\m܅loz#ݩe(+ n{bUfԸ_\{2ba7%Gh͗a_v.Į~{g=wuo|+_уTiK=N#O}>qwZo0@<p)@=.3v¤@(>d{6&~F![1HXA's:J2N`p f""F!)F1cb` m8 Ua("шI<'@SE5lU8F+T#F8qudYG=q$~ w萇>"GFr$&HMZ4bX0R d#UV\X;RlL/08GBR;J %+en΄''@Mk2ff`VJoB9e_fi%;0Cg"ٝ}n@&*MPF'-%uNP0tyo"*NY2aF:ȍGEJRT)'8ЕY7pQ73BqjꄧW)*p]!9*BPHt'CPͤhҪg5颮zu`5XmJ¬ಘ gZ}V6խh9ףS|g[KR{arU,v]*MªxjY52 h&zedjGW`lyJjVf*z\we^`( 2snG;醇1thl olyr'\0 ]u"}]uy_0@)`xz0?nz0 0'LafY0E,43ouђ_UqKݜ+2.0kL:~0%\᷶DN3J%6Y pfM@:ۙHE gը4iHE&90F׬fi% xy27^`tQ5{:n~s|R}-Ak< ǣhF;ߑ{XzX:eO3 guFUjVWկvg ZсuuJ:1ƌ7=NݤKlhC] 떽o6ŽrוF77n Mj{f܇dFc}kP97 }wGN6ŝm48uD|#8pr\lf\woˆsmHZ-Hs|C':uΤNOJu8Cp9ƭuۓ ] '-puc{wugB}}+\6E@hj qmoWVzrʤt=4xi5"A-#hnکbH`ꩭW #Bfn ʩ ? ڝjZ"1J u ૿:Qfp:˪ڬ:zX\ªZ.WP *:!/0KʮoښcJگ!p& ޔIa;Q:Hi>¶uPrOt(qy۵^˷O~ QQk˶ sKb+<@a1rWRbs{hFh)<#P9y[jyDԆF91+kE+˻+= B[kqϻB;_@{.?뽾#K(a盾5׾Z@ѻ<7!K{! ̩ u^[߻B2^sˣ4 M,RƇ C"MҌԎט{-ӌDXmZ 6<բ֤-]!]sLN ՔM0m0Dּ |&<}" UgiǝHlڱ-ٳ=՗-DݼlTBKdMgi>}ܮܰܓ}Pyvr%&.n.Dmnܑ mr~|">%'>)+-/j:]5nx4%h;$§Mkbֲ- MQ~S^-}\C^ w-L>dK5RlmghV8wp逰qAEDgbV ԦG7!in&oƮ*n>ҮԮ~X~^i5w9~QR.ﮆnˎͮrfN36F6h8o^F??h.5}! 46F8o:l<ʎCOE9Lh^!X⮫u9?/^u>H?j;p.gAuEvH k{"\([ i7Uֹ'OA=2RM<:*Y9W 9VmZ#}\u{70`…̼'W|A:hҥ+^ ((R)^:jkk@oD0C\l1H $bD%lq:"bԋĺq({H$C!<& S(-R fIJ,-h V#0?HaL2c) 4cM%ڤR%"dNBJZ-7 O&RJPD*Fɬ,D #DSL`EZEgd7ԑGʆT.!},R$&rJiP-Y0/SL244dM\JN:O==O7 "P&p-0D5kF|S˥R-SMiTFGG͵(UVmH%aeRVV .U2`5X4M%L)N(M#:jV Eb4G)T_-BWu1wcy덐T LY"~c.<@zV ev9sa)b5c=PAYpSWģfu#N=] TzER#[5)Mp&Z{?{bF4e`vNCf[ڒ]^YpK'Fx?7_k!'ˏ\s딴c[xَ9Gkry]\ɕhx)LZ>$,Šaw"En7ܛfb0 #3>_|@9Tu{ жY+j!Z25e/[yGh)` קavj`AhRG1 .R #B0|&ȷBb΅~ad>ؽOd$x8"#+oY@h'"PfaUظ@Pxx2Oe<#0$0t'tc wLjp=$ uij2dL"u&5R  4{D>/>#*K뻾>22:}2T0U#,r/ Y %B#ds%lB.ۋBg˽۽+<,-B/l;2 >r@st&<4vwDI]k (Ϩ ?h?p &! ۪y%9,Z“ .zZC) 21?K8H |!ћzZ6i y&.j q-\j4 iQ(@Í II0L%IɠT TɓXJ#K dxJʄHa9˙HK*[hvzߐ˹t<>;| 4.\DLWT̪ďxLpLvhLD̏M NJJ4J@ʻ|M!`JM#Mܴ- NΓ"N31N@Z+pKιΤ4 Nt,L5$O2O@OPO8`OApفτU\ 4Eێ]ݕ9]]} O޾M5%y2A]()U\{K^5e̅EӬЭQ}N%Xuօ_Z 5Ou!]+_G_HSM[ aA`PNe`}5Z}E<`U n&VnXe XLe=fa[8T%^u]}!b4%59bpbYm`}`ƽKA, -^.^^ aUN36 [5R6V]7f]c+ccEacq%%l ]^֑2 Uyykf ׂn(gqaa`g u^ghgfxHgIaz[&g g;舎H a8 'rYMh\1Ъ2)CMf]if<߈fo~(fl+nޫ"qfrfbNuVv&wfyz0g &Yh(Y;hLXhhh s-6%.h(ΏJe]f#ghƝYil9iiiv皀.!Bgfjnh@hJFFhhfk}Aiaⰻ>i6Q skt^l頎S ɦ!|ls,l3Vvjj6%Ɩ&^M~msm|BmDi(n pl䞲Xn i~lyָn}~m(m+9 oo~mk|NY^ "?iV2HT%~pf\un w;l 'j FlO% >q^"4[o43o ! ϕ,#$ [)GtD31h K,g4˘\'8Ƴa^a5a&:q;?kotyu (D`wpa?b cGPv=`vtЀvÕvGt6kpv?nהovP]Q?):'HPklWww&!wwB.ÔY^WxO07hzvUlpx$vv,wqQ/r?sGthV?Oqy3wgyy"y5 v ƭ0tjϠğzFl':m/oۑ'2GwUGu?_9]{6WԔ{glwMחt3)z=zç m0Zhzi׷ 'zw}/}?}N{vg6yٟ{/ќN~}IM1p9XBPJi!;&Rc␌p# "G(i2ʔ Vi LblФI&Α#}I' BĹ(#Jlje!T(_R]c*HL +XKӐ`lĴHuVH#r.]j.2zR VaĊ0noujԨ6"a1p3h.m4ԧ w4k"H`B BHqEt5 8˘^fִQf>JzG*U7Ok!Ɩ=kڶoΥ;]{W_vb%c` 7~ifh]h rG鴜lEЖmT IPi\pqLXG%lι$tY\]w]pMFS7zIE_w[$Br%E`"h 2 W`aazz' kxbۋ!vȣI>B$u5Pr&z*tԱ*L&wBizkꁰ+XRJqF,lR(it2hgex.jR;nj !"IV@*"o1Fif)b)ͱt4jjjJNJ+¶l*EV ~d…qA-Z;,hUjhK.ΤH$b&o>Jojo-˩$ |p *\:yۚk|{l.[2^'?++dM8s+x΅W2ʩk"(.˜I+4h-yF 1HzPZA9u57TbO@ q_q03By#=>iysRFҙu?QຆNvBl;nY<x$@,)yɤYCӞ=}3rh +jmV44FMn^\ׂA_ s#@̀Z8 v]bj' V`2x r`D<4yw[T\Oo0̔ Pi:0#(x1 -]qt, aR}V21f/C8f4y8"kqC_G/JbNL$;xJҁV`Yܙ1%hT#988V` C'JCBϐ;pCJl#&0x0 !{e0=ujb] :+`!%V:۱d3 P`e"1p#lG4 Vr>;V*lcjue].+9uf=kҺfueo\cEW=bЫ2},ab"6XJe3+v hE{^ @,kiZȶmm s[R(-~{VWin t 6UXsbaWedP^7d^r$=o}sն X`CB&q*PX&}\ WBYWb'F1y%;_Cu[c7xq~ =ɵ-p.! vrqsus/̪ ( b%eS5Us`o{kATgVe'nnp!\/VseI*u; f1̷ uCj՞W71mn@zƘZ;.Cٝh(Cx-0Fe1E%]͊ KS|O`Rb$(YF`-w&gwRr.嗼$o6oc *_yYˡF#AuףiL .u<^V•tYQ.'␝8\?¸R4nJ}9תwsAF?ݒny:>uUVϺwn{:{Knvj r{iݣCg[ 8;3p2gNuz^cF~$G~vf\(}Tfvۣ=I|=ÇV}^=^QX \_]!E EIX%md^ٞ]=K`!)!=E)UaVQ\` J~ A`=ޝ!}ڞH~EFT靚A b V|uR#ra ]@\"&@ τLq l"yp!ab֡k"`! bX!(Q09"@HQX"&^& u|p():)."" բ-.f//!0X"$",H4#:c@c|IcnPc5^dx"7j7b868@9#٢#0IJpJ2#TJĤLdL>"A`|$PV .!J(c1$RBeI:d&HPdeE@IKZG H$IBج$KK L$\d1$NZ^O P% @&cA.#*R?.$XH5R%66z̄ H~#8v%y|%4X|a bĨJJN.$e\/]6yOe% %azS&Y2hT%dN6eڢefdAfl&gHfhh^@iUJ&v&#h&MeХ]z f^&fr`gqAb*'s~sUddKe~uZvlu'h͑Y歠&'z~{Τ{&||&!^&~~pah| hrZ&PKH$.Sj'XcYB%ڊj愎hLc]dX'_f_N'XB%DފUBK鑚.)PG>i"Y*xUy%g6*֤fmI^znf__%p&~Zbȩx(9H("%\┞$yyw~f:zi|)^gffnćI*zɫa)vϟ**,zG Сe:|ӫ_Ϟl%Ox =<`釧OZMƓO@ e_fSQWr)לs_%݇Pu\y]yb(4X{IFe_HIZ8H&9jt`Fm[K$q^rms݉Iiti'b8FӎYُ9hx`PJ 1veojI!:d)`#u!ڵ&qz竰:g|D=#韜ZO&hm V (dtWa A_)jG*&wתP659 4e6 .ye@fٔpNm)ފ 'YZv+r$'0,@6TKѾ+hCVZ",l.0IL[^aud$j]㩼20l kȧ=̙ =T.7QEY;_YsV*MdevhMnwp˭3k 7뻀!IgQP{g|0޸]C`v4||]Bأ#W tBF >f龻ŽA5 xX߅_c=HzjcW@loOݳHZŎ}`]wW+." ;e7 2"1H DI ;3R1fZRHxs#A-oDp,RI/4B0&6P응pFVъΚT|b #cF4E|k$_6h8d_V! <(ѐ e"1F2V$#PKz1LϘ4Hi7(+vr*%!\"7Q/3HF{,1M2#JjVcf7J9}S- Kt*R%)-vqȴ'g~fCiJ (MzY*l[Yu@AfE)yKS~96V&šIBҦ:51!7:ZaI2EldK_\ZSer3&.2|\:Beߨj-zNlu']ud;! iQ}HbVы^:]сW"N_Xv˝maŚUG!{慲$lfwvVHَwM٩88\kZO-ӓ*c[P;-oNjRR]R{ܿ&'EfsԵY JFֶ"kSxGԼyEU[,o&K_R/l]mr*mksІbcF"ᴗY.81u΄Ld,q쿿h0xDx,,[(V6X| `6s?H\dVɽY2R+ [l0c&Ϝf #09+Ƀx2'mg=oϗ< 2ygFD'ͦd4RHKҰ6Mk::u ݸ~GUQXXijm}N{z>׌h`,Ivtm&}fqcV&"=kqv _z;۵ )?F Ӧv.mַ TrrOo.ѻ@gS|.dc>sۜ.Asx2z޷$e74QϽ ~Co=m:?D[_w 5O~P%kz_'W765b 'W^QՔnG} Hd7~<(?(']*xM:%B8FXP;ȃbヨBG8:؃mф@A8TO"X?(Ox@jM/"Cu"gCitJm?oqh6W~Dw} Jp=(=ח |~Q Ѣ-M8QJmZW16QٵN5}]-Ra,n\Rܴ=՘ Iӝ :=Ӆ]%ݳLn:%5m2PL=MY<$-@a`-mE޻nlSeQ >jl ^/.֑2&}>.'~)kxGQb> ex[K]Nj=0t}wN-~ڦHPH%\IrN5|`GcNiikn[m>Ko޿%t>vyI{.~1`n!EN.e}giѳ&E~z|*Nϊrp  Ce ^xѬsgn61]%}.Nf n `?Tav0$/rpN]"ێ>L^e_8X/C3$$?&>1?㷞 &~P {ڰj(=_o>mOoO_v/_ oUo%_Z8XûG/29CQq8%?t5!#XT ,dÇ$ڣXћ7$,c*9!r#YRIl)dK1eΤYM9oٳbETA aɒٔ.]ԩNQLU"Y~ڕ(maJ$;YԮ֭[q9Wyү0ċa-wG FE~Lw$Rn'iԩUׯAs[iչYRzժ֭]~;Ygײ}6.v˷_ F|V1cǐ#Ly52 RQТI_>TS| DkQpVtP pڔ?߬C3KlDS<sEqFjj1sʱG 1"H$QrɋF8zT" ,(.%DA0ǑrS4S4rM8s/ @,#  @9HPqvXlE_FkQGxRHR?H$'/1O,r{ SL)<3M4lSC59ӿ<ܳ!BB|(xӰNF0(URL3TN=4T!eMH\[}XO55q51}-`4nvxd\b*YhGj[[mks8:1drS\5,SMmw UQ{m~ˀW^ FXa}c#pb/~6Z^9A*tdoq=e#e_y]bf9^rG^>|vh)el׬4ؔnj㔺ecZl ݶ >Ҵ߁~nW՗[|ׂFXq7+Dɋ\ZkC^ l|!ULI-t@? b}nmuo}sjmK4_/ +̉X0 8x5Y7ǭQs`70 \裹}*\~)p~3,c B xG0jE<*7A 'إ R-d-En#b :dRc$# e - <2oLʛ葀|\r9jq9`S$EDNQ`$8AHJ2yyNM^0G7(8R5`C=S`g;!xSiyOozt,kE˪ْy.{9H|S1w%%rl3! iJP&̰IN0g81wӜa@:Nw6'\e=iϑ+:|SuU0e'B:D`[fFDSxƐӅ[Kڒ U&%N p3qU*n$*m/'# (:U"eCapXo@TOBP39ghzY(AkP ]6I m^$S4Nӟ`$[pT$AKmj[*갴խz5?/ kgfVU[չw J^W,a n[u,9 Nf9uv--^Nsu-`֖n[H8]P*UcU.o;>w]g.,V^7. “+([ u@b;,<'[׾ q~ZBôABTXajg5S׵&̌0`xm;L"6Z=õ.*+>C6&omms5Yq/VKXq\ګ|d\dsm܁O%K!:C[$ciAXV()a`<_n=HN;Z+d-ͻޏw'_-inO5dË犷m%?X1E{K?/9zoC[c[;#4 ݋;66 2ۥ{[k{7;{<#?3?ü +h?zl?ѣ?"C@.S(&ڳ888;D4 \ƀALBO ,(5˾T|3qAA"r=éлu)=AAջ=+!"8F8%,@&9)T>* +$,DP\-BB C1< 2>K<{#>,\ Ѓ)A ܿ? =EF[c<઒|wbКČ"H*[hzĉ{|}+Ȁ-:H XHiȇ,Ȋ< DADHWIDIEPI`IjpI҂4,II~GI]J!JI:-1<,tʧ$\ <| !LT |ȴTtHH,7eK2(ɓLy[IT:4IļɭɝIə vʷ4ͩLLt3|<},޼ʶ-tDlڴۄHdL N~kK,K LNI,ɕPLn`LDŽ̷--J:dDOlJl,ռ϶ie< dEvQXIFVA C{o@!%9(eSF!B!J!*1 a%c:\TcI:8L9%:X;~ccc d\V)C6D^䃸F!IJ*kMNސOPNE!S>"J]eVNW~e6X8ZZF[vV\>]F^>8ޢ9>\f("@ Af+BhPd;`d] HmnVoV̀ CcqrFs~tNu.eT&wxz{N|g$R]:dphFijhGH.mfŲddֱ.SeUF6RTy~e{Yn2&[>5]^)YI-Z`NFzjyN d&efgvhhai>.vpkdmksuؽR> kcc®î6ƦYB=CFL6& mDh=miJNko^knxŠkTڶm mSm!kzm., Ɓ^6WYl.%˦̞IjxxjʒhE&yXͼ&ߞZ1on1EAI X~ef#S:ge蟍 }n _ w /"*- 'q+hwWH`ԩ9MyNFks;q^r%>rF_$%~rrrSArf 01$2gx:Hs6s(M9&:?X;Gs6NC?KN'EF?GWHgrrMdBrr sAu(<`phY/Zo9:;g=/,?vAw J?vef7gh_ijkrr! u(u;;1?wU7uAs,qw֞غuw~=x7^Grgvr/LMrtoW p'-q?rs tWKy5_X 8Z[\]w6kg cz"'ʅ%RH!om|狱Bǖk *l92IG\ Ht)Ҋ#8M5WUV*ݧv8 Išn bC+i~)iI|'/mz9|ɏ|,g9| |S}l%0KE}Q}h9`6 *jpĈPb6h#H")dq*l幘Рaȑ 4gn-j(RR2])Ϩg|R5G*֤Zr+ذ[-1b˖l{\6"|F^Vgp'VȒgB2̦L8Ff-!S.Rf{@>u:/ɘk㼎@ɦdR|j6q\gPb2mjԩUb:=5 qƄ[JnMtd^^(`vXb6~M@hdZ5Alm`(PAt[C'q!sy@I(A8]\BSO?5|uzRgT%azeꈟ~&T" @bUTPffqvL3"b3fco8=jđGAvtQu05ڤ9=wRe|]ҩ`x@:H]+i}ɟb&y]ev*6!aY-Zb'֚g-NZm T#oNȦ*k@C/4Nq4p: Fn:꿳^O*S|Ƽw~ &Co \A8Ypg7.,R+&qs!љq?& vPh7[n; T'yL+I:Htx'< OUjF>ũxIH'4՞hc(}k:͉(~.z >C*8iR&PF.mΫs# &Ns &j.6Mx#̌> St_iNҳ]1g>~ß*@AP4\QhFڑB4}+FWƣ HRR0)mJY ,'^ΙN2Ӭ&"m1f4N TSUWLHUPXPV2P+ Јp[ y^Uf%Xt:E O}:YV5jf ]JlWBγV%>R+^6֫$l:fBenQ[߾.qb\*`R!vu3lF٤l5,{nt9N߬aRe}׿ (oxtM5Rs)<^ N96~![b:h)F/WXøzX=cyueCd9PE(?Z\2nU2lw\"&q8OlfX>jv1@f8/@c޹Dz!GxG4f|ܿR+ 5~ӑ>v'Z̘6q؝RMuWW.{] `no1uy%ѿ'5Ϳ&y]T/Og_0 Pd|A<*{VW)"%%$ԇN߷Gx?)-pHGQHo`GH -l`$96 sQ(KL B^`ɔLm ٥~]8 ` qaxMF9BY_XmȠr>I`A" l(5B`)~ VZ`j pm] !W6tD|_9_d`n f{` ޟj䟴YCńG-IaZ3<1AN؉G!ƢQڡ7@s!! 3aG#"!$*!R`T5b"2'v%(@)BAj Z(" ʢ;- !2a*D!,c!=a""J@#>"+iPLb%5Zc8f~l+|8RS?cƣ:``. ?!0b@ $!!"""*53>H4RdEEjbrFGr()ᖔ$+$J";$L. //`XN")M{A&cB.B6"7eD_LZ`j4S^e)VFv#Ud)$9mQt:dUXš<"=M]cNv^&7 @z1"# (#3f_%f~a:V^lq$Gb(&f9dZ iQ^f6$i%PYll%D>fKa&p&p|'dE,'J:'tƢtz&uufZ2x'7^x%lf^mmn{&go}g~~ҁ%rͮhj& t$2>liz'V(\dy"P*$Q^:{|gygŠhz( &'`fQ( -dh棫3 i鑪fV($i2"Z)fElq))f}h阦AfI\?()(%~ń*ਞ*^Y*ƪ"ѫΪ*~I*"Ů++&+6;.FN^kU+n+6v++!,3*~``5Z0CoID hd* Ķɓ%\٭ey@ɜIfr۹R@RBtH45fXʔӧJ=GU j5)ǯ;٤^sǷg2]A7]x ߿ LÈ+^WdAt()da.$BbҲ{ ȁ#dL6q绫=V-;kN7n[nT;ٲhmn\˸Ë` -c3hѤCNrJ/cUv9S8=nD&W Gp]\V\q G͉tF[]'v܍g(by^CE4QEe$]|GIQk_xUԅ5HÃ!MX!ڱѱ!b]]whhQVYY|#ȣ}? H5HQJOB9D* \)WY]~I]ؑXj*clz2nFg6zѝ9:=8䡻-y)GBͣRJ: VjکZ:duW'eG'|λ+J*ꫠ*UI*xl *FJk5W)i[%gkꜰ)-+oBd/(b/:,Kˠ3Gmhm=7Mqs1TW=% '{g0Tok~|FѾ% ݳtI4N7 Rol+dn-{~2}Mv̬v83&@vz/]m~i|.5&2`$6Pny쑜(Xr?x0v3{z1n{s+ / Lhǻ]?,:uG!Gc?˞սn|c"'(dt7qxӒB W70܃PnrěeI%sj/Nws'eꉟeҐܧ(O6Ԛs,(+C6t`(-56ъԀP#qUG?Ebh@$KϓT dV8^3,`lPÛZ)ԡUFRԥ:j#5IUS[VʯRkc%+|V@mk ף>0GTZ}3+Xv kŪՂli'5sfw敞{%a_;iՔr+1`*iG;lkR+o[N-#J!s̅)Ǣ]P׺]AvW̻ 0;^ռDo_{"msZE7lx8 1ӭj4vJygiշĹ\8{}p v7{('\G}qg\8'p&9L{=>4晢=C to:PrЙ5tfu@z”;iQ? Geңubl>%O EvWL,ݥdw)3z;Z~4P#"WF1򠧨Xg|g|л>G`fO{_>Ia<=l}оV=ٮ>YO}qq,o>\oTǾ=}(%43WSlAC'LGa  h}w '&!!Mb4SDhh$(H!(#X.X@3jQ4@8(;61!5@ăHh5;(1eP1KX-M8?X-S)UX-6`X*[)]HOhx&c8e(QGp"k؆)@IBSDIT$$ WQ,i GrDDsQ|ؔ{P2UyT\^ 1`yb{d1O~h醕R4q)C wDyɀwh L9m9suiɘ .{}}IlyY@ fɗ}Y`tn$pɕv-NbAY=LAp#Gm h2 ,yõќ8y{Di&頝qŝV} <@xQ|驞ȞM@iiimTe'IiF%D :ׂ ݩBv"Z%J' )*8+:'$AMKjΔo_+O-[((CZIH*JLr`"P*SЦnj-UW٥o+Gc8֑뷦lm ')EiDаLLN}STڦ=rO9JM]ҧP*'#MA.Fj~ʪ&*lDd+yFѡz+cn2ZMΊZTH2Ю*&S[QC*z*\}WN04&1\]r طOհ1ZuU*j%S5+ {|"; $@&H*;.0;5{;8г+|DBcsApuu-PfO#Q2qpqN"&ECW-֘\.^`WbGdkeNk[m 'op;Tq5yٷ𷔃aktc!{ؗ۸kTtBv{O~FKKfn[tt0k ;Ҹȋ; ; /۔"_뻂+y$[mۺۼX0lQ[a]T5@但 !n{+Ką);ŷ%;m2  V@s+ԤLx|5p8,tܸ ! +5s”ؾ/|14\8TW|B!8>]hl1ԮnKܵ%?F^]c.E4^GFn "o 젎E^/~2Qr4 kD L~ #Ϗ0 Oa29^J!?%ˎU{/O+> c}SQ@;o ցqH:9 z ~/.d U/Wϩp~9\%^Ar2d@xR:AoBE_w{/cgd~qsyld'COB_:?l_ih.4w|E5KmX/$XA .TM\Wb8mh4!E!Y2I ULK1eY&9u3OmTԨ4IM,5QSoQMUUnkaۍ% #R۪̥]vKߤm Taw-Ә%\2%RQ#ǍA Y$ʓU|Ru7eӹ'OA 5z4)RMm;[\Yj5,XeϦ%tη_Vvkl1 ɓW_ornp@u 4@cAncB O`Caɻz+jS *:b4 i4θhrYpEi| B"H$SrI1(LrI^(8j"0+,spS L辈밃p <@)̣J -0>9 CѢg*FRTqz+g1q\QsP#&x2܈+U.;ČL" M5IbM8V :+< mTAwM$OG#pRJ-SMQSo> uTRK=5}HV]5YrԵ^cUK`rXlYe5|sil[o 4\Ǎ\s͐v݅"y5T_ X vV-VɊl8ע׹!1a)̮Y$YZ z-eMBA-tf2fF}tuzEƣCr̗xUZ){kFX`c2b76;Y=P;dhn4nOۻ{ozH'|FYqa,z1<͛A}6=f"Z4.engG=jxP[!%)o!W&EgLf5Kz;<(\i_;.9-`!j%%d3kʇF ;a0uhW?/n߆F$ {b"!Q5`92x9)|C&D!B腱Z i8Ca!(C ~!۱fױݎmk"}x(d$)x,^gC8 KacRF3hq?8pr4배.l ?jˡ yHX c[JeP&7O"0oe)Oyt݌|eޤ%-IaR{uF[!1j}<Ie-!"Fͱ f$p*['&iMy"yv̔^D2;d!q%0(˞Fh-A6&A a걘|aCL5}!*UibԚ6%3'ikJjLUd)((4Bg|pfo'ԡ|.:&1` ͪyï5LFeGNk l^V](y> PP0[0_90 IDhCu6ZZיGukq.,Ar*#]|VA{Q&c IG:<H WJ"r,n-kR- ||9`iUU k_ 絭Gp  )eo\0 g8\ɀ2ݷPB;.Fvl/;D7rQ-&n2uqql%}p%LX@V&ֆ+U'}H\ Xmqkc6wiq|Jq!YKG^R^&/'28|_6/ˡecjxa±VЍ BMD7?΀s&7nķ.VЂS<TgqGSqp4EcmJ9-MrV螰<`8Οqw9_mfYg&OH7OOԩ1Ĺu7?9Bv~/NޞW,tG0wJJf4Z'|X3<}Chx\۵^1i< ??(?C8Kg[hxƈ0ڻ7ý?5*$z3>ܷUZ+jrF@::00 ij)<<%0?C1DDSJ;s ?p?!A"?+A}J&a/3138#:/EL$8#9C>)>*B B-4.,/\ 1L\?cpA;osكÖC2 zX?d!@<0A0CL@DEFD Ǔݲ .M ND0 ŧ(0$3CAACS1`?rc=B|;]^A`,a4bTc̋d4B0RjiFF6NYzšHE ˑi;~$Xj,ZAIh.xIx<@5a#ɰt/Ts!XJ< 6ȉ\JJʉ .ӎj).Jhf|\\H+HȞHՂVطv-FVǬb- J:4]8$u\%C\EIP#i\x9RD<0$ (К(j80L0P `xhgX G/sH۴mfȰVPvQ/R.S-TFSUWV!WVcM宜Z[^eee`RЎbc6 Hf6kfy搈%%\fk.l~$fGNpn/q&q61@#/vXwv xN g,)g?z?MXzLJ|鮌ʓ 7$v tGltoLYnL- I{FˏaZ/u(h„kH!0'Vhƌ6rHX"d* 4lVky楛Ьi&:w3Рa蠤Jw)ԨR`j֬ p]ڼbKbZ-jWe#-ܘn޴cnР~/ǎ, $bVjf1p zfY,̔(8F)UXWOh {<8ل׆ F(q7 [a~LT%LSdhѣF.N;xVfźW-e :ۮ7&]w0{]_ @\qydH\cEv[e yYhiƚk&ltn 9!aDq!TI);/!LIe] v BM~7^UG@z{G}oɕ_MDBEq!x :;M7iƙgFi 7χg"("o.c2(ǜA:z)N)%F\`uYi#]JN8 eH\b5 -Īٙiej:A(23i.j޸\(ݏҕz.O:۔JxeY7+؊ZwlflkfRZgm!jrpm%n a,bW#rb>YoJ3PY@,[>1i0ST3u-bjqH yaoXڟ_Y6Rqph6rαl9n<3W@5{F ңVwOT] BsRbh{v8q!4(D^o @0)f AșeJZAi0aߓ7|$,Vau-< c8?d.@!;!~D!g-1WM\`VIzhA-`W]Fs "4cLFgЍo,M_(C:yGPG d!?XŌSW#z- !M$f{E68޼ Dd4˱1]{RƒyS L>աqJiH^3?:BsLӟv*] [ EUzGH2SLgs/fۙMmӛߴK8WR3-M:q7w%$=tS e?Y4Endž:s&zMV+Fяլl8*LʩNr3i.2%6cnSRO*͠6d$IQפ.Om]T-3UVu*q* :j`mXAV]kGWVxt+nז})6KS:=lxWt.vZm'Q"ϣR6'TsPXUoU/Zي0JZa^%Xn{W.ץ|_[e0m\tb't OSYJ;.x?+Вu]*Dד|aۇk־rů~s[2x60p1Դ85lӹpr&钴L8xoYQd1HzÖ.\)y-T yKl8]Kfn,G97lY -eķڞ<=7*k٦'dcM;+z\F ihsNr-yFWp7w sAkQ[Ծꀱy& kYיWBS:tw'zQ&lbf=CM[!ƣӔn{7}R6|5S-Y߷齒^:^k'3:َx7%e-w .Ѓ6Ǚ8*naܴ\Vqt\!9u8۞Wno1퇀3;Ξt>w9Aopi$H'ҏg<`P!sx O0y9^9mp=iïٌ8[f.{x^kCv;HWj%%~؅CM4_~! A HP;X_OD-u}_` QX9П__m.$ ;,(FMJZ aaYHx  f_ f% _ Ԡ[ğA5(ܟDVX!aBVa\!JCQY-az a vҡ!abX"//)a"".`##z,IMbnUr!zirbzbm^D(: a Z`y!! "//cc16a #ZN0E36$Bc4)^b&B&VT'z#8 #9)*:a;`" ==".c?/@d1~$#4<$Dڕ4EZ#jrd Dm@6ؤhI*d++ce.$l?$Nz7OE$Pv Q5Zd6b6j$(:eɁ RJ*bIRJց;",dZeP\ZR][65Cr\Υ3֥]^R#&"%6!7n$EGSff!f%cnJcKd֣X^>Yr g _˹%}fPufRj.SkB[eRR76%nl{'oo#p`Jpv4=V&>;#M"Z>gD'[X\Y'wevj'wvg}gBqd|"S`z'{wgIV'd~q¤`js~g*"hg唃8\N(.~Eal*lfz^haIb|cgrPz6g(tt&Zn'v2>㑒J@!,4*zAOXÇ"J,@⢋ iq@ Q SY+ȳv, e0ll`}͞3=0*qYI#;# Ե yrՄ^Xב̑m6)算{X(9ܺĨ|vk\*rÆհc-wm;/]s_͓aj?e.OrB l%f5B0Yω`}탟GS؏'P$ ( *5wy ~3AV@ "_4y0; XFHB 9B 1Ms>jvaQ" h9h(N X?Mj+bHD*RRrД"IKHN%IlҎyI(E9>*)/.%qfYj^P2Lt9,ge0W2LP& }Ndb]^IQӚ6WDmV¹렮*$9#$Qy⅞Qsd.PURBJD): 2R(:S}4dIK͓1Yi2zu3)Em MXSRV~dC}@#aT dZ\ SwʔA4Ѹ[kF4c-YT2uNE[ WR.u]ʂiͬJ`_~$/M0,(U>ڶ8C[O6_jU &eXIl{HG-<ގ:J *^D?Mr'\U@l HHַصv.j+hTػ[/w_Li La 48E(x >n~_aH-w$Wx≒GŸޔ3u@z1HNU4_6.X]\`9H^\'7#q3,yf-CIA:`3yJkkLnY쬳|06LɂlT$[N)IF;ZHⳙ*)kzӃ7UZۺ^ֳHArxfMjָ&9D"_oWb԰M4Vv2g/jv۪MnZfIP}'E]p7w$\tq7)~ ޺;]}o N5[ŪWN+BpNy0nߛ$8A.klqWwN0|&UgcNgNuai=R8]nRPG#u=l`oǎcl'U)=wߋˌƒm_3X/j!Oy/Iʕϼ/\{GOқOWֻgOϽwOO;ЏO[Ͼ{O~7 Uf3+ଖ?Gp%oo?D^U~bMD Xa K Bxd%t r+Wȳ(h(!#h[%XB')&+-c$7hMm0DN1'RxEE0(؃4,脑4!IIJ=`-6S"`i@%_BaJcbeht{j@l8enpH?r8+Qp6v z|؇ ;1FSdZ((mH{X$Hh8$(H1dbȊs(hq葊IUNXf؈y}(GxoXʈf *(Ԉ((G28XBh8LѨ(qw؎Iw1SxjϨ؏XC Y|i|䁋 y֐^В.YyɑYY "8Ȑh(SW*/ p0WQySB9c?JR \ٕtNU^"`Pf:V!lY/r$T9$V)-XjG(&)die/Um+oyqs uxIzJ| )]bkIX"qcw i7ZQ^%4YY/$"H`ɚa$2[T> 8^јq~^ܤEFqB+9rYtyBDpY%[ ԝӵ!HBG!sțÞ3MgFQ)CBivGŸVqҠĜ81ٟ1ٛ* Z!4EbYWߘJИ4ZrX¤@ G(Y*-Z/z1JX7HBPR⠘ɢࢋ JacetJh"R zRGK+]4Ƨ2dPj Ԩvw/1`E*mgx}Z#Vx"@I܄YGʕLjE9 :ګzR-("*7QԪ*:* J*&)ߚ'Z ZDZh( j4hສگt1+ʓ"$d"a]ٕTfS±!.#k(PK1(`/[ 1K35k5jD:=&?)dFHK;MKCb; UW{"Y[zҵ60KC2Kq4{P۱J7k۶"뙼 )uK*,;V\bn7~O+QKR%ok";&15Ik;y7%C#F#&YP,㹗PʻX҂+ӚKUG˸\[˼bC2Ӌ E4VEKݛUANj `@%;FT˽Y&kE + c (4-tL* q׾k}d6TQPR$lDD.UāYrȫC:l`J(?X[;E M2L46Ah<0R,y=r~ImUם= !)=\7ի և-}֎- 89Mc}1cנm/}48^=I{Z۶u-PԕߝޏsۖAeUG|}Նςҩq߀Jя^B>ªK彳*鍊Pǭ-!ڄ}߆ߣRHVR/.-Ε ^b/'i G;=^'?^$LF~Z6G nڃ Nn4%l_Zu.WH`(4 Q3ISHT9#-DdPm~`. HjQ}M^z+^-,j3aPN3AC.)4]Q^ 4&I͎fݩg)ȼ"]ɑjP M1QTxʏ^ZғsW5Vg\Y`1E(~ᾎ.!~^B^P~ͦPpH9 Ϭ/o&0tV?oo~! #%Z.))N~ 35N7;s1   J,>U߄W'`0O_>-.(n+ӑD \ɑerO9tX$^`BD?ooxE!Q7M_u:90e)S ڿZ9zR$ɑcS:{eӶ5; 36tBC,PQJƬ:sU Z+az-{6T}+w.WbF /u:bŌC~|SZn:l't"4:eB ] ؼ6e&rB'Dp@KZt9$jN>6K.캋Qӫ*>[S19`Z@>KPLB 14@6Xp@ F)/BqJXtEe|ƭ 3GĻ R!3I[I௿rH-tNV/ s -|L4iS3Eэ78uVdXbdj=NNМP]+S=֓=J[>J1fXlB5uSdU4[n59KEM$Ps>=IZCOQF{"'/I0]TSNU7%v)xQM^{7_V_ kpfy- _|.|xؘ'~qQpAq-H􄴂H#U RrMӗSyRKT9g P}eUk<01pxdqƛP7z|rrED:sQiJ0j+[/L*Pj=cwzL ~xzEwM{f޻*W}&2~n| VM\:`qwc) /<0xsHw:-g{h`v+xǼE+#^ys !=Q/ٛ }|K*b-}~>p~sr׉In'odHm{ gt ֮xW0/g0! {Bbo9[b=!Ep9>$\u@5ǑZIb%{D):1q"=rEP`X7Ȫ@c,#eP 0bD-m9ͽ9u|K):9kK O;H4K\B 6xoqIwϹU<+W< ˝ߺī\_\P|<'!Ct )—vQI0]v[:#}^1=!C':r݆Z'}nn-=_G}:CblSPa97]'!^\1lەw+(.N}o+}E?z3,5'__>w^SytFx-k'xa9uTHQ_ ˙ >漁;ϟ/673: { c;"4 >< 8kk ؾSRaq?'???ٻ "7;K⳵k |#+B,)/80 1k4,5t6T78D=աCNC+C?C2(qə  EBR/\Ö,az_T ylŞş Ơ$ơ|HsJ(,lCtāBԠGy,QDu\KV|ɷKPK7C9{Aa ,yvbŴ!١T25 #Tb:b2D#Ifo1 ϙ ol)`4 9@Z4HPH L,ILN$ 5D.( LlO0OAROQ`L[ H4̷OrO N*e̠HпYQrPƘPS NuQȚ-O9OKO]QV騉$ }SO R!"#Q!%U(TC}P(p҂R)R PR:Үd< s+2]iyO8::M <=ŋTTG2Cm??(PG HIRŘRQ%UBTԖ W23}4=UMU]UM Q[UUBETE%F5c別etVhQiPnoU ThN`UsQCҨSvwyALU|`aP>dq5RRS|6 ט +885WX(8WVqY%BٔUY $|]r}-~T,5XGמROulXQUZT1U WUU]rZq+m><[]MۖZ۶}0 Yل څֆ-ڿ=ڈ\j%å67m\ Q\EW̥>ܹ9[7r+77"FMTMm>ZݻEؼM\kv>ܕVS^%^=ӡR"mݒͿ^J[-[Z%Z!XXݥZe\X9X\u+Z$ WLQP`g^% \yRUv\9a&-a݈[r\9э0 hF+`kTi; !1ۗ;*HK? $<;::6 ro#}lPuZ9)K^7cc)EO 5f~cD0Z9^:^\; 0eLe&gST֋`eccéݸc@ZB]c @e<fu\Y@edPd%dj>^NOm3~4~5VeرVWUhgkiklp^_3lFc3rJLFK¡ΰl~gi+맣"Vbekmf%"6mMFmSg>۶Ƨhv6&`m n.nlVIdsrn,?О䂸oinno6̻|mmjg&1n@Vo\˕&Y'!pS8pn'fnԆՖ~N^ol]yq,$n?tqɢqŰq7w^\'r gm wm kk6vXi qrr ,-/o 0W ț2'3/456sss#o$'<7=G WgoL8l)BCOYt'GV!Itn9KO 3?MߖNu1p0_cIm᠔IŕPVoN 0G 0)s=«2  D6Ҵ-Rg_hiWKjlsvvl 1wBPu9o yw #{wW|P} ~x9aŶopqrׄsI0Sm z{Nj|~Ӗ܀l uk珄7?yP088nM@C?5-*&z7ٓoΔgۮGłճyHmn8gx)𹿄ûZX{ݟ'ګ_y?|yMWƷ'w͇{"z{TK8eԗzzz}}ė6H>4Hx}_z/|9|F (h0„ iĈ9&R8Q ƌ6r#Hs\)i#G*Wl%̘2gҬi果XyʠIBEMȏVzi 驪Ro`u'L"qT襰DE*lm%'=49hz/u ckx^c.Çi $ Crn5j4Ԫ]'^EjQ)SPF]ҪZz :IزΆImӸ9ڽ7&L0P+| 92ɔo[ƜYs琠&>[iX6mHeP\S֛o UW_YfsE'uvvqB %^x'BzMV n9r6iA 7yxn .x; qsyFtru(ƈ$X"v8^/z4z{((ZS '_lH(mՈnAyr%.w!DeuטefbV`y/9#{ yyԞ|(4lY2l)r*ɚde%8UKI;mn)De^`BgQ( r &^Zy#X,$*-[Ҙ_A! +, +0 +LZ2+klﱄkpYKӺ,bmO"- Ȼ;YopH<0°$1A 1;ɲeʱ ;*R6|̦l;Aa}%'tEI;tZM; uSS ![7 ]1f.$ھ-,m)2u[uwy]\oTxC+эkz1:z <ձ5z.=k v)% wGkUoHw9#L8g<1n^"(-ҫgQ{^ MJ' |>1Z>bs7l/~4;Pٹ^JcM&* ~- M_ D6,Ǭi},qVH?qw)'QO;4%4Or^E-HJLAiPũmNr_ HC5oxy<Α"ucd0={}#h"HB2?T"EŒʹ&9JЌ}x&NzzxBЛXȕIc"j)Nȥ%˃q(d')Oaʏ3fٿ5D^ÊȚf3:nFś] $)Q FN*]<}%F{a 1LΟ4d!ꡓ6U9T(P_R^ΜT'#yN_F =YHb^($KuR2u MkzSh7}Ġ2 !, [@*\ȰÇ#Bŋ3jȱǏ !V II#O\ɲ˗SœIӠ̚8sɓ͞@cJQ?*etӧPM6t*իX.u(׮`.((ٲhӪ]˶۷pʝKkv˷L`@X̘ǂ"KۡˆٌυB@tfΜѨ^cϙMi; w횂+G9)7oسkνËOk(S,8Ċ/~ Yrdʗ1F3hѡf~q&[nsvso 7!Wކv (yx^_{"|a}~gi  *ƠQXq#ViXfyyyq`z1NěpRD}laehF!r\!ej(Fi̱ãF(d)åJ9v駜rFr1fgfp)g tgzI 8(J$ ;JjZ饚fF+-w1櫰(yh@Ԡn *hf+,iN.,S lVVkN4P"ނeZ W[k/ 2B6{#l8\4FVŲfƷvܮ"L,\\w 2Gsd] 4}R-j*aǨ<+z2,C5]{o6mڀAlaqS*fhޥ,U3xZ4z?xNV^j^7}w7+š~g.{gpܳÑ.Z'mx}#x2So=iLKHjl &HA*0se64qZTwkBV8M94 g8x8@ \H"Q`ib Ch̄'W gB: G&1Z8$&qM?(Rъb Q8/.v:_ Y2Le"F#雕E1wt G %"@3KZ$#I6q\T 2&-~R &y yJ.R`e+JYrFܠ-[҄/nj'I4櫐LeA3CTrD>'<*yN4NL;rLADY{sf?πue:aBY.t CS$щ^QaFg̍rԣr"'A/ӞN I@AԘΐPM=R=HYRhΆ1DuhMU>նqGjά[,t,EY֥:5nMlmR᱐%`,@҂f7{KxVhJMֺv_pkK[ޥ*ld7Rݬf;Yϊv'+maSZugtEX ok =nhI/2{AVnkHmQq+^.˴M/w//o48~;O`)O 7*/ Ux?[a\O;L% &U'N-@"®w\&8r{yC.2\ޥ&ydI 2>Z޲&e |za2<-(jݬ3Y\[4ϐ,@5tӔA1 ϣ#M,M8 i-sډ^ FZ֋CjH)JG INtyk :Ďle[;nnM,i$mی=v&=ށtg oskAuMl87kX .H8;OoSq⺦r o_s̼7zr.ѝ}9ątA~1IIzӧN[XϺַ{`NhOpNxϻOO;񐏼'O[ϼ7{ A0^}Izs]o{p[=o~|ki_|NZ>y|_}}__~d O~7yS@? Jb30Wc7%DBNWZ(0Hc!L hxCX`xJ 耂06!h`#hk!+-(/(35$)؁؃>HCI4AAhC7W.ӵVKxMHmbEhWOYԅD؀F( HE'ghDtvx^Ȇ`xpU6$Kpxn!еVk}})h#sXDuHw8:XHCV?4=EDA'BRpbPE~+=k_ZPO=&Kf=8%8DBhcH~"%bFBSL،:Ҋ8pqz3h8Pth ((^XZh9PPx؏HhxhB)y\ А)yg!8˨"i$$)Ie,9.1Y5)cxYMTVy= \ٕ yb:7 @yphijIIKٔ a;4 tHTWq6Lҕ^ W ij/ K'( v5U~9f҇BgɘiiK IYti  a|y&9Sy}3ÉƉJ>œ 9ɗZ)ٝ) yޱc7Dv " !]7kt؄z $Z jw *?}šp 7U&(cʘ  4Zsҗ:@ Z"A$ZG*Oj  8Q:%[ڥ_A*CJEjgIZKMڦ~Q&rJtjvjxzڡ|P0bP0쩨 *TJu!wjD[ A*@V`zj- #:zZ `9J, 9 ] I0jZ~zB9QLNzJ:?AjՙN9je2/P[:*I`+J&:A[D4 б 4Z аz#2;58K鳊:'[)zTf{9jۚirh䔗ԩ2( xt!%)Z`;bLd#gjI)nKpLr۞VYry{@y\[tfgo{q;iWyй 8amRK_$ۤr;bȷh xbK[F˺뺐 +tV85݋kDeۼ[Л۷AwbUUJe& uK[dIK2˹!A ,*EiL2ҋԫ). gqq-B)[]L&<(I쮢]6t164c˹?9`N; Aqc )(n˷C3]<.ìL@lɼLkm1T5p5ټݼ\|m, -. $^o/qOtow^Ծ%Jⅿា^T/VYO(acٝlj_l6: BOeĒέ:ũ~j 1nA0QC?V$BXU4nDc!.,R:aIbD1P39uOA% QIbڴS *J9n5ך% TQ iRe{ [qJ-]',b_/$L@»Uиe9%\@@\bĉ/fܨG Ī˘fz {mܹu=SO`kUO:[zknܽծmVwU/` /z̼mIK{. #={ ;{ ӂKŻSx'?(\l2ЊPj{@{,Kŀ @ݨQD4<,tA84AD D!\"Cš@ hB8xؠBd3t5z-@C{124C@ 76 9>? Dz;MkC#8Q0.46/ 01T 2>W|Ef>Z[C==)<=a0m9ʎGRBSd>Tt>UTGVdG,*$Z:P"2hM ?|<i* Y ڤ2A4Z,)PjIM/|?ɮBX&)GyIjHA1(O9SlO; M ,|tƀM͆>I! GԤ; N\@"Q|NݒzN, [Ty ډZh\eɲ ڏ[TEHۮ0 p[L-].mW}WeZe}5~\m^=;5X amǵkQ^ \ϥMх:Vӭв=՝u]iەmY}Yٙ=N[Z 7\M^Xu^e0jܗ^g^^^mϭeU8%5=ee5:R[u[vUUweU߭W 6ޠE^T%=>]Ze ʠ ` `nV{- _+]]]]_1aP `\!6%#N`Ue0{E^}(.D⊵`b0c-c8,[qa[a=$Y>^9SB֟D^dEnxddIV V>s=;{IiC9jC9˥5zfg'QgEdugg/gz&Y{ |}gX aYf ^~#h >hu"hi N&gsRtigV釱i&zF{|v}?6qWݕ&֍N视jQ潄;`k:#>'m֥YVffY]^+b?.giii-l6%(h cNTN>FKHbk +Xi&&1m#XMmyv淼צݖ>)n8n1In\^kynH6noF^fm|ﴀ^XkioJo/f>l ^p pޏЙ.>>QX|羞_n6q Ah;冿dd Xs6O6Op!"?Nrr.&5' (wN)j7jr..rs$9p")p4Os6oslyQFk8H+X&޾DrJtYqAJNg:3s#qQpsspLNwUVOg=o} B@OtOw['tixDu*mabcFd;(OPqjRSv3Pu.>Hwuu]uyq{/}.P `v/34_vvgnp~pgk#vxt_owCwDwfwtFcWy\py{Ȃ;0}`@bǒ-k,ڴjײm-\+%dw%NWk%D1)l8Y &L(t!F$x1ƖA$iJ5f˼75ز>Eu)F5b8M_"n9nBnޛ>[`† KX%Q }`4i0jYZO6[QGnE q)tz!gQW^`G^t7z: ET{Y|vh|I@`^;$w .FWXURhapt"@!y&oA'ĝ t^ YИzX#`;գ yW)99܅'ؔTViNixGa*EW靜b9`iBcxY G{j('1KzZNBץbdZ&eW[r9*j{k^U뭇Ũخk#f"IFڤ^UNnZ*I.k+W8C/+A{od:V/Fƶ/GEp^pW"SZXqs1M7%9*72F.;hGҌFlSb+ADeY};7`@] ;Y}Q&lE}U#0[Y@?TH!yxG8)CWUl:~:HՖVZi`SJik1^w3CGa4#F׼@/zӓ^ؗ\ Aqj\IRg=U}>>s鮀/$^G;. J@Fp3^L.h+i0>̰}%<#NpDi GU#l o9pNР?!c⎈D*рLqȐ( eE2h=] 013#S!5O{> 2a C>s :DI$QHGwd@1Tp+Z<ȍ2 % U)sx1;ْGr}qCc&sBhNZƒl"78΍Bs% >w1r'◹{Pd? OC(N I> qjRg"F=*CЇZ3h63O~(V7m\!,[N9z'JW,!m#%v& ǿ3S AІvHvNmh/Pzu`I]2HP3",b7'Uk~u8㓑t6ʣ\%=۰t-hC8V9\+Ev,=LkR4̩A5A$6Yζ~i y!=]׻׾9Is l+ w8!o'=&KC3lJf:^rd;_9[g\&L?O^m^ r#~{j;xw)?u3^نël|N{z-babtQ#f$7l}o`{O޽y~hc>>nWd ?ITyї>`[Xa_lhq_yݞ[L-Y_ ߋ_yU EU]i =ZJ]NuF%[V]_\aK@{؟  E  ^"̠ 5 :AVL_]!Na%E6Fa`Y\!`ya!H_`a !oe2!!]"!", A*\ȰÇ#Dŋ3jȱǏ !V II#O\ɲ˗SœIӠ̚8sS͞@cJQ?*etӧPM6t*իX.u(׮`.((ٲhӪ]˶۷pʝK x꽻"CQĈq-(6øǐ HLh̠gC]i2SYװ[Ȟͧ۵uݡo N+W1HسkνË N1cȑ'K|3͜=wklnp7q)\sAyv (yYz!6b|S7}$w~]_dGkox -( :8]% a*$v`"{-c2.6j P&j`f&qrꧭE )ضk*nY챔6kf,8`p-j- ~(d䖋nG;+$wo0<:26 .CH,<\n.y,o.PL7m2:μGīs+xFkN6P/X]0i=|͵8\Dw6tk#'sS嘻l1-Z~{{}NLxdjh1!G.cu"Rk9kg:`nꈟݱGK4W/ sK]?cׯ8,s" `Ӱ'qOQ8OaI]ip GHB{Ѐ>|0 WqJH"j~9`2T kx>I!z Ј` ㈐N`M +(cE-V$(>

Oy$; :L& @Ғ(% 0%-E/QS(M)V-40jI~P^zRFRJ8f8}T(EI|ªQzZ7ETOJא58h ӝnUGyJغwM@^׾UlπMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԖWLTyլ kUָ>wLˬMl:u_:ŎMc'fv-YR34RHve.u yη8ꂼk0K!''#G d P g pxWp#?A…@8GU |yg^sRL{w[8{e»xgrnlJ|=y r߻ zՓO{a{Ȁ ŗx젾ϏE h>kQ~y_@KWU~b||ցz '#WЧ,g*$A7ܗ|X# hq H'(|*"JhxXO⁵p 7/P'L W}-('ag`4xAP88;8>("8BCHXJXxL~wRHSh'Y(ƅx WvX'YF=!z$)^ȆU<[!(JeЈjt%v#xF#{؇5D8KlXk6|H"H.H8~AX,HXx؊"8(6?+8HcwwHvy#8b#,"ć$ȍ荔H31?юyp0H u%pt0US=V"A7i.%$|1T#Ed,ّ!:$Y]82YR!HD-;I&)@)cX^r62uB؎ٔ8+Q9SYWY%O^i`)I5y}ilٓ)9qi\1TqTJI>L N) "()S)!$Z)G?p9 Ji+1Y>1ŕpYI9aɚIМΉ$- :Bch0ћ}0IyH"AHhWץ{03r?ٞ ii兟z)3IʜFr: Zϑɡʩڞ j "Z>iy)zWeYZ.zʞ7;91>9EzG0KyM+3QZ9jr:0$ %a*c 0ezҦS b3t ac|4DwF)A*eG"?s1|s:*u SCa}.LNJc)7꞊ZW0aJzX2:J*8J.=z*_wq! d빌39uRj*cxBʱ3Jraჯ.S2#[2w°s+s Z[0!;y혰> S / >6={PAKCk--M3{R V;Xۜ"۵SI0bK,:Ԛ*[m۞o qsu˵)Hf`cSIK+u tY#}#(ewt v ǡ9sZ*Gҁ;Kr #*(+\{ 8Qy5˚Ke0˸˳ɛk++64ٹ#-w˼y!_R%UR9hqZKv6U?)Al0 $5ʻ˽[{8%˾qUٵL%X/G׶PqlrYrxM4'VGTĚJF(j+{X%Px𺧪EU,W|Yŵm`|mb|dBfhRlmo qHBǯFz(|\(~Y\Ņ\w^6eWrrEUL"\As<%*FBl1VmoN1sWrkȰy*{̦:̊Qy0Zk @er|m kol8bN˔V)W\ϒu.4y̦ͨ<|=KL\Cڼu޼kL@*)Zu.nplB l \6]CV^}ЫΖp3" ]HcԚԜԫN ґ%DҥʙՃ_-7d[h-j>l=n][pM0rMtmv=J #%')+m^1،m`jٿƔЖk-@T=ΡΣʎկ1UW}X/m#5 ot۸]jp۟i-܈F?]e2˝ |bJ=V۪=޷q,M#X-m*bĝx[u*}I]AʩϫM Z>\]~W!MD'*:LRrz .(M*ʀGY|mdwp~M-3tpcj%Fns;KQ{h+YMvwN,?keD܍|RS .Yӄꪎ'P|mq9.ex1&}NÀ^[ I=l腐芞U>n‘vk.cYbꪞ~nI˴^[^2y &>Z.5숾Fa"wnW!.5~N^Ik\[Ppt#.Y/;/"~_x$5!/#w Jǃ-//>{%^R;Y=*[FH퐾>N_~ZN_acIfO.2?z9#WwOޞpLOA`i/OoO%)揍o-"(7/qpڞkbϮ3OPJOS"Sp) `ؐC$NPEB5~ؑG!$ I)UdK1QYM/tLgÆ[\ zQ 0agt* 2 UW8OmP6YJԮĉh΅QȮB6_yxq.SPzJG%KReCV\ 3,X ބ@Bwы.4n# Ho3k {s'O=UZ(ҤK':UXY5W Ż=Qڴkպ}Z.ݺwQopác& .s :LB3c424Ԗc!^lj $v[;K4+8SN抂.:⏺ ;Nt;#L X|9: "TO *nHZk;Rbek@jyygm^n9uuA!Xh!fTFp#>zӔnL;Ff2l3hv>;6;Kl`o^a 8pNbӏNOV*땯he`Lf{y|MoЊv;i Zꨩ.jzkk1>S%b{{i^_Y 3^<M $%rqۛFjW;xGqx,ΐ yXAn.ݨMo(E8Á/|+.b#k=c܇9OB8sD҅3 `NwZ&XA]x`Gu9a MO݋af 8đC: ]4D&҈;CjJ>DRM|"5@ uk_1Hm46M ^#:~ώwXwCǝ/S A1~)"T" @a\$9&2ϒ$4ɐxq`"AR͔c.*]̯UifY/jlG)ɶU)N*]Q Nڀ+RWQ6{C, ؠo.#G'K h, d g-Za@IbhWц6na5i3J1^^l1BV-מL3itK"dXսuaw׻ZVxM{1/z }+7X~Qߵ ns[u-';\#w7N+ *5aKïIE,6o'NVLP葏UVU+jr\d#X-^.Y2s}wH[|,X8iFvhulEJ89Jn3k`J)4Ld&B2C""BAZ#6c  B~fwA+a-w-'yWZw oݙ4]jX.q ]e lIʠ2f;[(F@mkEoZ67 Nu 7txzоo\GYp:3 GÏh17Cg -&G9V}l#O=s4 .sw[poGOU;$PԅBuQ)|,&zSX4AUve3g}m{Vܙ6Խ'P»OA}2}^TCO|'tt7nS&d_\~gdd!`>]xn[xχ^SGZ:5%![?L}Ԟ)@=jS>(q˻rùӹ9@ @lƐ9i<X~e>뛼̾>?K*:cB???$@1L[@;$'B.lA@ tB A~A,<þB.>#%RӪ΋ p̀TA?B"+#t$l %&|/'($һ+, .B/,+1K,C:89EZ>-`C,|l:CJl$4ȳȎ\4$)dcKut˷IvTK tE91~ԜKV$̢1%ZHjJxʨJ%Jij\!L MM(GDtl˝xK;`usMU\Ŝ@MۜleHVHQ!xB8Y-)  wN .+z̈wS̋@}* Q~E`Mi_C2xQI ܉ &_Pv#/!R"qEM"2+/xR( " T>̿ PE^9uNiP{PP  7 E-;K -eݛ  -RM=2LRZRjү) *,-mt}0 !S53eu 56uSSS;S9T`ѽqQ SueG MTARRRPU RS,]VMWUXSJP-[456}-PwP8VH+e-fuVhi] jkETlUTmeTn=@oTpTqTrTs=W"+UWa%uWxޘW,׻.u/0WW[ Xn5m)6PUX- Pe؞0#AQ5Pi%j5kul%mun؅5Y>W;W pٗYOP MU{Y| Z}Y2XMZXZi Մ V9ֆؠZSMU`)T)8DEuF}Ԫ\9[R[۾ Hپ,}5Dء }e\ȵB)P /) d׉EXхӍU>Iʌ=`;2]5 8`^hҙe]h$ =7%uܣȐ^ދ^ _uN9?m_z{ш[YmN] hb'F& IA^T7`ש!xn\uv5<52=AQ U Ԯauvݑ-!"v#F$'~b(^* n c1c)c14N56ƭ789:.;c>?@aCdE !^M*IJnKd)bdbb )bR.e2 գX iexzeZ[]a=ga[A.MH_:_f&bg6bhV*%jkƀlgOgP&g Lj^tVZ^Re` a0A Paf߅fv䉖f%kmf~ *kù0M|X_T;R|6Z99#yk$hLa}C=TliP> ʌhG@죰=ɷ~$ 7m* "³.ނ~พ6~FNNnPþ\N2XlТz{쿈ff9dlelZmJn`kV 1!ힸmmnk"kn.e<ydlǶi.ʶ즀&o>v^xmo*xoȌVNAk~ߤVf>Ho_n>pp p8=Y$(OqRwqqf?HAq9Y߾!w"s%GN' )춃n6.Q/0smW2לro׆oؖo^snsxsQ 769  s&@ B$*' /p` G(оўt/G&/R,N~-$7ilz;'2ШF^hvxvZiGj?Lvmt|wRqS r6 tgumvwẃwy zo{zYw x bT ,8{H|_{{py@{lKKxo=defqVGkl-FmvGqos 7\ r9\tQW^x#9B]xcf7Z[p\v}a^Oף~_7K(Fc Q2`f[,g6ZVfu!n)X(q- 18c(Z_D YI$NBY^}QY%WPi[v) YNV` >t i:a9Yw~b{I*j((: i~>XC)xJ_)fj f^V&kV<, 08p{ w:X|10묡2X֊<2w؊7)bwB SS[e:}iAʪf7l/g80 /001 \rl(|6S^gDݞu]+'DGWQnMfΫd 4Eݠ.E ROMV_uxsMg1}wڱ<eG؍O{:ȀM*j>ZH)0PB+( HfyZ[.:k}zũ>zQ~?ڵ~rp{ݺ3ڎW-o̓Zq֛ՙ$,i >OtHPb~[g?Z(;!MwZ÷/8\`if<ҨF5|n6[)6@ qxDDT$n<4=)w+u! 9΄(\ B2fTeF82Gr@ (#+ T" DE6z2(}Qri\&5 5NR͓_eE9F򔨔gqި Xp,ZHRy𳟤x Pӝ)B*PJx(DC:U\HXJ ґmK[2anڡDx)Lc д?)Nӝ>9uTUEUZՠ(*WU>TmYz#lm3 t1}k.+X԰e/۱6JXrmk=/pVAgp'v ҫޱբyX>dD^jJ+oeJ=pU vֹЕn{KFH.^բ]lyp^w}Aj[%}ۊN. 0};`wF0`2tb!Ï>] 0QC<#8EYUߴ0qP9~3Lw,8v^ʐY"JNd((F5Be| -A2Ml7rS@1bM4~I_j[=Ja5u9 pr}hw޿Ko{Av fcdNWfxTppoC\-qiCNԑx恾o5O0MJk,sP̝:+ncEuF+ tӍ}bsg]zpŏeJ"߅6YchBY<) MMLG W7pe  ro./˳W݆h/|;i[J\VP#]f]ndRe eTf̱UfVlW!,7*rDpAf^ZpÇ=ICbwiجGW CFIɓSzKe0cI͛CrēOꥨ"E ZʔS+P1bJԫVjW`ÊKٳhӪ]˶۷p&5,B !MYѢ9zha%Oĸ1K>.aŔIsM:s PD"MʴӨ ukѸc˞Mse7ވ 8 Fuw'e.hr1\+EKYwi7f}^h^Gy詗jYW,†n֟C| G܀ s,ؠ A8e!]xg/!BɔTW]\j~2Xc_7c )Yʑyh7R fUti衈F^A#Fvؙs?>qb*vdRcSZ9(je-* ]0>)bGj mz*'q&$Ԫ t-+. % լEBLv$׺-v 'bޅn^.d1%wܩ6_ک$z\ ,sm B2MWl1kq'iȣK[-bamXl03g[53b?CMG{R/U[ |t! -f$#D.%Baf_ny콇ߤn( ^+87عo헃Y.*Һ/.дGN9k[m讍Nf)~ ( 鞂L7Pg HP+a ( &/'e8pH%ZY(85J{TiМi)2ܩ7{ڬRBQbTZJ&SE@5PXJ^jKҿ"`XZ֛SVCՠ|5%XjWtzeO T4%<Kٙ%4m[s TUh-=X{#*uiͣvUikcmbIemq;ݞlKދ=_rV%zatKnmLiUzYۥtEJ՚za^7o կ_*[ .]mU)j7{ GL(NW0gL8αw@L"HN&;PL*[Xβ.{`L2qѽՎfE`9E3h\6pN%Kgoͤ3RL^xE ]T8r_mPFѐ.Mi=P#]IwZQEMjϙL5sVV_հ61vkUEԩؓkmugkVkud+ H{ #Mn>v&Qv}H"Mop,w q{>wNu7}'߳nnlow)pl$N4Y|f Ɔ<" xdc*8ϹʇJ/ne%#yyq"?oKЅ>̜95җt>Ht%[uGtݭ;Ol'zBq]9HHpx98 ȇP4Vv8膺,؈8ÊxRaos͘8x(ы׈X"APʱ9=Y"0I(bhgbO("--?-GX8x/A% Y ِm8QrɑL 8= &iD +9 -ِ09"NH%;ّ?#I&y)KٔY #R9Ti ~Zي @)B)'r"I1#/94i-ts }uɕ ɗE Py&O395Y2YuxIhx#E xI 烋هwry Yx8BWSlwǘ}d9yh4ҹt I / |9ɚ?9~hSph<iXiܸi{I=II))4 |gG9מ陠 ʝ9i1q:چ I6dtA Fj!JP9nHGVzYG\ڥ[ZF Zm'7TGJQz/SnUzV|z`]cZC@B6D:1F Hڦ[r RTzrxzڧ^bodJ fjqʦv*tJvju]ʩd穂Zzrk :i>#Kdnԡ JC\*'͙e7tJ;9*:;LZڬ G Gd6E55 1pٺCaq8¥ (h* ʫH::jZ;Q2*Aa􆰴k W+{C뱗 Fxj'Ҳگ2`7:< kxBDF3-4PkR[$Yv׵N >e;g{ iK!k[ C4bDAi oK n [i3#NH{ȐC 1q)a!^*W!VM+;3kg A;61\,|̧̓.APz|ܿ9ʠʵ E+ а\kl9<3#| ܤ FC<1!̃<]͇pV?t i9("m}яƗ ҹ0ҎPҳp9 YAAA7n91E[6:iKCTmX=Z | Qlʨ<,<.\}2$0 M=?%FK, Ps ۲-!!M1[,!NŔCH0$69cNŸ,eUp5l!~ݢٻٯlҠ-MR۬zڲ=Q qc܀R/ۺ84HP-}ȝʍ̝ _ܞ|5Vr5-m ޴3iTڠt}L۴-*9} .`}~j O(ݜEݪbݗ7s #.N!n#8= -^/ $MAK)=~/]NͽCEGI>YSOP}=}O%')J3P}rN0"2竭9DF~9L|!>9 ">H$&]^g>= 炝rtsH~.'BAٶ̍{No&ZTޔ&\.NҹБY41\TZ0osp!߮P,;"D.3 NNΣNCaÎC >/`[E ?<oʡ I|N7!?#o~9)W>-o/ ~@$oN:as z COEN9g/xU0{1.e gJ\#7 9ʓ'T[ @X]W_J'/1N5ϢMvm"̙mgО  iTfk؏dT۶1ܹHT WsĪUdɕb޼wo¥+Hrefٽ(垱wGdmۆU<Ĉ`MgGJD6зF?00@`3#X1{"n2ӌJ+m?JC-ּ*f[M7z 8*øVQn{NFL;S;{j3EFl 囯$뀲*?."DqS|P%|B*PD8C@q`ZkĮTQ]|ј݆nƑjL9rqGdz$RCRɤO<(+RKJu/ sL\ *7$ގ_ =NV'5V.?[ 6:`p&8Bge7p99 T^.Г=Iy 7q\Lk]O5gyIڑhV:{$AI[sk[cˮbSO(?ieTnF :RBc ξUIȅ|"g{r3T~UW}Ijݖ꩐!T_2Z#Y*s=+YW WeHjeEeZU+*Z-{^_j[Ğfcsc!R|5fy.qKD;FajjZvQSmDleK[nՎWֱUHZ?\7f]ߜVAD7zE _DS!0W7}{J7o^H_ ?"VEp(~9LaGX8' 67"&LN+JaX 5Ѝ#׽@q{!Ki{qZ0H\ FBE`>]1cn=3ײ|]E^[s`Wa%ys0O}*Ǧ}B Ut˚ViLv))kzUtWAeQw_>u{a2@3a= Y(uatna,bI6*<{%^ߴZBJZVm$L9**eń۪JNbEmp,R ܤ9t#EzqnYkZt6:PihD_p\5&䐜#9y_KUqq/yvY 符Knols͌:`b|[%LzU)z׍ð}=+{ξqtWx|Fޯ]1*5w=݁|-p?_ƒ>Ó>3۰{÷{#s(=+k|?ῄB;k)@ߋ@; 4[仭LB/T>5k磅賉9 #AY5~ ?^; tA D 꿴˽۽&|߻(<@BܨLK-/s12<ÚHÒ AK<;:K:̳C3C?>T?$=@AD+;DHDah!2#B 3Ȩj3JjTiQHbXȀTȪnX*AAiAGH+Kѡ; {H*aڡv:C H<UXFa%k\$oEFqvG0#G1&AǍPGvX7qM7KGwǀ\Ĉ@ /HȉlH*&IF, [m GH(G8IEYGbhGII;QGG<ʤ|tñJȶXHkHаʫ ԡBKF ,;Yv\M<)ڡKl.\ GPD,DʵH 2ȃtL(L+Ϡ̈˄K}Ix,NH\d͖lKw{K&ɻI8MM,J,̍\̃N J^xPN\%̐α$Il1C|Tل˛ĪxdNNA(N>P(tJǔȤJhPPN $͓,ϔ,lO p{ }4){ AYuGRgk m i.+ '}H(](H /R+E,M -4}.CS\O,S8S=5M6U7]89M:;Ј!]dN=bqreQ6ōsmk!XشxWH)|.ݡf |V W~ X~HYe`@ dC`D`E F&2Mde#Xaghcxafff_n&eogc!)i*~°R=R$'iir7iqknc~ck^e!qNbfu꽀j>NAB(~&Ȟ찖 F V يՎVk↕v 2zju6lW8>ņXhi j>`If&ֈ6GٱfnmT[֒/2Bo6Ζ6iŨo(kooBiK>n~ވlLUxPjT jYj!1KΞٺІHՈ ܳi'Rx>pGd8H 5uឭȪ00ߠvē!"#O$G%+'(G)]#g+W ,-?s2sW"3sB5O)i7o8Ǫ9:'Ns ?@A/t8MtZhtxrIG+?,tr;5uQ4/}QT89;u$\]rC%de ;d_IoJghovQnR wW/w #BTdwpw'HO^wDw&w8~rbrOQ^om 7 uzjzwgRv࠭[B?:lv/jU)Tm7xzVouEA;bt|/ |]z$Oo zȗɯz7{A@ ϯB}xA| }~G|or'y1{|?{ONGe K1LPĈ&2h"7b#!"Gr*i2 ʔ9Vl ̘dfZY&Ι: Р`(ҤJ2m)ԨRU2fZz+SbdžiRִ-\HZucx ُ 'Nd1nj &dXa)Zh"hF~G?Yv'SL'mq܉ %:T.‡Z֮`Î{̴.ֻZ``O vxqcƏ!LrÇMТI6 YqZkl6V/ :V[PFj(qXeWEҜsTZ.MWSuo rwqh(a!6bȤdw{X|q~G(xO Z 6aMFlx&gJ& bW]HuIy }RE J]1%J6esH:iZX h$zzʤ:3*Ib8xQNb:mdXz!"+]U;y,Zu9X|'Kh(>ZdVzim$;wCj`':JmZ+ ,&0P- -ց+rۭBni(jͽ都k/nLZRXq +4RKrVSq#=hR *v^b2ʡ̲0,ֻ6q3lm>[ʫ* 5LP;+R= ^vMx`%6{v2ڠܩ{}L|ZR#x\18eUcl~9o%6d*+ʲ1荆֮izܫp {cnk财7>5eU' +jTNLJobBa1c] @=OncZR++h]N('b"a2VаSZ*0K> H^" aajKN{,iS!Rڧ 0Rqa֐7C絏 @ b HΡsZ]SQj87+oH8ڎfdS(1Qmt`)!~K:AdOH֌|"")IQv$2IEuR2dțbA)QJ=r\ Ieȴ9<]"E9rv|0Ⱦ.D29)"61cyM4fӎde\ $3$gHtPYtM;oExƳ[t_=)RdG;qbNsZґu. ә 6Q32Q,C|2Rŕ&59gcz}4E)VGZһ+Ri+X:3MkS>P5#Ts);ꭒ1z-*bU8t,`R0UiU:?\2T\x^]׺>jޤ*0RȪD_WVX+A~63A-]IkW.v5Z͵@7BO=/&u;~udJ^;{Ѭ[= WFdkڒa7{[_-&ʴCR8~joS^V)x=qK.k_X]ޝx a\I<#W9ZUo yD~+D2eJI-s٨-ͭ~`P)uM 02<9Ar5D2lbaYDrM嵄Wc.4ilƜ MiZIglD5aeNYVsshD+,cHM=4P,6[hœ>6< PZJ>~\f7ֵVkGZ@f5La ؾ16/Yt.r chafa-canvas ChafaCanvas chafa_canvas_new chafa_canvas_new_similar chafa_canvas_ref chafa_canvas_unref chafa_canvas_peek_config chafa_canvas_draw_all_pixels chafa_canvas_print chafa_canvas_get_char_at chafa_canvas_set_char_at chafa_canvas_get_colors_at chafa_canvas_set_colors_at chafa_canvas_get_raw_colors_at chafa_canvas_set_raw_colors_at chafa_canvas_build_ansi chafa_canvas_set_contents_rgba8
chafa-canvas-config ChafaPixelMode ChafaColorSpace ChafaCanvasMode ChafaDitherMode ChafaColorExtractor ChafaOptimizations ChafaCanvasConfig chafa_canvas_config_new chafa_canvas_config_copy chafa_canvas_config_ref chafa_canvas_config_unref chafa_canvas_config_get_geometry chafa_canvas_config_set_geometry chafa_canvas_config_get_cell_geometry chafa_canvas_config_set_cell_geometry chafa_canvas_config_get_pixel_mode chafa_canvas_config_set_pixel_mode chafa_canvas_config_get_canvas_mode chafa_canvas_config_set_canvas_mode chafa_canvas_config_get_color_extractor chafa_canvas_config_set_color_extractor chafa_canvas_config_get_color_space chafa_canvas_config_set_color_space chafa_canvas_config_get_preprocessing_enabled chafa_canvas_config_set_preprocessing_enabled chafa_canvas_config_peek_symbol_map chafa_canvas_config_set_symbol_map chafa_canvas_config_peek_fill_symbol_map chafa_canvas_config_set_fill_symbol_map chafa_canvas_config_get_transparency_threshold chafa_canvas_config_set_transparency_threshold chafa_canvas_config_get_fg_only_enabled chafa_canvas_config_set_fg_only_enabled chafa_canvas_config_get_fg_color chafa_canvas_config_set_fg_color chafa_canvas_config_get_bg_color chafa_canvas_config_set_bg_color chafa_canvas_config_get_work_factor chafa_canvas_config_set_work_factor chafa_canvas_config_get_dither_mode chafa_canvas_config_set_dither_mode chafa_canvas_config_get_dither_grain_size chafa_canvas_config_set_dither_grain_size chafa_canvas_config_get_dither_intensity chafa_canvas_config_set_dither_intensity chafa_canvas_config_get_optimizations chafa_canvas_config_set_optimizations
chafa-features ChafaFeatures chafa_get_builtin_features chafa_get_supported_features chafa_describe_features
chafa-symbol-map CHAFA_SYMBOL_WIDTH_PIXELS CHAFA_SYMBOL_HEIGHT_PIXELS ChafaSymbolTags ChafaSymbolMap chafa_symbol_map_new chafa_symbol_map_copy chafa_symbol_map_ref chafa_symbol_map_unref chafa_symbol_map_add_by_tags chafa_symbol_map_add_by_range chafa_symbol_map_remove_by_tags chafa_symbol_map_remove_by_range chafa_symbol_map_apply_selectors chafa_symbol_map_get_allow_builtin_glyphs chafa_symbol_map_set_allow_builtin_glyphs chafa_symbol_map_add_glyph
chafa-util CHAFA_VERSION_MIN_REQUIRED CHAFA_VERSION_MAX_ALLOWED CHAFA_VERSION_1_0 CHAFA_VERSION_1_2 CHAFA_VERSION_1_4 CHAFA_VERSION_1_6 CHAFA_VERSION_1_8 ChafaPixelType chafa_calc_canvas_geometry
chafa-term-info CHAFA_TERM_SEQ_LENGTH_MAX ChafaTermSeq ChafaTermInfo CHAFA_TERM_INFO_ERROR ChafaTermInfoError chafa_term_info_new chafa_term_info_copy chafa_term_info_ref chafa_term_info_unref chafa_term_info_get_seq chafa_term_info_set_seq chafa_term_info_have_seq chafa_term_info_supplement chafa_term_info_emit_reset_terminal_soft chafa_term_info_emit_reset_terminal_hard chafa_term_info_emit_reset_attributes chafa_term_info_emit_clear chafa_term_info_emit_cursor_to_pos chafa_term_info_emit_cursor_to_top_left chafa_term_info_emit_cursor_to_bottom_left chafa_term_info_emit_cursor_up chafa_term_info_emit_cursor_down chafa_term_info_emit_cursor_left chafa_term_info_emit_cursor_right chafa_term_info_emit_cursor_up_1 chafa_term_info_emit_cursor_down_1 chafa_term_info_emit_cursor_left_1 chafa_term_info_emit_cursor_right_1 chafa_term_info_emit_set_scrolling_rows chafa_term_info_emit_cursor_up_scroll chafa_term_info_emit_cursor_down_scroll chafa_term_info_emit_insert_cells chafa_term_info_emit_delete_cells chafa_term_info_emit_insert_rows chafa_term_info_emit_delete_rows chafa_term_info_emit_enable_cursor chafa_term_info_emit_disable_cursor chafa_term_info_emit_enable_echo chafa_term_info_emit_disable_echo chafa_term_info_emit_enable_insert chafa_term_info_emit_disable_insert chafa_term_info_emit_enable_wrap chafa_term_info_emit_disable_wrap chafa_term_info_emit_invert_colors chafa_term_info_emit_set_color_fg_16 chafa_term_info_emit_set_color_bg_16 chafa_term_info_emit_set_color_fgbg_16 chafa_term_info_emit_set_color_fg_256 chafa_term_info_emit_set_color_bg_256 chafa_term_info_emit_set_color_fgbg_256 chafa_term_info_emit_set_color_fg_direct chafa_term_info_emit_set_color_bg_direct chafa_term_info_emit_set_color_fgbg_direct chafa_term_info_emit_repeat_char chafa_term_info_emit_begin_sixels chafa_term_info_emit_end_sixels chafa_term_info_emit_begin_kitty_immediate_image_v1 chafa_term_info_emit_end_kitty_image chafa_term_info_emit_begin_kitty_image_chunk chafa_term_info_emit_end_kitty_image_chunk chafa_term_info_emit_begin_iterm2_image chafa_term_info_emit_end_iterm2_image
chafa-term-db ChafaTermDb chafa_term_db_new chafa_term_db_copy chafa_term_db_ref chafa_term_db_unref chafa_term_db_get_default chafa_term_db_detect chafa_term_db_get_fallback_info
chafa-1.8.0/docs/chafa.xml000066400000000000000000000306231411352071600153020ustar00rootroot00000000000000 chafa chafa Developer Hans Petter Jansson chafa 1 User Commands chafa Character art facsimile generator chafaOPTIONIMAGE Description chafa is a utility that converts all kinds of images, including animated GIFs, into (potentially animated) ANSI/Unicode character output that can be displayed in a terminal. It supports alpha transparency and multiple color modes and color spaces, and combines a range of Unicode characters for optimal output. You can specify one or more input files, but the default behavior is slightly different with multiple files -- for instance, animations will not loop forever when there is more than one input file. Options Background color of display (color name or hex). Partially transparent input will be blended with this color. Color names are based on those provided with X.Org. Defaults to black. Clear screen before processing each file. Set output color mode; one of [none, 2, 8, 16, 240, 256, full]. Defaults to full (24-bit). The 240-color mode is recommended over the 256-color one, since the lower 16 colors are unreliable and tend to differ between terminals. 16-color mode will use aixterm extensions to produce 16 foreground and background colors. 2-color mode will only emit the ANSI codes for reverse color and attribute reset, while "none" will emit no ANSI color codes whatsoever. In sixel mode, "full" will dynamically generate a 256-color palette for each image or animation frame. The other modes refer to built-in palettes. "none" and "2" are interchangeable and will use the specified foreground/background colors (see --fg and --bg). Method for extracting color from an area; one of [average, median]. Median normally produces crisper output, while average may perform better on noisy images. Defaults to average. Color space used for quantization; one of [rgb, din99d]. Defaults to rgb, which is faster but less accurate. Type of dithering to apply during quantization. One of [none, ordered, diffusion]. "Bayer" is a synonym for "ordered", and "fs" (Floyd-Steinberg) is a synonym for "diffusion". Dimensions of grain used when dithering. Specified as width x height, where each can be one of [1, 2, 4, 8] pixels. One character cell is by definition 8 pixels across in both dimensions. Defaults to 4x4 in symbol mode and 1x1 in sixel mode. Intensity of dithering pattern. Ranges from 0.0 to infinity, with 1.0 considered neutral. Lower values tend to reduce the amount of dithering done, while higher values increase it. In practice, values higher than 10.0 are unlikely to produce useful results. Time to show each file, in seconds. Defaults to zero for still images and for animations when multiple files are specified. If a single animation is specified, defaults to infinite. Animations will always be played through at least once, even if duration is e.g. zero. Foreground color of display (color name or hex). Together with the background color specified by --bg, this specifies the terminal's palette in color modes 2 and none. Color names are based on those provided with X.Org. Defaults to white. Leave the background color untouched. This produces character-cell output using foreground colors only, and will avoid resetting or inverting the colors. Specify character symbols to use for fill/gradients. Defaults to none. Usage is similar to that of --symbols; see below. Target font's width/height ratio. Can be specified as a real number or a fraction. Defaults to 1/2. Set output format; one of [iterm, kitty, sixels, symbols]. The default is iterm, kitty or sixels if the connected terminal supports one of these, falling back to symbols ("ANSI art") otherwise. Load glyph information from file, which can be any font file supported by FreeType (TTF, PCF, etc). The glyph outlines will replace any existing outlines, including builtins. Useful in symbol mode for custom font support or for improving quality with a specific font. Note that this only makes sense if the output terminal is using a matching font. Can be specified multiple times. Show a brief help text. Invert video. For display with bright backgrounds in color modes 2 and none. Swaps --fg and --bg. Compress the output by using control sequences intelligently [0-9]. 0 disables, 9 enables every available optimization. Defaults to 5, except for when used with "-c none", where it defaults to 0. Image preprocessing [on, off]. Defaults to on with 16 colors or lower, off otherwise. This enhances colors and contrast prior to conversion, which can be useful in low-color modes. Set maximum output dimensions in columns and rows. By default this will be the size of your terminal, or 80x25 if size detection fails. Set the speed animations will play at. This can be either a unitless multiplier (fractions are allowed), or a real number followed by "fps" to apply a specific framerate. Stretch image to fit output dimensions; ignore aspect. Implies --zoom. Specify character symbols to employ in final output. See below for full usage and a list of symbol classes. Threshold above which full transparency will be used [0.0 - 1.0]. Setting this to 0.0 will render a blank image, while a value of 1.0 will replace any transparency with the background color (configurable with --bg). Show version, feature and copyright information. Watch a single input file, redisplaying it whenever its contents change. Will run until manually interrupted or, if --duration is set, until it expires. How hard to work in terms of CPU and memory [1-9]. 1 is the cheapest, 9 is the most accurate. Defaults to 5. Allow scaling up beyond one character per pixel. Symbols Accepted classes for --symbols are [all, none, space, solid, stipple, block, border, diagonal, dot, quad, half, hhalf, vhalf, inverted, braille, technical, geometric, ascii, legacy, sextant, wedge, wide, narrow]. Some symbols belong to multiple classes, e.g. diagonals are also borders. You can specify a list of classes separated by commas, or prefix them with + and - to add or remove symbols relative to the existing set. The ordering is significant. The default symbol set is block+border+space-wide-inverted for all modes except "none", which uses block+border+space-wide (including inverse symbols). Examples chafa in.gif Show a potentially animated GIF image in the terminal. If this is an animation, it will run until the user generates an interrupt (typically ctrl-c). All parameters will be autodetected based on the current environment. chafa -c full -s 200 in.gif Like the above, but force truecolor output that is 200 characters wide and calculate the height preserving the aspect of the original image. chafa -c 16 --color-space din99d --symbols -dot in.jpg Generate 16-color output with perceptual color picking and avoid using dot symbols. chafa -c none --symbols block+border-solid in.png Generate uncolored output using block and border symbols, but avoid the solid block symbol. Further Reading See the Chafa homepage for more information. Author Written by Hans Petter Jansson hpj@hpjansson.org. chafa-1.8.0/docs/manpage.css000066400000000000000000000132741411352071600156430ustar00rootroot00000000000000/* ===== * * Reset * * ===== */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; vertical-align: baseline; } /* HTML5 display-role reset for older browsers */ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } ol, ul { list-style: none; } blockquote, q { } table { border-collapse: collapse; border-spacing: 0; } a:focus { outline: none; } /* ============ * * Theme Styles * * ============ */ body { box-sizing: border-box; color:#000; font-size: 16px; font-family: 'yorktenslabnormregular', serif; line-height: 1.5; -webkit-font-smoothing: antialiased; background: #f2f2f2; border-top: 0px solid #111; border-bottom: 1px solid #111; position: relative; max-width: 640px; padding: 10px 10px; margin: 0px auto; } h1, h2, h3, h4, h5, h6 { margin: 10px 0; font-weight: 600; color:#000; letter-spacing: -0.5px; } h1 { font-size: 36px; font-weight: 600; } h2 { padding-bottom: 5px; font-size: 32px; background: url('../img/bg-hr.png') repeat-x bottom; } h2 code { font-size: 24px; } h3 { font-size: 24px; } h4 { font-size: 21px; } h5 { font-size: 18px; } h6 { font-size: 16px; } p { margin: 10px 0 15px 0; } footer p { color: #f2f2f2; } a { text-decoration: none; color: #007ee0; text-shadow: none; } a:visited { color: #802acb; } footer a:hover { color: #f2f2f2; background-color: #007ee0; } em { font-style: italic; } strong { font-weight: bold; } img { position: relative; margin: 0 auto; max-width: 739px; padding: 5px; margin: 10px 0 10px 0; } pre, code { width: 100%; color: #111; background-color: #fff; font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; font-size: 14px; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } pre { width: 100%; padding: 4px; box-shadow: 0 0 4px rgba(0,0,0,.1); overflow: auto; } code { padding: 3px; margin: 0 3px; white-space: nowrap; box-shadow: 0 0 10px rgba(0,0,0,.1); } pre code { display: block; box-shadow: none; } blockquote { color: #666; margin-bottom: 20px; padding: 0 0 0 20px; border-left: 3px solid #bbb; } ul, ol, dl { margin-bottom: 15px } ul li { list-style: outside; padding-left: 0em; margin-left: 1em; } ol li { list-style: decimal inside; padding-left: 0em; margin-left: 1em; } dl dt { font-weight: bold; } dl dd { padding-left: 20px; } dl p { padding-left: 20px; } hr { height: 1px; margin-bottom: 5px; border: none; background: url('../images/bg-hr.png') repeat-x center; } table { border: 1px solid #383838; margin-bottom: 20px; text-align: left; } th { font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; padding: 4px; background: #383838; color: #fff; } td { padding: 4px; border: 1px solid #383838; } form { background: #f2f2f2; } img { /* width: 100%; */ max-width: 100%; } figure { width: 32%; display: inline-block; text-align: center; margin: 10px 0px 0px 0px; clear: text; padding: 0; vertical-align: top; } figure img { margin: 0; padding: 0; } figcaption { font-style: italic; margin: 0; padding: 0; } /* ===== * * Fonts * * ===== */ /*! * Web Fonts from Fontspring.com * * All OpenType features and all extended glyphs have been removed. * Fully installable fonts can be purchased at http://www.fontspring.com * * The fonts included in this stylesheet are subject to the End User License you purchased * from Fontspring. The fonts are protected under domestic and international trademark and * copyright law. You are prohibited from modifying, reverse engineering, duplicating, or * distributing this font software. * * (c) 2010-2019 Fontspring * * The fonts included are copyrighted by the vendor listed below. * * Vendor: Insigne Design * License URL: https://www.fontspring.com/licenses/insigne/webfont */ @font-face { font-display: swap; font-family: 'yorktenslabnormregular'; src: url('../yorktenslabnormregular-webfont.woff2') format('woff2'), url('../yorktenslabnormregular-webfont.woff') format('woff'); font-weight: normal; font-style: normal; } /* =================== * * Small Device Styles * * =================== */ @media screen and (max-width: 500px) { body { font-size: 16px; min-width: 320px; max-width: 480px; } h1 { font-size: 28px; } h2 { font-size: 24px; } h3 { font-size: 21px; } h4 { font-size: 18px; } h5 { font-size: 14px; } h6 { font-size: 12px; } code, pre { min-width: 320px; max-width: 480px; font-size: 11px; } img { width: 100%; max-width: 100%; } figure { width: 100%; max-width: 100%; padding: 5px; } figure.two { width: 100%; max-width: 100%; padding: 5px; } } chafa-1.8.0/docs/style.css000066400000000000000000000340461411352071600153730ustar00rootroot00000000000000body { font-family: cantarell, sans-serif; font-size: 16px; line-height: 1.5em; } .synopsis, .classsynopsis { /* tango:aluminium 1/2 */ background: #eeeeec; background: rgba(238, 238, 236, 0.5); border: solid 1px rgb(238, 238, 236); padding: 0.5em; } .programlisting { /* tango:sky blue 0/1 */ /* fallback for no rgba support */ background: #e6f3ff; border: solid 1px #729fcf; background: rgba(114, 159, 207, 0.1); border: solid 1px rgba(114, 159, 207, 0.2); padding: 0.5em; } .variablelist { padding: 4px; margin-left: 3em; } .variablelist td:first-child { vertical-align: top; } span.nowrap { white-space: nowrap; } div.gallery-float { float: left; padding: 10px; } div.gallery-float img { border-style: none; } div.gallery-spacer { clear: both; } a, a:visited { text-decoration: none; /* tango:sky blue 2 */ color: #3465a4; } a:hover { text-decoration: underline; /* tango:sky blue 1 */ color: #729fcf; } div.informaltable table { border-collapse: separate; border-spacing: 1em 0.3em; border: none; } div.informaltable table td, div.informaltable table th { vertical-align: top; } .function_type, .variable_type, .property_type, .signal_type, .parameter_name, .struct_member_name, .union_member_name, .define_keyword, .datatype_keyword, .typedef_keyword { text-align: right; } /* dim non-primary columns */ .c_punctuation, .function_type, .variable_type, .property_type, .signal_type, .define_keyword, .datatype_keyword, .typedef_keyword, .property_flags, .signal_flags, .parameter_annotations, .enum_member_annotations, .struct_member_annotations, .union_member_annotations { color: #888a85; } .function_type a, .function_type a:visited, .function_type a:hover, .property_type a, .property_type a:visited, .property_type a:hover, .signal_type a, .signal_type a:visited, .signal_type a:hover, .signal_flags a, .signal_flags a:visited, .signal_flags a:hover { color: #729fcf; } td p { margin: 0.25em; } div.table table { border-collapse: collapse; border-spacing: 0px; /* tango:aluminium 3 */ border: solid 1px #babdb6; } div.table table td, div.table table th { /* tango:aluminium 3 */ border: solid 1px #babdb6; padding: 3px; vertical-align: top; } div.table table th { /* tango:aluminium 2 */ background-color: #d3d7cf; } h4 { color: #555753; margin-top: 1em; margin-bottom: 1em; } hr { /* tango:aluminium 1 */ color: #d3d7cf; background: #d3d7cf; border: none 0px; height: 1px; clear: both; margin: 2.0em 0em 2.0em 0em; } dl.toc dt { padding-bottom: 0.25em; } dl.toc > dt { padding-top: 0.25em; padding-bottom: 0.25em; font-weight: bold; } dl.toc > dl { padding-bottom: 0.5em; } .parameter { font-style: normal; } .footer { padding-top: 3.5em; /* tango:aluminium 3 */ color: #babdb6; text-align: center; font-size: 80%; } .informalfigure, .figure { margin: 1em; } .informalexample, .example { margin-top: 1em; margin-bottom: 1em; } .warning { /* tango:orange 0/1 */ background: #ffeed9; background: rgba(252, 175, 62, 0.1); border-color: #ffb04f; border-color: rgba(252, 175, 62, 0.2); } .note { /* tango:chameleon 0/0.5 */ background: #d8ffb2; background: rgba(138, 226, 52, 0.1); border-color: #abf562; border-color: rgba(138, 226, 52, 0.2); } div.blockquote { border-color: #eeeeec; } .note, .warning, div.blockquote { padding: 0.5em; border-width: 1px; border-style: solid; margin: 2em; } .note p, .warning p { margin: 0; } div.warning h3.title, div.note h3.title { display: none; } p + div.section { margin-top: 1em; } div.refnamediv, div.refsynopsisdiv, div.refsect1, div.refsect2, div.toc, div.section { margin-bottom: 1em; } /* blob links */ h2 .extralinks, h3 .extralinks { float: right; /* tango:aluminium 3 */ color: #babdb6; font-size: 80%; font-weight: normal; } .lineart { color: #d3d7cf; font-weight: normal; } .annotation { /* tango:aluminium 5 */ color: #555753; font-weight: normal; } .structfield { font-style: normal; font-weight: normal; } acronym,abbr { border-bottom: 1px dotted gray; } /* code listings */ .listing_code .programlisting .normal, .listing_code .programlisting .normal a, .listing_code .programlisting .number, .listing_code .programlisting .cbracket, .listing_code .programlisting .symbol { color: #555753; } .listing_code .programlisting .comment, .listing_code .programlisting .linenum { color: #babdb6; } /* tango: aluminium 3 */ .listing_code .programlisting .function, .listing_code .programlisting .function a, .listing_code .programlisting .preproc { color: #204a87; } /* tango: sky blue 3 */ .listing_code .programlisting .string { color: #ad7fa8; } /* tango: plum */ .listing_code .programlisting .keyword, .listing_code .programlisting .usertype, .listing_code .programlisting .type, .listing_code .programlisting .type a { color: #4e9a06; } /* tango: chameleon 3 */ .listing_frame { /* tango:sky blue 1 */ border: solid 1px #729fcf; border: solid 1px rgba(114, 159, 207, 0.2); padding: 0px; } .listing_lines, .listing_code { margin-top: 0px; margin-bottom: 0px; padding: 0.5em; } .listing_lines { /* tango:sky blue 0.5 */ background: #a6c5e3; background: rgba(114, 159, 207, 0.2); /* tango:aluminium 6 */ color: #2e3436; } .listing_code { /* tango:sky blue 0 */ background: #e6f3ff; background: rgba(114, 159, 207, 0.1); } .listing_code .programlisting { /* override from previous */ border: none 0px; padding: 0px; background: none; } .listing_lines pre, .listing_code pre { margin: 0px; } @media screen { /* these have a as a first child, but since there are no parent selectors * we can't use that. */ a.footnote { position: relative; top: 0em ! important; } /* this is needed so that the local anchors are displayed below the naviagtion */ div.footnote a[name], div.refnamediv a[name], div.refsect1 a[name], div.refsect2 a[name], div.index a[name], div.glossary a[name], div.sect1 a[name] { display: inline-block; position: relative; top:-5em; } /* this seems to be a bug in the xsl style sheets when generating indexes */ div.index div.index { top: 0em; } body { padding-top: 2.5em; max-width: 60em; } p { max-width: 60em; } /* style and size the navigation bar */ table.navigation#top { position: fixed; background: #e2e2e2; border-bottom: solid 1px #babdb6; border-spacing: 5px; margin-top: 0; margin-bottom: 0; top: 0; left: 0; z-index: 10; } table.navigation#top td { padding-left: 6px; padding-right: 6px; } .navigation a, .navigation a:visited { /* tango:sky blue 3 */ color: #204a87; } .navigation a:hover { /* tango:sky blue 2 */ color: #3465a4; } td.shortcuts { /* tango:sky blue 2 */ color: #3465a4; font-size: 80%; white-space: nowrap; } td.shortcuts .dim { color: #babdb6; } .navigation .title { max-width: none; margin: 0px; font-weight: normal; } } @media screen and (min-width: 60em) { /* screen larger than 60em */ body { margin: auto; } } @media screen and (max-width: 60em) { /* screen less than 60em */ #nav_hierarchy { display: none; } #nav_interfaces { display: none; } #nav_prerequisites { display: none; } #nav_derived_interfaces { display: none; } #nav_implementations { display: none; } #nav_child_properties { display: none; } #nav_style_properties { display: none; } #nav_index { display: none; } #nav_glossary { display: none; } .gallery_image { display: none; } .property_flags { display: none; } .signal_flags { display: none; } .parameter_annotations { display: none; } .enum_member_annotations { display: none; } .struct_member_annotations { display: none; } .union_member_annotations { display: none; } /* now that a column is hidden, optimize space */ col.parameters_name { width: auto; } col.parameters_description { width: auto; } col.struct_members_name { width: auto; } col.struct_members_description { width: auto; } col.enum_members_name { width: auto; } col.enum_members_description { width: auto; } col.union_members_name { width: auto; } col.union_members_description { width: auto; } .listing_lines { display: none; } } @media print { table.navigation { visibility: collapse; display: none; } div.titlepage table.navigation { visibility: visible; display: table; background: #e2e2e2; border: solid 1px #babdb6; margin-top: 0; margin-bottom: 0; top: 0; left: 0; height: 3em; } } div.subindex { margin: 2em 7em 1em 0em; padding-left: 90px; background-position: 0 10px; background-repeat: no-repeat; min-height: 96px; } div.subindex p { color: #666; } div.subindex h2 { padding: 0; margin: 0; font-size: 230%; } div.subindex h2 a { color: #babdb6; text-decoration: inherit; } div.subindex h2 a:hover { text-decoration: underline; } div.page_title { border: none; } a.external, a.doc-link { text-decoration: none; } a.external:hover, a.doc-link:hover { text-decoration: underline; } h1 { color: #c4a000; text-shadow: white 0 -2px; border-bottom: 1px solid #d3d7cf; } h2, h3 { color: #c4a000; } div#subindex-references { background-image: url(api-reference.png); } div#subindex-guides { background-image: url(guides.png); } div#subindex-demos { background-image: url(platform-demos.png); } div#subindex-hig { background-image: url(hig.png); } .refentry hr { margin: 10px 0; } div.sidebar dt, div.toc dt { font-size: 100%; } dd dl { margin-left: 1em; } div.homeblock { margin: 0.7em 0 1.2em 0; } div.homeblock h2 { font-size: 42px; color: #0489B7; margin: 0; padding-bottom: 1ex; } div.homeblock a { color: inherit; text-decoration: none; } div.homeblock a:hover { text-decoration: underline; } div.homeblock p { margin-top: 0; font-size: 18px; line-height: 130%; } span.module-more { font-size: 75%; } dl.doc-index dt span.module-more a { font-weight: normal; } ul.language-list { -webkit-column-width: 15em; -webkit-column-gap: 2em; -moz-column-width: 15em; -moz-column-gap: 2em; column-width: 15em; column-gap: 2em; } div.sidebar.notitle { margin-top: 3em; } #footer_art.default { background-image: url(footer_art-library.png); } div.blocky1 { width: 65%; padding: 2em; display: table-cell } div.blocky2 { width: 35%; padding: 2em; display: table-cell } div.row { width: 50%; padding: 2em; display: table-row } .grid_4.subtle_box { margin-top: 1em; } span.citem { display: table-cell; text-align: center } #platform-overview a { background: #babdb6; display: inline-block; padding: 5px 10px; -moz-border-radius: 3px; -webkit-border-radius: 3px; text-decoration: none; color: #000; margin: 5px; width: 65px; } #platform-overview a:hover { box-shadow: 0 1px 2px #0489d7, 0 1px rgba(255, 255, 255, 0.4) inset; color: #0489d7; } #platform-overview p { padding: 0; margin: 0; padding-left: 5px; margin-bottom: 0; padding-bottom: 0; font-size: 10px; } #api-doc-box { float: right; width: 205px; margin: 0; } #api-doc-box input { width: 190px; } #api-doc-box h2 { font-size: 20px; } table#platform-overview { width: 700px; table-layout: fixed; } table#platform-overview th { width: 14%; } table#platform-overview td { background: #888a85; -moz-border-radius: 5px; -webkit-border-radius: 5px; color: #2e3436; margin-bottom: 10px; border: 2px solid white; } .synopsis, .classsynopsis { background: #eee; border: solid 1px #aaa; padding: 0.5em; } .programlisting { background: #eef; border: solid 1px #aaf; padding: 0.5em; } .variablelist { padding: 4px; margin-left: 3em; } .variablelist td:first-child { vertical-align: top; } .variablelist p { margin: 0; } .listing_lines, .listing_code { margin-top: 0px; margin-bottom: 0px; padding: 0.5em; } .listing_lines { /* tango:sky blue 0.5 */ background: #a6c5e3; /* tango:aluminium 6 */ color: #2e3436; } .listing_code { /* tango:sky blue 0 */ background: #e6f3ff; } div.gtk-doc dt { color: #2e3456; margin: 0; } div.gtk-doc div.index dt { line-height: 1.2; font-size: 100%; } div.gtk-doc td.shortcuts { background: white; border: 1px solid #d3d7cf; text-align: center; } div.gtk-doc span.refpurpose { color: #555; font-weight: normal; } #container .gsc-control-cse { background: transparent; } #container ul.i18n { /* force left-to-right for language sidebar */ direction: ltr; } table, table tr, table td, table th, table tfoot, table thead, table tbody { /* those properties are reset by reset.css, restore them */ border: 1px none #2E3436; } #container div.blockquote { background-image: none; } dt { color: inherit; } acronym { border-bottom: 1px dotted; } table tbody tr td p { margin: 0.5ex 1em 0.5ex 0; } br + br { display: none; } .docbook-legal-stuff > h3 { text-align: center; cursor: pointer; font-size: 100%; } .docbook-legal-stuff > div { font-size: 80%; } .docbook-legal-stuff > div dt { font-size: 100%; } img.application-icon { padding-right: 1ex; position: relative; top: 0.8ex; max-width: 48px; max-height: 48px; } h1 img.application-icon { top: 0.4ex; } div#frontpage-indexes { clear: both; padding-top: 2em; } div#frontpage-indexes > div { margin: 0 1em 1em 0; } p.doc-feedback { margin-top: 2em; } img.attachment { max-width: 100%; } div.body.homepage { background: url(cogs.png) 90% 150px no-repeat; } div#welcome h1 { color: #c4a000; text-shadow: white 0 -2px; border-bottom: 1px solid #d3d7cf; } div#welcome div { width: 96%; } div#welcome p { font-size: 150%; width: 65%; } pre.cmdsynopsis { white-space: normal; word-break: keep-all; } h2, h3, dl dt { margin: 0; } p { margin: 0.4em 0; } p.module-more a { text-decoration: none; } p.module-more a:hover { text-decoration: underline; } dt .module-more { font-weight: normal; } dl.doc-index dt{ font-size: 120%; } p.no-translation { opacity: 0.6; } /* =================== * * Small Device Styles * * =================== */ @media screen and (max-width: 500px) { font-size: 18px; } chafa-1.8.0/docs/using.xml000066400000000000000000000100571411352071600153640ustar00rootroot00000000000000 Using Chafa in your Application 3 Chafa Library Using Chafa in your Application How to use Chafa Compiling your Application To compile an application using Chafa, you need to tell the compiler where to find the Chafa header files and libraries. This is done with the pkg-config utility. The following interactive shell session demonstrates how pkg-config is used (the actual output on your system may be different): $ pkg-config --cflags chafa -I/usr/include/chafa -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include $ pkg-config --libs chafa -lm -lchafa -lglib-2.0 See the pkg-config website for more information about pkg-config. The simplest way to compile a program is to use the command substitution feature of the shell. If you enclose a command in backticks (not single quotes), then its output will be substituted into the command line before execution. So to compile Hello, World program using Chafa you would type the following: $ cc hello.c `pkg-config --cflags --libs chafa` -o hello Note that the name of the file must come before the other options (such as pkg-config), or else you may get an error from the linker. When using Chafa in your program, you only need to include the toplevel header chafa.h. An Example The following program turns a 3x3 red X stored in a const array into an ANSI string and prints it to stdout. It can be compiled as described in the previous section and run without arguments. Note that it will output UTF-8 regardless of the current locale. #include <chafa.h> #include <stdio.h> #define PIX_WIDTH 3 #define PIX_HEIGHT 3 #define N_CHANNELS 4 int main (int argc, char *argv []) { const guint8 pixels [PIX_WIDTH * PIX_HEIGHT * N_CHANNELS] = { 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff }; ChafaSymbolMap *symbol_map; ChafaCanvasConfig *config; ChafaCanvas *canvas; GString *gs; /* Specify the symbols we want */ symbol_map = chafa_symbol_map_new (); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_ALL); /* Set up a configuration with the symbols and the canvas size in characters */ config = chafa_canvas_config_new (); chafa_canvas_config_set_geometry (config, 23, 12); chafa_canvas_config_set_symbol_map (config, symbol_map); /* Create canvas */ canvas = chafa_canvas_new (config); /* Draw pixels and build ANSI string */ chafa_canvas_draw_all_pixels (canvas, CHAFA_PIXEL_RGBA8_UNASSOCIATED, pixels, PIX_WIDTH, PIX_HEIGHT, PIX_WIDTH * N_CHANNELS); gs = chafa_canvas_build_ansi (canvas); /* Print the string */ fwrite (gs->str, sizeof (char), gs->len, stdout); fputc ('\n', stdout); /* Free resources */ g_string_free (gs, TRUE); chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); chafa_symbol_map_unref (symbol_map); return 0; } chafa-1.8.0/docs/version.xml.in000066400000000000000000000000201411352071600163160ustar00rootroot00000000000000@CHAFA_VERSION@ chafa-1.8.0/libnsgif/000077500000000000000000000000001411352071600143575ustar00rootroot00000000000000chafa-1.8.0/libnsgif/COPYING000066400000000000000000000021071411352071600154120ustar00rootroot00000000000000Copyright (C) 2004 Richard Wilson Copyright (C) 2008 Sean Fox Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. chafa-1.8.0/libnsgif/Makefile.am000066400000000000000000000001761411352071600164170ustar00rootroot00000000000000noinst_LTLIBRARIES = libnsgif.la libnsgif_la_SOURCES = libnsgif.c libnsgif.h lzw.c lzw.h log.h AM_CPPFLAGS = -I${top_srcdir} chafa-1.8.0/libnsgif/README000066400000000000000000000035621411352071600152450ustar00rootroot00000000000000libnsgif - Decoding GIF files ============================= The functions provided by this library allow for efficient progressive GIF decoding. Whilst the initialisation does not ensure that there is sufficient image data to complete the entire frame, it does ensure that the information provided is valid. Any subsequent attempts to decode an initialised GIF are guaranteed to succeed, and any bytes of the image not present are assumed to be totally transparent. To begin decoding a GIF, the 'gif' structure must be initialised with the 'gif_data' and 'buffer_size' set to their initial values. The 'buffer_position' should initially be 0, and will be internally updated as the decoding commences. The caller should then repeatedly call gif_initialise() with the structure until the function returns 1, or no more data is avaliable. Once the initialisation has begun, the decoder completes the variables 'frame_count' and 'frame_count_partial'. The former being the total number of frames that have been successfully initialised, and the latter being the number of frames that a partial amount of data is available for. This assists the caller in managing the animation whilst decoding is continuing. To decode a frame, the caller must use gif_decode_frame() which updates the current 'frame_image' to reflect the desired frame. The required 'disposal_method' is also updated to reflect how the frame should be plotted. The caller must not assume that the current 'frame_image' will be valid between calls if initialisation is still occuring, and should either always request that the frame is decoded (no processing will occur if the 'decoded_frame' has not been invalidated by initialisation) or perform the check itself. It should be noted that gif_finalise() should always be called, even if no frames were initialised. Additionally, it is the responsibility of the caller to free 'gif_data'. chafa-1.8.0/libnsgif/README-chafa000066400000000000000000000007671411352071600163110ustar00rootroot00000000000000Notes on libnsgif as bundled with Chafa ======================================= The libnsgif bundled with Chafa is currently based on version 0.2.1. The original source is here: http://www.netsurf-browser.org/projects/libnsgif/ It has been slightly adapted from the original to make it fit into the Chafa build. It may have further modifications to make it more convenient to use in this context. It is my intention to submit any generally useful enhancements made for potential inclusion upstream. chafa-1.8.0/libnsgif/libnsgif.c000066400000000000000000001346731411352071600163360ustar00rootroot00000000000000/* * Copyright 2004 Richard Wilson * Copyright 2008 Sean Fox * * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ * Licenced under the MIT License, * http://www.opensource.org/licenses/mit-license.php */ #include #include #include #include #include #include #include "libnsgif.h" #include "log.h" #include "lzw.h" /** * * \file * \brief GIF image decoder * * The GIF format is thoroughly documented; a full description can be found at * http://www.w3.org/Graphics/GIF/spec-gif89a.txt * * \todo Plain text and comment extensions should be implemented. */ /** Maximum colour table size */ #define GIF_MAX_COLOURS 256 /** Internal flag that the colour table needs to be processed */ #define GIF_PROCESS_COLOURS 0xaa000000 /** Internal flag that a frame is invalid/unprocessed */ #define GIF_INVALID_FRAME -1 /** Transparent colour */ #define GIF_TRANSPARENT_COLOUR 0x00 /* GIF Flags */ #define GIF_FRAME_COMBINE 1 #define GIF_FRAME_CLEAR 2 #define GIF_FRAME_RESTORE 3 #define GIF_FRAME_QUIRKS_RESTORE 4 #define GIF_IMAGE_SEPARATOR 0x2c #define GIF_INTERLACE_MASK 0x40 #define GIF_COLOUR_TABLE_MASK 0x80 #define GIF_COLOUR_TABLE_SIZE_MASK 0x07 #define GIF_EXTENSION_INTRODUCER 0x21 #define GIF_EXTENSION_GRAPHIC_CONTROL 0xf9 #define GIF_DISPOSAL_MASK 0x1c #define GIF_TRANSPARENCY_MASK 0x01 #define GIF_EXTENSION_COMMENT 0xfe #define GIF_EXTENSION_PLAIN_TEXT 0x01 #define GIF_EXTENSION_APPLICATION 0xff #define GIF_BLOCK_TERMINATOR 0x00 #define GIF_TRAILER 0x3b /** standard GIF header size */ #define GIF_STANDARD_HEADER_SIZE 13 /** * Updates the sprite memory size * * \param gif The animation context * \param width The width of the sprite * \param height The height of the sprite * \return GIF_INSUFFICIENT_MEMORY for a memory error GIF_OK for success */ static gif_result gif_initialise_sprite(gif_animation *gif, unsigned int width, unsigned int height) { unsigned int max_width; unsigned int max_height; struct bitmap *buffer; /* Check if we've changed */ if ((width <= gif->width) && (height <= gif->height)) { return GIF_OK; } /* Get our maximum values */ max_width = (width > gif->width) ? width : gif->width; max_height = (height > gif->height) ? height : gif->height; /* Allocate some more memory */ assert(gif->bitmap_callbacks.bitmap_create); buffer = gif->bitmap_callbacks.bitmap_create(max_width, max_height); if (buffer == NULL) { return GIF_INSUFFICIENT_MEMORY; } assert(gif->bitmap_callbacks.bitmap_destroy); gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); gif->frame_image = buffer; gif->width = max_width; gif->height = max_height; /* Invalidate our currently decoded image */ gif->decoded_frame = GIF_INVALID_FRAME; return GIF_OK; } /** * Attempts to initialise the frame's extensions * * \param gif The animation context * \param frame The frame number * @return GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the * frame GIF_OK for successful initialisation. */ static gif_result gif_initialise_frame_extensions(gif_animation *gif, const int frame) { const unsigned char *gif_data, *gif_end; ssize_t gif_bytes; ssize_t block_size; /* Get our buffer position etc. */ gif_data = (const unsigned char *)(gif->gif_data + gif->buffer_position); gif_end = (const unsigned char *)(gif->gif_data + gif->buffer_size); /* Initialise the extensions */ while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { ++gif_data; if ((gif_bytes = (gif_end - gif_data)) < 1) { return GIF_INSUFFICIENT_FRAME_DATA; } /* Switch on extension label */ switch (gif_data[0]) { case GIF_EXTENSION_GRAPHIC_CONTROL: /* 6-byte Graphic Control Extension is: * * +0 CHAR Graphic Control Label * +1 CHAR Block Size * +2 CHAR __Packed Fields__ * 3BITS Reserved * 3BITS Disposal Method * 1BIT User Input Flag * 1BIT Transparent Color Flag * +3 SHORT Delay Time * +5 CHAR Transparent Color Index */ if (gif_bytes < 6) { return GIF_INSUFFICIENT_FRAME_DATA; } gif->frames[frame].frame_delay = gif_data[3] | (gif_data[4] << 8); if (gif_data[2] & GIF_TRANSPARENCY_MASK) { gif->frames[frame].transparency = true; gif->frames[frame].transparency_index = gif_data[5]; } gif->frames[frame].disposal_method = ((gif_data[2] & GIF_DISPOSAL_MASK) >> 2); /* I have encountered documentation and GIFs in the * wild that use 0x04 to restore the previous frame, * rather than the officially documented 0x03. I * believe some (older?) software may even actually * export this way. We handle this as a type of * "quirks" mode. */ if (gif->frames[frame].disposal_method == GIF_FRAME_QUIRKS_RESTORE) { gif->frames[frame].disposal_method = GIF_FRAME_RESTORE; } gif_data += (2 + gif_data[1]); break; case GIF_EXTENSION_APPLICATION: /* 14-byte+ Application Extension is: * * +0 CHAR Application Extension Label * +1 CHAR Block Size * +2 8CHARS Application Identifier * +10 3CHARS Appl. Authentication Code * +13 1-256 Application Data (Data sub-blocks) */ if (gif_bytes < 17) { return GIF_INSUFFICIENT_FRAME_DATA; } if ((gif_data[1] == 0x0b) && (strncmp((const char *) gif_data + 2, "NETSCAPE2.0", 11) == 0) && (gif_data[13] == 0x03) && (gif_data[14] == 0x01)) { gif->loop_count = gif_data[15] | (gif_data[16] << 8); } gif_data += (2 + gif_data[1]); break; case GIF_EXTENSION_COMMENT: /* Move the pointer to the first data sub-block Skip 1 * byte for the extension label */ ++gif_data; break; default: /* Move the pointer to the first data sub-block Skip 2 * bytes for the extension label and size fields Skip * the extension size itself */ if (gif_bytes < 2) { return GIF_INSUFFICIENT_FRAME_DATA; } gif_data += (2 + gif_data[1]); } /* Repeatedly skip blocks until we get a zero block or run out * of data This data is ignored by this gif decoder */ gif_bytes = (gif_end - gif_data); block_size = 0; while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { block_size = gif_data[0] + 1; if ((gif_bytes -= block_size) < 0) { return GIF_INSUFFICIENT_FRAME_DATA; } gif_data += block_size; } ++gif_data; } /* Set buffer position and return */ gif->buffer_position = (gif_data - gif->gif_data); return GIF_OK; } /** * Attempts to initialise the next frame * * \param gif The animation context * \return error code * - GIF_INSUFFICIENT_DATA for insufficient data to do anything * - GIF_FRAME_DATA_ERROR for GIF frame data error * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame * - GIF_DATA_ERROR for GIF error (invalid frame header) * - GIF_OK for successful decoding * - GIF_WORKING for successful decoding if more frames are expected */ static gif_result gif_initialise_frame(gif_animation *gif) { int frame; gif_frame *temp_buf; const unsigned char *gif_data, *gif_end; ssize_t gif_bytes; unsigned int flags = 0; unsigned int width, height, offset_x, offset_y; ssize_t block_size, colour_table_size; bool first_image = true; gif_result return_value; bool premature_eof = false; /* Get the frame to decode and our data position */ frame = gif->frame_count; /* Get our buffer position etc. */ gif_data = (const unsigned char *)(gif->gif_data + gif->buffer_position); gif_end = (const unsigned char *)(gif->gif_data + gif->buffer_size); gif_bytes = (gif_end - gif_data); /* Check if we've finished */ if ((gif_bytes > 0) && (gif_data[0] == GIF_TRAILER)) { return GIF_OK; } /* Check if there is enough data remaining. The shortest block of data * is a 4-byte comment extension + 1-byte block terminator + 1-byte gif * trailer */ if (gif_bytes < 6) { return GIF_INSUFFICIENT_DATA; } /* We could theoretically get some junk data that gives us millions of * frames, so we ensure that we don't have a silly number */ if (frame > 262143) return GIF_FRAME_DATA_ERROR; /* Get some memory to store our pointers in etc. */ if ((int)gif->frame_holders <= frame) { /* Allocate more memory */ temp_buf = (gif_frame *)realloc(gif->frames, (frame + 1) * sizeof(gif_frame)); if (temp_buf == NULL) { return GIF_INSUFFICIENT_MEMORY; } gif->frames = temp_buf; gif->frame_holders = frame + 1; } /* Store our frame pointer. We would do it when allocating except we * start off with one frame allocated so we can always use realloc. */ gif->frames[frame].frame_pointer = gif->buffer_position; gif->frames[frame].display = false; gif->frames[frame].virgin = true; gif->frames[frame].disposal_method = 0; gif->frames[frame].transparency = false; gif->frames[frame].frame_delay = 0; gif->frames[frame].redraw_required = false; /* Invalidate any previous decoding we have of this frame */ if (gif->decoded_frame == frame) { gif->decoded_frame = GIF_INVALID_FRAME; } /* We pretend to initialise the frames, but really we just skip over * all the data contained within. This is all basically a cut down * version of gif_decode_frame that doesn't have any of the LZW bits in * it. */ /* Initialise any extensions */ gif->buffer_position = gif_data - gif->gif_data; return_value = gif_initialise_frame_extensions(gif, frame); if (return_value != GIF_OK) { return return_value; } gif_data = (gif->gif_data + gif->buffer_position); gif_bytes = (gif_end - gif_data); /* Check if we've finished */ if ((gif_bytes = (gif_end - gif_data)) < 1) { return GIF_INSUFFICIENT_FRAME_DATA; } if (gif_data[0] == GIF_TRAILER) { gif->buffer_position = (gif_data - gif->gif_data); gif->frame_count = frame + 1; return GIF_OK; } /* If we're not done, there should be an image descriptor */ if (gif_data[0] != GIF_IMAGE_SEPARATOR) { return GIF_FRAME_DATA_ERROR; } /* Do some simple boundary checking */ if (gif_bytes < 10) { return GIF_INSUFFICIENT_FRAME_DATA; } offset_x = gif_data[1] | (gif_data[2] << 8); offset_y = gif_data[3] | (gif_data[4] << 8); width = gif_data[5] | (gif_data[6] << 8); height = gif_data[7] | (gif_data[8] << 8); /* Set up the redraw characteristics. We have to check for extending * the area due to multi-image frames. */ if (!first_image) { if (gif->frames[frame].redraw_x > offset_x) { gif->frames[frame].redraw_width += (gif->frames[frame].redraw_x - offset_x); gif->frames[frame].redraw_x = offset_x; } if (gif->frames[frame].redraw_y > offset_y) { gif->frames[frame].redraw_height += (gif->frames[frame].redraw_y - offset_y); gif->frames[frame].redraw_y = offset_y; } if ((offset_x + width) > (gif->frames[frame].redraw_x + gif->frames[frame].redraw_width)) { gif->frames[frame].redraw_width = (offset_x + width) - gif->frames[frame].redraw_x; } if ((offset_y + height) > (gif->frames[frame].redraw_y + gif->frames[frame].redraw_height)) { gif->frames[frame].redraw_height = (offset_y + height) - gif->frames[frame].redraw_y; } } else { first_image = false; gif->frames[frame].redraw_x = offset_x; gif->frames[frame].redraw_y = offset_y; gif->frames[frame].redraw_width = width; gif->frames[frame].redraw_height = height; } /* if we are clearing the background then we need to redraw enough to * cover the previous frame too */ gif->frames[frame].redraw_required = ((gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) || (gif->frames[frame].disposal_method == GIF_FRAME_RESTORE)); /* Boundary checking - shouldn't ever happen except with junk data */ if (gif_initialise_sprite(gif, (offset_x + width), (offset_y + height))) { return GIF_INSUFFICIENT_MEMORY; } /* Decode the flags */ flags = gif_data[9]; colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); /* Move our data onwards and remember we've got a bit of this frame */ gif_data += 10; gif_bytes = (gif_end - gif_data); gif->frame_count_partial = frame + 1; /* Skip the local colour table */ if (flags & GIF_COLOUR_TABLE_MASK) { gif_data += 3 * colour_table_size; if ((gif_bytes = (gif_end - gif_data)) < 0) { return GIF_INSUFFICIENT_FRAME_DATA; } } /* Ensure we have a correct code size */ if (gif_bytes < 1) { return GIF_INSUFFICIENT_FRAME_DATA; } if (gif_data[0] > LZW_CODE_MAX) { return GIF_DATA_ERROR; } /* Move our pointer to the actual image data */ gif_data++; --gif_bytes; /* Repeatedly skip blocks until we get a zero block or run out of data * These blocks of image data are processed later by gif_decode_frame() */ block_size = 0; while (block_size != 1) { if (gif_bytes < 1) return GIF_INSUFFICIENT_FRAME_DATA; block_size = gif_data[0] + 1; /* Check if the frame data runs off the end of the file */ if ((ssize_t)(gif_bytes - block_size) < 0) { /* Try to recover by signaling the end of the gif. * Once we get garbage data, there is no logical way to * determine where the next frame is. It's probably * better to partially load the gif than not at all. */ if (gif_bytes >= 2) { gif_bytes = 1; ++gif_data; premature_eof = true; break; } else { return GIF_INSUFFICIENT_FRAME_DATA; } } else { gif_bytes -= block_size; gif_data += block_size; } } /* Add the frame and set the display flag */ gif->buffer_position = gif_data - gif->gif_data; gif->frame_count = frame + 1; gif->frames[frame].display = true; /* Check if we've finished */ if (gif_bytes < 1) { return GIF_INSUFFICIENT_FRAME_DATA; } else { if (premature_eof || gif_data[0] == GIF_TRAILER) { return GIF_OK; } } return GIF_WORKING; } /** * Skips the frame's extensions (which have been previously initialised) * * \param gif The animation context * \return GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the * frame GIF_OK for successful decoding */ static gif_result gif_skip_frame_extensions(gif_animation *gif) { const unsigned char *gif_data, *gif_end; ssize_t gif_bytes; ssize_t block_size; /* Get our buffer position etc. */ gif_data = (const unsigned char *)(gif->gif_data + gif->buffer_position); gif_end = (const unsigned char *)(gif->gif_data + gif->buffer_size); gif_bytes = (gif_end - gif_data); /* Skip the extensions */ while (gif_data < gif_end && gif_data[0] == GIF_EXTENSION_INTRODUCER) { ++gif_data; if (gif_data >= gif_end) { return GIF_INSUFFICIENT_FRAME_DATA; } /* Switch on extension label */ switch(gif_data[0]) { case GIF_EXTENSION_COMMENT: /* Move the pointer to the first data sub-block * 1 byte for the extension label */ ++gif_data; break; default: /* Move the pointer to the first data sub-block 2 bytes * for the extension label and size fields Skip the * extension size itself */ if (gif_data + 1 >= gif_end) { return GIF_INSUFFICIENT_FRAME_DATA; } gif_data += (2 + gif_data[1]); } /* Repeatedly skip blocks until we get a zero block or run out * of data This data is ignored by this gif decoder */ gif_bytes = (gif_end - gif_data); block_size = 0; while (gif_data < gif_end && gif_data[0] != GIF_BLOCK_TERMINATOR) { block_size = gif_data[0] + 1; if ((gif_bytes -= block_size) < 0) { return GIF_INSUFFICIENT_FRAME_DATA; } gif_data += block_size; } ++gif_data; } /* Set buffer position and return */ gif->buffer_position = (gif_data - gif->gif_data); return GIF_OK; } static unsigned int gif_interlaced_line(int height, int y) { if ((y << 3) < height) { return (y << 3); } y -= ((height + 7) >> 3); if ((y << 3) < (height - 4)) { return (y << 3) + 4; } y -= ((height + 3) >> 3); if ((y << 2) < (height - 2)) { return (y << 2) + 2; } y -= ((height + 1) >> 2); return (y << 1) + 1; } static gif_result gif_error_from_lzw(lzw_result l_res) { static const gif_result g_res[] = { [LZW_OK] = GIF_OK, [LZW_OK_EOD] = GIF_END_OF_FRAME, [LZW_NO_MEM] = GIF_INSUFFICIENT_MEMORY, [LZW_NO_DATA] = GIF_INSUFFICIENT_FRAME_DATA, [LZW_EOI_CODE] = GIF_FRAME_DATA_ERROR, [LZW_BAD_ICODE] = GIF_FRAME_DATA_ERROR, [LZW_BAD_CODE] = GIF_FRAME_DATA_ERROR, }; return g_res[l_res]; } /** * decode a gif frame * * \param gif gif animation context. * \param frame The frame number to decode. * \param clear_image flag for image data being cleared instead of plotted. */ static gif_result gif_internal_decode_frame(gif_animation *gif, unsigned int frame, bool clear_image) { unsigned int index = 0; const unsigned char *gif_data, *gif_end; ssize_t gif_bytes; unsigned int width, height, offset_x, offset_y; unsigned int flags, colour_table_size, interlace; unsigned int *colour_table; unsigned int *frame_data = 0; // Set to 0 for no warnings unsigned int *frame_scanline; ssize_t save_buffer_position; unsigned int return_value = 0; unsigned int x, y, decode_y, burst_bytes; register unsigned char colour; /* Ensure this frame is supposed to be decoded */ if (gif->frames[frame].display == false) { return GIF_OK; } /* Ensure the frame is in range to decode */ if (frame > gif->frame_count_partial) { return GIF_INSUFFICIENT_DATA; } /* done if frame is already decoded */ if ((!clear_image) && ((int)frame == gif->decoded_frame)) { return GIF_OK; } /* Get the start of our frame data and the end of the GIF data */ gif_data = gif->gif_data + gif->frames[frame].frame_pointer; gif_end = gif->gif_data + gif->buffer_size; gif_bytes = (gif_end - gif_data); /* * Ensure there is a minimal amount of data to proceed. The shortest * block of data is a 10-byte image descriptor + 1-byte gif trailer */ if (gif_bytes < 12) { return GIF_INSUFFICIENT_FRAME_DATA; } /* Save the buffer position */ save_buffer_position = gif->buffer_position; gif->buffer_position = gif_data - gif->gif_data; /* Skip any extensions because they have allready been processed */ if ((return_value = gif_skip_frame_extensions(gif)) != GIF_OK) { goto gif_decode_frame_exit; } gif_data = (gif->gif_data + gif->buffer_position); gif_bytes = (gif_end - gif_data); /* Ensure we have enough data for the 10-byte image descriptor + 1-byte * gif trailer */ if (gif_bytes < 12) { return_value = GIF_INSUFFICIENT_FRAME_DATA; goto gif_decode_frame_exit; } /* 10-byte Image Descriptor is: * * +0 CHAR Image Separator (0x2c) * +1 SHORT Image Left Position * +3 SHORT Image Top Position * +5 SHORT Width * +7 SHORT Height * +9 CHAR __Packed Fields__ * 1BIT Local Colour Table Flag * 1BIT Interlace Flag * 1BIT Sort Flag * 2BITS Reserved * 3BITS Size of Local Colour Table */ if (gif_data[0] != GIF_IMAGE_SEPARATOR) { return_value = GIF_DATA_ERROR; goto gif_decode_frame_exit; } offset_x = gif_data[1] | (gif_data[2] << 8); offset_y = gif_data[3] | (gif_data[4] << 8); width = gif_data[5] | (gif_data[6] << 8); height = gif_data[7] | (gif_data[8] << 8); /* Boundary checking - shouldn't ever happen except unless the data has * been modified since initialisation. */ if ((offset_x + width > gif->width) || (offset_y + height > gif->height)) { return_value = GIF_DATA_ERROR; goto gif_decode_frame_exit; } /* Decode the flags */ flags = gif_data[9]; colour_table_size = 2 << (flags & GIF_COLOUR_TABLE_SIZE_MASK); interlace = flags & GIF_INTERLACE_MASK; /* Advance data pointer to next block either colour table or image * data. */ gif_data += 10; gif_bytes = (gif_end - gif_data); /* Set up the colour table */ if (flags & GIF_COLOUR_TABLE_MASK) { if (gif_bytes < (int)(3 * colour_table_size)) { return_value = GIF_INSUFFICIENT_FRAME_DATA; goto gif_decode_frame_exit; } colour_table = gif->local_colour_table; if (!clear_image) { for (index = 0; index < colour_table_size; index++) { /* Gif colour map contents are r,g,b. * * We want to pack them bytewise into the * colour table, such that the red component * is in byte 0 and the alpha component is in * byte 3. */ unsigned char *entry = (unsigned char *) &colour_table[index]; entry[0] = gif_data[0]; /* r */ entry[1] = gif_data[1]; /* g */ entry[2] = gif_data[2]; /* b */ entry[3] = 0xff; /* a */ gif_data += 3; } } else { gif_data += 3 * colour_table_size; } gif_bytes = (gif_end - gif_data); } else { colour_table = gif->global_colour_table; } /* Ensure sufficient data remains */ if (gif_bytes < 1) { return_value = GIF_INSUFFICIENT_FRAME_DATA; goto gif_decode_frame_exit; } /* check for an end marker */ if (gif_data[0] == GIF_TRAILER) { return_value = GIF_OK; goto gif_decode_frame_exit; } /* Get the frame data */ assert(gif->bitmap_callbacks.bitmap_get_buffer); frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); if (!frame_data) { return GIF_INSUFFICIENT_MEMORY; } /* If we are clearing the image we just clear, if not decode */ if (!clear_image) { lzw_result res; const uint8_t *stack_base; const uint8_t *stack_pos; /* Ensure we have enough data for a 1-byte LZW code size + * 1-byte gif trailer */ if (gif_bytes < 2) { return_value = GIF_INSUFFICIENT_FRAME_DATA; goto gif_decode_frame_exit; } /* If we only have a 1-byte LZW code size + 1-byte gif trailer, * we're finished */ if ((gif_bytes == 2) && (gif_data[1] == GIF_TRAILER)) { return_value = GIF_OK; goto gif_decode_frame_exit; } /* If the previous frame's disposal method requires we restore * the background colour or this is the first frame, clear * the frame data */ if ((frame == 0) || (gif->decoded_frame == GIF_INVALID_FRAME)) { memset((char*)frame_data, GIF_TRANSPARENT_COLOUR, gif->width * gif->height * sizeof(int)); gif->decoded_frame = frame; /* The line below would fill the image with its * background color, but because GIFs support * transparency we likely wouldn't want to do that. */ /* memset((char*)frame_data, colour_table[gif->background_index], gif->width * gif->height * sizeof(int)); */ } else if ((frame != 0) && (gif->frames[frame - 1].disposal_method == GIF_FRAME_CLEAR)) { return_value = gif_internal_decode_frame(gif, (frame - 1), true); if (return_value != GIF_OK) { goto gif_decode_frame_exit; } } else if ((frame != 0) && (gif->frames[frame - 1].disposal_method == GIF_FRAME_RESTORE)) { /* * If the previous frame's disposal method requires we * restore the previous image, find the last image set * to "do not dispose" and get that frame data */ int last_undisposed_frame = frame - 2; while ((last_undisposed_frame >= 0) && (gif->frames[last_undisposed_frame].disposal_method == GIF_FRAME_RESTORE)) { last_undisposed_frame--; } /* If we don't find one, clear the frame data */ if (last_undisposed_frame == -1) { /* see notes above on transparency * vs. background color */ memset((char*)frame_data, GIF_TRANSPARENT_COLOUR, gif->width * gif->height * sizeof(int)); } else { return_value = gif_internal_decode_frame(gif, last_undisposed_frame, false); if (return_value != GIF_OK) { goto gif_decode_frame_exit; } /* Get this frame's data */ assert(gif->bitmap_callbacks.bitmap_get_buffer); frame_data = (void *)gif->bitmap_callbacks.bitmap_get_buffer(gif->frame_image); if (!frame_data) { return GIF_INSUFFICIENT_MEMORY; } } } gif->decoded_frame = frame; gif->buffer_position = (gif_data - gif->gif_data) + 1; /* Initialise the LZW decoding */ res = lzw_decode_init(gif->lzw_ctx, gif->gif_data, gif->buffer_size, gif->buffer_position, gif_data[0], &stack_base, &stack_pos); if (res != LZW_OK) { return gif_error_from_lzw(res); } /* Decompress the data */ for (y = 0; y < height; y++) { if (interlace) { decode_y = gif_interlaced_line(height, y) + offset_y; } else { decode_y = y + offset_y; } frame_scanline = frame_data + offset_x + (decode_y * gif->width); /* Rather than decoding pixel by pixel, we try to burst * out streams of data to remove the need for end-of * data checks every pixel. */ x = width; while (x > 0) { burst_bytes = (stack_pos - stack_base); if (burst_bytes > 0) { if (burst_bytes > x) { burst_bytes = x; } x -= burst_bytes; while (burst_bytes-- > 0) { colour = *--stack_pos; if (((gif->frames[frame].transparency) && (colour != gif->frames[frame].transparency_index)) || (!gif->frames[frame].transparency)) { *frame_scanline = colour_table[colour]; } frame_scanline++; } } else { res = lzw_decode(gif->lzw_ctx, &stack_pos); if (res != LZW_OK) { /* Unexpected end of frame, try to recover */ if (res == LZW_OK_EOD) { return_value = GIF_OK; } else { return_value = gif_error_from_lzw(res); } goto gif_decode_frame_exit; } } } } } else { /* Clear our frame */ if (gif->frames[frame].disposal_method == GIF_FRAME_CLEAR) { for (y = 0; y < height; y++) { frame_scanline = frame_data + offset_x + ((offset_y + y) * gif->width); if (gif->frames[frame].transparency) { memset(frame_scanline, GIF_TRANSPARENT_COLOUR, width * 4); } else { memset(frame_scanline, colour_table[gif->background_index], width * 4); } } } } gif_decode_frame_exit: /* Check if we should test for optimisation */ if (gif->frames[frame].virgin) { if (gif->bitmap_callbacks.bitmap_test_opaque) { gif->frames[frame].opaque = gif->bitmap_callbacks.bitmap_test_opaque(gif->frame_image); } else { gif->frames[frame].opaque = false; } gif->frames[frame].virgin = false; } if (gif->bitmap_callbacks.bitmap_set_opaque) { gif->bitmap_callbacks.bitmap_set_opaque(gif->frame_image, gif->frames[frame].opaque); } if (gif->bitmap_callbacks.bitmap_modified) { gif->bitmap_callbacks.bitmap_modified(gif->frame_image); } /* Restore the buffer position */ gif->buffer_position = save_buffer_position; return return_value; } /* exported function documented in libnsgif.h */ void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks) { memset(gif, 0, sizeof(gif_animation)); gif->bitmap_callbacks = *bitmap_callbacks; gif->decoded_frame = GIF_INVALID_FRAME; } /* exported function documented in libnsgif.h */ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data) { const unsigned char *gif_data; unsigned int index; gif_result return_value; /* Initialize values */ gif->buffer_size = size; gif->gif_data = data; if (gif->lzw_ctx == NULL) { lzw_result res = lzw_context_create( (struct lzw_ctx **)&gif->lzw_ctx); if (res != LZW_OK) { return gif_error_from_lzw(res); } } /* Check for sufficient data to be a GIF (6-byte header + 7-byte * logical screen descriptor) */ if (gif->buffer_size < GIF_STANDARD_HEADER_SIZE) { return GIF_INSUFFICIENT_DATA; } /* Get our current processing position */ gif_data = gif->gif_data + gif->buffer_position; /* See if we should initialise the GIF */ if (gif->buffer_position == 0) { /* We want everything to be NULL before we start so we've no * chance of freeing bad pointers (paranoia) */ gif->frame_image = NULL; gif->frames = NULL; gif->local_colour_table = NULL; gif->global_colour_table = NULL; /* The caller may have been lazy and not reset any values */ gif->frame_count = 0; gif->frame_count_partial = 0; gif->decoded_frame = GIF_INVALID_FRAME; /* 6-byte GIF file header is: * * +0 3CHARS Signature ('GIF') * +3 3CHARS Version ('87a' or '89a') */ if (strncmp((const char *) gif_data, "GIF", 3) != 0) { return GIF_DATA_ERROR; } gif_data += 3; /* Ensure GIF reports version 87a or 89a */ /* if ((strncmp(gif_data, "87a", 3) != 0) && (strncmp(gif_data, "89a", 3) != 0)) LOG(("Unknown GIF format - proceeding anyway")); */ gif_data += 3; /* 7-byte Logical Screen Descriptor is: * * +0 SHORT Logical Screen Width * +2 SHORT Logical Screen Height * +4 CHAR __Packed Fields__ * 1BIT Global Colour Table Flag * 3BITS Colour Resolution * 1BIT Sort Flag * 3BITS Size of Global Colour Table * +5 CHAR Background Colour Index * +6 CHAR Pixel Aspect Ratio */ gif->width = gif_data[0] | (gif_data[1] << 8); gif->height = gif_data[2] | (gif_data[3] << 8); gif->global_colours = (gif_data[4] & GIF_COLOUR_TABLE_MASK); gif->colour_table_size = (2 << (gif_data[4] & GIF_COLOUR_TABLE_SIZE_MASK)); gif->background_index = gif_data[5]; gif->aspect_ratio = gif_data[6]; gif->loop_count = 1; gif_data += 7; /* Some broken GIFs report the size as the screen size they * were created in. As such, we detect for the common cases and * set the sizes as 0 if they are found which results in the * GIF being the maximum size of the frames. */ if (((gif->width == 640) && (gif->height == 480)) || ((gif->width == 640) && (gif->height == 512)) || ((gif->width == 800) && (gif->height == 600)) || ((gif->width == 1024) && (gif->height == 768)) || ((gif->width == 1280) && (gif->height == 1024)) || ((gif->width == 1600) && (gif->height == 1200)) || ((gif->width == 0) || (gif->height == 0)) || ((gif->width > 2048) || (gif->height > 2048))) { gif->width = 1; gif->height = 1; } /* Allocate some data irrespective of whether we've got any * colour tables. We always get the maximum size in case a GIF * is lying to us. It's far better to give the wrong colours * than to trample over some memory somewhere. */ gif->global_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); gif->local_colour_table = calloc(GIF_MAX_COLOURS, sizeof(unsigned int)); if ((gif->global_colour_table == NULL) || (gif->local_colour_table == NULL)) { gif_finalise(gif); return GIF_INSUFFICIENT_MEMORY; } /* Set the first colour to a value that will never occur in * reality so we know if we've processed it */ gif->global_colour_table[0] = GIF_PROCESS_COLOURS; /* Check if the GIF has no frame data (13-byte header + 1-byte * termination block) Although generally useless, the GIF * specification does not expressly prohibit this */ if (gif->buffer_size == (GIF_STANDARD_HEADER_SIZE + 1)) { if (gif_data[0] == GIF_TRAILER) { return GIF_OK; } else { return GIF_INSUFFICIENT_DATA; } } /* Initialise enough workspace for a frame */ if ((gif->frames = (gif_frame *)malloc(sizeof(gif_frame))) == NULL) { gif_finalise(gif); return GIF_INSUFFICIENT_MEMORY; } gif->frame_holders = 1; /* Initialise the bitmap header */ assert(gif->bitmap_callbacks.bitmap_create); gif->frame_image = gif->bitmap_callbacks.bitmap_create(gif->width, gif->height); if (gif->frame_image == NULL) { gif_finalise(gif); return GIF_INSUFFICIENT_MEMORY; } /* Remember we've done this now */ gif->buffer_position = gif_data - gif->gif_data; } /* Do the colour map if we haven't already. As the top byte is always * 0xff or 0x00 depending on the transparency we know if it's been * filled in. */ if (gif->global_colour_table[0] == GIF_PROCESS_COLOURS) { /* Check for a global colour map signified by bit 7 */ if (gif->global_colours) { if (gif->buffer_size < (gif->colour_table_size * 3 + GIF_STANDARD_HEADER_SIZE)) { return GIF_INSUFFICIENT_DATA; } for (index = 0; index < gif->colour_table_size; index++) { /* Gif colour map contents are r,g,b. * * We want to pack them bytewise into the * colour table, such that the red component * is in byte 0 and the alpha component is in * byte 3. */ unsigned char *entry = (unsigned char *) &gif-> global_colour_table[index]; entry[0] = gif_data[0]; /* r */ entry[1] = gif_data[1]; /* g */ entry[2] = gif_data[2]; /* b */ entry[3] = 0xff; /* a */ gif_data += 3; } gif->buffer_position = (gif_data - gif->gif_data); } else { /* Create a default colour table with the first two * colours as black and white */ unsigned int *entry = gif->global_colour_table; entry[0] = 0x00000000; /* Force Alpha channel to opaque */ ((unsigned char *) entry)[3] = 0xff; entry[1] = 0xffffffff; } } /* Repeatedly try to initialise frames */ while ((return_value = gif_initialise_frame(gif)) == GIF_WORKING); /* If there was a memory error tell the caller */ if ((return_value == GIF_INSUFFICIENT_MEMORY) || (return_value == GIF_DATA_ERROR)) { return return_value; } /* If we didn't have some frames then a GIF_INSUFFICIENT_DATA becomes a * GIF_INSUFFICIENT_FRAME_DATA */ if ((return_value == GIF_INSUFFICIENT_DATA) && (gif->frame_count_partial > 0)) { return GIF_INSUFFICIENT_FRAME_DATA; } /* Return how many we got */ return return_value; } /* exported function documented in libnsgif.h */ gif_result gif_decode_frame(gif_animation *gif, unsigned int frame) { return gif_internal_decode_frame(gif, frame, false); } /* exported function documented in libnsgif.h */ void gif_finalise(gif_animation *gif) { /* Release all our memory blocks */ if (gif->frame_image) { assert(gif->bitmap_callbacks.bitmap_destroy); gif->bitmap_callbacks.bitmap_destroy(gif->frame_image); } gif->frame_image = NULL; free(gif->frames); gif->frames = NULL; free(gif->local_colour_table); gif->local_colour_table = NULL; free(gif->global_colour_table); gif->global_colour_table = NULL; lzw_context_destroy(gif->lzw_ctx); gif->lzw_ctx = NULL; } chafa-1.8.0/libnsgif/libnsgif.h000066400000000000000000000147771411352071600163450ustar00rootroot00000000000000/* * Copyright 2004 Richard Wilson * Copyright 2008 Sean Fox * * This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/ * Licenced under the MIT License, * http://www.opensource.org/licenses/mit-license.php */ /** * \file * Interface to progressive animated GIF file decoding. */ #ifndef _LIBNSGIF_H_ #define _LIBNSGIF_H_ #include #include /* Error return values */ typedef enum { GIF_WORKING = 1, GIF_OK = 0, GIF_INSUFFICIENT_FRAME_DATA = -1, GIF_FRAME_DATA_ERROR = -2, GIF_INSUFFICIENT_DATA = -3, GIF_DATA_ERROR = -4, GIF_INSUFFICIENT_MEMORY = -5, GIF_FRAME_NO_DISPLAY = -6, GIF_END_OF_FRAME = -7 } gif_result; /** GIF frame data */ typedef struct gif_frame { /** whether the frame should be displayed/animated */ bool display; /** delay (in cs) before animating the frame */ unsigned int frame_delay; /* Internal members are listed below */ /** offset (in bytes) to the GIF frame data */ unsigned int frame_pointer; /** whether the frame has previously been used */ bool virgin; /** whether the frame is totally opaque */ bool opaque; /** whether a forcable screen redraw is required */ bool redraw_required; /** how the previous frame should be disposed; affects plotting */ unsigned char disposal_method; /** whether we acknoledge transparency */ bool transparency; /** the index designating a transparent pixel */ unsigned char transparency_index; /** x co-ordinate of redraw rectangle */ unsigned int redraw_x; /** y co-ordinate of redraw rectangle */ unsigned int redraw_y; /** width of redraw rectangle */ unsigned int redraw_width; /** height of redraw rectangle */ unsigned int redraw_height; } gif_frame; /* API for Bitmap callbacks */ typedef void* (*gif_bitmap_cb_create)(int width, int height); typedef void (*gif_bitmap_cb_destroy)(void *bitmap); typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap); typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque); typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap); typedef void (*gif_bitmap_cb_modified)(void *bitmap); /** Bitmap callbacks function table */ typedef struct gif_bitmap_callback_vt { /** Create a bitmap. */ gif_bitmap_cb_create bitmap_create; /** Free a bitmap. */ gif_bitmap_cb_destroy bitmap_destroy; /** Return a pointer to the pixel data in a bitmap. */ gif_bitmap_cb_get_buffer bitmap_get_buffer; /* Members below are optional */ /** Sets whether a bitmap should be plotted opaque. */ gif_bitmap_cb_set_opaque bitmap_set_opaque; /** Tests whether a bitmap has an opaque alpha channel. */ gif_bitmap_cb_test_opaque bitmap_test_opaque; /** The bitmap image has changed, so flush any persistant cache. */ gif_bitmap_cb_modified bitmap_modified; } gif_bitmap_callback_vt; /** GIF animation data */ typedef struct gif_animation { /** LZW decode context */ void *lzw_ctx; /** callbacks for bitmap functions */ gif_bitmap_callback_vt bitmap_callbacks; /** pointer to GIF data */ unsigned char *gif_data; /** width of GIF (may increase during decoding) */ unsigned int width; /** heigth of GIF (may increase during decoding) */ unsigned int height; /** number of frames decoded */ unsigned int frame_count; /** number of frames partially decoded */ unsigned int frame_count_partial; /** decoded frames */ gif_frame *frames; /** current frame decoded to bitmap */ int decoded_frame; /** currently decoded image; stored as bitmap from bitmap_create callback */ void *frame_image; /** number of times to loop animation */ int loop_count; /* Internal members are listed below */ /** current index into GIF data */ ssize_t buffer_position; /** total number of bytes of GIF data available */ ssize_t buffer_size; /** current number of frame holders */ unsigned int frame_holders; /** index in the colour table for the background colour */ unsigned int background_index; /** image aspect ratio (ignored) */ unsigned int aspect_ratio; /** size of colour table (in entries) */ unsigned int colour_table_size; /** whether the GIF has a global colour table */ bool global_colours; /** global colour table */ unsigned int *global_colour_table; /** local colour table */ unsigned int *local_colour_table; } gif_animation; /** * Initialises necessary gif_animation members. */ void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks); /** * Initialises any workspace held by the animation and attempts to decode * any information that hasn't already been decoded. * If an error occurs, all previously decoded frames are retained. * * @return Error return value. * - GIF_FRAME_DATA_ERROR for GIF frame data error * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to process * any more frames * - GIF_INSUFFICIENT_MEMORY for memory error * - GIF_DATA_ERROR for GIF error * - GIF_INSUFFICIENT_DATA for insufficient data to do anything * - GIF_OK for successful decoding * - GIF_WORKING for successful decoding if more frames are expected */ gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data); /** * Decodes a GIF frame. * * @return Error return value. If a frame does not contain any image data, * GIF_OK is returned and gif->current_error is set to * GIF_FRAME_NO_DISPLAY * - GIF_FRAME_DATA_ERROR for GIF frame data error * - GIF_INSUFFICIENT_FRAME_DATA for insufficient data to complete the frame * - GIF_DATA_ERROR for GIF error (invalid frame header) * - GIF_INSUFFICIENT_DATA for insufficient data to do anything * - GIF_INSUFFICIENT_MEMORY for insufficient memory to process * - GIF_OK for successful decoding */ gif_result gif_decode_frame(gif_animation *gif, unsigned int frame); /** * Releases any workspace held by a gif */ void gif_finalise(gif_animation *gif); #endif chafa-1.8.0/libnsgif/log.h000066400000000000000000000010321411352071600153050ustar00rootroot00000000000000/* * Copyright 2003 James Bursa * Copyright 2004 John Tytgat * * This file is part of NetSurf, http://www.netsurf-browser.org/ * Licenced under the MIT License, * http://www.opensource.org/licenses/mit-license.php */ #include #ifndef _LIBNSGIF_LOG_H_ #define _LIBNSGIF_LOG_H_ #ifdef NDEBUG # define LOG(x) ((void) 0) #else # define LOG(x) do { fprintf(stderr, x), fputc('\n', stderr); } while (0) #endif /* NDEBUG */ #endif /* _LIBNSGIF_LOG_H_ */ chafa-1.8.0/libnsgif/lzw.c000066400000000000000000000242521411352071600153440ustar00rootroot00000000000000/* * This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/ * Licensed under the MIT License, * http://www.opensource.org/licenses/mit-license.php * * Copyright 2017 Michael Drake */ #include #include #include #include #include "lzw.h" /** * \file * \brief LZW decompression (implementation) * * Decoder for GIF LZW data. */ /** * Context for reading LZW data. * * LZW data is split over multiple sub-blocks. Each sub-block has a * byte at the start, which says the sub-block size, and then the data. * Zero-size sub-blocks have no data, and the biggest sub-block size is * 255, which means there are 255 bytes of data following the sub-block * size entry. * * Note that an individual LZW code can be split over up to three sub-blocks. */ struct lzw_read_ctx { const uint8_t *data; /**< Pointer to start of input data */ uint64_t data_len; /**< Input data length */ uint64_t data_sb_next; /**< Offset to sub-block size */ const uint8_t *sb_data; /**< Pointer to current sub-block in data */ uint64_t sb_bit; /**< Current bit offset in sub-block */ uint64_t sb_bit_count; /**< Bit count in sub-block */ }; /** * LZW dictionary entry. * * Records in the dictionary are composed of 1 or more entries. * Entries point to previous entries which can be followed to compose * the complete record. To compose the record in reverse order, take * the `last_value` from each entry, and move to the previous entry. * If the previous_entry's index is < the current clear_code, then it * is the last entry in the record. */ struct lzw_dictionary_entry { uint8_t last_value; /**< Last value for record ending at entry. */ uint8_t first_value; /**< First value for entry's record. */ uint16_t previous_entry; /**< Offset in dictionary to previous entry. */ }; /** * LZW decompression context. */ struct lzw_ctx { /** Input reading context */ struct lzw_read_ctx input; uint32_t previous_code; /**< Code read from input previously. */ uint32_t previous_code_first; /**< First value of previous code. */ uint32_t initial_code_size; /**< Starting LZW code size. */ uint32_t current_code_size; /**< Current LZW code size. */ uint32_t current_code_size_max; /**< Max code value for current size. */ uint32_t clear_code; /**< Special Clear code value */ uint32_t eoi_code; /**< Special End of Information code value */ uint32_t current_entry; /**< Next position in table to fill. */ /** Output value stack. */ uint8_t stack_base[1 << LZW_CODE_MAX]; /** LZW decode dictionary. Generated during decode. */ struct lzw_dictionary_entry table[1 << LZW_CODE_MAX]; }; /* Exported function, documented in lzw.h */ lzw_result lzw_context_create(struct lzw_ctx **ctx) { struct lzw_ctx *c = malloc(sizeof(*c)); if (c == NULL) { return LZW_NO_MEM; } *ctx = c; return LZW_OK; } /* Exported function, documented in lzw.h */ void lzw_context_destroy(struct lzw_ctx *ctx) { free(ctx); } /** * Advance the context to the next sub-block in the input data. * * \param[in] ctx LZW reading context, updated on success. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. */ static lzw_result lzw__block_advance(struct lzw_read_ctx *ctx) { uint64_t block_size; uint64_t next_block_pos = ctx->data_sb_next; const uint8_t *data_next = ctx->data + next_block_pos; if (next_block_pos >= ctx->data_len) { return LZW_NO_DATA; } block_size = *data_next; if ((next_block_pos + block_size) >= ctx->data_len) { return LZW_NO_DATA; } ctx->sb_bit = 0; ctx->sb_bit_count = block_size * 8; if (block_size == 0) { ctx->data_sb_next += 1; return LZW_OK_EOD; } ctx->sb_data = data_next + 1; ctx->data_sb_next += block_size + 1; return LZW_OK; } /** * Get the next LZW code of given size from the raw input data. * * Reads codes from the input data stream coping with GIF data sub-blocks. * * \param[in] ctx LZW reading context, updated. * \param[in] code_size Size of LZW code to get from data. * \param[out] code_out Returns an LZW code on success. * \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise. */ static inline lzw_result lzw__next_code( struct lzw_read_ctx *ctx, uint8_t code_size, uint32_t *code_out) { uint32_t code = 0; uint8_t current_bit = ctx->sb_bit & 0x7; uint8_t byte_advance = (current_bit + code_size) >> 3; assert(byte_advance <= 2); if (ctx->sb_bit + code_size <= ctx->sb_bit_count) { /* Fast path: code fully inside this sub-block */ const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3); switch (byte_advance) { case 2: code |= data[2] << 16; /* Fall through */ case 1: code |= data[1] << 8; /* Fall through */ case 0: code |= data[0] << 0; } ctx->sb_bit += code_size; } else { /* Slow path: code spans sub-blocks */ uint8_t byte = 0; uint8_t bits_remaining_0 = (code_size < (8 - current_bit)) ? code_size : (8 - current_bit); uint8_t bits_remaining_1 = code_size - bits_remaining_0; uint8_t bits_used[3] = { [0] = bits_remaining_0, [1] = bits_remaining_1 < 8 ? bits_remaining_1 : 8, [2] = bits_remaining_1 - 8, }; while (true) { const uint8_t *data = ctx->sb_data; lzw_result res; /* Get any data from end of this sub-block */ while (byte <= byte_advance && ctx->sb_bit < ctx->sb_bit_count) { code |= data[ctx->sb_bit >> 3] << (byte << 3); ctx->sb_bit += bits_used[byte]; byte++; } /* Check if we have all we need */ if (byte > byte_advance) { break; } /* Move to next sub-block */ res = lzw__block_advance(ctx); if (res != LZW_OK) { return res; } } } *code_out = (code >> current_bit) & ((1 << code_size) - 1); return LZW_OK; } /** * Clear LZW code dictionary. * * \param[in] ctx LZW reading context, updated. * \param[out] stack_pos_out Returns current stack position. * \return LZW_OK or error code. */ static lzw_result lzw__clear_codes( struct lzw_ctx *ctx, const uint8_t ** const stack_pos_out) { uint32_t code; uint8_t *stack_pos; /* Reset dictionary building context */ ctx->current_code_size = ctx->initial_code_size + 1; ctx->current_code_size_max = (1 << ctx->current_code_size) - 1;; ctx->current_entry = (1 << ctx->initial_code_size) + 2; /* There might be a sequence of clear codes, so process them all */ do { lzw_result res = lzw__next_code(&ctx->input, ctx->current_code_size, &code); if (res != LZW_OK) { return res; } } while (code == ctx->clear_code); /* The initial code must be from the initial dictionary. */ if (code > ctx->clear_code) { return LZW_BAD_ICODE; } /* Record this initial code as "previous" code, needed during decode. */ ctx->previous_code = code; ctx->previous_code_first = code; /* Reset the stack, and add first non-clear code added as first item. */ stack_pos = ctx->stack_base; *stack_pos++ = code; *stack_pos_out = stack_pos; return LZW_OK; } /* Exported function, documented in lzw.h */ lzw_result lzw_decode_init( struct lzw_ctx *ctx, const uint8_t *compressed_data, uint64_t compressed_data_len, uint64_t compressed_data_pos, uint8_t code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out) { struct lzw_dictionary_entry *table = ctx->table; /* Initialise the input reading context */ ctx->input.data = compressed_data; ctx->input.data_len = compressed_data_len; ctx->input.data_sb_next = compressed_data_pos; ctx->input.sb_bit = 0; ctx->input.sb_bit_count = 0; /* Initialise the dictionary building context */ ctx->initial_code_size = code_size; ctx->clear_code = (1 << code_size) + 0; ctx->eoi_code = (1 << code_size) + 1; /* Initialise the standard dictionary entries */ for (uint32_t i = 0; i < ctx->clear_code; ++i) { table[i].first_value = i; table[i].last_value = i; } *stack_base_out = ctx->stack_base; return lzw__clear_codes(ctx, stack_pos_out); } /* Exported function, documented in lzw.h */ lzw_result lzw_decode(struct lzw_ctx *ctx, const uint8_t ** const stack_pos_out) { lzw_result res; uint32_t code_new; uint32_t code_out; uint8_t last_value; uint8_t *stack_pos = ctx->stack_base; uint32_t clear_code = ctx->clear_code; uint32_t current_entry = ctx->current_entry; struct lzw_dictionary_entry * const table = ctx->table; /* Get a new code from the input */ res = lzw__next_code(&ctx->input, ctx->current_code_size, &code_new); if (res != LZW_OK) { return res; } /* Handle the new code */ if (code_new == clear_code) { /* Got Clear code */ return lzw__clear_codes(ctx, stack_pos_out); } else if (code_new == ctx->eoi_code) { /* Got End of Information code */ return LZW_EOI_CODE; } else if (code_new > current_entry) { /* Code is invalid */ return LZW_BAD_CODE; } else if (code_new < current_entry) { /* Code is in table */ code_out = code_new; last_value = table[code_new].first_value; } else { /* Code not in table */ *stack_pos++ = ctx->previous_code_first; code_out = ctx->previous_code; last_value = ctx->previous_code_first; } /* Add to the dictionary, only if there's space */ if (current_entry < (1 << LZW_CODE_MAX)) { struct lzw_dictionary_entry *entry = table + current_entry; entry->last_value = last_value; entry->first_value = ctx->previous_code_first; entry->previous_entry = ctx->previous_code; ctx->current_entry++; } /* Ensure code size is increased, if needed. */ if (current_entry == ctx->current_code_size_max) { if (ctx->current_code_size < LZW_CODE_MAX) { ctx->current_code_size++; ctx->current_code_size_max = (1 << ctx->current_code_size) - 1; } } /* Store details of this code as "previous code" to the context. */ ctx->previous_code_first = table[code_new].first_value; ctx->previous_code = code_new; /* Put rest of data for this code on output stack. * Note, in the case of "code not in table", the last entry of the * current code has already been placed on the stack above. */ while (code_out > clear_code) { struct lzw_dictionary_entry *entry = table + code_out; *stack_pos++ = entry->last_value; code_out = entry->previous_entry; } *stack_pos++ = table[code_out].last_value; *stack_pos_out = stack_pos; return LZW_OK; } chafa-1.8.0/libnsgif/lzw.h000066400000000000000000000063511411352071600153510ustar00rootroot00000000000000/* * This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/ * Licensed under the MIT License, * http://www.opensource.org/licenses/mit-license.php * * Copyright 2017 Michael Drake */ #ifndef LZW_H_ #define LZW_H_ /** * \file * \brief LZW decompression (interface) * * Decoder for GIF LZW data. */ /** Maximum LZW code size in bits */ #define LZW_CODE_MAX 12 /* Declare lzw internal context structure */ struct lzw_ctx; /** LZW decoding response codes */ typedef enum lzw_result { LZW_OK, /**< Success */ LZW_OK_EOD, /**< Success; reached zero-length sub-block */ LZW_NO_MEM, /**< Error: Out of memory */ LZW_NO_DATA, /**< Error: Out of data */ LZW_EOI_CODE, /**< Error: End of Information code */ LZW_BAD_ICODE, /**< Error: Bad initial LZW code */ LZW_BAD_CODE, /**< Error: Bad LZW code */ } lzw_result; /** * Create an LZW decompression context. * * \param[out] ctx Returns an LZW decompression context. Caller owned, * free with lzw_context_destroy(). * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_context_create( struct lzw_ctx **ctx); /** * Destroy an LZW decompression context. * * \param[in] ctx The LZW decompression context to destroy. */ void lzw_context_destroy( struct lzw_ctx *ctx); /** * Initialise an LZW decompression context for decoding. * * Caller owns neither `stack_base_out` or `stack_pos_out`. * * \param[in] ctx The LZW decompression context to initialise. * \param[in] compressed_data The compressed data. * \param[in] compressed_data_len Byte length of compressed data. * \param[in] compressed_data_pos Start position in data. Must be position * of a size byte at sub-block start. * \param[in] code_size The initial LZW code size to use. * \param[out] stack_base_out Returns base of decompressed data stack. * \param[out] stack_pos_out Returns current stack position. * There are `stack_pos_out - stack_base_out` * current stack entries. * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode_init( struct lzw_ctx *ctx, const uint8_t *compressed_data, uint64_t compressed_data_len, uint64_t compressed_data_pos, uint8_t code_size, const uint8_t ** const stack_base_out, const uint8_t ** const stack_pos_out); /** * Fill the LZW stack with decompressed data * * Ensure anything on the stack is used before calling this, as anything * on the stack before this call will be trampled. * * Caller does not own `stack_pos_out`. * * \param[in] ctx LZW reading context, updated. * \param[out] stack_pos_out Returns current stack position. * Use with `stack_base_out` value from previous * lzw_decode_init() call. * There are `stack_pos_out - stack_base_out` * current stack entries. * \return LZW_OK on success, or appropriate error code otherwise. */ lzw_result lzw_decode( struct lzw_ctx *ctx, const uint8_t ** const stack_pos_out); #endif chafa-1.8.0/tests/000077500000000000000000000000001411352071600137245ustar00rootroot00000000000000chafa-1.8.0/tests/Makefile.am000066400000000000000000000005151411352071600157610ustar00rootroot00000000000000AM_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) LDADD = $(GLIB_LIBS) $(top_builddir)/chafa/libchafa.la noinst_PROGRAMS = \ term-info-test term_info_test_SOURCES = \ term-info-test.c ## -- General --- ## Include $(top_builddir)/chafa to get generated chafaconfig.h. AM_CPPFLAGS = \ -I$(top_srcdir)/chafa \ -I$(top_builddir)/chafa chafa-1.8.0/tests/example.c000066400000000000000000000037051411352071600155300ustar00rootroot00000000000000#include #include #define PIX_WIDTH 3 #define PIX_HEIGHT 3 #define N_CHANNELS 4 int main (int argc, char *argv []) { const guint8 pixels [PIX_WIDTH * PIX_HEIGHT * N_CHANNELS] = { 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff }; ChafaSymbolMap *symbol_map; ChafaCanvasConfig *config; ChafaCanvas *canvas; GString *gs; /* Specify the symbols we want */ symbol_map = chafa_symbol_map_new (); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_ALL); /* Set up a configuration with the symbols and the canvas size in characters */ config = chafa_canvas_config_new (); chafa_canvas_config_set_geometry (config, 40, 20); chafa_canvas_config_set_symbol_map (config, symbol_map); /* Create canvas */ canvas = chafa_canvas_new (config); /* Draw pixels and build ANSI string */ /* Test a deprecated function */ chafa_canvas_set_contents_rgba8 (canvas, pixels, PIX_WIDTH, PIX_HEIGHT, PIX_WIDTH * N_CHANNELS); chafa_canvas_draw_all_pixels (canvas, CHAFA_PIXEL_RGBA8_UNASSOCIATED, pixels, PIX_WIDTH, PIX_HEIGHT, PIX_WIDTH * N_CHANNELS); gs = chafa_canvas_build_ansi (canvas); /* Print the string */ fwrite (gs->str, sizeof (char), gs->len, stdout); fputc ('\n', stdout); /* Free resources */ g_string_free (gs, TRUE); chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); chafa_symbol_map_unref (symbol_map); return 0; } chafa-1.8.0/tests/ncurses.c000066400000000000000000000141301411352071600155510ustar00rootroot00000000000000/* Example program that shows how to use a Chafa canvas with ncurses. * * To build: * * gcc ncurses.c $(pkg-config --libs --cflags chafa) $(ncursesw6-config --libs --cflags) -o ncurses */ #include #include #include /* Parameters for gradient pixmap. It will be scaled automatically to fit the canvas, * so this just needs to be big enough to avoid it getting too blurry. The number * of channels is always four, corresponding to CHAFA_PIXEL_RGBA8_UNASSOCIATED. */ #define PIXMAP_WIDTH 1024 #define PIXMAP_HEIGHT 1024 #define PIXMAP_N_CHANNELS 4 /* Terminal size detected in main loop */ static int screen_width, screen_height; static ChafaCanvasMode detect_canvas_mode (void) { /* COLORS is a global variable defined by ncurses. It depends on termcap * for the terminal specified in TERM. In order to test the various modes, you * could try running this program with either of these: * * TERM=xterm * TERM=xterm-16color * TERM=xterm-256color * TERM=xterm-direct */ return COLORS >= (1 << 24) ? CHAFA_CANVAS_MODE_TRUECOLOR : COLORS >= (1 << 8) ? CHAFA_CANVAS_MODE_INDEXED_240 : COLORS >= (1 << 4) ? CHAFA_CANVAS_MODE_INDEXED_16 : COLORS >= (1 << 3) ? CHAFA_CANVAS_MODE_INDEXED_8 : CHAFA_CANVAS_MODE_FGBG; } static ChafaCanvas * create_canvas (void) { ChafaSymbolMap *symbol_map; ChafaCanvasConfig *config; ChafaCanvas *canvas; ChafaCanvasMode mode = detect_canvas_mode (); /* Specify the symbols we want: Box drawing and block elements are both * useful and widely supported. */ symbol_map = chafa_symbol_map_new (); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_SPACE); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_BLOCK); chafa_symbol_map_add_by_tags (symbol_map, CHAFA_SYMBOL_TAG_BORDER); /* Set up a configuration with the symbols and the canvas size in characters */ config = chafa_canvas_config_new (); chafa_canvas_config_set_canvas_mode (config, mode); chafa_canvas_config_set_symbol_map (config, symbol_map); /* Reserve one row below canvas for status text */ chafa_canvas_config_set_geometry (config, screen_width, screen_height - 1); /* Apply tweaks for low-color modes */ if (mode == CHAFA_CANVAS_MODE_INDEXED_240) { /* We get better color fidelity using DIN99d in 240-color mode. * This is not needed in 16-color mode because it uses an extra * preprocessing step instead, which usually performs better. */ chafa_canvas_config_set_color_space (config, CHAFA_COLOR_SPACE_DIN99D); } if (mode == CHAFA_CANVAS_MODE_FGBG) { /* Enable dithering in monochromatic mode so gradients become * somewhat legible. */ chafa_canvas_config_set_dither_mode (config, CHAFA_DITHER_MODE_ORDERED); } /* Create canvas */ canvas = chafa_canvas_new (config); chafa_symbol_map_unref (symbol_map); chafa_canvas_config_unref (config); return canvas; } static void paint_canvas (ChafaCanvas *canvas) { guint8 *pixmap; int x, y; /* Generate a gradient pixmap */ pixmap = g_malloc (PIXMAP_WIDTH * PIXMAP_HEIGHT * PIXMAP_N_CHANNELS); for (y = 0; y < PIXMAP_HEIGHT; y++) { for (x = 0; x < PIXMAP_WIDTH; x++) { guint8 *pixel = pixmap + (y * PIXMAP_WIDTH + x) * PIXMAP_N_CHANNELS; pixel [0] = (x * 255) / PIXMAP_WIDTH; pixel [1] = (y * 255) / PIXMAP_HEIGHT; pixel [2] = 0; pixel [3] = 255; } } /* Paint it to the canvas */ chafa_canvas_draw_all_pixels (canvas, CHAFA_PIXEL_RGBA8_UNASSOCIATED, pixmap, PIXMAP_WIDTH, PIXMAP_HEIGHT, PIXMAP_WIDTH * PIXMAP_N_CHANNELS); g_free (pixmap); } static void canvas_to_ncurses (ChafaCanvas *canvas) { ChafaCanvasMode mode = detect_canvas_mode (); int pair = 256; /* Reserve lower pairs for application in direct-color mode */ int x, y; for (y = 0; y < screen_height - 1; y++) { for (x = 0; x < screen_width; x++) { wchar_t wc [2]; cchar_t cch; int fg, bg; /* wchar_t is 32-bit in glibc, but this may not work on e.g. Windows */ wc [0] = chafa_canvas_get_char_at (canvas, x, y); wc [1] = 0; if (mode == CHAFA_CANVAS_MODE_TRUECOLOR) { chafa_canvas_get_colors_at (canvas, x, y, &fg, &bg); init_extended_pair (pair, fg, bg); } else if (mode == CHAFA_CANVAS_MODE_FGBG) { pair = 0; } else { /* In indexed color mode, we've probably got enough pairs * to just let ncurses allocate and reuse as needed. */ chafa_canvas_get_raw_colors_at (canvas, x, y, &fg, &bg); pair = alloc_pair (fg, bg); } setcchar (&cch, wc, A_NORMAL, -1, &pair); mvadd_wch (y, x, &cch); pair++; } } } static void show_image (void) { ChafaCanvas *canvas; canvas = create_canvas (); paint_canvas (canvas); canvas_to_ncurses (canvas); mvprintw (screen_height - 1, 0, "%d colors detected. Try resizing, or press any key to exit.", COLORS); chafa_canvas_unref (canvas); } int main (int argc, char *argv []) { int running = TRUE; /* Set up locale to get proper Unicode */ setlocale (LC_ALL, ""); /* Start interactive ncurses session */ initscr (); start_color (); use_default_colors (); raw (); keypad (stdscr, TRUE); noecho (); curs_set (0); /* Keep running until a key is pressed. Handle terminal resize. */ while (running) { clear (); getmaxyx (stdscr, screen_height, screen_width); show_image (); refresh (); if (getch () != KEY_RESIZE) running = FALSE; } endwin (); return 0; } chafa-1.8.0/tests/postinstall.sh000077500000000000000000000001331411352071600166340ustar00rootroot00000000000000#!/bin/sh set -evx cc example.c $(pkg-config --libs --cflags chafa) -o example ./example chafa-1.8.0/tests/term-info-test.c000066400000000000000000000047061411352071600167540ustar00rootroot00000000000000#include #include static void formatting_test (void) { ChafaTermInfo *ti; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 12 + 1]; gchar *out = buf; ti = chafa_term_info_new (); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_RESET_TERMINAL_SOFT, "soft-reset", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_CURSOR_UP, "cursor-up-%1", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_CURSOR_TO_POS, "%1-cursor-to-pos-%2", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT, "%1%2-fg-direct-%3", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT, "%1-bg-direct%2%3-", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT, "%1%2-fgbg-%3,%4%5-%6", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FG_16, "aix%1,", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_BG_16, "aix%1,", NULL); chafa_term_info_set_seq (ti, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16, "aix-%1-%2,", NULL); out = chafa_term_info_emit_reset_terminal_soft (ti, out); out = chafa_term_info_emit_cursor_up (ti, out, 9876); out = chafa_term_info_emit_cursor_to_pos (ti, out, 1234, 0); out = chafa_term_info_emit_set_color_fg_direct (ti, out, 41, 0, 244); out = chafa_term_info_emit_set_color_bg_direct (ti, out, 0, 100, 99); out = chafa_term_info_emit_set_color_fgbg_direct (ti, out, 1, 199, 99, 0, 0, 9); out = chafa_term_info_emit_set_color_fg_16 (ti, out, 0); out = chafa_term_info_emit_set_color_fg_16 (ti, out, 8); out = chafa_term_info_emit_set_color_bg_16 (ti, out, 0); out = chafa_term_info_emit_set_color_bg_16 (ti, out, 8); out = chafa_term_info_emit_set_color_fgbg_16 (ti, out, 0, 0); out = chafa_term_info_emit_set_color_fgbg_16 (ti, out, 8, 8); *out = '\0'; chafa_term_info_unref (ti); g_assert_cmpstr (buf, ==, "soft-reset" "cursor-up-9876" "1234-cursor-to-pos-0" "410-fg-direct-244" "0-bg-direct10099-" "1199-fgbg-99,00-9" "aix30," "aix90," "aix40," "aix100," "aix-30-40," "aix-90-100,"); } int main (int argc, char *argv []) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/term-info/formatting", formatting_test); return g_test_run (); } chafa-1.8.0/tools/000077500000000000000000000000001411352071600137225ustar00rootroot00000000000000chafa-1.8.0/tools/Makefile.am000066400000000000000000000000301411352071600157470ustar00rootroot00000000000000SUBDIRS = chafa fontgen chafa-1.8.0/tools/chafa/000077500000000000000000000000001411352071600147645ustar00rootroot00000000000000chafa-1.8.0/tools/chafa/Makefile.am000066400000000000000000000016721411352071600170260ustar00rootroot00000000000000if WANT_TOOLS bin_PROGRAMS = \ chafa chafa_SOURCES = \ chafa.c \ file-mapping.c \ file-mapping.h \ font-loader.c \ font-loader.h \ gif-loader.c \ gif-loader.h \ named-colors.c \ named-colors.h \ xwd-loader.c \ xwd-loader.h # We can pass -rpath so the binary knows where to find libchafa.so when # installed outside /usr (e.g. the default /usr/local). This affects Ubuntu. # Resolved by running ldconfig. See Github issue #32. # # This is disabled by default. chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(MAGICKWAND_CFLAGS) $(FREETYPE_CFLAGS) if ENABLE_RPATH chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir) endif chafa_LDADD = $(GLIB_LIBS) $(MAGICKWAND_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la endif ## -- General --- ## Include $(top_builddir)/chafa to get generated chafaconfig.h. AM_CPPFLAGS = \ -I$(top_srcdir)/chafa \ -I$(top_builddir)/chafa \ -I$(top_srcdir)/libnsgif chafa-1.8.0/tools/chafa/chafa.c000066400000000000000000001704601411352071600162020ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include /* strspn, strlen, strcmp, strncmp, memset */ #include /* setlocale */ #include /* ioctl */ #include /* open */ #include /* stat */ #include /* open */ #include /* STDOUT_FILENO */ #include /* sigaction */ #include /* tcgetattr, tcsetattr */ #ifdef HAVE_WAND_MAGICKWAND_H # include #else /* HAVE_MAGICKWAND_MAGICKWAND_H */ # include #endif #include #include "font-loader.h" #include "gif-loader.h" #include "named-colors.h" #include "xwd-loader.h" #define ANIM_FPS_MAX 100000.0 #define FILE_DURATION_DEFAULT 0.0 typedef struct { gchar *executable_name; gboolean show_help; gboolean show_version; GList *args; ChafaCanvasMode mode; ChafaColorExtractor color_extractor; ChafaColorSpace color_space; ChafaDitherMode dither_mode; ChafaPixelMode pixel_mode; gint dither_grain_width; gint dither_grain_height; gdouble dither_intensity; ChafaSymbolMap *symbol_map; ChafaSymbolMap *fill_symbol_map; gboolean symbols_specified; gboolean is_interactive; gboolean clear; gboolean verbose; gboolean invert; gboolean preprocess; gboolean stretch; gboolean zoom; gboolean watch; gboolean fg_only; gint width, height; gint cell_width, cell_height; gdouble font_ratio; gint work_factor; gint optimization_level; ChafaOptimizations optimizations; guint32 fg_color; gboolean fg_color_set; guint32 bg_color; gboolean bg_color_set; gdouble transparency_threshold; gdouble file_duration_s; /* If > 0.0, override the framerate specified by the input file. */ gdouble anim_fps; /* Applied after FPS is determined. If final value >= ANIM_FPS_MAX, * eliminate interframe delay altogether. */ gdouble anim_speed_multiplier; ChafaTermInfo *term_info; } GlobalOptions; static GlobalOptions options; static volatile sig_atomic_t interrupted_by_user = FALSE; static void sigint_handler (G_GNUC_UNUSED int sig) { interrupted_by_user = TRUE; } static guchar get_hex_byte (const gchar *str) { gint b = 0; if (*str >= '0' && *str <= '9') b = *str - '0'; else if (*str >= 'a' && *str <= 'f') b = *str - 'a' + 10; else if (*str >= 'A' && *str <= 'F') b = *str - 'A' + 10; else g_assert_not_reached (); b <<= 4; str++; if (*str >= '0' && *str <= '9') b |= *str - '0'; else if (*str >= 'a' && *str <= 'f') b |= *str - 'a' + 10; else if (*str >= 'A' && *str <= 'F') b |= *str - 'A' + 10; else g_assert_not_reached (); return b; } static gboolean parse_color (const gchar *str, guint32 *col_out, GError **error) { gint len; gchar *label = NULL; gchar *p0; gboolean result = FALSE; str += strspn (str, " \t"); len = strspn (str, "#abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); if (len < 1) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unrecognized color '%s'.", str); goto out; } p0 = label = g_ascii_strdown (str, len); /* Hex triplet, optionally prefixed with # or 0x */ if (*p0 == '#') p0++; else if (p0 [0] == '0' && (p0 [1] == 'x')) p0 += 2; len = strspn (p0, "0123456789abcdef"); if (len != (gint) strlen (p0) || len < 6) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unrecognized color '%s'.", str); goto out; } if (len > 6) p0 += (len - 6); *col_out = (get_hex_byte (p0) << 16) + (get_hex_byte (p0 + 2) << 8) + get_hex_byte (p0 + 4); result = TRUE; out: g_free (label); return result; } static GList * collect_variable_arguments (int *argc, char **argv [], gint first_arg) { GList *l = NULL; gint i; for (i = first_arg; i < *argc; i++) l = g_list_prepend (l, g_strdup ((*argv) [i])); return g_list_reverse (l); } static const gchar copyright_notice [] = "Copyright (C) 2018-2021 Hans Petter Jansson et al.\n" "Incl. libnsgif copyright (C) 2004 Richard Wilson, copyright (C) 2008 Sean Fox\n\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"; static void print_version (void) { gchar *builtin_features_str = chafa_describe_features (chafa_get_builtin_features ()); gchar *supported_features_str = chafa_describe_features (chafa_get_supported_features ()); g_printerr ("Chafa version %s\n\nFeatures: %s\nApplying: %s\n\n%s\n", CHAFA_VERSION, chafa_get_builtin_features () ? builtin_features_str : "none", chafa_get_supported_features () ? supported_features_str : "none", copyright_notice); g_free (builtin_features_str); g_free (supported_features_str); } static void print_summary (void) { const gchar *summary = " Chafa (Character Art Facsimile) image-to-text converter.\n\n" "Options:\n" " -h --help Show help.\n" " --version Show version.\n" " -v, --verbose Be verbose.\n\n" " --bg=COLOR Background color of display (color name or hex).\n" " --clear Clear screen before processing each file.\n" " -c, --colors=MODE Set output color mode; one of [none, 2, 8, 16, 240, 256,\n" " full]. Defaults to full (24-bit).\n" " --color-extractor=EXTR Method for extracting color from an area\n" " [average, median]. Average is the default.\n" " --color-space=CS Color space used for quantization; one of [rgb, din99d].\n" " Defaults to rgb, which is faster but less accurate.\n" " --dither=DITHER Set output dither mode; one of [none, ordered,\n" " diffusion]. No effect with 24-bit color. Defaults to none.\n" " --dither-grain=WxH Set dimensions of dither grains in 1/8ths of a\n" " character cell [1, 2, 4, 8]. Defaults to 4x4.\n" " --dither-intensity=NUM Multiplier for dither intensity [0.0 - inf].\n" " Defaults to 1.0.\n" " -d, --duration=SECONDS The time to show each file. If showing a single file,\n" " defaults to zero for a still image and infinite for an\n" " animation. For multiple files, defaults to zero. Animations\n" " will always be played through at least once.\n" " --fg=COLOR Foreground color of display (color name or hex).\n" " --fg-only Leave the background color untouched. This produces\n" " character-cell output using foreground colors only.\n" " --fill=SYMS Specify character symbols to use for fill/gradients.\n" " Defaults to none. See below for full usage.\n" " --font-ratio=W/H Target font's width/height ratio. Can be specified as\n" " a real number or a fraction. Defaults to 1/2.\n" " -f, --format=FORMAT Set output format; one of [iterm, kitty, sixels, symbols].\n" " Iterm, kitty and sixels yield much higher quality but enjoy\n" " limited support. Symbols mode yields beautiful ANSI art.\n" " --glyph-file=FILE Load glyph information from FILE, which can be any\n" " font file supported by FreeType (TTF, PCF, etc).\n" " --invert Invert video. For display with bright backgrounds in\n" " color modes 2 and none. Swaps --fg and --bg.\n" " -O, --optimize=NUM Compress the output by using control sequences\n" " intelligently [0-9]. 0 disables, 9 enables every\n" " available optimization. Defaults to 5, except for when\n" " used with \"-c none\", where it defaults to 0.\n" " -p, --preprocess=BOOL Image preprocessing [on, off]. Defaults to on with 16\n" " colors or lower, off otherwise.\n" " -s, --size=WxH Set maximum output dimensions in columns and rows. By\n" " default this will be the size of your terminal, or 80x25\n" " if size detection fails.\n" " --speed=SPEED Set the speed animations will play at. This can be\n" " either a unitless multiplier, or a real number followed\n" " by \"fps\" to apply a specific framerate.\n" " --stretch Stretch image to fit output dimensions; ignore aspect.\n" " Implies --zoom.\n" " --symbols=SYMS Specify character symbols to employ in final output.\n" " See below for full usage and a list of symbol classes.\n" " -t, --threshold=NUM Threshold above which full transparency will be used\n" " [0.0 - 1.0].\n" " --watch Watch a single input file, redisplaying it whenever its\n" " contents change. Will run until manually interrupted\n" " or, if --duration is set, until it expires.\n" " -w, --work=NUM How hard to work in terms of CPU and memory [1-9]. 1 is the\n" " cheapest, 9 is the most accurate. Defaults to 5.\n" " --zoom Allow scaling up beyond one character per pixel.\n\n" " Accepted classes for --symbols and --fill are [all, none, space, solid,\n" " stipple, block, border, diagonal, dot, quad, half, hhalf, vhalf, inverted,\n" " braille, technical, geometric, ascii, legacy, sextant, wedge, wide, narrow,\n" " extra]. Some symbols belong to multiple classes, e.g. diagonals are also\n" " borders. You can specify a list of classes separated by commas, or prefix them\n" " with + and - to add or remove symbols relative to the existing set. The\n" " ordering is significant.\n\n" " The default symbol set is block+border+space-wide-inverted for all modes\n" " except \"none\", which uses block+border+space-wide (including inverse symbols).\n\n" "Examples:\n" " Generate 16-color output with perceptual color picking and avoid using\n" " dot and stipple symbols:\n\n" " $ chafa -c 16 --color-space din99d --symbols -dot-stipple in.jpg\n\n" " Generate uncolored output using block and border symbols, but avoid the\n" " solid block symbol:\n\n" " $ chafa -c none --symbols block+border-solid in.png\n"; g_printerr ("Usage:\n %s [OPTION...] [FILE...]\n\n%s\n", options.executable_name, summary); } static gboolean parse_colors_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; if (!g_ascii_strcasecmp (value, "none")) options.mode = CHAFA_CANVAS_MODE_FGBG; else if (!g_ascii_strcasecmp (value, "2")) options.mode = CHAFA_CANVAS_MODE_FGBG_BGFG; else if (!g_ascii_strcasecmp (value, "16")) options.mode = CHAFA_CANVAS_MODE_INDEXED_16; else if (!g_ascii_strcasecmp (value, "8")) options.mode = CHAFA_CANVAS_MODE_INDEXED_8; else if (!g_ascii_strcasecmp (value, "240")) options.mode = CHAFA_CANVAS_MODE_INDEXED_240; else if (!g_ascii_strcasecmp (value, "256")) options.mode = CHAFA_CANVAS_MODE_INDEXED_256; else if (!g_ascii_strcasecmp (value, "full") || !g_ascii_strcasecmp (value, "rgb") || !g_ascii_strcasecmp (value, "tc") || !g_ascii_strcasecmp (value, "truecolor")) options.mode = CHAFA_CANVAS_MODE_TRUECOLOR; else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Colors must be one of [none, 2, 16, 240, 256, full]."); result = FALSE; } return result; } static gboolean parse_color_extractor_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; if (!g_ascii_strcasecmp (value, "average")) options.color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; else if (!g_ascii_strcasecmp (value, "median")) options.color_extractor = CHAFA_COLOR_EXTRACTOR_MEDIAN; else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Color extractor must be one of [average, median]."); result = FALSE; } return result; } static gboolean parse_color_space_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; if (!g_ascii_strcasecmp (value, "rgb")) options.color_space = CHAFA_COLOR_SPACE_RGB; else if (!g_ascii_strcasecmp (value, "din99d")) options.color_space = CHAFA_COLOR_SPACE_DIN99D; else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Color space must be one of [rgb, din99d]."); result = FALSE; } return result; } static gboolean parse_dither_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; if (!g_ascii_strcasecmp (value, "none")) options.dither_mode = CHAFA_DITHER_MODE_NONE; else if (!g_ascii_strcasecmp (value, "ordered") || !g_ascii_strcasecmp (value, "bayer")) options.dither_mode = CHAFA_DITHER_MODE_ORDERED; else if (!g_ascii_strcasecmp (value, "diffusion") || !g_ascii_strcasecmp (value, "fs")) options.dither_mode = CHAFA_DITHER_MODE_DIFFUSION; else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Dither must be one of [none, ordered, diffusion]."); result = FALSE; } return result; } static gboolean parse_font_ratio_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; gdouble ratio = -1.0; gint width = -1, height = -1; gint o = 0; sscanf (value, "%d/%d%n", &width, &height, &o); if (width < 0 || height < 0) sscanf (value, "%d:%d%n", &width, &height, &o); if (width < 0 || height < 0) sscanf (value, "%lf%n", &ratio, &o); if (o != 0 && value [o] != '\0') { width = -1; height = -1; ratio = -1.0; goto out; } if (width > 0 && height > 0) ratio = width / (gdouble) height; out: if (ratio <= 0.0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Font ratio must be specified as a real number or fraction."); result = FALSE; } else { options.font_ratio = ratio; } return result; } static gboolean parse_symbols_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { options.symbols_specified = TRUE; return chafa_symbol_map_apply_selectors (options.symbol_map, value, error); } static gboolean parse_fill_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { return chafa_symbol_map_apply_selectors (options.fill_symbol_map, value, error); } static gboolean parse_format_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; ChafaPixelMode pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; if (!strcasecmp (value, "symbol") || !strcasecmp (value, "symbols") || !strcasecmp (value, "ansi")) { pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; } else if (!strcasecmp (value, "sixel") || !strcasecmp (value, "sixels")) { pixel_mode = CHAFA_PIXEL_MODE_SIXELS; } else if (!strcasecmp (value, "kitty")) { pixel_mode = CHAFA_PIXEL_MODE_KITTY; } else if (!strcasecmp (value, "iterm") || !strcasecmp (value, "iterm2")) { pixel_mode = CHAFA_PIXEL_MODE_ITERM2; } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Output format given as '%s'. Must be one of [iterm, kitty, sixels, symbols].", value); result = FALSE; } if (result) options.pixel_mode = pixel_mode; return result; } static gboolean parse_size_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; gint width = -1, height = -1; gint n; gint o = 0; n = sscanf (value, "%d%n", &width, &o); if (value [o] == 'x' && value [o + 1] != '\0') { gint o2; n = sscanf (value + o + 1, "%d%n", &height, &o2); if (n == 1 && value [o + o2 + 1] != '\0') { width = height = -1; goto out; } } out: if (width < 0 && height < 0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Output dimensions must be specified as [width]x[height], [width]x or x[height], e.g 80x25, 80x or x25."); result = FALSE; } else if (width == 0 || height == 0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Output dimensions must be at least 1x1."); result = FALSE; } options.width = width; options.height = height; return result; } static gboolean parse_dither_grain_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; gint width = -1, height = -1; gint n; gint o = -1; n = sscanf (value, "%d%n", &width, &o); if (o > 0 && value [o] == 'x' && value [o + 1] != '\0') { gint o2; n = sscanf (value + o + 1, "%d%n", &height, &o2); if (n == 1 && value [o + o2 + 1] != '\0') { width = height = -1; goto out; } } out: if (height < 0) height = width; if (width < 0) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Grain size must be specified as [width]x[height] or [dim], e.g. 8x4 or 4."); result = FALSE; } else if ((width != 1 && width != 2 && width != 4 && width != 8) || (height != 1 && height != 2 && height != 4 && height != 8)) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Grain dimensions must be exactly 1, 2, 4 or 8."); result = FALSE; } options.dither_grain_width = width; options.dither_grain_height = height; return result; } static gboolean parse_glyph_file_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = FALSE; FileMapping *file_mapping; FontLoader *font_loader; gunichar c; gpointer c_bitmap; gint width, height; file_mapping = file_mapping_new (value); if (!file_mapping) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unable to open glyph file '%s'.", value); goto out; } font_loader = font_loader_new_from_mapping (file_mapping); file_mapping = NULL; /* Font loader owns it now */ if (!font_loader) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Unable to load glyph file '%s'.", value); goto out; } while (font_loader_get_next_glyph (font_loader, &c, &c_bitmap, &width, &height)) { chafa_symbol_map_add_glyph (options.symbol_map, c, CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, width, height, width * 4); chafa_symbol_map_add_glyph (options.fill_symbol_map, c, CHAFA_PIXEL_RGBA8_PREMULTIPLIED, c_bitmap, width, height, width * 4); g_free (c_bitmap); } font_loader_destroy (font_loader); result = TRUE; out: if (file_mapping) file_mapping_destroy (file_mapping); return result; } static gboolean parse_preprocess_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = TRUE; if (!g_ascii_strcasecmp (value, "on") || !g_ascii_strcasecmp (value, "yes")) { options.preprocess = TRUE; } else if (!g_ascii_strcasecmp (value, "off") || !g_ascii_strcasecmp (value, "no")) { options.preprocess = FALSE; } else { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Preprocessing must be one of [on, off]."); result = FALSE; } return result; } static gboolean parse_anim_speed_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { gboolean result = FALSE; gdouble d; if (!g_ascii_strcasecmp (value, "max") || !g_ascii_strcasecmp (value, "maximum")) { options.anim_fps = G_MAXDOUBLE; result = TRUE; } else { gchar *endptr; d = g_strtod (value, &endptr); if (endptr == value || d <= 0.0) goto out; while (g_ascii_isspace (*endptr)) endptr++; if (!g_ascii_strcasecmp (endptr, "fps")) { options.anim_fps = d; result = TRUE; } else if (*endptr == '\0') { options.anim_speed_multiplier = d; result = TRUE; } /* If there's unknown junk at the end, fail. */ } out: if (!result) g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Animation speed must be either \"max\", a real multiplier, or a real framerate followed by \"fps\". It must be greater than zero."); return result; } static gboolean parse_color_str (const gchar *value, guint32 *col_out, const gchar *error_message, GError **error) { const NamedColor *named_color; guint32 col = 0x000000; gboolean result = TRUE; named_color = find_color_by_name (value); if (named_color) { col = (named_color->color [0] << 16) | (named_color->color [1] << 8) | (named_color->color [2]); } else if (!parse_color (value, &col, NULL)) { g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, error_message, value); result = FALSE; } if (result) *col_out = col; return result; } static gboolean parse_fg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { options.fg_color_set = parse_color_str (value, &options.fg_color, "Unrecognized foreground color '%s'.", error); return options.fg_color_set; } static gboolean parse_bg_color_arg (G_GNUC_UNUSED const gchar *option_name, const gchar *value, G_GNUC_UNUSED gpointer data, GError **error) { options.bg_color_set = parse_color_str (value, &options.bg_color, "Unrecognized background color '%s'.", error); return options.bg_color_set; } static void get_tty_size (void) { struct winsize w; gboolean have_winsz = FALSE; if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &w) >= 0 || ioctl (STDERR_FILENO, TIOCGWINSZ, &w) >= 0 || ioctl (STDIN_FILENO, TIOCGWINSZ, &w) >= 0) have_winsz = TRUE; if (!have_winsz) { const gchar *term_path; gint fd = -1; term_path = ctermid (NULL); if (term_path) fd = open (term_path, O_RDONLY); if (fd >= 0) { if (ioctl (fd, TIOCGWINSZ, &w) >= 0) have_winsz = TRUE; close (fd); } } if (!have_winsz) return; if (w.ws_col > 0) options.width = w.ws_col; /* We subtract one line for the user's prompt */ if (w.ws_row > 2) options.height = w.ws_row - 1; /* If .ws_xpixel and .ws_ypixel are filled out, we can calculate * aspect information for the font used. Sixel-capable terminals * like mlterm set these fields, but most others do not. */ if (w.ws_xpixel > 0 && w.ws_ypixel > 0) { options.cell_width = w.ws_xpixel / w.ws_col; options.cell_height = w.ws_ypixel / w.ws_row; if (options.cell_width > 0 && options.cell_height > 0) options.font_ratio = (gfloat) options.cell_width / (gfloat) options.cell_height; } } static struct termios saved_termios; static void tty_options_init (void) { struct termios t; if (!options.is_interactive) return; tcgetattr (STDIN_FILENO, &saved_termios); t = saved_termios; t.c_lflag &= ~ECHO; tcsetattr (STDIN_FILENO, TCSANOW, &t); } static void tty_options_deinit (void) { if (!options.is_interactive) return; tcsetattr (STDIN_FILENO, TCSANOW, &saved_termios); } static void detect_terminal (ChafaTermInfo **term_info_out, ChafaCanvasMode *mode_out, ChafaPixelMode *pixel_mode_out) { ChafaCanvasMode mode; ChafaPixelMode pixel_mode; ChafaTermInfo *term_info; ChafaTermInfo *fallback_info; gchar **envp; envp = g_get_environ (); term_info = chafa_term_db_detect (chafa_term_db_get_default (), envp); if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_DIRECT) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_DIRECT)) mode = CHAFA_CANVAS_MODE_TRUECOLOR; else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_256) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_256)) mode = CHAFA_CANVAS_MODE_INDEXED_240; else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FG_16) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_BG_16)) mode = CHAFA_CANVAS_MODE_INDEXED_16; else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_RESET_ATTRIBUTES)) mode = CHAFA_CANVAS_MODE_FGBG_BGFG; else mode = CHAFA_CANVAS_MODE_FGBG; if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_KITTY_IMMEDIATE_IMAGE_V1)) pixel_mode = CHAFA_PIXEL_MODE_KITTY; else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS) && chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_END_SIXELS)) pixel_mode = CHAFA_PIXEL_MODE_SIXELS; else pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS; /* Make sure we have fallback sequences in case the user forces * a mode that's technically unsupported by the terminal. */ fallback_info = chafa_term_db_get_fallback_info (chafa_term_db_get_default ()); chafa_term_info_supplement (term_info, fallback_info); chafa_term_info_unref (fallback_info); *term_info_out = term_info; *mode_out = mode; *pixel_mode_out = pixel_mode; g_strfreev (envp); } static gboolean parse_options (int *argc, char **argv []) { GError *error = NULL; GOptionContext *context; gint detected_width; gboolean result = TRUE; const GOptionEntry option_entries [] = { /* Note: The descriptive blurbs here are never shown to the user */ { "help", 'h', 0, G_OPTION_ARG_NONE, &options.show_help, "Show help", NULL }, { "version", '\0', 0, G_OPTION_ARG_NONE, &options.show_version, "Show version", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options.verbose, "Be verbose", NULL }, { "bg", '\0', 0, G_OPTION_ARG_CALLBACK, parse_bg_color_arg, "Background color of display", NULL }, { "clear", '\0', 0, G_OPTION_ARG_NONE, &options.clear, "Clear", NULL }, { "colors", 'c', 0, G_OPTION_ARG_CALLBACK, parse_colors_arg, "Colors (none, 2, 16, 256, 240 or full)", NULL }, { "color-extractor", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_extractor_arg, "Color extractor (average or median)", NULL }, { "color-space", '\0', 0, G_OPTION_ARG_CALLBACK, parse_color_space_arg, "Color space (rgb or din99d)", NULL }, { "dither", '\0', 0, G_OPTION_ARG_CALLBACK, parse_dither_arg, "Dither", NULL }, { "dither-grain",'\0', 0, G_OPTION_ARG_CALLBACK, parse_dither_grain_arg, "Dither grain", NULL }, { "dither-intensity", '\0', 0, G_OPTION_ARG_DOUBLE, &options.dither_intensity, "Dither intensity", NULL }, { "duration", 'd', 0, G_OPTION_ARG_DOUBLE, &options.file_duration_s, "Duration", NULL }, { "fg", '\0', 0, G_OPTION_ARG_CALLBACK, parse_fg_color_arg, "Foreground color of display", NULL }, { "fg-only", '\0', 0, G_OPTION_ARG_NONE, &options.fg_only, "Foreground only", NULL }, { "fill", '\0', 0, G_OPTION_ARG_CALLBACK, parse_fill_arg, "Fill symbols", NULL }, { "format", 'f', 0, G_OPTION_ARG_CALLBACK, parse_format_arg, "Format of output pixel data (iterm, kitty, sixels or symbols)", NULL }, { "font-ratio", '\0', 0, G_OPTION_ARG_CALLBACK, parse_font_ratio_arg, "Font ratio", NULL }, { "glyph-file", '\0', 0, G_OPTION_ARG_CALLBACK, parse_glyph_file_arg, "Glyph file", NULL }, { "invert", '\0', 0, G_OPTION_ARG_NONE, &options.invert, "Invert foreground/background", NULL }, { "optimize", 'O', 0, G_OPTION_ARG_INT, &options.optimization_level, "Optimization", NULL }, { "preprocess", 'p', 0, G_OPTION_ARG_CALLBACK, parse_preprocess_arg, "Preprocessing", NULL }, { "work", 'w', 0, G_OPTION_ARG_INT, &options.work_factor, "Work factor", NULL }, { "size", 's', 0, G_OPTION_ARG_CALLBACK, parse_size_arg, "Output size", NULL }, { "speed", '\0', 0, G_OPTION_ARG_CALLBACK, parse_anim_speed_arg, "Animation speed", NULL }, { "stretch", '\0', 0, G_OPTION_ARG_NONE, &options.stretch, "Stretch image to fix output dimensions", NULL }, { "symbols", '\0', 0, G_OPTION_ARG_CALLBACK, parse_symbols_arg, "Output symbols", NULL }, { "threshold", 't', 0, G_OPTION_ARG_DOUBLE, &options.transparency_threshold, "Transparency threshold", NULL }, { "watch", '\0', 0, G_OPTION_ARG_NONE, &options.watch, "Watch a file's contents", NULL }, { "zoom", '\0', 0, G_OPTION_ARG_NONE, &options.zoom, "Allow scaling up beyond one character per pixel", NULL }, { NULL } }; ChafaCanvasMode canvas_mode; ChafaPixelMode pixel_mode; memset (&options, 0, sizeof (options)); context = g_option_context_new ("[COMMAND] [OPTION...]"); g_option_context_set_help_enabled (context, FALSE); g_option_context_add_main_entries (context, option_entries, NULL); options.executable_name = g_strdup ((*argv) [0]); /* Defaults */ options.symbol_map = chafa_symbol_map_new (); chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BLOCK); chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_BORDER); chafa_symbol_map_add_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_SPACE); chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_WIDE); options.fill_symbol_map = chafa_symbol_map_new (); options.is_interactive = isatty (STDIN_FILENO) && isatty (STDOUT_FILENO); detect_terminal (&options.term_info, &canvas_mode, &pixel_mode); options.mode = CHAFA_CANVAS_MODE_MAX; /* Unset */ options.pixel_mode = pixel_mode; options.dither_mode = CHAFA_DITHER_MODE_NONE; options.dither_grain_width = -1; /* Unset */ options.dither_grain_height = -1; /* Unset */ options.dither_intensity = 1.0; options.preprocess = TRUE; options.fg_only = FALSE; options.color_extractor = CHAFA_COLOR_EXTRACTOR_AVERAGE; options.color_space = CHAFA_COLOR_SPACE_RGB; options.width = 80; options.height = 25; options.font_ratio = -1.0; /* Unset */ options.work_factor = 5; options.optimization_level = G_MININT; /* Unset */ options.fg_color = 0xffffff; options.bg_color = 0x000000; options.transparency_threshold = G_MAXDOUBLE; /* Unset */ options.file_duration_s = G_MAXDOUBLE; options.anim_fps = -1.0; options.anim_speed_multiplier = 1.0; get_tty_size (); detected_width = options.width; if (!g_option_context_parse (context, argc, argv, &error)) { g_printerr ("%s: %s\n", options.executable_name, error->message); return FALSE; } /* Now we've established the pixel mode, apply dependent defaults */ if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS) { /* Character cell defaults */ if (options.mode == CHAFA_CANVAS_MODE_MAX) options.mode = canvas_mode; if (options.dither_grain_width < 0) options.dither_grain_width = 4; if (options.dither_grain_height < 0) options.dither_grain_height = 4; if (options.font_ratio < 0.0) options.font_ratio = 1.0 / 2.0; } else { /* iTerm2/Kitty/sixel defaults */ if (options.mode == CHAFA_CANVAS_MODE_MAX) options.mode = CHAFA_CANVAS_MODE_TRUECOLOR; if (options.dither_grain_width < 0) options.dither_grain_width = 1; if (options.dither_grain_height < 0) options.dither_grain_height = 1; if (options.font_ratio < 0.0) options.font_ratio = 1.0 / 2.0; } /* Kitty leaves the cursor in the column trailing the last row of the * image. However, if the image is as wide as the terminal, the cursor * will wrap to the first column of the following row, making us lose * track of its position. * * This is not a problem when instructed to clear the terminal, as we * use absolute positioning then. * * If needed, trim one column from the image to make the cursor position * predictable. */ if (options.pixel_mode == CHAFA_PIXEL_MODE_KITTY && !options.clear && options.width > 1 && options.width == detected_width) options.width -= 1; if (options.work_factor < 1 || options.work_factor > 9) { g_printerr ("%s: Work factor must be in the range [1-9].\n", options.executable_name); return FALSE; } if (options.transparency_threshold == G_MAXDOUBLE) options.transparency_threshold = 0.5; if (options.transparency_threshold < 0.0 || options.transparency_threshold > 1.0) { g_printerr ("%s: Transparency threshold %.1lf is not in the range [0.0-1.0].\n", options.executable_name, options.transparency_threshold); return FALSE; } if (options.show_version) { print_version (); return TRUE; } if (*argc < 2) { print_summary (); return FALSE; } options.args = collect_variable_arguments (argc, argv, 1); if (options.show_help) { print_summary (); return FALSE; } if (options.watch && g_list_length (options.args) != 1) { g_printerr ("%s: Can only use --watch with exactly one file.\n", options.executable_name); return FALSE; } /* --stretch implies --zoom */ options.zoom |= options.stretch; if (options.invert) { guint32 temp_color; temp_color = options.bg_color; options.bg_color = options.fg_color; options.fg_color = temp_color; } if (options.file_duration_s == G_MAXDOUBLE && options.args && options.args->next) { /* The default duration when we have multiple files */ options.file_duration_s = FILE_DURATION_DEFAULT; } /* Since FGBG mode can't use escape sequences to invert, it really * needs inverted symbols. In other modes they will only slow us down, * so disable them unless the user specified symbols of their own. */ if (options.mode != CHAFA_CANVAS_MODE_FGBG && !options.symbols_specified) chafa_symbol_map_remove_by_tags (options.symbol_map, CHAFA_SYMBOL_TAG_INVERTED); /* If optimization level is unset, enable optimizations. However, we * leave them off for FGBG mode, since control sequences may be * unexpected in this mode unless explicitly asked for. */ if (options.optimization_level == G_MININT) options.optimization_level = (options.mode == CHAFA_CANVAS_MODE_FGBG) ? 0 : 5; if (options.optimization_level < 0 || options.optimization_level > 9) { g_printerr ("%s: Optimization level %d is not in the range [0-9].\n", options.executable_name, options.optimization_level); return FALSE; } /* Translate optimization level to flags */ options.optimizations = CHAFA_OPTIMIZATION_NONE; if (options.optimization_level >= 1) options.optimizations |= CHAFA_OPTIMIZATION_REUSE_ATTRIBUTES; if (options.optimization_level >= 6) options.optimizations |= CHAFA_OPTIMIZATION_REPEAT_CELLS; if (options.optimization_level >= 7) options.optimizations |= CHAFA_OPTIMIZATION_SKIP_CELLS; g_option_context_free (context); return result; } static void auto_orient_image (MagickWand *image) { #ifdef HAVE_MAGICK_AUTO_ORIENT_IMAGE MagickAutoOrientImage (image); #else PixelWand *pwand = NULL; switch (MagickGetImageOrientation (image)) { case UndefinedOrientation: case TopLeftOrientation: default: break; case TopRightOrientation: MagickFlopImage (image); break; case BottomRightOrientation: pwand = NewPixelWand (); MagickRotateImage (image, pwand, 180.0); break; case BottomLeftOrientation: MagickFlipImage (image); break; case LeftTopOrientation: MagickTransposeImage (image); break; case RightTopOrientation: pwand = NewPixelWand (); MagickRotateImage (image, pwand, 90.0); break; case RightBottomOrientation: MagickTransverseImage (image); break; case LeftBottomOrientation: pwand = NewPixelWand (); MagickRotateImage (image, pwand, 270.0); break; } if (pwand) DestroyPixelWand (pwand); MagickSetImageOrientation (image, TopLeftOrientation); #endif } static GString * build_string (ChafaPixelType pixel_type, const guint8 *pixels, gint src_width, gint src_height, gint src_rowstride, gint dest_width, gint dest_height) { ChafaCanvasConfig *config; ChafaCanvas *canvas; GString *gs; config = chafa_canvas_config_new (); chafa_canvas_config_set_geometry (config, dest_width, dest_height); chafa_canvas_config_set_canvas_mode (config, options.mode); chafa_canvas_config_set_pixel_mode (config, options.pixel_mode); chafa_canvas_config_set_dither_mode (config, options.dither_mode); chafa_canvas_config_set_dither_grain_size (config, options.dither_grain_width, options.dither_grain_height); chafa_canvas_config_set_dither_intensity (config, options.dither_intensity); chafa_canvas_config_set_color_extractor (config, options.color_extractor); chafa_canvas_config_set_color_space (config, options.color_space); chafa_canvas_config_set_fg_color (config, options.fg_color); chafa_canvas_config_set_bg_color (config, options.bg_color); chafa_canvas_config_set_preprocessing_enabled (config, options.preprocess); chafa_canvas_config_set_fg_only_enabled (config, options.fg_only); if (options.transparency_threshold >= 0.0) chafa_canvas_config_set_transparency_threshold (config, options.transparency_threshold); if (options.cell_width > 0 && options.cell_height > 0) chafa_canvas_config_set_cell_geometry (config, options.cell_width, options.cell_height); chafa_canvas_config_set_symbol_map (config, options.symbol_map); chafa_canvas_config_set_fill_symbol_map (config, options.fill_symbol_map); /* Work switch takes values [1..9], we normalize to [0.0..1.0] to * get the work factor. */ chafa_canvas_config_set_work_factor (config, (options.work_factor - 1) / 8.0); chafa_canvas_config_set_optimizations (config, options.optimizations); canvas = chafa_canvas_new (config); chafa_canvas_draw_all_pixels (canvas, pixel_type, pixels, src_width, src_height, src_rowstride); gs = chafa_canvas_print (canvas, options.term_info); chafa_canvas_unref (canvas); chafa_canvas_config_unref (config); return gs; } static void process_image (MagickWand *wand, gint *dest_width_out, gint *dest_height_out) { gint src_width, src_height; gint dest_width, dest_height; auto_orient_image (wand); src_width = MagickGetImageWidth (wand); src_height = MagickGetImageHeight (wand); dest_width = options.width; dest_height = options.height; chafa_calc_canvas_geometry (src_width, src_height, &dest_width, &dest_height, options.font_ratio, options.zoom, options.stretch); if (dest_width_out) *dest_width_out = dest_width; if (dest_height_out) *dest_height_out = dest_height; } typedef struct { GString *gs; gint dest_width, dest_height; gint delay_ms; } GroupFrame; typedef struct { GList *frames; } Group; static void group_build (Group *group, MagickWand *wand) { memset (group, 0, sizeof (*group)); for (MagickResetIterator (wand); !interrupted_by_user; ) { GroupFrame *frame; if (!MagickNextImage (wand)) break; frame = g_new0 (GroupFrame, 1); process_image (wand, &frame->dest_width, &frame->dest_height); frame->delay_ms = MagickGetImageDelay (wand) * 10; if (frame->delay_ms == 0) frame->delay_ms = 50; /* String representation is built on demand and cached */ group->frames = g_list_prepend (group->frames, frame); } group->frames = g_list_reverse (group->frames); } static void group_build_gif (Group *group, GifLoader *loader) { memset (group, 0, sizeof (*group)); for (gif_loader_first_frame (loader); !interrupted_by_user; ) { GroupFrame *frame; frame = g_new0 (GroupFrame, 1); frame->dest_width = -1; frame->dest_height = -1; frame->delay_ms = -1; /* String representation is built on demand and cached */ group->frames = g_list_prepend (group->frames, frame); if (!gif_loader_next_frame (loader)) break; } group->frames = g_list_reverse (group->frames); } static void group_build_xwd (Group *group) { GroupFrame *frame; memset (group, 0, sizeof (*group)); frame = g_new0 (GroupFrame, 1); frame->dest_width = -1; frame->dest_height = -1; frame->delay_ms = -1; group->frames = g_list_prepend (group->frames, frame); } static void group_clear (Group *group) { GList *l; for (l = group->frames; l; l = g_list_next (l)) { GroupFrame *frame = l->data; if (frame->gs) g_string_free (frame->gs, TRUE); g_free (frame); } g_list_free (group->frames); memset (group, 0, sizeof (*group)); } static void interruptible_usleep (gint us) { while (us > 0 && !interrupted_by_user) { gint sleep_us = MIN (us, 50000); g_usleep (sleep_us); us -= sleep_us; } } static gboolean write_to_stdout (gpointer buf, gsize len) { return fwrite (buf, 1, len, stdout) == len ? TRUE : FALSE; } /* FIXME: This function is ripe for refactoring, probably to something with * a heap-allocated context. */ static gboolean run_magickwand (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { MagickWand *wand = NULL; gboolean is_animation = FALSE; gdouble anim_elapsed_s = 0.0; GTimer *timer; Group group = { NULL }; GList *l; gint loop_n = 0; FileMapping *file_mapping; XwdLoader *xwd_loader; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 1]; gchar *p0; timer = g_timer_new (); /* Try XWD fast path first */ file_mapping = file_mapping_new (filename); xwd_loader = xwd_loader_new_from_mapping (file_mapping); if (!xwd_loader) { PixelWand *color; file_mapping_destroy (file_mapping); file_mapping = NULL; wand = NewMagickWand (); color = NewPixelWand (); PixelSetColor (color, "none"); MagickSetBackgroundColor (wand, color); DestroyPixelWand (color); if (MagickReadImage (wand, filename) < 1) { gchar *error_str = NULL; ExceptionType severity; gchar *try_filename; gint r; error_str = MagickGetException (wand, &severity); /* Try backup strategy for XWD. It's a file type we want to support * due to the fun implications with Xvfb etc. The filenames in use * tend to have no extension, and the file magic isn't very definite, * so ImageMagick doesn't know what to do on its own. */ try_filename = g_strdup_printf ("XWD:%s", filename); r = MagickReadImage (wand, try_filename); g_free (try_filename); if (r < 1) { if (!quiet) g_printerr ("%s: Error loading '%s': %s\n", options.executable_name, filename, error_str); MagickRelinquishMemory (error_str); goto out; } } } if (interrupted_by_user) goto out; if (xwd_loader) { group_build_xwd (&group); } else /* wand */ { is_animation = MagickGetNumberImages (wand) > 1 ? TRUE : FALSE; if (is_animation) { MagickWand *wand2 = MagickCoalesceImages (wand); wand = DestroyMagickWand (wand); wand = wand2; } if (interrupted_by_user) goto out; group_build (&group, wand); if (interrupted_by_user) goto out; } do { /* Outer loop repeats animation if desired */ if (wand) MagickResetIterator (wand); for (l = group.frames; l && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < options.file_duration_s); l = g_list_next (l)) { GroupFrame *frame = l->data; gdouble elapsed_ms, remain_ms; if (wand) MagickNextImage (wand); g_timer_start (timer); if (!frame->gs) { ChafaPixelType pixel_type; gint src_width, src_height, src_rowstride; const guint8 *pixels; if (xwd_loader) { pixels = xwd_loader_get_image_data (xwd_loader, &pixel_type, &src_width, &src_height, &src_rowstride); /* FIXME: This shouldn't happen -- but if it does, our * options for handling it gracefully here aren't great. * Needs refactoring. */ if (!pixels) break; frame->dest_width = options.width; frame->dest_height = options.height; chafa_calc_canvas_geometry (src_width, src_height, &frame->dest_width, &frame->dest_height, options.font_ratio, options.zoom, options.stretch); } else /* wand */ { src_width = MagickGetImageWidth (wand); src_height = MagickGetImageHeight (wand); src_rowstride = src_width * 4; pixels = g_malloc (src_height * src_rowstride); MagickExportImagePixels (wand, 0, 0, src_width, src_height, "RGBA", CharPixel, (void *) pixels); pixel_type = CHAFA_PIXEL_RGBA8_UNASSOCIATED; } frame->gs = build_string (pixel_type, pixels, src_width, src_height, src_rowstride, frame->dest_width, frame->dest_height); if (!xwd_loader) g_free ((void *) pixels); } p0 = buf; if (options.clear) { if (is_first_frame) { /* Clear */ p0 = chafa_term_info_emit_clear (options.term_info, p0); } /* Home cursor between frames */ p0 = chafa_term_info_emit_cursor_to_top_left (options.term_info, p0); } else if (!is_first_frame) { /* Cursor up N steps */ p0 = chafa_term_info_emit_cursor_up (options.term_info, p0, frame->dest_height); } /* Put a blank line between files in non-clear mode */ if (is_first_frame && !options.clear && !is_first_file) *(p0++) = '\n'; if (!write_to_stdout (buf, p0 - buf)) goto out; if (!write_to_stdout (frame->gs->str, frame->gs->len)) goto out; /* No linefeed after frame in sixel mode */ if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS || options.pixel_mode == CHAFA_PIXEL_MODE_KITTY || options.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) { if (!write_to_stdout ("\n", 1)) goto out; } if (fflush (stdout) != 0) goto out; if (is_animation) { /* Account for time spent converting and printing frame */ elapsed_ms = g_timer_elapsed (timer, NULL) * 1000.0; if (options.anim_fps > 0.0) remain_ms = 1000.0 / options.anim_fps; else remain_ms = frame->delay_ms; remain_ms /= options.anim_speed_multiplier; remain_ms = MAX (remain_ms - elapsed_ms, 0); if (remain_ms > 0.0001 && 1000.0 / (gdouble) remain_ms < ANIM_FPS_MAX) interruptible_usleep (remain_ms * 1000); anim_elapsed_s += MAX (elapsed_ms, frame->delay_ms) / 1000.0; } is_first_frame = FALSE; } loop_n++; } while (options.is_interactive && is_animation && !interrupted_by_user && !options.watch && anim_elapsed_s < options.file_duration_s); out: if (wand) DestroyMagickWand (wand); if (xwd_loader) xwd_loader_destroy (xwd_loader); group_clear (&group); g_timer_destroy (timer); return is_animation; } static gboolean run_gif (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean *success_out) { FileMapping *mapping = NULL; GifLoader *loader = NULL; const guint8 *pixels; gboolean success = FALSE; gboolean is_animation = FALSE; gdouble anim_elapsed_s = 0.0; GTimer *timer; Group group = { NULL }; GList *l; gint loop_n = 0; gchar buf [CHAFA_TERM_SEQ_LENGTH_MAX * 2 + 1]; gchar *p0; timer = g_timer_new (); mapping = file_mapping_new (filename); if (!mapping) goto out; loader = gif_loader_new_from_mapping (mapping); if (!loader) goto out; /* Loader owns the mapping now */ mapping = NULL; success = TRUE; if (interrupted_by_user) goto out; is_animation = gif_loader_get_n_frames (loader) > 1 ? TRUE : FALSE; group_build_gif (&group, loader); gif_loader_first_frame (loader); do { /* Outer loop repeats animation if desired */ for (l = group.frames; l && !interrupted_by_user && (loop_n == 0 || anim_elapsed_s < options.file_duration_s); l = g_list_next (l)) { GroupFrame *frame = l->data; gdouble elapsed_ms, remain_ms; g_timer_start (timer); if (!frame->gs) { gint src_width, src_height; pixels = gif_loader_get_frame_data (loader, &frame->delay_ms); if (!pixels) goto out; frame->delay_ms *= 10; if (frame->delay_ms == 0) frame->delay_ms = 50; gif_loader_get_geometry (loader, &src_width, &src_height); frame->dest_width = options.width; frame->dest_height = options.height; chafa_calc_canvas_geometry (src_width, src_height, &frame->dest_width, &frame->dest_height, options.font_ratio, options.zoom, options.stretch); frame->gs = build_string (CHAFA_PIXEL_RGBA8_UNASSOCIATED, pixels, src_width, src_height, src_width * 4, frame->dest_width, frame->dest_height); gif_loader_next_frame (loader); } p0 = buf; if (options.clear) { if (is_first_frame) { /* Clear */ p0 = chafa_term_info_emit_clear (options.term_info, p0); } /* Home cursor between frames */ p0 = chafa_term_info_emit_cursor_to_top_left (options.term_info, p0); } else if (!is_first_frame) { /* Cursor up N steps */ p0 = chafa_term_info_emit_cursor_up (options.term_info, p0, frame->dest_height); } /* Put a blank line between files in non-clear mode */ if (is_first_frame && !options.clear && !is_first_file) *(p0++) = '\n'; if (!write_to_stdout (buf, p0 - buf)) goto out; if (!write_to_stdout (frame->gs->str, frame->gs->len)) goto out; /* No linefeed after frame in sixel mode */ if (options.pixel_mode == CHAFA_PIXEL_MODE_SYMBOLS || options.pixel_mode == CHAFA_PIXEL_MODE_KITTY || options.pixel_mode == CHAFA_PIXEL_MODE_ITERM2) { if (!write_to_stdout ("\n", 1)) goto out; } if (fflush (stdout) != 0) goto out; if (is_animation) { /* Account for time spent converting and printing frame */ elapsed_ms = g_timer_elapsed (timer, NULL) * 1000.0; if (options.anim_fps > 0.0) remain_ms = 1000.0 / options.anim_fps; else remain_ms = frame->delay_ms; remain_ms /= options.anim_speed_multiplier; remain_ms = MAX (remain_ms - elapsed_ms, 0); if (remain_ms > 0.0001 && 1000.0 / (gdouble) remain_ms < ANIM_FPS_MAX) interruptible_usleep (remain_ms * 1000); anim_elapsed_s += MAX (elapsed_ms, frame->delay_ms) / 1000.0; } is_first_frame = FALSE; } loop_n++; } while (options.is_interactive && is_animation && !interrupted_by_user && !options.watch && anim_elapsed_s < options.file_duration_s); out: if (loader) gif_loader_destroy (loader); group_clear (&group); if (mapping) file_mapping_destroy (mapping); g_timer_destroy (timer); if (success_out) *success_out = success; return is_animation; } static gboolean run (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet) { gboolean is_animation; gboolean success = FALSE; is_animation = run_gif (filename, is_first_file, is_first_frame, &success); if (success) return is_animation; is_animation = run_magickwand (filename, is_first_file, is_first_frame, quiet); return is_animation; } static int run_watch (const gchar *filename) { GTimer *timer; gboolean is_first_frame = TRUE; tty_options_init (); MagickWandGenesis (); timer = g_timer_new (); for ( ; !interrupted_by_user; ) { struct stat sbuf; if (!stat (filename, &sbuf)) { /* Sadly we can't rely on timestamps to tell us when to reload * the file, since they can take way too long to update. */ run (filename, TRUE, is_first_frame, TRUE); is_first_frame = FALSE; g_usleep (10000); } else { /* Don't hammer the path if the file is temporarily gone */ g_usleep (250000); } if (g_timer_elapsed (timer, NULL) > options.file_duration_s) break; } g_timer_destroy (timer); MagickWandTerminus (); tty_options_deinit (); return 0; } static int run_all (GList *filenames) { GList *l; if (!filenames) return 0; tty_options_init (); MagickWandGenesis (); for (l = filenames; l && !interrupted_by_user; l = g_list_next (l)) { gchar *filename = l->data; gboolean was_animation; was_animation = run (filename, l->prev ? FALSE : TRUE, TRUE, FALSE); if (!was_animation && options.file_duration_s != G_MAXDOUBLE) { interruptible_usleep (options.file_duration_s * 1000000.0); } } MagickWandTerminus (); tty_options_deinit (); return 0; } static void proc_init (void) { struct sigaction sa = { 0 }; /* Must do this early. Buffer size probably doesn't matter */ setvbuf (stdout, NULL, _IOFBF, 32768); setlocale (LC_ALL, ""); sa.sa_handler = sigint_handler; sa.sa_flags = SA_RESETHAND; sigaction (SIGINT, &sa, NULL); } int main (int argc, char *argv []) { int ret; proc_init (); if (!parse_options (&argc, &argv)) exit (1); ret = options.watch ? run_watch (options.args->data) : run_all (options.args); if (options.symbol_map) chafa_symbol_map_unref (options.symbol_map); if (options.fill_symbol_map) chafa_symbol_map_unref (options.fill_symbol_map); if (options.term_info) chafa_term_info_unref (options.term_info); return ret; } chafa-1.8.0/tools/chafa/file-mapping.c000066400000000000000000000151421411352071600175030ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_MMAP # include #endif #include "file-mapping.h" #ifdef HAVE_MMAP /* Workaround for non-Linux platforms */ # ifndef MAP_NORESERVE # define MAP_NORESERVE 0 # endif #endif struct FileMapping { gchar *path; gpointer data; gsize length; gint fd; guint is_mmapped : 1; }; static gsize safe_read (gint fd, void *buf, gsize len) { gsize ntotal = 0; guint8 *buffer = buf; while (len > 0) { unsigned int nread; int iread; /* Passing nread > INT_MAX to read is implementation defined in POSIX * 1003.1, therefore despite the unsigned argument portable code must * limit the value to INT_MAX! */ if (len > INT_MAX) nread = INT_MAX; else nread = (unsigned int)/*SAFE*/len; iread = read (fd, buffer, nread); if (iread == -1) { /* This is the devil in the details, a read can terminate early with 0 * bytes read because of EINTR, yet it still returns -1 otherwise end * of file cannot be distinguished. */ if (errno != EINTR) { /* I.e. a permanent failure */ return 0; } } else if (iread < 0) { /* Not a valid 'read' result: */ return 0; } else if (iread > 0) { /* Continue reading until a permanent failure, or EOF */ buffer += iread; len -= (unsigned int)/*SAFE*/iread; ntotal += (unsigned int)/*SAFE*/iread; } else return ntotal; } return ntotal; /* len == 0 */ } static void free_file_data (FileMapping *file_mapping) { if (file_mapping->data) { #ifdef HAVE_MMAP if (file_mapping->is_mmapped) munmap (file_mapping->data, file_mapping->length); else #endif g_free (file_mapping->data); } if (file_mapping->fd >= 0) close (file_mapping->fd); file_mapping->fd = -1; file_mapping->data = NULL; file_mapping->is_mmapped = FALSE; } static gint open_file (FileMapping *file_mapping) { return open (file_mapping->path, O_RDONLY); } static guint8 * read_file (gint fd, size_t *data_size) { struct stat sb; guint8 *buffer; size_t size; size_t n; if (fstat (fd, &sb)) { return NULL; } size = sb.st_size; buffer = g_try_malloc (size); if (!buffer) { g_printerr ("Unable to allocate %lld bytes\n", (long long) size); return NULL; } if (lseek (fd, 0, SEEK_SET) != 0) { g_free (buffer); return NULL; } n = safe_read (fd, buffer, size); if (n != size) { g_free (buffer); return NULL; } *data_size = size; return buffer; } static gboolean map_or_read_file (FileMapping *file_mapping) { gboolean result = FALSE; struct stat sbuf; gint t; if (file_mapping->data) return TRUE; if (file_mapping->fd < 0) file_mapping->fd = open_file (file_mapping); if (file_mapping->fd < 0) goto out; t = fstat (file_mapping->fd, &sbuf); if (!t) { file_mapping->length = sbuf.st_size; #ifdef HAVE_MMAP /* Try memory mapping first */ file_mapping->data = mmap (NULL, file_mapping->length, PROT_READ, MAP_SHARED | MAP_NORESERVE, file_mapping->fd, 0); #endif } if (file_mapping->data) { file_mapping->is_mmapped = TRUE; } else { file_mapping->data = read_file (file_mapping->fd, &file_mapping->length); file_mapping->is_mmapped = FALSE; } if (!file_mapping->data) goto out; result = TRUE; out: return result; } FileMapping * file_mapping_new (const gchar *path) { FileMapping *file_mapping; file_mapping = g_new0 (FileMapping, 1); file_mapping->path = g_strdup (path); file_mapping->fd = -1; return file_mapping; } void file_mapping_destroy (FileMapping *file_mapping) { free_file_data (file_mapping); g_free (file_mapping->path); g_free (file_mapping); } gconstpointer file_mapping_get_data (FileMapping *file_mapping, gsize *length_out) { if (!file_mapping->data) map_or_read_file (file_mapping); if (length_out) *length_out = file_mapping->length; return file_mapping->data; } gboolean file_mapping_taste (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length) { if (file_mapping->fd < 0) file_mapping->fd = open_file (file_mapping); if (file_mapping->fd < 0) return FALSE; if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) return FALSE; if (safe_read (file_mapping->fd, out, length) != length) return FALSE; return TRUE; } gboolean file_mapping_has_magic (FileMapping *file_mapping, goffset ofs, gconstpointer data, gsize length) { gchar *buf; if (file_mapping->data) { if (ofs + length <= file_mapping->length && !memcmp ((const guint8 *) data + ofs, data, length)) return TRUE; return FALSE; } if (file_mapping->fd < 0) file_mapping->fd = open_file (file_mapping); if (file_mapping->fd < 0) return FALSE; if (lseek (file_mapping->fd, ofs, SEEK_SET) != ofs) return FALSE; buf = alloca (length); if (safe_read (file_mapping->fd, buf, length) != length) return FALSE; if (memcmp (buf, data, length)) return FALSE; return TRUE; } chafa-1.8.0/tools/chafa/file-mapping.h000066400000000000000000000026651411352071600175160ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __FILE_MAPPING_H__ #define __FILE_MAPPING_H__ #include G_BEGIN_DECLS typedef struct FileMapping FileMapping; FileMapping *file_mapping_new (const gchar *path); void file_mapping_destroy (FileMapping *file_mapping); gboolean file_mapping_taste (FileMapping *file_mapping, gpointer out, goffset ofs, gsize length); gconstpointer file_mapping_get_data (FileMapping *file_mapping, gsize *length_out); gboolean file_mapping_has_magic (FileMapping *file_mapping, goffset ofs, gconstpointer data, gsize length); G_END_DECLS #endif /* __FILE_MAPPING_H__ */ chafa-1.8.0/tools/chafa/font-loader.c000066400000000000000000000360251411352071600173500ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include #include #include FT_FREETYPE_H #include #include "font-loader.h" #define DEBUG(x) #define REQ_WIDTH_DEFAULT 15 #define REQ_HEIGHT_DEFAULT 8 /* The font is read in two passes; once for narrow (single-cell) symbols, * and once for wide (double-cell) ones. This allows us to use a different * resolution for each -- 8x8 vs 16x8. */ typedef enum { FONT_PASS_NARROW, FONT_PASS_WIDE } FontPass; struct FontLoader { /* General / I/O */ FileMapping *mapping; const guint8 *file_data; size_t file_data_len; FT_Library ft_lib; FT_Face ft_face; /* Cell size that provides a good fit for font, pre-scaled */ gint font_width; gint font_height; /* Baseline offset, vertical from top */ gint baseline_ofs; /* Iterator */ FontPass pass; FT_ULong glyph_charcode; gint n_glyphs_read; }; /* With 256 bins we get a histogram for integer values [-128 .. 127]. This * is more than enough for the sizes we'll be getting, which should be in the * 0..16 range, give or take a little. Values outside the histogram's range * will be silently discarded. */ #define SMALL_HISTOGRAM_N_BINS 256 typedef struct { gint count [SMALL_HISTOGRAM_N_BINS]; gint first_bin; gint n_values; } SmallHistogram; static void small_histogram_init (SmallHistogram *hist) { memset (hist, 0, sizeof (*hist)); hist->first_bin = - (SMALL_HISTOGRAM_N_BINS / 2); } static void small_histogram_add (SmallHistogram *hist, gint value) { gint bin_index; bin_index = value - hist->first_bin; if (bin_index < 0 || bin_index >= SMALL_HISTOGRAM_N_BINS) return; hist->count [bin_index]++; hist->n_values++; } static gint small_histogram_get_quantile (SmallHistogram *hist, gint dividend, gint divisor) { gint i; gint n = 0; g_return_val_if_fail (dividend <= divisor, 0); for (i = 0; i < SMALL_HISTOGRAM_N_BINS; i++) { n += hist->count [i]; if (n >= (hist->n_values * dividend) / divisor) break; } return hist->first_bin + i; } static void small_histogram_get_range (SmallHistogram *hist, gint *min_out, gint *max_out) { gint min, max; min = small_histogram_get_quantile (hist, 1, 8); max = small_histogram_get_quantile (hist, 7, 8); if (min_out) *min_out = min; if (max_out) *max_out = max; } static FontLoader * font_loader_new (void) { return g_new0 (FontLoader, 1); } /* Get a bit from a rendered FreeType glyph bitmap. Going out of bounds is allowed * and will return zero. * * Returns: 1 for inked bits or 0 for uninked. */ static guint get_bitmap_bit (const FontLoader *loader, const FT_GlyphSlot slot, gint i, gint j) { const FT_Bitmap *bm; gint x, y; bm = &slot->bitmap; x = i - (gint) slot->bitmap_left - (loader->font_width - (slot->advance.x >> 6)) / 2; y = j - (loader->font_height - (gint) slot->bitmap_top) + (loader->font_height - loader->baseline_ofs); if (x < 0 || x >= (gint) bm->width || y < 0 || y >= (gint) bm->rows) return 0; /* MSB first */ return (bm->buffer [y * bm->pitch + (x / 8)] >> (7 - (x % 8))) & 1; } /* Get the 7th octile values for glyph width, height and baseline. This means 87.5% of * the glyphs will have values equal to or lower than the returned value. Discarding * the upper 12.5% prevents outliers from affecting the result. */ static gboolean measure_glyphs (FontLoader *loader, gint *width_out, gint *height_out, gint *baseline_out) { FT_ULong glyph_charcode; FT_UInt glyph_index; FT_GlyphSlot slot; SmallHistogram x_adv_hist; SmallHistogram asc_hist; SmallHistogram desc_hist; gint asc_max, desc_max; gboolean success = FALSE; small_histogram_init (&x_adv_hist); small_histogram_init (&asc_hist); small_histogram_init (&desc_hist); for (glyph_charcode = FT_Get_First_Char (loader->ft_face, &glyph_index); glyph_index != 0; glyph_charcode = FT_Get_Next_Char (loader->ft_face, glyph_charcode, &glyph_index)) { if (!g_unichar_isprint (glyph_charcode) || g_unichar_ismark (glyph_charcode)) continue; /* Skip glyphs that are not relevant to this pass */ if ((loader->pass == FONT_PASS_NARROW && g_unichar_iswide (glyph_charcode)) || (loader->pass == FONT_PASS_WIDE && !g_unichar_iswide (glyph_charcode))) continue; /* FIXME: No need to render? */ if (FT_Load_Glyph (loader->ft_face, glyph_index, FT_LOAD_RENDER | FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO)) continue; slot = loader->ft_face->glyph; small_histogram_add (&x_adv_hist, slot->advance.x / 64 > 0 ? slot->advance.x / 64 : slot->bitmap_left + slot->bitmap.width); small_histogram_add (&asc_hist, slot->bitmap_top); small_histogram_add (&desc_hist, (gint) slot->bitmap.rows - (gint) slot->bitmap_top); } if (x_adv_hist.n_values == 0) goto out; small_histogram_get_range (&x_adv_hist, NULL, width_out); small_histogram_get_range (&asc_hist, NULL, &asc_max); small_histogram_get_range (&desc_hist, NULL, &desc_max); if (height_out) *height_out = asc_max + desc_max; if (baseline_out) *baseline_out = asc_max; success = TRUE; out: return success; } /* Find a pixel size that produces rendered symbols matching our ideal * size as closely as possible. Due to variable-width fonts, precise * font sizes being unavailable, etc. we do this by probing and * simple statistical analysis. * * We start with an initial guess and change our request in increments * until we either hit our desired size or exceed it. Then we back off * once. This is done in both dimensions simultaneously. */ static gboolean find_best_pixel_size_scalable (FontLoader *loader, gint target_width) { gboolean success = FALSE; gint req_width = REQ_WIDTH_DEFAULT; gint req_height = REQ_HEIGHT_DEFAULT; gint width = 0, height = 0, baseline; gint width_chg = 0, height_chg = 0; while ((width != target_width && width_chg != 3) || (height != CHAFA_SYMBOL_HEIGHT_PIXELS && height_chg != 3)) { if (FT_Set_Pixel_Sizes (loader->ft_face, req_width, req_height)) goto out; if (!measure_glyphs (loader, &width, &height, &baseline)) goto out; if (width < target_width) { req_width++; width_chg |= 1; } if (width > target_width) { req_width--; width_chg |= 2; } if (height < CHAFA_SYMBOL_HEIGHT_PIXELS) { req_height++; height_chg |= 1; } if (height > CHAFA_SYMBOL_HEIGHT_PIXELS) { req_height--; height_chg |= 2; } } /* If we can't get the exact size we want, make sure we get something * slightly bigger instead of slightly smaller. */ while (height < CHAFA_SYMBOL_HEIGHT_PIXELS) { req_height++; if (FT_Set_Pixel_Sizes (loader->ft_face, req_width, req_height)) goto out; if (!measure_glyphs (loader, &width, &height, &baseline)) goto out; } loader->font_width = width; loader->font_height = height; loader->baseline_ofs = baseline; success = TRUE; out: return success; } /* See the description of find_best_pixel_size_scalable() for the overall * strategy used here. */ static gboolean find_best_pixel_size_fixed (FontLoader *loader, gint target_width) { gboolean success = FALSE; const FT_Bitmap_Size *avsz = loader->ft_face->available_sizes; gint best_width = 0, best_height = 0, best_baseline = 0; gint i; if (!loader->ft_face->available_sizes) goto out; for (i = 0; i < loader->ft_face->num_fixed_sizes; i++) { gint width, height, baseline; if (FT_Set_Pixel_Sizes (loader->ft_face, avsz [i].width, avsz [i].height)) continue; if (!measure_glyphs (loader, &width, &height, &baseline)) goto out; /* Prefer strikes bigger than and as close as possible to actual target size */ if (((best_width < target_width || best_height < CHAFA_SYMBOL_HEIGHT_PIXELS) && (width >= best_width && height >= best_height)) || ((best_width > target_width || best_height > CHAFA_SYMBOL_HEIGHT_PIXELS) && (width >= target_width && height >= CHAFA_SYMBOL_HEIGHT_PIXELS) && (width < best_width || height < best_height))) { best_width = width; best_height = height; best_baseline = baseline; } } if (best_width == 0 || best_height == 0) goto out; if (FT_Set_Pixel_Sizes (loader->ft_face, best_width, best_height)) goto out; loader->font_width = best_width; loader->font_height = best_height; loader->baseline_ofs = best_baseline; success = TRUE; out: return success; } static gboolean begin_pass (FontLoader *loader, FontPass pass) { loader->pass = pass; if (pass == FONT_PASS_NARROW) { if (!find_best_pixel_size_scalable (loader, CHAFA_SYMBOL_WIDTH_PIXELS) && !find_best_pixel_size_fixed (loader, CHAFA_SYMBOL_WIDTH_PIXELS)) return FALSE; } else if (pass == FONT_PASS_WIDE) { if (!find_best_pixel_size_scalable (loader, CHAFA_SYMBOL_WIDTH_PIXELS * 2) && !find_best_pixel_size_fixed (loader, CHAFA_SYMBOL_WIDTH_PIXELS * 2)) return FALSE; } else { g_assert_not_reached (); } return TRUE; } static gboolean next_pass (FontLoader *loader) { loader->n_glyphs_read = 0; if (loader->pass == FONT_PASS_NARROW) return begin_pass (loader, FONT_PASS_WIDE); return FALSE; } FontLoader * font_loader_new_from_mapping (FileMapping *mapping) { FontLoader *loader = NULL; gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); loader = font_loader_new (); loader->mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; if (FT_Init_FreeType (&loader->ft_lib) != 0) goto out; if (FT_New_Memory_Face (loader->ft_lib, loader->file_data, loader->file_data_len, 0, /* face index */ &loader->ft_face)) goto out; if (!begin_pass (loader, FONT_PASS_NARROW) && !begin_pass (loader, FONT_PASS_WIDE)) goto out; success = TRUE; out: if (!success) { if (loader) { font_loader_destroy (loader); loader = NULL; } } return loader; } void font_loader_destroy (FontLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->ft_face) FT_Done_Face (loader->ft_face); if (loader->ft_lib) FT_Done_FreeType (loader->ft_lib); g_free (loader); } static void generate_glyph (const FontLoader *loader, const FT_GlyphSlot slot, gpointer *glyph_out, gint *width_out, gint *height_out) { guint8 *glyph_data; gint i, j; const guint8 val [2] = { 0x00, 0xff }; glyph_data = g_malloc (loader->font_width * loader->font_height * 4); for (j = 0; j < loader->font_height; j++) { for (i = 0; i < loader->font_width; i++) { guint b = get_bitmap_bit (loader, slot, i, j); if (b) { DEBUG (g_printerr ("XX")); } else { DEBUG (g_printerr ("..")); } glyph_data [(j * loader->font_width + i) * 4] = val [b]; glyph_data [(j * loader->font_width + i) * 4 + 1] = val [b]; glyph_data [(j * loader->font_width + i) * 4 + 2] = val [b]; glyph_data [(j * loader->font_width + i) * 4 + 3] = val [b]; } DEBUG (g_printerr ("\n")); } DEBUG (g_printerr ("\n")); *glyph_out = glyph_data; *width_out = loader->font_width; *height_out = loader->font_height; } /* Load a glyph to an RGBA8 output buffer of fixed size CHAFA_SYMBOL_WIDTH_PIXELS * * CHAFA_SYMBOL_HEIGHT_PIXELS. Each pixel will be set to either 0xffffffff (inked) * or 0x00000000 (uninked). */ gboolean font_loader_get_next_glyph (FontLoader *loader, gunichar *char_out, gpointer *glyph_out, gint *width_out, gint *height_out) { FT_GlyphSlot slot; gboolean success = FALSE; FT_UInt glyph_index = 0; slot = loader->ft_face->glyph; while (!glyph_index) { if (loader->n_glyphs_read == 0) { loader->glyph_charcode = FT_Get_First_Char (loader->ft_face, &glyph_index); } else { loader->glyph_charcode = FT_Get_Next_Char (loader->ft_face, loader->glyph_charcode, &glyph_index); } if (!glyph_index) { if (next_pass (loader)) continue; break; } loader->n_glyphs_read++; /* Skip glyphs that are not relevant to this pass */ if (!g_unichar_isprint (loader->glyph_charcode) || g_unichar_ismark (loader->glyph_charcode) || (loader->pass == FONT_PASS_NARROW && g_unichar_iswide (loader->glyph_charcode)) || (loader->pass == FONT_PASS_WIDE && !g_unichar_iswide (loader->glyph_charcode))) glyph_index = 0; } if (!glyph_index) goto out; if (FT_Load_Glyph (loader->ft_face, glyph_index, FT_LOAD_RENDER | FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO)) goto out; generate_glyph (loader, slot, glyph_out, width_out, height_out); *char_out = loader->glyph_charcode; DEBUG (g_printerr ("Loaded symbol %04x: %dx%d -> %dx%d (ofs %d,%d bmsize %dx%d xadv %d/64=%d)\n", *char_out, loader->font_width, loader->font_height, *width_out, *height_out, slot->bitmap_left, slot->bitmap_top, slot->bitmap.width, slot->bitmap.rows, (gint) slot->advance.x, (gint) slot->advance.x >> 6)); success = TRUE; out: return success; } chafa-1.8.0/tools/chafa/font-loader.h000066400000000000000000000024671411352071600173600ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2019-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __FONT_LOADER_H__ #define __FONT_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct FontLoader FontLoader; FontLoader *font_loader_new_from_mapping (FileMapping *mapping); void font_loader_destroy (FontLoader *loader); gboolean font_loader_get_next_glyph (FontLoader *loader, gunichar *char_out, gpointer *glyph_out, gint *width_out, gint *height_out); G_END_DECLS #endif /* __FONT_LOADER_H__ */ chafa-1.8.0/tools/chafa/gif-loader.c000066400000000000000000000126601411352071600171460ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "gif-loader.h" #define BYTES_PER_PIXEL 4 #define MAX_IMAGE_BYTES (128 * 1024 * 1024) struct GifLoader { FileMapping *mapping; const guint8 *file_data; size_t file_data_len; gif_animation gif; gif_result code; gint current_frame_index; guint gif_is_initialized : 1; guint frame_is_decoded : 1; }; static void * bitmap_create (int width, int height) { /* ensure a stupidly large bitmap is not created */ if (((long long) width * (long long) height) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) return NULL; return g_malloc0 (width * height * BYTES_PER_PIXEL); } static void bitmap_set_opaque (void *bitmap, bool opaque) { (void) opaque; /* unused */ (void) bitmap; /* unused */ g_assert (bitmap); } static bool bitmap_test_opaque (void *bitmap) { (void) bitmap; /* unused */ g_assert (bitmap != NULL); return false; } static unsigned char * bitmap_get_buffer (void *bitmap) { g_assert (bitmap != NULL); return bitmap; } static void bitmap_destroy (void *bitmap) { g_assert (bitmap != NULL); g_free (bitmap); } static void bitmap_modified (void *bitmap) { (void) bitmap; /* unused */ g_assert (bitmap != NULL); } static GifLoader * gif_loader_new (void) { return g_new0 (GifLoader, 1); } GifLoader * gif_loader_new_from_mapping (FileMapping *mapping) { gif_bitmap_callback_vt bitmap_callbacks = { bitmap_create, bitmap_destroy, bitmap_get_buffer, bitmap_set_opaque, bitmap_test_opaque, bitmap_modified }; gif_result code; GifLoader *loader = NULL; gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); if (!file_mapping_has_magic (mapping, 0, "GIF89a", 6)) goto out; loader = gif_loader_new (); loader->mapping = mapping; loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) goto out; gif_create (&loader->gif, &bitmap_callbacks); loader->gif_is_initialized = TRUE; do { code = gif_initialise (&loader->gif, loader->file_data_len, (gpointer) loader->file_data); if (code != GIF_OK && code != GIF_WORKING) goto out; } while (code != GIF_OK); success = TRUE; out: if (!success) { if (loader) { if (loader->gif_is_initialized) gif_finalise (&loader->gif); g_free (loader); loader = NULL; } } return loader; } void gif_loader_destroy (GifLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); if (loader->gif_is_initialized) gif_finalise (&loader->gif); g_free (loader); } void gif_loader_get_geometry (GifLoader *loader, gint *width_out, gint *height_out) { g_return_if_fail (loader != NULL); g_return_if_fail (loader->gif_is_initialized); *width_out = loader->gif.width; *height_out = loader->gif.height; } gint gif_loader_get_n_frames (GifLoader *loader) { g_return_val_if_fail (loader != NULL, 0); g_return_val_if_fail (loader->gif_is_initialized, 0); return loader->gif.frame_count; } const guint8 * gif_loader_get_frame_data (GifLoader *loader, gint *post_frame_delay_hs_out) { g_return_val_if_fail (loader != NULL, NULL); g_return_val_if_fail (loader->gif_is_initialized, NULL); if (!loader->frame_is_decoded) { gif_result code = gif_decode_frame (&loader->gif, loader->current_frame_index); if (code != GIF_OK) return NULL; } loader->frame_is_decoded = TRUE; if (post_frame_delay_hs_out) *post_frame_delay_hs_out = loader->gif.frames [loader->current_frame_index].frame_delay; return loader->gif.frame_image; } void gif_loader_first_frame (GifLoader *loader) { g_return_if_fail (loader != NULL); g_return_if_fail (loader->gif_is_initialized); if (loader->current_frame_index == 0) return; loader->current_frame_index = 0; loader->frame_is_decoded = FALSE; } gboolean gif_loader_next_frame (GifLoader *loader) { g_return_val_if_fail (loader != NULL, FALSE); g_return_val_if_fail (loader->gif_is_initialized, FALSE); if (loader->current_frame_index + 1 >= (gint) loader->gif.frame_count) return FALSE; loader->current_frame_index++; loader->frame_is_decoded = FALSE; return TRUE; } chafa-1.8.0/tools/chafa/gif-loader.h000066400000000000000000000027151411352071600171530ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __GIF_LOADER_H__ #define __GIF_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct GifLoader GifLoader; GifLoader *gif_loader_new_from_mapping (FileMapping *mapping); void gif_loader_destroy (GifLoader *loader); void gif_loader_get_geometry (GifLoader *loader, gint *width_out, gint *height_out); gint gif_loader_get_n_frames (GifLoader *loader); const guint8 *gif_loader_get_frame_data (GifLoader *loader, gint *post_frame_delay_hs_out); void gif_loader_first_frame (GifLoader *loader); gboolean gif_loader_next_frame (GifLoader *loader); G_END_DECLS #endif /* __GIF_LOADER_H__ */ chafa-1.8.0/tools/chafa/named-colors.c000066400000000000000000000700101411352071600175110ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include /* strlen */ #include "named-colors.h" /* These colors are from X.Org's rgb.txt, filtered to remove duplicate * entries with spaces in them. We handle names with spaces in the lookup * function instead. Grey/gray variants were kept. * * For more background see https://en.wikipedia.org/wiki/X11_color_names */ static const NamedColor named_colors [] = { { { 255, 250, 250 }, "snow" }, { { 248, 248, 255 }, "GhostWhite" }, { { 245, 245, 245 }, "WhiteSmoke" }, { { 220, 220, 220 }, "gainsboro" }, { { 255, 250, 240 }, "FloralWhite" }, { { 253, 245, 230 }, "OldLace" }, { { 250, 240, 230 }, "linen" }, { { 250, 235, 215 }, "AntiqueWhite" }, { { 255, 239, 213 }, "PapayaWhip" }, { { 255, 235, 205 }, "BlanchedAlmond" }, { { 255, 228, 196 }, "bisque" }, { { 255, 218, 185 }, "PeachPuff" }, { { 255, 222, 173 }, "NavajoWhite" }, { { 255, 228, 181 }, "moccasin" }, { { 255, 248, 220 }, "cornsilk" }, { { 255, 255, 240 }, "ivory" }, { { 255, 250, 205 }, "LemonChiffon" }, { { 255, 245, 238 }, "seashell" }, { { 240, 255, 240 }, "honeydew" }, { { 245, 255, 250 }, "MintCream" }, { { 240, 255, 255 }, "azure" }, { { 240, 248, 255 }, "AliceBlue" }, { { 230, 230, 250 }, "lavender" }, { { 255, 240, 245 }, "LavenderBlush" }, { { 255, 228, 225 }, "MistyRose" }, { { 255, 255, 255 }, "white" }, { { 0, 0, 0 }, "black" }, { { 47, 79, 79 }, "DarkSlateGray" }, { { 47, 79, 79 }, "DarkSlateGrey" }, { { 105, 105, 105 }, "DimGray" }, { { 105, 105, 105 }, "DimGrey" }, { { 112, 128, 144 }, "SlateGray" }, { { 112, 128, 144 }, "SlateGrey" }, { { 119, 136, 153 }, "LightSlateGray" }, { { 119, 136, 153 }, "LightSlateGrey" }, { { 190, 190, 190 }, "gray" }, { { 190, 190, 190 }, "grey" }, { { 190, 190, 190 }, "X11Gray" }, { { 190, 190, 190 }, "X11Grey" }, { { 128, 128, 128 }, "WebGray" }, { { 128, 128, 128 }, "WebGrey" }, { { 211, 211, 211 }, "LightGrey" }, { { 211, 211, 211 }, "LightGray" }, { { 25, 25, 112 }, "MidnightBlue" }, { { 0, 0, 128 }, "navy" }, { { 0, 0, 128 }, "NavyBlue" }, { { 100, 149, 237 }, "CornflowerBlue" }, { { 72, 61, 139 }, "DarkSlateBlue" }, { { 106, 90, 205 }, "SlateBlue" }, { { 123, 104, 238 }, "MediumSlateBlue" }, { { 132, 112, 255 }, "LightSlateBlue" }, { { 0, 0, 205 }, "MediumBlue" }, { { 65, 105, 225 }, "RoyalBlue" }, { { 0, 0, 255 }, "blue" }, { { 30, 144, 255 }, "DodgerBlue" }, { { 0, 191, 255 }, "DeepSkyBlue" }, { { 135, 206, 235 }, "SkyBlue" }, { { 135, 206, 250 }, "LightSkyBlue" }, { { 70, 130, 180 }, "SteelBlue" }, { { 176, 196, 222 }, "LightSteelBlue" }, { { 173, 216, 230 }, "LightBlue" }, { { 176, 224, 230 }, "PowderBlue" }, { { 175, 238, 238 }, "PaleTurquoise" }, { { 0, 206, 209 }, "DarkTurquoise" }, { { 72, 209, 204 }, "MediumTurquoise" }, { { 64, 224, 208 }, "turquoise" }, { { 0, 255, 255 }, "cyan" }, { { 0, 255, 255 }, "aqua" }, { { 224, 255, 255 }, "LightCyan" }, { { 95, 158, 160 }, "CadetBlue" }, { { 102, 205, 170 }, "MediumAquamarine" }, { { 127, 255, 212 }, "aquamarine" }, { { 0, 100, 0 }, "DarkGreen" }, { { 85, 107, 47 }, "DarkOliveGreen" }, { { 143, 188, 143 }, "DarkSeaGreen" }, { { 46, 139, 87 }, "SeaGreen" }, { { 60, 179, 113 }, "MediumSeaGreen" }, { { 32, 178, 170 }, "LightSeaGreen" }, { { 152, 251, 152 }, "PaleGreen" }, { { 0, 255, 127 }, "SpringGreen" }, { { 124, 252, 0 }, "LawnGreen" }, { { 0, 255, 0 }, "green" }, { { 0, 255, 0 }, "lime" }, { { 0, 255, 0 }, "X11Green" }, { { 0, 128, 0 }, "WebGreen" }, { { 127, 255, 0 }, "chartreuse" }, { { 0, 250, 154 }, "MediumSpringGreen" }, { { 173, 255, 47 }, "GreenYellow" }, { { 50, 205, 50 }, "LimeGreen" }, { { 154, 205, 50 }, "YellowGreen" }, { { 34, 139, 34 }, "ForestGreen" }, { { 107, 142, 35 }, "OliveDrab" }, { { 189, 183, 107 }, "DarkKhaki" }, { { 240, 230, 140 }, "khaki" }, { { 238, 232, 170 }, "PaleGoldenrod" }, { { 250, 250, 210 }, "LightGoldenrodYellow" }, { { 255, 255, 224 }, "LightYellow" }, { { 255, 255, 0 }, "yellow" }, { { 255, 215, 0 }, "gold" }, { { 238, 221, 130 }, "LightGoldenrod" }, { { 218, 165, 32 }, "goldenrod" }, { { 184, 134, 11 }, "DarkGoldenrod" }, { { 188, 143, 143 }, "RosyBrown" }, { { 205, 92, 92 }, "IndianRed" }, { { 139, 69, 19 }, "SaddleBrown" }, { { 160, 82, 45 }, "sienna" }, { { 205, 133, 63 }, "peru" }, { { 222, 184, 135 }, "burlywood" }, { { 245, 245, 220 }, "beige" }, { { 245, 222, 179 }, "wheat" }, { { 244, 164, 96 }, "SandyBrown" }, { { 210, 180, 140 }, "tan" }, { { 210, 105, 30 }, "chocolate" }, { { 178, 34, 34 }, "firebrick" }, { { 165, 42, 42 }, "brown" }, { { 233, 150, 122 }, "DarkSalmon" }, { { 250, 128, 114 }, "salmon" }, { { 255, 160, 122 }, "LightSalmon" }, { { 255, 165, 0 }, "orange" }, { { 255, 140, 0 }, "DarkOrange" }, { { 255, 127, 80 }, "coral" }, { { 240, 128, 128 }, "LightCoral" }, { { 255, 99, 71 }, "tomato" }, { { 255, 69, 0 }, "OrangeRed" }, { { 255, 0, 0 }, "red" }, { { 255, 105, 180 }, "HotPink" }, { { 255, 20, 147 }, "DeepPink" }, { { 255, 192, 203 }, "pink" }, { { 255, 182, 193 }, "LightPink" }, { { 219, 112, 147 }, "PaleVioletRed" }, { { 176, 48, 96 }, "maroon" }, { { 176, 48, 96 }, "X11Maroon" }, { { 128, 0, 0 }, "WebMaroon" }, { { 199, 21, 133 }, "MediumVioletRed" }, { { 208, 32, 144 }, "VioletRed" }, { { 255, 0, 255 }, "magenta" }, { { 255, 0, 255 }, "fuchsia" }, { { 238, 130, 238 }, "violet" }, { { 221, 160, 221 }, "plum" }, { { 218, 112, 214 }, "orchid" }, { { 186, 85, 211 }, "MediumOrchid" }, { { 153, 50, 204 }, "DarkOrchid" }, { { 148, 0, 211 }, "DarkViolet" }, { { 138, 43, 226 }, "BlueViolet" }, { { 160, 32, 240 }, "purple" }, { { 160, 32, 240 }, "X11Purple" }, { { 128, 0, 128 }, "WebPurple" }, { { 147, 112, 219 }, "MediumPurple" }, { { 216, 191, 216 }, "thistle" }, { { 255, 250, 250 }, "snow1" }, { { 238, 233, 233 }, "snow2" }, { { 205, 201, 201 }, "snow3" }, { { 139, 137, 137 }, "snow4" }, { { 255, 245, 238 }, "seashell1" }, { { 238, 229, 222 }, "seashell2" }, { { 205, 197, 191 }, "seashell3" }, { { 139, 134, 130 }, "seashell4" }, { { 255, 239, 219 }, "AntiqueWhite1" }, { { 238, 223, 204 }, "AntiqueWhite2" }, { { 205, 192, 176 }, "AntiqueWhite3" }, { { 139, 131, 120 }, "AntiqueWhite4" }, { { 255, 228, 196 }, "bisque1" }, { { 238, 213, 183 }, "bisque2" }, { { 205, 183, 158 }, "bisque3" }, { { 139, 125, 107 }, "bisque4" }, { { 255, 218, 185 }, "PeachPuff1" }, { { 238, 203, 173 }, "PeachPuff2" }, { { 205, 175, 149 }, "PeachPuff3" }, { { 139, 119, 101 }, "PeachPuff4" }, { { 255, 222, 173 }, "NavajoWhite1" }, { { 238, 207, 161 }, "NavajoWhite2" }, { { 205, 179, 139 }, "NavajoWhite3" }, { { 139, 121, 94 }, "NavajoWhite4" }, { { 255, 250, 205 }, "LemonChiffon1" }, { { 238, 233, 191 }, "LemonChiffon2" }, { { 205, 201, 165 }, "LemonChiffon3" }, { { 139, 137, 112 }, "LemonChiffon4" }, { { 255, 248, 220 }, "cornsilk1" }, { { 238, 232, 205 }, "cornsilk2" }, { { 205, 200, 177 }, "cornsilk3" }, { { 139, 136, 120 }, "cornsilk4" }, { { 255, 255, 240 }, "ivory1" }, { { 238, 238, 224 }, "ivory2" }, { { 205, 205, 193 }, "ivory3" }, { { 139, 139, 131 }, "ivory4" }, { { 240, 255, 240 }, "honeydew1" }, { { 224, 238, 224 }, "honeydew2" }, { { 193, 205, 193 }, "honeydew3" }, { { 131, 139, 131 }, "honeydew4" }, { { 255, 240, 245 }, "LavenderBlush1" }, { { 238, 224, 229 }, "LavenderBlush2" }, { { 205, 193, 197 }, "LavenderBlush3" }, { { 139, 131, 134 }, "LavenderBlush4" }, { { 255, 228, 225 }, "MistyRose1" }, { { 238, 213, 210 }, "MistyRose2" }, { { 205, 183, 181 }, "MistyRose3" }, { { 139, 125, 123 }, "MistyRose4" }, { { 240, 255, 255 }, "azure1" }, { { 224, 238, 238 }, "azure2" }, { { 193, 205, 205 }, "azure3" }, { { 131, 139, 139 }, "azure4" }, { { 131, 111, 255 }, "SlateBlue1" }, { { 122, 103, 238 }, "SlateBlue2" }, { { 105, 89, 205 }, "SlateBlue3" }, { { 71, 60, 139 }, "SlateBlue4" }, { { 72, 118, 255 }, "RoyalBlue1" }, { { 67, 110, 238 }, "RoyalBlue2" }, { { 58, 95, 205 }, "RoyalBlue3" }, { { 39, 64, 139 }, "RoyalBlue4" }, { { 0, 0, 255 }, "blue1" }, { { 0, 0, 238 }, "blue2" }, { { 0, 0, 205 }, "blue3" }, { { 0, 0, 139 }, "blue4" }, { { 30, 144, 255 }, "DodgerBlue1" }, { { 28, 134, 238 }, "DodgerBlue2" }, { { 24, 116, 205 }, "DodgerBlue3" }, { { 16, 78, 139 }, "DodgerBlue4" }, { { 99, 184, 255 }, "SteelBlue1" }, { { 92, 172, 238 }, "SteelBlue2" }, { { 79, 148, 205 }, "SteelBlue3" }, { { 54, 100, 139 }, "SteelBlue4" }, { { 0, 191, 255 }, "DeepSkyBlue1" }, { { 0, 178, 238 }, "DeepSkyBlue2" }, { { 0, 154, 205 }, "DeepSkyBlue3" }, { { 0, 104, 139 }, "DeepSkyBlue4" }, { { 135, 206, 255 }, "SkyBlue1" }, { { 126, 192, 238 }, "SkyBlue2" }, { { 108, 166, 205 }, "SkyBlue3" }, { { 74, 112, 139 }, "SkyBlue4" }, { { 176, 226, 255 }, "LightSkyBlue1" }, { { 164, 211, 238 }, "LightSkyBlue2" }, { { 141, 182, 205 }, "LightSkyBlue3" }, { { 96, 123, 139 }, "LightSkyBlue4" }, { { 198, 226, 255 }, "SlateGray1" }, { { 185, 211, 238 }, "SlateGray2" }, { { 159, 182, 205 }, "SlateGray3" }, { { 108, 123, 139 }, "SlateGray4" }, { { 202, 225, 255 }, "LightSteelBlue1" }, { { 188, 210, 238 }, "LightSteelBlue2" }, { { 162, 181, 205 }, "LightSteelBlue3" }, { { 110, 123, 139 }, "LightSteelBlue4" }, { { 191, 239, 255 }, "LightBlue1" }, { { 178, 223, 238 }, "LightBlue2" }, { { 154, 192, 205 }, "LightBlue3" }, { { 104, 131, 139 }, "LightBlue4" }, { { 224, 255, 255 }, "LightCyan1" }, { { 209, 238, 238 }, "LightCyan2" }, { { 180, 205, 205 }, "LightCyan3" }, { { 122, 139, 139 }, "LightCyan4" }, { { 187, 255, 255 }, "PaleTurquoise1" }, { { 174, 238, 238 }, "PaleTurquoise2" }, { { 150, 205, 205 }, "PaleTurquoise3" }, { { 102, 139, 139 }, "PaleTurquoise4" }, { { 152, 245, 255 }, "CadetBlue1" }, { { 142, 229, 238 }, "CadetBlue2" }, { { 122, 197, 205 }, "CadetBlue3" }, { { 83, 134, 139 }, "CadetBlue4" }, { { 0, 245, 255 }, "turquoise1" }, { { 0, 229, 238 }, "turquoise2" }, { { 0, 197, 205 }, "turquoise3" }, { { 0, 134, 139 }, "turquoise4" }, { { 0, 255, 255 }, "cyan1" }, { { 0, 238, 238 }, "cyan2" }, { { 0, 205, 205 }, "cyan3" }, { { 0, 139, 139 }, "cyan4" }, { { 151, 255, 255 }, "DarkSlateGray1" }, { { 141, 238, 238 }, "DarkSlateGray2" }, { { 121, 205, 205 }, "DarkSlateGray3" }, { { 82, 139, 139 }, "DarkSlateGray4" }, { { 127, 255, 212 }, "aquamarine1" }, { { 118, 238, 198 }, "aquamarine2" }, { { 102, 205, 170 }, "aquamarine3" }, { { 69, 139, 116 }, "aquamarine4" }, { { 193, 255, 193 }, "DarkSeaGreen1" }, { { 180, 238, 180 }, "DarkSeaGreen2" }, { { 155, 205, 155 }, "DarkSeaGreen3" }, { { 105, 139, 105 }, "DarkSeaGreen4" }, { { 84, 255, 159 }, "SeaGreen1" }, { { 78, 238, 148 }, "SeaGreen2" }, { { 67, 205, 128 }, "SeaGreen3" }, { { 46, 139, 87 }, "SeaGreen4" }, { { 154, 255, 154 }, "PaleGreen1" }, { { 144, 238, 144 }, "PaleGreen2" }, { { 124, 205, 124 }, "PaleGreen3" }, { { 84, 139, 84 }, "PaleGreen4" }, { { 0, 255, 127 }, "SpringGreen1" }, { { 0, 238, 118 }, "SpringGreen2" }, { { 0, 205, 102 }, "SpringGreen3" }, { { 0, 139, 69 }, "SpringGreen4" }, { { 0, 255, 0 }, "green1" }, { { 0, 238, 0 }, "green2" }, { { 0, 205, 0 }, "green3" }, { { 0, 139, 0 }, "green4" }, { { 127, 255, 0 }, "chartreuse1" }, { { 118, 238, 0 }, "chartreuse2" }, { { 102, 205, 0 }, "chartreuse3" }, { { 69, 139, 0 }, "chartreuse4" }, { { 192, 255, 62 }, "OliveDrab1" }, { { 179, 238, 58 }, "OliveDrab2" }, { { 154, 205, 50 }, "OliveDrab3" }, { { 105, 139, 34 }, "OliveDrab4" }, { { 202, 255, 112 }, "DarkOliveGreen1" }, { { 188, 238, 104 }, "DarkOliveGreen2" }, { { 162, 205, 90 }, "DarkOliveGreen3" }, { { 110, 139, 61 }, "DarkOliveGreen4" }, { { 255, 246, 143 }, "khaki1" }, { { 238, 230, 133 }, "khaki2" }, { { 205, 198, 115 }, "khaki3" }, { { 139, 134, 78 }, "khaki4" }, { { 255, 236, 139 }, "LightGoldenrod1" }, { { 238, 220, 130 }, "LightGoldenrod2" }, { { 205, 190, 112 }, "LightGoldenrod3" }, { { 139, 129, 76 }, "LightGoldenrod4" }, { { 255, 255, 224 }, "LightYellow1" }, { { 238, 238, 209 }, "LightYellow2" }, { { 205, 205, 180 }, "LightYellow3" }, { { 139, 139, 122 }, "LightYellow4" }, { { 255, 255, 0 }, "yellow1" }, { { 238, 238, 0 }, "yellow2" }, { { 205, 205, 0 }, "yellow3" }, { { 139, 139, 0 }, "yellow4" }, { { 255, 215, 0 }, "gold1" }, { { 238, 201, 0 }, "gold2" }, { { 205, 173, 0 }, "gold3" }, { { 139, 117, 0 }, "gold4" }, { { 255, 193, 37 }, "goldenrod1" }, { { 238, 180, 34 }, "goldenrod2" }, { { 205, 155, 29 }, "goldenrod3" }, { { 139, 105, 20 }, "goldenrod4" }, { { 255, 185, 15 }, "DarkGoldenrod1" }, { { 238, 173, 14 }, "DarkGoldenrod2" }, { { 205, 149, 12 }, "DarkGoldenrod3" }, { { 139, 101, 8 }, "DarkGoldenrod4" }, { { 255, 193, 193 }, "RosyBrown1" }, { { 238, 180, 180 }, "RosyBrown2" }, { { 205, 155, 155 }, "RosyBrown3" }, { { 139, 105, 105 }, "RosyBrown4" }, { { 255, 106, 106 }, "IndianRed1" }, { { 238, 99, 99 }, "IndianRed2" }, { { 205, 85, 85 }, "IndianRed3" }, { { 139, 58, 58 }, "IndianRed4" }, { { 255, 130, 71 }, "sienna1" }, { { 238, 121, 66 }, "sienna2" }, { { 205, 104, 57 }, "sienna3" }, { { 139, 71, 38 }, "sienna4" }, { { 255, 211, 155 }, "burlywood1" }, { { 238, 197, 145 }, "burlywood2" }, { { 205, 170, 125 }, "burlywood3" }, { { 139, 115, 85 }, "burlywood4" }, { { 255, 231, 186 }, "wheat1" }, { { 238, 216, 174 }, "wheat2" }, { { 205, 186, 150 }, "wheat3" }, { { 139, 126, 102 }, "wheat4" }, { { 255, 165, 79 }, "tan1" }, { { 238, 154, 73 }, "tan2" }, { { 205, 133, 63 }, "tan3" }, { { 139, 90, 43 }, "tan4" }, { { 255, 127, 36 }, "chocolate1" }, { { 238, 118, 33 }, "chocolate2" }, { { 205, 102, 29 }, "chocolate3" }, { { 139, 69, 19 }, "chocolate4" }, { { 255, 48, 48 }, "firebrick1" }, { { 238, 44, 44 }, "firebrick2" }, { { 205, 38, 38 }, "firebrick3" }, { { 139, 26, 26 }, "firebrick4" }, { { 255, 64, 64 }, "brown1" }, { { 238, 59, 59 }, "brown2" }, { { 205, 51, 51 }, "brown3" }, { { 139, 35, 35 }, "brown4" }, { { 255, 140, 105 }, "salmon1" }, { { 238, 130, 98 }, "salmon2" }, { { 205, 112, 84 }, "salmon3" }, { { 139, 76, 57 }, "salmon4" }, { { 255, 160, 122 }, "LightSalmon1" }, { { 238, 149, 114 }, "LightSalmon2" }, { { 205, 129, 98 }, "LightSalmon3" }, { { 139, 87, 66 }, "LightSalmon4" }, { { 255, 165, 0 }, "orange1" }, { { 238, 154, 0 }, "orange2" }, { { 205, 133, 0 }, "orange3" }, { { 139, 90, 0 }, "orange4" }, { { 255, 127, 0 }, "DarkOrange1" }, { { 238, 118, 0 }, "DarkOrange2" }, { { 205, 102, 0 }, "DarkOrange3" }, { { 139, 69, 0 }, "DarkOrange4" }, { { 255, 114, 86 }, "coral1" }, { { 238, 106, 80 }, "coral2" }, { { 205, 91, 69 }, "coral3" }, { { 139, 62, 47 }, "coral4" }, { { 255, 99, 71 }, "tomato1" }, { { 238, 92, 66 }, "tomato2" }, { { 205, 79, 57 }, "tomato3" }, { { 139, 54, 38 }, "tomato4" }, { { 255, 69, 0 }, "OrangeRed1" }, { { 238, 64, 0 }, "OrangeRed2" }, { { 205, 55, 0 }, "OrangeRed3" }, { { 139, 37, 0 }, "OrangeRed4" }, { { 255, 0, 0 }, "red1" }, { { 238, 0, 0 }, "red2" }, { { 205, 0, 0 }, "red3" }, { { 139, 0, 0 }, "red4" }, { { 255, 20, 147 }, "DeepPink1" }, { { 238, 18, 137 }, "DeepPink2" }, { { 205, 16, 118 }, "DeepPink3" }, { { 139, 10, 80 }, "DeepPink4" }, { { 255, 110, 180 }, "HotPink1" }, { { 238, 106, 167 }, "HotPink2" }, { { 205, 96, 144 }, "HotPink3" }, { { 139, 58, 98 }, "HotPink4" }, { { 255, 181, 197 }, "pink1" }, { { 238, 169, 184 }, "pink2" }, { { 205, 145, 158 }, "pink3" }, { { 139, 99, 108 }, "pink4" }, { { 255, 174, 185 }, "LightPink1" }, { { 238, 162, 173 }, "LightPink2" }, { { 205, 140, 149 }, "LightPink3" }, { { 139, 95, 101 }, "LightPink4" }, { { 255, 130, 171 }, "PaleVioletRed1" }, { { 238, 121, 159 }, "PaleVioletRed2" }, { { 205, 104, 137 }, "PaleVioletRed3" }, { { 139, 71, 93 }, "PaleVioletRed4" }, { { 255, 52, 179 }, "maroon1" }, { { 238, 48, 167 }, "maroon2" }, { { 205, 41, 144 }, "maroon3" }, { { 139, 28, 98 }, "maroon4" }, { { 255, 62, 150 }, "VioletRed1" }, { { 238, 58, 140 }, "VioletRed2" }, { { 205, 50, 120 }, "VioletRed3" }, { { 139, 34, 82 }, "VioletRed4" }, { { 255, 0, 255 }, "magenta1" }, { { 238, 0, 238 }, "magenta2" }, { { 205, 0, 205 }, "magenta3" }, { { 139, 0, 139 }, "magenta4" }, { { 255, 131, 250 }, "orchid1" }, { { 238, 122, 233 }, "orchid2" }, { { 205, 105, 201 }, "orchid3" }, { { 139, 71, 137 }, "orchid4" }, { { 255, 187, 255 }, "plum1" }, { { 238, 174, 238 }, "plum2" }, { { 205, 150, 205 }, "plum3" }, { { 139, 102, 139 }, "plum4" }, { { 224, 102, 255 }, "MediumOrchid1" }, { { 209, 95, 238 }, "MediumOrchid2" }, { { 180, 82, 205 }, "MediumOrchid3" }, { { 122, 55, 139 }, "MediumOrchid4" }, { { 191, 62, 255 }, "DarkOrchid1" }, { { 178, 58, 238 }, "DarkOrchid2" }, { { 154, 50, 205 }, "DarkOrchid3" }, { { 104, 34, 139 }, "DarkOrchid4" }, { { 155, 48, 255 }, "purple1" }, { { 145, 44, 238 }, "purple2" }, { { 125, 38, 205 }, "purple3" }, { { 85, 26, 139 }, "purple4" }, { { 171, 130, 255 }, "MediumPurple1" }, { { 159, 121, 238 }, "MediumPurple2" }, { { 137, 104, 205 }, "MediumPurple3" }, { { 93, 71, 139 }, "MediumPurple4" }, { { 255, 225, 255 }, "thistle1" }, { { 238, 210, 238 }, "thistle2" }, { { 205, 181, 205 }, "thistle3" }, { { 139, 123, 139 }, "thistle4" }, { { 0, 0, 0 }, "gray0" }, { { 0, 0, 0 }, "grey0" }, { { 3, 3, 3 }, "gray1" }, { { 3, 3, 3 }, "grey1" }, { { 5, 5, 5 }, "gray2" }, { { 5, 5, 5 }, "grey2" }, { { 8, 8, 8 }, "gray3" }, { { 8, 8, 8 }, "grey3" }, { { 10, 10, 10 }, "gray4" }, { { 10, 10, 10 }, "grey4" }, { { 13, 13, 13 }, "gray5" }, { { 13, 13, 13 }, "grey5" }, { { 15, 15, 15 }, "gray6" }, { { 15, 15, 15 }, "grey6" }, { { 18, 18, 18 }, "gray7" }, { { 18, 18, 18 }, "grey7" }, { { 20, 20, 20 }, "gray8" }, { { 20, 20, 20 }, "grey8" }, { { 23, 23, 23 }, "gray9" }, { { 23, 23, 23 }, "grey9" }, { { 26, 26, 26 }, "gray10" }, { { 26, 26, 26 }, "grey10" }, { { 28, 28, 28 }, "gray11" }, { { 28, 28, 28 }, "grey11" }, { { 31, 31, 31 }, "gray12" }, { { 31, 31, 31 }, "grey12" }, { { 33, 33, 33 }, "gray13" }, { { 33, 33, 33 }, "grey13" }, { { 36, 36, 36 }, "gray14" }, { { 36, 36, 36 }, "grey14" }, { { 38, 38, 38 }, "gray15" }, { { 38, 38, 38 }, "grey15" }, { { 41, 41, 41 }, "gray16" }, { { 41, 41, 41 }, "grey16" }, { { 43, 43, 43 }, "gray17" }, { { 43, 43, 43 }, "grey17" }, { { 46, 46, 46 }, "gray18" }, { { 46, 46, 46 }, "grey18" }, { { 48, 48, 48 }, "gray19" }, { { 48, 48, 48 }, "grey19" }, { { 51, 51, 51 }, "gray20" }, { { 51, 51, 51 }, "grey20" }, { { 54, 54, 54 }, "gray21" }, { { 54, 54, 54 }, "grey21" }, { { 56, 56, 56 }, "gray22" }, { { 56, 56, 56 }, "grey22" }, { { 59, 59, 59 }, "gray23" }, { { 59, 59, 59 }, "grey23" }, { { 61, 61, 61 }, "gray24" }, { { 61, 61, 61 }, "grey24" }, { { 64, 64, 64 }, "gray25" }, { { 64, 64, 64 }, "grey25" }, { { 66, 66, 66 }, "gray26" }, { { 66, 66, 66 }, "grey26" }, { { 69, 69, 69 }, "gray27" }, { { 69, 69, 69 }, "grey27" }, { { 71, 71, 71 }, "gray28" }, { { 71, 71, 71 }, "grey28" }, { { 74, 74, 74 }, "gray29" }, { { 74, 74, 74 }, "grey29" }, { { 77, 77, 77 }, "gray30" }, { { 77, 77, 77 }, "grey30" }, { { 79, 79, 79 }, "gray31" }, { { 79, 79, 79 }, "grey31" }, { { 82, 82, 82 }, "gray32" }, { { 82, 82, 82 }, "grey32" }, { { 84, 84, 84 }, "gray33" }, { { 84, 84, 84 }, "grey33" }, { { 87, 87, 87 }, "gray34" }, { { 87, 87, 87 }, "grey34" }, { { 89, 89, 89 }, "gray35" }, { { 89, 89, 89 }, "grey35" }, { { 92, 92, 92 }, "gray36" }, { { 92, 92, 92 }, "grey36" }, { { 94, 94, 94 }, "gray37" }, { { 94, 94, 94 }, "grey37" }, { { 97, 97, 97 }, "gray38" }, { { 97, 97, 97 }, "grey38" }, { { 99, 99, 99 }, "gray39" }, { { 99, 99, 99 }, "grey39" }, { { 102, 102, 102 }, "gray40" }, { { 102, 102, 102 }, "grey40" }, { { 105, 105, 105 }, "gray41" }, { { 105, 105, 105 }, "grey41" }, { { 107, 107, 107 }, "gray42" }, { { 107, 107, 107 }, "grey42" }, { { 110, 110, 110 }, "gray43" }, { { 110, 110, 110 }, "grey43" }, { { 112, 112, 112 }, "gray44" }, { { 112, 112, 112 }, "grey44" }, { { 115, 115, 115 }, "gray45" }, { { 115, 115, 115 }, "grey45" }, { { 117, 117, 117 }, "gray46" }, { { 117, 117, 117 }, "grey46" }, { { 120, 120, 120 }, "gray47" }, { { 120, 120, 120 }, "grey47" }, { { 122, 122, 122 }, "gray48" }, { { 122, 122, 122 }, "grey48" }, { { 125, 125, 125 }, "gray49" }, { { 125, 125, 125 }, "grey49" }, { { 127, 127, 127 }, "gray50" }, { { 127, 127, 127 }, "grey50" }, { { 130, 130, 130 }, "gray51" }, { { 130, 130, 130 }, "grey51" }, { { 133, 133, 133 }, "gray52" }, { { 133, 133, 133 }, "grey52" }, { { 135, 135, 135 }, "gray53" }, { { 135, 135, 135 }, "grey53" }, { { 138, 138, 138 }, "gray54" }, { { 138, 138, 138 }, "grey54" }, { { 140, 140, 140 }, "gray55" }, { { 140, 140, 140 }, "grey55" }, { { 143, 143, 143 }, "gray56" }, { { 143, 143, 143 }, "grey56" }, { { 145, 145, 145 }, "gray57" }, { { 145, 145, 145 }, "grey57" }, { { 148, 148, 148 }, "gray58" }, { { 148, 148, 148 }, "grey58" }, { { 150, 150, 150 }, "gray59" }, { { 150, 150, 150 }, "grey59" }, { { 153, 153, 153 }, "gray60" }, { { 153, 153, 153 }, "grey60" }, { { 156, 156, 156 }, "gray61" }, { { 156, 156, 156 }, "grey61" }, { { 158, 158, 158 }, "gray62" }, { { 158, 158, 158 }, "grey62" }, { { 161, 161, 161 }, "gray63" }, { { 161, 161, 161 }, "grey63" }, { { 163, 163, 163 }, "gray64" }, { { 163, 163, 163 }, "grey64" }, { { 166, 166, 166 }, "gray65" }, { { 166, 166, 166 }, "grey65" }, { { 168, 168, 168 }, "gray66" }, { { 168, 168, 168 }, "grey66" }, { { 171, 171, 171 }, "gray67" }, { { 171, 171, 171 }, "grey67" }, { { 173, 173, 173 }, "gray68" }, { { 173, 173, 173 }, "grey68" }, { { 176, 176, 176 }, "gray69" }, { { 176, 176, 176 }, "grey69" }, { { 179, 179, 179 }, "gray70" }, { { 179, 179, 179 }, "grey70" }, { { 181, 181, 181 }, "gray71" }, { { 181, 181, 181 }, "grey71" }, { { 184, 184, 184 }, "gray72" }, { { 184, 184, 184 }, "grey72" }, { { 186, 186, 186 }, "gray73" }, { { 186, 186, 186 }, "grey73" }, { { 189, 189, 189 }, "gray74" }, { { 189, 189, 189 }, "grey74" }, { { 191, 191, 191 }, "gray75" }, { { 191, 191, 191 }, "grey75" }, { { 194, 194, 194 }, "gray76" }, { { 194, 194, 194 }, "grey76" }, { { 196, 196, 196 }, "gray77" }, { { 196, 196, 196 }, "grey77" }, { { 199, 199, 199 }, "gray78" }, { { 199, 199, 199 }, "grey78" }, { { 201, 201, 201 }, "gray79" }, { { 201, 201, 201 }, "grey79" }, { { 204, 204, 204 }, "gray80" }, { { 204, 204, 204 }, "grey80" }, { { 207, 207, 207 }, "gray81" }, { { 207, 207, 207 }, "grey81" }, { { 209, 209, 209 }, "gray82" }, { { 209, 209, 209 }, "grey82" }, { { 212, 212, 212 }, "gray83" }, { { 212, 212, 212 }, "grey83" }, { { 214, 214, 214 }, "gray84" }, { { 214, 214, 214 }, "grey84" }, { { 217, 217, 217 }, "gray85" }, { { 217, 217, 217 }, "grey85" }, { { 219, 219, 219 }, "gray86" }, { { 219, 219, 219 }, "grey86" }, { { 222, 222, 222 }, "gray87" }, { { 222, 222, 222 }, "grey87" }, { { 224, 224, 224 }, "gray88" }, { { 224, 224, 224 }, "grey88" }, { { 227, 227, 227 }, "gray89" }, { { 227, 227, 227 }, "grey89" }, { { 229, 229, 229 }, "gray90" }, { { 229, 229, 229 }, "grey90" }, { { 232, 232, 232 }, "gray91" }, { { 232, 232, 232 }, "grey91" }, { { 235, 235, 235 }, "gray92" }, { { 235, 235, 235 }, "grey92" }, { { 237, 237, 237 }, "gray93" }, { { 237, 237, 237 }, "grey93" }, { { 240, 240, 240 }, "gray94" }, { { 240, 240, 240 }, "grey94" }, { { 242, 242, 242 }, "gray95" }, { { 242, 242, 242 }, "grey95" }, { { 245, 245, 245 }, "gray96" }, { { 245, 245, 245 }, "grey96" }, { { 247, 247, 247 }, "gray97" }, { { 247, 247, 247 }, "grey97" }, { { 250, 250, 250 }, "gray98" }, { { 250, 250, 250 }, "grey98" }, { { 252, 252, 252 }, "gray99" }, { { 252, 252, 252 }, "grey99" }, { { 255, 255, 255 }, "gray100" }, { { 255, 255, 255 }, "grey100" }, { { 169, 169, 169 }, "DarkGrey" }, { { 169, 169, 169 }, "DarkGray" }, { { 0, 0, 139 }, "DarkBlue" }, { { 0, 139, 139 }, "DarkCyan" }, { { 139, 0, 139 }, "DarkMagenta" }, { { 139, 0, 0 }, "DarkRed" }, { { 144, 238, 144 }, "LightGreen" }, { { 220, 20, 60 }, "crimson" }, { { 75, 0, 130 }, "indigo" }, { { 128, 128, 0 }, "olive" }, { { 102, 51, 153 }, "RebeccaPurple" }, { { 192, 192, 192 }, "silver" }, { { 0, 128, 128 }, "teal" }, { { 0, 0, 0 }, NULL } }; /* Eliminate spaces from input name */ static gchar * mangle_name (const gchar *name) { gchar *mangled_name; const gchar *p0; gchar *p1; mangled_name = g_malloc (strlen (name) + 1); for (p0 = name, p1 = mangled_name; *p0; p0++) { gchar c = *p0; if (c == ' ') continue; *(p1++) = c; } *p1 = '\0'; return mangled_name; } const NamedColor * find_color_by_name (const gchar *name) { const NamedColor *named_color; gchar *mangled_name; mangled_name = mangle_name (name); for (named_color = named_colors; named_color->name; named_color++) { if (!g_ascii_strcasecmp (mangled_name, named_color->name)) break; } if (!named_color->name) named_color = NULL; g_free (mangled_name); return named_color; } chafa-1.8.0/tools/chafa/named-colors.h000066400000000000000000000021441411352071600175210ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __NAMED_COLORS_H__ #define __NAMED_COLORS_H__ #include G_BEGIN_DECLS typedef struct { guint8 color [3]; const gchar *name; } NamedColor; const NamedColor *find_color_by_name (const gchar *name); G_END_DECLS #endif /* __NAMED_COLORS_H__ */ chafa-1.8.0/tools/chafa/xwd-loader.c000066400000000000000000000212051411352071600171760ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "xwd-loader.h" #define DEBUG(x) typedef struct { guint32 header_size; /* Size of the header in bytes */ guint32 file_version; /* X11WD file version (always 07h) */ guint32 pixmap_format; /* Pixmap format */ guint32 pixmap_depth; /* Pixmap depth in pixels */ guint32 pixmap_width; /* Pixmap width in pixels */ guint32 pixmap_height; /* Pixmap height in pixels */ guint32 x_offset; /* Bitmap X offset */ guint32 byte_order; /* Byte order of image data */ guint32 bitmap_unit; /* Bitmap base data size */ guint32 bitmap_bit_order; /* Bit-order of image data */ guint32 bitmap_pad; /* Bitmap scan-line pad*/ guint32 bits_per_pixel; /* Bits per pixel */ guint32 bytes_per_line; /* Bytes per scan-line */ guint32 visual_class; /* Class of the image */ guint32 red_mask; /* Red mask */ guint32 green_mask; /* Green mask */ guint32 blue_mask; /* Blue mask */ guint32 bits_per_rgb; /* Size of each color mask in bits */ guint32 n_colors; /* Number of colors in image */ guint32 color_map_entries; /* Number of entries in color map */ guint32 window_width; /* Window width */ guint32 window_height; /* Window height */ gint32 window_x; /* Window upper left X coordinate */ gint32 window_y; /* Window upper left Y coordinate */ guint32 window_border_width; /* Window border width */ } XwdHeader; typedef struct { guint32 pixel; guint16 red; guint16 green; guint16 blue; guint8 flags; guint8 pad; } XwdColor; struct XwdLoader { FileMapping *mapping; gconstpointer file_data; gconstpointer image_data; gsize file_data_len; XwdHeader header; }; DEBUG ( static void dump_header (XwdHeader *header) { g_printerr ("Header size: %u\n" "File version: %u\n" "Pixmap format: %u\n" "Pixmap depth: %u\n" "Pixmap width: %u\n" "Pixmap height: %u\n" "X offset: %u\n" "Byte order: %u\n" "Bitmap unit: %u\n" "Bitmap bit order: %u\n" "Bitmap pad: %u\n" "Bits per pixel: %u\n" "Bytes per line: %u\n" "Visual class: %u\n" "Red mask: %u\n" "Green mask: %u\n" "Blue mask: %u\n" "Bits per RGB: %u\n" "Number of colors: %u\n" "Color map entries: %u\n" "Window width: %u\n" "Window height: %u\n" "Window X: %d\n" "Window Y: %d\n" "Window border width: %u\n---\n", header->header_size, header->file_version, header->pixmap_format, header->pixmap_depth, header->pixmap_width, header->pixmap_height, header->x_offset, header->byte_order, header->bitmap_unit, header->bitmap_bit_order, header->bitmap_pad, header->bits_per_pixel, header->bytes_per_line, header->visual_class, header->red_mask, header->green_mask, header->blue_mask, header->bits_per_rgb, header->n_colors, header->color_map_entries, header->window_width, header->window_height, header->window_x, header->window_y, header->window_border_width); } ) static ChafaPixelType compute_pixel_type (XwdLoader *loader) { XwdHeader *h = &loader->header; if (h->bits_per_pixel == 24) { if (h->byte_order == 0) return CHAFA_PIXEL_BGR8; else return CHAFA_PIXEL_RGB8; } if (h->bits_per_pixel == 32) { if (h->byte_order == 0) return CHAFA_PIXEL_BGRA8_PREMULTIPLIED; else return CHAFA_PIXEL_ARGB8_PREMULTIPLIED; } return CHAFA_PIXEL_MAX; } #define ASSERT_HEADER(x) if (!(x)) return FALSE static gboolean load_header (XwdLoader *loader) // gconstpointer in, gsize in_max_len, XwdHeader *header_out) { XwdHeader *h = &loader->header; XwdHeader in; const guint32 *p = (const guint32 *) ∈ if (!file_mapping_taste (loader->mapping, &in, 0, sizeof (in))) return FALSE; h->header_size = g_ntohl (*(p++)); h->file_version = g_ntohl (*(p++)); h->pixmap_format = g_ntohl (*(p++)); h->pixmap_depth = g_ntohl (*(p++)); h->pixmap_width = g_ntohl (*(p++)); h->pixmap_height = g_ntohl (*(p++)); h->x_offset = g_ntohl (*(p++)); h->byte_order = g_ntohl (*(p++)); h->bitmap_unit = g_ntohl (*(p++)); h->bitmap_bit_order = g_ntohl (*(p++)); h->bitmap_pad = g_ntohl (*(p++)); h->bits_per_pixel = g_ntohl (*(p++)); h->bytes_per_line = g_ntohl (*(p++)); h->visual_class = g_ntohl (*(p++)); h->red_mask = g_ntohl (*(p++)); h->green_mask = g_ntohl (*(p++)); h->blue_mask = g_ntohl (*(p++)); h->bits_per_rgb = g_ntohl (*(p++)); h->color_map_entries = g_ntohl (*(p++)); h->n_colors = g_ntohl (*(p++)); h->window_width = g_ntohl (*(p++)); h->window_height = g_ntohl (*(p++)); h->window_x = g_ntohl (*(p++)); h->window_y = g_ntohl (*(p++)); h->window_border_width = g_ntohl (*(p++)); /* Only support the most common/useful subset of XWD files out there; * namely, that corresponding to screen dumps from modern X.Org servers. */ ASSERT_HEADER (h->header_size >= sizeof (XwdHeader)); ASSERT_HEADER (h->file_version == 7); ASSERT_HEADER (h->pixmap_depth == 24); ASSERT_HEADER (h->bits_per_rgb == 8); ASSERT_HEADER (h->bytes_per_line >= h->pixmap_width * (h->bits_per_pixel / 8)); ASSERT_HEADER (compute_pixel_type (loader) < CHAFA_PIXEL_MAX); loader->file_data = file_mapping_get_data (loader->mapping, &loader->file_data_len); if (!loader->file_data) return FALSE; ASSERT_HEADER (loader->file_data_len >= h->header_size + h->n_colors * sizeof (XwdColor) + h->pixmap_height * h->bytes_per_line); loader->image_data = (const guint8 *) loader->file_data + h->header_size + h->n_colors * sizeof (XwdColor); return TRUE; } static XwdLoader * xwd_loader_new (void) { return g_new0 (XwdLoader, 1); } XwdLoader * xwd_loader_new_from_mapping (FileMapping *mapping) { XwdLoader *loader; g_return_val_if_fail (mapping != NULL, NULL); loader = xwd_loader_new (); loader->mapping = mapping; if (!load_header (loader)) { g_free (loader); return NULL; } DEBUG (dump_header (&loader->header)); return loader; } void xwd_loader_destroy (XwdLoader *loader) { if (loader->mapping) file_mapping_destroy (loader->mapping); g_free (loader); } gconstpointer xwd_loader_get_image_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); if (pixel_type_out) *pixel_type_out = compute_pixel_type (loader); if (width_out) *width_out = loader->header.pixmap_width; if (height_out) *height_out = loader->header.pixmap_height; if (rowstride_out) *rowstride_out = loader->header.bytes_per_line; return loader->image_data; } chafa-1.8.0/tools/chafa/xwd-loader.h000066400000000000000000000025011411352071600172010ustar00rootroot00000000000000/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2018-2021 Hans Petter Jansson * * This file is part of Chafa, a program that turns images into character art. * * Chafa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chafa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ #ifndef __XWD_LOADER_H__ #define __XWD_LOADER_H__ #include #include "file-mapping.h" G_BEGIN_DECLS typedef struct XwdLoader XwdLoader; XwdLoader *xwd_loader_new_from_mapping (FileMapping *mapping); void xwd_loader_destroy (XwdLoader *loader); gconstpointer xwd_loader_get_image_data (XwdLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out); G_END_DECLS #endif /* __XWD_LOADER_H__ */ chafa-1.8.0/tools/fontgen/000077500000000000000000000000001411352071600153625ustar00rootroot00000000000000chafa-1.8.0/tools/fontgen/Makefile.am000066400000000000000000000001051411352071600174120ustar00rootroot00000000000000EXTRA_DIST = \ README.md \ chafa8x8.py \ compare.py \ svg2ttf.py chafa-1.8.0/tools/fontgen/README.md000066400000000000000000000017201411352071600166410ustar00rootroot00000000000000Chafa8x8 Custom Block Font === ## Advantages of this custom font 1. One can read large fonts from images more easily. 2. Very good at dealing with some natural image textures. 3. Higher definition compared to block glyphs in standard Unicode. ## How to build this font 1. Prepare many images, and put them to a folder, e.g. `~/coco/*.jpg`. 2. Create dataset from those images. `./chafa8x8.py CreateDataset --glob "$HOME/coco/*.jpg"` 3. Run K-Means algorithm to find cluster center vectors. `./chafa8x8.py Clustering` 4. Post-process. Generate C code, SVG images and TTF font. `./chafa8x8.py GenA` ## Acknowledgement The core algorithm used to generate the glyphs is [K-Means](https://en.wikipedia.org/wiki/K-means_clustering), one of the clustering algorithms in the machine learning literature. You can generate a font by using some image dataset e.g. [MSCOCO](http://cocodataset.org) dataset (research-oriented). ## License Copyright (C) 2018 Mo Zhou, LGPLv3+ chafa-1.8.0/tools/fontgen/chafa8x8.py000077500000000000000000000172241411352071600173570ustar00rootroot00000000000000#!/usr/bin/env python3 # This script is written for Chafa in order to generate custom fonts. # Copyright (C) 2018 Mo Zhou # License: LGPLv3+ import os, sys import argparse import glob import json import time import random import numpy as np from tqdm import tqdm from PIL import Image from subprocess import call from sklearn.cluster import KMeans, MiniBatchKMeans from scipy.signal import convolve2d as conv2d # Preserved Unicode Plane for Private use, see wikipedia for detail. UNICODE_BASE = 0x100000 def bdump8x8(v: np.ndarray, *, clang=False, padding=0) -> str: ''' dump the 8x8 bitmap ''' assert(v.size == 64) dump = [] for (i, v) in enumerate(v): if i%8==0 and clang: dump.append(' '*padding + "\"") if v: dump.append('X') else: dump.append(' ' if clang else '.') if (i+1)%8==0 and clang: dump.append("\"") if (i+1)%8==0: dump.append('\n') return ''.join(dump) def mainCreateDataset(argv): ''' Generate dataset used for generating chafa8x8 font from MS-COCO dataset http://cocodataset.org/ ''' ag = argparse.ArgumentParser() ag.add_argument('--glob', type=str, default='~/coco/*.jpg') ag.add_argument('-Mc', type=int, default=16, help='num crops per image') ag.add_argument('-N', type=int, default=64, help='vector length') ag.add_argument('--save', type=str, default='chafa8x8.npz') ag = ag.parse_args(argv) if not os.path.exists(ag.save): # Glob images images = glob.glob(ag.glob, recursive=True) Mi = len(images) print(f'=> Found {Mi} images. Will sample {ag.Mc} vectors from each image') print(f' -> Dataset size M = {Mi*ag.Mc}') print(f' -> The resulting dataset takes {Mi*ag.Mc*ag.N/(1024**2)} MiB space.') # Sample Mi*Mc vectors as the dataset from those images print(f'=> Sampling vectors from every image ...') dataset = np.zeros((Mi*ag.Mc, ag.N), dtype=np.uint8) for i in tqdm(range(Mi)): image = random.choice(images) image = Image.open(image) width, height = image.size for c in range(ag.Mc): w = random.randrange(16, min(width, height//2, 48)) h = 2*w # ratio: h/w = 2/1 woff = random.randrange(0, width - w) hoff = random.randrange(0, height - h) box = (woff, hoff, woff+w, hoff+h) region = image.crop(box).convert('1') region = region.resize((8,8)) region = np.array(region).ravel() dataset[i*ag.Mc+c, :] = region # save the dataset as acche np.savez(ag.save, dataset=dataset) else: print(f'=> Dataset already generated: {ag.save}') def mainClustering(argv): ''' Run K-Means algorithm on the dataset. For large-scale training, MiniBatchKMeans will be much faster than KMeans. (This will still take some time if dataset is large enough) ''' ag = argparse.ArgumentParser() ag.add_argument('--dataset', type=str, default='chafa8x8.npz') ag.add_argument('--save', type=str, default='chafa8x8.raw.json') ag.add_argument('-C', type=int, default=5120, help='number of clusters') ag = ag.parse_args(argv) print(f'=> loading dataset from {ag.dataset}') dataset = np.load(ag.dataset)['dataset'] # Findout the cluster centers with KMeans print('=> Clustering ...') kmeans = MiniBatchKMeans(n_clusters=ag.C, init='k-means++', init_size=37*ag.C, batch_size=8*ag.C, compute_labels=False, verbose=True).fit(dataset) centers = (kmeans.cluster_centers_ >= 0.5).astype(np.uint8) # Save the result to JSON file centers = list(sorted([list(map(int, center)) for center in centers], key=lambda x:sum(x))) json.dump(centers, open(ag.save, 'w')) print(f'=> {ag.save}') def mainPostproc(argv): ''' Post-processing to make the glyphs "smoother" and do deduplication Default convolution kernel: Gaussian 3x3 ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, default='chafa8x8.raw.json') ag.add_argument('--save', type=str, default='chafa8x8.json') ag = ag.parse_args(argv) # load raw vectors centers = json.load(open(ag.json, 'r')) # [default kernel] Gaussian kernel (kernel size = 3) Kg = np.array([[1/16, 1/8, 1/16], [1/8, 1/4, 1/8], [1/16, 1/8, 1/16]]) # mean value (glyphs tends to have round corner instead of sharp ones) Km = np.ones((3,3))/9 # Select Kg as the default kernel K = Kg for (i, center) in enumerate(centers): center = np.array(center).reshape((8,8)) center = conv2d(center, K, 'same').ravel() centers[i] = (center >= 0.5) centers = np.unique(np.array(centers), axis=0) print(' -> number of centers:', centers.shape[0]) centers = list(sorted([list(map(int, center)) for center in centers], key=lambda x:sum(x))) json.dump(centers, open(ag.save, 'w')) print(f'=> Result saved to {ag.save}') def mainDump(argv): ''' Dump human-readable bitmaps from json file ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, required=True) ag = ag.parse_args(argv) centers = json.load(open(ag.json, 'r')) for (i, center) in enumerate(centers): center = np.array(center) print('________', i) print(bdump8x8(center)) def mainGenC(argv): ''' Generate C code ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, default='chafa8x8.json') ag.add_argument('--dst', type=str, default='chafa8x8.h') ag = ag.parse_args(argv) f = open(ag.dst, 'w') f.write(f'/* Auto-generated by {os.path.basename(__file__)}\n') f.write(f' * Version: {time.ctime()}\n') f.write(f' */\n') centers = json.load(open(ag.json, 'r')) for (i, center) in enumerate(centers): code = UNICODE_BASE + i comment = (1 not in center) if comment: f.write('#if 0\n') f.write('{\n') f.write(f' /* Chafa8x8 Font, ID: {i}, Unicode: 0x{code:x} */\n') f.write(f' CHAFA_SYMBOL_TAG_CUSTOM,\n') f.write(f' 0x{code:x},\n') f.write(bdump8x8(np.array(center), clang=True, padding=4)) f.write('},\n') if comment: f.write('#endif\n') f.close() def mainGenSVG(argv): ''' Generate SVG files ''' ag = argparse.ArgumentParser() ag.add_argument('--json', type=str, default='chafa8x8.json') ag.add_argument('--dir', type=str, default='chafa8x8_svg') ag = ag.parse_args(argv) if not os.path.exists(ag.dir): os.mkdir(ag.dir) centers = json.load(open(ag.json, 'r')) for (i, center) in enumerate(centers): f = open(os.path.join(ag.dir, f'{i}.svg'), 'w') f.write('''\ ''') template = '\n' center = np.array(center).reshape(8, 8).astype(np.uint8) indeces = np.argwhere(center > 0) for index in indeces: r, c = index f.write(template.format(w=62.5, h=127.5, x=0+c*62.5, y=-40+r*127.5)) f.write(''' \n''') f.close() def mainGenFont(argv): call(['python2.7', 'svg2ttf.py']) def mainGenA(argv): call(['./chafa8x8.py', 'Postproc']) call(['./chafa8x8.py', 'GenC']) call(['./chafa8x8.py', 'GenSVG']) call(['./chafa8x8.py', 'GenFont']) if __name__ == '__main__': eval(f'main{sys.argv[1]}')(sys.argv[2:]) chafa-1.8.0/tools/fontgen/compare.py000077500000000000000000000013041411352071600173630ustar00rootroot00000000000000#!/usr/bin/env python3.6 # (C) 2018 Mo Zhou # LGPLv3+ import glob import argparse import random from subprocess import call if __name__ == '__main__': ag = argparse.ArgumentParser() ag.add_argument('--glob', type=str, required=True) ag.add_argument('-n', type=int, default=32) ag = ag.parse_args() images = glob.glob(ag.glob, recursive=True) print(f'=> found {len(images)} images') for i in range(ag.n): im = random.choice(images) print('<<<<<< chafa: block') call(['chafa', '-s', '80x24', '--symbols', 'block', im]) print('------') call(['chafa', '-s', '80x24', '--symbols', 'custom', im]) print('>>>>>> chafa: symbols=custom') chafa-1.8.0/tools/fontgen/svg2ttf.py000077500000000000000000000015031411352071600173350ustar00rootroot00000000000000#!/usr/bin/python2.7 # (C) 2018 Mo Zhou # LGPLv3+ import fontforge as ff import json N = len(json.load(open('chafa8x8.json', 'r'))) try: font = ff.font() font.clear() font.copyright = "(C) 2018 Mo Zhou , MIT License" font.fontname = "Chafa8x8" font.familyname = "monospace" font.fullname = "Chafa8x8 block glyphs by K-Means for character art" font.version = "0a" font.encoding = 'Custom' print 'Number of glyphs:', N for i in range(N): glyph = font.createChar(0x100000 + i) glyph.importOutlines('chafa8x8_svg/%d.svg'%i) glyph.left_side_bearing = 0 glyph.right_side_bearing = 0 glyph.width = 0 glyph.vwidth = 0 #font.save('chafa8x8.sfd') font.generate('chafa8x8.ttf') font.close() except Exception as e: print e